STM32 Fatal Fault helper

25 Oct 2017

Just a quick post this week as I’ve been busy.

Sometimes on embedded boards you want to stop when an error occurs and spit out some debug information. Usually I use one of the USART ports and stream stuff out of there, but that’s not always worth the setup. You could use a debugger, but I’ve not got round to working out how to get all of that to work on my boards yet, that’s on my list of things to do.

So I’ve got this short bit of code that stores some values into easy to find registers and then going into an infinite loop while flashing SOS on the debug LED.

#define ERR_LONG HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); \
HAL_Delay(300); HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(100);

#define ERR_SHORT HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); \
HAL_Delay(100); HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); HAL_Delay(100);

void FatalFault(uint32_t val1, uint32_t val2)
{
    UNUSED(val1);
    UNUSED(val2);

    asm("mov R7,R0;"
        "mov R8,R1;"
        "mov R9,R14;"
    );

    HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);

    while (1) {
        ERR_SHORT
        ERR_SHORT
        ERR_SHORT

        ERR_LONG
        ERR_LONG
        ERR_LONG

        ERR_SHORT
        ERR_SHORT
        ERR_SHORT

        HAL_Delay(200);
    }
}

The macros are just there to make the SOS part easier to write as it keeps things clearer.

The small chunk of assembly does the more interesting stuff.

ARM cores tend to pass the first four parameters as registers with the rest going on the stack. While this makes them easy to get you need to move them out of the way before you call anything else, which is what the first two instructions do.

The STCube GPIO instructions don’t do much so it’s safe (at the moment) to just move them over to R7, R8. If this isn’t the case in future you can just replace the function calls with the assembly (write to the GPIOx_BSRR and GPIOx_BRR registers).

I used those as at the top of the second column in the ST-LINK Utility’s ‘MCU Core’ view.

R14 holds the calling functions return address, e.g. the program counter (PC) when the branch was taken, so having that gives you some more context about the fault.

Using the map file

You might be wondering what the point of having the callers address is if I’m not using a debugger.

When GCC compiler stuff it can drop out a map file, which lists all the different parts the linker joined together and their addresses. If you start at the end of the file and go backwards a bit you’ll find a whole lot of .text.XXXXX entries, these are the function names, for example (I’ve shortened the lines to make them more readable):

 .text.HAL_GPIO_Init
                0x080003d8      0x1d8 S:\Apps\stm32\Projects\...\stm32f1xx_hal_gpio.o
                0x080003d8                HAL_GPIO_Init
 .text.HAL_GPIO_WritePin
                0x080005b0        0xc S:\Apps\stm32\Projects\...\stm32f1xx_hal_gpio.o
                0x080005b0                HAL_GPIO_WritePin
 .text.HAL_GPIO_TogglePin
                0x080005bc        0x8 S:\Apps\stm32\Projects\...\stm32f1xx_hal_gpio.o
                0x080005bc                HAL_GPIO_TogglePin

If you had a pre-fault call PC of 0x080004c6 you know that it occurred inside the HAL_GPIO_Init function, as it’s between 0x080003d8 (starting address of HAL_GPIO_Init) and 0x080005b0 (starting address of HAL_GPIO_WritePin).

This post has been taged as STM32