RTOS

Overview

The mbed RTOS is a C++ wrapper over the Keil RTX code. For more information about Keil RTX, check the Keil CMSIS-RTOS tutorial and the element14 introduction to Keil RTX. You can use these resources as a general introduction to RTOS principles; it is important to be familiar with the concepts behind an RTOS in order to understand this guide.

The code of the mbed RTOS can be found in the mbed-os repository, in the rtos subdirectory. The Doxygen is available here.

Thread

The Thread class allows defining, creating and controlling thread functions in the system.

A Thread can be in the following states:

  • Running: The currently running thread. Only one thread at a time can be in this state.
  • Ready: Threads that are ready to run. Once the running thread has terminated or is waiting, the ready thread with the highest priority becomes the running thread.
  • Waiting: Threads that are waiting for an event to occur.
  • Inactive: Threads that are not created or terminated. These threads typically consume no system resources.

The main() function

The function main is a special thread function that is started at system initialization and has the initial priority osPriorityNormal; it is the first thread the RTOS schedules.

Thread example

The code below uses two separate threads to blink two LEDs. The first thread is automatically created and executes the main function; the second thread is created explicitly inside main.

 


#include "mbed.h"
 
DigitalOut led1(LED1);
DigitalOut led2(LED2);
Thread thread;
 
void led2_thread() {
    while (true) {
        led2 = !led2;
        wait(1);
    }
}
 
int main() {
    thread.start(led2_thread);
    
    while (true) {
        led1 = !led1;
        wait(0.5);
    }
}

Thread example with callbacks

The Callback API provides a convenient way to pass arguments to spawned threads.

 


#include "mbed.h"

Thread thread;
DigitalOut led1(LED1);
volatile bool running = true;

// Blink function toggles the led in a long running loop
void blink(DigitalOut *led) {
    while (running) {
        *led = !*led;
        wait(1);
    }
}

// Spawns a thread to run blink for 5 seconds
int main() {
    thread.start(callback(blink, &led1));
    wait(5);
    running = false;
    thread.join();
}

Thread class reference

Public Types

Public Member Functions

 Thread (osPriority priority=osPriorityNormal, uint32_t stack_size=OS_STACK_SIZE, unsigned char *stack_mem=NULL, const char *name=NULL)
 Thread (mbed::Callback< void()> task, osPriority priority=osPriorityNormal, uint32_t stack_size=OS_STACK_SIZE, unsigned char *stack_mem=NULL)
template<typename T >
 Thread (T *argument, void(T::*task)(), osPriority priority=osPriorityNormal, uint32_t stack_size=OS_STACK_SIZE, unsigned char *stack_mem=NULL)
template<typename T >
 Thread (T *argument, void(*task)(T *), osPriority priority=osPriorityNormal, uint32_t stack_size=OS_STACK_SIZE, unsigned char *stack_mem=NULL)
 Thread (void(*task)(void const *argument), void *argument=NULL, osPriority priority=osPriorityNormal, uint32_t stack_size=OS_STACK_SIZE, unsigned char *stack_mem=NULL)
osStatus start (mbed::Callback< void()> task)
template<typename T , typename M >
osStatus start (T *obj, M method)
osStatus join ()
osStatus terminate ()
osStatus set_priority (osPriority priority)
osPriority get_priority ()
int32_t signal_set (int32_t signals)
int32_t signal_clr (int32_t signals)
State get_state ()
uint32_t stack_size ()
uint32_t free_stack ()
uint32_t used_stack ()
uint32_t max_stack ()
const char * get_name ()

Static Public Member Functions

static osEvent signal_wait (int32_t signals, uint32_t millisec=osWaitForever)
static osStatus wait (uint32_t millisec)
static osStatus yield ()
static osThreadId gettid ()
static void attach_idle_hook (void(*fptr)(void))
static void attach_terminate_hook (void(*fptr)(osThreadId id))

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< Thread >
 NonCopyable ()
 ~NonCopyable ()

Mutex

A Mutex is used to synchronize the execution of threads, for example to protect the access to a shared resource.

Warning: ISR
The Mutex methods cannot be called from interrupt service routines (ISR). In the current version of mbed OS, if you attempt to use a mutex from within an ISR, nothing happens; attempts to lock a mutex succeed immediately, regardless of whether the lock is actually free. In other words, if you acquire a mutex lock in an ISR, you can break the thread safety mechanisms and introduce race-conditions into an otherwise safe piece of code. Future versions of mbed OS will provide warnings and ultimately prevent this from happening.

Mutex example

Use Mutex to protect printf().

 


#include "mbed.h"

Mutex stdio_mutex;
Thread t2;
Thread t3;
    
void notify(const char* name, int state) {
    stdio_mutex.lock();
    printf("%s: %d\n\r", name, state);
    stdio_mutex.unlock();
}

void test_thread(void const *args) {
    while (true) {
        notify((const char*)args, 0); wait(1);
        notify((const char*)args, 1); wait(1);
    }
}

int main() {
    t2.start(callback(test_thread, (void *)"Th 2"));
    t3.start(callback(test_thread, (void *)"Th 3"));

    test_thread((void *)"Th 1");
}

Note: C standard library Mutexes
The ARM C standard library already has Mutexes in place to protect the access to stdio, so on the LPC1768 the above example is not necessary. On the other hand, the LPC11U24 does not provide default stdio Mutexes, making the above example a necessity.

Warning: stdio, malloc and new in ISR
Because of the mutexes in the ARM C standard library, you cannot use stdio (printf, putc, getc and so on), malloc and new in ISR.

Mutex class reference

Public Member Functions

 Mutex ()
 Mutex (const char *name)
osStatus lock (uint32_t millisec=osWaitForever)
bool trylock ()
osStatus unlock ()

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< Mutex >
 NonCopyable ()
 ~NonCopyable ()

Semaphore

A Semaphore manages thread access to a pool of shared resources of a certain type.

Semaphore example

Use Semaphore to protect printf().

 


#include "mbed.h"

Semaphore two_slots(2);
Thread t2;
Thread t3;
    
void test_thread(void const *name) {
    while (true) {
        two_slots.wait();
        printf("%s\n\r", (const char*)name);
        wait(1);
        two_slots.release();
    }
}

int main (void) {
    t2.start(callback(test_thread, (void *)"Th 2"));
    t3.start(callback(test_thread, (void *)"Th 3"));

    test_thread((void *)"Th 1");
}

Semaphore class reference

Public Member Functions

 Semaphore (int32_t count=0)
 Semaphore (int32_t count, uint16_t max_count)
int32_t wait (uint32_t millisec=osWaitForever)
osStatus release (void)

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< Semaphore >
 NonCopyable ()
 ~NonCopyable ()

Signals

Each Thread can wait for signals and be notified of events:

 


#include "mbed.h"

Thread thread;
DigitalOut led(LED1);

void led_thread() {
    while (true) {
        // Signal flags that are reported as event are automatically cleared.
        Thread::signal_wait(0x1);
        led = !led;
    }
}

int main (void) {
    thread.start(callback(led_thread));

    while (true) {
        wait(1);
        thread.signal_set(0x1);
    }
}

Queue and MemoryPool

Queue

A Queue allows you to queue pointers to data from producer threads to consumer threads:

Queue<message_t, 32> queue;

message_t *message;

queue.put(message);

osEvent evt = queue.get();
if (evt.status == osEventMessage) {
    message_t *message = (message_t*)evt.value.p;

Queue class reference

Public Member Functions

 Queue ()
osStatus put (T *data, uint32_t millisec=0, uint8_t prio=0)
osEvent get (uint32_t millisec=osWaitForever)

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< Queue< T, queue_sz > >
 NonCopyable ()
 ~NonCopyable ()

MemoryPool

You can use the MemoryPool class to define and manage fixed-size memory pools:

MemoryPool<message_t, 16> mpool;

message_t *message = mpool.alloc();

mpool.free(message);

MemoryPool class reference

Public Member Functions

 MemoryPool ()
 ~MemoryPool ()
T * alloc (void)
T * calloc (void)
osStatus free (T *block)

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< MemoryPool< T, pool_sz > >
 NonCopyable ()
 ~NonCopyable ()

Queue and MemoryPool example

This example shows Queue and MemoryPool (see below) managing measurements.

 


#include "mbed.h"

typedef struct {
    float    voltage;   /* AD result of measured voltage */
    float    current;   /* AD result of measured current */
    uint32_t counter;   /* A counter value               */
} message_t;

MemoryPool<message_t, 16> mpool;
Queue<message_t, 16> queue;
Thread thread;

/* Send Thread */
void send_thread (void) {
    uint32_t i = 0;
    while (true) {
        i++; // fake data update
        message_t *message = mpool.alloc();
        message->voltage = (i * 0.1) * 33; 
        message->current = (i * 0.1) * 11;
        message->counter = i;
        queue.put(message);
        wait(1);
    }
}

int main (void) {
    thread.start(callback(send_thread));
    
    while (true) {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage) {
            message_t *message = (message_t*)evt.value.p;
            printf("\nVoltage: %.2f V\n\r"   , message->voltage);
            printf("Current: %.2f A\n\r"     , message->current);
            printf("Number of cycles: %u\n\r", message->counter);
            
            mpool.free(message);
        }
    }
}

Mail

Mail works like a queue, with the added benefit of providing a memory pool for allocating messages (not only pointers).

Mail class reference

Public Member Functions

 Mail ()
T * alloc (uint32_t millisec=0)
T * calloc (uint32_t millisec=0)
osStatus put (T *mptr)
osEvent get (uint32_t millisec=osWaitForever)
osStatus free (T *mptr)

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< Mail< T, queue_sz > >
 NonCopyable ()
 ~NonCopyable ()

Mail example

This code uses mail to manage measurement.

 


#include "mbed.h"

/* Mail */
typedef struct {
  float    voltage; /* AD result of measured voltage */
  float    current; /* AD result of measured current */
  uint32_t counter; /* A counter value               */
} mail_t;

Mail<mail_t, 16> mail_box;
Thread thread;

void send_thread (void) {
    uint32_t i = 0;
    while (true) {
        i++; // fake data update
        mail_t *mail = mail_box.alloc();
        mail->voltage = (i * 0.1) * 33; 
        mail->current = (i * 0.1) * 11;
        mail->counter = i;
        mail_box.put(mail);
        wait(1);
    }
}

int main (void) {
    thread.start(callback(send_thread));
    
    while (true) {
        osEvent evt = mail_box.get();
        if (evt.status == osEventMail) {
            mail_t *mail = (mail_t*)evt.value.p;
            printf("\nVoltage: %.2f V\n\r"   , mail->voltage);
            printf("Current: %.2f A\n\r"     , mail->current);
            printf("Number of cycles: %u\n\r", mail->counter);
            
            mail_box.free(mail);
        }
    }
}

RtosTimer

Warning: DEPRECATED

The RtosTimer has been superseded by the EventQueue. The RtosTimer and EventQueue duplicate the functionality of timing events outside of interrupt context, however the EventQueue has additional features to handle deferring other events to multiple contexts.

Use the RtosTimer class to create and and control timer functions in the system. A timer function is called when a time period expires, so both one-shot and periodic timers are possible. A timer can be started, restarted or stopped.

Timers are handled in the thread osTimerThread. Callback functions run under the control of this thread and may use CMSIS-RTOS API calls.

RtosTimer example

Control the timing of four LEDs.

 


#include "mbed.h"
#include "rtos.h"

DigitalOut LEDs[4] = {
    DigitalOut(LED1), DigitalOut(LED2), DigitalOut(LED3), DigitalOut(LED4)
};

void blink(void const *n) {
    LEDs[(int)n] = !LEDs[(int)n];
}

int main(void) {
    RtosTimer led_1_timer(blink, osTimerPeriodic, (void *)0);
    RtosTimer led_2_timer(blink, osTimerPeriodic, (void *)1);
    RtosTimer led_3_timer(blink, osTimerPeriodic, (void *)2);
    RtosTimer led_4_timer(blink, osTimerPeriodic, (void *)3);
    
    led_1_timer.start(2000);
    led_2_timer.start(1000);
    led_3_timer.start(500);
    led_4_timer.start(250);
    
    Thread::wait(osWaitForever);
}

RtosTimer class reference

Public Member Functions

 RtosTimer (void(*func)(void const *argument), os_timer_type type=osTimerPeriodic, void *argument=NULL)
 RtosTimer (mbed::Callback< void()> func, os_timer_type type=osTimerPeriodic)
template<typename T , typename M >
 RtosTimer (T *obj, M method, os_timer_type type=osTimerPeriodic)
osStatus stop (void)
osStatus start (uint32_t millisec)

Additional Inherited Members

- Protected Member Functions inherited from mbed::NonCopyable< RtosTimer >
 NonCopyable ()
 ~NonCopyable ()

Interrupt Service Routines

The same RTOS API can be used in ISR. The only two warnings are:

  • 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"

Thread thread;
Ticker ticker;
Queue<uint32_t, 5> queue;
DigitalOut myled(LED1);

void queue_isr() {
    queue.put((uint32_t*)2);
    myled = !myled;
}

void queue_thread() {
    while (true) {
        queue.put((uint32_t*)1);
        wait(1);
    }
}

int main (void) {
    thread.start(callback(queue_thread));
    ticker.attach(queue_isr, 1.0);

    while (true) {
        osEvent evt = queue.get();
        if (evt.status != osEventMessage) {
            printf("queue->get() returned %02x status\n\r", evt.status);
        } else {
            printf("queue->get() returned %d\n\r", evt.value.v);
        }
    }
}

Default Timeouts

The mbed RTOS API has made the choice of defaulting to 0 timeout (no wait) for the producer methods, and osWaitForever (infinite wait) for the consumer methods.

A typical scenario for a producer could be a peripheral triggering an interrupt to notify an event; in the corresponding interrupt service routine you cannot wait (this would deadlock the entire system). On the other side, the consumer could be a background thread waiting for events; in this case the desired default behaviour is not using CPU cycles until this event is produced, hence the osWaitForever.

Warning: No wait in ISR
When calling an RTOS object method in an ISR all the timeout parameters have to be set to 0 (no wait); waiting in ISR is not allowed.

Status and error codes

The CMSIS-RTOS functions will return the following statuses:

  • osOK: function completed; no event occurred.
  • osEventSignal: function completed; signal event occurred.
  • osEventMessage: function completed; message event occurred.
  • osEventMail: function completed; mail event occurred.
  • osEventTimeout: function completed; timeout occurred.
  • osErrorParameter: a mandatory parameter was missing or specified an incorrect object.
  • osErrorResource: a specified resource was not available.
  • osErrorTimeoutResource: a specified resource was not available within the timeout period.
  • osErrorISR: the function cannot be called from interrupt service routines (ISR).
  • osErrorISRRecursive: function called multiple times from ISR with same object.
  • osErrorPriority: system cannot determine priority or thread has illegal priority.
  • osErrorNoMemory: system is out of memory; it was impossible to allocate or reserve memory for the operation.
  • osErrorValue: value of a parameter is out of range.
  • osErrorOS: unspecified RTOS error - runtime error but no other error message fits.