Teensy Serial over USB Timing

Hello,

I'm currently working on a project where velocity commands for a robot are sent from a computer over USB Serial to a Teensy 4.1. On the computer side of things I am writing a short string (roughly 10 to 15 bytes ending with a '\n' character) to the serial port at 250Hz. On the Teensy I am continuously checking SerialUSB.available() and then get the characters using SerialUSB.read(). When I detect the '\n' I store the current time according to micros() and print that out. From my testing, the difference in time between detections of the '\n' character (indicating new message has arrived) ranges from 4ms (250Hz) to 10ms (100Hz) which is not the desired frequency.

I'm curious if this is something that is inherent to using serial over USB or if there is something I can do on the Teensy to get it up to the desired 250Hz?

Similar example Teensy code:

Code:
void setup()
{
    SerialUSB1.println("Setting up");
}

void loop()
{
    if (SerialUSB.available())
    {
        SerialUSB1.println(micros());
        while (SerialUSB.available())
        {
            SerialUSB1.println((char)SerialUSB.read());
        }
    }
}

Similar example serial publishing shell script:

Code:
while :
do
    echo "0.000 0.000" > /dev/ttyACM0
    sleep 0.004
done

Thanks!


(Note: this is a simplified example of the code I am running on the Teensy and a simplified example of what I am running to write data to the serial line. Hopefully these simplifications don't introduce any unintended differences from what I am actually running.)
 
The problem is on the Linux side. You can't get 4ms timing accuracy from a shell script. Shell scripts which fork processes have nowhere near that level of timing accuracy!

Here's a quick & dirty C program which gets close, using the nanosleep() function for the 4ms delay.

Code:
// gcc -Wall -O2 -o test test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>

int main()
{
        int fd = open("/dev/ttyACM0", O_RDWR);
        if (fd < 0) {
                printf("unable to open port");
                exit(1);
        }
        const char str[] = "0.000 0.000\n";
        const int len = strlen(str);
        struct timespec ts;
        ts.tv_sec = 0;
        ts.tv_nsec = 4000000; // 4ms
        while (1) {
                int n = write(fd, str, len);
                if (n != len) break;
                nanosleep(&ts, NULL);
        }
        close(fd);
}

I modified your program slightly to print the elapsed microseconds, so the results are easier to read.

Code:
void setup() {
  SerialUSB1.println("Setting up");
}

void loop() {
  if (SerialUSB.available()) {
    static uint32_t prior_m = 0;
    uint32_t m = micros();
    SerialUSB1.print(m);
    SerialUSB1.print(", diff = ");
    SerialUSB1.println(m - prior_m);
    prior_m = m;
    while (SerialUSB.available()) {
      char c = SerialUSB.read();
      SerialUSB1.println(c);
      if (c == 10) break;
    }
  }
}

Here is the result when I run it on my Linux desktop with a Teensy 4.1.

screenshot.png

The result is slightly more than 4000 microseconds because the code on the Linux side is taking time between the delays. To get more accurate timing, you would need to use more sophisticated delaying on the Linux side which takes into account the time which has been spent running the code, rather than a simple fixed delay.

The Teensy side is very fast & accurate. The imprecise timing you're seeing is coming from the Linux side because you're using a shell script which involves substantial delays to launch programs.
 
Thanks for the quick reply and the very detailed response, Paul.

In reality I'm using C++ in ROS on Linux with a while loop that is supposed to be running at 250Hz and the shell script was supposed to be a simple way to test the timing. But either way, this conversation thread has been enough to prove that it is not an issue with the communication to the Teensy and is instead an issue with the code that is running in Linux. I chose to modify the code I posted here because I wasn't sure of the familiarity with ROS of the people in this community.

On a somewhat related note though, I have noticed sometimes that when plugging and unplugging the USB port while code is running on the Teensy (powered separately), it causes a pretty long delay within the code (haven't been able to test the specifics of it but I think I've seen on the order of about 120ms or so). Is this also expected behavior and if so I'm curious what the cause is? If not, it's probably just something with my code I can look into more before posting here again.

Thanks again!
 
On a somewhat related note though, I have noticed sometimes that when plugging and unplugging the USB port while code is running on the Teensy (powered separately), it causes a pretty long delay within the code (haven't been able to test the specifics of it but I think I've seen on the order of about 120ms or so). Is this also expected behavior and if so I'm curious what the cause is?

Yes, this is the expected Serial.print() behavior when the PC has stopped communicating. Or to be more technically accurate, the USB host controller has stopped transmitting IN tokens which allow Teensy to transmit buffered data. Inside the USB serial code on Teensy, there is an intentional timeout where we conclude the PC has disconnected or otherwise stopped working and all new Serial.print() data is discarded, rather than letting Serial.print() forever block your program from running.

You can avoid this timeout by use of Serial.availableForWrite(), which tells you the amount of space remaining in the transmit buffer. If the remaining space is less than the number of bytes in the message you will transmit, you can decide to something other than calling Serial.print() which will end up waiting.
 
Back
Top