Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 18 of 18

Thread: "wfi" on Teensy 4.0 stops millis()

  1. #1
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801

    "wfi" on Teensy 4.0 stops millis()

    Using "wfi" on Teensy 4.0 1062 has been odd - in use since noted during Beta with link to NXP doc - finally found an example showing even when it 'works' - it breaks millis() and micros(). And it explains when it works as noted on T_3.x but not T_4 because millis() is not driven by an interrupt.

    This example works because it was code for a forum post with a falling _isr() trigger. In adding to the code printing millis() shows it no longer changes - even after 5 minutes

    What is the fix or alternative code to get "wfi" to generally work without explicitly setting up an interrupt workaround - which may still break millis()?

    Code:
    // https://forum.pjrc.com/threads/60313-Configuring-Extenal-Interrupts-(Teensy-LC)?p=234803&viewfull=1#post234803
    volatile bool LED_State = LOW;
    volatile uint32_t iCnt = 0;
    
    void setup() {
    	Serial.begin(115200);
    	while (!Serial && millis() < 4000 );
    	Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
    	pinMode(0, INPUT_PULLUP);
    	attachInterrupt(digitalPinToInterrupt(0), startBit_isr, FALLING);
    
    	pinMode(LED_BUILTIN, OUTPUT); // LED for debugging
    	LED_State = HIGH;
    	digitalWrite(LED_BUILTIN, LED_State);
    }
    
    uint32_t myiCnt = 0;
    void loop() {
    	if ( myiCnt != iCnt) {
    		myiCnt = iCnt;
    		Serial.printf("_isr() @%lums %luus [iCnt%lu]\n", millis(), micros(), iCnt);
    	}
    	asm volatile("wfi");
    }
    
    void startBit_isr()
    {
    	LED_State = !LED_State;
    	iCnt++;
    	digitalWriteFast(LED_BUILTIN, LED_State);
    }
    Output from a series of presses - millis() never changes and micros() only varies by some limited small amount based on ARM_CNT changes:
    T:\tCode\FORUM\ISR_LED\ISR_LED.ino Apr 2 2020 11:55:10
    _isr() @412ms 412002us [iCnt1]
    _isr() @412ms 412008us [iCnt2]
    _isr() @412ms 412015us [iCnt3]
    _isr() @412ms 412021us [iCnt4]
    _isr() @412ms 412027us [iCnt5]

    // 5-10 minutes later …

    _isr() @412ms 412357us [iCnt55]
    _isr() @412ms 412363us [iCnt56]
    _isr() @412ms 412370us [iCnt57]
    NOTE: This is running on T_4.0, when the same code runs on a T_3.6 it has no issue

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    Longer code with intervalTimer added - note I saw recently suggested a timer interrupt or something would keep "wfi" running where otherwise no interrupts occur to WAKE the T_4 - but it does not help millis().

    Run this code with "wfi" commented and the prints of millis() tracks with the interval timer driven wCnt::
    Code:
    // https://forum.pjrc.com/threads/60313-Configuring-Extenal-Interrupts-(Teensy-LC)?p=234803&viewfull=1#post234803
    // https://forum.pjrc.com/threads/60315-quot-wfi-quot-on-Teensy-4-0-stops-millis()?p=234808&viewfull=1#post234808
    volatile bool LED_State = LOW;
    volatile uint32_t iCnt = 0;
    volatile uint32_t wCnt = 0;
    
    IntervalTimer myTimer;
    
    void setup() {
    	Serial.begin(115200);
    	while (!Serial && millis() < 4000 );
    	Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
    	pinMode(0, INPUT_PULLUP);
    	attachInterrupt(digitalPinToInterrupt(0), startBit_isr, FALLING);
    
    	pinMode(LED_BUILTIN, OUTPUT); // LED for debugging
    	LED_State = HIGH;
    	digitalWrite(LED_BUILTIN, LED_State);
    	myTimer.begin(wfiWake, 1000);  // blinkLED to run every 0.15 seconds
    	wCnt = millis();
    }
    void wfiWake() {
    	wCnt++;
    }
    
    uint32_t myiCnt = 0;
    void loop() {
    	if ( myiCnt != iCnt) {
    		myiCnt = iCnt;
    		Serial.printf("_isr() @%lums %luus [iCnt%lu - wCnt%lu]\n", millis(), micros(), iCnt, wCnt);
    	}
    	asm volatile("wfi");
    }
    
    void startBit_isr()
    {
    	iCnt++;
    	digitalWriteFast(LED_BUILTIN, LED_State);
    	LED_State = !LED_State;
    }
    Last edited by defragster; 04-07-2020 at 06:55 AM.

  3. #3
    Senior Member
    Join Date
    Jul 2014
    Posts
    2,665
    I vaguely recall, that millis() is not based on periodic interrupt. In fact, for my loggers I had to remove wfi from loop() do ensure sufficient loop circles for disk writes.

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    Indeed it is NOT - There was a note to that effect recently - saying for WFI to work required some 'other' interrupt for it to wake. I had intermittent results over time - when the sketch Serial.print()'s 'right' it triggers a USB interrupt it seems then would wake - print and repeat … unless the timing was wrong.

    That is interesting to know - but having millis() drop dead as a side effect was not yet noted that I saw. I'm not sure what triggers the millis() counter - but with WFI usage … it is nothing for a trigger? And without an overt using WFI is a hang.

    Have pointed to a longer NXP App Note doc twice { this one : AN12085_LowPower.pdf ??? } with details on making WFI do something properly … a page or two for prep and exit around WFI as the basis of multiple power modes.

  5. #5
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    That means this "_VectorsRam[15] = my_systick_function;" code isn't getting run for some reason and EventResponder will also be disabled?
    Code:
    extern "C" void systick_isr(void)
    {
    	systick_cycle_count = ARM_DWT_CYCCNT;
    	systick_millis_count++;
    	MillisTimer::runFromTimer();
    }

  6. #6
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    Yes, the SYSTICK does not wake the CPU, on IMXRT. NXP designed it this way.
    Other interrupts can do that, and thats seems to be configurable(?) - It must be somewhere in the Bible (NXP RefManual) or ARM Architecture Manual, or..

  7. #7
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    The doc is : nxp.com/docs/en/application-note/AN12085.pdf

    Doesn't look like it would offer any solution here - but it gives the proper way to get to 'System Idle' with WFI.

    Dropping speed/voltage and clocks before WFI wouldn't help here and then waking to raise voltage/speed and clocks as needed would add some time - could do most of that time testing with set_arm_clock 600>132>600.

    It does that - in addition to toggling some other bits before and after WFI to maximize power reduction.

    But the problem given the non-waking interrupt as the base for millis() is the current form of asm'wfi' breaks millis and eventResponder and could lead to stalled/coma execution if used like in the T_3.x family.

  8. #8
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    This doc does not mention how to configure interrupts to wakeup the teensy.

  9. #9
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    USB activity can wake him up, so there's probably a way to do this with every interrupt... you could use a timer interrupt.
    I had this on my todo list, but I didn't feel like looking for it yet.

  10. #10
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    sample code in p#2 above does wake with an interval timer - and it also wakes onpin interrupt - which is what I wrote for another thread forum answer when I decided to add WFI to it and saw millis() die.

    The table in the doc says GPIO will wake: Click image for larger version. 

Name:	wakeup.png 
Views:	1 
Size:	72.4 KB 
ID:	19576

  11. #11
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    Doesn't it wake with the GPIO Interrupt? (#P2)
    What is "Other wake-up Source" ?

  12. #12
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    Quote Originally Posted by Frank B View Post
    Doesn't it wake with the GPIO Interrupt? (#P2)
    What is "Other wake-up Source" ?
    Yes samples as noted in p#10 were first tested on pin interrupt - because that is what the other forum post was about.

    As far as 'other' … not sure … in the doc it notes:
    o SW8 user key wake-up.
    o GPT timer wake-up.
    Not sure if there is any others ...

  13. #13
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    So you may need to use GPT or RTC to keep millis() running.
    But I wonder for what that would be needed...

  14. #14
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    A short delay(#) like 'SNOOZE' like function that wraps 'wfi'. Rather than just 'wfi until interrupt' - it would be wfiNap(#) that repeats 'wfi' for a fixed time "#" in some fashion - though should return on a true 'waking interrupt' perhaps wfiNap(0)?

    The RTC is not much use because it only exposes seconds - and will have to check if ARM_CNT stalls during 'wfi'. Would take some repeat timers to get to requested time - fix the systick_millis_count and return?

  15. #15
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    6,909
    Good idea

    You could do it like in #2, with a unused GPIO

  16. #16
    Senior Member
    Join Date
    Feb 2015
    Location
    Finland
    Posts
    189
    Perhaps micros() and millis() could be implemented on top of a GPT running at 1 MHz (using the peripheral clock, ipg_clk), so its counter is directly in microseconds, and wraps around every ~ 4295 seconds (71.5 minutes)?

    Then, add an RTC interrupt every few seconds, maybe once a minute or so (anything from once per second to once every 2147 seconds would work), whatever is sensible to restrict the maximum duration of a single wfi. Have the default interrupt handler for that execute micros64(), discarding the result.

    Finally, implement micros64() returning a 64-bit unsigned integer. It would read the 32-bit GPT counter, and compare its 8 high bits to the last stored 8 high bits. If they wrapped around, the 24-bit overflow counter is incremented. These two fields are packed in a single 32-bit word, which is stored atomically (exclusive store). Note that if micros64() gets interrupted by a call that does the same, it does not matter which one succeeds, as either one will yield correct results; as long as there is at least two micros64() calls during each GPT counter wraparound period. The function returns the combined 24+32 = 56-bit microsecond counter.

    Then, micros() is just a wrapper that returns the low 32 bits of micros64(), and millis() returns the low 32 bits of (micros64()/1000).

    This does add a bit of overhead to both millis() and micros() (especially the 64-bit division to millis()!), but as far as I can tell, it would solve this issue with minimal impact on existing code. Note that it would also make sense to implement the delay functions using the underlying micros64(), converting the duration of delay to 64-bit microseconds. Also, the GPT does not need to have an overflow interrupt, so it would not interfere with wfi (but you'd still need to use an RTC interrupt to limit wfi to a half hour or less; fifteen minutes or less to be extra careful).

    The 56-bit microsecond counter only wraps every couple of millenia (256 microseconds ≃ 2283 years). After 4295 seconds (71 minutes, or a bit over an hour) of run time, millis() would no longer be micros()/1000, but that's because both millis() and micros() would increment at a steady rate and micros() wrap at 32-bit boundaries, without any kind of jumps.
    Last edited by Nominal Animal; 04-04-2020 at 09:41 PM. Reason: to->of

  17. #17
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    FALSE :: "wfi" stops the processor - so it stops the interval timer! p#2 above only seemed to work because USB processing the .printf() resulted in a WAKE from wfi.
    <<EDITING THIS>>

    Changing to this with second "wfi" NEVER WAKES - this results in a COMA!!!!
    >> WRONG - the code wasn't properly run and tested. It silently counts IntervalTimer 1ms ticks and only displays them on button interrupt. Pushing the button shows the timer is running as reported in posts above.


    That was discovered when trying to get the time for clock speed changes. millis() appears to run well on an unchanged clock when no "wfi".
    Changing clock when voltage and other changes are needed going LOW then HIGH back to 600 MHz seems to take 1.8 ms each cycle - so a clock change would give a 'sleep' of near 2ms minimum with clock drop, even if there is a way to assure the "wfi" won't cause a COMA.
    >> This 'COMA' report also isn't right - the timer was running as " myTimer.begin(wfiWake, 1000000); " - so those interrupts were too few to get timely results!
    Changing to : myTimer.begin(wfiWake, 1000); completes the test below in a timely fashion for the loops of 1,000

    >> Using a proper timer freq this does show it takes about 1.8ms to drop and raise the CPU speed - but using 'wfi' does not stop IntervalTimer operation
    I got it to WAKE using a Serial1.print - filling the FIFO, like USB - causes it to wake. Without the Serial1.print("1234567890"); shown below the Teensy 4.0 hibernates:
    >> - It is true that the FIFO send interrupt does work to terminate the 'wfi'.
    Code:
    //
    extern "C" uint32_t set_arm_clock(uint32_t frequency);
    int32_t ii, tt, oc, wCnt = 0;
    elapsedMillis aTime;
    IntervalTimer myTimer;
    
    void wfiWake() {
    	wCnt++;
    }
    void setup() {
    	// put your setup code here, to run once:
    	Serial.begin(115200);
    	Serial1.begin(2000000);
    	myTimer.begin(wfiWake, 1000000);
    	while (!Serial && millis() < 4000 );
    	Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
    
    	oc = F_CPU_ACTUAL;
    }
    
    #define CLK_BASE 	600000000
    #define CLK_528 	528000000
    #define CLK_132 	132000000
    #define CLK_120 	120000000
    #define CLK_32 		32000000
    #define iiLOOPS		1000
    
    void loop() {
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_528);
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @528<>600 in %lu ms\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_132);
    		set_arm_clock(CLK_120);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @132<>120 in %lu ms\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_120);
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @120<>600 in %lu ms\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_32);
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @32<>600 in %lu ms\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_BASE);
    		Serial1.print("1234567890");
    		asm volatile("wfi");
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @600<>600 in %lu ms || WFI\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_120);
    		Serial1.print("1234567890");
    		asm volatile("wfi");
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @120<>600 in %lu ms || WFI\n", ii, tt);
    
    	set_arm_clock(oc);
    	aTime = 0;
    	for ( ii = 0; ii < iiLOOPS; ii++ ) {
    		set_arm_clock(CLK_32);
    		Serial1.print("1234567890");
    		asm volatile("wfi");
    		set_arm_clock(CLK_BASE);
    	}
    	tt = aTime;
    	Serial.printf("#%lu @32<>600 in %lu ms || WFI\n", ii, tt);
    
    	set_arm_clock(oc);
    	delay(5000);
    	Serial.print("\n");
    }
    Last edited by defragster; 04-07-2020 at 06:55 AM. Reason: Opps ... Correction

  18. #18
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    11,801
    Updated p#17 - IntervalTimer does run across and wake 'wfi'! as originally tested and reported.

    millis() as reported does lose time across the 'wfi' - but IntervalTimer runs well.

    Updated above code to use IntervalTimer to measure time and print that and slipping millis during the testing - with the Serial1.print()'s removed.:
    Code:
    T:\tCode\T4\WFI_ClockSet\WFI_ClockSet.ino Apr  6 2020 23:50:13
    #1000 @528<>600 in 1798 ms 	{millis=2150 | 2150}
    #1000 @132<>120 in 107 ms 	{millis=2257 | 2257}
    #1000 @120<>600 in 1817 ms 	{millis=4075 | 4075}
    #1000 @32<>600 in 1857 ms 	{millis=5932 | 5932}
    #1000 @600<>600 in 999 ms || WFI 	{millis=5942 | 6931}
    #1000 @120<>600 in 2000 ms || WFI 	{millis=7791 | 8931}
    #1000 @32<>600 in 2004 ms || WFI 	{millis=9666 | 10935}
    * the 'ms' timings are intervalTimer _isr 1 ms ticks, and on the right millis is the millis() reported time versus the
    * the 600<>600 loses the most time with WFI because the clock change is a NOP going 600 to 600 MHz so the 'wfi' is really stopping millis()
    >otherwise this shows the intervalTimer and millis() run unchanged as the set_arm_clock() is used for slower clocks - when 'wfi' isn't used.

    NOTE: I edited the post #2 code to have the IntevalTimer prove it is alive with 500ms blink and print a '.' each second and then each minute (60K ms ticks) print the line like a button _isr does:
    Code:
    T:\tCode\FORUM\ISR_LED\ISR_LED.ino Apr  7 2020 00:45:17
    ............................................................
    _wfiWake() @345ms 346000us [iCnt=0 - wCnt=60000]
    ............................................................
    _wfiWake() @345ms 346000us [iCnt=0 - wCnt=120000]
    ............................................................
    _wfiWake() @345ms 346000us [iCnt=0 - wCnt=180000]
    That shows the 'wfi' has frozen the millis()and micros() count at 345 just after startup - even as time passes … now 10 minutes and running in SerMon. I should measure current to see the effect here and on p#17 code.
    Last edited by defragster; 04-07-2020 at 07:57 AM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •