On a Teensy 4.1 with 16 MB PSRAM, my program freezes for exactly 2^30 CPU cycles (1.789 s at 600 MHz), at intervals that are multiples of 2^32 CPU cycles (7.16 s at 600 MHz). During the stall the main loop and my IntervalTimer stop, but systick keeps counting. The stall lands in whatever code happens to be executing.
The 4:1 ratio between the interval and the stall length, and the fact that both scale exactly with the core clock, make me think this is tied to the 32-bit cycle counter rolling over - but I cannot find where.
The stall length is a fixed number of CPU cycles. Rebuilding at a different core clock scales it exactly:
600 MHz --> stall: 1 788 160 us --> 1.07290 x 10^9
528 MHz --> stall: 2 032 130 us --> 1.07296 x 10^9
Ratio of the two durations: 1.1364. Ratio of the two clocks: 600/528 = 1.1364. The cycle count is essentially 2^30 (1.0737 x 10^9), about 0.07% low.
The interval between stalls is 2^32 cycles or a multiple. Timestamps from two runs at 600 MHz, seconds since start:
run A: 19, 26, 33, 40, 47, 55, 69, 83, 105, 112, 126, 133, 140
run B: 12, 19, 26, 33, 40, 47, 69, 76, 105, 119, 126, 133, 140
Spacing is 7 s or multiples of 7 s. 2^32 / 600e6 = 7.158 s.
At 528 MHz the spacing becomes 8 s (2^32 / 528e6 = 8.133 s):
13, 21, 30, 38, 46, 54, 78, 86, 103, 111, 119, 127, 135, 143
Stall lengths come in multiples: 1.788 s, 3.577 s, occasionally 5.37 s - always an integer multiple of the same quantum.
systick keeps running, my IntervalTimer does not. I measure elapsed time with micros(), which is built on systick_millis_count, so if it reports 1.788 s then the systick ISR must have run ~1788 times. Meanwhile a counter incremented by my own 1 ms IntervalTimer advances only 40-160 times over the same interval.
Raising the IntervalTimer priority from 160 to 32 changed nothing, so this is not preemption by a higher-priority handler.
The stall lands anywhere. I instrumented several call sites; the long delay has been observed inside analogRead(), inside delayMicroseconds(20), and inside a routine decoding a 30x12 pixel sprite from valid data. There is no single function at fault.
Each of these was disabled or bypassed while the rest kept running, and the stalls continued unchanged:
- SD card - accumulated time inside _read is zero during stalls
- PSRAM - a dedicated stress test (64 blocks of 16-256 KB, allocate, free, write and scattered read every iteration, both chips) ran 5 h 40 min / 383 381 iterations with zero stalls, including with the SD card mounted and yield() called every iteration
- I2C touch controller - bus untouched
- ADC - averaging of 4 and 16 give an identical quantum; with analogRead() removed entirely the stall moves elsewhere
- Display - both with DMA (updateScreenAsync) and with blocking updateScreen(), and with transmission skipped entirely
- Audio library and MQS output - not initialised
- USB - stalls occur when powered from a charger with no host attached
- Power supply - no droop visible on a scope, a 4700 uF cap across 3.3 V changes nothing
- Temperature - hottest component on the board reads 36.4 C on a thermal camera
Disabling the audio path and display DMA together made stalls more frequent (every ~7 s instead of every few tens of seconds) and longer (mostly double quanta). That suggests interrupt sources were shortening the stalls rather than causing them.
Setup:
Teensy 4.1, 600 MHz, two IPS6404L PSRAM chips (16 MB), hand-soldered
PlatformIO, framework-arduinoteensy 1.162.0, GCC 15.2.1
-Os -flto -fno-strict-aliasing
Display: ILI9341 over SPI with DMA, 320x240 framebuffer in DMAMEM
SdFat over SDIO (built-in socket)
One IntervalTimer at 1 ms, priority 160
Audio library with AudioPlayQueue -> MQS
The application is a port of a 1990s DOS game; peak heap usage in PSRAM is 8.7 MB
I grepped the core and libraries for ARM_DWT_CYCCNT and every use I found follows the safe while (ARM_DWT_CYCCNT - begin < cycles) pattern. micros() in delay.c looks correct too - it clamps the fraction and uses unsigned subtraction.
Questions:
- Is there anything in the core, in SdFat, or in the FlexSPI/PSRAM support that would stop the main loop and PIT timers while leaving systick running, for a duration tied to the cycle counter?
- Could a PIT/GPT timer be gated or reloaded in a way that produces a 2^30-cycle gap?
- Has anyone seen stalls synchronised to the 32-bit cycle counter rollover on the i.MX RT1062?
Happy to run further tests - I have the board instrumented with per-stage timing and a set of compile-time switches to cut out each subsystem.
The 4:1 ratio between the interval and the stall length, and the fact that both scale exactly with the core clock, make me think this is tied to the 32-bit cycle counter rolling over - but I cannot find where.
The stall length is a fixed number of CPU cycles. Rebuilding at a different core clock scales it exactly:
600 MHz --> stall: 1 788 160 us --> 1.07290 x 10^9
528 MHz --> stall: 2 032 130 us --> 1.07296 x 10^9
Ratio of the two durations: 1.1364. Ratio of the two clocks: 600/528 = 1.1364. The cycle count is essentially 2^30 (1.0737 x 10^9), about 0.07% low.
The interval between stalls is 2^32 cycles or a multiple. Timestamps from two runs at 600 MHz, seconds since start:
run A: 19, 26, 33, 40, 47, 55, 69, 83, 105, 112, 126, 133, 140
run B: 12, 19, 26, 33, 40, 47, 69, 76, 105, 119, 126, 133, 140
Spacing is 7 s or multiples of 7 s. 2^32 / 600e6 = 7.158 s.
At 528 MHz the spacing becomes 8 s (2^32 / 528e6 = 8.133 s):
13, 21, 30, 38, 46, 54, 78, 86, 103, 111, 119, 127, 135, 143
Stall lengths come in multiples: 1.788 s, 3.577 s, occasionally 5.37 s - always an integer multiple of the same quantum.
systick keeps running, my IntervalTimer does not. I measure elapsed time with micros(), which is built on systick_millis_count, so if it reports 1.788 s then the systick ISR must have run ~1788 times. Meanwhile a counter incremented by my own 1 ms IntervalTimer advances only 40-160 times over the same interval.
Raising the IntervalTimer priority from 160 to 32 changed nothing, so this is not preemption by a higher-priority handler.
The stall lands anywhere. I instrumented several call sites; the long delay has been observed inside analogRead(), inside delayMicroseconds(20), and inside a routine decoding a 30x12 pixel sprite from valid data. There is no single function at fault.
Each of these was disabled or bypassed while the rest kept running, and the stalls continued unchanged:
- SD card - accumulated time inside _read is zero during stalls
- PSRAM - a dedicated stress test (64 blocks of 16-256 KB, allocate, free, write and scattered read every iteration, both chips) ran 5 h 40 min / 383 381 iterations with zero stalls, including with the SD card mounted and yield() called every iteration
- I2C touch controller - bus untouched
- ADC - averaging of 4 and 16 give an identical quantum; with analogRead() removed entirely the stall moves elsewhere
- Display - both with DMA (updateScreenAsync) and with blocking updateScreen(), and with transmission skipped entirely
- Audio library and MQS output - not initialised
- USB - stalls occur when powered from a charger with no host attached
- Power supply - no droop visible on a scope, a 4700 uF cap across 3.3 V changes nothing
- Temperature - hottest component on the board reads 36.4 C on a thermal camera
Disabling the audio path and display DMA together made stalls more frequent (every ~7 s instead of every few tens of seconds) and longer (mostly double quanta). That suggests interrupt sources were shortening the stalls rather than causing them.
Setup:
Teensy 4.1, 600 MHz, two IPS6404L PSRAM chips (16 MB), hand-soldered
PlatformIO, framework-arduinoteensy 1.162.0, GCC 15.2.1
-Os -flto -fno-strict-aliasing
Display: ILI9341 over SPI with DMA, 320x240 framebuffer in DMAMEM
SdFat over SDIO (built-in socket)
One IntervalTimer at 1 ms, priority 160
Audio library with AudioPlayQueue -> MQS
The application is a port of a 1990s DOS game; peak heap usage in PSRAM is 8.7 MB
I grepped the core and libraries for ARM_DWT_CYCCNT and every use I found follows the safe while (ARM_DWT_CYCCNT - begin < cycles) pattern. micros() in delay.c looks correct too - it clamps the fraction and uses unsigned subtraction.
Questions:
- Is there anything in the core, in SdFat, or in the FlexSPI/PSRAM support that would stop the main loop and PIT timers while leaving systick running, for a duration tied to the cycle counter?
- Could a PIT/GPT timer be gated or reloaded in a way that produces a 2^30-cycle gap?
- Has anyone seen stalls synchronised to the 32-bit cycle counter rollover on the i.MX RT1062?
Happy to run further tests - I have the board instrumented with per-stage timing and a set of compile-time switches to cut out each subsystem.