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.
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.
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.
#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.
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.