Teensy 4.1 HardwareSerial usage.

SamiH

New member
I'm working on a project where I need to send 8 bytes of data over 6 hardware serial ports at a strict 1 kHz rate—any delay causes the motor driver to jitter.

In my current setup, I’m using an IntervalTimer to trigger the send every 1ms. When triggered, I send 8 bytes over Serial1 through Serial6 (all at 460800 baud, the max the motor driver can do). I measured the send time and it takes about 200 µs in the worst case. I'm expecting a reply from each serial port around 250 µs later, assuming it takes the same amount time to read, the whole cycle shouldn't take more than 650 µs

Here’s a simplified version of the code:

C++:
IntervalTimer sendTimer;
volatile bool sendFlag = false;

uint8_t data[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; // 8 bytes

void triggerSend() {
  sendFlag = true;
}

void setup() {
  Serial.begin(115200);
  while (!Serial);       // Wait

  Serial1.begin(460800);
  Serial2.begin(460800);
  Serial3.begin(460800);
  Serial4.begin(460800);
  Serial5.begin(460800);
  Serial6.begin(460800);

  sendTimer.begin(triggerSend, 1000); // 1 kHz = every 1000 µs
}

void loop() {
  if (sendFlag) {
    sendFlag = false;

    uint32_t t_start = micros();

    Serial1.write(data, sizeof(data));
    Serial2.write(data, sizeof(data));
    Serial3.write(data, sizeof(data));
    Serial4.write(data, sizeof(data));
    Serial5.write(data, sizeof(data));
    Serial6.write(data, sizeof(data));
    Serial1.flush();
    Serial2.flush();
    Serial3.flush();
    Serial4.flush();
    Serial5.flush();
    Serial6.flush();
    uint32_t t_end = micros();
    uint32_t duration = t_end - t_start;

    Serial.print("Write duration (us): ");
    Serial.println(duration);
  }
}
void serialEvent()
{
}
/* based on the feedback on each port, do some math */
void serialEvent1()
{
}
void serialEvent2()
{
}
void serialEvent3()
{
}
void serialEvent4()
{
}
void serialEvent5()
{
}
void serialEvent6()
{
}
Given that the Teensy 4.1 hardware serial FIFO buffer is only 4 bytes deep, is this approach reliable for a strict 1 kHz send/receive cycle across 6 serial ports?


Are there risks of overruns or timing conflicts that could break this under real-time constraints?
 
The hardware buffer is only 4 bytes but the HardwareSerial class automatically creates a 64 byte firmware buffer for each direction that is then sent/received using the hardware interrupts.

I'd remove those flush commands. That is really only of use if you want to clear out old received data without reading it.
Also if your data to send is constant then define it as const, that may let the compiler do some extra optimisations.


For the receive side the simplest solution would be to add if (Serial1.avalible()) {...} etc within the loop code.
This could delay the response to the transmit time event very slightly (the time taken to check for data waiting) but would be simple to implement.
While this would add a reasonable amount of code to the main loop (.avalible() checks both the firmware buffer and the uart hardware buffers so it's a few lines of code) the actual processor time in comparison to uart speeds will still be tiny.
In terms of receive time it will be a tiny bit slower than some sort of interrupt based processing but assuming your main loop really is that empty the time difference will be negligible.

In summary, do it the simple way and check if it's good enough before you worry too much about adding complexity that will probably only save a few fractions of a us.
 
Also if your data to send is constant then define it as const, that may let the compiler do some extra optimisations.
That is not the case at all, there will be all sorts of calculation happening in the main loop, this is like I mentioned a bare bones version of the code.

I guess I will update the post once I have all the hardware in hand
 
Whether a transmit is due is only checked once per loop(). I'm guessing transmit time is more critical than your time of processing the receive so if main loop is good enough for transmit is should be good enough for the receive as well.

If transmit needs to be better then put the Serial.write() lines in the timer IRQ, the amount of data you are writing will all fit in the Tx buffer so this won't cause any blocking in the IRQ itself.
 
yield() is called each time before loop() re-enters. yield() can be called elsewhere to trigger the serialEvent#() functions. It has relatively efficient parsing of all the active UART Serial ports in use. delay() calls yield - but that would add wasted millis's. But, any tight or time abusive code without a yield() would delay the UART checks.
 
If transmit needs to be better then put the Serial.write() lines in the timer IRQ, the amount of data you are writing will all fit in the Tx buffer so this won't cause any blocking in the IRQ itself.
Or if all of them needed to be started as close together as possible, create a loop to post 1 byte at a time to each port. I think that would get the transmits as "synchronized" as possible.
 
Or if all of them needed to be started as close together as possible, create a loop to post 1 byte at a time to each port. I think that would get the transmits as "synchronized" as possible.
That would only be needed for the first byte on each port. Once all the ports are running you could easily put the rest of the message in the tx buffers for each port long before the first byte has finished sending.

But I suspect this doesn't matter that much. Filling the buffer for each port in turn is probably still going to result in a time difference between ports that is equivalent to less than 1 bit of data. That is a smaller time difference than the UART spec allows for baud rate mismatch errors. If something is that sensitive to time variation then only a crazy person would use an asynchronous bus to define the timing.
 
Back
Top