peterhinch / micropython-async

Application of uasyncio to hardware interfaces. Tutorial and code.
MIT License
726 stars 166 forks source link

STM32F405 CANbus errors when using encoder.py #81

Closed ilium007 closed 2 years ago

ilium007 commented 2 years ago

I have no idea where to start looking for the issue but when I use this encoder.py and then send a CANbus message via the callback I end up with consistent CANbus errors and bus failure.

When I use the miketeachman rotary.py (https://github.com/miketeachman/micropython-rotary) with similar callback CANbus message sending there are never any bus state errors.

msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x1  data: 0x20400
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x1  data: 0x10400
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x1  data: 0x400
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x1  data: 0x10401
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x1  data: 0x400
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x3  data: 0x10400
msg_id: 0x1EB00103      nid: 0x75       cid: 0x103      bus_state: 0x3  data: 0x20400
Task exception wasn't retrieved
future: <Task> coro= <generator object '_run' at 200066e0>
Traceback (most recent call last):
  File "uasyncio/core.py", line 1, in run_until_complete
  File "/sd/lib/primitives/encoder.py", line 68, in _run
  File "main.py", line 39, in enc_cb
  File "main.py", line 54, in send_msg
OSError: 16
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x4300
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x4300
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x2300
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x2400
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x2300
msg_id: 0x1EB00101      nid: 0x75       cid: 0x101      bus_state: 0x3  data: 0x2400
ilium007 commented 2 years ago

Note, this is my debug output above relating to my own CANbus message protocol. The bus fails when bus_state goes over state 0x03:

CAN.ERROR_PASSIVE – the controller is on and in the Error Passive state (at least one of TEC or REC is 128 or greater);

ilium007 commented 2 years ago

I think this has something to do with reading the fifo buffer using event() class (set, wait and clear). I have changed it to use threadsafeflag for reading and for now the issues have stopped. In my testing I have pushbuttons and rotary encoders that send messages on the CANbus and a seperate STM32F405 that is receiving them.

ilium007 commented 2 years ago

I stripped out all of there asyncio pushbutton code and just running two rotary encoders, it all seems to be working fine. This is just prototyping code, I am really just learning about uasyncio...

Sender:

from machine import Pin
import uasyncio as asyncio
from primitives.encoder import Encoder
from pyb import CAN

can1 = CAN(1, CAN.NORMAL, extframe = True, auto_restart=True, baudrate=500000)

nid = 0x75 #DEC 117 node id

# encoder pins
pin_c2 = Pin(Pin.cpu.C2, Pin.IN)
pin_c3 = Pin(Pin.cpu.C3, Pin.IN)
pin_b10 = Pin(Pin.cpu.B10, Pin.IN)
pin_b11 = Pin(Pin.cpu.B11, Pin.IN)

def enc_send_msg(pos, id):
    abs_pos = abs(pos)
    input_id = id
    byte0 = abs_pos & 0xff
    count_upper = (abs_pos >> 8) & 0x03
    byte1 = (input_id << 2) | count_upper
    # set sign bit
    if pos >= 0:
        byte2 = 0x00
    else:
        byte2 = 0x01
    send_msg(can1, nid, 0x0103, [byte2, byte1, byte0])

def send_msg(bus, nid, cid, msg):
    fpf = 1  #first packet flag
    lpf = 1  #last packet flag
    nid_lower = nid & 0x7   # split node id
    nid_upper = nid >> 3
    #4 bytes in a 29-bit msg_id; lower 2 bytes are channel_id
    byte2 = (nid_lower << 5) | (lpf << 4)
    byte3 = (fpf << 4) | nid_upper
    msg_id = (byte3 << 24) | (byte2 << 16) | cid
    buf = bytearray(msg)
    bus.send(buf, msg_id)

    #print("{0:08b} {1:08b} {2:08b} {3:08b} | ".format(
    #    (msg_id >> 24), ((msg_id >> 16) & 0xff), ((msg_id >> 8) & 0xff), msg_id & 0xff), end='')
    #for x in buf[:-1]:
    #    print("{0:08b} ".format(x) , end='')
    #print("{0:08b} ".format(buf[-1]))

    print('msg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tdata: 0x{:X}'.format(
        msg_id, nid, cid, can1.state(), int.from_bytes(buf, 'big')))

enc0 = Encoder(pin_c2, pin_c3, div=4, callback=lambda p,d: enc_send_msg(p, 0x00))
enc1 = Encoder(pin_b10, pin_b11, div=4, callback=lambda p,d: enc_send_msg(p, 0x01))

async def main():

    while True:
        await asyncio.sleep_ms(0)

try:
    asyncio.run(main())
except (KeyboardInterrupt, Exception) as e:
    print('Exception {} {}\n'.format(type(e).__name__, e))
finally:
    ret = asyncio.new_event_loop()  # Clear retained uasyncio state

Receiver:

import uasyncio as asyncio
from pyb import Pin, CAN

can1 = CAN(1, CAN.NORMAL, extframe = True, auto_restart=True, baudrate=500000)

# define filters later, for now let everything through
can1.setfilter(0,CAN.MASK16,0,(0,0,0,0))

buf = bytearray(8)
# tuple containing id, rtr flag, filter match index, data
lst = [0, 0, 0, memoryview(buf)]

tsf = asyncio.ThreadSafeFlag()

def cb(bus, reason):
    if reason == 0:
        #print('pending')
        bus.recv(0, lst)
        #event.set()
        tsf.set()
    if reason == 1:
        print('full')
    if reason == 2:
        print('overflow')

can1.rxcallback(0, cb)

def process_msg(lst):

    #print("{0:08b} {1:08b} {2:08b} {3:08b} | ".format(
    #    (lst[0] >> 24), ((lst[0] >> 16) & 0xff), ((lst[0] >> 8) & 0xff), lst[0] & 0xff), end='')
    #for x in lst[3][:-1]:
    #    print("{0:08b} ".format(x) , end='')
    #print("{0:08b} ".format(lst[3][-1]))

    msg_id = lst[0]
    cid = msg_id & 0xffff
    nid_lower = (msg_id >> 21) & 0x07
    nid_upper = (msg_id >> 24) & 0x0f
    nid = (nid_upper << 3) | nid_lower

    if cid == 0x0101: #push button
        state_id = lst[3][1] & 0x1f #mask bottom 5 bits from byte1

        if state_id == 0x03:
            state_name = 'push'
        elif state_id == 0x04:
            state_name = 'release'
        elif state_id == 0x05:
            state_name = 'hold'

        input_id_lower = lst[3][1] >> 5
        input_id_upper = (lst[3][0] & 0x07)
        input_id = input_id_upper | input_id_lower

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'button', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    elif cid == 0x0102: #toggle switch

        state_id = lst[3][0] & 0x03 #mask bottom 2 bits
        input_id = lst[3][0] >> 2
        if state_id == 0x01:
            state_name = 'on'
        elif state_id == 0x02:
            state_name = 'off'

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'switch', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    elif cid == 0x0103: #rotary encoder
        input_id = lst[3][1] >> 0x02
        sign = lst[3][0] & 0x01
        count_msb = lst[3][1] & 0x03
        count = (count_msb << 8) | lst[3][2]

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tsign: 0x{:X}\tcount: 0x{:X}'.format(
            'encoder', msg_id, nid, cid, can1.state(), input_id, sign, count))

    elif cid == 0x0104: #hall effect sensor
        input_id_lower = lst[3][1] >> 5
        input_id_upper = (lst[3][0] & 0x07)
        input_id = input_id_upper | input_id_lower

        state_id = lst[3][1] & 0x1f #mask bottom 5 bits
        if state_id == 0x03:
            state_name = 'sense'
        elif state_id == 0x04:
            state_name = 'wait'
        elif state_id == 0x05:
            state_name = 'hold'

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'hall', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    else:
        print('undefined')

async def main():

    while True:
        await tsf.wait()
        process_msg(lst)
        await asyncio.sleep_ms(0)

try:
    asyncio.run(main())
except (KeyboardInterrupt, Exception) as e:
    print('Exception {} {}\n'.format(type(e).__name__, e))
finally:
    ret = asyncio.new_event_loop()  # Clear retained uasyncio state
ilium007 commented 2 years ago

Problem was in my receiving code. Jimmo helped me out on the Micropython Discord channel.

import uasyncio as asyncio
from pyb import Pin, CAN

screen_debug = False

tsf = asyncio.ThreadSafeFlag()

can1 = CAN(1, CAN.NORMAL, extframe = True, auto_restart=True, baudrate=500000)

# fix filters
can1.setfilter(0,CAN.MASK16,0,(0,0,0,0))

# set can1 callback
can1.rxcallback(0, cb)

def cb(bus, reason):
    if reason == 0:
        #print('pending')
        tsf.set()
    if reason == 1:
        print('full')
        # raise exception and halt
    if reason == 2:
        print('overflow')
        # raise exception and halt

def process_msg():
    buf = can1.recv(0)

    if screen_debug:
        print('CAN info: {}'.format(can1.info()))

        print("{0:08b} {1:08b} {2:08b} {3:08b} | ".format(
            (buf[0] >> 24), ((buf[0] >> 16) & 0xff), ((buf[0] >> 8) & 0xff), buf[0] & 0xff), end='')
        for x in buf[3][:-1]:
            print("{0:08b} ".format(x) , end='')
        print("{0:08b} ".format(buf[3][-1]))

    msg_id = buf[0]
    cid = msg_id & 0xffff
    nid_lower = (msg_id >> 21) & 0x07
    nid_upper = (msg_id >> 24) & 0x0f
    nid = (nid_upper << 3) | nid_lower

    if cid == 0x0101: #push button
        state_id = buf[3][1] & 0x1f #mask bottom 5 bits from byte1

        if state_id == 0x03:
            state_name = 'push'
        elif state_id == 0x04:
            state_name = 'release'
        elif state_id == 0x05:
            state_name = 'hold'

        input_id_lower = buf[3][1] >> 5
        input_id_upper = (buf[3][0] & 0x07)
        input_id = input_id_upper | input_id_lower

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'button', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    elif cid == 0x0102: #toggle switch

        state_id = buf[3][0] & 0x03 #mask bottom 2 bits
        input_id = buf[3][0] >> 2
        if state_id == 0x01:
            state_name = 'on'
        elif state_id == 0x02:
            state_name = 'off'

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'switch', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    elif cid == 0x0103: #rotary encoder
        input_id = buf[3][1] >> 0x02
        sign = buf[3][0] & 0x01
        count_msb = buf[3][1] & 0x03
        count = (count_msb << 8) | buf[3][2]

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tsign: 0x{:X}\tcount: {}'.format(
            'encoder', msg_id, nid, cid, can1.state(), input_id, sign, count))

    elif cid == 0x0104: #hall effect sensor
        input_id_lower = buf[3][1] >> 5
        input_id_upper = (buf[3][0] & 0x07)
        input_id = input_id_upper | input_id_lower

        state_id = buf[3][1] & 0x1f #mask bottom 5 bits
        if state_id == 0x03:
            state_name = 'sense'
        elif state_id == 0x04:
            state_name = 'wait'
        elif state_id == 0x05:
            state_name = 'hold'

        print('input: {}\tmsg_id: 0x{:X}\tnid: 0x{:X}\tcid: 0x{:X}\tbus_state: 0x{:X}\tinput_id: 0x{:X}\tstate_id: 0x{:X}\tstate_name: {}'.format(
            'hall', msg_id, nid, cid, can1.state(), input_id, state_id, state_name))

    else:
        print('undefined')

async def main(): 
    while True:
        await tsf.wait()
        process_msg()
        await asyncio.sleep_ms(0)

try:
    asyncio.run(main())
except (KeyboardInterrupt, Exception) as e:
    print('Exception {} {}\n'.format(type(e).__name__, e))
finally:
    ret = asyncio.new_event_loop()  # Clear retained uasyncio state