Execution
Threads
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 (e.g. 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. You should not use it for new designs. Use EventQueue instead.
On top of the standard system threads, some drivers may use additional threads. Users 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.
#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]));
wait(5);
}
}
}
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 %02x)\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();
}
}
}