RPMSG and FlexCAN

Hello,
I’m running a program which is a mix of the flexcan interrupt transfer and the rpmsg_lite_str_echo_rtos examples found on the MCUXpresso SDK.
The idea is to send messages from a Linux container using the virtual ttyRPMSG30 and then send that message over flexCAN and viceversa.
I use two tasks, one for RPMSG and one for CAN, and I use global flags to let the tasks know when a message has arrived from either Linux or CAN.
The thing is that the way from Linux to CAN works fine, but when I send from CAN, it receives the frame, sets the flag, but never reaches the statement that controls whether the flag is set or not, hence never sends the message to Linux. I thought that removing the timeout from the “rpmsg_queue_recv_nocopy” function (setting it to 0 or a small value) would change this behavior, but the problem with that is that this way the program no longer works in any direction (Linux to CAN or CAN to Linux).
I’m attaching part of my code (with tasks and related variables) to see if you can help me with this and what I might be doing wrong.

//-------------------------------------------RPMSG Definitions-----------------------------------------------//
#define RPMSG_LITE_SHMEM_BASE         (VDEV1_VRING_BASE)
#define RPMSG_LITE_LINK_ID            (RL_PLATFORM_IMX8QM_M4_A_USER_LINK_ID)
#define RPMSG_LITE_NS_ANNOUNCE_STRING "rpmsg-virtual-tty-channel"
#define RPMSG_LITE_MASTER_IS_LINUX
#define APP_TASK_STACK_SIZE (256)
#ifndef LOCAL_EPT_ADDR
#define LOCAL_EPT_ADDR (30)
#endif

#ifdef RPMSG_LITE_MASTER_IS_LINUX
static char helloMsg[13];
#endif /* RPMSG_LITE_MASTER_IS_LINUX */

/*******************************************************************************
 * Variables
 ******************************************************************************/
static TaskHandle_t rpmsg_task_handle = NULL;
static TaskHandle_t can_task_handle = NULL;

flexcan_handle_t flexcanHandle;
volatile bool txComplete = false;
volatile bool rxComplete = false;

flexcan_mb_transfer_t txXfer, rxXfer;
flexcan_frame_t rxframe,txframe;
uint32_t txIdentifier = 1; //This varies from 1 to 2047
uint32_t rxIdentifier = 0; //Changes according to the ID of the incoming frame

bool tx_can_flag=false;//Flag to activate frame transmission to CAN
bool rx_can_flag=false;//Flag to activate frame reception to CAN

static char app_buf[512]; /* Each RPMSG buffer can carry less than 512 payload */
uint8_t can_tx_frame[8];
uint8_t can_rx_frame[8];
uint8_t can_tx_len;
uint8_t can_rx_len;

/*******************************************************************************
 * Prototypes
 ******************************************************************************/

static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData);
void copy_tx_frame(flexcan_frame_t *dest,uint8_t *src);
void copy_rx_frame(uint8_t *dest,flexcan_frame_t *src);
static void rpmsg_task(void *param);
static void can_task(void *param);


/*!
 * @brief Main function
 */
int main(void)
{
    if (xTaskCreate(rpmsg_task, "RPMSG_TASK", APP_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 2, &rpmsg_task_handle) != pdPASS)
    {
        PRINTF("\r\nFailed to create RPMSG task\r\n");
        for (;;)
            ;
    }

    if (xTaskCreate(can_task, "CAN_TASK", APP_TASK_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &can_task_handle) != pdPASS)
    {
        PRINTF("\r\nFailed to create can task\r\n");
        for (;;)
            ;
    }


    vTaskStartScheduler();

    PRINTF("Failed to start FreeRTOS on core0.\n");
    for (;;)
        ;


}

/*******************************************************************************
 * Interrupt Handlers and Callback Functions
 ******************************************************************************/

/*!
 * @brief FlexCAN Call Back function
 */
static void flexcan_callback(CAN_Type *base, flexcan_handle_t *handle, status_t status, uint32_t result, void *userData)
{
    switch (status)
    {
        case kStatus_FLEXCAN_RxIdle:
            if (RX_MESSAGE_BUFFER_NUM == result)
            {
                rxComplete = true;
            }
            break;

        case kStatus_FLEXCAN_TxIdle:
            if (TX_MESSAGE_BUFFER_NUM == result)
            {
                txComplete = true;
            }
            break;
        default:
            break;
    }
}


static void rpmsg_task(void *param)
{
    volatile uint32_t remote_addr;
    struct rpmsg_lite_endpoint *volatile my_ept;
    volatile rpmsg_queue_handle my_queue;
    struct rpmsg_lite_instance *volatile my_rpmsg;
    void *rx_buf;
    uint32_t len;
    int32_t result;
    void *tx_buf;
    uint32_t size;

    /* Print the initial banner */
    PRINTF("\r\nRPMSG String Echo FreeRTOS RTOS API Demo...\r\n");


    my_rpmsg = rpmsg_lite_remote_init((void *)RPMSG_LITE_SHMEM_BASE, RPMSG_LITE_LINK_ID, RL_NO_FLAGS);


    while (0 == rpmsg_lite_is_link_up(my_rpmsg))
        ;

    my_queue = rpmsg_queue_create(my_rpmsg);
    my_ept   = rpmsg_lite_create_ept(my_rpmsg, LOCAL_EPT_ADDR, rpmsg_queue_rx_cb, my_queue);

    //Codigo nuevo
    SDK_DelayAtLeastUs(1000000U,SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
    (void)rpmsg_ns_announce(my_rpmsg, my_ept, RPMSG_LITE_NS_ANNOUNCE_STRING, RL_NS_CREATE);

    PRINTF("\r\nNameservice sent, ready for incoming messages...\r\n");


    #ifdef RPMSG_LITE_MASTER_IS_LINUX
    /* Wait Hello handshake message from Remote Core. */
    (void)rpmsg_queue_recv(my_rpmsg, my_queue, (uint32_t *)&remote_addr, helloMsg, sizeof(helloMsg), ((void *)0),
                           RL_BLOCK);
    #endif /* RPMSG_LITE_MASTER_IS_LINUX */

    for (;;)
    {
        /* Get RPMsg rx buffer with message */
        result = rpmsg_queue_recv_nocopy(my_rpmsg, my_queue, (uint32_t *)&remote_addr, (char **)&rx_buf, &len, RL_BLOCK);

        if (result==RL_SUCCESS)
        {
            /* Copy string from RPMsg rx buffer */
            assert(len < sizeof(app_buf));
            memcpy(app_buf, rx_buf, len);
            app_buf[len] = 0; /* End string by '\0' */

            if ((len == 2) && (app_buf[0] == 0xd) && (app_buf[1] == 0xa))
                PRINTF("Get New Line From Master Side\r\n");
            else
            {
                tx_can_flag=true;
                can_tx_len=(uint8_t)len;
                memcpy(can_tx_frame,app_buf,can_tx_len);
                PRINTF("Get Message From Master Side : \"%s\" [len : %d]\r\n", app_buf, len);
            }
            /* Release held RPMsg rx buffer */
            result = rpmsg_queue_nocopy_free(my_rpmsg, rx_buf);
            if (result != 0)
            {
                assert(false);
            }
        }
             
        /* Get rx buffer from CAN */
        if (rx_can_flag)
        {
            PRINTF("CAN frame received \n\r");
            tx_buf = rpmsg_lite_alloc_tx_buffer(my_rpmsg, &size, RL_BLOCK);
            assert(tx_buf);
            /* Copy string to RPMsg tx buffer */
            memcpy(tx_buf, can_rx_frame, can_rx_len);
            /* Echo back received message with nocopy send */
            result = rpmsg_lite_send_nocopy(my_rpmsg, my_ept, remote_addr, tx_buf, (uint32_t) can_rx_len);
            if (result != 0)
            {
                assert(false);
            }
            rx_can_flag=false;
        }
    }
}

static void can_task(void *param){ 
    flexcan_config_t flexcanConfig;
    flexcan_rx_mb_config_t mbConfig;

    FLEXCAN_GetDefaultConfig(&flexcanConfig);

/* If special quantum setting is needed, set the timing parameters. */
#if (defined(SET_CAN_QUANTUM) && SET_CAN_QUANTUM)
    flexcanConfig.timingConfig.phaseSeg1 = PSEG1;
    flexcanConfig.timingConfig.phaseSeg2 = PSEG2;
    flexcanConfig.timingConfig.propSeg   = PROPSEG;
#if (defined(FSL_FEATURE_FLEXCAN_HAS_FLEXIBLE_DATA_RATE) && FSL_FEATURE_FLEXCAN_HAS_FLEXIBLE_DATA_RATE)
    flexcanConfig.timingConfig.fphaseSeg1 = FPSEG1;
    flexcanConfig.timingConfig.fphaseSeg2 = FPSEG2;
    flexcanConfig.timingConfig.fpropSeg   = FPROPSEG;
#endif
#endif

    //Avoids receiving a frame sent by itself regardless of mask rules
    flexcanConfig.disableSelfReception = true;

    FLEXCAN_Init(CAN, &flexcanConfig, CAN_CLK_FREQ);

    /* Create FlexCAN handle structure and set call back function. */
    FLEXCAN_TransferCreateHandle(CAN, &flexcanHandle, flexcan_callback, NULL);

    /* Set Rx Masking mechanism. */
    FLEXCAN_SetRxMbGlobalMask(CAN, FLEXCAN_RX_MB_STD_MASK(rxIdentifier, 0, 0));

    /* Setup Rx Message Buffer. */
    mbConfig.format = kFLEXCAN_FrameFormatStandard;
    mbConfig.type   = kFLEXCAN_FrameTypeData;
    mbConfig.id     = FLEXCAN_ID_STD(rxIdentifier);

    FLEXCAN_SetRxMbConfig(CAN, RX_MESSAGE_BUFFER_NUM, &mbConfig, true);

    /* Setup Tx Message Buffer. */
    FLEXCAN_SetTxMbConfig(CAN, TX_MESSAGE_BUFFER_NUM, true);

    rxXfer.mbIdx = (uint8_t)RX_MESSAGE_BUFFER_NUM;
    rxXfer.frame = &rxframe;

    txXfer.mbIdx = (uint8_t)TX_MESSAGE_BUFFER_NUM;
    txXfer.frame = &txframe;

    txframe.id = FLEXCAN_ID_STD(txIdentifier);
    

    (void)FLEXCAN_TransferReceiveNonBlocking(CAN, &flexcanHandle, &rxXfer);

    for (;;){
        if (tx_can_flag && can_tx_len<=8)
        {
            copy_tx_frame(&txframe,can_tx_frame);
            txframe.length=can_tx_len;
            tx_can_flag=false;    
            (void)FLEXCAN_TransferSendNonBlocking(CAN, &flexcanHandle, &txXfer);        
        }
        if (txComplete)
        {
            LOG_INFO("Message sent\n\r");
            txComplete=false;
        }

        if (rxComplete)
        { 
            copy_rx_frame(can_rx_frame, &rxframe);
            can_rx_len=(uint8_t)rxframe.length;
            rx_can_flag=true;
            rxComplete=false;
            (void)FLEXCAN_TransferReceiveNonBlocking(CAN, &flexcanHandle, &rxXfer);
        }
        
    }
}

void copy_tx_frame(flexcan_frame_t *dest,uint8_t *src){
    dest->dataByte0=src[0];
    dest->dataByte1=src[1];
    dest->dataByte2=src[2];
    dest->dataByte3=src[3];
    dest->dataByte4=src[4];
    dest->dataByte5=src[5];
    dest->dataByte6=src[6];
    dest->dataByte7=src[7];
}

void copy_rx_frame(uint8_t *dest,flexcan_frame_t *src){
    dest[0]=src->dataByte0;
    dest[1]=src->dataByte1;
    dest[2]=src->dataByte2;
    dest[3]=src->dataByte3;
    dest[4]=src->dataByte4;
    dest[5]=src->dataByte5;
    dest[6]=src->dataByte6;
    dest[7]=src->dataByte7;
}

My setup is:
Apalis iMX8QM V1.0B
Apalis Eva Board V1.1A
TorizonCore 5.3.0+build.7

Thanks

Greetings @juanigevo!

I assume you have already removed any reference to FlexCAN in your device tree, right?

Are you able to pinpoint in which statement your application is hanging? Here a debugger setup would be helpful.

Does sending a message directly - not incoming from CAN - work?

Hi @gustavo.tx ,
Yes, I disabled both flexCAN 1 and 2 on the device tree. This was preventing me from using CAN and RPMSG together in the first place.
For what I’ve seen with the debugger is that the function “hangs” here:

result = rpmsg_queue_recv_nocopy(my_rpmsg, my_queue, (uint32_t *)&remote_addr, (char **)&rx_buf, &len, RL_BLOCK);

The thing I noticed is that for example if I send a message from Linux, it goes through RPMSG and then to CAN normally. However, when I send a message from CAN, it activates the flag but on the RPMSG task never get to the part that evaluates that flag (rx_can_flag), but if I send a new message from Linux the RPMSG task “unstucks” and evaluates the flag and enters the if statement .
If I set the timeout other than “RL_BLOCK” from the rpmsg receive function then I’m not even able to receive any message from Linux either.
As I pointed out in my post, CAN works, I can send and receive the messages, the problem is that it gets “stuck” on the rpmsg receive message function and never evaluates the condition.
As for your third question, I tested sending back messages to Linux but using the "rpmsg_lite_str_echo_rtos " example which works with only one task (the RPMSG) and it receives the messages just fine. I’ll try to send a message over my program and then I tell you how it goes.
One last thing, I’m thinking maybe to separate receiving from Linux on one task, and sending to Linux on another task and the CAN task leave it as it is now. Is it possible? What do you think about that.
Thanks.
Juan

Hi @gustavo.tx
I changed the timeout of the receive no copy function to about 100 ms and then added a 10 ms delay on each task and that worked for the RPMSG task not to hang anymore.
However, it still doesn’t send anything to Linux (or at least that I could see it on the terminal).
I tried sending a string every 3 seconds through the “rpmsg_lite_send_nocopy” function and I couldn’t see anything either.
Is it something I’m doing wrong with this function, how could I see what is coming to the Linux terminal?
I used these commands to try to see data on the terminal:

stty -F /dev/ttyRPMSG30 -echo
exec 3<> /dev/ttyRPMSG30 
cat <&3

Following the instructions on this Post
I’ll be waiting for your reply.
Thanks,
Juan

Hi @juanigevo,

For me it’s obvious that you should move if(rx_can_flag){} frpm rpmsg_task to can_task. Yes, rpmsg_queeue_recv (_nocopy) is blocking. But this is fine for preemptive RTOS like FreeRTOS.
I would rename your rpmsg_task to rpmsg_rx_task and perhaps can_task to can_rx_task. One should receive from RPMSG and send to CAN, another one receive from CAN and send to RPMSG. Isn’t it? At least I do so in my application.

Edward

Hi Edward,
Yes, the idea is to receive from RPMSG and send to CAN, and receive from CAN and send to RPMG.
Thanks for the suggestion.
Juan

Hi @gustavo.tx,
I followed a different approach and now I’m using freeRTOS tasknotify function to let the task know when a message has arrived. Again, from Linux to CAN everything works fine but when I send a message from CAN it goes to the CAN callback, and I execute the xTaskNotifyFromISR function and it get stucked on the vPortValidateInterruptPriority function. I’ve read that the interrupt priority from the source it originated (CAN in this case) needs to be lower than configMAX_SYSCALL_INTERRUPT_PRIORITY.
This one is 2 so I set the CAN interrupt priority to 7 with
NVIC_SetPriority(DMA_FLEXCAN0_INT_IRQn, 7);
just to make sure, but the problem persist. Actually, if I read the current priority with NVIC_GetPriority, it gives me 0 all the time, which I think it makes the validate priority function to stuck on the first place.
Is there something wrong I’m doing here? How can it be fixed?
Thanks

Hi @juanigevo,
I don’t have much experience with rpmsg but let’s try to think about what’s going on:

result = rpmsg_queue_recv_nocopy(my_rpmsg, my_queue, (uint32_t *)&remote_addr, (char **)&rx_buf, &len, RL_BLOCK);

If this function is blocking, freeRTOS will put this task to sleep until there’s data on the message queue (in this case this data should come from your linux application). In this case to unlock it your linux application must write to the rpmsg.
I think that this is a problem for your design because you will only process received packets when your linux application writes something.
When you’re processing the receive part (rx_can_flag), there’s another opportunity for blocking on rpmsg_lite_send_nocopy. I assume this would block if the send queue is full. This would happen if your linux application is not reading the data on its end.
I think that you could simplify your code, it doesn’t seem that you need the CAN task at all. You’re using the non-blocking variants for sending and receiving CAN data and controlling the end of the operations with the callbacks.
You could try something like this in your rpmsg task (pseudo code) and don’t create the CAN task anymore.

initialize_can();
initialize_rpmsg();

for (;;) {

// we're calling rx with timeout so that we release
// the task to do something else while there are no messages
// on the rpmsg rx queue
success = check_rpmsg_rx(TIMEOUT);
if (success) {
    // we received a message on rpmsg rx queue, process
    // it and start the transfer on CAN
    handle_and_process_can_tx();
}
// check if we've received something on the CAN bus
if (rx_flag_done) {
   // we did, process the buffer and send it through
   // rpmsg
   handle_and_process_can_rx();
   // again we're using a timeout here, so that our task doesn't
   // get stuck if the queue is full. This gives us a chance to 
   // handle the error, print a message...
   success = send_rpmsg(TIMEOUT);
   if (!success) {
        // error in tx to rpmsg, queue full?
        // print message on the console?
       handle_error();
   }
}
// start the next CAN rx cycle, this must be thought out together
// with the timeouts used in the task so that we don't spend too much
// time without an RX command on CAN
rx_can_nonblocking();
}

By doing it in one task the code will be simpler and you won’t need to worry about concurrency problems.
Do you think that this could work?
Regards,
Rafael Beims