Home | Projects | Notes > ARM Cortex-M3/M4 Processor > Implementing a Scheduler
Implement a scheduler which schedules multiple user tasks (we will use 4 tasks) in a Round-Robin fashion by carrying out the context switch operation. (Round-Robin scheduling algorithm - Time slices are assigned to each task in equal portions and in circular order.)
Use SysTick handler to carry out the context switch operation between multiple tasks (Later update the code using PendSV for context switching)
What is a task?
A task is nothing but a piece of code, or you can call it a "C function", which does a specific job when it is allowed to run on the CPU.
A task has its own stack to create its local variables when it runs on the CPU. Also, when the scheduler decides to remove a task from CPU, scheduler first saves the context (state) of the task in task's private stack.
In summary, a piece of code or a function is called a task when it is schedulable and never loses its 'state' unless it is deleted permanently.
Create 4 user tasks (i.e., 4 never returning C functions)
Stack point selection
MSP - Handler mode (Scheduler; SysTick_Handler()
/ PendSV_Handler()
)
PSP - Thread mode (User tasks)
Our program will use both MSP and PSP for stack utilization.
Stack assessment
STM32F407 microcontroller board has 128 KB (SRAM1 + SRAM2) of RAM.
We will use 5 KB of stack memory, each 1 KB of which will be assigned to each task and a scheduler. (4 tasks + 1 scheduler).
How much stack to assign is completely dependent on your application design, the number of APIs to be called from the stack and so on.
xxxxxxxxxx
161RAM_END +-------------------+ T1_STACK_START
2 | Private stack T1 |
3 +-------------------+ T1_STACK_END = T2_STACK_START
4 | Private stack T2 |
5 +-------------------+ T2_STACK_END
6 | Private stack T3 |
7 +-------------------+
8 | Private stack T4 |
9 +-------------------+
10 | Scheduler |
11 +-------------------+
12 | |
13 | ... |
14 | |
15RAM_START +-------------------+
16
Reserve stack areas for all the tasks and scheduler
Scheduling policy selection
Round-Robin preemptive scheduling
No task priority
Use SysTick timer to generate exception for every 1 ms to run the scheduler code
Scheduling
An algorithm which makes the decision of preempting a running task from the CPU and makes the decision about which task to dispatch (i.e., allocate CPU) next
The decision could be based on many factors such as system load, the priority of tasks, share resource access, or a simple Round-Robin method.
Context switching
The procedure of switching out the currently running task from the CPU after saving the task's execution context or state and switching in the next task's to run on the CPU by retrieving the past execution context or state of the task.
State of a task
Inside a microcontroller (e.g., STM32x) is a processor (e.g., ARM Cortex-M4) which contains NVIC, MPU, SCP, FPU, Debug Unit, etc.
Inside a processor is a processor core which contains ALU, Core registers (General purpose, special purpose, special registers, etc.), etc.
When a task is running, there will be various resources associated with it that contains the information about the task. In general, the state of a process = General purpose registers + Some special purpose registers + Status register. These are what need to be stored and retrieved during the context switching.
General purpose registers
PC - Will be holding the next instruction the preempted task needs to execute when it is dispatched again.
LR - Return address
PSP - Stack pointer for the user tasks (contains information about how each task is using the stack)
PSR - Snapshot of the status flags (N, V, Z, C, etc.)
Other special registers such as PRIMASK, FAULTMASK, BASEPRI, CONTROL registers are privileged registers and they will NOT be stored! (Most of the time user tasks will be running with unprivileged access level so storing these registers wouldn't make any sense. Kernel should touch these registers not the user tasks.)
Case study of context switching: T1 switching out, T2 switching in
Running T1
Scheduler (Context Switching)
Context saving
push
the context of T1 onto T1's private stackSave the PSP value of T1 using a global variable
Context retrieving
Retrieve the PSP value of T2 from the corresponding global variable
pop
the context of T2 from T2's private stackRun T2
As an exception entry sequence, the following registers will get pushed onto stack automatically by the processor:
R0, R1, R2, R3, R12, LR, PC, xPSR (we'll call it Stack Frame 1 or SF1)
So, our job in implementing scheduler is to make sure that the rest of the task state will get pushed onto stack as well.
R4 - R11 (we'll call it Stack Frame 2 or SF2)
Task's stack area initialization and storing of dummy stack frame
Each task can consume a maximum of 1 KB of memory as a private stack.
This stack is used to hold tasks' local variables and context (SF1 + SF2)
When a task is getting scheduled for the very first time, it doesn't have any context. So, it is the programmer's responsibility to store dummy SF1 and SF2 in the task's stack area as a part of "Task initialization" sequence before launching the scheduler.
Set all the general purpose registers to 0 (R0 - R12)
PSR - 0x01000000
Only the T bit is important (bit[24]) and it must be set (1) for ARM Cortex-M4
PC - Address of the
task2_handler()
make sure that lsb of the address is 1; for T bit.LR - A special value EXC_RETURN which controls the exception exit.
0xFFFFFFFD is appropriate for our design. (Return to Thread mode, exception return uses non-floating-point state from the PSP and execution uses PSP after return.)
Configure the SysTick timer to produce exception every 1 ms.
Processor Clock = 16 MHz
SysTick timer count clock = 16 MHz
1 ms is 1 KHz in frequency domain (in 1 sec, a thousand context switches will happen)
So, to bring down SysTick timer count clock from 16 MHz to 1 KHz use a divisor (i.e., reload value)
1 KH is the TICK_HZ (desired exception frequency)
You'll need to use "SysTick Reload Value Register (SYST_RVR
)" and SYST_CVR
. The initial reload value stored in SYST_RVR
gets copied into SYST_CVR
and this is the count start value. When the down count of SYST_RVR
reaches 0, the next count value is retrieved from SYS_CVR
so this count start value must not be modified!
Also, since the exception gets triggered when the reload value is reset to the start value after reaching 0, you must use N-1 for the actual reload value where N is the number of clock cycle required for every exception. So, if we want the exception to occur every 16000 clock cycle, the reload value should be 15999.
Initialize scheduler stack pointer (MSP)
To secure MSP we need, naked function will be used for initialization.
Initialize tasks' stack memory
Store dummy SF1 and SF2
(After the first attempt to control 4 LEDs with x4 elongation) Introducing blocking state for tasks.
When a task has got nothing to do, it should simply call a delay function which should put the task into the BLOCKED state from READY state until the specified delay is elapsed.
We should now maintain 2 states for a task; READY an BLOCKED.
The scheduler should schedule ONLY those tasks which are in READY state.
The scheduler also should unblock the blocked tasks if their blocking period is over and put them back to READY state.
Update the code by introducing the Task Control Block (TCB). TCB will replace a number of global variables you declared before.
xxxxxxxxxx
71typedef struct
2{
3 uint32_t psp_value;
4 uint32_t block_count;
5 uint8_t current_state;
6 (void)(*task_handler)(void);
7} TCB_t;
Block a task for a given number of ticks
Introduce a function called task_delay()
which puts the calling task to the BLOCKED state for a given number of ticks. task_delay()
is not a software-based (i.e., using for
loop) delay.
e.g., If a task calls task_delay(1000)
then the function puts the task into BLOCKED state and allows the next task to run on the CPU.
Here, the number 1000 denotes a block period in terms of ticks, the task who calls this function is going to block for 1000 ticks (SysTick exceptions), i.e., for 1000 ms since each tick happens for every 1 ms.
The scheduler should check elapsed block period of each blocked task and put them back to READY state if the block period is over.
The task_delay()
function will be implemented by using a global tick count.
Idle task
What if all the tasks are blocked? Who is going to run on the CPU?
Use the idle task to run on the CPU if all the tasks are blocked. The idle task is like user tasks but only runs when all user tasks are blocked, and you can put the CPU to sleep.
Global tick count
How does the scheduler decide when to put the BLOCKED state tasks (blocked using task_delay()
function) back to the READY state?
In a real-time operating system, or a complete embedded OS, a task can block for a number of reasons. e.g., To wait for an event, by calling the delay()
function, to block over a semaphore, etc.
In our example, though, a task can block only when it calls the task_delay()
function.
It has to compare the task's delay tick count with a global tick count.
So, scheduler should maintain a global tick count and update it for every SysTick exception.
Revisit deciding which task to run next
With the two states of tasks introduced, now it depends on the "state" of the next task.
If next task is in BLOCKED state, skip it and check the one comes next until there is a task in READY state that is not the Idle task.
If all other tasks are found to be in the BLOCKED state, then select the Idle task.
State of the Idle task will always be READY, and it cannot be changed.
Now, time to introduce PendSV handler
PendSV is a better choice for handling context switches than SysTick.
Implement SysTick_Handler()
as a regular C function, PendSV_Handler()
as a naked function.
SysTick_Handler()
Update global tick count
PendSV_Handler()
Carry out context switch
To change PendSV exception state to pending:
Interrupt Control and State Register (ICSR)
led.h
xxxxxxxxxx
291/*
2 * led.h
3 *
4 * Created on: Feb 10, 2023
5 * Author: klee
6 */
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void led_init_all(void);
25void led_on(uint8_t led_no);
26void led_off(uint8_t led_no);
27void delay(uint32_t count);
28
29/* LED_H_ */
led.c
xxxxxxxxxx
581/*
2 * led.c
3 *
4 * Created on: Feb 10, 2023
5 * Author: klee
6 */
7
8
9
10
11// Software based delay (this will introduce some problem in our example).
12// Timer-based delay is what can solve the issue.
13void delay(uint32_t count)
14{
15 for(uint32_t i = 0 ; i < count ; i++);
16}
17
18void led_init_all(void)
19{
20
21 uint32_t *pRccAhb1enr = (uint32_t*)0x40023830;
22 uint32_t *pGpiodModeReg = (uint32_t*)0x40020C00;
23
24
25 *pRccAhb1enr |= ( 1 << 3);
26 //configure LED_GREEN
27 *pGpiodModeReg |= ( 1 << (2 * LED_GREEN));
28 *pGpiodModeReg |= ( 1 << (2 * LED_ORANGE));
29 *pGpiodModeReg |= ( 1 << (2 * LED_RED));
30 *pGpiodModeReg |= ( 1 << (2 * LED_BLUE));
31
32
33 //configure the outputtype
34 *pGpioOpTypeReg |= ( 1 << (2 * LED_GREEN));
35 *pGpioOpTypeReg |= ( 1 << (2 * LED_ORANGE));
36 *pGpioOpTypeReg |= ( 1 << (2 * LED_RED));
37 *pGpioOpTypeReg |= ( 1 << (2 * LED_BLUE));
38
39
40 led_off(LED_GREEN);
41 led_off(LED_ORANGE);
42 led_off(LED_RED);
43 led_off(LED_BLUE);
44}
45
46void led_on(uint8_t led_no)
47{
48 uint32_t *pGpiodDataReg = (uint32_t*)0x40020C14;
49 *pGpiodDataReg |= ( 1 << led_no);
50
51}
52
53void led_off(uint8_t led_no)
54{
55 uint32_t *pGpiodDataReg = (uint32_t*)0x40020C14;
56 *pGpiodDataReg &= ~( 1 << led_no);
57
58}
main.h
xxxxxxxxxx
411/*
2 * main.h
3 *
4 * Created on: Feb 10, 2023
5 * Author: klee
6 */
7
8
9
10
11
12// guarantees T-bit set
13
14/* stack memory calculation */
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// task states
34
35
36
37
38 // good technique when you want to #define multiple statements
39
40
41/* MAIN_H_ */
main.c
xxxxxxxxxx
4161/**
2 ******************************************************************************
3 * @file : main.c
4 * @author : Auto-generated by STM32CubeIDE
5 * @brief : Main program body
6 ******************************************************************************
7 * @attention
8 *
9 * Copyright (c) 2023 STMicroelectronics.
10 * All rights reserved.
11 *
12 * This software is licensed under terms that can be found in the LICENSE file
13 * in the root directory of this software component.
14 * If no LICENSE file comes with this software, it is provided AS-IS.
15 *
16 ******************************************************************************
17 */
18
19
20
21
22
23
24
25
26
27
28void task1_handler(void); // user task 1 of the application
29void task2_handler(void); // user task 2
30void task3_handler(void); // user task 3
31void task4_handler(void); // user task 4
32void init_systick_timer(uint32_t tick_hz);
33__attribute__((naked)) void init_scheduler_stack(uint32_t sched_top_of_stack);
34void init_tasks_stack(void);
35__attribute__((naked)) void switch_sp_to_psp(void);
36void enable_processor_faults(void);
37
38// fault handlers
39void HardFault_Handler(void);
40void MemManage_Handler(void);
41void BusFault_Handler(void);
42
43void task_delay(uint32_t tick_count); // tick_count: for how many ticks the task should
44 // remain in BLOCKED state
45void idle_task(void);
46
47uint8_t current_task = 1; // task 0 is idle task, start with task1
48 // idle task should be launched only when all the tasks are blocked
49uint32_t g_tick_count = 0;
50
51typedef struct
52{
53 uint32_t psp_value;
54 uint32_t block_count;
55 uint8_t current_state;
56 void (*task_handler)(void);
57} TCB_t;
58
59TCB_t user_tasks[MAX_TASKS];
60
61int main(void)
62{
63 // enable processor faults so we can track invalid access to memory if any
64 enable_processor_faults();
65
66
67 // initialize scheduler stack
68 init_scheduler_stack(SCHED_STACK_START);
69
70 // initialize tasks's stack with dummy stack frame
71 init_tasks_stack(); // all tasks will be initialized inside this function
72
73 // initialize all the LEDs
74 led_init_all();
75
76
77 // generate the SysTick timer exception
78 init_systick_timer(TICK_HZ);
79
80 // Launch the first task. (Needs to run with the MSP stack pointer)
81 switch_sp_to_psp();
82 task1_handler();
83
84 /* Loop forever */
85 for(;;);
86}
87
88void idle_task(void)
89{
90 while (1);
91}
92
93void task1_handler(void)
94{
95 while (1)
96 {
97 led_on(LED_GREEN);
98 task_delay(1000); // will be blocked for the next 1000 ticks
99 led_off(LED_GREEN);
100 task_delay(1000); // will be blocked for the next 1000 ticks
101 }
102}
103
104void task2_handler(void)
105{
106 while (1)
107 {
108 led_on(LED_ORANGE);
109 task_delay(500); // will be blocked for the next 1000 ticks
110 led_off(LED_ORANGE);
111 task_delay(500); // will be blocked for the next 1000 ticks
112 }
113}
114
115void task3_handler(void)
116{
117 while (1)
118 {
119 led_on(LED_BLUE);
120 task_delay(250); // will be blocked for the next 1000 ticks
121 led_off(LED_BLUE);
122 task_delay(250); // will be blocked for the next 1000 ticks
123 }
124}
125
126void task4_handler(void)
127{
128 while (1)
129 {
130 led_on(LED_RED);
131 task_delay(125); // will be blocked for the next 1000 ticks
132 led_off(LED_RED);
133 task_delay(125); // will be blocked for the next 1000 ticks
134 }
135}
136
137void init_systick_timer(uint32_t tick_hz)
138{
139 // SysTick Reload Value Register: 0xE000E014
140 // Do not modify CVR (Current Value Register), SVR is the one to modify.
141 // Every clock cycle the CVR value will be decremented by 1,
142 // and when CVR value reaches 0, it will first reload the SVR value
143 // to itself and then the exception will be called. (Note that the exception
144 // is not called as soon as the CVR value reaches 0.)
145 // This is why you need to use a RELOAD value of N-1.
146 // -> Search SYST_RVR in the processor generic user guide.
147 uint32_t *pSRVR = (uint32_t *)0xE000E014;
148 uint32_t *pSCSR = (uint32_t *)0xE000E010;
149 uint32_t count_value = (SYSTICK_TIM_CLK / tick_hz) - 1;
150 // Tf the SysTick interrupt is required every 100 clock pulses, set RELOAD to 99.
151
152 // load the value into SVR
153 *pSRVR &= ~(0x00FFFFFF); // only 24 bits are valid in this register (don't touch reserved bits)
154 *pSRVR |= count_value;
155
156 // Do some settings on SysTick Control and Status Register (SYST_CSR): 0xE000E010
157 // Bit[2] CLKSOURCE: set (since SysTick timer will pull and use processor clock)
158 // Bit[1] TICKINT: set (counting down to zero asserts the SysTick exception request)
159 *pSCSR |= (1 << 1);
160 *pSCSR |= (1 << 2);
161 // Bit[0] ENABLE: set (counter enabled)
162 *pSCSR |= (1 << 0);
163}
164
165__attribute__((naked)) void init_scheduler_stack(uint32_t sched_top_of_stack)
166{
167 //__asm volatile("msr, msp, r0"); // since r0 will contain the value (AAPCS)
168 __asm volatile("msr msp, %0": : "r"(sched_top_of_stack):);
169 __asm volatile("bx lr"); // bx(branch indirect) does [pc] <- [lr] (return from function call)
170}
171
172void init_tasks_stack(void)
173{
174 user_tasks[0].current_state = TASK_READY_STATE; // idle task - always running
175 user_tasks[1].current_state = TASK_READY_STATE;
176 user_tasks[2].current_state = TASK_READY_STATE;
177 user_tasks[3].current_state = TASK_READY_STATE;
178 user_tasks[4].current_state = TASK_READY_STATE;
179
180 user_tasks[0].psp_value = IDLE_STACK_START;
181 user_tasks[1].psp_value = T1_STACK_START;
182 user_tasks[2].psp_value = T2_STACK_START;
183 user_tasks[3].psp_value = T3_STACK_START;
184 user_tasks[4].psp_value = T4_STACK_START;
185
186 user_tasks[0].task_handler = idle_task;
187 user_tasks[1].task_handler = task1_handler;
188 user_tasks[2].task_handler = task2_handler;
189 user_tasks[3].task_handler = task3_handler;
190 user_tasks[4].task_handler = task4_handler;
191
192 uint32_t *pPSP;
193
194 for (int i = 0; i < MAX_TASKS; i++)
195 {
196 pPSP = (uint32_t *)user_tasks[i].psp_value;
197
198 // ARM Cortex-M4 stack model is Full Descending:
199 // Decrement first, and then store the value
200 pPSP--;
201 *pPSP = DUMMY_XPSR; // 0x01000000
202
203 pPSP--; // pc
204 *pPSP = (uint32_t)user_tasks[i].task_handler; // all elements must be odd to maintain T bit
205
206 pPSP--; // lr
207 *pPSP = 0xFFFFFFFD; // EXC_RETURN
208
209 // rest of the stack frame (SF1, SF2) are to be initilized to 0
210 for (int j = 0; j < 13; j++)
211 {
212 pPSP--;
213 *pPSP = 0;
214 }
215
216 user_tasks[i].psp_value = (uint32_t)pPSP; // preserve PSPs
217 }
218}
219
220// Round-Robin
221void update_next_task(void)
222{
223 int state = TASK_BLOCKED_STATE; // why initialize to this state?
224 for (int i = 0; i < MAX_TASKS; i++)
225 {
226 current_task++;
227 current_task %= MAX_TASKS;
228 state = user_tasks[current_task].current_state;
229 // if current task is a schedulable task
230 if (state == TASK_READY_STATE && current_task != 0)
231 break;;
232 }
233 // at this point:
234 // 1. a schedulable task (other than idle task) is found
235 // 2. all tasks other than the idle task are BLOCKED
236
237 // if case 2
238 if (state == TASK_BLOCKED_STATE)
239 current_task = 0;
240}
241
242uint32_t get_psp_value(void)
243{
244 return user_tasks[current_task].psp_value;
245}
246
247
248void save_psp_value(uint32_t current_psp_value)
249{
250 user_tasks[current_task].psp_value = current_psp_value;
251}
252
253__attribute__((naked)) void switch_sp_to_psp(void)
254{
255 __asm volatile("push {lr}"); // preserve LR to later return back to main()
256
257 // 1. Initialize the PSP with TASK1 stack start
258 // get the value of psp of current task
259 __asm volatile("bl get_psp_value"); // bl since you need to come back
260 // according to AAPCS, initial SP of the current task will be stored in r0
261 __asm volatile("msr psp, r0"); // initialize PSP
262 __asm volatile("pop {lr}");
263
264 // up to this point MSP has been used
265
266 // 2. Change SP to PSP using CONTROL register
267 // Bit[1] SPSEL: set
268 __asm volatile("mov r0, #0x02");
269 __asm volatile("msr CONTROL, r0"); // wouldn't it corrupt other bits of CONTROL reg?
270
271 __asm volatile("bx lr"); // force return to main()
272}
273
274void enable_processor_faults(void)
275{
276 uint32_t *pSHCSR = (uint32_t *)0xE000ED24;
277
278 *pSHCSR |= (1 << 16); // MemManage
279 *pSHCSR |= (1 << 17); // BusFault
280 *pSHCSR |= (1 << 18); // UsageFault
281}
282
283void schedule(void)
284{
285 // Simply pend the PendSV exception (set bit[28] PENDSVSET)
286 uint32_t *pICSR = (uint32_t *)0xE000ED04;
287 *pICSR |= (1 << 28);
288}
289
290void task_delay(uint32_t tick_count)
291{
292 // Disable interrupt first to prevent 'race condition'. (Use PRIMASK register)
293 // PRIMASK: Priority Mask Register, if bit[0] is set, prevents the activation
294 // of all exceptions with configurable priority.
295 // Since user_task[] is a global array which can be accessed by both
296 // the handler mode code and the thread mode code.
297 INTERRUPT_DISABLE();
298 // All interrupts including SysTick exception, etc. are disabled.
299 // Only Thread mode code is running.
300
301 // if current_task is idle task, do nothing and return
302 if (current_task)
303 {
304 user_tasks[current_task].block_count = g_tick_count + tick_count;
305 // delay period is calculated with respect to g_tick_count
306 // in other words, when g_tickc_count becomes the block_count value in the future
307 // this task will be qualified to be changed to RUNNING state
308 user_tasks[current_task].current_state = TASK_BLOCKED_STATE;
309
310 // allow other available task to run
311 schedule(); // trigger the PendSV handler
312 }
313
314 // Enable interrupt
315 INTERRUPT_ENABLE();
316}
317
318// does context switch
319__attribute__((naked)) void PendSV_Handler()
320{
321 // c.f. To debug an interrupt handler, you need to set a breakpoint inside it
322 // and run the program. Stepping-over line by line will not detect it.
323
324 // Save the context of current task. -------------------------------------------
325 // 1. Get current running task's PSP value
326 __asm volatile("mrs r0, psp");
327 // 2. Using that PSP value store SF2 (R4 - R11); SF1 is already store by the
328 // processor.
329 // Do not use push since in a handler always MSP will be used.
330 // This is the case where you need store register contents into memory.
331 // Use 'stmdb': store multiple registers, decrement before (This simulates
332 // push operation of ARM Cortex-M4 whose stack model is Full Descending)
333 __asm volatile("stmdb r0!, {r4-r11}"); // if ! is appended, "write back", which
334 // means the final address, that is loaded from or stored to, is written back
335 // into r0 (update r0)
336
337 __asm volatile("push {lr}"); // preserve EXC_RETURN value
338 // this has to be done manually since it's a naked function
339
340 // 3. Save the current PSP value
341 __asm volatile("bl save_psp_value");
342
343 // Retrieve the context of next task -------------------------------------------
344 // 1. Decide next task to run
345 __asm volatile("bl update_next_task");
346 // 2. Get its past PSP value (At this point current_task value will have been
347 // updated already.
348 __asm volatile("bl get_psp_value"); // r0 will contain PSP of the current_task
349 // 3. Using that PSP value retrieve SF2 (R4 to R11)
350 // Do not use pop since in a handler always MSP will be used.
351 // This is the case where you need load memory contents to registers.
352 // Use 'ldmia': load multiple registers, increment after (ia is default)
353 // pop operation of ARM Cortex-M4 whose stack model is Full Descending)
354 __asm volatile("ldmia r0!, {r4-r11}"); // if ! is appended, "write back", which
355 // means the final address, that is loaded from or stored to, is written back
356 // into r0 (update r0)
357 // 4. Update PSP and exit.
358 __asm volatile("msr psp, r0");
359 // now psp points to the stack of the task to be executed
360
361 __asm volatile("pop {lr}");
362 __asm volatile("bx lr"); // this has to be done manually since naked function
363 // At this point, context switch has been completed. SysTick handler exit sequence
364 // will take place automatically bye the processor.
365 // Q: Does PC now point to the first instruction the task to be executed should run?
366}
367
368void update_global_tick_count(void)
369{
370 g_tick_count++;
371}
372
373void unblock_tasks(void)
374{
375 for (int i = 1; i < MAX_TASKS; i++) // i = 0 -> idle task (no need to check)
376 {
377 if (user_tasks[i].current_state != TASK_READY_STATE)
378 {
379 if (user_tasks[i].block_count == g_tick_count)
380 {
381 user_tasks[i].current_state = TASK_READY_STATE;
382 }
383 }
384 }
385}
386
387// Scheduler (does not have to be 'naked' function)
388void SysTick_Handler(void)
389{
390 uint32_t *pICSR = (uint32_t *)0xE000ED04;
391 update_global_tick_count();
392
393 // Determine if the BLOCKED task meets condition to become RUNNING state.
394 unblock_tasks();
395
396 // Pend the PendSV exception (set bit[28] PENDSVSET)
397 *pICSR |= (1 << 28);
398}
399
400void HardFault_Handler(void)
401{
402 printf("Exception: HardFault\n");
403 while(1);
404}
405
406void MemManage_Handler(void)
407{
408 printf("Exception: MemManage\n");
409 while(1);
410}
411
412void BusFault_Handler(void)
413{
414 printf("Exception: BusFault\n");
415 while(1);
416}
Nayak, K. (2022). Embedded Systems Programming on ARM Cortex-M3/M4 Processor [Video file]. Retrieved from https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/