exothink/eXoCAN

send more than 1 CAN message in a row

epikao opened this issue · 9 comments

Hello

Is it possible to send more than 1 Can message in a row (without delay) and without data being lost at the recipient? How can this be implemented properly?

void canISR() // get CAN bus frame passed by a filter into fifo0
{
    can.receive(id, fltIdx, rxbytes);  // empties fifo0 so that another another rx interrupt can take place
}

void setup()
{
  can.attachInterrupt(canISR);
}

void loop()
{
...
...
    can.transmit(frame1...);
    can.transmit(frame2...);
    can.transmit(frame3...);
    can.transmit(frame4...);
    can.transmit(frame5...);
...
}


Thank you

or perhaps I should ask differently?
As a sender - how do I know if the recipient has emptied the mailbox?
My suggestion:
Sender sends, and if the recipient empties the mailbox, the recipient also sends something, and if the sender receives the message from the recipient, the sender can send something again....

Are you experiencing dropped messages or thinking they may get dropped. The 103 handles them in hardware. The FIFO can contain more that one RX message. I have several on a bus sending and one receiving and it never experienced any drops.

I suggest you 'loop back' and compare sent and received data. One sends the other receives it and echoes it back. The first compares the two.

BTW there is a bit that indicates a FIFO over run. It's all in the documentation.

What data rate.

Are you experiencing dropped messages or thinking they may get dropped

Just thinking, and because I found some issues about in the internet, see for example here:
https://www.mikrocontroller.net/topic/370955 (but is another library)

But yes, thank you for your input, I will try it first with echoes. And with documentation I think you mean the STM32F103 datasheet / CAN part / register?...

BTW: For me it makes no sense why SN65HVD230 should not work. Do you have an idea why it was not working with your setup? I have both chips (TJA and SN), and I will of course test them as soon as I can....

Hello,

I just tested it (with your blinkInterrupt example), see code below. With no delay or delay(1) it does not work (it shows always only the first frame), with delay(2) between every transmit it works.... but I'm not happy with such a solution.

void loop()
{
  //if (millis() / txDly != last)                 // send every txDly, rx handled by the interrupt
  //{
    //last = millis() / txDly;
    //txData[1]++;
    can.transmit(txMsgID1, txData1, txDataLen);
    delay(2);
    can.transmit(txMsgID2, txData2, txDataLen);
    delay(2);
    can.transmit(txMsgID3, txData3, txDataLen);
    delay(2);
    can.transmit(txMsgID4, txData4, txDataLen);
    Serial.println("loop and transmit");
    delay(2);
  //}
}

I believe you are incorrect. I've looked at it on an oscilloscope and have seen one message sent immediately after the other without any issues. You are sending print statements via serial IO. It is the print statements that slow things down.

I believe you are incorrect. I've looked at it on an oscilloscope and have seen one message sent immediately after the other without any issues. You are sending print statements via serial IO. It is the print statements that slow things down.

hmm, I removed the Serial.print in the interrupt receive function, but still the same issue, see below.
I still think the ISR receive function can't empty the buffer fast enough?

without delay:
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 1 fltIdx: 0 rxbytes: 1
...

with delay(2) between every transmit:
canISR, id: 2 fltIdx: 0 rxbytes: 2
canISR, id: 3 fltIdx: 0 rxbytes: 3
canISR, id: 4 fltIdx: 0 rxbytes: 4
canISR, id: 1 fltIdx: 0 rxbytes: 1
canISR, id: 2 fltIdx: 0 rxbytes: 2
canISR, id: 3 fltIdx: 0 rxbytes: 3
canISR, id: 4 fltIdx: 0 rxbytes: 4
canISR, id: 1 fltIdx: 0 rxbytes: 1
...

My Code:

void canISR() // get CAN bus frame passed by a filter into fifo0
{
    if(rxCount == 0){
      can.receive(id1, fltIdx, rxbytes1);  // empties fifo0 so that another another rx interrupt can take place
    }else if(rxCount == 1){
      can.receive(id2, fltIdx, rxbytes2);  // empties fifo0 so that another another rx interrupt can take place
    }else if(rxCount == 2){
      can.receive(id3, fltIdx, rxbytes3);  // empties fifo0 so that another another rx interrupt can take place
    }else if(rxCount == 3){
      can.receive(id4, fltIdx, rxbytes4);  // empties fifo0 so that another another rx interrupt can take place
    }
    rxCount++;
    if(rxCount>3){
      rxCount = 0;
    }
digitalToggle(bluePillLED);
}

void loop()
{
  //if (millis() / txDly != last)                 // send every txDly, rx handled by the interrupt
  //{
    //last = millis() / txDly;
    //txData[1]++;
    can.transmit(txMsgID1, txData1, txDataLen);
    delay(2);
    can.transmit(txMsgID2, txData2, txDataLen);
    delay(2);
    can.transmit(txMsgID3, txData3, txDataLen);
    delay(2);
    can.transmit(txMsgID4, txData4, txDataLen);
    delay(2);

    Serial.print("canISR, id: "); Serial.print(id1); Serial.print("  fltIdx: "); Serial.print(fltIdx); Serial.print("  rxbytes: "); Serial.println(rxbytes1[0]);
    Serial.print("canISR, id: "); Serial.print(id2); Serial.print("  fltIdx: "); Serial.print(fltIdx); Serial.print("  rxbytes: "); Serial.println(rxbytes2[0]);
    Serial.print("canISR, id: "); Serial.print(id3); Serial.print("  fltIdx: "); Serial.print(fltIdx); Serial.print("  rxbytes: "); Serial.println(rxbytes3[0]);
    Serial.print("canISR, id: "); Serial.print(id4); Serial.print("  fltIdx: "); Serial.print(fltIdx); Serial.print("  rxbytes: "); Serial.println(rxbytes4[0]);

  //}
}




I am experiencing the same issue.
If two messages are sent one after the other, only the first arrives.

The cause I found was that the eXoCAN::transmit() function returns false if the transmit mailbox 0 is not empty.
When the first message is sent the function returns as soon as the message is queued but before the state machine has successfully sent it.
When the second message is sent the MBE (mail box empty) bit has not been set yet and the function returns without doing anything.

The solution was to pause waiting for the MBE bit. This issue has clearly been seen before since there is already a suitable pause in the code which has been commented out. All I had to do was restore the pause.

`bool eXoCAN::transmit(int txId, const void *ptr, unsigned int len)
{

uint32_t timeout = 10UL, startT = millis();
while (periphBit(tsr, 26) == 0) // tx not ready
{
    if((millis() - startT) > timeout) {
        return false;           //Timeout
    }
}

// TME0
//if (periphBit(tsr, 26) == 0) // tx mailbox 0 not ready)
//    return false;

if (_extIDs)
    MMIO32(ti0r) = (txId << 3)  + 0b100; // // set 29b extended ID.
else
    MMIO32(ti0r) = (txId << 21) + 0b000; //12b std id

MMIO32(tdt0r) = (len << 0);
// this assumes that misaligned word access works
MMIO32(tdl0r) = ((const uint32_t *)ptr)[0];
MMIO32(tdh0r) = ((const uint32_t *)ptr)[1];

periphBit(ti0r, 0) = 1; // tx request
return true;

}`

A better solution would be to use all three transmit mailboxes in sequence.

Here is the version of eXoCAN::transmit() I am using.
This one uses all three mailboxes so up to 3 messages may be sent consecutively. To send more ,the user must put a short delay between groups. I tried using a while loop to wait until a mailbox became free, but that did not work for some reason.

bool eXoCAN::transmit(int txId, const void ptr, unsigned int len)
{
    uint8_t mbx = 0x00;                     //mailbox offset 0=mailbox 0, 0x10=mailbox1, 0x20 = mailbox 2
    bool mbx_empty = false;
    uint8_t i;
    for (i = 0; i<3; i++) {                      //Check each mailbox in turn to find an empty one
        if (periphBit(tsr, 26 + i) == 1) {
            mbx_empty = true;
            mbx=  i *  0x10;
            break;                                   //Mailbox empty
        }
    }
    if(!mbx_empty) return false; //No mailbox available try later

    if (_extIDs)
        MMIO32(ti0r + mbx) = (txId << 3)  + 0b100;       // // set 29b extended ID.
    else
        MMIO32(ti0r + mbx) = (txId << 21) + 0b000;     //12b std id

    MMIO32(tdt0r + mbx) = (len << 0);
    // this assumes that misaligned word access works
    MMIO32(tdl0r + mbx) = ((const uint32_t *)ptr)[0];
    MMIO32(tdh0r + mbx) = ((const uint32_t *)ptr)[1];

    periphBit(ti0r + mbx, 0) = 1; // tx request
    return true;
}

Hello delboy711. Thank you for posting your solution.

In my examples I use the following to 'pace' the TX :
if (millis() / txDly != last) // send every txDly

This leaves it up to the application to make sure that they are not trying to send faster than the hardware is capable of. Since the data rate and message length are variable in the application the minimum time between consecutive transmissions is also variable. My design decision was maximize throughput by minimizing code overhead. The 'check' that I commented out helps accomplish that and also keeps the code from blocking. Blocking occurs if another device corrupts the bus, the 'check' stalls and the code freezes.

I also chose not to use the two additional mailboxes because once they are filled you have the same exact issue.

My application has a dozen stm32f103s on the bus. Some update data such as fuel level in tens of seconds, while tachometer data is updated in tens of milliseconds.

Regards,
John