IFCT - Improved Flexcan Teensy Library

Status
Not open for further replies.
ah - that may explain why it did run at the first stage just until I thought enhancements would be needed for something else. Well, I changed the meaning of my own protocol bytes so that's not a problem any longer. Thanks anyway - it help to understand!

But man, I'm still having problem to understand the TX side deeply: When I lower my sending rate, things are smooth without congestion and the receiving end catches up without any backlog. But I need higher rates and I'm sure that the bus is not saturated so it has to be software runtime issues?!

I apologise but I have some more questions:
- one of my application runs fine without msg.seq=1 with 6 TX mailboxes to deliver in. But as soon as I do msg.seq=0 and I send one frame, it locks itself up for almost a whole second then the 2nd frame is sent etc... same code, same receiver... So what could lead into this situation? Shouldn't depletion be decoupled from sequencing as long as there are enough TX mailboxes?

- Where do I see or configure the TX transmit queue size? what/how is this linked to the template parameter of the FlexCan_T4 constructor?

- does the write() complete after it get's the ACK from the receiving node or is this asynced?

- does the receiving callback have to run through until the next call can be processed? or can they be "overrun" leading into consistency problems (overwriting volatiles) and depending on the previous question, lead into congestions on the bus?

- Can0.events() doesn't seem to return anything? I thought that is useful to understand the queue capacity?!

- there is no timeout argument in this library (or not yet?) - so not control for retries or timeout parameters? correct?

Maybe it's lack of my knowhow and documentation but maybe a couple of instructions would be needed for anybody that run into "congestions/locked/hanging" problems especially when dealing with sender and receiver code. Maybe there are some best practices what to do and what not to do. In my application it's about realtime, low-latency, continuous small messages (either 8 frames with 1 byte or 1 frame with 8 bytes, with ext IDs) from a master node to a client node. Bus arbritation/layer 1 prio is only needed when there is another type of message (another message ID) on the same bus, if that happens, I'd like to have some control of priority, those are messages that come every now and then, so not at a constant rate. The reason I went for MB's is to get some convenience to sort the messages based on the type (the message ID, really). But maybe in my application all I needed was a FIFO sending as fast as possible and on the receiver do the "triage" in one FIFO callback (parse based on message ID etc... all in software)....

Could you spend some minutes on this, please? I'm happy to pay for your time or donate something!

Thanks.
 
The RX and TX queue are sized from constructor, as a power of 2.

The write is complete (but not yet transmitted) when a TX mailbox is available and your data is written to it. It is non blocking, and relies on the hardware to handle the arbitration to fully transfer it to the bus. The function returns 0 or 1, basically 0 if no mailboxes were available (so you can know if you need to resend) or 1 if a mailbox was written successfully. The rest is done in hardware, so it's up to the transmissions and controller to finalize and clear the mailbox. The library will only see free mailboxes to be written to.

Can0.events() is mandatory only when used for Interrupt enabled receptions (FIFO or MBs), and for sequential sends. Sequential sends is only stored in the queue, and they only write to the absolute first transmit mailbox. All other transmit mailboxes are not used in sequential sends. Normal sends fill up all available TX mailboxes, and are not queued. Mailbox + FIFO polling receptions, and normal sends do not need events().

For retries, a user can handle this with his own timer, and the return flag. If a 0 is returned, it means you need to either resend again or set your own timer to resend the frame. It's easier just to take note of the return flag, to know how to handle the resends.

Happy to help, but this is a hobby to me and I support the forum, PJRC, and it's users, and I don't expect anything in return :)

Also, you can use sequential TX alternatively. The return value of the last 12 bits show how many messages are in queue. if your TX QUEUE is:
Code:
FlexCAN_T4<CAN3, RX_SIZE_256, [COLOR="#0000FF"][B]TX_SIZE_16[/B][/COLOR]> Can0;

this means you have 16 queues. So when sending a sequential frame, we don't monitor the return of the write function, because it is different behaviour. For sequential sends, the absolute first mailbox is the only transmit mailbox used for sending. If it is empty, it will be filled and 1 returned. If it is full and you write another frame, 1 can still be returned, as it's stored in the queue. The only time 0 will appear is if the queue is being filled faster than it can unload. The queue is only processed via events() so that must always be ran to push frames in-order into transmissions.

if ( Can0.events & 0xFFF ) { /* if there are queues to be transmitted */
while ( Can0.events & 0xFFF ) { /* while there are queues to be transmitted */

Or handle the resends based on the queues if theyre maxed
if ( (Can0.events & 0xFFF) == 16 ) { /* queues full, resend if necessary */

There is always room for adding improovements, my other library blasts the whole queue with data and polls events() until the transmissions are done. It was noted by skpang, an LCD line buffer of 640 bytes with header info on a 1MBps nominal, 8Mbps data bus resulted in 1.5ms total transfer. It's not CAN2 speeds but it is pretty fast, even for single mailbox sequential sends.


Code:
uint32_t timeout = millis();
if ( (Can0.events & 0xFFF) > 10  ) while ( (Can0.events & 0xFFF) && (millis - timeout < 100) );

This code basically tries to transmit the frames in the queue when more than 10 are consecutively queued if your loop() is not fast enough to run events(). It has it's purpose and probably there can be a function made to make it easier for the user, functionality is there though.
Keep in mind that if you write 20 frames to transmit sequentially before you hit the events(), you'll just overflow and lose your queues. It is very important that events() is ran just as fast as you can send, this usually works better in tight loops, or with hardware timers, or teensythreads, or just managing looping events till your queues deplete. You can always choose a bigger queue, if you plan to send 100 messages per second, set a TX queue of 256, blast it, then poll events() checking the last 12 bits to push them onto the network
 
Last edited:
OK thank you very much.
However, Can0.events does not extist and Can0.events() doesn't return an int:

error: invalid operands of types 'void' and 'int' to binary 'operator&'
if ( (Can0.events() & 0xFFF) > 10 ) while ( (Can0.events() & 0xFFF) && (millis - timeout < 100) );
 
ok thanks - will do.
One additional questions - this time on the receiving side:
What's the effect of the RX buffer when working with callbacks? Wouldn't the callback fire as soon as one frame went through the filters (of the associated MB)? What's the point to buffer on RX side? Using callbacks the code is always "picking up".

What happens on long running callbacks i.e. the next packet invokes the callback before the first one exists? Is that the buffer?

I think I have a long running callback and while fast sending is OK the code seems to catch up with the fast sending rate but then at irregular times it will stop calling the callback (but the main loop continues) for about 1 second, then it continues to match the rate for another seconds - this repeats on and on.... How can I narrow this down to understand where/why the code "hangs"?

Thanks for far - I'm almost there ;-)
 
The receive buffer is only used when using interrupts, not when polling.
The ISR only drops the frames in the queue, the callback fires when events() in the loop is ran and queues exist. So you process the queues in your code while the ISR just buffers it in the queues
 
Hello, I'm currently having some speed issues when using this library and I think it has to do with the buffer. I'm using a Teensy 3.5 connected to FTDI FT813 display, based off the Gameduino code. I also have the Teensy connected to my aftermarket car ECU through Can Bus using a SN65HVD230 transceiver.

My ECU is able to broadcast an 11bit Can signal at 10hz, 20hz, and 50hz. I was originally using other CAN libraries and couldn't go faster than 10hz, anything past that and the data on the display would lag 5-10 seconds. This is also happening on the serial monitor. The data is driving a gauge needle so at 10hz, the movement is really choppy, I'd like it to be 20hz or better yet, 50hz.

I switched to the IFCT library and was able to get the data reading at 50hz using the IFCTsimpleMBPolling sketch. The issue is, the moment I add in the display code, it drops the data back to around 10hz. I originally thought it was maybe due to something in the display code but I hooked up a potentiometer straight to the Teensy and the reading is really smooth, about the loop rate of the display which is around 60hz.

I tried both of the Interrupt based sketches and I would see normal looking data on the serial monitor at 10hz but as soon as I change the ECU to 20 or 50hz, it seems to only read the first frame and then the other frames randomly appear for one line every 5-10 seconds.

I'm pretty new to coding in general so all of this CAN stuff is beyond me at the moment. I think it might have something to do with the buffer being full or something? From some reading, possibly adding mailboxes and or filters might help but I'm still trying to wrap my head around it all and how to even add it.

Below is the code I'm currently using, any help would be greatly appreciated!

Code:
#include <IFCT.h>
#include <TeensyThreads.h>
#include <GD23ZUTX.h>

float TPS, POT;
const int potPIN = 38;
const int backLight = 37;

void setup() {
  
  pinMode(potPIN, INPUT);
  pinMode(backLight, OUTPUT);
  
  Can0.setBaudRate(500000);
  Can0.intervalTimer(); // enable queue system and run callback in background.

  GD.begin(); //Initialize display

  delay(100);
  digitalWrite(backLight, HIGH); // Backlight pin enable
  
}

void loop() {
  
  CAN_message_t rxmsg;

  if ( Can0.read(rxmsg) ) {
    switch (rxmsg.id) { // Using IDs from 1512 as Megasquirt CAN broadcast frames for Simplified Dash Broadcasting.
      // EAch frame represents a data group http://www.msextra.com/doc/pdf/Megasquirt_CAN_Broadcast.pdf
      case 1523:
        (TPS) = (float)(word(rxmsg.buf[0], rxmsg.buf[1])); 
      break;
    }
  }

  POT = analogRead(potPIN);
  Serial.println(TPS);

  GD.Clear();               //Next seven lines are to display the guage needle widgets
  GD.ColorRGB(0xff0000);
  GD.cmd_gauge(242,417,270,12544,9,5,(map(TPS, 0, 1000,1170, 2170)),1000); //CAN Bus gauge needle
  GD.ColorRGB(0xffffff);
  GD.cmd_gauge(242,417,270,12544,9,5,(map(POT, 0, 1023,1170, 2170)),1000); //POT to Teensy gauge needle

  GD.swap();
  GD.finish();
  
}
 
try using the FlexCAN_T4 example, and put in the constructor CAN0 since you are using teensy 3.5. See if anything changes, it is more recent than IFCT

Also you will miss frames if your loop is doing alot of work. Sometimes if car frames are important they must be interrupt based not polling. Teensy 3.5 has a 16 mailbox controller (or 6 queue deep FIFO). Although you may think more mailboxes are better, it depends based on your programs reaction time. An MCP2515 uses 2 mailboxes and people use them as gateways.

In any case you should comment out some display functions and run CAN in interrupt mode on a full speed loop to check if your ECU and teensy can keep up to each other. They shouldn't lag and you shouldn't miss frames either.
 
Thanks for the quick reply! Just tried again using FlexCAN_T4 and it's the same result. I added a micros() counter to determine loops per second and with the display lines commented out, I get about 25000 loops/sec. The serial monitor displays a really fast stream which I believe is pretty much 50hz.

When I add the display lines back in, the loops/sec drop to 54 loops/sec and the serial monitor stream drops back to about 10hz. I guess what I don't understand is why isn't the Can data being read at 54hz if it's looping through the code?

Can I somehow make the CAN data act like the data I'm getting from the analog read? Below is a video I recorded that kind of shows what I'm talking about. The white needle is the pot connected straight to the Teensy. The red needle is the pot connected to the ECU and going through the Can bus.

[video]https://photos.app.goo.gl/LhsXZ2qirCwB9cky6[/video]
 
So I tried again using the CAN2.0_example_FIFO_with_interrupts sketch and was able to get it to read full speed. This time when I add in the display lines, the Can data pretty much stops instead of running 10hz, this is with the ECU outputting 50hz. If I switch the ECU broadcast rate tp 10hz without doing anything else, the data stream comes back at 10hz.

Images below of what I'm seeing in the serial monitor,

This is with just the unmodified CanFlex_T4 interrupt sketch, ECU is broadcasting at 50hz and the data is reading really fast.

fFNOSnG.png


This is how the monitor looks when I add the display lines back in. This is still with the ECU at 50hz, if I change the broadcast rate to 10hz, the data starts streaming at 10hz. I do notice that right when the sketch is done uploading and the serial monitor begins, there seems to be like a second where the data looks normal and then it starts looking like this.

8eECEGJ.png
 
maybe the lcd code is slowing things down?, also if can.events() is in the loop(), remove it. The callback will fire directly rather than use the queues.

i guess there is 3 modes to flexcan_t4,
polling: read()
semi-polling in loop: events() (uses queues)
and direct callback firing: no events() in loop (no queues, direct fire)
 
So I had a genius idea to use another Teensy connected to the same Can bus, read the data without the display code and use the DAC to output to the Teensy with the display. It's actually working great with the same speed as the pot that is directly connected, albeit, there's a lot of noise. Going to try outputting a pwm signal instead. I guess sometimes modern problems require primitive solutions...lol

I don't believe can.events() was in the code. I closed that sketch without saving but I'll set it up and try again.

EDIT: Tried the PWM route and it actually works perfectly! I guess that will have to do for now until I slowly learn more.
 
Last edited:
Status
Not open for further replies.
Back
Top