I'm a beginner to embedded software. I try to build my simple real time operating system kernel using C code with the ARM Cortex-M4F Based MCU Tiva C LaunchPad and run in the IAR Embedded Workbench IDE.
The system can support 3 tasks, task A blinks the red LED, task B blinks the blue LED and task C blinks the green LED. The tasks are scheduled in a round-robin way. It uses SysTick to trigger PendSV once per second for context switching.
The following code works fine to blink the LEDs as expected.
#include "include/tm4c_cmsis.h"
#include <intrinsics.h>
#define SYS_CLOCK_HZ 16000000U
#define LED_RED (1U << 1)
#define LED_BLUE (1U << 2)
#define LED_GREEN (1U << 3)
#define MAX_TASK_NUM 3
#define MAX_TASK_SIZE 0x40
int OSStack[MAX_TASK_NUM][MAX_TASK_SIZE] __attribute__ ((aligned (4)));
void task_A();
void task_B();
void task_C();
/* Task Control Block (TCB) */
typedef struct {
int *sp; /* stack pointer */
int status; // 0: does not exists, 1: created, 2: running
} OSTask;
OSTask OSTask_List[MAX_TASK_NUM];
int OS_curr; /* index of the current task */
int OS_next;
int OS_tn; // total task number
int *sp_curr;
int *sp_next;
void OSInit(){
// configure GPIOF for LED blinking
SYSCTL->RCGC2 |= (1U << 5);
GPIOF->DIR |= (1<<3)|(1<<2)|(1<<1);
GPIOF->DEN |= (1<<3)|(1<<2)|(1<<1);
SysTick->LOAD = SYS_CLOCK_HZ - 1;
SysTick->VAL = 0;
SysTick->CTRL = (1U << 2) | (1U << 1) | 1;
OS_tn = 0;
}
void OSCreateTask(void* taskH){
int n=OS_tn;
OS_tn++;
int* p = (int *) OSStack;
OSTask_List[n].sp = p + ((n+1) * MAX_TASK_SIZE);
// init the stack for each task
*(--OSTask_List[n].sp) = (1U << 24); /* xPSR */
*(--OSTask_List[n].sp) = (uint32_t)taskH; /* PC */
*(--OSTask_List[n].sp) = 0x0000000EU + n*16; /* LR */
*(--OSTask_List[n].sp) = 0x0000000CU + n*16; /* R12 */
*(--OSTask_List[n].sp) = 0x00000003U + n*16; /* R3 */
*(--OSTask_List[n].sp) = 0x00000002U + n*16; /* R2 */
*(--OSTask_List[n].sp) = 0x00000001U + n*16; /* R1 */
*(--OSTask_List[n].sp) = 0x00000000U + n*16; /* R0 */
*(--OSTask_List[n].sp) = 0x0000000BU + n*16; /* R11 */
*(--OSTask_List[n].sp) = 0x0000000AU + n*16; /* R10 */
*(--OSTask_List[n].sp) = 0x00000009U + n*16; /* R9 */
*(--OSTask_List[n].sp) = 0x00000008U + n*16; /* R8 */
*(--OSTask_List[n].sp) = 0x00000007U + n*16; /* R7 */
*(--OSTask_List[n].sp) = 0x00000006U + n*16; /* R6 */
*(--OSTask_List[n].sp) = 0x00000005U + n*16; /* R5 */
*(--OSTask_List[n].sp) = 0x00000004U + n*16; /* R4 */
}
// cannot create tasks out of order
void OSSchd(){
if (OS_curr == OS_tn-1)
OS_next = 0;
else
OS_next = OS_curr + 1;
sp_curr = OSTask_List[OS_curr].sp;
sp_next = OSTask_List[OS_next].sp;
*(volatile uint32_t *)0xE000ED04 = (1U << 28);
}
void PendSV_Handler(void) {
asm("PUSH {r4-r11}");
asm("LDR r3, =sp_curr");
asm("STR sp, [r3,#0x00]");
asm("LDR r3, =sp_next");
asm("LDR sp, [r3,#0x00]");
OS_curr = OS_next;
asm("POP {r4-r11}");
}
void SysTick_Handler(void) {
GPIOF->DATA = 0;
OSSchd();
}
void lightRed(void){
GPIOF->DATA_Bits[LED_RED] ^= LED_RED;
}
int main() {
OSInit();
OSCreateTask((void *)task_A);
OSCreateTask((void *)task_B);
OSCreateTask((void *)task_C);
__enable_interrupt();
while (1) {
}
}
void task_A() {
while (1) {
//lightRed();
GPIOF->DATA_Bits[LED_RED] ^= LED_RED;
}
}
void task_B() {
while (1) {
GPIOF->DATA_Bits[LED_BLUE] ^= LED_BLUE;
}
}
void task_C() {
while (1) {
GPIOF->DATA_Bits[LED_GREEN] ^= LED_GREEN;
}
}
However, when I try to change the code inside task_A by calling the function lightRed() as follow:
void lightRed(void){
GPIOF->DATA_Bits[LED_RED] ^= LED_RED;
}
...
void task_A() {
while (1) {
lightRed();
}
}
The three LEDs only blink for 2 cycles and no further response. I stop executing the code and the debugger shows the following problems:
: HardFault exception.
: The processor has escalated a configurable-priority exception to HardFault.
: An integrity check error has occurred on EXC_RETURN (CFSR.INVPC).
: Exception occured at PC = 0x7, LR = 0x1000000
: See the call stack for more information.
: The stack pointer for stack 'CSTACK' (currently 0x200000E0) is outside the stack range (0x20000330 to 0x20000B30)
Also, the call stack is as follow:
-> [__iar_zero_init3 + 0x39]
<Exception frame>
[__vector_table + 0x7]
How can I solve this problem?
This is, fittingly enough, definitely a case of stack overflow. The debug message you're getting is somewhat bogus; the valid stack range given (0x20000330 to 0x20000B30) is probably the configured range for the main stack, which you're not using (all of your tasks are using stacks from your OSStack array).
You've allocated 64 bytes (0x40) for each task's stack. The stack frame you've defined for a task that's suspended is already 64 bytes in size, and that's before it's done anything. If a task is running, and has used any stack space for anything whatsoever, then when it is suspended it will use an additional 64 bytes on top of whatever it is using at the time. This pretty much guarantees you a stack overflow on any nontrivial task, and in this case as soon as you introduce the function call into task_A() you'll be forcing it to use the stack.
The immediate fix is simple: just increase the value of the MAX_TASK_SIZE constant.
Incidentally, the Cortex-M4 has dual stack capability, so you can configure thread-mode code to use a different stack from handler-mode code. This can simplify analysis of stack usage and reduce the required size of task stacks, because interrupt service routines will not use the task stacks for their local storage. Your context switch can remain almost the same, but must read and write PSP to obtain and modify the task stack pointers rather than just using sp.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With