Another tutorial series on writing a RTOS kernel...
I'm starting a new series about writing a RTOS kernel for ARM Cortex-M3/4 processors which are the base choices for commonly used STM32, NXP microcontrollers. I plan giving the all theoretical and practical details so that you guys will have the skills that most engineer don't have.
Firstly, let me give the my notes on the processors itself.
Architecture
The Cortex-M3 and Cortex-M4 processors are ARMv7-M architecture.
Programmer's Model
Both processors have two operation status and two modes:
Handler mode: When executing an exception handler such as ISR. When in handler mode, the processor always has privileged access level.
Thread mode: When executing normal application code, the processor can be either in privileged access level or unprivileged level. This is controlled by a special register called CONTROL.
Software can switch the processor in privileged thread mode to unprivileged thread mode. However, it cannot switch itself back from unprivileged to privileged.
Registers
Registers R0-R12 are general purpose registers. The first eight (R0-R7) are also called low registers. Due to the limited available space in the instruction set, many 16-bit instructions can only access the low registers. The higher registers (R8-R12) can be used with 32-bit instructions, and a few with 16-bit instruction. The initial values of R0-R12 are undefined.
R13, Stack Pointer (SP) is used for accessing the stack memory via PUSH and POP operations. Physically there are two different: Main Stack Pointer (MSP) and Process Stack Pointer (PSP).
The selection of SP is determined by a special register called CONTROL.
For most cases, it is not necessary to use the PSP if the application doesn't require an embedded OS. Many simple applications can rely on the MSP completely. The PSP is normally used when an embedded OS is involved, where the stack for the OS kernel and application tasks are separated. The initial value of PSP is undefined, and the initial of MSP is taken from the first word of the memory during the reset sequence.
R14, Link Register (LR) is used for holding the return address when calling a function or subroutine. During exception handling, the LR is also updated automatically to a special EXC_RETURN value, which is then used for triggering the exception return at the end of the exception handler.
R15, Program Counter (PC) returns the current instruction address plus 4 (this is due to the pipeline nature of the design).
Behavior of the Application Program Status Register
The Program Status Register is composed of three status registers:
- Application PSR (APSR)
- Execution PSR (EPSR)
- Interrupt PSR (IPSR)
These three registers can be accessed as one combined register, referred to as xPSR.
The PRIMASK, FAULTMASK, and BASEPRI registers are all used for exception or interrupt masking. Each exception has a priority level where a smaller number is a higher priority. These special registers are used to mask exceptions based on priority levels.
The PRIMASK register is a 1-bit wide interrupt mask register. When set, it blocks all exceptions apart from the NMI and HardFault exception. The most common usage is to disable all interrupts for a time critical process. The FAULTMASK register is very similar to PRIMASK, but it also blocks the HardFault exception. Unlike PRIMASK, FAULTMASK is cleared automatically at exception return. The BASEPRI makes exceptions or interrupts based on priority level. When BASEPRI is set to 0, it is disabled. When it is set to a non-zero value, it blocks exceptions (including interrupts) that have the same or lower priority level., while allowing exceptions with a higher priority level to be accepted by the processor.
The CONTROL register defines:
- The selection of stack pointer (MSP or PSP)
- Access level in thread mode (privileged/unprivileged)
In addition, for Cortex-M4 processor with a floating point unit, one bit of the CONTROL register indicates if the current context uses the floating point unit or not.
The Cortex-M4 processor has an optional floating-point unit. This provides additional registers for floating-point data processing, as well as a Floating Point Status and Control Register (FPSCR). S0-S32 ("S" means single precision) and D0-D15 ("D" means double precision) can be accessed using floating-point instructions.
Memory System
The Cortex-M3/4 processors have the following memory system features:
- 4GB linear address space with 32-bit addressing.
- Architecturally defined memory map - the 4GB memory space is divided into a number of regions for various predefined and peripheral uses.
- Support for little- and big-endian memory systems.
- Bit band accesses - this allows atomic access to individual bits in SRAM or peripheral address space.
- Memory Protection Unit (MPU) - The MPU is a programmable unit which defines access permissions for various memory regions.
- Unaligned transfer support
The MPU can be used to protect memory regions by means of defining access permissions in privileged and unprivileged access states.
Exception and Interrupt
Exceptions are events that cause changes to program flow. When one happens, the processor suspends the current execution task and executes a part of the program called the exception handler. After the execution of the exception handler is completed, the processor then resumes normal program execution. Interrupts are one type of exception and generally are generated from peripheral or external inputs, and in some cases they can be triggered by software. The exception handlers for interrupts are also referred to as Interrupt Service Routines (ISRes).
Each exception source has an exception number. Exception numbers 1 to 15 are classified as system exceptions, and exceptions 16 and above are for interrupts.
The NVIC (Nested Vectored Interrupt Controller) handles the exceptions and interrupt configurations, prioritization, and interrupt masking. The NVIC has the following features:
- Flexible exception and interrupt management
- Nested exception/interrupt support
- Vectored exception/interrupt entry
- Interrupt masking
When an exception event tasks place and is accepted by the processor code, the corresponding exception handler is executed. To determine the starting address of the exception handler, a vector table mechanism is used. The vector table is an array of word data inside the system memory, each representing the starting address of one exception type.
Several types of exceptions are fault handling exceptions. By default the BusFault, UsageFault, and MemManageFault are disabled and all fault events trigger the HardFault exceptions. However, the configurations are programmable and you can enable the three programmable fault exceptions individually to handle different types of faults. The HardFault exception is always enabled.
System Control Block (SCB)
The SCB contains various registers for:
- Controlling processor configurations (e.g., low-power mode)
- Providing fault status information (fault status registers)
- Vector Table Relocation (VTOR)
Debug
There are two types of interfaces provided in the Cortex-M processors: debug and trace. The debug interface allows a debug adaptor to connect to a Cortex-M microcontroller to control the debug features and access the memory space on the chip. The Cortex-M processor supports both JTAG and SWD.
The trace interface is used to collect information from the processor during run-time such as data, event, profiling information, or even complete details of program execution.
Reset and Reset Sequence
In typical Cortex-M microcontrollers, there can be three types of reset:
- Power on reset - reset everything in the microcontroller. This includes the processor and its debug support component and peripherals.
- System reset - reset just the processor and peripherals, but not the debug support component of the processor.
- Processor reset - reset the processor only.
After reset and before the processor starts executing the program, the Cortex-M processors read the first two words from the memory. The beginning of the memory space contains the vector table, and the first two words in the vector table are the initial value for MSP.
Instruction Set
The SuperVisor Call (SVC) instruction is used to generate the SVC exception. Typically, SVC is used with an embedded RTOS, where an application task running in an unprivileged execution state can request services from the OS, which runs in the privileged execution state. The SVC exception mechanism provides the transition from unprivileged to privileged.
The switching of PRIMASK or FAULTMASK to disable and enable an interrupt is commonly used to ensure timing critical code can finish quickly without getting interrupted.
There are two main instructions for entering sleep modes: Wait for Interrupt (WFI) and Wait for Event (WFE).
Memory barrier instructions:
- DMB (Data Memory Barrier); ensures that all memory accesses are completed before new memory access is committed.
- DSB (Data Synchronization Barrier); ensures that all memory accesses are completed before next instruction is executed.
- ISB (Instruction Synchronization Barrier); flushes the pipeline and ensures that all previous instructions are completed before executing new instructions.
Exception and Interrupts
When the Cortex-M processor accepts an exception request, the processor needs to determine the starting address of the exception handler. This information is stored in the vector table in the memory. By default, the vector table starts at memory address 0, and the vector address is arranged according to the exception number times. The vector table is normally defined in the startup codes provided by vendors.
The vector table used in startup code also contains the initial value of the main stack pointer (MSP).
There are various status attributes applicable to each interrupt:
- Each interrupt can be either disabled or enabled
- Each interrupt can be either pending or not pending
- Each interrupt can be either in active or inactive state
The processor accepts an exception if the following
conditions are met:
- The processor is running
- The exception is enabled
- The exception has higher priority than the current priority level
- The exception is not blocked by an exception masking register
An exception entrance sequence contains several operations:
- Stacking of a number of registers.
- Fetching the exception vector.
- Fetching the instructions in parallel to the stacking operation to reduce latency.
- Fetching the instructions for the exception handler to be executed.
Exception Handling in Detail
You can program your exception handlers or ISR as normal C routines/functions.
R0-R3, R12, LR, and PSR are called "caller saved registers". The program code that calls a subroutine needs to save these register contents into memory before the function call if these values will still be needed after the function call. Register values that are not required after the function call don't have to be saved.
R4-R11 are called "callee-saved registers". The subroutine or function being called needs to make sure the contents of these registers are unaltered at the end of the function. The values of these registers could change in the middle of the function execution, but need to be restored to their original values before function exit.
In order to allow a C function to be used as an exception handler, the exception mechanism needs to save R0-R3, R12, LR, and PSR at exception entrance automatically, and restore them at exception exit under the control of the processor's hardware.
So in total eight registers need to be saved during the
exception handling sequence. For the Cortex-M4 processor with floating-point unit, the exception mechanism also needs to save S0-S15 and FPSCR if the floating-point unit is used. This is indicated by a bit in the CONTROL register called FPCA.
As the processor enters the execution handler or Interrupt Service Routine (ISR), the value of the LR is updated to code called EXC_RETURN. The value of this code is used to trigger the exception return mechanism when it is loaded into the PC.
When an exception occurs and is accepted by the processor, the stacking sequence starts to push the registers into the stack, forming the stack frame.
The term 'interrupt latency' refers to the delay from the start of the interrupt request to the start of the interrupt handler execution.
Low Power and System Control Features
Today we see many low power Cortex-M microcontrollers with very sophisticated system features that enable longer battery life. For example:
- Various run modes and sleep modes are available
- Ultra low power Real-Time Clock (RTC), watchdog and Brown-Out Detector (BOD)
- Smart peripherals that can operate while the processor remains in sleep mode
- Flexible clock system control features to allow clock signals for inactive parts of the design to be turned off
The processor support two sleep modes: Sleep and Deep Sleep. The Cortex-M processor provides a register called the System Control Register (SCR) to allow you to select between the Sleep mode and Deep Sleep mode.
The processor provides two instructions for entering sleep modes: WFI and WFE.
WFE can be woken up by any events. This event register can be set by:
- Exception entrance and exit
- When SEV-On-Pend feature is enabled
- An External Event Signal (RXEV) from on-ship hardware
- Execution of the SEV (Send Event) instruction
- Debug event
The Sleep-on-Exit feature is very useful for interrupt driven applications where all operations are carried out inside the interrupt handler. When enabled, the Cortex-M processor automatically enters sleep mode when exiting from an exception handler and returning to thread mode.
The Cortex-M processors have a small integrated timer called the SysTick timer. It is integrated as a part of the NVIC and can generate the SysTick exception. The SysTick timer is a simple decrement 24-bit timer, and can run on processor clock frequency or from a reference clock frequency.
OS Support Features
A number of features are implemented in the architecture to make OS implementation easier and more efficient. For example:
- Shadowed stack pointer: Two stack pointers are available. The MSP is used for the OS kernel and interrupt handlers. The PSP is used by application tasks.
- SysTick timer: A simple timer included inside the processor. This enables an embedded OS to be used on the wide range of Cortex-M microcontrollers available.
- SVC and PendSV exceptions: These two exception types are essential for the operations of embedded OSs such as the implementation of context switching.
- Unprivileged execution level: This allows a basic security model that restricts the access rights of some application tasks.
- Exclusive accesses: The exclusive load and store instructions are useful for semaphore and mutual exclusive operations in the OS.
MSP is the default stack pointer. It is used in the thread mode when the CONTROL bit1 is 0, and it is always used in handler mode.
PSP is used in thread mode when the CONTROL bit 1 is set to 1.
Stack operations like PUSH and POP instructions, and most instructions that use SP use the currently selected stack pointer. You can also access the MSP and PSP directly using MRS and MSR instructions. In simple applications without an embedded OS or RTOS, you can just use the MSP for all operations and ignore the PSP.
In system with an embedded OS or RTOS, the exception handlers use the MSP, while application tasks use the PSP. Each application task has its own stack space, and the context-switching code in the OS updates the PSP each time the context is switched.
Typically, to use the process stack, put OS in handler mode, and program PSP directly, then use an exception return sequence to "jump" into the application task.
For example, when an OS first starts in thread mode, it can use the SVC exception to enter the handler mode. Then it can create a stack frame in the process stack, and trigger an exception return that uses the PSP. When the stack frame is loaded, the application is started.
In OS designs, we need to switch between different tasks. This is typically called context-switching. Context-switching is usually carried out in the PendSV exception handler, which can triggered by the periodic SysTick exception. Inside the context-switching operation, we need to:
- Save the current status of the registers in the current task
- Save the current PSP value
- Set the PSP to the last SP value for the next task
- Restore the last values for the next tasks
- Use exception return to switch to the task
The SVC exception is generated using the SVC instruction. An immediate value is required for this instruction, which works as a parameter-passing method. The SVC exception handler can then extract the parameter and determine what action it needs to perform. For example:
SVC #0x3 ; Call SVC function 3
The PendSV exception is triggered by setting its pending status by writing to the Interrupt Control and State Register (ICSR). Unlike the SVC exception, it is not precise. So its pending status can be set inside a higher priority exception handler and executed when the higher-priority handler finishes. Using this case, we can schedule the PendSV exception handler to be executed after all other interrupt processing tasks are done, by making sure that the PendSV has the lowest exception priority level. This is very useful for a context switching operation, which is key operation in various OS designs.
Below shows an example sequence of context-switching using PendSV with the following event sequence:
- Task A calls SVC for task switching.
- The OS receives the request, prepares for context switching, and pends the PendSV exception.
- When the CPU exits SVC, it enters PendSV immediately and does the context switch.
- When PendSV finishes and returns to thread level, it executes Task B.
- An interrupt occurs and interrupt handler is entered.
- While running the interrupt handler routine, a SysTick exception takes place.
- The OS carries out the essential operation, then pends the PendSV exception and gets ready for the context switch.
- When the SysTick exception exits, it returns to the interrupt service routine.
- When the interrupt service routine completes, the PendSV starts and does the actual context-switch operations.
- When PendSV is complete, the program returns to thread level; this time it returns to task A and continues the processing.
In a multi-tasking system, it is common that a number of tasks need to share a limited number of resources. This feature is usually called a semaphore. For the case when only one resource is available, it is also called mutual exclusive, which is one type of semaphore.
The processor supports a feature called exclusive access. The semaphore variable are read and written using exclusive load and exclusive store.
Fault Exceptions and Fault Handling
By default, all the faults trigger the HardFault exception. Cortex-M3/4 processors have three additional configurable fault exception handlers:
- MemManageFault
- BasFault
- UsageFault
These exceptions are triggered if they are enabled, and if their priority is higher than the current exception priority level. These exceptions are called 'configurable fault' exceptions, and have programmable exception priority levels.
The fault handlers can be used in a number of ways:
- Shut down the system safely
- Inform users or other systems that it encountered a problem
- Carry out self-reset
- In the case of multi-tasking systems, the offending tasks could be terminated and restarted
- Other remedial actions can be carried out o try to fix the problem if possible
The help detect what type of error was encountered in the fault handler, the Cortex-M3/4 processors also have a number of Fault Status Registers (FSRs). The status bits inside these FSRs indicate the kind of fault detected.
Causes of faults:
- Memory management faults
- Bus faults
- Usage faults
- Hard Faults
Floating-Point Operations
The Floating Point Unit (FPU) in the Cortex-M4 processor is optional and supports single precision floating point
calculations. The FPU in the Cortex-M4 processor is based on an extension of the ARMv7-M architecture called FPv4-SP (Floating Point version 4 - Single Precision). This is a subset of the VFPv4-D16 extension for ARMv-7A and ARMv7-R architecture.
The FPU adds a number of registers to the processor system:
- CPACR (Co-processor Access Control Register) in SCB (System Control Block)
- Floating point register bank
- Floating point Status and Control Register (FPSCR)
Introduction to the Debug and Trace Features
Two types of trace interface operation modes are supported: either a single-pin model called Serial Wire Viewer (SWV) (using a signal called Serial Wire Output (SWO)), or multi-pin Trace Port interface.
The debug and trace features of the Cortex-M processors are designed based on the CoreSight Debug architecture.
Many Cortex-M microcontrollers support both JTAG and SWD protocols.
Another part of the CoreSight architecture concerns trace data handling. In the Cortex-M3/4 processors, there can be three types of trace source:
- Embedded Trace Macrocell (ETM)
- Data Watchpoint and Trace (DWT)
- Instrumentation Trace Macrocell (ITM)
I think that I've noted the all required details to start writing the RTOS kernel. In next post, I will start writing it.
Resources:
- The Definitive Guide to ARM Cortex-M3 and ARM Cortex-M4 Processors, Third Edition, Joseph Yiu, Newnes, 2014






Top comments (0)