Teensy 4.1 freezes after receiving serial command

Doodads

Member
I am replacing an Arduino Uno with a Teensy 4.1, but I'm having trouble with serial communication. On the Uno, when I send "CHAN" from the serial monitor, the Arduino responds with "#OK." Using the same sketch on the Teensy, the Teensy seems to freeze after receiving "CHAN" and becomes unresponsive. To upload new code to the Teensy after it freezes, I have to press the physical program button.

The end goal is to have the Teensy read thermocouple data and send that data to coffee roasting software on my Windows 10 machine. The coffee roasting software uses the "CHAN" message to initialize before sending a "READ" command to request temperature data.

I did some searching on this forum and tried adding "delay(10)" to the loop without success. I have the Teensy 4.1 selected under "Teensy Ports."

I have tried the "HelloSerialMonitor" Teensy example code and it works.

Source code:
Code:
//Arduino --> Artisan Roaster Scope Communication Test
// TC4 Communication Code adapted from FilePhil https://github.com/FilePhil/TC4-Emulator/blob/master/TC4-Emulator.ino


bool unit_F = false; //true = °F - false = °C

const long BAUD = 115200;

double temp1 = 101;
double temp2 = 102;
double temp3 = 103;
double temp4 = 104;
String msg;

// These variables are used for serial data input and parsing. Taken from example 4 and 5 of Robin2's thread "Serial Input Basics - updated" https://forum.arduino.cc/index.php?topic=396450.0
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromPC[numChars] = {0};

int integerFromPC = 0;
float floatFromPC = 0.0;


boolean newData = false;
// end serial variables


void recvWithEndMarker() { //changed example 5 to eliminate start markers - Artisan uses newlines only, no start markers.
  //removed "recvInProgress" because it's not used in recvwithEndMarker - will this cause issues elsewhere?
  static byte ndx = 0;
  char endMarker = '\n';
  char rc;

  while (Serial.available() > 0) {
    rc = Serial.read();

    if (rc != endMarker) {
      receivedChars[ndx] = rc;
      ndx++;
      if (ndx >= numChars) {
        ndx = numChars - 1;
      }
    }
    else {
      receivedChars[ndx] = '\0'; // terminate the string
      ndx = 0;
      newData = true;
    }
  }
}

//============

void parseData() {      // split the data into its parts
  // Artisan lines to parse: "CHAN;ijkl" "UNITS;u" "READ"

  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars, ";");     // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
  //Serial.println(messageFromPC);

  if (strcmp(messageFromPC, "READ") == 0) {
    Command_READ();
  }
  else if (strcmp(messageFromPC, "CHAN") == 0) {
    Serial.println("#OK");
  }
  else if (strcmp(messageFromPC, "UNITS") == 0) {
    strtokIndx = strtok(NULL, ";");
    //Serial.println(strtokIndx);

    if (strcmp(strtokIndx, "F") == 0) {
      unit_F = true;
      Serial.println("#OK Fahrenheit");
    }
    else if (strcmp(strtokIndx, "C") == 0) {
      unit_F = false;
      Serial.println("#OK Celsius");
    }
  }
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integerFromPC = atoi(strtokIndx);     // convert this part to an integer

  strtokIndx = strtok(NULL, ",");
  floatFromPC = atof(strtokIndx);     // convert this part to a float

}

//Send Data
void Command_READ() {
  Serial.print("0.00,");
  Serial.print(temp1);
  Serial.print(",");
  Serial.print(temp2);
  Serial.print(",");
  Serial.print(temp3);
  Serial.print(",");
  Serial.println(temp4);
}

//Parsing Serial Commands
void handleSerialCommand() {

  recvWithEndMarker();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    //showParsedData();
    newData = false;
  }
}

void setup() {
  Serial.begin(BAUD);
}


void loop() {
  handleSerialCommand();
  delay(10);
}

Error messages: When trying to upload after using the serial monitor (and freezing the Teensy) I get this message:
"Unable to open COM4 for reboot request
Windows Error Info: Access is denied.
more ideas... https://forum.pjrc.com/threads/40632?p=126667&viewfull=1#post126667
Teensy did not respond to a USB-based request to enter program mode.
Please press the PROGRAM MODE BUTTON on your Teensy to upload your sketch."

Hardware: Teensy 4.1, Windows 10 PC

Wiring: Teensy 4.1 with Micro USB cable plugged into PC, no other wires connected.

Software: Arduino IDE 1.8.13, Teensyduino 1.53
 
Pretty amazing that this works on a UNO:
Code:
  [COLOR=#ff0000]strtokIndx[/COLOR] = strtok([COLOR=#ff0000]NULL[/COLOR], ","); // this continues where the previous call left off
  integerFromPC = atoi([COLOR=#ff0000]strtokIndx[/COLOR]);     // convert this part to an integer

  [COLOR=#ff0000]strtokIndx[/COLOR] = strtok([COLOR=#ff0000]NULL[/COLOR], ",");
  floatFromPC = atof([COLOR=#ff0000]strtokIndx[/COLOR]);     // convert this part to a float

Doodads:
It will crash on the UNO, too.
You just don't see it.
The uno is prints the texts over a serial connection. The Teensy uses USB, async.
If you add a delay(1); just before the marked lines above, the Teensy acts like a UNO, prints the texts, but will still crash (after that).

The crash happens because of the NULL pointers. So this needs a rework.
 
Last edited:
@Defragster, @Paul:

Hehe, with #include <T4_PowerButton.h> it shows a Nullpointer-Exception.
Without the include, it dies silent.
 
Yes, parts of it, not the whole lib.
Maybe this, with the "power button" functionality added.

...and perhaps it should show a text that says more clearly that it was a nullptr?
Code:
Hardfault.
Return Address: 0x000045A0 IPSR:0x03 CSFR: 0x00000082 XPSR: 0x81000000
    (DACCVIOL) Data Access Violation
    (MMARVALID) Accessed Address: 0x00000000
 
Pretty amazing that this works on a UNO:
Code:
  [COLOR=#ff0000]strtokIndx[/COLOR] = strtok([COLOR=#ff0000]NULL[/COLOR], ","); // this continues where the previous call left off
  integerFromPC = atoi([COLOR=#ff0000]strtokIndx[/COLOR]);     // convert this part to an integer

  [COLOR=#ff0000]strtokIndx[/COLOR] = strtok([COLOR=#ff0000]NULL[/COLOR], ",");
  floatFromPC = atof([COLOR=#ff0000]strtokIndx[/COLOR]);     // convert this part to a float

Doodads:
It will crash on the UNO, too.
You just don't see it.
The uno is prints the texts over a serial connection. The Teensy uses USB, async.
If you add a delay(1); just before the marked lines above, the Teensy acts like a UNO, prints the texts, but will still crash (after that).

The crash happens because of the NULL pointers. So this needs a rework.

I added delay(1) as you suggested and sure enough, it printed! And yep, it crashed right after.

So the problem is the NULL pointers - I thought this was standard practice for continuing to use the strtok() function after passing it a string?

This code does work multiple times on the UNO - I can send "CHAN," "READ," etc without causing a crash (or perhaps the UNO crashes and then recovers without me noticing?). Is it the difference in USB communication method that allows it to work on the UNO? I'm not familiar with the behavior of USB asynchronous vs serial.
 
<edit>: I agree this should be provided in the core with a way to activate it for display on boot. Working on this in prior Beta's (with debug_tt) it seemed like it would be useful - but there were so few reports of FAULT like behavior - and while trapping the error could often still print to USB before it hung - it was not as elegant a solution as storing the info in 'static' RAM and displaying it on restart when the Teensy returned to full function in all cases! And providing an external library and needing to connect it was non-trivial.

Paul added code mem exception control to enforce NULL pointer failure on T_4.x's. Before that Teensy 4.x would not care about NULL pointer use either. It was up to the code at hand to 'suffer the consequences' There were a few odd 'HANGS' when that was put in that had been working before.

It may work on the UNO because it is 'ignored' and until the NULL pointer abuse causes lasting functional damage it continues to 'appear to be running well'.

One other useful note KurtE drops with regard to debugging is when a spot in code is suspected as the failure point and no SerMon output appears add : Serial.flush() after the Serial.print("debug this note") which in place of the delay(1) - blocks the code until the output is transmitted. If it doesn't show then - it is failing or 'damaged' before that spot.

If this is the right stuff - also Frank - looks like you could test fault code working against the second - Stack OverFlow? {from T4's startup.c}::
Code:
...
	// TODO: trap regions should be created last, because the hardware gives
	//  priority to the higher number ones.
	SCB_MPU_RBAR = 0x00000000 | REGION(i++); // [B]trap NULL pointer deref[/B]
	SCB_MPU_RASR =  DEV_NOCACHE | NOACCESS | SIZE_32B;
...	
	SCB_MPU_RBAR = ((uint32_t)&_ebss) | REGION(i++); // [B]trap stack overflow[/B]
	SCB_MPU_RASR = SCB_MPU_RASR_TEX(0) | NOACCESS | NOEXEC | SIZE_32B;
...

And this added to .ld file:
Code:
	.text.itcm : {
		. = . + 32; /* MPU to trap NULL pointer deref */
...
 
Well it detects this:
Code:
#include <T4_PowerButton.h>
extern unsigned long _ebss;
void setup() {
  uint32_t *p = ((uint32_t)&_ebss);
  *p = 0xdeadbeef;
}

void loop() {}
and prints
Code:
Hardfault.
Return Address: 0x166
    (DACCVIOL) Data Access Violation
    (MMARVALID) Accessed Address: 0x20002540
I testet a stack overflow with recursive calls, hm, does not work. Don't know why, at the moment.. any idea?
 
Well it detects this:
...

I testet a stack overflow with recursive calls, hm, does not work. Don't know why, at the moment.. any idea?

Interesting that is failing to trigger on code over filling the stack? It is a 32 Byte(?) region? Maybe the recursive code isn't reading|writing to it to trigger but just stepping over it?

Have not gotten to test the added Fault detect code yet :( - but did glance at that example though didn't look close enough with this in mind.
 
Had an other idea. Switched to "optimize: debug", and voilá, it prints the error.

Hm it works if I switch to optimize "debug"
Code:
#include <T4_PowerButton.h>

void setup() {
  setup();
}

void loop() {}
So, GCC seems to do something weired.
 
Interesting - well it is an otherwise empty function - may it gets optimized away :) ???

Could do a normal non-debug build and add a static var and use a stack var to get it and change it in some way it doesn't just optimize that way and edit the static directly. Or maybe call another function with a return value in setup before calling setup?

Does it get to loop() ?
 
Found it. GCC is smarter than me :)
Without optimization it produces this ("bl")
With optimization it just branches ("b") to the start of the function - id does not call. So, correct - there is no stack overflow..
The example code is just too simple.
 
Right. This crashes, as expected:
Code:
#include <T4_PowerButton.h>

void setup() {
  [COLOR=#ff0000]volatile char c = 42;[/COLOR]
  setup();
}

void loop() {}
 
I'll create a PR for Paul... not sure if he wants to merge it.. This certainly needs some advocates

As long as there is a clean and easy way to include or enable it it sells itself - having a lib in the CORES would be awesome to just add #include 'something'. As noted I started working on it in T_3.6 beta I think - revised it for T_4.0 after finding that noted 'naked' stack dump function but never saw the path for printing reliably. And even trying to keep it simple it wasn't simple or clean enough, or used often enough that I stuck with it - or got back to it.

Paul commented in T_4.0 he expected to revisit it to get better info and blinking - that was before I added that 'naked' code that solved the stack dump better than what was there originally - and then it didn't get revisited.

The GDB debugger is nice, but some real effort to get right and get connected for usage between build and then SerMon/Terminal connection setup and command line.

This isn't real time debugging - but AFTER a FAULT - there isn't much chance of normal function at times and nothing you can do about it except restart. And if info can be saved at fault time and shown on restart - that is the best you can do.

> It might be interesting to have the default OVER TEMP code feed this - does it? Once it goes PANIC there is no chance it seems - but the step before that could be logged and shown on restart.

> Also perhaps recording info like F_CPU_ACTUAL, millis() at Fault, and other relevant easy to gather notes that might explain the trouble or be useful::
>> It runs for 5 minutes and dies, or it dies at 200 or 400 millis().
>> Or the one T_4.0 I BURNED UP because I was building on a fresh T_4.0 that kept 'failing to work' because the IDE was still set to high overclocking speed with heat sync required when it did not have one and I didn't notice until after the damage was done after building and rebuilding thinking the trouble was in the i2c display library I was adding.
 
> It might be interesting to have the default OVER TEMP code feed this - does it

Hmm, I don't think so.
Let me think about how to add this....
It would be a good feature. Possibly, in this case, it should also lower the speed of the CPU to give it a chance to cool down.
 
Hmm, I don't think so.
Let me think about how to add this....

Indeed maybe not ... I think you'd have to hook the warn callback and make a note of that in the static RAM. Then if it went OVERTEMP and restarted - not sure if that is a reported FAULT? If it is reported then it is already displayed - if not then on restart it could be posted as a warning that may or may not have been the cause of the restart.
 
I added delay(1) as you suggested and sure enough, it printed! And yep, it crashed right after.

So the problem is the NULL pointers - I thought this was standard practice for continuing to use the strtok() function after passing it a string?

This code does work multiple times on the UNO - I can send "CHAN," "READ," etc without causing a crash (or perhaps the UNO crashes and then recovers without me noticing?). Is it the difference in USB communication method that allows it to work on the UNO? I'm not familiar with the behavior of USB asynchronous vs serial.


I realize this was posted a while back, but I just ran into the same issue and didn't see the actual resolution on here.

The strtok(NULL, ",") does work, however it is the atof(strtokIndx) being passed the NULL from strtok that is causing the crash.

I'm sure there is another way to do this, but I wound up doing:
Code:
  strtokIndx = strtok(NULL, ",");          // this continues where the previous call left off
  if(NULL != strtokIndx)
  {
    integerFromPC = atoi(strtokIndx);     // convert this part to an integer
  }

  // or

  strtokIndx = strtok(NULL, ",");
  floatFromPC = (NULL != strtokIndx) ? atof(strtokIndx) : 0;        // convert this part to a float, with a default if not found

You could throw in an else for default/error values if you want.
Like I said, there is surely a different/better way of doing this, but it is working for me now.
 
Back
Top