cancel
Showing results for 
Search instead for 
Did you mean: 

HardFault in STM32F103 because of array class member in FreeRTOS C++ wrapper

IProg.1759
Associate II

Three weeks I am trying to cope with a mysterious problem.

I will begin from general description and then dive into details. The MCU is STM32F103RFT6, HAL and C++ Wrapper for FreeRTOS 10.2 are in use.

So, I have only one task running (not counting Idle Task). Continious infinite stream of bytes arrives at UART. 72 bytes every 10ms. Inside the task I am periodically arming DMA to receive these 72 bytes and suspending the task twice using ulTaskNotifyTake(). There're RxHalfComplete and RxFullComplete callback functions which are called by DMA IRQ handler when it half or fully finishes reception. Inside these callback functions there's nothing but vTaskNotifyGiveFromISR with portYIELD_FROM_ISR calls.

When arming DMA a buffer must be provided. The buffer can be declared as stack variable inside task's body or as private member variable of a class. What perplexing me is that when buffer is the stack variable - everything is OK. And if it is member of the class - Hard Fault comes within several seconds.

Now, the necessary pieces of code.

As I said FreeRTOS native calls a wrapped into C++ classes. So here is how the wrapper for task creation lools like:

class Thread
{
public:
        Thread(const char* const threadName, uint16_t stackDepth, UBaseType_t threadPriority)
        {
                BaseType_t result = xTaskCreate(
                                        TaskAdapter, 
                                        threadName, 
                                        stackDepth,
                                        this, 
                                        threadPriority,
                                        &handle);
        }
       
       TaskHandle_t getTaskHandle(void)
       {
           return handle;
       }
protected:
        virtual void run(void) = 0;
private:
        TaskHandle_t handle;
 
        static void TaskAdapter(void *pvParameters)
        {
            Thread *task = static_cast<Thread *>(pvParameters);         
            task->run();
            #if (INCLUDE_vTaskDelete == 1)
                vTaskDelete(task->handle);
            #endif
        }
 }

It is inherited by another class which implements desired behaviour:

class DataGrabberReceiver : public Thread
{
    private:
        uint8_t classMemberByteBuffer[72];
 
    public:
        DataGrabberReceiver(const char* const threadName, uint16_t stackDepth, UBaseType_t threadPriority) : Thread(threadName, stackDepth, threadPriority) { //no-op here }; 
 
        void run(void) override
        {
                uint8_t stackAllocatedByteBuffer[72];
                QueueHandle_t queue = xQueueCreate(72, sizeof(uint8_t));
 
                while(true)
                {
                        //with this if-statement everything works
                        if(HAL_OK == HAL_UART_Receive_DMA(&huart_DataGrabber, stackAllocatedByteBuffer, 72))
                        //and with this one HardFault comes in seconds after start
                        //if(HAL_OK == HAL_UART_Receive_DMA(&huart_DataGrabber, classMemberByteBuffer, 72))
                         //only one above mentioned if-statement can be uncommented
                         {
                                 //waiting for the first half being received into buffer
                                 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);  
                                 //and copying it inside the queue
                                 for(int i = 0; i < 36; ++i)
                                 {
                                        xQueueSendToBack(queue, &stackAllocatedByteBuffer[i], 1);
                                        //xQueueSendToBack(queue, &classMemberByteBuffer[i], 1);
                                }
                                //waiting for the second hald being received into buffer
                                 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
                                 //and copying further
                                 for(int i = 36; i < 72; ++i)
                                 {
                                        xQueueSendToBack(queue, &stackAllocatedByteBuffer[i], 1);
                                        //xQueueSendToBack(queue, &classMemberByteBuffer[i], 1);
                                }
                         }
                }
        }
};

The overriden RxCallbacks:

void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart_DataGrabber) 
    {   
        BaseType_t px = pdFALSE;
        vTaskNotifyGiveFromISR(dataGrabber_handle, &px);
        portYIELD_FROM_ISR(px);
    }
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == huart_DataGrabber) 
    {   
        BaseType_t px = pdFALSE;
        vTaskNotifyGiveFromISR(dataGrabber_handle, &px);
        portYIELD_FROM_ISR(px);
    }
}

The code in main.cpp:

#include "stm32f1xx_hal.h"
#include "DataGrabberReceiver.h"
 
UART_HandleTypeDef huart_DataGrabber;
TaskHandle_t dataGrabber_handle;
 
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_USART2_UART_Init();
 
    auto receiverThread = DataGrabberReceiver("DataGrabberReceiver", 128, 3);
    dataGrabber_handle = receiverThread.getTaskHandle();
 
    vTaskStartScheduler();
 
    /* Here we should never get */
    while(1) {  }
}

everything is quite obvious there and even more: it is generated by ST CumeMX. So task is created, scheduler started and... everything works fine if inside DataGrabberReceiver::run's while cycle I work with stackAllocatedByteBuffer[]. And I am facing HardFault if I work with classMemberByteBuffer[].

Let me answer some questions:

0. Task's stack was set at different sizes. 128 words, 1024 words.. all the same.

1. Yes, NVIC interrupt priorities of DMA and UART are logically lower than configMAX_SYSCALL_INTERRUPT_PRIORITY.

2. If one makes classMemberByteBuffer static or global - everything works fine.

3. Heap size in FreeRTOS config file set at 30KB.

4. I am using heap4.c and have tried to move it to another location, defining manualy ucHeap array and asking linker to put it at certain RAM position - it did not helped.

5. If one rejects wrapper everything seems working. But I need runtime polymorphism for interfaces and incapsulation. C function pointers are not the case.

6. When in HardFault stack trace (I am using Segger J-Link via SWD) is like this

Thread #1 57005 (Suspended : Signal : SIGTRAP:Trace/breakpoint trap)    
    HardFault_Handler() at stm32f1xx_it.c:78 0x8003a20  
    <signal handler called>() at 0xfffffff1 
    uxListRemove() at list.c:218 0x8004748  
    xTaskIncrementTick() at tasks.c:2,571 0x800565e 
    xPortSysTickHandler() at port.c:445 0x8004534   
    osSystickHandler() at cmsis_os.c:1,415 0x8004624    
    SysTick_Handler() at stm32f1xx_it.c:166 0x8003a4a   
    <signal handler called>() at 0xfffffffd 
    prvPortStartFirstTask() at port.c:270 0x8004360 
    xPortStartScheduler() at port.c:350 0x8004402

The cause is floating and makes one think that something wrong with kernel which I believe is impossible.

7. Class member variables are stored somewhere in RAM heap while task's stack variables are stored in FreeRTOS's heap. They are separated in RAM space. But I don't think that someone's (not FreeRTOS's) stack overlaps the RAM heap and currupting memory.

Who can explain or have faced with such "magic"? I am almost sure that memory is currupted somehow, but there is no reasons for race conditions because the byteBuffer is guarded. But HOW?? Help me please!

10 REPLIES 10

Did you try to monitor how a thread consumes resources? Maybe a stack overflow within one task happens. Try to debug in multithreading mode.

https://mcuoneclipse.com/2016/04/09/freertos-thread-debugging-with-eclipse-and-openocd/

https://dzone.com/articles/show-freertos-threads-in-eclipse-debug-view-with-s

A Task List feature from NXP helps a lot monitoring resources.

vApplicationStackOverflowHook must be redeclared in the separate .c file. I've just filled it's body with __ASM volatile ("BKPT");