Help with debugging an INVSTATE hard fault on STM32F405 microcontroller
I'm having a hard time tracking a source of the hard fault occuring in my program written in C++ for the STM32F405 microcontroller.
I only use three sources of interrupts in this project - two timer interrupts (TIM2 overflow happening with 36kHz frequency having intermediate priority, and the TIM3 overflow interrupt happening at 12kHz frequency, having lowest priority) and also a DMA1 transfer complete interrupt with highest priority.
The hardest part of tracking that error comes from the fact that it seems completely unpredictable - it can happen a few seconds into the program, or the whole program can run for half an hour without problems and then crash.
Using the build-in fault analyzer of Atollic True Studio I've been able to find out that the actual type of fault is INVSTATE. I did some reading on this issue, and apparently this error could be caused by trying to branch to an instruction with an even address (LSB equal to 0). Fortunately, the fault analyzer does all the stack-involving calculations for user and allows to jump to the faulty C or assembly line.
I jumped to the assembly line, and turns out this is the faulty line:
080022fe: cmp r4, r8I was confused at first, because there's no branching involved in this line, but I think that it may be the first line AFTER the fault has occured, so I looked at the previous line:
080022fa: bl 0x8001dd4 <Generator::getOutput()>So there's our branch. I'm assuming that the program jumps to Generator::getOutput() function, and crashes on it's return. This function call happens in the ISR of TIM3 overflow.
Looking at fault analyzer's snapshot of the registers' state just before the fault I noticed a weird thing - the link register pointed to the location in the RAM, not in FLASH memory 0x20001e70. The function that supposedly crashes ends with `bx lr` instruction, so that would explain the INVSTATE fault (the address is even). Also, that address points to some field of a statically alocated object, not to the stack.
This value appears in LR register only when the fault occurs - like I mentioned, the program can run for minutes without crashing, which means that this function gets called millions of times, and when I stepped through this function many times the LR register always contained proper return value (`0x080022ff`)
So the thing that is bugging me - where could this value come from? There are no further function calls or branches in the `getOutput()` function, so the LR register *does not* get updated there. The only interrupt with higher priority than currently serviced interrupt is the DMA Transfer Complete interrupt. Here's the DMA ISR:
void DMA1_Stream4_IRQHandler(void) {
if(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4))
{
DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
if (channel_to_send < 4) {
dac.DmaSend(channel_to_send++);
}
}
}As you can see, the ISR calls the DmaSend function. Here's it's code:
void Dac::DmaSend(uint8_t channel) {
uint32_t value = offset_[channel] + register_[channel]*multiplier_[channel];
if (value > 65535) {
value = 0;
} else {
value = 65535 - value;
}
GPIOC->BSRRL |= GPIO_Pin_5;
output_buffer_[0] = (1 << 5) | (channel << 1);
output_buffer_[1] = (uint8_t)(value >> 8);
output_buffer_[2] = (uint8_t) value;
GPIOC->BSRRH |= GPIO_Pin_5;
DMA_Cmd(DMA1_Stream4, ENABLE);
}Is it possible that the DMA interrupt service routine somehow corrupts the link register? The only real memory write (outside of writing the SFRs) happening in the ISR is the write to output_buffer_ array, and it does not overflow (the array is declared as uint8_t output_buffer_[3]). If not, then what could possibly corrupt the LR value? Or am I missing something here and the value is compeletely fine?
