Skip to content

Hard fault: memory access or instruction error found intermittently on RP2040 CAN Bus Feather #8643

Open
@0nix

Description

@0nix

CircuitPython version

8.2.7

Code/REPL

RCVRID = 0x001 #CHANGE THIS LINE
# RP2040 CAN Bus Feather

from time import sleep
import time
import board
import neopixel
from rainbowio import colorwheel
from digitalio import DigitalInOut, Pull
from adafruit_mcp2515.canio import Message, RemoteTransmissionRequest
from adafruit_mcp2515 import MCP2515 as CAN
import adafruit_mcp2515.canio as canio

COLORS = {
    0x0: (0,0,0,0),
    0x1: (255,0,0,0),
	0x2: (0,255,0,0),
	0x3: (0,0,255,0),
	0x4: (255,255,0,0),
	0x5: (0,255,255,0),     
	0x6: (255,0,255,0),
	0x7: (0,0,0,255),
	0x8: (255,128,0,0),
	0x9: (0,128,255,0),
	0xA: (128,0,255,0), 
	0xB: (255,0,128,0), 
	0xC: (0,128,0,0),
	0xD: (100,149,237,0),
	0xE: (139,0,0,0),
	0xF: (218,165,32,0),
	0x10: (32,178,270,0),
	0x11: (255,69,0,0),
	0x12: (0,255,255,128),
    0x13: 0x13,  #Rainbow 		= 0x13, 
    0x14: 0x14 #FastRainbow	= 0x14,
    #Skip			= 0xA0
} 
GREEN = (0,255,0)
RED = (255,0,0)
WHITE = (255,255,255)
NONE = (0,0,0)

LLPin = DigitalInOut(board.D5)
ULPin = DigitalInOut(board.D6)
URPin = DigitalInOut(board.D9)
LRPin = DigitalInOut(board.D10)

LL_LightOn = COLORS[0x3]
LL_LightOff = COLORS[0x0]
UL_LightOn = COLORS[0x3]
UL_LightOff = COLORS[0x0]
UR_LightOn = COLORS[0x3]
UR_LightOff = COLORS[0x0]
LR_LightOn = COLORS[0x3]
LR_LightOff = COLORS[0x0]

RainbowColor = 0x0
FastRainbowSet =  ((255, 0, 0, 0), (255, 40, 0, 0), (255, 150, 0,0), (0, 255, 0,0), (0, 0, 255, 0), (180, 0, 255,0))

LightsPin = board.D11

Lights = neopixel.NeoPixel(LightsPin, 4, brightness = 0.5, auto_write = True, pixel_order = neopixel.RGBW)
statusPixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
statusPixel.brightness = 0.1

try:
    cs = DigitalInOut(board.CAN_CS)
    cs.switch_to_output()
    spi = board.SPI()

    can_bus = CAN(
        spi, cs, loopback=False, silent=False
    )  # use loopback to test without another device
except Exception as e:
    print(e)
    statusPixel.fill((255,0,255))

LLPin.switch_to_input(pull=Pull.UP)
ULPin.switch_to_input(pull=Pull.UP)
URPin.switch_to_input(pull=Pull.UP)
LRPin.switch_to_input(pull=Pull.UP)

DEBUG = False
HAS_INITED = False
MSGTIMEOUT = 1

def can_send_msg(sender_id,data):
    message = canio.Message(id=sender_id, data=data)
    can_bus.send(message)
    if (can_bus.transmit_error_count > 0) or (can_bus.receive_error_count > 0):
        print(
            f"🔴 MSG tx_err={can_bus.transmit_error_count} rx_err={can_bus.receive_error_count}"
        )
    return

def can_recv_msg():
        msg = listener.receive()
        if (can_bus.transmit_error_count > 0) or (can_bus.receive_error_count > 0):
            print(f"🔴 MSG tx_err={can_bus.transmit_error_count} rx_err={can_bus.receive_error_count}")
        if msg is None:
            if DEBUG: print("🟡 MSG not received within timeout")
            return
            #continue
        if isinstance(msg, canio.Message):
            print(f"RCCV = {msg.data}")
            if(msg.id == RCVRID + 0X100):
                try:
                    global LL_LightOn
                    global LL_LightOff
                    global UL_LightOn
                    global UL_LightOff
                    global UR_LightOn
                    global UR_LightOff
                    global LR_LightOn
                    global LR_LightOff

                    lights = list(msg.data)
                    print(f"rcv = LIGHTS {lights}")
                    for idx, x in enumerate(lights):
                        if lights[idx] == 0xA0:
                            continue
                        if idx == 0:
                            LL_LightOn      = COLORS[lights[0]]
                        if idx == 1:
                            LL_LightOff     = COLORS[lights[1]]
                        if idx == 2:
                            UL_LightOn      = COLORS[lights[2]]
                        if idx == 3:
                            UL_LightOff     = COLORS[lights[3]]
                        if idx == 4:
                            UR_LightOn      = COLORS[lights[4]]
                        if idx == 5:
                            UR_LightOff     = COLORS[lights[5]]
                        if idx == 6:
                            LR_LightOn      = COLORS[lights[6]]
                        if idx == 7:
                            LR_LightOff     = COLORS[lights[7]]
                except Exception as e:
                    print(e)
        else:
            if DEBUG: print("🟡 not a canio message")


def setLight(lightArr,idx,colorRef):

    if(colorRef == 0x13): #Rainbow
        lightArr[idx] = RainbowColor
        return
    if(colorRef == 0x14): #Fast Rainbow
        lightArr[idx] = FastRainbowColor
        return
    if(lightArr[idx] != colorRef):
        lightArr[idx] = colorRef

matcher = canio.Match(address=(RCVRID + 0X100))
listener = can_bus.listen(timeout=MSGTIMEOUT, matches=[matcher])

old_bus_state = None
statusPixel.fill((0,0,0))
global timestamp
timestamp = time.monotonic()
lastTick = -1
lastFrame = 0
lastFastTick = -1
lastFastFrame = 0
readInterval = 1
fastReadInterval = 0.083
lastMsg = ""
while True:
    statusPixel.fill((0,0,0))
    now = time.monotonic()
    bus_state = can_bus.state
    if bus_state != old_bus_state:
                if bus_state == 0:
                    print(f"🟣 BUS state changed to ACTIVE")
                if bus_state == 1:
                    print(f"🟣 BUS state changed to WARNING -- MODERATE ERRORS")
                if bus_state == 2:
                    print(f"🟣 BUS state changed to PASSIVE -- TOO MANY ERRORS")
                if bus_state == 3:
                    print(f"🟣 BUS state changed to OFF -- NO ACK ON PACKETS")
                old_bus_state = bus_state
    if bus_state == 0 and HAS_INITED == False:
        Lights.fill((0,0,0,0))
        Lights[0] = (255,0,0)
        Lights[1] = (0,255,0)
        Lights[2] = (0,0,255)
        Lights[3] = (255,255,255)
        HAS_INITED = True
        sleep(2)
        Lights[0] = (0,0,0)
        Lights[1] = (0,0,0)
        Lights[2] = (0,0,0)
        Lights[3] = (0,0,0)

    if bus_state == 1:
        statusPixel.fill((255,255,0))
    if bus_state == 2:
        statusPixel.fill((255,0,0))
    if bus_state == 3:
        statusPixel.fill((255,255,255))

    RainbowColor = colorwheel((time.monotonic()*50)%255)
    
    if now >= lastFastTick + fastReadInterval:
        lastFastTick = now
        if(lastFastFrame < len(FastRainbowSet) - 1):
            lastFastFrame += 1
        else:
            lastFastFrame = 0

        FastRainbowColor = FastRainbowSet[lastFastFrame]

    msg = "="
    
    if(LLPin.value == False): # D5 Pin
        msg += "a"
        setLight(Lights,0,LL_LightOn)
    else:
        msg += "x"
        setLight(Lights,0,LL_LightOff)
    if(ULPin.value == False): # D6 Pin
        msg += "b"
        setLight(Lights,1,UL_LightOn)
    else:
        msg += "x"
        setLight(Lights,1,UL_LightOff)
    if(URPin.value == False): # D9 Pin
        msg += "c"
        setLight(Lights,2,UR_LightOn)
    else:
        msg += "x"
        setLight(Lights,2,UR_LightOff)
    if(LRPin.value == False): #D10 Pin
        msg += "d"
        setLight(Lights,3,LR_LightOn)
    else:
        msg += "x"
        setLight(Lights,3,LR_LightOff)
    
    try:
        if DEBUG: print(f"MSG avail={listener.in_waiting()} unread={can_bus.unread_message_count}")
        if bus_state == 0 or bus_state == 1:
            if(can_bus.unread_message_count > 0):

                print('process can')
                lastRead = now
                can_recv_msg()

            if(lastMsg != msg):
                can_send_msg(RCVRID,bytes(msg,'ascii'))
                
                lastMsg = msg
        else:
            if (msg == "=xxxx"):
                pass
            else:
               print(msg)
               statusPixel.fill((0,0,255))
            
    except Exception as e:
        print(e)
        statusPixel.fill((255,255,0))

Behavior

Code runs normally until arbitrarily, it crashes, stops updating the NeoPIxel strip based on pin status, and presents the error You are in safe mode because: CircuitPython core code crashed hard. Whoops! Hard fault: memory access or instruction error.

Description

No response

Additional information

This bug report was submitted under advisement of Dan Halbert on the Discord server.

I understand the error to be a memory access or allocation error.

I don't know why this happens and how to get it to appear consisntely. I've got it running in 50+ different instances of the same board with the same CircuitPython version and it never happens on some boards, and on others it just happens by leaving the board to be idle.

I suspect that my allocation of strings that constitute the message to be written to the CAN Bus to be culprit and have ran a modified version of the code that uses a list of characters instead of writing a string.

# previous code here
msgSeg = ['=','x','x','x','x']

while True:
# some code here
    if(LLPin.value == False): # D5 Pin
        msgSeg[1]= "a"
        setLight(Lights,0,LL_LightOn)
    else:
        msgSeg[1]= "x"
        setLight(Lights,0,LL_LightOff)
    if(ULPin.value == False): # D6 Pin
        msgSeg[2]= "b"
        setLight(Lights,1,UL_LightOn)
    else:
        msgSeg[2]= "x"
        setLight(Lights,1,UL_LightOff)
    if(URPin.value == False): # D9 Pin
        msgSeg[3]= "c"
        setLight(Lights,2,UR_LightOn)
    else:
        msgSeg[3]= "x"
        setLight(Lights,2,UR_LightOff)
    if(LRPin.value == False): #D10 Pin
        msgSeg[4]= "d"
        setLight(Lights,3,LR_LightOn)
    else:
        msgSeg[4]= "x"
        setLight(Lights,3,LR_LightOff)
    msg = ''.join(msgSeg)
    try:
        if DEBUG: print(f"MSG avail={listener.in_waiting()} unread={can_bus.unread_message_count}")
        if bus_state == 0 or bus_state == 1:
            if(can_bus.unread_message_count > 0):

                lastRead = now
                can_recv_msg()

            if(lastMsg != msg):
                can_send_msg(RCVRID,bytes(msg,'ascii'))
                
                lastMsg = msg
        else:
            if (msg == blankMsg):
                pass
            else:
               #print(msg)
               statusPixel.fill((0,0,255))
            
    except Exception as e:
        print(e)
        statusPixel.fill((255,255,0))

# rest of code

This has not been as consistently tested as the previous string approach, but it is showing some promise. This does not explain how some boards are able to deal with this issue just fine while others do not.

Furthermore, the boards may be exposed to a higher chance of ESD since they are in proximity to acrylic boards where people are expected to step on and slide. While the acrylic does not come into direct contact with the boards, I have noticed they accumulate static electricity so this may also play a factor.

The code also does not sleep between while loops, would this be a factor?

Any and all help is tremendously appreciated.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions