Teensy 3.1 - Correct way to use interrupts on received Serial data

Status
Not open for further replies.

dmhummel

Well-known member
There seem to be a few potential ways to fire an interrupt on data arriving on a Serial port. Which is the most robust/correct way? I am handling three different Serial communications separately Serial1,2,3.
 
probably write your own handlers that is specific to what you need but you haven't given an example of what your trying to do (Forum Rule)?
 
probably write your own handlers that is specific to what you need but you haven't given an example of what your trying to do (Forum Rule)?

I simply want to see an example of the best practice way to enable the handler. I am comfortable with the code I will put in the handler, probably just read from the buffer and switch a volatile flag for use elsewhere.

For what I'm trying to do -- I am controlling a series of actuators that communicate via Serial. The provide responses at unpredictable times so the best approach for me is to implement a handler that checks the serial buffer at the right times. Right now I check the serial buffer right before I send my next command, but that is not a very good approach. Interrupts would be better. I was going to try a simple Arduino style FALLING pin interrupt on the RX pin, but I suspect there is a better way that is more native to this processor and the way it handles serial.
 
Last edited:
crude/effective: Serialn.available() called as needed
new method I read exists: Serial events addition to library

Overkill: FreeRTOS multitasking and a task suspends until serial received.
 
you can use the builtin serialEvent to trigger a callback when data is available. It works from the yield function which is called throughout the core so it will simulate using the actual isr. You can call yield() in your own functions to.
 
you can use the builtin serialEvent to trigger a callback when data is available. It works from the yield function which is called throughout the core so it will simulate using the actual isr. You can call yield() in your own functions to.
Where's that API documented?
 
Where's that API documented?

here: http://arduino.cc/en/Reference/SerialEvent
here: http://arduino.cc/en/Tutorial/SerialEvent
here: https://groups.google.com/a/arduino.cc/forum/#!topic/developers/-joR59Xp6ho

and a cheap example:
Code:
char buffer[1000];


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.begin(115200);
  while(!Serial);
  delay(1000);
  Serial.println("Arduino serialEvent Example");
}


void loop() {
  delay(1000);
}


void serialEvent1() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  int i = 0;
  while(Serial1.available()) buffer[i++] = Serial1.read();
  buffer[i] = 0;
  Serial.print(buffer);
}
 
Good Info duff. Both the impact/value of yield() and the ease of use of serialEvent1(). Based on the .cc/tutorial it has a more complete example showing you don't have to be afraid of doing work in the serialEvent1() function like you would in the case of true interrupt code. Also the tutorial shows since it takes [yield() or] exiting loop() you don't have to worry with disabling interrupts when working with flag values that can be set in the serialEvent1() code. Also no explicit setup of the interrupt or function to be called!
 
High Speed serialEvent() loop back

I took two wires on EACH an LC and 3.1 from 0:RX1 to 8:TX3 and 1TX1 to 7:RX3 to make a loop from Serial1 to Serial3 [ I want to use SPI so Serial2 and #9&#10 were used ]

Using the above info I created a sketch with SerialEvent #1 and #3 and made a demo of having the Event() handler pulling the characters outside loop() and then just checking for the newline to have been received completing a string.

I got my 3.1 to run at 921600 and the LC to run at 220000 (it corrupted at 230400). I may be losing some strings (the Event()_While should exit on \n as it sets the flag), but I don't see obvious signs of it. My only recovery attempt works - if the Ping Pong stops for 5 seconds the one port sends a RESTART, this only happens when I pull out on of the wires. This Restart event writes this string and also adds 1,000 to another ping pong counter that should indicate strings coming and going balance out when not lost. Ideally I would have extended this to the count of '\n' in the Event code. [but once you get behind the curve - you'll never catch up]

I started with a 'Serial Event example' and ping pong when Serial1 gets a '\n' it showed on USB and wrote to Serial3. To make sure USB didn't slow things down I ended with it showing strings only every 10th millis() incoming - the spew was too fast to scan anyhow. In the attached code I put in a periodic delay() that is now commented out, but gave a short Serial rest on alternate ports.

This was just to prove the concept on each Teensy - but would be the basis of two Teensy's connected over two serial ports for debug spew when one had a spare SPI TFT display. All the incoming strings are buffered outside the loop [ I didn't insert any yield() calls as my loop() is short and USB events may cause a yield already]

There is nothing graceful about this code - I just hacked a duplicate of what was in the sample and added variables willy-nilly when I thought to extend something - and then I quit.
View attachment SerialEventDual.ino
 
Last edited:
Need DMA on the UARTs at those speeds. Without it, and with no FIFOs, way too many interrupts per second.
 
This is a little off track, but that serialEvent() example in the Arduino tutorial seems not robust -- the received string isn't terminated with a '0', and if another char comes in between the StringComplete and the loop, it will be appended -- therefore the received string isn't guaranteed to end with a '\n'. Is this a better way ?

Code:
void serialEvent() {
  while (Serial.available()[COLOR=#ff0000] && !stringComplete[/COLOR]) { [COLOR=#0000ff]// depend on[/COLOR][COLOR=#0000ff] code in loop() to clear this flag[/COLOR]
    // get the new byte:
    char inChar = (char)Serial.read(); 
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
      [COLOR=#ff0000]inputString += '\0';[/COLOR]
     } 
  }
}
 
Jp3141 -
This was my first sighting of that code construct :: inputString += inChar; and it apparently works to treat the indicated memory as a null terminated string. You'll note that after printing it empties the string with inputString1 = "";

As far as two strings at once - somehow that isn't happening - the timing is such that even at these speeds it is bounding on the '\n' - otherwise my USB print would show the partial next string.

stevech -
ran over night on my 3.1 at 921600 baud with no apparent hitches - the only break was when I yanked the 'cable' and it forced a restart. So the onboard FIFO's and software used are up to the task. As noted I throttled 10:1 the USB output back to prevent that I/O from messing up the serial.
My problem on the LC may very well have been a bad multiplier math? I'm not sure they both have the same FIFO support on all ports either.
 
serialEvent() is difficult to find in the main web pages for the Arduino library documentation.


@defragster: But at these UART speeds, what's the interrupt rate and %CPU used in ISRs and so on. Esp. on the LC.

serialEvent()
I'm accustomed to reading a string with the '\n' replaced with a '\0'.
Also, does serialEvent() check for max string length overrun?

And of course, not all incoming serial data is text/string (vs. 8 bit unsigned chars). Need a mode flag.
 
Last edited:
This was just a proof of concept and example for the Serial Event. There isn't much on it - but it works amazingly simply and amazingly well so not much to say.

The \n is stored In the string and it must Null terminate or the USB prints would fail, it also maintains the index for end of string so the code is re-reentrant on partial strings, but the time gaps on the 'println' must match the receiving pretty well as I haven't seen chars buffered after the \n because they would print

As far a speed - worst case you might BURST at these speeds and it works - but if you are running the Serial at these rates you are not doing much else. Good thing was both send and receive using the #1 and #3 hardware could do what they did - and they did it on the same Teensy at the same time. So in practice as a Debug tool or real interface - the sender or receiver only does half the work - and then doesn't spit the same strings to USB either.

Again it wasn't made to be pretty - just took the example and made it do this which seems simple and effective to answer the OP question - the Teensy Serial IO can be done outside the main loop at high speed without major impact and without complicated attention to interrupts or trying to improve it.
 
As a [contrived] demo of the potential and use of serialEvent() - this worked well with 2 ports on one Teensy. I just cross wired two 220000baud ports from 3.1 to LC and they work as it was written 188K messages (times 2 paired ports) passed in 9 minutes - 3 milliseconds between ~19 char messages.

For real use, YMMV. It has some obvious problems.-Thinking of making a MarkII version because splitting the wires from LC:LC and 3.1:3.1 as LC:3.1 and 3.1:LC it can see one not keeping pace with the other after power cycling one. Lost data as one passes the NL and reads into the next message - restart is also broken as the ping/pong restart only hits one channel pair - and with two devices I now have two pairs that no longer share the same routes but do share restart variables.

Serial data transfer was trivial - stripping ends of two wires to join two pairs of pins was easy thanks to the Teensy Card! I have the ease and reliability of interrupt background task, with about zero effort. Some effort to add more robustness depends on the needs and constraints. And options for means of transmission and the needs.

I just found I had placed a delay(5) that was representing 50% of the processor power reserved. Because without the delay the 3.1 is hitting 2 msec/msg and the LC is at 3 msc/msg. As noted the two linked slows to 3msec per message [xmit and rcv on each pair] and the 1 message lost out of 400,000 was probably on startup.

Oh - and I'd take back in part my
... if you are running the Serial at these rates you are not doing much else.
because with both send and rcv on each message the LC has some spare cycles left thanks to the FIFO's - the 3.1 doubly so.
 
Last edited:
This seems like a very robust platform where the underlying library gives a great user friendly 'interrupt' driven utility without the hazards of rolling your own - as long as you respect the buffers to hardware

Found I had a delay(5) on both transmit spots [I had removed one and the asymmetry was tolerated] - removed the second and the average time between \n's is 1.14ms/msg at 22 char msg (longer now that millis() == 151971346) - it include the Millis() so coming up on 42hrs running both legs LC::3.1 and 3.1::LC - where one leg then feeds the other as each device #1 and #3 receive on one and print on the other.

Minor edits to check failure on each leg. Only comm break was when one wire popped out - I put it back in and it recovered that leg recording the missed messages. Because on leg feeds the other the miss counts match in both devices. Retry messages hit mostly the broken leg 10 times on one unit and the other unit did 1 and the other two legs didn't retry - the 3.1 hit 5 seconds first and it's retry kept the others happy but aware.

Lots of room for code improvement depending on real use case - as this is a four port ping pong match over two devices means two players with two paddles each - and two balls in play.

Minor tweaks two separate the two legs inside each Teensy, for recovery - but no integrity check other than msg ends on \n and each one counted and once received it sends out the other port - not back to where it came. And I stopped reading once a \n was received and didn't read again until the response was set out the other port. And on 5 sec failure to receive I waited 1 sec between Restart messages.

The serial is buffered - but my short messages (as I read the serial sources) fit in the Tx==40 (on #3) and Rx==64 byte default buffer - so the hardware handles each message in a buffer, things would be different out side those ranges, or repeated messages without the alternate send/rcv ping pong.

Quite a lot of interaction in a simple test case. Ideally there would be a second receive buffer reading while waiting for the send response to transmit - but I'd want to do that with cleaner arrays than hard coded indexes - including the serial port instances which require duplicate code with alternate references. You might guess I'm enjoying this after 17 years of vacation from coding. These Teensy's are cool and way more powerful than my first computer [CPU: Zilog Z80 @ 1.774 MHz, Memory: 4 KB ~ 48 KB ] - and way less expensive [$700].
 
Hi All,

It's probably too late to ask a question here...
Code:
char buffer[1000];


void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.begin(115200);
  while(!Serial);
  delay(1000);
  Serial.println("Arduino serialEvent Example");
}


void loop() {
  delay(1000);
}


void serialEvent1() {
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  int i = 0;
  while(Serial1.available()) buffer[i++] = Serial1.read();
  buffer[i] = 0;
  Serial.print(buffer);
}
Can I use interrupts in this case?
115k interrupts per second sounds slightly like an overkill...
 
Hi All,

It's probably too late to ask a question here...

Can I use interrupts in this case?
115k interrupts per second sounds slightly like an overkill...

Not sure what the question is? Or if this presents a usable answer :)

serialEvent1() is not based on interrupts - just called on each exit of loop() or use of delay() in user code. The Teensy 3.1/3.2 Serial1 hardware has an 8 byte FIFO and uses interrupts as needed to keep data flowing in and out of the buffer space for read or write.

But - yes in general interrupts can surely be used where useful or supported. For a Teensy 115K baud isn't very fast - it can generally do 2 or 3 M baud reliably along with other tasks interrupt or otherwise.
 
Thank you very much for your answer!

My assumption was that serialEvent1() was based on interrupts.

I'm trying to do a serial logger, which stores packets to SD card.
Unfortunately, sometimes it takes too much time to write to SD card, so it looses packets, that's how I got here :)

Thanks a lot!
 
I'm trying to do a serial logger, which stores packets to SD card.
Unfortunately, sometimes it takes too much time to write to SD card, so it looses packets, that's how I got here :)

classic solution is:
poll the serial line at interrupt level (e.g. with periodic interrupt) and store data on a 'large' buffer that is capable to store at least 100 ms of data (1 s is better)
at loop level poll the data store and write to uSD card in junks of 512 bytes.
 
Perhaps worth to mention that it would be 1152 interrupts/second.. 115200 BITs/Seconds .. 8 Bit per char +Startbit + Stopbit.. = 10 BIT, 1 Interrupt per char. (A bit more than 1KB/second - really slow for a Teensy 3.x).

But still, just use Serial.available() - or SerialEvent ;) , and Serial1 (has largest FIFO+ Buffer) - no need for interrupts here.
 
Thank you for your suggestions!
Frank,
This was my first initial approach and it works in 99% of time, but I still have lost/corrupted packets from time to time(really depends on a performance of SD card).
Collecting packets and writing them in chunks, as WMXZ described definitely improves the picture, but looks like I still need some sort of interrupt here.
WMXZ, do you mean https://www.pjrc.com/teensy/td_timing_IntervalTimer.html ?
Just wanted to make sure I understood the idea...
 
Status
Not open for further replies.
Back
Top