Bare Metal RTOS (BMOS)


This project seeks to implement a basic RTOS, featuring several drivers, on the STM32 Nucleo-64 development board. The RTOS supports preemption, tasks, semaphores, dynamic interrupt installation, and a logging subsystem. It offers a UART and GPIO driver specific to the Nucleo dev board. The RTOS also provides syscall stubs to support Newlib.

Project Goals

This project was primarily developed to cement my understanding of RTOS concepts by implementing an RTOS directly in hardware. I implemented GPIO and UART drivers both to simply testing and enable more compelling demos. The primary goal of the project was to fully support switching between multiple tasks on an ARM core, and allow tasks to preempt each other.

Task implementation

The RTOS task support uses simple task control blocks, defined in rtos/sys/task/task.c . Each task has an independent stack, and during the process of a context switch the running task will have all registers pushed onto its stack. When a task is resumed, registers can simply be popped off the task's stack. Tasks can be blocked (if they are pending on a semaphore), delayed, or preempted (if preemption is enabled). When preemption is enabled, the SysTick interrupt handler will determine if preemption is required. Context switches are triggered via the PendSV exception.

Startup flow

The RTOS startup flow first creates the idle task control block, then triggers an SVCall exception. The exception handler for SVCalls will enable the SysTick interrupt, switch to the process stack pointer, and run whichever task is selected as the active task. If the user has not configured tasks, this will be the idle task. Otherwise, the highest priority task they have created will run.

Task Blocking

A task can be delayed or blocked. Delayed tasks will be placed in a queue, and each SysTick exception will decrement the delay of all tasks in the queue. If a task reaches zero delay, it will be moved to the ready queue, and if preemption is enabled the task will be switched into. A task can also be blocked by taking from a semaphore. The task will be placed into a block queue, and will only be removed when another task gives to the semaphore.

Logging subsystem

The RTOS logging system is relatively simple. It implements various levels of debug logs, which can be disabled by defining a lower verbosity log level. The logging statements are passed to a console device, which may be implemented using SWO or a UART.

Newlib and Syscalls

Newlib is implemented, along with support for a subset of syscalls. Memory allocation is supported using a global heap, and writing to stdout will result in statements being printed to the selected console device. If a program calls exit, the RTOS can optionally log the exit code instead of simply hanging.

Drivers

Drivers for the LPUART device and GPIO peripheral are implemented in the RTOS. These drivers allow the user to print to or read from a console, as well as control GPIO devices. Both these drivers are implemented without use of the STM32 HAL, and support interrupt driven operation. DMA support is not implemented in the LPUART driver, however.

Portability

The RTOS and drivers are separated, and the only device driver the RTOS relies on being available is a UART driver for console operation. Therefore the RTOS should theoretically be portable to any Cortex-M MCU, provided that the linker file is correctly updated.

Code reference

The code for BMOS can be found here.