Interrupt on Serial Data receive Teensy 4.0 USB port

Hello all,

I am working on a project where I am doing a lot of computation in recursive functions, but need to be able to react immediately to small amounts of incoming serial data, which will happen frequently but not on an exact schedule. Somewhere on the order of 5 bytes every 1.2 milliseconds... half the time the system is running, and the other half the time, every 30 or so ms.
Ideally, I need to have incoming serial data trigger an interrupt.
The serial connection I'm using is the USB port on the teensy 4.0.
I'd like to keep the existing hardwareserial libraries, not redo things!
I can't constantly poll Serial.available() - that's really not practical. I can't pepper my functions with that call, it would be inelegant and probably not fast enough anyway.
I suppose I could make an interrupt timer that would hammer Serial.available() on a very very frequent schedule, but that seems incredibly wasteful - 99% of the time, the buffer will be empty and that entire interrupt service will hamper operations.

My service routine won't be complicated - just needs to see if the incoming byte is the end of the message, and if it is, operate on the message. It'll get out way before the next byte is available.

Is there a reasonable way to make the hardware serial trigger an interrupt (or just call a function, I guess) after retrieving the byte from the serial port?
Do I need to edit the hardwareserial code, or is there a more straightforward reasonable way to do this that doesn't require editing existing libraries?

Thank you
Eric
 
I'm using the serial communication thru the teensy (4,0)'s standard USB (micro) port. i.e. Serial.read(), Serial.println(), etc.
 
Then please don't confuse that with Hardware Serial which on Teensy usually refers to the UART Serial ports.
EDIT:
As far as I understand it the USB protocol may well collect together data into data packets and NOT individual bytes.
Teensyduino then collects these bytes from the packets and buffers them for collection via the USB Serial Library.

I would suggest that you consider using Real Serial ports to gather your 5 data bytes every 1.2ms or 30ms.
How are you going to detect the start or end of your 5 bytes of data to synch them?
 
Last edited:
As @BriComp mentioned not HardwareSerial.
But in usb_serial.h and .c

Not sure if implementing your own serialEvent() function would do what you want or not...
 
Then please don't confuse that with Hardware Serial which on Teensy usually refers to the UART Serial ports.
EDIT:
As far as I understand it the USB protocol may well collect together data into data packets and NOT individual bytes.
Teensyduino then collects these bytes from the packets and buffers them for collection via the USB Serial Library.

I would suggest that you consider using Real Serial ports to gather your 5 data bytes every 1.2ms or 30ms.
How are you going to detect the start or end of your 5 bytes of data to synch them?
My apologies for the confusion, I didn't know that the different serial hardware were handled differently.

I could move over to the hardware serial pins, but that will increase complexity - I already have the USB connection for power and programming, and I'll need to keep that. So I'd have to have an extra connection just for data, and i'd wind up ultimately putting that thru a usb to serial converter anyway, so it'll come thru packetized regardless.

My protocol is very simple and has dedicated start and end bytes, so it's easy to parse. I just need to automatically handle the data as soon as it comes in (so be it if it's shifted a little due to USB packet chicanery, nothing I can do to change that), hence my desire to have the serial (or usb or whatever it is that handles the incoming data) interrupt also run a function of mine to parse the data.

Thanks
Eric
 
I have NOT used them but the Hardware Serial ports have extern void serialEvent1(void); but I must admit that I have never used them.
You say you I can't constantly poll Serial.available() - that's really not practical. I can't pepper my functions with that call, it would be inelegant and probably not fast enough anyway. then even if you had an interrupt routine to notify you that a byte had been received how were you going to know if you don't periodically query a variable?

Serial data, be it USB or Hardware Serial, is collected and stored in a buffer ready for the user to interrogate and get the data out.

Going off air now for an hour or two.
 
I have NOT used them but the Hardware Serial ports have extern void serialEvent1(void); but I must admit that I have never used them.

Neither did I. So I did a quick test with USB-Serial which works nicely. Might come in handy sometime...

C++:
void serialEvent()  // do your parsing here
{
  char c = Serial.read();

  // do something with the character
  if(c == '1')
  {
    Serial.println("--------------");
    Serial.println(c);
    Serial.println("--------------");
  }
}

void setup()
{
}

void loop()
{
  // simulate some long running foreground task
  Serial.println(millis());
  delay(100);
}

Which prints:

Code:
1078
3778
3878
3978
4078
4178
4278
4378
4478
4578
4678
4778
4878
--------------
1
--------------
4978
5078
5178
5278
5378
5478
5578

when I send "1" from the PC to the teensy
 
Last edited:
I have NOT used them but the Hardware Serial ports have extern void serialEvent1(void); but I must admit that I have never used them.
You say you I can't constantly poll Serial.available() - that's really not practical. I can't pepper my functions with that call, it would be inelegant and probably not fast enough anyway. then even if you had an interrupt routine to notify you that a byte had been received how were you going to know if you don't periodically query a variable?

Serial data, be it USB or Hardware Serial, is collected and stored in a buffer ready for the user to interrogate and get the data out.

Going off air now for an hour or two.
I would have my routine parse the serial data in the interrupt handler. so every time data comes in, the ISR is triggered, it checks the data, if the data are complete, it does what it needs to do with it, and then gets out of the ISR.

I can't check directly on a teensy now, but does serialEvent1 or something similar exist for the USB serial?
 
Just checked... according to the documentation, Serialevent() runs at the end of loop()s. so that's not really an interrupt. My loop()routine can run for a very long time, it won't trigger soon enough.
 
It probably runs on yield. e.g. after loop or while delay or other long running functions are spinning, or whenever you call yield in your code. But yes, this is not an interrupt.
 
It probably runs on yield. e.g. after loop or while delay or other long running functions are spinning, or whenever you call yield in your code. But yes, this is not an interrupt.
That'll be brutal for what I'm trying to do. I need to be made aware the data is available much sooner.
 
That'll be brutal for what I'm trying to do. I need to be made aware the data is available much sooner.
I didn't know that Serial_Event() was available for usb Serial. You learn something everyday.
As for your problem I suggest you setup a timer to periodically inspect Serial.Available() and act accordingly.
 
I didn't know that Serial_Event() was available for usb Serial. You learn something everyday.
As for your problem I suggest you setup a timer to periodically inspect Serial.Available() and act accordingly.
That's going to be too wasteful. I'm digging around in the usb_serial.* files, it looks like I should be able to trigger a handler right after usb data is received. I'll post back with how it works out if I'm successful.

Thanks for the help, all
 
I used DMA on the serial port to solve this type of problem 10 yrs ago on the T3.2. It used a set size of transfer and when each byte came in, it was automatically dumped into a buffer of bytes. When the number of bytes was completed the DMA would issue an interrupt which would set a flag which was checked in the loop. Because the data was coming in asynchronously, I found this was the best way to handle outside data coming into the T3.2 for processing. This method has been working almost continually for 10 years.
One of our senior software guys here wrote the the serialDMA class for the T3.0, and I modified it to work with the T3.2. I think it was written by Duff.

Regards,
Ed
 
I used DMA on the serial port to solve this type of problem 10 yrs ago on the T3.2. It used a set size of transfer and when each byte came in, it was automatically dumped into a buffer of bytes. When the number of bytes was completed the DMA would issue an interrupt which would set a flag which was checked in the loop. Because the data was coming in asynchronously, I found this was the best way to handle outside data coming into the T3.2 for processing. This method has been working almost continually for 10 years.
One of our senior software guys here wrote the the serialDMA class for the T3.0, and I modified it to work with the T3.2. I think it was written by Duff.

Regards,
Ed
How did you get the DMA to issue an interrupt? I wouldn't want to set a flag and then poll for that flag, but if I can put my service routine right in that DMA interrupt, i'd be set

Thanks
Eric
 
As I recall, the SerialDMAClass had 3 choices, fixed number of bytes, a specific character at the end of the block, and I can't remember the 3rd choice for the interrupt to be set. I choose fixed number of bytes which worked quite well for me. The DMA section of the Teensy's is very powerful, but not simple to program. There are many people on this forum that have used the DMA hardware section of the Teensy's to accomplish some really great jobs very quickly.

Regards,
Ed
 
I just looked up the name of the library I used: UartEvent.h by Colin Duffy copyright 2015.
A little less than 10 years ago.

Regards,
Ed
 
ericscottf, another option would be to use TeensyThreads library and have one thread be your main (with the code you currently have inside loop) and other thread keeps checking the USB Serial for available data.

Keep in mind that TeensyThreads is buggy and unmaintained. I also don't recommend using it for preemptive threading as most of the serial communication code is non reentrant. If you write the threads to be cooperative and use a lot of threads.yield(), I think you will be able to achieve what you want.

Victor
 
ericscottf, another option would be to use TeensyThreads library and have one thread be your main
...
If you write the threads to be cooperative and use a lot of threads.yield(), I think you will be able to achieve what you want.
Not sure how this would be anything different than simply adding a lot of calls to yield()...

@ericscottf But if want the quickest response... And you don't mind editing the core code for every release... You could simply hack on cores\teensy4\usb_serial.c in the function rx_event(transfer_t *t)

And add in the ability to get called...
Something like:
Code:
// new stuff
void (usb_rx_cb)() = nullptr;
void set_usb_rx_cb( void (*cb)()) {
  usb_rx_cb = cb;
// End new stuff

static void rx_event(transfer_t *t)
{
    int len = rx_packet_size - ((t->status >> 16) & 0x7FFF);
    int i = t->callback_param;
    printf("rx event, len=%d, i=%d\n", len, i);
    if (len > 0) {
        // received a packet with data
        uint32_t head = rx_head;
        if (head != rx_tail) {
            // a previous packet is still buffered
            uint32_t ii = rx_list[head];
            uint32_t count = rx_count[ii];
            if (len <= CDC_RX_SIZE_480 - count) {
                // previous buffer has enough free space for this packet's data
                memcpy(rx_buffer + ii * CDC_RX_SIZE_480 + count,
                    rx_buffer + i * CDC_RX_SIZE_480, len);
                rx_count[ii] = count + len;
                rx_available += len;
                rx_queue_transfer(i);
                // TODO: trigger serialEvent
               // NEW >>>
               If (usb_rx_cb) (*usb_rx_cb)();
              // End new ..
                return;
            }
        }
        // add this packet to rx_list
        rx_count[i] = len;
        rx_index[i] = 0;
        if (++head > RX_NUM) head = 0;
        rx_list[head] = i;
        rx_head = head;
        rx_available += len;
        // TODO: trigger serialEvent
        // NEW >>>
        If (usb_rx_cb) (*usb_rx_cb)();
        // End new ..
    } else {
        // received a zero length packet
        rx_queue_transfer(i);
    }
}

I marked some quick and dirty edits, might compile, might not...
Then your sketch would need to add in the call to set the CB... and implement it...
not in header file, so would probably need extern "C" define for callback set function...
 
Not sure how this would be anything different than simply adding a lot of calls to yield()...

@ericscottf But if want the quickest response... And you don't mind editing the core code for every release... You could simply hack on cores\teensy4\usb_serial.c in the function rx_event(transfer_t *t)

And add in the ability to get called...
Something like:
Code:
// new stuff
void (usb_rx_cb)() = nullptr;
void set_usb_rx_cb( void (*cb)()) {
  usb_rx_cb = cb;
// End new stuff

static void rx_event(transfer_t *t)
{
    int len = rx_packet_size - ((t->status >> 16) & 0x7FFF);
    int i = t->callback_param;
    printf("rx event, len=%d, i=%d\n", len, i);
    if (len > 0) {
        // received a packet with data
        uint32_t head = rx_head;
        if (head != rx_tail) {
            // a previous packet is still buffered
            uint32_t ii = rx_list[head];
            uint32_t count = rx_count[ii];
            if (len <= CDC_RX_SIZE_480 - count) {
                // previous buffer has enough free space for this packet's data
                memcpy(rx_buffer + ii * CDC_RX_SIZE_480 + count,
                    rx_buffer + i * CDC_RX_SIZE_480, len);
                rx_count[ii] = count + len;
                rx_available += len;
                rx_queue_transfer(i);
                // TODO: trigger serialEvent
               // NEW >>>
               If (usb_rx_cb) (*usb_rx_cb)();
              // End new ..
                return;
            }
        }
        // add this packet to rx_list
        rx_count[i] = len;
        rx_index[i] = 0;
        if (++head > RX_NUM) head = 0;
        rx_list[head] = i;
        rx_head = head;
        rx_available += len;
        // TODO: trigger serialEvent
        // NEW >>>
        If (usb_rx_cb) (*usb_rx_cb)();
        // End new ..
    } else {
        // received a zero length packet
        rx_queue_transfer(i);
    }
}

I marked some quick and dirty edits, might compile, might not...
Then your sketch would need to add in the call to set the CB... and implement it...
not in header file, so would probably need extern "C" define for callback set function...
Trying to make this work, can you elaborate a bit more on what you said:
"Then your sketch would need to add in the call to set the CB... and implement it...
not in header file, so would probably need extern "C" define for callback set function..."
Doesn't compile as is, but I'm assuming that's because I'm missing things in my main sketch?

TYVM!
 
rying to make this work, can you elaborate a bit more on what you said:
"Then your sketch would need to add in the call to set the CB... and implement it...
not in header file, so would probably need extern "C" define for callback set function..."
Doesn't compile as is, but I'm assuming that's because I'm missing things in my main sketch?
In the code I did quick and dirty I added the function:
Code:
void set_usb_rx_cb( void (*cb)()) {
  usb_rx_cb = cb;
// End new stuff
In a C file...

So if you had some function in your sketch like:
void newSerialUSBDataCB () {
// Do your processing.
}

And in your setup code you wanted to setup to call this, you could add:
Code:
set_usb_rx_cb(&newSerialUSBDataCB);

The compiler would not find this function defined:
So above this you cold add:
Code:
extern void set_usb_rx_cb( void (*cb)());

But the system would expect that it would find one with the C++ naming convention, so instead you would add something like:
Code:
extern "C" void set_usb_rx_cb( void (*cb)());

or
Code:
extern "C" {
    extern void set_usb_rx_cb( void (*cb)());
}

To let the compiler know that you are calling a C function.
 
Back
Top