The Main Loop
2018
Asynchronous programming in an embedded environment
What does a CPU do when it has nothing to do?
The Arduino Way
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
}
void loop()
{
digitalWrite(LED_BUILTIN, HIGH);
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
The Arduino Way
void delay(double __ms)
{
double __tmp = ((F_CPU) / 4e3) * __ms;
uint16_t __ticks;
if (__tmp < 1.0)
{
__ticks = 1;
}
else if (__tmp > 65535)
{
__ticks = (uint16_t) (__ms * 10.0);
while(__ticks)
{
_delay_loop_2(((F_CPU) / 4e3) / 10);
__ticks --;
}
return;
}
else
{
__ticks = (uint16_t)__tmp;
}
_delay_loop_2(__ticks);
}
void _delay_loop_2(uint16_t c)
{
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (c)
: "0" (c)
);
}
void _delay_loop_2(uint16_t c)
{
// more or less
while (c--);
}
The FreeRTOS Way
void vTaskDelay(uint32_t ticks)
{
while (ticks > 0)
{
vTaskSuspendAll();
prvAddCurrentTaskToDelayedList(ticks);
vTaskResumeAll();
}
}
The FreeRTOS Way
void xPortSysTickHandler(void)
{
if (xTaskIncrementTick())
{
SCB->ICSR =
SCB_ICSR_PENDSVSET_Msk;
__SEV();
}
}
bool xTaskIncrementTick(void)
{
if(xConstTickCount >=
xNextTaskUnblockTime)
{
pxTCB =
listGET_HEAD(pxDelayedTaskList);
prvAddTaskToReadyList(pxTCB);
return true;
}
}
void xPortPendSVHandler(void)
{
asm volatile
(
" stmdb r0!, {r4-r11, r14} \n"
" str r0, [r2] \n"
"bl vTaskSwitchContext \n"
" ldr r1, [r3] \n"
" ldr r0, [r1] \n"
" ldmia r0!, {r4-r11, r14} \n"
)
}
void vTaskSwitchContext(void)
{
pxCurrentTCB =
taskSELECT_HIGHEST_PRIORITY_TASK();
}
The FreeRTOS Way
void prvIdleTask(void *)
{
while (true)
{
ticks = prvGetExpectedIdleTime();
vPortSuppressTicksAndSleep(ticks);
}
}
void vPortSuppressTicksAndSleep(uint32_t ticks)
{
do {
__WFE();
} while (!(NVIC->ISPR[0] | NVIC->ISPR[1]));
}
CPU vs I/O
The official 4.2BSD release came in August 1983
select(2) first appeared in 4.2BSD
The UNIX Way
int connection = socket(...);
fd_set descriptors;
while (true)
{
FD_ZERO(&descriptors);
FD_SET(connection, &descriptors);
select(&descriptors);
if (FD_ISSET(connection, &descriptors))
process_connection();
}
What if...
void some_task(void)
{
int i = 0;
while (true)
{
printf("%d\n", i++);
return sleep_ms(100);
}
}
???
State machine
void some_task(void)
{
static enum state state = STATE_IDLE;
static int i = 0;
switch (state)
{
case STATE_IDLE:
printf("%d\n", i++);
state = STATE_WAITING;
sleep_ms(100);
break;
case STATE_WAITING:
state = STATE_IDLE;
break;
}
}
void some_task(struct event *event)
{
static enum state state = STATE_IDLE;
static int i = 0;
switch (state)
{
case STATE_IDLE:
printf("%d\n", i++);
state = STATE_WAITING;
sleep_ms(100);
break;
case STATE_WAITING:
if (event->id == EVENT_TIMEOUT)
state = STATE_IDLE;
break;
}
}
Events
void sleep_ms(
uint32_t timeout)
{
// configure TIMER0
}
void TIMER0_Handler(void)
{
struct event ev = {
.id = EVENT_TIMEOUT,
.data = NULL,
};
queue_put(&ev);
}
NRF_SDH_BLE_OBSERVER(
m_ble_observer,
0,
ble_evt_handler,
NULL);
void ble_evt_handler(
ble_evt_t const * p_ble_evt,
void * p_context)
{
struct event ev = {
.id = EVENT_BLE,
.data = ...
};
queue_put(&ev);
}
Sources
bool queue_pop(struct queue *q, struct event *e);
void queue_push(struct queue *q, struct event *e);
struct queue queue;
while (true)
{
struct event;
if (!queue_pop(&queue, &event))
{
__WFE();
continue;
}
some_task(&event);
another_task(&event);
}
Queues
bool queue_pop(struct queue *q, struct event *e);
void queue_push_back(struct queue *q, struct event *e);
void queue_push_front(struct queue *q, struct event *e);
struct queue queue;
while (true)
{
struct event;
if (!queue_pop(&queue, &event))
{
__WFE();
continue;
}
some_task(&event);
another_task(&event);
}
Priorities
Blocking
void some_task(struct event *event)
{
static enum state state = STATE_IDLE;
switch (state)
{
case STATE_IDLE:
state = STATE_WAITING;
long_computation();
break;
// ...
}
}
Blocking: worker thread
struct work
{
void (*callback)(void *);
void *data;
};
void worker(void)
{
struct work work;
while (true)
{
if (queue_pop(&queue, &work))
work.callback(work.data);
}
}
-
no task synchronisation
-
trivial unit testing
-
lower resource usage
-
well-defined behaviour
-
enforced task isolation
-
somewhat harder to read
-
state explosion
Pros
Cons
hierarchical state machines
pushdown automata
coroutines
The Main Loop
Thank you
Questions?
Main Loop vol 0, embedded @ Silvair
By Michał Lowas-Rzechonek
Main Loop vol 0, embedded @ Silvair
Asynchronous programming in an embedded environment
- 930