API Can lose messages

Hello,
I can’t use the read function under interrupt because the function uses a mutex.

int CAN::read(CANMessage &msg, int handle)
{
lock();
int ret = can_read(&_can, &msg, handle);
unlock();
return ret;
}

When I remove the mutex in CAN::read, and I use the function under interrupt, I don’t lose any messages.
But I can no longer send a message.

I tried different solutions: events, variables, … but I’m losing messages.

Could you help me ?

Thanks for advance.

Hello Didier,

I can’t use the read function under interrupt because the function uses a mutex.

You are right. To overcome that the following design pattern works for me:

  • Create a CAN Rx interrupt ISR as short as possible (set an event flag):
#define CAN_RX_INT_FLAG             (1UL << 0)

EventFlags          eventFlags;

void isrCanRx()
{
    eventFlags.set(CAN_RX_INT_FLAG);
}

  • Design a “CAN read” task (data producer), running in its own thread (waits for a CAN Rx event). When a “CAN read event” is triggered it reads the CAN message and puts it into a mail queue:
typedef struct
{
    unsigned int    id;
    uint8_t         tag;
    float           temp;
    uint8_t         mode;
    uint8_t         target;
} mail_t;

Mail<mail_t, 24>    mail_box;
CAN                 can(PA_11, PA_12, 500000);

void taskCanRx(void)
{
    CanMsg  rxMsg;

    while (1) {
        eventFlags.wait_all(CAN_RX_INT_FLAG);    // wait for CAN Rx interrupt
        if (can.read(rxMsg)) {
            mail_t*     mail = mail_box.try_alloc();    // create a mail msg on the heap
            if (mail != nullptr) {
                mail->id = rxMsg.id;                    // set mail ID
                switch (rxMsg.id) {
                    case (HEARTBEAT_RX + ID_NODE):
                        break;

                    case PDO_RX1 + ID_NODE:
                        rxMsg >> mail->target;
                        rxMsg >> mail->mode;
                        break;
                    ...
                }

                mail_box.put(mail);
            }
        }
    }
}
  • Setup a CAN data consumer called by the main thread
void getCanData()
{
    mail_t*     mail;

    while (!mail_box.empty()) {
        mail = mail_box.try_get();  // get a mail from the queue
        if (mail != nullptr) {
            switch (mail->id) {
                case (HEARTBEAT_RX + ID_NODE):
                    ThisThread::sleep_for(5ms);
                    canBusConnected = publishHeartbeat(mail->tag);
                    break;

                case (PDO_RX1 + ID_NODE):
                    relay[mail->tag].mode = mail->mode;
                    if (relay[mail->tag].target != mail->target) {
                        relay[mail->tag].target = mail->target;
                        relay[mail->tag].toggle = true;
                    }
                    break;

                 ...
            }

            mail_box.free(mail);    // remove mail from the heap
        }
    }
}
...
  • In the “main” function setup as many CAN filters as you can. This will reduce the rate of CAN Rx interrupts.
...
Thread              threadCanRx(osPriorityHigh);

int main()
{
...
    // Setup CAN filters
    can.filter(HEARTBEAT_RX + ID_NODE, 0b11111111111, CANStandard, 0);
    can.filter(PDO_RX1 + ID_NODE, 0b11111111111, CANStandard, 1);
...
    // Start CAN receive thread
    threadCanRx.start(callback(taskCanRx)); // canRx() waits for CAN Rx interrupt

    // Attach CAN Rx ISR
    can.attach(isrCanRx);
...

    // The main thread
    while(1) {
        ...
        // Get data from the CAN Mail Queue
        getCanData();
        ...
    }
}