Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
CMSIS Task Scheduler
The same round-robin RTOS from Example 15 — rebuilt using CMSIS (Cortex Microcontroller Software Interface Standard) APIs. Compare raw register manipulation vs CMSIS abstractions and understand the portability benefits.
1. What is CMSIS?
CMSIS (Cortex Microcontroller Software Interface Standard) is an ARM-defined hardware abstraction layer for Cortex-M processors. It provides:
- CMSIS-Core: Peripheral register structs (SCB, NVIC, SysTick), intrinsic functions, and core access functions
- CMSIS-DSP: Optimized DSP/ML algorithms for Cortex-M4 FPU
- CMSIS-RTOS: Standard RTOS API (wrapper over FreeRTOS, Mbed, etc.)
- CMSIS-Driver: Peripheral driver interface (UART, SPI, I2C…)
For this tutorial, we focus on CMSIS-Core which maps to the header file core_cm4.h included via stm32f4xx.h.
| CMSIS Component | Header | Provides |
|---|---|---|
| core_cm4.h | Direct include | SysTick_Type, SCB_Type, NVIC_Type structs; __set_MSP, __get_PSP, etc. |
| stm32f4xx.h | Via CMSIS pack | Includes core_cm4.h + STM32F4-specific peripheral definitions |
| cmsis_gcc.h | Via core_cm4.h | GCC-specific intrinsics: __ISB, __DSB, __NOP, __WFI |
2. CMSIS SysTick Configuration
SysTick_Config(uint32_t ticks) is a CMSIS function that fully configures SysTick with one call. It sets the reload value, enables the interrupt, and enables the counter.
Raw Register Version (Example 15)
void init_systick_timer(uint32_t tick_hz)
{
uint32_t *pSRVR =
(uint32_t*)0xE000E014;
uint32_t *pSCSR =
(uint32_t*)0xE000E010;
uint32_t count =
(SYSTICK_TIM_CLK/tick_hz)-1;
*pSRVR &= ~(0x00FFFFFF);
*pSRVR |= count;
*pSCSR |= (1 << 1); /* TICKINT */
*pSCSR |= (1 << 2); /* CLKSRC */
*pSCSR |= (1 << 0); /* ENABLE */
}
CMSIS Version (Example 16)
void init_systick_timer(uint32_t tick_hz)
{
uint32_t ticks =
(SYSTICK_TIM_CLK/tick_hz)-1;
/* SysTick_Config sets:
SYST_RVR = ticks
SYST_CVR = 0 (clear)
SYST_CSR: TICKINT=1,
CLKSOURCE=1,
ENABLE=1 */
SysTick_Config(ticks);
}
SysTick_Config Implementation (from CMSIS source)
/* CMSIS SysTick_Config — for reference (do not copy verbatim) */
/* Source: ARM CMSIS 5, core_cm4.h */
static inline uint32_t SysTick_Config(uint32_t ticks)
{
/* Reload value must fit in 24 bits (SysTick is 24-bit) */
if ((ticks - 1UL) > SysTick_LOAD_RELOAD_Msk)
return 1UL; /* error: ticks too large */
SysTick->LOAD = ticks - 1UL; /* Set reload value */
/* Set SysTick exception priority to lowest */
NVIC_SetPriority(SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
SysTick->VAL = 0UL; /* Clear current value */
/* Enable: CLKSOURCE=1 (processor clock), TICKINT=1, ENABLE=1 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return 0UL; /* success */
}
============================================================
3. CMSIS SCB Structure — System Control Block
CMSIS defines SCB as a global pointer to a SCB_Type struct at address 0xE000ED00. All SCB registers are accessible as struct fields with descriptive names.
| CMSIS Field | Register Name | Address | Description |
|---|---|---|---|
SCB->CPUID | CPUID | 0xE000ED00 | Processor identification |
SCB->ICSR | ICSR | 0xE000ED04 | Interrupt Control State (PendSV, PendST) |
SCB->VTOR | VTOR | 0xE000ED08 | Vector Table Offset Register |
SCB->AIRCR | AIRCR | 0xE000ED0C | Application Interrupt Reset Control |
SCB->SCR | SCR | 0xE000ED10 | System Control Register (sleep modes) |
SCB->CCR | CCR | 0xE000ED14 | Config & Control (DIV_0_TRP, UNALIGN) |
SCB->SHPR[0-2] | SHPR1-3 | 0xE000ED18 | System Handler Priority Registers |
SCB->SHCSR | SHCSR | 0xE000ED24 | System Handler Control & State |
SCB->CFSR | CFSR | 0xE000ED28 | Configurable Fault Status (UFSR+BFSR+MMFSR) |
SCB->HFSR | HFSR | 0xE000ED2C | HardFault Status Register |
SCB->MMFAR | MMFAR | 0xE000ED34 | MemManage Fault Address Register |
SCB->BFAR | BFAR | 0xE000ED38 | BusFault Address Register |
CMSIS SHCSR Masks
| CMSIS Mask | Value | Meaning |
|---|---|---|
| SCB_SHCSR_MEMFAULTENA_Msk | (1UL << 16) | Enable MemManageFault handler |
| SCB_SHCSR_BUSFAULTENA_Msk | (1UL << 17) | Enable BusFault handler |
| SCB_SHCSR_USGFAULTENA_Msk | (1UL << 18) | Enable UsageFault handler |
| SCB_ICSR_PENDSVSET_Msk | (1UL << 28) | Set PendSV pending |
| SCB_ICSR_PENDSVCLR_Msk | (1UL << 27) | Clear PendSV pending |
| SCB_ICSR_PENDSTSET_Msk | (1UL << 26) | Set SysTick pending |
4. Complete Side-by-Side Comparison
Example 15 — Raw Registers
/* Enable fault handlers */
uint32_t *pSHCSR =
(uint32_t*)0xE000ED24;
*pSHCSR |= (1 << 16);
*pSHCSR |= (1 << 17);
*pSHCSR |= (1 << 18);
Example 16 — CMSIS
/* Enable fault handlers */
SCB->SHCSR |=
SCB_SHCSR_MEMFAULTENA_Msk;
SCB->SHCSR |=
SCB_SHCSR_BUSFAULTENA_Msk;
SCB->SHCSR |=
SCB_SHCSR_USGFAULTENA_Msk;
Set MSP (Raw)
__attribute__((naked))
void init_scheduler_stack(
uint32_t top)
{
__asm volatile(
"MSR MSP, %0" :: "r"(top));
__asm volatile("BX LR");
}
Set MSP (CMSIS)
__attribute__((naked))
void init_scheduler_stack(
uint32_t top)
{
/* __set_MSP is a CMSIS
intrinsic function */
__set_MSP(top);
__asm volatile("BX LR");
}
Trigger PendSV (Raw)
void schedule(void)
{
uint32_t *pICSR =
(uint32_t*)0xE000ED04;
*pICSR |= (1 << 28);
}
Trigger PendSV (CMSIS)
void schedule(void)
{
SCB->ICSR |=
SCB_ICSR_PENDSVSET_Msk;
}
5. CMSIS Core Intrinsic Functions
CMSIS-Core provides inline assembly wrappers for common Cortex-M core operations. These compile to the exact same instructions as hand-written assembly, but are portable and self-documenting.
| CMSIS Function | Assembly Equivalent | Purpose |
|---|---|---|
__get_MSP() | MRS Rn, MSP | Read Main Stack Pointer |
__set_MSP(val) | MSR MSP, Rn | Write Main Stack Pointer |
__get_PSP() | MRS Rn, PSP | Read Process Stack Pointer |
__set_PSP(val) | MSR PSP, Rn | Write Process Stack Pointer |
__get_CONTROL() | MRS Rn, CONTROL | Read CONTROL register |
__set_CONTROL(val) | MSR CONTROL, Rn | Write CONTROL register |
__get_PRIMASK() | MRS Rn, PRIMASK | Read PRIMASK |
__set_PRIMASK(val) | MSR PRIMASK, Rn | Write PRIMASK |
__disable_irq() | CPSID I | Set PRIMASK=1 (disable IRQs) |
__enable_irq() | CPSIE I | Clear PRIMASK=0 (enable IRQs) |
__ISB() | ISB | Instruction Synchronization Barrier |
__DSB() | DSB | Data Synchronization Barrier |
__DMB() | DMB | Data Memory Barrier |
__NOP() | NOP | No Operation (timing/alignment) |
__WFI() | WFI | Wait For Interrupt (low power) |
__WFE() | WFE | Wait For Event |
__SEV() | SEV | Send Event |
__REV(val) | REV Rn, Rm | Byte-reverse word (endian swap) |
__RBIT(val) | RBIT Rn, Rm | Bit-reverse word |
__CLZ(val) | CLZ Rn, Rm | Count Leading Zeros |
6. Complete CMSIS Scheduler
/* ================================================================
* CMSIS TASK SCHEDULER
* Same architecture as Example 15 but using CMSIS abstractions
* ================================================================ */
#include <stdio.h>
#include "main.h"
#include "stm32f4xx.h" /* Includes core_cm4.h → CMSIS definitions */
typedef struct {
uint32_t psp_value;
uint32_t block_count;
uint8_t current_state;
void (*task_handler)(void);
} TCB_t;
TCB_t user_tasks[MAX_TASKS];
uint8_t current_task = 1;
uint32_t g_tick_count = 0;
int main(void)
{
/* ── 1. Enable configurable fault handlers via CMSIS ── */
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk;
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
/* ── 2. Move MSP to scheduler stack area ── */
init_scheduler_stack(SCHED_STACK_START);
/* ── 3. Build fake context frames for all tasks ── */
init_tasks_stack();
/* ── 4. Init LEDs ── */
led_init_all();
/* ── 5. Configure SysTick at 1kHz using CMSIS ── */
init_systick_timer(TICK_HZ);
/* ── 6. Switch to PSP for Thread mode ── */
switch_sp_to_psp();
/* ── 7. Launch Task1 ── */
task1_handler();
for(;;);
}
/* ── CMSIS SysTick setup ── */
void init_systick_timer(uint32_t tick_hz)
{
uint32_t ticks = (SYSTICK_TIM_CLK / tick_hz) - 1;
SysTick_Config(ticks); /* CMSIS: sets RVR, clears CVR, enables all */
}
/* ── CMSIS __set_MSP instead of inline asm ── */
__attribute__((naked)) void init_scheduler_stack(uint32_t top)
{
__set_MSP(top); /* CMSIS intrinsic: MSR MSP, R0 */
__asm volatile("BX LR");
}
/* ── Enable processor faults using CMSIS SCB struct ── */
void enable_processor_faults(void)
{
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk; /* bit 16 */
SCB->SHCSR |= SCB_SHCSR_BUSFAULTENA_Msk; /* bit 17 */
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk; /* bit 18 */
}
/* ── Pend PendSV using CMSIS SCB->ICSR ── */
void schedule(void)
{
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; /* bit 28 */
}
/* ── SysTick Handler: tick + unblock + pend PendSV via CMSIS ── */
void SysTick_Handler(void)
{
update_global_tick_count();
unblock_tasks();
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; /* trigger context switch */
}
/* ── PendSV: identical to Example 15 (no CMSIS equiv for STMDB/LDMIA) ── */
__attribute__((naked)) void PendSV_Handler(void)
{
__asm volatile("MRS R0, PSP");
__asm volatile("STMDB R0!, {R4-R11}");
__asm volatile("PUSH {LR}");
__asm volatile("BL save_psp_value");
__asm volatile("BL update_next_task");
__asm volatile("BL get_psp_value");
__asm volatile("LDMIA R0!, {R4-R11}");
__asm volatile("MSR PSP, R0");
__asm volatile("POP {LR}");
__asm volatile("BX LR");
}
/* ── Task handlers use task_delay (blocking) instead of busy-wait ── */
void task1_handler(void)
{
while(1) {
led_on(LED_GREEN);
task_delay(1000); /* block for 1000ms */
led_off(LED_GREEN);
task_delay(1000);
}
}
void task2_handler(void)
{
while(1) {
led_on(LED_ORANGE);
task_delay(500); /* block for 500ms */
led_off(LED_ORANGE);
task_delay(500);
}
}
/* ── task_delay uses CMSIS __disable_irq / __enable_irq ── */
void task_delay(uint32_t ticks)
{
__disable_irq(); /* CPSID I = PRIMASK=1 */
if (current_task != 0) {
user_tasks[current_task].block_count =
g_tick_count + ticks;
user_tasks[current_task].current_state = TASK_BLOCKED_STATE;
schedule();
}
__enable_irq(); /* CPSIE I = PRIMASK=0 */
}
============================================================
7. CMSIS SysTick_Type Struct — Behind the Scenes
/* CMSIS SysTick_Type definition (from core_cm4.h — ARM copyright) */
/* Structure at base address 0xE000E010 */
typedef struct
{
volatile uint32_t CTRL; /* 0xE000E010: Control and Status Register */
volatile uint32_t LOAD; /* 0xE000E014: Reload Value Register */
volatile uint32_t VAL; /* 0xE000E018: Current Value Register */
volatile uint32_t CALIB; /* 0xE000E01C: Calibration Register (RO) */
} SysTick_Type;
#define SysTick ((SysTick_Type *)0xE000E010)
/* Bit masks for SysTick->CTRL */
#define SysTick_CTRL_ENABLE_Msk (1UL << 0) /* Counter enable */
#define SysTick_CTRL_TICKINT_Msk (1UL << 1) /* Enable exception */
#define SysTick_CTRL_CLKSOURCE_Msk (1UL << 2) /* Clock: 1=processor */
#define SysTick_CTRL_COUNTFLAG_Msk (1UL << 16) /* Count reached zero */
#define SysTick_LOAD_RELOAD_Msk (0xFFFFFF) /* 24-bit mask */
============================================================
8. NVIC Functions in CMSIS
| CMSIS Function | Raw Equivalent | Purpose |
|---|---|---|
NVIC_EnableIRQ(IRQn) | ISER[n/32] |= (1<<(n%32)) | Enable IRQ N |
NVIC_DisableIRQ(IRQn) | ICER[n/32] |= (1<<(n%32)) | Disable IRQ N |
NVIC_SetPendingIRQ(IRQn) | ISPR[n/32] |= (1<<(n%32)) | Set IRQ pending |
NVIC_ClearPendingIRQ(IRQn) | ICPR[n/32] |= (1<<(n%32)) | Clear pending |
NVIC_GetPendingIRQ(IRQn) | ISPR[n/32] & (1<<(n%32)) | Read pending status |
NVIC_SetPriority(IRQn, pri) | IPR[n] = pri << (8-PRIOBITS) | Set priority |
NVIC_GetPriority(IRQn) | IPR[n] >> (8-PRIOBITS) | Read priority |
NVIC_SystemReset() | SCB->AIRCR = (0x05FA<<16)|4 | Trigger system reset |
9. Interview Questions & Answers
Q1. What is CMSIS and why was it created?
Q2. What does SysTick_Config() do internally?
Q3. What is the difference between __disable_irq() and PRIMASK-based critical sections?
__disable_irq() compiles to CPSID I which sets PRIMASK=1. The manual PRIMASK approach uses MSR PRIMASK, R0 with R0=1. Both block all configurable interrupts (everything except NMI and HardFault). The CMSIS version is preferred because it’s more readable and portable — CPSID/CPSIE are actually shorter Thumb-2 instructions than the MSR approach and don’t require a scratch register.Q4. Why is PendSV_Handler still written in assembly even with CMSIS?
naked (no compiler prologue/epilogue), and the register-level PSP manipulation (MRS/MSR) must be done in a specific sequence. CMSIS intrinsics exist for __get_PSP/__set_PSP, but integrating them in a naked function body requires careful handling — many RTOS implementations keep PendSV entirely in assembly for reliability.Q5. What header files are needed to use SCB, NVIC, and SysTick with CMSIS on STM32F4?
#include "stm32f4xx.h" is needed. This header includes: stm32f407xx.h (device-specific peripherals) → core_cm4.h (CMSIS core for Cortex-M4) → cmsis_gcc.h (GCC-specific intrinsics). The full chain is provided by the STM32CubeF4 HAL package or the CMSIS pack for STM32F4. In Eclipse/CubeIDE projects, these are automatically included via the project include paths.Q6. How does using CMSIS improve code maintainability over raw register access?
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk clearly states what is being done. Raw *(uint32_t*)0xE000ED24 |= (1<<16) requires looking up the address and bit meaning every time.2) Type safety: CMSIS structs are typed — the compiler catches wrong-sized accesses.
3) Portability: Moving to STM32L4 (also Cortex-M4) requires only changing the device header, not every register address.
4) IntelliSense/autocomplete: IDEs can navigate CMSIS struct fields with autocomplete.
Q7. What is the SCB_ICSR_PENDSVSET_Msk value and what does it do?
Q8. Can CMSIS intrinsic functions be used in interrupt handlers?
Q9. What is the performance cost of CMSIS compared to raw register access?
static inline or expand to single instructions. SCB->ICSR compiles to a single LDR/STR instruction to address 0xE000ED04 — identical to the raw pointer approach. __disable_irq() compiles to a single CPSID I instruction. The CMSIS abstraction is purely at the source level — no runtime overhead.Q10. What major difference exists between Example 15 (raw) and Example 16 (CMSIS) task behaviors?
delay(DELAY_COUNT_1S)) while Example 16 uses blocking delays (task_delay(1000)). Busy-wait wastes CPU cycles spinning in a loop. task_delay puts the task into BLOCKED state immediately and schedules another task — idle CPU time goes to other tasks or the idle task. The CMSIS version is therefore more efficient. The context switching mechanism (PendSV, STMDB/LDMIA) is identical in both examples.STM32F407 Deep Dive Series — Example 16: CMSIS Task Scheduler
CMSIS 5 Documentation · ARM Cortex-M4 TRM · STM32F4 Reference Manual
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments