Stack restoration after context switch or yield from task's subroutine

This question relates to a port on a Microchip ATSAML21E18, Cortex-M0.

Within a task, I have a subroutine where I have declared several local variables. From within this same subroutine, I initialize these local variables when the subroutine is called. Later within the same subroutine call, I call a vTaskDelay(). When the context returns to the subroutine after the delay, the local variables are not correct.

If I make the variables global, everything works fine. Shouldn’t the local variable data be saved prior to the delay and then restored when context returns to this task?

Any clarity would be appreciated.

There is no need to add custom context saving. This has to and is done by FreeRTOS.
The symptom points to a stack overflow given there is no application code bug somewhere else.
Did you define configASSERT and enabled stack checking ?
You could also show the task code and tell how much stack you’ve specified for the task.

Hartmut,

Thank you for the fast reply. I conducted some additional testing today. I had defined the task with a stack of 128 words originally. configCHECK_FOR_STACK_OVERFLOW was defined as 2 and I did NOT have configASSERT defined. I defined configASSERT and also boosted my stack size to 512 words. I recompiled and re-ran the code and had the same results. There was no stack overflow detected and no assertion errors. The strange phenomena was that the local variables would always change to the same values after the vTaskDelay.

I am using the latest version of the IAR Cortex-M development system. I opened the view window for monitoring local variables and set breakpoints before and after the vTaskDelay. I noticed that when breaking prior to the vTaskDelay, some of the local variables were stored in RAM and some were stored in registers. No surprise here. But breaking after the vTaskDelay, the local variable storage locations had changed to different memory/register locations.

Grasping at straws, I checked my optimization settings and noted that optimization was set to None. I changed the optimization level to Low, recompiled and downloaded the code. When I ran the code again, everything worked as expected and the local variable values and storage locations were the same both before and after the call to vTaskDelay. A very strange observation. I’m interested in your thoughts. Thanks.

This is indeed very strange… and sounds a bit fishy.
I’d verify that the compiler/FreeRTOS combination is basically working also with no optimization (as it has to) with a single task for instance using your mentioned subroutine or something similar without additional active ISRs etc.
So this basic test application has just 2 tasks: the idle task and your test task.
The scheduler with running SysTick, context switching including stack saving/restoration must work at any reasonable optimization level and of course without optimization.
I wonder why the compiler put local variables into registers with no optimization. That’s usually not the case. Seems that ‘None’ is something like ‘-Og’ and not ‘-O0’ with GCC.
But I’m not familiar with the IAR toolchain.

I agree with you regarding to optimization solution. It may be a red herring. To provide more information regarding interrupt applications, this subroutine is actually loading an I2C port that is communicating with an I2C EEProm. The vTaskDelay is used after the write operation instead of polling the chip for the write complete operation.

The I2C driver implementation from Microchip uses an interrupt to handle the I2C transaction and effectively blocks using a semaphore until the complete transaction is done. This transaction is completed before the vDelayTask is called. The local variables were initialized before the I2C transaction and were still correct after the transaction completed. The fault appeared to only occur after the vTaskDelay call.

I am using the default system tick (1 ms) and there are no other interrupts in use, other than the I2C and systick. My plan is to create a separate project, without the I2C functionality, with one task. From within that task, I will call a function that includes some local initialized variables and then call vTaskDelay and see how that works with No and low optimization.

Thanks for your feedback. I will update the post when I complete this other test.

1 Like

An additional thing that you can do is to put a data breakpoint at a memory location that you know is going to get corrupted after the call to vTaskDelay. That way you’ll be able to catch this corruption right when it happens.

I agree with @hs2 that correct register save/restore is so fundamental to context switching that you would encounter many more problems left and right if anything was wrong in your port.

One thing I remember having seen, though, is ISRs declared “naked”, ie with no ABI compliance that do not correctly save and restore all MCU registers. Can we see the declaration and ideally corresponding assembly code of your ISR?