Teensy 4.x Hooks before setup()

defragster

Senior Member+
Below is a sample sketch that shows creation of local hook() functions called before entry to setup as the Reset process proceeds.

It makes some notes on what can be expected to work in each of the three hooks and shows how they can be defined as required "C" code in a C++ sketch.

This sketch records startup time as possible, and there are notes below

Code:
uint32_t gTest[4]; // Global storage test values
uint32_t testLog( uint32_t test ); // Demo storage and use of RAM variables

// These 'hook' functions must be callable from "C" code so: extern "C": Expressed three ways below
// These 'hook' functions are called before setup() - they have progressively more functionality online
// Called from ResetHandler() in ..\hardware\teensy\avr\cores\teensy4\startup.c
// This is "C" code and until setup() is called is it before any C++ constructors have been called
//    For example: EEPROM.read(1); // C++ not ready, and this is "C"

extern "C" {
// The early hook MUST be in FLASHMEM - middle/late may as well be as they are only executed once
// Passed parameters and Stack variables only, static and 'global' variables not yet ready
  FLASHMEM void startup_early_hook(void) {
    testLog( 1 ); // ARM_DWT_CYCCNT not started, and RAM will be cleared
    // TOO SOON to call these : Hard Crash and Teensy goes OFFLILNE as CrashReport capture and interrupt vectors not set
    // pinMode( 1, OUTPUT );
    // digitalWrite( 1, HIGH );
    // DMAMEM cache not setup, not configured for use
  }
} // extern "C" block


extern "C" void startup_middle_hook(void);
extern "C" volatile uint32_t systick_millis_count;
FLASHMEM void startup_middle_hook(void) {
  testLog( 2 );
  pinMode( 2, OUTPUT );
  digitalWrite( 2, HIGH );
  // OPTIONAL FASTER STARTUP: force millis() to be 300 to skip startup delays
  // systick_millis_count = 300;
}


extern "C" FLASHMEM void startup_late_hook(void) {
  testLog( 3 );
  pinMode( 3, OUTPUT );
  digitalWrite( 3, HIGH );
}

// There are fixed delays in the Resethandler() to allow USB init to being and other connected devices to power up on cold start
// These are after _middle_ and before _late_ hooks If these values below are #DEFINED for the build the wait times can be changed
// The code above records entry to _middle_ on a T_4.1 with no PSRAM at 1ms to _middle_ and then 300ms to _late_
// With PSRAM it is 327ms on a locked Teensy 4.1 with 1 PSRAM, and 653 ms on an unlocked T_4.1 with (2) PSRAMs
// If NOT predefined when building these values are used in TD 1.57:
//      #define TEENSY_INIT_USB_DELAY_BEFORE 20
//      #define TEENSY_INIT_USB_DELAY_AFTER 280 // added to above value gives 300 ms
// Shown above in startup_middle_hook() is a systick_millis_count clock advance that will avoid this delay at runtime.

void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  if ( CrashReport )
    Serial.print( CrashReport );
  Serial.println( testLog( 4 ) ); // recall 'saved' hook values 1,2,3: values offset for recall +3 and +6
  Serial.println( testLog( 7 ) );
  Serial.println( testLog( 5 ) );
  Serial.println( testLog( 8 ) );
  Serial.println( testLog( 6 ) );
  Serial.println( testLog( 9 ) );
}

void loop() {
  // put your main code here, to run repeatedly:
}

uint32_t testLog( uint32_t test ) { // Demo storage and use of RAM variables
  static uint32_t lTest[4];
  switch ( test ) {
  case 1: // called by _early_
    lTest[ test ] = ARM_DWT_CYCCNT + 1; // Still 0, not running - RAM variables not ready for use
    gTest[ test ] = millis(); // RAM variables not ready for use
    break;
  case 2: // called by _middle_
    lTest[ test ] = ARM_DWT_CYCCNT; // now running : value stored
    gTest[ test ] = millis(); // now running : value stored
    break;
  case 3: // called by late_
    lTest[ test ] = ARM_DWT_CYCCNT; // now running : value stored
    gTest[ test ] = millis(); // now running : value stored
    break;
  case 4:
    return lTest[ test - 3 ];
    break;
  case 5:
    return lTest[ test - 3 ];
    break;
  case 6:
    return lTest[ test - 3 ];
    break;
  case 7:
    return gTest[ test - 6 ];
    break;
  case 8:
    return gTest[ test - 6 ];
    break;
  case 9:
    return gTest[ test - 6 ];
    break;
  }
  return 0;
}
 
Last edited:
@PaulStoffregen - just looked at Twitter and saw this posted: pjrc.com/teensy/td_startup.html
> just 20 days after this code was posted.

Updated p#1 code above to show method for faster startup (unless T_4.1 with QSPI PSRAM} at runtime to bypass timed waits.

For a T_4.1 with single PSRAM output is this:
Code:
C:\T_Drive\tCode\T4\StartupHooks\StartupHooks.ino Dec  8 2022 01:07:28
0
0
196368631
327
211378204
325
 
Code like this might take over 2 seconds to pull the pin #45 high ... here's why ... and why the above p#1 was made:
Code:
void setup() {
   Serial.begin(38400);
   while (!Serial) {}
   pinMode(45, INPUT_PULLUP);
}

>Qeustion: is it expected that there is a short delay before a pin is pulled high with INPUT_PULLUP?
Yes, that is expected.
On startup the processor enters ResetHandler code. It is delayed entry to setup() in startup.c - for 300 ms after the clock is started - before completing the final initialization steps:
Code:
...
	startup_middle_hook();

#if !defined(TEENSY_INIT_USB_DELAY_BEFORE)
        #define TEENSY_INIT_USB_DELAY_BEFORE 20
#endif
#if !defined(TEENSY_INIT_USB_DELAY_AFTER)
        #define TEENSY_INIT_USB_DELAY_AFTER 280
#endif
	// for background about this startup delay, please see these conversations
	// https://forum.pjrc.com/threads/36606?p=113980&viewfull=1#post113980
	// https://forum.pjrc.com/threads/31290?p=87273&viewfull=1#post87273

[B]	while (millis() < TEENSY_INIT_USB_DELAY_BEFORE) ; // wait
	usb_init();
	while (millis() < TEENSY_INIT_USB_DELAY_AFTER + TEENSY_INIT_USB_DELAY_BEFORE) ; // wait
[/B]	//printf("before C++ constructors\n");
	startup_debug_reset();
	startup_late_hook();
	__libc_init_array();
	//printf("after C++ constructors\n");
	//printf("before setup\n");
	main();

Putting 'extern "C" startup_middle_hook() { // code here }' in the sketch would be called before this delay and simple pinMode settings are valid there.
Code:
extern "C" void startup_middle_hook(void);
FLASHMEM void startup_middle_hook(void) {
  pinMode(45, INPUT_PULLUP);
}


Additionally the posted code shows: Serial.begin(38400); used first in setup().
> that is optional as USB will arrive ASAP - but calling Serial.begin(38400); will delay as much as 2000 ms allowing for USB to be online before return.

So placing other non-USB code before that will have it execute much sooner.
Code:
  pinMode(45, INPUT_PULLUP);
  void setup() {
  Serial.begin(38400); // this will return when Serial is online, or 2000 ms, whichever is faster, also returns after 750 ms if USB shows no indication of connection pending
  while (!Serial) {}
}
 
That doesn't really explain why "pinMode(X, INPUT_PULLUP)" needs an amount of time to pull the pin high, only how to avoid the wait if the pin can be set that way at startup.
 
That doesn't really explain why "pinMode(X, INPUT_PULLUP)" needs an amount of time to pull the pin high, only how to avoid the wait if the pin can be set that way at startup.

The delay to the 'observed' pin being pulled HIGH is not from any delay in pinMode(), but the fact that pinMode in the example code will not be called for up to 2.3 seconds waiting for setup() to be called (300ms) and then another 2000ms while Serial is not detected to be online.

Serial when connected to a ready host is typically online in 500-900 ms and at that point the pinMode would drive the pin high.
 
That doesn't really explain why "pinMode(X, INPUT_PULLUP)" needs an amount of time to pull the pin high, only how to avoid the wait if the pin can be set that way at startup.

Sample Sketch showing pinMode has NO DELAY.
Code:
// https://forum.pjrc.com/threads/70852-Teensy-4-x-Hooks-before-setup()
uint32_t tMHookA;
uint32_t tMHookB;
uint32_t tSetupA;
uint32_t tSetupB;
uint32_t tSetupC;

/* TEST entry and exec speed
  PINS 2&3 JUMPERED: test timing to startup_middle_hook
  PINS 4&5 JUMPERED: test timing to normal setup() entry
*/

extern "C" void startup_middle_hook(void);
void startup_middle_hook(void) {
  tMHookA = millis();;
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT);
  pinMode(5, INPUT);
[B]  [COLOR="#FF0000"]while [/COLOR]( LOW == digitalReadFast( 3 ) ) {[/B]
    tMHookB = millis();;
  }
}

void setup() {
  tSetupA = millis();;
  Serial.begin(38400);
  tSetupB = millis();;
  while (!Serial) {}
  pinMode(4, INPUT_PULLUP);
[B]  [COLOR="#FF0000"]while [/COLOR]( LOW == digitalReadFast( 5 ) ) {[/B]
    tSetupC = millis();;
  }
  Serial.printf("entry to middle_hook %ld\n",tMHookA );
  Serial.printf("exit middle_hook with pin 3 HIGH %ld\n",tMHookB );
  Serial.printf("entry to setup %ld\n",tSetupA );
  Serial.printf("Serial online %ld\n",tSetupB );
  Serial.printf("exit setup with pipn 5 HIGH %ld\n",tSetupC );
}

void loop() {
  // put your main code here, to run repeatedly:
}

Here are the results of that code - the RED lines show that pinMode completed and the pin was HIGH and the while()'s were never even entered:
Code:
entry to middle_hook 1
[B][COLOR="#FF0000"]exit middle_hook with pin 3 HIGH 0[/COLOR][/B]
entry to setup 300
Serial online 2301
[B][COLOR="#FF0000"]exit setup with pipn 5 HIGH 0[/COLOR][/B]

<edit notes>:
KUDOS to self! typed and built this code without the usual set of 5 rebuilds for syntax errors and the code worked as planned and expected/described!
One error is last pin shows 'pipn' not 'pin' was typed in error, and some double ';;' were copy/pasted as well.
NOTE: build was started before PORT was selected to a T_4.0, selected port before build completed and upload worked, but SerMon was NOT open and online as shown for 2.301 seconds

<edit>Doing a second upload with SerMon online results in:
Code:
entry to middle_hook 1
[B][COLOR="#FF0000"]exit middle_hook with pin 3 HIGH 0[/COLOR][/B]
entry to setup 300
[B][U]Serial online 585[/U][/B]
[B][COLOR="#FF0000"]exit setup with pin 5 HIGH 0[/COLOR][/B]
 
Last edited:
The program as shown in your post doesn't work for me on a T4.1. Did you mean to set INPUT_PULLUP on pins 2 and 4, but test the levels of pins 3 and 5? Wouldn't you expect 2 and 4 to be HIGH, and 3 and 5 to be LOW? With nothing connected to any of those pins, my code never exits either of the while() loops.

EDIT: never mind! Did not see the comment that 2-3 and 4-5 were jumpered.
 
Last edited:
That doesn't match with the original question that you were answering, which featured this code that always started off saying the button was pressed regardless of its state:
Code:
void setup() {
  Serial.begin(38400);
  while (!Serial) {}
  pinMode(45, INPUT_PULLUP);
}

void loop()
{
  delay(1);
  if (digitalRead(45) == HIGH) {
    Serial.println("Button is not pressed...");
  } else {
    Serial.println("Button pressed!!!");
  }
  delay(250);
}

The startup code has absolutely no influence here, it's over and done with before pinMode() is called.
 
That doesn't match with the original question that you were answering, which featured this code that always started off saying the button was pressed regardless of its state.
The startup code has absolutely no influence here, it's over and done with before pinMode() is called.

Yes, and I tried that original code on a T4.1 and the level of pin 45 is always HIGH, with or without the delay(1). Could there be something about the Micromod hardware that is different?
 
Missed above replies - wondered if _ISR's might be usable in middle_hooks and they are.

@jmarsh: This thread was not meant to answer details in micromod thread - just the order and speed of execution on startup.
@joepasquariello: Code notes:
Code:
/* TEST entry and exec speed
  PINS 2&3 JUMPERED: test timing to startup_middle_hook : T_MM 42&43 on ATP G2-G3
  PINS 4&5 JUMPERED: test timing to normal setup() entry : T_MM 44&45 on ATP G4-G5
*/

Here are results of extended code on T_4.0: {MicroMod on desk and building now IDE 1.8.19 and TD 1.59 beta 2 - results next post}
Code:
entry to middle_hook 1831
middle_hook extra code complete 1832
while diff time for pin 3 HIGH 1
	ISR CPU_CYCLES delay for pin 3 HIGH 196
exit middle_hook with pin 3 HIGH 1833
entry to setup 300000
Serial online 363000
setup while diff time for pin 5 HIGH 0
	ISR CPU_CYCLES delay for pin 5 HIGH 266
setup code complete with pin 5 HIGH 363000
[U]All times (except CYCLES) in microseconds[/U]

Here is the code used - NOTE: _isr values were timed wrong without this: delayNanoseconds(300); // THERE IS A RACE HERE ...
<EDIT> Updated T_MM compatible code in p#11 below
 
Last edited:
T_MM carrier labels (ATP) not as nice a PJRC simple pin map #'s with a CARD! .... Arrrgh - had to run PinTest to ID usable pin numbers ... and handling the ATP board to jumper pins shows it VERY touch sensitive giving false triggers!

T_MM pin remapped after code above took it offline:
Code:
C:\T_Drive\tCode\FORUM\ResetTiming\ResetTiming.ino Jun 13 2023 11:59:04
entry to middle_hook 1823
middle_hook extra code complete 1824
while diff time for pin 3 HIGH 1
	ISR CPU_CYCLES delay for pin 3 HIGH 321
exit middle_hook with pin 3 HIGH 1825
entry to setup 300001
Serial online 368000
setup while diff time for pin 5 HIGH 1
	ISR CPU_CYCLES delay for pin 5 HIGH 358
setup code complete with pin 5 HIGH 368001
All times (except CYCLES) in microseconds
noted: longer _isr response time some 100 cycles longer on T_MM?

Code hack to #define alternate pin#'s to run on T_MM:
Code:
// https://forum.pjrc.com/threads/70852-Teensy-4-x-Hooks-before-setup()
uint32_t tMHookA;
uint32_t tMHookA2;
uint32_t tMHookB;
uint32_t tMHookC;
uint32_t tSetupA;
uint32_t tSetupB;
uint32_t tSetupC;
uint32_t tSetupD;
uint32_t tIsr3A;
volatile uint32_t tIsr3B;
uint32_t tIsr5A;
volatile uint32_t tIsr5B;

/* TEST entry and exec speed
[B][COLOR="#FF0000"]  PINS 2&3 JUMPERED: test timing to startup_middle_hook : T_MM 42&43 on ATP G2-G3
  PINS 4&5 JUMPERED: test timing to normal setup() entry : T_MM 44&45 on ATP G4-G5
[/COLOR][/B]*/
#ifdef ARDUINO_TEENSY_MICROMOD
[B]#define P2 42
#define P3 43
#define P4 44
#define P5 45[/B]
#else
#define P2 2
#define P3 3
#define P4 4
#define P5 5
#endif

void Isr3() {
  tIsr3B = ARM_DWT_CYCCNT;
  asm("dsb");
}

void Isr5() {
  tIsr5B = ARM_DWT_CYCCNT;
  asm("dsb");
}

extern "C" void startup_middle_hook(void);
void startup_middle_hook(void) {
  tMHookA = micros();
  pinMode(P2, INPUT_PULLDOWN);
  pinMode(P4, INPUT_PULLDOWN);
  pinMode(P3, INPUT);
  pinMode(P5, INPUT);
[B][COLOR="#FF0000"]  delayNanoseconds(300);  // THERE IS A RACE HERE: residual voltage triggers ISR early? Wait after PULLDOWN![/COLOR][/B]
  attachInterrupt(P5, Isr5, RISING);
  attachInterrupt(P3, Isr3, RISING);
  tMHookA2 = micros();
  tIsr3A = ARM_DWT_CYCCNT;
  pinMode(P2, INPUT_PULLUP);
  while (LOW == digitalReadFast(P3)) {
    tMHookB = micros();
  }
  tMHookC = micros();
  ;
}

void setup() {
  tSetupA = micros();
  Serial.begin(38400);
  tSetupB = micros();
  while (!Serial) {}
  tIsr5A = ARM_DWT_CYCCNT;
  pinMode(P4, INPUT_PULLUP);
  while (LOW == digitalReadFast(P5)) {
    tSetupC = micros();
  }
  tSetupD = micros();
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  Serial.printf("entry to middle_hook %lu\n", tMHookA);
  Serial.printf("middle_hook extra code complete %lu\n", tMHookA2);
  Serial.printf("while diff time for pin 3 HIGH %lu\n", tMHookB - tMHookA2);
  Serial.printf("\tISR CPU_CYCLES delay for pin 3 HIGH %lu\n", tIsr3B - tIsr3A);
  Serial.printf("exit middle_hook with pin 3 HIGH %lu\n", tMHookC);
  Serial.printf("entry to setup %lu\n", tSetupA);
  Serial.printf("Serial online %lu\n", tSetupB);
  Serial.printf("setup while diff time for pin 5 HIGH %lu\n", tSetupC - tSetupB);
  Serial.printf("\tISR CPU_CYCLES delay for pin 5 HIGH %lu\n", tIsr5B - tIsr5A);
  Serial.printf("setup code complete with pin 5 HIGH %lu\n", tSetupD);
  Serial.printf("All times (except CYCLES) in microseconds\n");
}

void loop() {
  // put your main code here, to run repeatedly:
}
 
Bottom line from new posts up to #11:

Using startup_middle_hook() allows Teensy with 1062 (T_4.x and T_MM) to exert pin control about 2 ms after power on and resetHandler starts the clocks.
Waiting until setup() (unless build presents the #define's below with alternate values) delays that until 300 ms after power on and clock starts.

Code:
#if !defined(TEENSY_INIT_USB_DELAY_BEFORE)
        #define TEENSY_INIT_USB_DELAY_BEFORE 20
#endif
#if !defined(TEENSY_INIT_USB_DELAY_AFTER)
        #define TEENSY_INIT_USB_DELAY_AFTER 280
#endif
Those delays are present to slow startup to match typical prior Arduino devices startup more closely [IIRC] and also allows the USB code to get a clean start as leaving to setup() sooner can slow the USB init [IIRC].

Also reminder that the Serial.begin(38400); is OPTIONAL as USB will start as soon as the HOST connect allows, but when called the delay (seen in p#6) return can keep code after that waiting up to 2300 ms, or less if USB comes online faster.
Serial.print() is not reliable until if(Serial) is TRUE, but putting other 'non-USB' code before that can get it done much sooner - especially when needed in startup_middle_hook()

Cool note - this 10 month old thread only seen by 10 before now read by 26 at this posting!
 
I feel ya with the mmod pin numbers. The conversion chart I extracted from the Sparkfun website is always on my desk. It maps all the teensy pins to micromod pins, but I had to cull it out of a bunch of surrounding html so I could print it on one page. Too bad they didn't provide a standalone printable card.

I can convert it into a spreadsheet for you.
 
I feel ya with the mmod pin numbers. ...

Thanks Bob, @mjs513 made one during T_MM Beta - but that has been years now and printed copy no longer on top of piles :) He posted a link to it again in past year and I saw it - but not in today's early effort.
>> Now that I don't need it ... I found the printed copy with each pin labeled on the ATP board image that @mjs513 did. And here is a JPG of it from disk not even 26 months old:
MModATP_PinMap.jpg
 
Back
Top