On Arduino IDE 2.3.2 serialEvent[x] does not work for teensy 3.1

emre2blue

Member
Hi,
I had a project on Teensy 3.1 and it worked perfectly without touching for almost 7 years. Now I need something to change in that. Like a PWM frequency. I've opened my file on Arduino IDE 2.3.2 . Changed the PWM freq. Upload without any error. Now it does not work :( .

* SerialEvent1 2 3 I was using it. But now I cannot get any data from any serial port. I can send messages but I cannot get data with interrupts.

I've tried simplest code but not working either

C:
char rd = 0;
void setup()
{
  Serial1.begin(115200, SERIAL_8N1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
void loop()
{
}
void serialEvent1(void)
{
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    rd = Serial1.read();
}

For testing the HARDWARE this code is running perfectly

C:
char rd = 0;
void setup()
{
  Serial1.begin(115200, SERIAL_8N1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
void loop()
{
  if (Serial1.available())
    {
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    rd = Serial1.read();
    }
}

So I know that TEENSY is ok. Uploading is ok. But there is something wrong in compiler or ???

PS: I'm using v1.59.00 on BoardManager.
* Also the setup is like this.
1723401805208.png
 
It looks like the two programs you posted are identical. Did you mean to post two different programs? I'm guessing, but don't you need to somehow "enable" event handling? I don't think simply defining SerialEvent1() is enough for it be executed.
 
It looks like the two programs you posted are identical. Did you mean to post two different programs? I'm guessing, but don't you need to somehow "enable" event handling? I don't think simply defining SerialEvent1() is enough for it be executed.
There was no need for preparations for SerialEventX() before. I've run the program for several years. But now I compile it with new IDE. I think it is compiler related.
 
There is a problem with T_3.2 and serialEvent1 processing ! IDE 1.8.19 and TD 1.59
Test NOTE: Jumper Pin 0 and Pin 1 on the test Teensy to see Tx return on same Serial1 UART Rx
Code below on a T_MM - working as expected with serialEvent1() getting called outside loop():
Code:
Hello USB World
Hello World
3301
6302
...
Build the same for Teensy 3.2 and the yield() call is not processing the serialEvent1() as it should and the data is found in loop() - in this case - 3 seconds later:
Code:
Hello USB World
LOOP USB: DATA WAITING >>>>>
Hello World

LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>>
3301

LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>>
6302

LOOP USB: DATA WAITING <<<<<

TEST CODE:
Code:
char rd = 0;
void setup()
{
  Serial.begin(115200);
  Serial.printf ("Hello USB World \n\r");
  Serial1.begin(115200, SERIAL_8N1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
elapsedMillis aTime;
void loop()
{
  if ( aTime > 3000 ) {
    if ( Serial1.available()) {
      Serial.printf ("LOOP USB: DATA WAITING >>>>> \n");
      serialEvent1();
      Serial.printf ("\nLOOP USB: DATA WAITING <<<<< \n\r");
    }
    Serial1.println(millis());
    aTime = 0;
  }
}
void serialEvent1(void)
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  while ( Serial1.available()) {
    rd = Serial1.read();
    Serial.print(rd);
  }
}

<EDIT>: Also Interesting
T_MM LED flickers ON and OFF - so not all data is read in a single call to serialEvent as it is still arriving due to fast response
On the T_3.2 the LED toggles each 3 seconds showing the data is cleared in a single call leaving the LED changing once per Rx group
 
Last edited:
@defragster.38915 I've tested your sample code. And I have the same 3 second delay.

Code:
LOOP USB: DATA WAITING >>>>> 27309
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 30310
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 33311
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 36312
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 39313
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 42314
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 45315
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 48316
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 51317
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 54318
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 57319
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 60320
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 63321
LOOP USB: DATA WAITING <<<<<
LOOP USB: DATA WAITING >>>>> 66322
LOOP USB: DATA WAITING <<<<<

So what should we do? I can re-organise my program for now. Because I need it to run. But for the Future project it should be fixed right ?
 
So what should we do? I can re-organise my program for now. Because I need it to run.
Indeed, there is a problem with the expected calling of serialEvent1() and it should be addressed in future release.

For now something like the p#4 code does and manually call the serialEvent1() code from loop().

There is nothing magic about serialEvent code - it is simply called from yield() and yield is called on every exit of loop(). Though yield() is also called from delay() and it is the yield code that seems to be broken.

If no other EVENT features of yield() are used you can provide your own and it will replace the WEAK default yield:
Doing that in the prior example (code below) on the T_3.2 gives the expected result where loop() never processes the UART data:
Code:
Hello USB World

Hello World
3301
6302
9303
// ...

Here a local copy of yield() is added to replace the one not working as it should. This excludes other features of yield() unless added - but those are EventResponder functions not typically used. If other SerialEvent#()'s are needed they would need to be added:
Code:
char rd = 0;
void yield() {
  serialEvent1();
}
void setup()
{
  Serial.begin(115200);
  Serial.printf ("Hello USB World \n\r");
  Serial1.begin(115200, SERIAL_8N1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
elapsedMillis aTime;
void loop()
{
  if ( aTime > 3000 ) {
    if ( Serial1.available()) {
      Serial.printf ("LOOP USB: DATA WAITING >>>>> \n");
      serialEvent1();
      Serial.printf ("\nLOOP USB: DATA WAITING <<<<< \n\r");
    }
    Serial1.println(millis());
    aTime = 0;
  }
}
void serialEvent1(void)
{
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  while ( Serial1.available()) {
    rd = Serial1.read();
    Serial.print(rd);
  }
}
 
So what should we do? I can re-organise my program for now. Because I need it to run. But for the Future project it should be fixed right ?
Yes it should probably be fixed.

There is a problem with T_3.2 and serialEvent1 processing ! IDE 1.8.19 and TD 1.59
Test NOTE: Jumper Pin 0 and Pin 1 on the test Teensy to see Tx return on same Serial1 UART Rx
Code below on a T_MM - working as expected with serialEvent1() getting called outside loop():
Is it specific to T3.2, or I would assume all Teensy 3.x?

Has anyone tried to debug it?

I ask the first question as wondering if I have to dig through my semi-organized parts and projects for a T3.2. Organized means I typically cannot find anything 😆 Or just the first T3.x I run into.
 
I did a quick and dirty with a 3.5... first 3.x I found...
Simple sketch:
C++:
//extern uint8_t _serialEvent1_default;
//extern uint8_t _serialEvent2_default;

void setup() {
    while (!Serial) {}
    Serial.begin(115200);
    Serial1.begin(115200);
//    Serial2.begin(115200);

//    Serial.println(_serialEvent1_default);
//    Serial.println(_serialEvent2_default);
}

void loop() {
    int ch = Serial.read();
    if (ch != -1) Serial1.write(ch);
}

void serialEvent1() {
    Serial.write(Serial1.read());
}

I was first checking to see if: _serialEvent1_default (and 2 as well for good measure)

Sort of a hack that if you don't define some of the serialEventX objects, there will be a default one
defined and default has it defined as: uint8_t _serialEvent1_default PROGMEM = 1;
If you define the serialEvent1 the serialEvent1.cpp object will not be included in your build:
so instead the one in HardwareSerial.cpp: uint8_t _serialEvent1_default __attribute__((weak)) PROGMEM = 0 ;
Will win...

When Serial1.begin() is called it checks the value of _serialEvent1_default if it is 0.
it sets a few variables, that are checked by the yield calls.
Such that when yield is called:
C++:
void yield(void)
{
    static uint8_t running=0;
    if (!yield_active_check_flags) return;    // nothing to do
    if (running) return; // TODO: does this need to be atomic?
    running = 1;
...

it can bail quickly with: yield_active_check_flags


1723469396777.png
 
Since I could not find a T3.2 I did pretty much what @KurtE but with a slight mod to his sketch to dup what the sketch in post 1 was trying to do.
Code:
void setup()
{
  Serial1.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  delay(2000);
  Serial1.printf ("Hello World from setup\n");
}
void loop()
{
    Serial1.printf("Hello World from loop\n");
    delay(100);

}

void serialEvent1(void)
{
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
    String incomingString = Serial1.readStringUntil('\n');
    Serial.println(incomingString);
}

I added the delay(2000) to allow the serial monitor time to open and register and then in the loop added the delay just to watch it blink.

1723474793468.png
 
Ditto - just dug one out as well and retested
I should mention that I am running the nightly build of IDE 2.x. In my case the one dated 20240630
Teensy version 1.59.0

However my cores are running off current sources, so potentially there could have been something that changed
since the last teensy release. In addition to that I am running my own Fork/Branch where I added in some support
for Teensy 4.x variants. I have not made any changes to the teeny3 cores, so should be the same as PaulStoffregen master
branch.
 
However my cores are running off current sources, so potentially there could have been something that changed
since the last teensy release. In addition to that I am running my own Fork/Branch where I added in some support
for Teensy 4.x variants. I have not made any changes to the teeny3 cores, so should be the same as PaulStoffregen master
branch.
I tried it with both IDE 2.3.3 from 2/21/24 nightly as well IDE 1.8.19 which is not using your modified core files just in case.
 
Not seeing what to change in p#4 or p#6 {Remove local yield()} code to make it work?
It was 2AM - only tried on IDE 1.8.19 with T_3.2 - did not look at sources - knew it was due to yield() minimization excluding it for some reason.

Please run posted code and if it works - there is something different in the build this code fails to have a yield() call from outside loop, even adding a delay(1) in loop().

This matches and exemplifies the reported behavior - only loop() calls serialEvent1(), and indeed something about the code after p#6 seems to work - but there is some unseen difference?

Code:
//void yield() {   serialEvent1(); }
// extern uint8_t _serialEvent1_default;
void setup()
{
  Serial.begin(115200);
  Serial.printf ("Hello USB World \n\r");
  Serial1.begin(115200, SERIAL_8N1);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
elapsedMillis aTime;
void loop()
{
  if ( aTime > 3000 ) {
    if ( Serial1.available()) {
      Serial.printf ("LOOP USB: DATA WAITING >>>>> \n");
      serialEvent1();
      Serial.printf ("\nLOOP USB: DATA WAITING <<<<< \n\r");
    }
    Serial1.println(millis());
    aTime = 0;
  }
  delay(1);
}
void serialEvent1(void)
{
  char rd = 0;
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  while ( Serial1.available()) {
    rd = Serial1.read();
    Serial.print(rd);
  }
}
 
Did a bit more playing/digging since the original post was curious about taking input and converting the value to say an integer from serial1 and doing something with it. According the specs SerialEvent is automatically called at the end of loop so no need to call it as done in previous post - kind of just makes it a function call. The following sketch will write millis to Serial1 every 3 seconds and then print it out to the serial monitor
Code:
/* basic sketch idea from:
 * https://forum.arduino.cc/t/read-serial-data-rs232-and-convert-numbers-of-the-string-to-integer/382893/4
 *
 * strtol: converts byte string to int: https://en.cppreference.com/w/cpp/string/byte/strtol
 *
 */

String inputString = ""; // a string to hold incoming data
boolean stringComplete = false; // whether the string is complete
int measurement_int = 0;
char inputStringChar[50];

void setup()
{
  // initialize serial:
  Serial.begin(9600);
  Serial1.begin(9600);

  // reserve 80 bytes for the inputString:
  inputString.reserve(80);

  Serial1.println(millis());
}

void loop()
{
  // print the string when a newline arrives:
  if (stringComplete) {
    //Serial.println(inputString);
    char * p_end;
    measurement_int = strtol(inputString.c_str(), &p_end, 10);//converts data to integer
    Serial.println(measurement_int);
    // clear the string:
    inputString = "";
    stringComplete = false;
  }
  delay(3000);
  Serial1.println(millis());
}

void serialEvent1()
{
  while (Serial1.available())
  {
    // get the new byte:
    char inChar = (char)Serial1.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;
      inputString.toCharArray(inputStringChar, 50);
    }
  }
}

Note: this is a pretty good tutorial if you dont want to use serial event: https://forum.arduino.cc/t/serial-input-basics-updated/382007
 
Side note: Arduino has more or less ditched the SerialEvent concept. Or at least more or less all of their newer board cores, like UNO R4, GIGA, etc do not support it. And issues raised against them were closed out as won't fix (by design)
Makes sense - in Early days here it seemed like magic - but it is just called as noted outside each loop(), yield() and on delay() calls. Not interrupt 'Event' or other 'something for nothing' transfer. Having it in those places can assure 'busy code' can keep Rx buffers in check without other overt code calls - or it could introduce surprise 'waits'.

As the OP and @defragster code here demo the T_3.2 no longer has it working in the simple cases shown - if other posted code works it isn't clear what the difference is to make it trigger versus fail.
 
This seems to work with a couple of caveats:

Code:
void setup()
{
  Serial1.begin(115200);
  delay(2000);
  Serial1.printf ("Hello World from setup!!!!\n\r");
  Serial1.printf ("Hello Teensy from setup!!!!\n\r");
}

void loop()
{
}

char inChar = 0;
void serialEvent1() {
    while (Serial1.available()) {
      inChar = (char)Serial1.read();
      Serial.print(inChar);
    }
}

Specifying Serial_8n1 seems to get it confused and removed trying to blink LED - seems it just goes on when it gets called??
 
This seems to work with a couple of caveats:

Specifying Serial_8n1 seems to get it confused and removed trying to blink LED - seems it just goes on when it gets called??
You found it @mjs513 ! No more "DATA WAITING"
Code:
Hello USB World

Hello World
3301
6302
9303
// ...

Taking the 8N1 off the .begin() and it is working!
Something in the Teensy3 CORES distinguishes that added option PARAM as 'Not Using Serial1'
Prior code with that commented out works as expected:

Code:
//void yield() {   serialEvent1(); }
void setup()
{
  Serial.begin(115200);
  Serial.printf ("Hello USB World \n\r");
  //  Serial1.begin(115200, SERIAL_8N1);
  Serial1.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial1.printf ("Hello World \n\r");
}
elapsedMillis aTime;
void loop()
{
  if ( aTime > 3000 ) {
    if ( Serial1.available()) {
      Serial.printf ("LOOP USB: DATA WAITING >>>>> \n");
      serialEvent1();
      Serial.printf ("\nLOOP USB: DATA WAITING <<<<< \n\r");
    }
    Serial1.println(millis());
    aTime = 0;
  }
  delay(1);
}
void serialEvent1(void)
{
  char rd = 0;
  digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
  while ( Serial1.available()) {
    rd = Serial1.read();
    Serial.print(rd);
  }
}
 
Specifying Serial_8n1 seems to get it confused and removed trying to blink LED - seems it just goes on when it gets called??

Oops yes that would explain it...

To understand it, it is best to look at the sources. I did most of this work mainly for Teensy 4.x, where I had just one implementation and the c++
was not just a wrapper to call c functions... Sort of subtle. I will show the code for Serial2, mainly to avoid a few ifdefs and the like for Serial1, but same issue for all of them...

Verbose Description:

In HardwareSerial.h we have:
C++:
class HardwareSerial2 : public HardwareSerial
{
public:
    constexpr HardwareSerial2(void (* const se)()) : HardwareSerial(se) {}
    virtual void begin(uint32_t baud);
    virtual void begin(uint32_t baud, uint32_t format) {
                      serial2_begin(BAUD2DIV2(baud));
                      serial2_format(format); }
    virtual void end(void)        { serial2_end(); }
    virtual void transmitterEnable(uint8_t pin) { serial2_set_transmit_pin(pin); }

In HardwareSerial2.cpp we have:
Code:
#include <Arduino.h>
#include "HardwareSerial.h"

HardwareSerial2 Serial2(&serialEvent2);

uint8_t _serialEvent2_default __attribute__((weak)) PROGMEM = 0 ;

void HardwareSerial2::begin(uint32_t baud) {
    serial2_begin(BAUD2DIV2(baud));
    if (!_serialEvent2_default) addToSerialEventsList();
}

There is also Serial2.c - which has all of the c code for Serial2...

And in file: SerialEvent2.cpp we have:
C++:
#include <Arduino.h>
#include "HardwareSerial.h"
void serialEvent2() __attribute__((weak));
void serialEvent2() {}        // No use calling this so disable if called...
uint8_t _serialEvent2_default PROGMEM = 1;
Things are split up into multiple files like this, as if a sketch does not reference Serial2, then as core being built as a library, then the code of HardwareSerial2.cpp and Serial2.c will not be included in the sketch, including all of the buffers. The yield code is setup to have a table of callbacks and the like for the SerialEventX objects...

If your code has for example serialEvent2 defined then the code in SerialEvent2.cpp will not be included:
As such the define uint8_t _serialEvent2_default PROGMEM = 1; wont be used, instead the one in
HardwareSerial2.cpp: uint8_t _serialEvent2_default __attribute__((weak)) PROGMEM = 0 ;
will be.

And the main begin method:
Code:
void HardwareSerial2::begin(uint32_t baud) {
    serial2_begin(BAUD2DIV2(baud));
    if (!_serialEvent2_default) addToSerialEventsList();
}
Continue to the short version:

Short Version:
With the Teensy 4.x implementation of all of this.

There is just one begin method:
Code:
class HardwareSerial : public Stream
{
public:
    virtual void begin(uint32_t baud, uint16_t format=0) = 0;
Which does that stuff.

The issue with the T3.x stuff is there are two:
Code:
class HardwareSerial2 : public HardwareSerial
{
public:
    constexpr HardwareSerial2(void (* const se)()) : HardwareSerial(se) {}
    virtual void begin(uint32_t baud);
    virtual void begin(uint32_t baud, uint32_t format) {
                      serial2_begin(BAUD2DIV2(baud));
                      serial2_format(format); }
    virtual void end(void)        { serial2_end(); }
...

The version with the format bypasses the code in the c++ main begin method...
Probably best fix is to change them like:
Code:
class HardwareSerial2 : public HardwareSerial
{
public:
    constexpr HardwareSerial2(void (* const se)()) : HardwareSerial(se) {}
    virtual void begin(uint32_t baud);
    virtual void begin(uint32_t baud, uint32_t format) {
                      begin(baud);
                      serial2_format(format); }
    virtual void end(void)        { serial2_end(); }
...
 
The version with the format bypasses the code in the c++ main begin method...
Probably best fix is to change them like:
Code:
class HardwareSerial2 : public HardwareSerial
{
public:
    constexpr HardwareSerial2(void (* const se)()) : HardwareSerial(se) {}
    virtual void begin(uint32_t baud);
    virtual void begin(uint32_t baud, uint32_t format) {
                      begin(baud);
                      serial2_format(format); }
    virtual void end(void)        { serial2_end(); }
...
Get rid of the second begin method, add format parameter to the first method with a default value so it doesn't have to be explicitly given? That should work with all existing code...
 
Like the T4.x code...
I pushed up changes for this into my fork/branch: https://github.com/KurtE/cores/tree/variants_override
Fix T3.x SerialEventX when format is passed in.
Code:
Before there were two different begin Methods that were wrappers to the c begin and format functions.
The code that checked for SerialEvent being defined in a sketch was only in the begin method where format was not passed in.
Changed these to have only one begin method, with an optional format descriptor If format is not specified I pass in: (uint32_t)-1
which the begin method checks for if it is -1 format is not called otherwise it is. Note: with T4.x the default was always 0 and the format
code was always done, but did not want to this here on the off chance there are T3.x sketches out there where maybe the first call
 SerialX.begin(baud, some-not-default-format)... and later call SerialX.begin(baud), and expected the format not to change.

7 changed files
teensy3\HardwareSerial.h
teensy3\HardwareSerial1.cpp
teensy3\HardwareSerial2.cpp
teensy3\HardwareSerial3.cpp
teensy3\HardwareSerial4.cpp
teensy3\HardwareSerial5.cpp
teensy3\HardwareSerial6.cpp
teensy3\HardwareSerial.h

Which part of a PR I have: #750
Or the changes can be cherry picked if other stuff in PR is not pulled in.

Edit: Should mention I tested with simple sketch:
C++:
#define SERIALX Serial5
void setup() {
    while (!Serial) {}
    Serial.begin(115200);
    SERIALX.begin(115200, SERIAL_8N1);
//    Serial2.begin(115200);

//    Serial.println(_serialEvent1_default);
//    Serial.println(_serialEvent2_default);
}

void loop() {
    int ch = Serial.read();
    if (ch != -1) SERIALX.write(ch);
}

void serialEvent5() {
    Serial.write(SERIALX.read());
}
Where I would edit what SerialX was and the name of the SerialEvent...
I tried on T3.5 for 1-5... Did not try 6 as... too lazy to find one where I broke out the pins on the bottom of the chip
 
Where I would edit what SerialX was and the name of the SerialEvent...
I tried on T3.5 for 1-5... Did not try 6 as... too lazy to find one where I broke out the pins on the bottom of the chip
Just tested as well with my simple sketch in post#18 with the exception I added SERIAL_8N1 to the Serial.begin statement and as expected it worked :) Nice job.
 
Back
Top