What is CMSIS Task Scheduler-Embedded C Programming Training Online

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Example 16 — STM32F407 Discovery

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 ComponentHeaderProvides
core_cm4.hDirect includeSysTick_Type, SCB_Type, NVIC_Type structs; __set_MSP, __get_PSP, etc.
stm32f4xx.hVia CMSIS packIncludes core_cm4.h + STM32F4-specific peripheral definitions
cmsis_gcc.hVia core_cm4.hGCC-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 FieldRegister NameAddressDescription
SCB->CPUIDCPUID0xE000ED00Processor identification
SCB->ICSRICSR0xE000ED04Interrupt Control State (PendSV, PendST)
SCB->VTORVTOR0xE000ED08Vector Table Offset Register
SCB->AIRCRAIRCR0xE000ED0CApplication Interrupt Reset Control
SCB->SCRSCR0xE000ED10System Control Register (sleep modes)
SCB->CCRCCR0xE000ED14Config & Control (DIV_0_TRP, UNALIGN)
SCB->SHPR[0-2]SHPR1-30xE000ED18System Handler Priority Registers
SCB->SHCSRSHCSR0xE000ED24System Handler Control & State
SCB->CFSRCFSR0xE000ED28Configurable Fault Status (UFSR+BFSR+MMFSR)
SCB->HFSRHFSR0xE000ED2CHardFault Status Register
SCB->MMFARMMFAR0xE000ED34MemManage Fault Address Register
SCB->BFARBFAR0xE000ED38BusFault Address Register

CMSIS SHCSR Masks

CMSIS MaskValueMeaning
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 FunctionAssembly EquivalentPurpose
__get_MSP()MRS Rn, MSPRead Main Stack Pointer
__set_MSP(val)MSR MSP, RnWrite Main Stack Pointer
__get_PSP()MRS Rn, PSPRead Process Stack Pointer
__set_PSP(val)MSR PSP, RnWrite Process Stack Pointer
__get_CONTROL()MRS Rn, CONTROLRead CONTROL register
__set_CONTROL(val)MSR CONTROL, RnWrite CONTROL register
__get_PRIMASK()MRS Rn, PRIMASKRead PRIMASK
__set_PRIMASK(val)MSR PRIMASK, RnWrite PRIMASK
__disable_irq()CPSID ISet PRIMASK=1 (disable IRQs)
__enable_irq()CPSIE IClear PRIMASK=0 (enable IRQs)
__ISB()ISBInstruction Synchronization Barrier
__DSB()DSBData Synchronization Barrier
__DMB()DMBData Memory Barrier
__NOP()NOPNo Operation (timing/alignment)
__WFI()WFIWait For Interrupt (low power)
__WFE()WFEWait For Event
__SEV()SEVSend Event
__REV(val)REV Rn, RmByte-reverse word (endian swap)
__RBIT(val)RBIT Rn, RmBit-reverse word
__CLZ(val)CLZ Rn, RmCount 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 FunctionRaw EquivalentPurpose
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)|4Trigger system reset
============================================================

9. Interview Questions & Answers

Q1. What is CMSIS and why was it created?
CMSIS (Cortex Microcontroller Software Interface Standard) is an ARM-defined abstraction layer created to solve the portability problem in embedded software. Before CMSIS, every silicon vendor defined their own register access methods, making code non-portable. CMSIS standardizes: peripheral struct definitions (SCB, NVIC, SysTick), intrinsic functions (__set_MSP, __disable_irq), and RTOS interfaces. Code written with CMSIS APIs compiles on any Cortex-M device from any vendor with minimal changes.
Q2. What does SysTick_Config() do internally?
SysTick_Config(ticks) performs four operations: (1) Validates that ticks fits in 24 bits (SysTick is a 24-bit counter), returning error code 1 if too large. (2) Writes (ticks-1) to SysTick->LOAD (the reload value register). (3) Clears SysTick->VAL to reset the current count. (4) Writes to SysTick->CTRL enabling the counter (bit0), enabling the interrupt (bit1), and selecting the processor clock (bit2). Returns 0 on success.
Q3. What is the difference between __disable_irq() and PRIMASK-based critical sections?
They are functionally identical. __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?
CMSIS does not provide abstractions for STMDB/LDMIA multi-register load-store instructions. These are critical for efficiently saving/restoring R4-R11 in a single instruction. Also, PendSV must be declared 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?
Only #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?
1) Self-documenting: 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?
SCB_ICSR_PENDSVSET_Msk = (1UL << 28). Writing this to SCB->ICSR sets the PendSV exception to “pending” state. PendSV will then execute after all higher-priority exceptions complete. In the scheduler, this is how SysTick triggers a context switch without performing it inline — it just sets the pending bit, and PendSV fires after SysTick returns.
Q8. Can CMSIS intrinsic functions be used in interrupt handlers?
Yes, all CMSIS intrinsic functions are valid in both Thread mode and Handler mode. __get_PSP, __set_PSP, __get_MSP, __set_MSP, __get_PRIMASK, __set_PRIMASK — all work in any context. The exception is CONTROL register modifications: CONTROL.SPSEL can only be written in Thread mode, and CONTROL.nPRIV can only be written from privileged code. __set_CONTROL in Handler mode only affects FPCA (bit2) and is otherwise ignored for bits[1:0].
Q9. What is the performance cost of CMSIS compared to raw register access?
Zero additional cost. CMSIS functions and struct accesses are all marked 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?
Example 15 uses busy-wait delays (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

Leave a Reply

Your email address will not be published. Required fields are marked *