Skip to main content
Associate II
June 20, 2024
Question

Wrong PC (progam counter) when exiting ISR

  • June 20, 2024
  • 38 replies
  • 13520 views

Hi,

i'm using a STM32L031. In my code i first initialize the MCU (FLASH, CLOCK, INTERRUPTs, GPIOs), start the LPTIM (running on LSI), then slow down the MSI clock, start the timer22 for PWM and go into low-power sleep mode.  When i let the program run i experience an hard-fault after exiting the ISR (count = ccr) from the LPTIM because at the moment when r7 and PC gets popped from the stack PC gets a wrong value and suddenly points to RAM instead of FLASH. The strange thing for me is also, that when i don't use the LPTIM for waking up the MCU, but use a GPIO (EXTI) ist works fine. It also works fine when i don't go into low-power sleep mode but into stop-mode and then let the LPTIM wake up the MCU. In this case of course the PWM doesn't work while in stop-mode.

So can someone help me out an tell me why the pc gets a wrong value?

Here my example code:

#######################################################

int main(void){

// Initialize

FLASH->ACR |= FLASH_ACR_PRFTEN; // Enable Pre-Fetch

RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // Clock für Systemconfig aktivieren

RCC->APB1ENR |= RCC_APB1ENR_PWREN;

 

// Clock setup

RCC->ICSCR &= ~(0b111 << 13);

RCC->ICSCR |= 0b101 << 13;

while(!(RCC->CR & RCC_CR_MSIRDY));

 

// GPIO setup

RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Aktivieren des Clock-Gates für GPIOA

GPIOA->MODER &= ~(0b11); // Pin PA0 als Input - BUTTON

GPIOA->PUPDR |= 0b10; // Pin PA0 Pull-down

EXTI->IMR |= 0b1; // Interrupt Maske für PA0

EXTI->RTSR |= EXTI_RTSR_RT0; // Konfigure Interrupt für Rising Edge für PA0

NVIC_SetPriority(EXTI0_1_IRQn, 1); // Priorität von PA0 Interrupt auf 0

NVIC_EnableIRQ(EXTI0_1_IRQn); // Interrupt von PA0 aktivieren

PWR->CSR |= PWR_CSR_EWUP1; // Enable PA0 als Wakeup Pin

GPIOA->MODER &= ~(0b11 << 30); // Pin PA15 als Input - DPS INTERRUPT

EXTI->IMR |= 0b1 << 15; // Interrupt Maske für PA15

EXTI->FTSR |= EXTI_RTSR_RT15; // Konfigure Interrupt für Falling Edge für PA15

NVIC_SetPriority(EXTI4_15_IRQn, 2); // Priorität von PA15 Interrupt auf 0

NVIC_EnableIRQ(EXTI4_15_IRQn); // Interrupt von PA15 aktivieren

GPIOA->MODER &= ~(0b11 << 14); // Pin PA7 auf Alternate Function Mode

GPIOA->MODER |= 0b10 << 14;

GPIOA->AFR[0] |= 0b0101 << 28; // Pin PA7 mit TIM22 CH2 verbinden

GPIOA->MODER &= ~(0b1111 << 6); // PA4 (EN1) - PA3 (EN2) - Konfiguration Verstärker

GPIOA->MODER |= (0b0101 << 6); // PA4 + PA3 auf Output Mode

GPIOA->MODER &= ~(1 << (9 * 2)); // PA9 auf Alternate function mode

GPIOA->MODER &= ~(1 << (10 * 2)); // PA10 auf Alternate function mode

GPIOA->OTYPER |= 1 << 9; // PA9 auf open-drain

GPIOA->OTYPER |= 1 << 10; // PA10 auf open-drain

GPIOA->OSPEEDR |= 0b11 << (9 * 2); // PA9 auf High-Speed

GPIOA->OSPEEDR |= 0b11 << (10 * 2); // PA10 auf High-Speed

GPIOA->PUPDR |= 1 << (9 * 2); // PA9 auf Pull-Up

GPIOA->PUPDR |= 1 << (10 * 2); // PA10 auf Pull-Up

GPIOA->AFR[1] |= 1 << 4; // PA9 mit I2C1_SCL verbinden

GPIOA->AFR[1] |= 1 << 8; // PA10 mit I2C1_SDA verbinden

GPIOA->MODER &= ~(1 << ((6 * 2) + 1)); // Default 11 -> 01 -> PA6 auf Output Mode

GPIOA->ODR |= (1 << 6); // PA6 auf HIGH

__enable_irq();

 

// LPTIM setup

piepsen = 1;

RCC->CSR |= RCC_CSR_LSION; // LSI aktivieren

while(!(RCC->CSR & RCC_CSR_LSIRDY)); // Warten bis LSI Ready

RCC->CCIPR |= RCC_CCIPR_LPTIM1SEL_0; // LSI als Clock Souce für LPTIM

RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN; // Aktivieren des Clock-Gates für LPTIM1

NVIC_EnableIRQ(LPTIM1_IRQn);

NVIC_SetPriority(EXTI0_1_IRQn, 3); // Priorität von PA0 Interrupt auf 0

LPTIM1->CFGR |= 0b101 << 9; // Prescaler auf 32 -> 37kHz -> 1156.25 Hz

LPTIM1->IER |= 0b11;

LPTIM1->CR |= LPTIM_CR_ENABLE; // LPTIM starten

LPTIM1->ARR = (1156.25 / 1000)*(200); // Auto Reload Register

LPTIM1->CMP = (1156.25 / 1000)*100; // Capture Compare

LPTIM1->CR |= LPTIM_CR_SNGSTRT; // Timer start in Single Shot mode

 

// Slow down clock

RCC->ICSCR &= ~(0b111 << 13);

RCC->ICSCR |= 0b001 << 13;

while(!(RCC->CR & RCC_CR_MSIRDY)); // Wait for MSI is ready

 

// Start TIMER 22

RCC->APB2ENR |= RCC_APB2ENR_TIM22EN;

TIM22->ARR = 131072/500; // Auto-reload value (period)

TIM22->CCR2 = 131072/500/2;

TIM22->CCMR1 |= 0b110 << 12; // PWM mode 1

TIM22->CCER |= TIM_CCER_CC2E; // Enable channel 2 output

TIM22->CR1 |= TIM_CR1_CEN; // Enable TIM22

 

// Enter LOW-POWER SLEEP MODE

while (FLASH->SR & FLASH_SR_BSY) {} // Auf Flash warten bis alles erledigt

FLASH->ACR |= FLASH_ACR_SLEEP_PD; // FLASH schlafen legen

PWR->CR |= PWR_CR_LPSDSR; // Volt Regulator in Low Power Mode --> einziger vorteil wäre sonst schnellerer wake-up

__WFI();

 

while(1);

}

The ISR looks like this:

void LPTIM1_IRQHandler(void) { // Überwachung Piepsdauer

if(LPTIM1->ISR & 0b1){ // Capture Compare Interrupt -> Piepsen AUS

status = 3;

LPTIM1->ICR |= 0b1;

TIM22_Stop(); // Abfrage damit nicht Gamification unterbrochen wird

}

else{ // Auto Reload Register Interrupt -> Pieps-Pause AUS

piepsen = 0;

LPTIM1->ICR |= 0b1 << 1;

LPTIM_Stop();

}

}

#####################################################################

Screenshots:

1. In screenshot "Beginning" you can see the system right after the ISR was triggered.

2. Screenshot "Before" was taken just before the hard-fault happens.

3. Screenshot "Hard-Fault" doesn't need an explanation 

Thanks in advance

Greetings from Salzburg, Austria

 

This topic has been closed for replies.

38 replies

Tesla DeLorean
Guru
June 20, 2024

Please use the </> icon / tool to paste in source code.

Not sure why the stack would get corrupt, perhaps look at other interrupts / callbacks and what they might be doing that would damage the stack frame.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
BrigeiAuthor
Associate II
June 21, 2024

Hi,

There are no other interrupts just the LPTIM interrupt. The sequence is like:

1. initializing

2. starting LPTIM in interrupt mode

3. starting TIM22 witb PWM

4. starting low-power sleep mode

5. waiting for interrupt. Because of the LPTIM settings (100ms and 200ms) it‘s guaranteed, that i‘m in this state when the interrupt happens

6. interrupt from LPTIM. there is no other interrupt going on and no callbacks.

7. Hard fault when exiting the ISR from LPTIM

BrigeiAuthor
Associate II
June 21, 2024
int main(void){
// Initialize
FLASH->ACR |= FLASH_ACR_PRFTEN; // Enable Pre-Fetch
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; // Clock für Systemconfig aktivieren
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
 
// Clock setup
RCC->ICSCR &= ~(0b111 << 13);
RCC->ICSCR |= 0b101 << 13;
while(!(RCC->CR & RCC_CR_MSIRDY));
 
// GPIO setup
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Aktivieren des Clock-Gates für GPIOA
GPIOA->MODER &= ~(0b11); // Pin PA0 als Input - BUTTON
GPIOA->PUPDR |= 0b10; // Pin PA0 Pull-down
EXTI->IMR |= 0b1; // Interrupt Maske für PA0
EXTI->RTSR |= EXTI_RTSR_RT0; // Konfigure Interrupt für Rising Edge für PA0
NVIC_SetPriority(EXTI0_1_IRQn, 1); // Priorität von PA0 Interrupt auf 0
NVIC_EnableIRQ(EXTI0_1_IRQn); // Interrupt von PA0 aktivieren
PWR->CSR |= PWR_CSR_EWUP1; // Enable PA0 als Wakeup Pin
GPIOA->MODER &= ~(0b11 << 30); // Pin PA15 als Input - DPS INTERRUPT
EXTI->IMR |= 0b1 << 15; // Interrupt Maske für PA15
EXTI->FTSR |= EXTI_RTSR_RT15; // Konfigure Interrupt für Falling Edge für PA15
NVIC_SetPriority(EXTI4_15_IRQn, 2); // Priorität von PA15 Interrupt auf 0
NVIC_EnableIRQ(EXTI4_15_IRQn); // Interrupt von PA15 aktivieren
GPIOA->MODER &= ~(0b11 << 14); // Pin PA7 auf Alternate Function Mode
GPIOA->MODER |= 0b10 << 14;
GPIOA->AFR[0] |= 0b0101 << 28; // Pin PA7 mit TIM22 CH2 verbinden
GPIOA->MODER &= ~(0b1111 << 6); // PA4 (EN1) - PA3 (EN2) - Konfiguration Verstärker
GPIOA->MODER |= (0b0101 << 6); // PA4 + PA3 auf Output Mode
GPIOA->MODER &= ~(1 << (9 * 2)); // PA9 auf Alternate function mode
GPIOA->MODER &= ~(1 << (10 * 2)); // PA10 auf Alternate function mode
GPIOA->OTYPER |= 1 << 9; // PA9 auf open-drain
GPIOA->OTYPER |= 1 << 10; // PA10 auf open-drain
GPIOA->OSPEEDR |= 0b11 << (9 * 2); // PA9 auf High-Speed
GPIOA->OSPEEDR |= 0b11 << (10 * 2); // PA10 auf High-Speed
GPIOA->PUPDR |= 1 << (9 * 2); // PA9 auf Pull-Up
GPIOA->PUPDR |= 1 << (10 * 2); // PA10 auf Pull-Up
GPIOA->AFR[1] |= 1 << 4; // PA9 mit I2C1_SCL verbinden
GPIOA->AFR[1] |= 1 << 8; // PA10 mit I2C1_SDA verbinden
GPIOA->MODER &= ~(1 << ((6 * 2) + 1)); // Default 11 -> 01 -> PA6 auf Output Mode
GPIOA->ODR |= (1 << 6); // PA6 auf HIGH
__enable_irq();
 
// LPTIM setup
piepsen = 1;
RCC->CSR |= RCC_CSR_LSION; // LSI aktivieren
while(!(RCC->CSR & RCC_CSR_LSIRDY)); // Warten bis LSI Ready
RCC->CCIPR |= RCC_CCIPR_LPTIM1SEL_0; // LSI als Clock Souce für LPTIM
RCC->APB1ENR |= RCC_APB1ENR_LPTIM1EN; // Aktivieren des Clock-Gates für LPTIM1
NVIC_EnableIRQ(LPTIM1_IRQn);
NVIC_SetPriority(EXTI0_1_IRQn, 3); // Priorität von PA0 Interrupt auf 0
LPTIM1->CFGR |= 0b101 << 9; // Prescaler auf 32 -> 37kHz -> 1156.25 Hz
LPTIM1->IER |= 0b11;
LPTIM1->CR |= LPTIM_CR_ENABLE; // LPTIM starten
LPTIM1->ARR = (1156.25 / 1000)*(200); // Auto Reload Register
LPTIM1->CMP = (1156.25 / 1000)*100; // Capture Compare
LPTIM1->CR |= LPTIM_CR_SNGSTRT; // Timer start in Single Shot mode
 
// Slow down clock
RCC->ICSCR &= ~(0b111 << 13);
RCC->ICSCR |= 0b001 << 13;
while(!(RCC->CR & RCC_CR_MSIRDY)); // Wait for MSI is ready
 
// Start TIMER 22
RCC->APB2ENR |= RCC_APB2ENR_TIM22EN;
TIM22->ARR = 131072/500; // Auto-reload value (period)
TIM22->CCR2 = 131072/500/2;
TIM22->CCMR1 |= 0b110 << 12; // PWM mode 1
TIM22->CCER |= TIM_CCER_CC2E; // Enable channel 2 output
TIM22->CR1 |= TIM_CR1_CEN; // Enable TIM22
 
// Enter LOW-POWER SLEEP MODE
while (FLASH->SR & FLASH_SR_BSY) {} // Auf Flash warten bis alles erledigt
FLASH->ACR |= FLASH_ACR_SLEEP_PD; // FLASH schlafen legen
PWR->CR |= PWR_CR_LPSDSR; // Volt Regulator in Low Power Mode --> einziger vorteil wäre sonst schnellerer wake-up
__WFI();
 
while(1);
}
gbm
Super User
June 21, 2024

Look for the problem in the routines called from LPTIM ISR.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
BrigeiAuthor
Associate II
June 21, 2024

The functions i also use in other places in the code - they work fine. The problem also appears when i don‘t use them (comment out) in the ISR. 

BrigeiAuthor
Associate II
June 21, 2024

…and like i said, it works fine when i use the stop mode instead of the low power sleep mode.

waclawek.jan
Super User
June 22, 2024

There's only an empty loop after WFI. Why would r7 content matter? 

JW

BrigeiAuthor
Associate II
June 22, 2024

The while(1) is only for testing purpose and r7 is not relevant for me. In my opinion it's only the program counter which has a wrong value when it is popped back from the stack. In the meanwhile i tried to put a Delay(1) right in front of the _wfi (instead of the debug break point) but i still get a hard-fault.

Tesla DeLorean
Guru
June 22, 2024

Seems like more going on here.

The RAM/FLASH should maintain in low power mode. Wait state or prefetch should also work.

Not clear why the stack would get trashed short of other things running, perhaps another IRQ or SysTick, or the power.

Have Hard Fault handler output diagnostics. Try to identify when the stack content gets damaged, and work that test backward to point it changes.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
BrigeiAuthor
Associate II
June 24, 2024

Unfortunately i can't explain why.  To previous asked questions: There should not be another IRQ or SysTick Interrupt. @Tesla DeLorean What did you mean with "or power"?

About the Hard Fault diagnostics: There is nothing in the 0xE000ED28 (CFSR), 0xE000E008 (ACTLR), 0xE000EFA8 (ABFSR), 0xE000ED28 (MMFSR) or 0xE000ED2C (HFSR) Register (just zeros) when the GDB stops in the Hard Fault Routine. (By the way is it normal that this registers aren't listet in de SFR View in Stm32CubeIDE? I have to look them up in the memory itself). Is there anything else i could do for diagnostics?

I also realized, that when i debug and set the break point at the _wfi (like i mentioned before) the mcu doesn't actually enter the sleep mode but just steps over it. There is no interrupt pending - so how come?!

Another thing - i noticed, that when i put the LPTIM and the Sleep Mode in "Debug Mode" (with DBGMCU->CR |= 1 and DBGMCU->APB1FZ |= 1 << 31) the MCU enters the sleep mode with _wfi and i don't get the hard fault neither. 

When i debug step by step everything kind of looks normal to the point where i let the MCU run with the play button. Then i always end up with the corrupt stack. How can i just get one step backwards? Or is there a proper way to debug the sleep mode? There must be something in between the _wfi and the interrupt by the LPTIM.

Tesla DeLorean
Guru
June 24, 2024

That power to critical circuits or clocks was lost, resulting in the RAM or MCU state getting compromised.

Using the debugger in the low-power modes is also problematic, it's better for the system to report it's situation independently, say via a UART, so you can monitor what's happening non-invasively, and as close to user-experience as possible.

Tips, Buy me a coffee, or three.. PayPal Venmo (See Profile) Up vote any posts that you find helpful, it shows what's working..
BrigeiAuthor
Associate II
June 25, 2024

I just tried to set the timer, which makes the interrupt, at a longer period so i have the opportunity to stop the programm when it is in sleep mode and have a look at the registers. There i noticed, that the pushed pc from the Enter_LP_Sleep function, has the value 0x80001B63 which ist not aligned?! Should it be? After the value is popped the program jumps correctly to 0x80001B62. But ist this already a sign for a problem at this point?

waclawek.jan
Super User
June 27, 2024

Show content of stack before WFI() and after it (i.e. at the beginning of the ISR which woke up the processor).

JW

BrigeiAuthor
Associate II
June 27, 2024
  1. Break before _wfi --> everything looks NORMAL
  2. After continuing from Step 1. break at ISR LPTIM --> pc in stack frame is OK
  3. From beginning without breaking at _wfi --> pc in stack frame ist CORRUPT
waclawek.jan
Super User
June 27, 2024

Interesting.

In the problematic case, the ISR stacking goes wrong and only one of the 8 words (PSR) gets actually stacked. As if the SRAM would be unavailable for the core immediately after the wakeup.

Now I don't know why is that, but to me this thing is suspicious in your code:

 

// Slow down clock
RCC->ICSCR &= ~(0b111 << 13);
RCC->ICSCR |= 0b001 << 13;
while(!(RCC->CR & RCC_CR_MSIRDY)); // Wait for MSI is ready

 

Here, you switch the MSI speed *twice* - first you set it to range 0 and immediately after that to 1. Don't do that.

Try to place a bunch of NOPs or a loop just before WFI, just as an experiment.

Also, how are RCC_AHBSMENR bits set?

JW

 

PS.

> has the value 0x80001B63 which ist not aligned?! Should it be? After the value is popped the program jumps correctly to 0x80001B62

That's OK. The Cortex-Mx processors don't use the original 32-bit ARM instruction set, but the 16-bit Thumb instruction set, and there, the LSB of address indicates the Thumb mode.

BrigeiAuthor
Associate II
June 28, 2024

Thanks for your reply. I just tried:

1. Avoiding setting clock to "0", instead directly setting it to desired speed --> still same behavior HARD-FAULT

2. 30 NOP in front of _wfi (actually visible in assambler code) --> still same behavior HARD-FAULT

3. All bits in RCC_AHBSMENR are 1 --> everything activated in sleep-mode

ST Employee
July 2, 2024

Hello @Brigei 

check STM32L031xx/L041xx device errata - Errata sheet section 2.1.2 : 

SarraS_0-1719937124158.png

 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
waclawek.jan
Super User
July 2, 2024

Hi @Sarra.S ,

IMO the quoted erratum does not apply to this case:

The erratum talks about invalid FLASH fetch. This is obviously not the case here: the first FLASH fetch after wakeup is the interrupt vector fetch, and that is obviously correct, as execution reaches the ISR. The problem is the incorrectly stacked registers. In other words, the problem is *not* FLASH fetch, the problem is *register stacking in SRAM fails*, i.e. it's the SRAM which is problematic after wakeup.

JW

 

ST Employee
July 3, 2024

Hey @waclawek.jan

This is related to the flash wakeup time when the system is waking up from LPSLEEP. 

The first instruction fetch from Flash after waking up is incorrect due to the Flash not being ready (as described in the errata, recovery time is needed), the POP that attempts to restore the PC from the stack could end up fetching the wrong address for the PC, and the CPU will resume execution at an incorrect address leading to hardFault. 

As the workaround suggest and I think @Brigei tested it, removing the line that puts the flash in power down mode will solve the issue. 

 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
waclawek.jan
Super User
July 4, 2024

Hi @Sarra.S ,

 

The first instruction fetch from Flash after waking up is incorrect due to the Flash not being ready (as described in the errata, recovery time is needed), the POP that attempts to restore the PC from the stack could end up fetching the wrong address for the PC, and the CPU will resume execution at an incorrect address leading to hardFault.

This is not the case here.

If you look at screenshot in 3.jpg in @Brigei 's post above, PC points to the correct address of first instruction in LPTIM1_IRQHandler(). However, what is not correct there are the stacked registers.

Also, the whole thread is about the fact, that the hardfault does *not* occur immediately after the wakeup, but that *whole* LPTIM1_IRQHandler() after wakeup is executed correctly (that's many many fetches from FLASH, all correct), and that the hardfault occurs upon *exit* from LPTIM1_IRQHandler(), when the incorrectly stacked PC is popped from the stack.

So, as I've said above, the problem is *not* incorrectly woken up FLASH, but that 7 out of the 8 register values stored to RAM during the stacking upon ISR entry are not written by the processor into RAM at all, and those RAM places retained their previous values. Again, refer to the screenshots in @Brigei 's post above and their descriptions, they very clearly indicate the problem, and it very obviously is NOT consequence of incorrect FLASH fetch.

That the problem appears to go away by disabling FLASH powerdown, is IMO coincidental, thus without further investigation of the true mechanism of problem should not be considered to be a solution to this problem.

JW

ST Employee
July 8, 2024

Hello @waclawek.jan 

I'm still investigating, meanwhile, ideally @Brigei shares a project to debug or more screenshots of all the registers/disassembly 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.
waclawek.jan
Super User
July 8, 2024

@Brigei,

Do you use some sort of (maybe homebrew) RTOS?

Post a minimal complete compilable example exhibiting the problem.

JW

BrigeiAuthor
Associate II
July 9, 2024

Hi,

you'll find the project attached.

About RTOS: I do have installed homebrew but only for terminal packages. I'm working on my macbook with STM32CubeIDE Version: 1.14.1 Build: 20064_20240111_1413 (UTC).

ST Employee
July 10, 2024

Hello @Brigei and @waclawek.jan 

When disabling debug in low power mode before entering the debug session, the hardfault is no longer visible, this is probably due the desynchronization between core and its subsystem peripherals (flash).

This issue is under investigation in internal ticket 186267.

 

 

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.