SPI init - Open MV crash or disconnect the card

Hi everyone,

I am trying to connect thought SPI an LCD screen and a CAN module to an Arduino Nicla Vision. But the connexion while running, the code seems stop (no flags printed), and openmv disconnect the arduino or simply crash !

I am working on the CAN module with an MCP2515, I modified some code I found .

First I used multiple files but either openmv copy them on the Nicla and don’t find them causing error (!?) or simply crash.

So I put everything in a .py file and then it seems run but without printing flags.

This is the code :

"""================
=====IMPORT =====
==================="""

from ustruct import pack, unpack, pack_into
from pyb import Pin, SPI #from machine import SPI
import utime
from micropython import const

"""================
===== CLASSES =====
==================="""

# SPI instructions:
class mcp_spi():
    RESET = const(0xC0)
    READ = const(0x03)
    READ_RX_BUFFER_0 = const(0x90)
    READ_RX_BUFFER_1 = const(0x94)
    WRITE = const(0x02)
    WRITE_TX_BUFFER_0 = const(0x40)
    WRITE_TX_BUFFER_1 = const(0x42)
    WRITE_TX_BUFFER_2 = const(0x44)
    READ_STATUS = const(0xA0)
    RX_STATUS = const(0xB0)
    BIT_MODIFY = const(0x05)
    RTS0 = const(0x81)
    RTS1 = const(0x82)
    RTS2 = const(0x84)

# operating modes
class op_mode():
    op_mask = const(0xE0)
    normal = const(0x00)
    sleep = const(0x20)
    loopback = const(0x40)
    listen = const(0x60)
    configuration = const(0x80)

# address to registers
class address():
    CANCTRL = const(0x0F)
    CANSTAT = const(0x0E)
    CNF1 = const(0x2A)
    CNF2 = const(0x29)
    CNF3 = const(0x28)

"""============
===== CAN =====
==============="""

class CAN:
    def __init__(self):
        self.spi = SPI(4, SPI.MASTER, baudrate=int(1e6), polarity=0, phase=0)
        self.cs = Pin("PA10", Pin.OUT)
        self.msgbuf = bytearray(13) # for loading and retrieving messages
        self.buf8 = bytearray(1)    # for reading adresses

    # SPI helper methods
    def spi_start(self):
        self.spi.init(SPI.MASTER, baudrate=int(1e6), polarity=0, phase=0, bits=8, firstbit=SPI.MSB)
        self.cs.off()

    def spi_end(self):
        self.cs.on()
        self.spi.deinit()

    def write_register(self, address, data):
        self.spi_start()
        self.spi.write(pack("<B", mcp_spi.WRITE))
        self.spi.write(pack("<B", address))
        for byte in data:
            self.spi.write(pack("<B", byte))
        self.spi_end()

    def bitmodify_register(self, address, mask, data):
        self.spi_start()
        self.spi.write(pack("<B", mcp_spi.BIT_MODIFY))
        self.spi.write(pack("<B", address))
        self.spi.write(pack("<B", mask))
        self.spi.write(pack("<B", data))
        self.spi_end()

    def read_register(self, address):
        self.spi_start()
        self.spi.write(pack("<B", mcp_spi.READ))
        self.spi.write(pack("<B", address))
        self.spi.readinto(self.buf8)
        self.spi_end()

    def reset(self):
        self.spi_start()
        self.spi.write(pack("<B", mcp_spi.RESET))
        self.spi_end()

    def read_rx_buffer(self, buffer=0):
        self.spi_start()
        if buffer == 0:
            self.spi.write(pack("<B", mcp_spi.READ_RX_BUFFER_0))
        if buffer == 1:
            self.spi.write(pack("<B", mcp_spi.READ_RX_BUFFER_1))
        self.spi.readinto(self.msgbuf)
        self.spi_end()

    def load_tx_buffer(self, buffer): #Write provided load whatever is in msgbuf to selected TX buffer
        self.spi_start()
        if buffer == 0:
            self.spi.write(pack("<B", mcp_spi.WRITE_TX_BUFFER_0))

        if buffer == 1:
            self.spi.write(pack("<B", mcp_spi.WRITE_TX_BUFFER_1))

        if buffer == 2:
            self.spi.write(pack("<B", mcp_spi.WRITE_TX_BUFFER_2))
        self.spi.write(self.msgbuf)
        self.spi_end()

    def request_to_send(self, buffer=0):

        self.spi_start()
        if buffer == 0:
            self.spi.write(pack("<B", mcp_spi.RTS0))
        if buffer == 1:
            self.spi.write(pack("<B", mcp_spi.RTS1))
        if buffer == 2:
            self.spi.write(pack("<B", mcp_spi.RTS2))
        self.spi_end()

    def read_status(self): #Fetch recieve and transmit status and flags
        self.spi_start()
        self.spi.write(pack("<B", mcp_spi.READ_STATUS))
        self.spi.readinto(self.buf8)
        data = self.buf8[0]
        self.spi_end()
        status = {
            "RX0IF": data & 0x01,
            "RX1IF": data >> 1 & 0x01,
            "TX0REQ": data >> 2 & 0x01,
            "TX0IF": data >> 3 & 0x01,
            "TX1REQ": data >> 4 & 0x01,
            "TX1IF": data >> 5 & 0x01,
            "TX2REQ": data >> 6 & 0x01,
            "TX2IF": data >> 7 & 0x01,
        }
        return status

    def rx_status(self):

        self.spi_start()

        self.spi.write(pack("<B", mcp_spi.RX_STATUS))
        self.spi.readinto(self.buf8)
        data = self.buf8[0]

        self.spi_end()

        status = {
            "filter_match": data & 0x07,
            "RTR": data >> 3 & 0x01,
            "extendedID": data >> 4 & 0x01,
            "RXB0": data >> 6 & 0x01,
            "RXB1": data >> 7 & 0x01
        }

        return status

    # SET MODES ----------------------------
    def get_opmod(self):
        self.read_register(address.CANSTAT)
        opmod = self.buf8[0] >> 5
        return opmod

    # Set OPMOD on CANCTRL and check that the mode is sucessfuly set
    def set_opmod(self, mode):
        import time
        cur_mode = self.get_opmod()
        if cur_mode == mode >> 5:
            print("mode already set")
            return
        else:
            i = 0
            while i < 10:  # try to set mode 10 times
                self.bitmodify_register(address.CANCTRL, op_mode.op_mask, mode)
                time.sleep_ms(100)
                cur_mode = self.get_opmod()
                if cur_mode == mode >> 5:
                    print("mode sucessfully set")
                    return
                else:
                    print("retrying setting mode...")
                    i += 1
            print("Failed setting mode")
            return

    # initiate CAN communication
    def init(self,
             bit_rate=1E6,   #bit_rate: CAN bus bit rate (1Mbt)
             clock_freq=1E7, #clock_freq: Can module occilator frequency (10 Mhz)
             SJW=1,          #SJW: synchronization Jump Width: 1 to 4, default(1)
             BTLMODE=1,      #BTLMODE: : PS2 Bit Time Length bit: 0 or 1, default(1)
             SAM=0,          #SAM: Sample Point Configuration bit: 0 or 1, default(0)
             PRESEG=0,       #PRESEG: lengh of propagation segment: default(2)
             PHSEG1=2,       #PHSEG1: lenght of PS1, default(7)
             PHSEG2=2        #PHSEG2: lenght of PS2, default(2) (only valid when BTLMODE=1)
             ):
        print("HI_1")

        import time
        print("HI_2")

        self.reset()
        print("HI_3")

        time.sleep_ms(10)

        print("HI_4")

        print("Entering configuration mode...")
        self.set_opmod(op_mode.configuration)

        print("calulating BRP and setting configuration registers 1, 2 and 3")
        self._set_timing(bit_rate, clock_freq, SJW, BTLMODE,SAM, PRESEG, PHSEG1, PHSEG2)

        print("Entering normal mode...")
        self.set_opmod(op_mode.normal)

    def _set_timing(
        self,
        bit_rate,
        clock_freq,
        SJW,        #SJW: synchronization Jump Width: 1 to 4, default(1)
        BTLMODE,    #BTLMODE: : PS2 Bit Time Length bit: 0 or 1, default(0)
        SAM,        #SAM: Sample Point Configuration bit: 0 or 1, default(1)
        PRESEG,     #PHSEG1: lenght of PS1, default(7)
        PHSEG1,     #PHSEG2: lenght of PS2, default(6) (only valid when BTLMODE=1)
        PHSEG2):    #PRESEG: lengh of propagation segment: default(2)

        # Calculate bit time and time quantum
        bit_time = 1 / bit_rate
        tq = bit_time / 16

        # Calculate Baud rate prescaler
        BRP = int((tq * clock_freq) / 2 - 1)
        # assert BRP % 1 > 0, "warning, bit-rate and
        # clock-frequency is not compatible"

        # Set configuration register 1
        SJW = SJW - 1  # length of 1 is 0 etc.
        assert len(bin(SJW)) - 2 <= 2, "SJW must be 1 to 4"
        assert len(bin(BRP)) - 2 <= 5, "BRP must be under 31"

        self.bitmodify_register(address.CNF1, 0xc0, SJW << 6)
        self.bitmodify_register(address.CNF1, 0x3f, BRP)

        # Set configuration register 2
        assert len(bin(BTLMODE)) - 2 <= 1, "BTLMODE must be 0 or 1"
        assert len(bin(SAM)) - 2 <= 1, "SAM must be 0 or 1"
        assert len(bin(PHSEG1)) - 2 <= 3, "PHSEG1 must be 0 to 7"
        assert len(bin(PRESEG)) - 2 <= 3, "PRESEG must be 0 to 7"

        self.bitmodify_register(address.CNF2, 0x80, BTLMODE << 7)
        self.bitmodify_register(address.CNF2, 0x40, SAM << 6)
        self.bitmodify_register(address.CNF2, 0x38, PHSEG1 << 3)
        self.bitmodify_register(address.CNF2, 0x07, PRESEG)

        # Set configuration register 3
        assert len(bin(PHSEG2)) - 2 <= 3, "PHSEG2 must be 0 to 7"

        self.bitmodify_register(address.CNF3, 0x07, PHSEG2)

    # Filter and masks (currently not working)

    def set_filter(self, id=0x000, filter=0, extendedID=False, clear=False):
        self.set_opmod(op_mode.configuration)

        address = [0x00, 0x04, 0x08, 0x10, 0x14, 0x18]

        if filter <= 1:
            mask_address = 0x20
            ctrl_address = 0x60
        else:
            mask_address = 0x24
            ctrl_address = 0x70

        self._prepare_id(id, extendedID)

        self.write_register(address=address[filter], data=self.msgbuf[0:4])
        self.write_register(address=mask_address, data=[
                            0xff, 0xff, 0x00, 0x00])

        self.bitmodify_register(ctrl_address, 0x60, 0x00)  # activate filtering

        if clear:
            self.bitmodify_register(0x60, 0x60, 0xff)  # clear filtering
            self.bitmodify_register(0x70, 0x60, 0xff)  # clear filtering

        self.set_opmod(op_mode.normal)

    def _prepare_id(self, id, ext):
        info = [0, 0, 0, 0]
        if ext:
            id = id & 0x1fffffff
            info[3] = id & 0xff
            info[2] = id >> 8
            id = id >> 16
            info[1] = id & 0x03
            info[1] += (id & 0x1c) << 3
            info[1] |= 0x08
            info[0] = id >> 5
        else:
            id = id & 0x7ff
            info[0] = id >> 3
            info[1] = (id & 0x07) << 5
            info[2] = 0
            info[3] = 0

        pack_into("<4B", self.msgbuf, 0, *info)

    # read messages
    def read_message(self):
        """returns a dictionary containing message info
        id: message id
        rtr: remote transmit request
        extended: extended id
        data_lenght: number of bytes recieved
        data: list of bytes"""

        # fetch recieve status
        status = self.rx_status()
        if status["RXB0"] == 1:
            self.read_rx_buffer(buffer=0)
        elif status["RXB1"] == 1:
            self.read_rx_buffer(buffer=1)
        else:
            return False  # exit if no buffer is full

        id = self.msgbuf[0] << 3 | self.msgbuf[1] >> 5
        if status["extendedID"]:
            id = id << 8 | self.msgbuf[2]
            id = id << 8 | self.msgbuf[3]

        data_length = self.msgbuf[4] & 0x0f

        if data_length > 0:
            data = list(self.msgbuf[5:5+data_length])
        else:
            data = list()

        message = {
            "id": id,
            "RTR": status["RTR"],
            "extendedID": status["extendedID"],
            "data_length": data_length,
            "data": data
        }
        return message

    # Write message
    def write_message(self, id, data, rtr=0, extendedID=False):
        """Prepares a message to be sent on available trasmission buffer

        id: a standar or extended message id
        message: list of bytes max = 8
        rtr: remote transmission request, if 1, no data is sent, default(0)
        extendedID: Set to True if message ID is extended, else Fales
        """
        data_length = len(data)

        self._prepare_id(id, extendedID)

        if rtr:
            dlc = data_length | 0x40
        else:
            dlc = data_length

        self.msgbuf[4] = dlc
        self.msgbuf[5:5+data_length] = bytearray(data)

        status = self.read_status()

        if status["TX0REQ"] == 0:
            self.load_tx_buffer(buffer=0)
            self.request_to_send(buffer=0)
            TXIF = 0x04
            self.bitmodify_register(0x2c, TXIF, 0x00)  # clear interupt flag

        elif status["TX1REQ"] == 0:
            self.load_tx_buffer(buffer=1)
            self.request_to_send(buffer=1)
            TXIF = 0x10
            self.bitmodify_register(0x2c, TXIF, 0x00)  # clear interupt flag

        elif status["TX2REQ"] == 0:
            self.load_tx_buffer(buffer=2)
            self.request_to_send(buffer=2)
            TXIF = 0x20
            self.bitmodify_register(0x2c, TXIF, 0x00)  # clear interupt flag

        else:
            return  # no transmit buffers are available

    # quick function to check for incoming messages
    def message_available(self):
        status = self.rx_status()
        if status["RXB0"] == 1 or status["RXB1"] == 1:
            return True
        else:
            return False

    """
    enableing and clearing interrupts -----------------

    Enables interrupt conditions that will activate the INT pin.

    Note: errors must be cleared by MCU.
    use clear_message_error(), clear_wake_up()
    and clear_errors() to clear these flags

    tx_empty and rx_full are automatically cleared
    when reading or writing messages
    """

    def enable_interrupts(self,
        message_error=False,
        wake_up=False,
        errors=False,
        tx0_empty=False,
        tx1_empty=False,
        tx2_empty=False,
        rx0_full=False,
        rx1_full=False):

        if message_error:
            self.bitmodify_register(0x2b, 0x80, 0xff)
        if wake_up:
            self.bitmodify_register(0x2b, 0x40, 0xff)
        if errors:
            self.bitmodify_register(0x2b, 0x20, 0xff)
        if tx0_empty:
            self.bitmodify_register(0x2b, 0x04, 0xff)
        if tx1_empty:
            self.bitmodify_register(0x2b, 0x08, 0xff)
        if tx2_empty:
            self.bitmodify_register(0x2b, 0x10, 0xff)
        if rx0_full:
            self.bitmodify_register(0x2b, 0x01, 0xff)
        if rx1_full:
            self.bitmodify_register(0x2b, 0x02, 0xff)

    def clear_message_error(self):
        self.bitmodify_register(0x2c, 0x80, 0x00)

    def clear_wake_up(self):
        """Clears the wake-up interrupt flag and wakes-up the device"""
        self.bitmodify_register(0x2c, 0x40, 0x00)

    def clear_error(self):
        self.bitmodify_register(0x2c, 0x20, 0x00)

    def which_interrupt(self):
        """returns information about which interrupt condition
         triggered the interrupt"""
        self.read_register(0x0e)
        i_code = self.buf8[0] >> 1 & 0x07

        codes = ["No interrupts", "Error", "Wake-up", "TX0", "TX1", "TX2", "RX0", "RX1"]
        return codes[i_code]

    def abort_messages(self): #Request abort of all pending transmissions
        self.bitmodify_register(0x0f, 0x10, 0xff)

"""=============
===== MAIN =====
================"""

can = CAN()
can.init()

According to my test, openMV seems crash during the CAN.init() function.

Is it me who did something wrong or there is a bug from OpenIDE ? there are so few information about the arduino Nicla that I’m lost.

Thanks for your help.

Hi, please narrow down the issue to one thing… posting a lot of code and asking if it’s broken is effectively asking me to debug your code. I don’t have time to do this anymore.

So, please try to find the line of code that causes the crash and I can answer questions about that line of code and if there’s a bug that could cause a crash there.

I ran your code in the IDE and I don’t see any crashes. I see this output, but note I don’t have the actual hardware connected:

HI_1
HI_2
HI_3
HI_4
Entering configuration mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
retrying setting mode...
Failed setting mode
calulating BRP and setting configuration registers 1, 2 and 3
Entering normal mode...
mode already set

Do you have the latest firmware ? If not you should make sure to update the firmware first, make sure to erase the flash filesystem and try the script again from the IDE. You could also try the development firmware from github.

Note the board has a FD-CAN controller so you don’t need that MCP chip you just need a CAN transceiver (see our CAN shield for example).

Ok, so I did my best to simplify the problem and show the default.
The problem is the import.
In the main file I got :

#location : ./can_test.py
from mcp2515.can import test

can = test()

In the mcp2515 folder, I got 2 .py files :

#location : ./mcp2515/__init__.py
from . import test
#location : ./mcp2515/can.py
class test:
    def __init__(self):
        self.data = "data"

So, it doesn’t seems to come from the SPI nor the wiring.
OpenMV crash without an error message when I import the test class from a subfolder.
If I delete the init.py file, I got an error “no module named mcp2515”
When I manually copy everything on the nicla board and I run from the nicla, I got the same results. :face_with_monocle:

Thanks for helping me to know why OpenMV don’t wan’t me to structure my code. It’s the only IDE that I know which simple crash without error message :face_with_diagonal_mouth:

(The most efficient solution that I found is to add an underscore before the file name to differentiate modules form main code).

#location : ./mcp2515/__init__.py
from . import test

This should be

from .can import test

Thank you !!! It seems work but the openMV messages are just a little bit annoying.

I try with the simple example and it work perfectly.

With the whole code, I have to copy manualy the files on the Nicla Vision otherwise OpenMV is smapping me to copy the project’s files on the Nicla until I refuse. :face_with_raised_eyebrow:

When I manually copy them to avoid potential errors, I got these messages :

  • “The module “mcp2515” on your OpenMV Cam is different that the copy on your computer. Do you want OpenMV IDE to update the module on your computer”. No, how can they be different, I just copy them !
  • “Module “can” may be necessary for your script. Do you want a copy on your OpenMV Cam ?”. No, the can module is included in his folder. Don’t create a can.py file in the root folder.

And then, the main file run perfectly. :no_mouth:
It seems that I fight more agains the IDE than the code.