cancel
Showing results for 
Search instead for 
Did you mean: 

Hard fault with STM32G0B1 and LVGL

JulienD
Senior

Hello,

 

On one hand, I built an app that uses I2C, 2 UARTS, ADCs, captures PWM, provides PWM. All of them rely heavily on the use of DMAs. This app gives good results without any apparent issue.

On the  other hand, I built a test app on the same target to drive a display using LVGL 9.2.2. It seems to work too.

 

So far, so good... Now I put both together: the result produces an hard fault with STKALIGN and UNALIGN_TRP bits set.

I suspect an overflow somewhere.

As the app is pretty big, I started to strip the code as much as possible while keeping the hardfault. Stripping code allowed the app to be compiled without optimization hoping this will help resolving.

This leads to a single module that is provided below.

Some elements:
- app is pre-configured using CubeMx.
- in the provided code, nearly any change will make the hard fault disappear (at least for minutes, I don't have the oppportunity to let the app run for hours for each change I did).
- pwm capture and pwm outputs are not enabled in the code. But if I remove one of them from the ioc configuration, the hard fault disappears.

After several days of struggling, I'm requesting some help on how I could solve or investigate deeper the failure.

Here's the code for the display module and how it is called, a zip for the whole minimal project if other pieces of information are needed, also screenshot of hard fault and cubeMx.

 

Thanks

Julien

JulienD_1-1740667213052.png

 

JulienD_0-1740667038774.png

 

 

#include "display.h"
#include <string.h>
#include <strings.h>
#include <stdint.h>
#include <stddef.h>

#include "lvgl.h"

#include <assert.h>

#include "usart.h"
#include "consts.h"

#define WIDTH 128
#define HEIGHT  64


#define DISP_BUF_SIZE   ((WIDTH * HEIGHT / 8))

static lv_obj_t* m_default_scr;
static lv_color_t m_buffer_lvgl[DISP_BUF_SIZE + 8] = { 0 };    // +8 reserved for lvgl
static lv_display_t* m_display = NULL;
static lv_obj_t* lblAltitude = NULL;


volatile static bool m_modification_allowed = true;

static char m_text[50];

static char m_uart_buffer[DISP_BUF_SIZE] = {0};

static void send_buffer_to_display(uint8_t* buffer, uint32_t len);
static void prepare_lvgl_screen(void);
static void my_flush_cb(lv_display_t* display, const lv_area_t* area, uint8_t* px_map);
static void my_rounder_cb(lv_event_t* e);


//----------------------------------------------------------------------------------------------------------------


void display__init()
{
    prepare_lvgl_screen();
}


void display__process(void)
{
    if (m_modification_allowed)
    {
        static uint8_t i = 0;
        i++;
        if (i % 2 == 0)
            strcpy(m_text, "0");
        else
            strcpy(m_text, "1");
        lv_label_set_text_static(lblAltitude, m_text);

    }
    lv_timer_handler();
}


static void send_buffer_to_display(uint8_t* buffer, uint32_t len)
{
    assert(len <= sizeof(m_uart_buffer));
    memcpy(m_uart_buffer, buffer, sizeof(m_uart_buffer));

    HAL_HalfDuplex_EnableTransmitter(&UART_GSU);
    HAL_UART_Transmit_DMA(&UART_GSU, (uint8_t*)m_uart_buffer, len);
}


//---------------------------------  uart end transfer callback ------------------------------------------------


void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart)
{
    if (huart == &UART_GSU)
    {
        lv_display_flush_ready(m_display);   // tells LVGL transfer is ended. It can render a frame again if it wants to.
        m_modification_allowed = true;
    }
}



//---------------------------------  LVGL stuff ------------------------------------------------


static void my_flush_cb(lv_display_t* display, const lv_area_t* area, uint8_t* px_map)
{
    m_modification_allowed = false;
    send_buffer_to_display(px_map + 8, DISP_BUF_SIZE);
}


// Never called by LVGL
static void my_rounder_cb(lv_event_t* e)
{
    lv_area_t* area = lv_event_get_param(e);

    /* Round the height to the nearest multiple of 8 */
    area->y1 = (area->y1 & ~0x7);
    area->y2 = (area->y2 | 0x7);
}

static void prepare_lvgl_screen(void)
{
    lv_init();

    m_display = lv_display_create(WIDTH, HEIGHT);
    lv_display_set_color_format(m_display, LV_COLOR_FORMAT_I1);

    lv_display_set_buffers(m_display, m_buffer_lvgl, NULL, sizeof(m_buffer_lvgl), LV_DISPLAY_RENDER_MODE_FULL);
    lv_display_set_flush_cb(m_display, my_flush_cb);
    lv_tick_set_cb(HAL_GetTick);
    lv_display_add_event_cb(m_display, my_rounder_cb, LV_EVENT_INVALIDATE_AREA, m_display);

    m_default_scr = lv_disp_get_scr_act(NULL);

    //--- lblAltitude
    lblAltitude = lv_label_create(m_default_scr);
    assert(lblAltitude);
    lv_label_set_text_static(lblAltitude, m_text);
    lv_obj_set_width(lblAltitude, 100);
    lv_obj_set_x(lblAltitude, 0);
    lv_obj_set_y(lblAltitude, 00);

    lv_scr_load(m_default_scr);
}
int main(void)
{
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_DMA_Init();
    MX_TIM2_Init();
    MX_ADC1_Init();
    MX_TIM4_Init();
    MX_TIM3_Init();
    MX_TIM14_Init();
    MX_TIM15_Init();
    MX_USART5_UART_Init();
    MX_TIM1_Init();
 
   // Disable IWDG if core is halted
    __HAL_DBGMCU_FREEZE_IWDG();

    display__init();

    while (1)
    {
        display__process();
    }
}

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
bracz
Associate

You say your code is "big". Is it by a chance bigger than 128K for a 256K part / bigger than 256K for a 512K part?

Maybe you are running into a silicon errata regarding dual-bank flash. Basically when you have dual-bank flash, you can not have code in both of the banks. Otherwise the processor core crashes in weird ways, typically hardfault.

 

bracz_0-1740942981343.png

and also in the reference manual, section 3.3.5:

bracz_1-1740943031876.png

 

The workaround is to disable prefetch ( `__HAL_FLASH_PREFETCH_BUFFER_DISABLE();`), which will probably cost you a lot of performance. For me it seems that disabling DUAL_BANK bit in the option bits resolved the one crash I was having, but idk if it is stable this way.

 

 

 

View solution in original post

7 REPLIES 7
JulienD
Senior

Hello again, 

Going a bit further:

- I was able to remove tim3 from configuration while keeping the hard fault.

 

Remaining peripherals interrupt handlers, are:

 

void DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQHandler(void)
{
    /* USER CODE BEGIN DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */

    /* USER CODE END DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 0 */
    HAL_DMA_IRQHandler(&hdma_adc1);   
    HAL_DMA_IRQHandler(&hdma_tim15_ch1);
    HAL_DMA_IRQHandler(&hdma_usart5_rx);
    HAL_DMA_IRQHandler(&hdma_tim4_ch2);
    HAL_DMA_IRQHandler(&hdma_usart5_tx);
    /* USER CODE BEGIN DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */

    /* USER CODE END DMA1_Ch4_7_DMA2_Ch1_5_DMAMUX1_OVR_IRQn 1 */
}

void USART3_4_5_6_LPUART1_IRQHandler(void)
{
    /* USER CODE BEGIN USART3_4_5_6_LPUART1_IRQn 0 */

    /* USER CODE END USART3_4_5_6_LPUART1_IRQn 0 */
    HAL_UART_IRQHandler(&huart5);
    /* USER CODE BEGIN USART3_4_5_6_LPUART1_IRQn 1 */

    /* USER CODE END USART3_4_5_6_LPUART1_IRQn 1 */
}

 

 

With this configuration the app crashes.

Now, if I comment any  of the following commented lines. Hard fault disappears. Of course, I can't comment the usart5_tx handler because it is used to transmit the frame buffer to the display.

 

//    HAL_DMA_IRQHandler(&hdma_adc1);
//    HAL_DMA_IRQHandler(&hdma_tim15_ch1);
//    HAL_DMA_IRQHandler(&hdma_usart5_rx);
//    HAL_DMA_IRQHandler(&hdma_tim4_ch2);
    HAL_DMA_IRQHandler(&hdma_usart5_tx);

 

 

What's very surprising is that these handlers are for peripherals that are configured but not in use. I double checked by inserting  conditionnal breakpoints in HAL_DMA_IRQHandler in the "if this interrupts occurs", and the app never stopped.

 

I have the feeling that I'm not looking in the right direction.

I also tried to increase greatly heap and stack sizes without any change. Btw, a breakpoint in _sbrk function showed that there's no allocation in heap.

 

 

 

 

 

Hello Andrew,

Thanks for the interesting pointers. Unfortunately cortex-M0+ offer less features than M3-M4.

Anyway! Here's what I get:

SP : 0x2002 3F88

JulienD_0-1740757368530.png

Extract from memory dump at this address:

JulienD_1-1740757475664.png

If I did everything well, the instruction that causes the error should be at the address 0x60 which, based on the documentation is part of the information block and is an alias of 0x8000060 which is somewhere in the interrupt vector. The 18th (0x60 / 4) element of this vector is RTC_TAMP_IRQHandler.

1- Is that correct? It's suspicious to me :S

2- If yes, what does that mean?

 

 

 

 

Debugging the HardFault Handler is the wrong approach, you need to unpack the fault context to see the code that's actually offending the MCU

Instrument the handler to get actionable data without having to mine it from the debugger..

 

https://github.com/cturvey/RandomNinjaChef/blob/main/KeilHardFault.c

 

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
bracz
Associate

You say your code is "big". Is it by a chance bigger than 128K for a 256K part / bigger than 256K for a 512K part?

Maybe you are running into a silicon errata regarding dual-bank flash. Basically when you have dual-bank flash, you can not have code in both of the banks. Otherwise the processor core crashes in weird ways, typically hardfault.

 

bracz_0-1740942981343.png

and also in the reference manual, section 3.3.5:

bracz_1-1740943031876.png

 

The workaround is to disable prefetch ( `__HAL_FLASH_PREFETCH_BUFFER_DISABLE();`), which will probably cost you a lot of performance. For me it seems that disabling DUAL_BANK bit in the option bits resolved the one crash I was having, but idk if it is stable this way.

 

 

 

There are 8 words pushed at the stack at ISR/fault handler entry; the last of which (which you've highlighted) is content of the xPSR register. The PC is stacked one before that. You also have the memory dump set to display MSB first (Eclipse's default, I won't comment on this here), so the stacked PC is 0x2000A40C. (In your initial post, with different version of the program, it's 0x2000160C.) This is in RAM. It's unlikely you have program deliberately in RAM, so this is probably consequence of jumping through an incorrectly set function pointer.

Try to go back in stack and look what's at FLASH addresses. Try to set breakpoints on those addresses, run the program up to there and then single-step through those points to see, where does the fault occur.

JW

JulienD
Senior

And the winner is.... bracz! 

 

Thanks a lot.

Thanks also to Andrew, Clive and Jan (by order of apparition), your help is always  valuable.

 

Julien