-
Notifications
You must be signed in to change notification settings - Fork 2
/
04.Preemptive_Task_Switching.ino
154 lines (133 loc) · 5.24 KB
/
04.Preemptive_Task_Switching.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
This example shows how preemptive task switching can be implemented on
top of avr_getcontext(), avr_makecontext(), AVR_SAVE_CONTEXT_GLOBAL_POINTER(),
AVR_RESTORE_CONTEXT_GLOBAL_POINTER() and a hardware timer, which
generates interrupts.
The main idea is quite simple. There are two tasks, both run
indefinitely: one enables the built-in LED, the other one disables
it. System timer, which is implemented on top of watchdog running in
interrupt mode, ticks every one second and switches the tasks in Round
Robin fashion during the tick. Thus, the LED does not remain enabled
or disabled for more than one second.
One notable interesting point here is that we convert the initial
execution context of an MCU into a switchable task. This allows loop()
function to work as expected. We use it to enable the LED. To do so we
allocate the first element of the 'tasks' array for the initial MCU
execution context, we modify the global variables used for task
switching in such a way that during the first interrupt tick the
initial execution context gets saved into the first element of the 'tasks'
array.
Please keep in mind that our intention here is to show how task
switching can be performed in a preemptive task executive, not to
implement an RTOS in one Arduino sketch. In a real RTOS system timer
would tick at a much higher rate, and on every tick, it would perform
much more complicated scheduling code.
Nevertheless, if you are brave enough to write your own RTOS, this
tiny sketch might be a good start.
***
Author: Artem Boldariev <artem@boldariev.com>
This example code is in the public domain.
See UNLICENSE.txt in the examples directory for license details.
*/
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avrcontext_arduino.h>
//// Global variables
extern "C" {
avr_context_t *volatile current_task_ctx; // current task context
}
static size_t current_task_num; // current task index
static avr_context_t dummy_ctx; // never going to be used
static avr_context_t tasks[2]; // contexts for tasks
static uint8_t disabler_stack[128]; // stack for the disabler_task
// This function is the second task body.
// It tries to disable the built-in LED (forever).
static void disabler_task(void *)
{
for (;;)
{
digitalWrite(LED_BUILTIN, LOW);
}
}
// This function starts system timer. We use the watchdog timer
// because it is unused by default on Arduino boards.
void start_system_timer(void)
{
cli(); // disable interrupts
MCUSR &= ~(1<<WDRF);
wdt_reset(); // reset watchdog timer
// configure WD timer
WDTCSR |= 1 << WDCE | 1 << WDE; // enable WD timer configuration mode
WDTCSR = 0; // reset WD timer
wdt_enable(WDTO_1S); // configure period
WDTCSR |= 1 << WDIE; // use WD timer in interrupt mode
sei(); // enable interrupts
}
void setup(void)
{
// Enable builtin LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Initialise dummy context.
//
// Actually, we could avoid initialising it. If our intention was
// to implement a real thread manager/operating system we could
// create a context which upon activation after task completion
// would run scheduler to choose the next task.
//
// Our tasks run code in endless loops, so this context never gets
// activated.
//
// Our tasks run code in endless loops, so this context never gets
// activated.
avr_getcontext(&dummy_ctx);
// Initialise the first task.
//
// Convert the currently running code into a first task. When the
// system timer ticks for the first time, the current execution
// context is going to be saved into tasks[0]. See the
// switch_task() function for the actual task switching code.
// To put it simply: we hijack the current execution context and
// make it schedulable.
current_task_num = 0;
current_task_ctx = &tasks[0];
// Initialise the second task.
//
// This task starts execution on the first system timer tick.
avr_getcontext(&tasks[1]);
avr_makecontext(&tasks[1],
(void*)&disabler_stack[0], sizeof(disabler_stack),
&dummy_ctx,
disabler_task, NULL);
// start scheduling
start_system_timer();
// after returning from this function
// loop() gets executed (as usual).
}
// This code tries to enable built-in LED (forever).
void loop(void)
{
digitalWrite(LED_BUILTIN, HIGH);
}
static void switch_task(void)
{
current_task_num = current_task_num == 0 ? 1 : 0;
current_task_ctx = &tasks[current_task_num];
}
// System Timer Interrupt System Routine.
//
// Please keep in mind that ISR_NAKED attribute is important, because
// we have to save the current task execution context without changing
// it.
ISR(WDT_vect, ISR_NAKED)
{
// save the context of the current task
AVR_SAVE_CONTEXT_GLOBAL_POINTER(
"cli\n", // disable interrupts during task switching
current_task_ctx);
switch_task(); // switch to the other task.
WDTCSR |= 1 << WDIE; // re-enable watchdog timer interrupts to avoid reset
// restore the context of the task to which we have just switched.
AVR_RESTORE_CONTEXT_GLOBAL_POINTER(current_task_ctx);
asm volatile("reti\n"); // return from the interrupt and activate the restored context.
}