Execution
Threads
In single-microcontroller embedded systems, a thread is an independent segment of the program that executes within the single process running on the microcontroller. Threading is a fundamental technique in software development, which allows multiple tasks to run concurrently using schedulers, either standalone or included in a real time operating system (RTOS). Threads provide a lot of flexibility to the developer but come at the cost of resources for the scheduler. Mbed OS, in its default full profile, provides an RTOS for applications where resources are not critical. The bare metal profile does not include the RTOS or threading capabilities.
There are two types of schedulers (a central piece of software):
- Preemptive: The scheduler is responsible for picking and running the threads. Preemptive schedulers can use different algorithms:
- Mbed OS uses priority based round robin.
- In an algorithm not used in Mbed OS, threads are executed in equal time slots according to their priority.
- Cooperative: The scheduler doesn't actively manage the threads. An active thread needs to yield execution for another thread to run. Mbed OS does not support this.
Your application (main
function) starts execution in the main thread, but it's not the only thread running in Mbed OS. There are many threads running system services, such as:
-
Main: The default thread that executes the application's
main
function. The main thread has 4kB of stack space by default. The application can configure it inmbed_app.json
by defining theMAIN_STACK_SIZE
parameter. -
Idle: The thread that's run by the scheduler when there's no other activity in the system (for example, all other threads are waiting for some event). It's used to make sure the board is not burning empty processor cycles, but is put to sleep for as long as possible.
-
Timer: The thread that handles system and user timer objects.
Note: The user timer class RtosTimer is deprecated and you should not use it in new designs. Use EventQueue instead.
On top of the standard system threads, some drivers may use additional threads. You can create threads using the Thread class.
Modes
Mbed OS executes in two modes:
- Thread mode - Default application mode. All user threads execute in this mode. It uses dedicated thread specific stack memory.
- Handler mode - Interrupt mode, system code and interrupt handlers execute in this mode. It uses static system ISR stack memory.
Handler mode
All the ISR handlers execute in this mode. You can use the same RTOS API in ISR handlers. The important difference between the modes is that code written for the handler mode can't wait; it executes as fast as possible and returns to the thread mode. Therefore:
- You cannot use
Mutex
. - Wait in ISR is not allowed; all the timeouts in method parameters have to be set to 0.
ISR example
This example uses a message from the queue to trigger an interrupt.
/*
* Copyright (c) 2020 Arm Limited and affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
#include "mbed.h"
Ticker ticker;
Thread thread;
Queue<const char *, 5> trail;
// Since we're printing from multiple threads, we need a mutex
Mutex print_lock;
enum ExecutionTypes {
IDLE,
USER,
ISR
};
const char *ExecutionMessages[] = {
"the idle thread",
"a user thread",
"interrupt context"
};
void handler()
{
// Check to see if we're in interrupt context
if (core_util_is_isr_active()) {
// Do not print since we're in interrupt context
trail.put(&(ExecutionMessages[ISR]));
} else {
// Safe to print since we're in a user thread
print_lock.lock();
printf("Starting user thread\r\n");
print_lock.unlock();
while (true) {
trail.put(&(ExecutionMessages[USER]));
ThisThread::sleep_for(500);
}
}
}
void custom_idle_function()
{
// Custom idle behavior would go here
// We won't print here since the default idle thread's stack is too small
trail.put(&(ExecutionMessages[IDLE]));
// Switch back to the default idle behavior
Kernel::attach_idle_hook(NULL);
}
int main()
{
printf("Starting execution example\r\n");
// Attach the custom idle thread function
Kernel::attach_idle_hook(custom_idle_function);
// Trigger the interrupt every 3 seconds
ticker.attach(handler, 3);
// Start the user thread
thread.start(handler);
// Get the past exectuion trail
while (true) {
osEvent evt = trail.get();
if (evt.status != osEventMessage) {
print_lock.lock();
printf("Failed to retrieve the execution trail (returned %02lx)\r\n", evt.status);
print_lock.unlock();
} else {
print_lock.lock();
printf("Execution was in %s\r\n", *(const char **)evt.value.v);
print_lock.unlock();
}
}
}