Embedded HandbookApps
All sections › Appendices
STM32F103, STM32F407 · level reference · 8 min read
Open in the interactive handbook

Appendix B. CMSIS cheat sheet

Everything from CMSIS that you need when you work with registers directly, on one page. This is not a peripheral library: CMSIS gives you access to the Cortex‑M core (interrupts, the system timer, masks, barriers, bit operations) and register access macros. You configure the peripherals yourself, with registers from the family header (appendix A).

Every name here is called in a test file and built for Cortex‑M3.


B.1 Register access

Macros from the family header (stm32f10x.h, stm32f4xx.h). They hide nothing: SET_BIT, CLEAR_BIT and MODIFY_REG expand into one read-modify-write operation, the others into one read or one write.

Macro What it does Equivalent
SET_BIT(REG, BIT) set REG \|= BIT
CLEAR_BIT(REG, BIT) clear REG &= ~BIT
READ_BIT(REG, BIT) read; not 0/1, but a mask REG & BIT
CLEAR_REG(REG) zero the register REG = 0
WRITE_REG(REG, VAL) write the whole register REG = VAL
READ_REG(REG) read the whole register REG
MODIFY_REG(REG, CLR, SET) clear a mask, set bits REG = (REG & ~CLR) \| SET

⚠️ READ_BIT returns a mask, not a boolean value. READ_BIT(x, BIT5) == 1 is true only for bit zero (section 8). Compare with zero: READ_BIT(x, BIT5) != 0.

⚠️ Read-modify-write macros are not suitable for flag registers. SET_BIT(EXTI->PR, EXTI_PR_PR0) writes back all the ones it has read, and so clears all the pending lines along the way. CLEAR_BIT(TIM2->SR, TIM_SR_UIF) also writes zero into a flag that got set between the read and the write, and the event is lost. Flags are cleared with a direct write: EXTI->PR = EXTI_PR_PR0, TIM2->SR = ~TIM_SR_UIF (sections 7 and 13).

MODIFY_REG is the only correct way to change a field of several bits: first clear the whole field, then write the value. Two separate SET_BIT/CLEAR_BIT calls leave an intermediate state.

A two-bit field: MODIFY_REG changes it in one step, two separate macros leave the intermediate state 00


B.2 Interrupts: NVIC

Function What it does
NVIC_SetPriorityGrouping(g) where the priority is split (the PRIGROUP field): 7 − g bits go to preemption, but no more than are implemented. On STM32, with g = 0…3 all 4 bits are preemption, as FreeRTOS needs (section 24); with g = 7, none. In HAL the digit in the name is the number of preemption bits: NVIC_PRIORITYGROUP_4 = 3
NVIC_GetPriorityGrouping() current grouping
NVIC_EnableIRQ(irq) / NVIC_DisableIRQ(irq) enable / disable
NVIC_GetEnableIRQ(irq) whether it is enabled
NVIC_SetPendingIRQ(irq) / NVIC_ClearPendingIRQ(irq) set / clear pending
NVIC_GetPendingIRQ(irq) whether it is pending
NVIC_GetActive(irq) whether it is being serviced now
NVIC_SetPriority(irq, p) logical priority: 0 is the highest, 15 the lowest
NVIC_GetPriority(irq) read the logical priority
NVIC_EncodePriority(g, pre, sub) build from preemption priority and subpriority
NVIC_SystemReset() software reset; does not return

The logical priority p goes into the register as p << 4: STM32 has four significant bits out of eight. NVIC_SetPriority(irq, 5) is 0x50 in the register, the very FreeRTOS threshold (section 28).

The PRIGROUP field splits the priority byte: with g from 0 to 3 all four implemented bits go to preemption, with g = 4 three bits go to preemption and one to subpriority, with g = 7 none; example with g = 4: group 2 and subpriority 1 give logical priority 5, 0x50 in the register

The order of configuration: grouping → priority → enable. The default priority is 0, the highest; under a kernel this is a mistake.

irq comes from the IRQn_Type enumeration in the family header: USART1_IRQn, EXTI0_IRQn, TIM2_IRQn… System exceptions are there too, with negative numbers: SysTick_IRQn = −1, PendSV_IRQn = −2, SVCall_IRQn = −5.


B.3 Global masking

Three mask registers, three levels of strictness:

Register Functions Masks
PRIMASK __disable_irq() / __enable_irq(), __get_PRIMASK() / __set_PRIMASK(v) everything except NMI and HardFault
BASEPRI __get_BASEPRI() / __set_BASEPRI(v) / __set_BASEPRI_MAX(v) priorities equal to the threshold and lower (numerically ≥ v); v is the register value, that is, the priority shifted into the upper 4 bits (5 → 0x50); 0 masks nothing
FAULTMASK __get_FAULTMASK() / __set_FAULTMASK(v) everything, including HardFault; only for fault handling

__set_BASEPRI_MAX(v) can only raise the threshold, not lower it: it is safe in nested sections. FreeRTOS sets BASEPRI = 0x50 while it works with its lists (section 28).

The priority byte and the three masks: logical priority 5 in the upper four bits gives 0x50; PRIMASK masks everything except NMI and HardFault, BASEPRI = 0x50 masks priorities 5…15, FAULTMASK also masks HardFault

A correct critical section without a kernel restores the previous value instead of enabling unconditionally:

uint32_t saved = __get_PRIMASK();
__disable_irq();
/* ... */
__set_PRIMASK(saved);       /* а не __enable_irq(): секция могла быть вложенной */

Other core registers: __get_IPSR() is the number of the active exception (0 means thread mode: this is how you check "am I in an interrupt?"); __get_MSP() / __get_PSP() and the matching set functions are the two stack pointers (section 23); __get_CONTROL() / __set_CONTROL(v) are privileges and stack selection.


B.4 System timer

Function What it does
SysTick_Config(ticks) start with a period of ticks clock cycles, interrupt enabled, lowest priority; returns 0 on success
SystemCoreClockUpdate() recalculate SystemCoreClock from the RCC registers
SystemCoreClock variable: the core frequency in hertz, what you wrote, not what you measured

SysTick_Config(SystemCoreClock / 1000) gives a 1 ms tick. The argument is a number of clock cycles, 24 bits wide: at 72 MHz nothing slower than 4 Hz is possible (section 33).

⚠️ SystemCoreClock is correct only if SystemCoreClockUpdate() was called after configuring RCC, or the frequency was set by the standard SystemInit. If you raised the clock by hand and did not update it, every calculation based on it is wrong.


B.5 Sleep and barriers

Function What it does
__WFI() wait for interrupt: Sleep mode until any interrupt
__WFE() wait for event: the same, but also wakes on a SEV event
__SEV() send an event
__NOP() nothing; for code alignment and as a place for a breakpoint. The timing is not guaranteed, the core may drop a NOP from the pipeline; short delays are counted with DWT->CYCCNT or a timer (section 13)
__DSB() data barrier: wait until all memory accesses complete
__ISB() instruction barrier: flush the pipeline
__DMB() memory access ordering barrier

When barriers are mandatory: after writing registers that change the behavior of the core or memory, such as SCB->VTOR, BASEPRI, enabling the MPU, switching the vector table, entering sleep. The typical pair before __WFI(): __DSB(); __ISB();. FreeRTOS calls them itself in critical sections.


B.6 Bit operations

One instruction each, faster than any loop in C.

Function What it does Example
__REV(x) reverse the bytes of a word 0x12345678 → 0x78563412
__REV16(x) bytes in each halfword network order for uint16_t
__REVSH(x) signed halfword
__RBIT(x) reverse the bits 1 → 0x80000000
__CLZ(x) count leading zeros __CLZ(1) = 31; number of the highest set bit = 31 − __CLZ(x)
__ROR(x, n) rotate right
__SSAT(x, n) / __USAT(x, n) saturate to n bits, signed / unsigned no overflow

__REV is what you need for network byte order (section 37) and for protocols with reversed byte order. __CLZ is a fast search for the highest bit: this is exactly how the FreeRTOS priority scheduler finds a ready task.


B.7 Exclusive access

Atomicity on Cortex‑M3 without disabling interrupts:

do {
    v = __LDREXW(&counter);             /* прочитать с меткой */
} while (__STREXW(v + 1, &counter));    /* записать, если никто не вмешался */

__STREXW returns 0 on success and 1 if there was an interrupt or another write between the read and the write; then you retry. This is how an atomic increment (section 30) is done without a critical section. The variants for halfword and byte are __LDREXH/__STREXH, __LDREXB/__STREXB; __CLREX() clears the tag.

Cortex‑M0 does not have these instructions; there you only have masking.


B.8 Bit-banding

These are not CMSIS functions but a property of the Cortex‑M3/M4 address space (section 1): each bit of the first megabyte of SRAM and of the peripherals has a matching word in the alias region.

#define BITBAND_PERI(addr, bit)  (0x42000000u + (((addr) - 0x40000000u) * 32u) + ((bit) * 4u))
#define PC13_ODR  (*(volatile uint32_t *)BITBAND_PERI(0x4001100Cu, 13))

PC13_ODR = 0;      /* атомарная запись одного бита без чтения регистра */

On F1, GPIOC->ODR is at address 0x4001100C (the port C base is 0x40011000), and its bit 13 is address 0x422201B4, as in section 1. It is safer not to type the constant by hand but to take it from the header, (uint32_t)&GPIOC->ODR: 0x40010C0C is already GPIOB->ODR, and such a macro would silently control PB13. A write to the alias is atomic and does not touch neighboring bits, which ODR |= … cannot give you. Cortex‑M0 and M7 do not have this region.


B.9 What to remember

Open in the interactive handbook