6 years, 10 months ago.

CAN Example dosent work with MBED-OS

This code is a standard way to test CAN ports.

#include "mbed.h"
 
Ticker ticker;
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);

CAN can1(PD_0 ,PD_1 );
CAN can2(PB_12, PB_6 );

char counter = 0;
 
void send() {
    printf("send()\r\n");
    can1.frequency(100000);
    can2.frequency(100000);
    if(can1.write(CANMessage(1100, &counter, 1))) {
        printf("loop send()\r\n");
        counter++;
        printf("Message sent: %d\r\n", counter);
        led1 = !led1;
    }
}
 
int main() {
    ticker.attach(&send, 2);
    CANMessage msg;
    while(1) {
        printf("loop wait()\r\n");
        if(can2.read(msg)) {
            printf("Message received: %d, from %d\r\n", msg.data[0], msg.id);
            if(msg.id==1100)
                led2 = !led2;
            if(msg.id==1102)
                led3 = !led3;     
        }
        wait(0.5);
    }
}

It works just fine with mbed (not mbed-os). So the code inside mbed-os must be broken .. I believe it is the ticker which doesn't work as it should. Note: My evaluation board is : Nucleo-F207ZG

1 Answer

6 years, 10 months ago.

I spent some time with CAN on Nucleo F092RC board in mbed-os 5 a couple months ago. It was kind of broken. I note they fixed a few things but I'm going to guess things like the filters still don't work. I note CAN.read() still uses a mutex even though you are not supposed to use a mutex in ISR context. Yet if you are using an interrupt CAN.read() is the only way to clear the flag. You should probably dig out the ST manual and look at the CAN registers so you can see what's what. Then find can_api.c for your target which is where low level functions are implemented.

You can put one CAN device in loopback mode and write and read from the same module. I would probably start there. You may have to physically put a jumper between H and L as well. Part of the CAN initialization is to wait to see the high bus level for some amount of time. If it can't see the bus recessive state, initialization will fail and it will print you a message.

Here is a test I was doing trying to see what speeds I could get in terms of data throughput. When I started off I was losing a bunch of frames but I eventually got it patched up so I wasn't losing anything - as you would hope since it's in loopback. I was using this a while ago, did not actually recheck it now, but is at least close.

/* ----- System ----- */
#include "mbed.h"
#include "CAN.h"

/* ----- Constants ----- */
const int   kWaitMs          = 1;                     // thread refresh rate
const int   kMessagesPerLoop = 100;                   //
const float kTestLength      = 5.0;                   // Length of the speed test.
const uint32_t temp          = (uint32_t)kTestLength; // help with some calculations
const int kCANbps            = 1000000;               // CAN speed bps, 1Mbps max


/* ----- Struct ----- */

// Makes packing and unpacking messages easier
typedef struct bytes_64 {
    uint8_t b0;
    uint8_t b1;
    uint8_t b2;
    uint8_t b3;
    uint8_t b4;
    uint8_t b5;
    uint8_t b6;
    uint8_t b7;
} bytes_64;

typedef union {
    uint64_t data;
    bytes_64 bytes;
} data_packed;

/* ----- Variables ----- */
data_packed send_data;
volatile data_packed receive_data;
volatile bool receive_flag = false;

// Make CAN (RD TD)
CAN can(PB_8, PB_9);

// Needed in isr too
CANMessage receive_message;

/* ----- Functions ----- */
void messageReceivedISR();
void threadCANTest();


/* ----- Main ----- */
int main() {
    Thread thread_messages(osPriorityNormal, 1024);

    thread_messages.start(threadCANTest);

    /* ----- Main Loop ----- */
    while (true) {
        Thread::wait(10000);
    }
}

/** CAN ISR Receive
 */
void messageReceivedISR() {
    // Read the message.
    can.read(receive_message);
    receive_flag = true;
}

/** Test speed of CAN transmission.
 */
void threadCANTest() {
    Timer t; // For bus speed test
    int   attempts_count = 0;
    int   success_count  = 0;

    CANMessage send_message;

    int send_status = 0;
    int send_id     = 10;

    send_data.data = 0;

    can.frequency(kCANbps);

    /// @todo Loopback, for testing only, remove
    can.mode(CAN::LocalTest);

    // Interrupt on new Frame
    can.attach(messageReceivedISR, CAN::RxIrq);

    send_id = 0;

    printf("\r\nCAN Test started %2.1f seconds...\r\n", kTestLength);
    t.start();

    // Loop until test time is over
    while (t.read() < kTestLength) {
        send_message = CANMessage(send_id, (char *)&send_data, 8);

        // Try until send succeeds
        while (!can.write(send_message)) {}

        // Should not be needed, but can add delay if getting loss
        // wait_us(120);

        attempts_count++;

        // Wait for receive.  Can wait on read directly or use ISR
        // while (!can.read(receive_message));
        while (receive_flag != true) {}

        receive_flag = false;

        receive_data.bytes.b7 = receive_message.data[7];
        receive_data.bytes.b6 = receive_message.data[6];
        receive_data.bytes.b5 = receive_message.data[5];
        receive_data.bytes.b4 = receive_message.data[4];
        receive_data.bytes.b3 = receive_message.data[3];
        receive_data.bytes.b2 = receive_message.data[2];
        receive_data.bytes.b1 = receive_message.data[1];
        receive_data.bytes.b0 = receive_message.data[0];

        if (receive_data.data == send_data.data) {
            success_count++;
        }

        send_data.data = send_data.data + 1;
        send_id++;
    } // while

    printf("CAN Test %2.1f sec Complete. Frames Attempted = %d, Frames Success = %d, Payload Data Bps = %d\r\n",
           kTestLength,
           attempts_count,
           success_count,
           (uint32_t)((success_count * 8)) / temp);
}

Thank you for your answer. From your answer, I understand that you use a local version of mbed-os. I use the online and your suggestions are not possible since you cannot change the source code. What should I do?

posted by M J. 31 May 2017

You can import the mbed-os source from github. I just tried it and it seemed to work. After you click import there is a hyperlink at the top of the page to import from URL:

https://github.com/ARMmbed/mbed-os.git

I would still give it a try. The CAN stuff might be mostly working or working well enough for your purposes. The biggest issues were that read() and write () always returned success regardless of if they failed or not, but they did patch that. You may run into some issues with data throughput or lost frames, but you can just retry.

Start as simple as possible with one device in loopback mode and H,.L wired together externally. Monitor your debug printf messages, it does give you hints when it fails sometimes.

posted by Graham S. 31 May 2017

HI Graham, I tried your last suggestion (thanks I didn't know you can import the source code like that, thanks). I have a USB-TO-CAN debugger. What I see, CAN1 send the message, I can capture the message using the debugger, but CAN2 never activates and the loop doesn't work either. Would you please write your code so I debug your code?

include "mbed.h"
 
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);

CAN can1(PD_0 ,PD_1 );
CAN can2(PB_12, PB_6 );

char counter = 0;
 
void messageReceivedISR() {
 CANMessage msg;
 if(can2.read(msg)) {
            printf("Message received: %d, from %d\r\n", msg.data[0], msg.id);
            if(msg.id==1100)
                led2 = !led2;
            if(msg.id==1102)
                led3 = !led3;     
        }

   
}
 
int main() {
    
   
   // can1.frequency(100000);
   // can2.frequency(100000);
    can2.attach(messageReceivedISR, CAN::RxIrq);
    while(1) {
        printf("loop wait()\r\n");
        if(can1.write(CANMessage(1100, &counter, 1))) {
            printf("loop send()\r\n");
            counter++;
            printf("Message sent: %d\r\n", counter);
            led1 = !led1;
        }
        
       // wait_ms(500);
    }
}

posted by M J. 01 Jun 2017