Mistake on this page?
Report an issue in GitHub or email us

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 in mbed_app.json by defining the MAIN_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();
        }
    }
}

Important Information for this Arm website

This site uses cookies to store information on your computer. By continuing to use our site, you consent to our cookies. If you are not happy with the use of these cookies, please review our Cookie Policy to learn how they can be disabled. By disabling cookies, some features of the site will not work.