I came across an interesting bug recently. Suddenly my firmware would start crashing, and in the debugger I see that it calls a function with wrong parameters. The offending code:
1 2 3 4 |
if( var) { func( var); var = 0; } |
When the problem happens, var is actually 0. It turns out that the load of the variable address returned the value stored just before. On Arm, this is a PC relative load LDR R3, [PC, offset]. How can something so fundamental go wrong?
The behavior under the debugger is odd. When I single step through the code, there is no problem. I put a dynamic breakpoint after the load, triggering when a bad register value is detected. Still never triggered. I hit go, and just a few iterations later I hit the problem.
The code is executed just after the processor wakes from sleep, and the problem only happens after waking from deep sleep, but not from normal sleep. After deep sleep I need to switch from the default clock to my desired clock. The problem does not happen immediately, there are a dozens others instructions correctly executed after waking up before the read error occurs. What can cause this?
I use an STM32 processor, and run it using the HSI48 clock used for the USB peripheral. The processor has a clock recovery system (CRS) which can automatically trim HSI48 to be precisely 48 MHz. I have selected the internal RTC as the trimming reference. Now while the CPU is in deep sleep, all clocks apart from the RTC are stopped. When waking up, the CRS resumes counting once we are back on the HSI48. But the wakeup is caused be the same RTC, so the RTC clock events as seen by the CRS are systematically delayed, and so it decides to trim the HSI48 faster, and faster. In the end the processor is clocked just a tiny bit too fast for its internal flash memory, so reads go wrong.
To avoid this runaway, I updated the deep sleep code to reset the trim value after waking up:
1 2 3 4 5 6 7 8 9 10 11 12 |
PWR_EnterSTOPMode( PWR_Regulator_ON, PWR_STOPEntry_WFI); RCC_HSI48Cmd( ENABLE); if( CRS->CR & CRS_CR_AUTOTRIMEN ) { CRS->CR &= ~CRS_CR_AUTOTRIMEN; CRS->CR = ( CRS->CR & ~CRS_CR_TRIM) | 0x20 << 8; CRS->CR |= CRS_CR_AUTOTRIMEN; } while( ( RCC->CR2 & RCC_CR2_HSI48RDY) == 0) { } RCC_SYSCLKConfig( RCC_SYSCLKSource_HSI48); |
So when your flash reads start to fail, remember to check the clocks. They might be trimmed outside specifications.