CodeRigor

FREE LESSON

CMSIS-Core: device headers, NVIC & intrinsics

Use Arm CMSIS-Core on the Cortex-M4: device configuration, logical NVIC priorities, nested PRIMASK guards, barriers and saturating intrinsics.

After interrupts and critical sections, you know what the registers mean. CMSIS gives those operations a standard C interface that C++ can use without inventing a second register layout. The learning goal is to connect that interface to the hardware behavior you already understand.

Core, device and peripheral layers

The course pins Arm CMSIS 6.4.0. Include <crucible/mcu/cmsis_device.h> on the Cortex-M4 target. Our device header names the MPS2 AN386 IRQs, declares three implemented priority bits and the fixed 25 MHz clock, and includes Arm's real core_cm4.h. It does not supply an STM32 HAL or make this emulated board pin-compatible with another MCU.

The device header connects portable Core APIs to this board
Arm supplies the core accessors. The device description supplies IRQ numbers and feature choices. Peripheral register maps remain device-specific.
The device header connects portable Core APIs to this board
Arm supplies the core accessors. The device description supplies IRQ numbers and feature choices. Peripheral register maps remain device-specific.

CMSIS is a family of interfaces and components. Core accessors do not start an RTOS. cmsis_os2.h describes the RTOS2 API; a kernel and adapter implement it. CMSIS-DSP/NN are separate math/ML libraries, CMSIS-Driver specifies peripheral interfaces, and CMSIS-DAP concerns debug probes. None is implied by including Core. This runner supports Core and the following RTOS2 lesson; it does not install those other packs.

Startup and the linker script are still necessary. The course already provides the vector table, reset/FPU setup and semihosted newlib. Do not link another startup file with a competing vector table. SystemCoreClock is the current fixed 25 MHz board clock here; SystemCoreClockUpdate() refreshes that value. It is not a PLL configuration routine.

Pass logical priorities to the NVIC

Three implemented bits mean logical priorities 0–7. A smaller IRQ priority number means greater urgency. CMSIS performs the register encoding; pass 6, not 6 << 5, to NVIC_SetPriority. That direction is different from FreeRTOS/CMSIS thread priorities, where a larger value has higher priority.

One logical priority, one encoding step
NVIC_SetPriority already left-aligns the logical value in the implemented byte bits. Shifting before the call applies the encoding twice.
One logical priority, one encoding step
NVIC_SetPriority already left-aligns the logical value in the implemented byte bits. Shifting before the call applies the encoding twice.

Enable and pending state are independent. Disabling an IRQ does not erase its pending bit. Clear the peripheral's asserted source as its manual requires; clearing only the NVIC bit does not silence a still-asserted peripheral. The build helper assumes the peripheral has already been quiesced, then reconfigures only TIMER0 or TIMER1. It rejects invalid arguments before any register write. System exceptions have negative IRQ numbers and need their own documented operations; do not treat them as arbitrary external IRQ lines.

C++
#include <crucible/mcu/cmsis_device.h>
#include <cstdio>
int main() {
    NVIC_DisableIRQ(TIMER0_IRQn);
    NVIC_ClearPendingIRQ(TIMER0_IRQn);
    NVIC_SetPriority(TIMER0_IRQn,6);
    std::printf("logical priority = %lu\n",(unsigned long)NVIC_GetPriority(TIMER0_IRQn));
    std::printf("signed clamp = %ld\n",(long)__SSAT(500,8));
}

Interrupt masks have an owner

A short PRIMASK guard must save before disabling, then restore exactly what it saved. If it always enables on destruction, an inner scope can expose the outer scope's half-updated state. __get_PRIMASK, __disable_irq and __set_PRIMASK express the operations without handwritten inline assembly.

A nested scope cannot release the outer mask
The inner scope saves 1 and must restore 1. Only the outer scope that saved 0 may return the processor to an unmasked state.
A nested scope cannot release the outer mask
The inner scope saves 1 and must restore 1. Only the outer scope that saved 0 may return the processor to an unmasked state.

PRIMASK masks configurable exceptions, including the tick; it does not mask NMI or HardFault. Keep the scope short and nonblocking. It is not a general thread mutex, an inter-core lock, or permission to call RTOS operations with interrupts masked. Under FreeRTOS, use its documented critical-section APIs for kernel work; this lesson's guard is a bare-metal operation.

Intrinsics express distinct processor operations

  • __SSAT(x,8) clamps to [-128,127]; __USAT(x,8) clamps to [0,255]. These Core instruction intrinsics do not require the CMSIS-DSP library.
  • __DMB() orders relevant explicit memory accesses. It does not by itself wait for every access to complete.
  • __DSB() adds completion before subsequent instruction execution, useful when a documented peripheral/processor sequence requires it before sleeping.
  • __ISB() synchronizes the instruction stream after relevant context changes. It neither clears a peripheral interrupt source nor replaces a data barrier.

Follow the processor and peripheral documentation for the actual sequence. A barrier does not make a multi-step update atomic, and volatile does not replace synchronization. The build tests verify nested masks, real pending IRQ delivery, input rejection and saturation extremes. QEMU establishes functional behavior here; hardware latency, DMA/cache effects and board bring-up need measurements on the intended device.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Historical overview of the common core and device layers. This course uses CMSIS 6; use the current reference below for APIs and separately packaged components.