FreeRTOS does not execute first task after 'svc 0' call

Hello,
I am new to freeRTOS and tried running it on an STM32F405 chip. For that, I used the port files
from /portable/GCC/ARM_CM4F and for the config file I used the file from the github-repo
(not allowed to post links) AndColla/stm32f4discovery-freertos-makefile because it also uses libopencm3.

My code can be found on the github-repo: (not allowed to post links) Hackertreff-Reutte/RT3s_Firmware branch: freertos

main.c

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include "FreeRTOS.h"
#include "task.h"

/*
 * Handler in case our application overflows the stack
 */
void vApplicationStackOverflowHook(
	TaskHandle_t xTask __attribute__((unused)),
    char *pcTaskName __attribute__((unused))) {

	for (;;);
}


static void task1(void *args __attribute__((unused))) {
    for (;;) {
		gpio_toggle(GPIOE, GPIO0);
		vTaskDelay(pdMS_TO_TICKS(1000));
	}
}

static void task2(void *args __attribute__((unused))) {
    for (;;) {
		gpio_set(GPIOE, GPIO1);
		vTaskDelay(pdMS_TO_TICKS(1000));
	}
}

int main(){

    rcc_clock_setup_pll(&rcc_hse_8mhz_3v3[RCC_CLOCK_3V3_168MHZ]);

    //LED
    rcc_periph_clock_enable(RCC_GPIOE);
    gpio_mode_setup(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO0);
    gpio_mode_setup(GPIOE, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO1);

    xTaskCreate(task1, "LED1", 100, NULL, 2, NULL);
    xTaskCreate(task2, "LED2", 100, NULL, 2, NULL);

    /* Start the scheduler. */
	vTaskStartScheduler();
    
    for(;;) {}

    return 0;
}

What does not work: Task execution

What I have found out so far:

If xTaskCreate() is called it returns pdPASS
If vTaskStartScheduler() is called it runs until it executes the arm instruction svc 0 in the function prvPortStartFirstTask() [located in port.c] after that it never returns and that should be the intended behaviour as far as I understood it.

What is different to a normal STM32:

  • There is a bootloader on the chip, hence it has a different memory layout
  • The stack pointer is set to -4 from the usual location due to a bug in the bootloader (off by one error while sanity checking)

Memory Layout

{
 sram (rwx) : ORIGIN = 0x20000200, LENGTH = 128k - 0x200  /* 0x200 some space for the bootloader? */
 flash (rx) : ORIGIN = 0x0800C000, LENGTH = 1M - 48K
 ccm (rwx) : ORIGIN = 0x10000000, LENGTH = 64K
}

Stack Pointer

PROVIDE(_stack = ORIGIN(sram) + LENGTH(sram) - 4);

Sadly I can’t debug the chip directly, because it is built into a radio and therefore I don’t have easy access.

Ideas on the next steps of debugging would be appreciated. Or maybe I did something wrong?

did you possibly not enable interrupts at rtos startup?

I think they are enabled in the port.c file

function prvPortStartFirstTask()

__asm volatile (
        " ldr r0, =0xE000ED08 	\n"/* Use the NVIC offset register to locate the stack. */
        " ldr r0, [r0] 			\n"
        " ldr r0, [r0] 			\n"
        " msr msp, r0			\n"/* Set the msp back to the start of the stack. */
        " mov r0, #0			\n"/* Clear the bit that indicates the FPU is in use, see comment above. */
        " msr control, r0		\n"
        " cpsie i				\n"/* Globally enable interrupts. */
        " cpsie f				\n"
        " dsb					\n"
        " isb					\n"
        " svc 0					\n"/* System call to start first task. */
        " nop					\n"
        " .ltorg				\n"
        );

I also looked at some other stm32 libopencm3 examples and they all pretty much have the same main.c file

ok, next step would be to ensure that your gpio setup works - can you control your LEDs successfully before OS start? Meaning your gpio assignments and peripheral clock setup is ok? Maybe everything does work except you do not see anything… :smirk:

1 Like

Good idea, I tested this already (is my debug method). I can control the LEDs until svc 0 is called.

In short:

  • LED works before xTaskCreate() and after it
  • LED works before vTaskStartScheduler()
  • LED does not work after vTaskStartSchedular() due to it blocking.

I’m not familiar with libopencm3 but maybe there is an issue with the FreeRTOS awareness means the 3 exception handlers mandatory for FreeRTOS.
Have a look here if it brings you a step further:

How do you know that you do not end up in a fault? You could toogle the LED in the fault handler to test that. If the code ends up in an exception (as it does most likely), check the usual suspects (irq priority ordering, task stack issues, wrong critical section handling).

I see that you are mapping FreeRTOS handlers to your handlers - RT3s_Firmware/FreeRTOSConfig.h at freertos · Hackertreff-Reutte/RT3s_Firmware · GitHub

Can you verify that this SVC handler is getting called?

I am not sure on how I can verify that it is called.
If I try to turn on a LED in this function it won’t turn on.

I looked in the elf file and found the following entries in the vector_table

...
0800c00c c9 ce 00 08     vector_t  blocking_handler+1      hard_fault
0800c010 c9 ce 00 08     vector_t  blocking_handler+1      memory_manag
...
0800c02c e1 c2 00 08     vector_t  sv_call_handler+1       sv_call
...
 0800c038 41 c3 00 08     vector_t  pend_sv_handler+1       pend_sv
 0800c03c a5 c3 00 08     vector_t  sys_tick_handler+1      systick

If I follow the sv_call I reach the same code as implemented in the vPortSVCHandler() function

That’s what I guessed. And following

could hopefully solve the problem. @jefftenney is an expert :+1:
Are there any hints from the libopencm3 community regarding integration or conflicts with FreeRTOS ?

As far as I have understood the post I need to write the defines correctly so that the FreeRTOS functions are used in the ISR.

I have done this already in my config file

#define vPortSVCHandler sv_call_handler
#define xPortPendSVHandler pend_sv_handler
#define xPortSysTickHandler sys_tick_handler

I also tried setting
configCHECK_FOR_STACK_OVERFLOW to 0, but that did not change anything

I also found another post on the platform io forum

https://community.platformio.org/t/translating-make-file-for-freertos-libopencm3-to-platformio-ini/11177/10

(can’t post links)

but it looks like I don’t have those issues, due to the correct defines.

But I am not sure.

I’d recommend to keep configCHECK_FOR_STACK_OVERFLOW set to 2.
Did you just larger (x 2) task stacks ?
I guess configMINIMAL_STACK_SIZE is still set to the default 128.

Did you try the suggestion from @RAc ? Define configASSET and turn-on/blink an LED if an assert fires. Also, do the same in the hard fault handler.

You mentioned the chip is built into the radio. Is it a new product? Or a product that used to work that you are trying to update? Do the interrupt vectors get remapped from the bootloader to the application?

I did it with for configASSERT.

#define configASSERT(x)     if((x) == 0) { assert_blink(); }

and it does not blink
and for sanity checking, I changed it to

#define configASSERT(x)     if((x) != 0) { assert_blink(); }

and it blinks.

I am not sure how to do this for the hard_fault_handler

@rtel It is the RT3s handled radio and I am trying to create a custom firmware for it. I am not really sure what the bootloader does, because I don’t have any info on it. Most of what I know is from the OpenRTX project and some experiments.
Is there a way to check whether the table gets remapped?

You can read the vector base register - but anything like that will be hard without a debugger or log output.

Try inserting the code

gpio_set(GPIOE, GPIO1);

into your fault handler. Careful: Depending on how complex the logic behind gpio_set is, this may yield wrong results. Ideally, find out which assembly statement the call eventually resolves to and add it as inline assembly into your fault handler.

But since there is reason to believe that the pending irq is never called, we may be barking up a wrong tree anyway.

You may want to add a few more LED toogle commands in tasks.c and port.c to narrow down the point from when on something goes haywire.

1 Like

I implemented the hard fault handler as suggested, but the LED did not light up.
I also checked the binary file with ghidra and the vector table linked to the right handler code.
I didn’t use the gpio_set() call and instead used:

*(uint *)(GPIOE + 0x18) = (uint)GPIO0;

(this code was tested and works)

1 Like

No debugger? And a mysterious bootloader? And one hand tied behind your back? :slight_smile:

The SCB->VTOR register is the vector base register that @rtel was talking about. In your situation, you should check the value of that register as a way to divide the problem space. If that register is set to 0x0800C000 then you’re in good shape on the vector front. However, if that register is set to anything else, you have a more difficult challenge ahead.

It is possible that the bootloader sets the VTOR register to 0x0800C000 prior to jumping to the application. This would be great.

It is also possible that the bootloader handles all interrupts, and it “calls” the application’s interrupt handlers as found in the table at 0x0800C000. This would not be good and may even make your task impossible. You might get away with setting the VTOR register to 0x0800C000 in this case, but you might break something if the bootloader has functionality you require even while your application is running.

The address of SCB->VTOR is 0xE000ED08. Can you check this register for the value 0x0800C000 and then use the LED to get results? Check the register just prior to calling vTaskStartScheduler().

1 Like

I read out the address 0xE000ED08 via USB-serial that I have already implemented. It turns out that the bootloader sets the vector table address to 0x08000000. That also explains why the handlers don’t work.

Out of curiosity, I have overwritten the value at 0xE000ED08 with 0x800C000 and now the tasks are executed.

*(uint32_t*)0xE000ED08 = 0x0800C000;

Looks a bit hacky but if it works it works.

Thanks to all that help me in my endeavour.
Also thanks for the detailed instructions on how to access the VTOR value.

As for the bootloader, as far as I know, it does not provide anything that will be used by my new firmware.
The only question remains if this approach will backfire some time later on.

1 Like