Embedded HandbookПриложения
Все разделы › Приложения
STM32F103, STM32F407 · уровень справочник · 8 мин чтения
Открыть в интерактивном справочнике

Приложение B. Шпаргалка CMSIS

Всё из CMSIS, что нужно при работе с регистрами напрямую, — на одной странице. Это не библиотека периферии: CMSIS даёт доступ к ядру Cortex‑M (прерывания, системный таймер, маски, барьеры, битовые операции) и макросы доступа к регистрам. Периферию вы настраиваете сами регистрами из заголовка семейства (приложение A).

Каждое имя отсюда вызвано в проверочном файле и собрано под Cortex‑M3.


B.1 Доступ к регистрам

Макросы из заголовка семейства (stm32f10x.h, stm32f4xx.h). Ничего не скрывают: SET_BIT, CLEAR_BIT и MODIFY_REG раскрываются в одну операцию чтения-изменения-записи, остальные — в одно чтение или одну запись.

Макрос Что делает Эквивалент
SET_BIT(REG, BIT) установить REG \|= BIT
CLEAR_BIT(REG, BIT) сбросить REG &= ~BIT
READ_BIT(REG, BIT) прочитать; не 0/1, а маска REG & BIT
CLEAR_REG(REG) обнулить REG = 0
WRITE_REG(REG, VAL) записать целиком REG = VAL
READ_REG(REG) прочитать целиком REG
MODIFY_REG(REG, CLR, SET) сбросить маску, установить биты REG = (REG & ~CLR) \| SET

⚠️ READ_BIT возвращает маску, а не логическое значение. READ_BIT(x, BIT5) == 1 истинно только для нулевого бита (раздел 8). Сравнивать с нулём: READ_BIT(x, BIT5) != 0.

⚠️ Для регистров флагов макросы чтения-изменения-записи не годятся. SET_BIT(EXTI->PR, EXTI_PR_PR0) записывает обратно все прочитанные единицы — и сбрасывает заодно все ожидающие линии. CLEAR_BIT(TIM2->SR, TIM_SR_UIF) пишет ноль и во флаг, который взвёлся между чтением и записью, — событие теряется. Флаги сбрасывают прямой записью: EXTI->PR = EXTI_PR_PR0, TIM2->SR = ~TIM_SR_UIF (разделы 7 и 13).

MODIFY_REG — единственный правильный способ сменить поле из нескольких бит: сначала очистить всё поле, потом записать значение. Два отдельных SET_BIT/CLEAR_BIT оставляют промежуточное состояние.

Поле из двух бит: MODIFY_REG меняет его за один шаг, два отдельных макроса оставляют промежуточное состояние 00


B.2 Прерывания: NVIC

Функция Что делает
NVIC_SetPriorityGrouping(g) где делится приоритет (поле PRIGROUP): на вытеснение идёт 7 − g бит, но не больше реализованных. У STM32 при g = 0…3 все 4 бита — вытеснение, как и нужно FreeRTOS (раздел 24), при g = 7 — ни одного. У HAL цифра в имени — число бит вытеснения: NVIC_PRIORITYGROUP_4 = 3
NVIC_GetPriorityGrouping() текущая группировка
NVIC_EnableIRQ(irq) / NVIC_DisableIRQ(irq) разрешить / запретить
NVIC_GetEnableIRQ(irq) разрешено ли
NVIC_SetPendingIRQ(irq) / NVIC_ClearPendingIRQ(irq) взвести / снять ожидание
NVIC_GetPendingIRQ(irq) ждёт ли
NVIC_GetActive(irq) обслуживается ли сейчас
NVIC_SetPriority(irq, p) приоритет логический: 0 — высший, 15 — низший
NVIC_GetPriority(irq) прочитать логический
NVIC_EncodePriority(g, pre, sub) собрать из вытеснения и подприоритета
NVIC_SystemReset() программный сброс; не возвращается

Логический приоритет p попадает в регистр как p << 4: у STM32 четыре значащих бита из восьми. NVIC_SetPriority(irq, 5) — это 0x50 в регистре, тот самый порог FreeRTOS (раздел 28).

Поле PRIGROUP делит байт приоритета: при g от 0 до 3 все четыре реализованных бита идут на вытеснение, при g = 4 — три бита на вытеснение и один на подприоритет, при g = 7 — ни одного; пример при g = 4: группа 2 и подприоритет 1 дают логический приоритет 5, в регистре 0x50

Порядок при настройке: группировка → приоритет → разрешение. Приоритет по умолчанию — 0, высший; под ядром это ошибка.

irq — из перечисления IRQn_Type заголовка семейства: USART1_IRQn, EXTI0_IRQn, TIM2_IRQn… Системные исключения там же с отрицательными номерами: SysTick_IRQn = −1, PendSV_IRQn = −2, SVCall_IRQn = −5.


B.3 Глобальное маскирование

Три регистра масок — три степени строгости:

Регистр Функции Маскирует
PRIMASK __disable_irq() / __enable_irq(), __get_PRIMASK() / __set_PRIMASK(v) всё, кроме NMI и HardFault
BASEPRI __get_BASEPRI() / __set_BASEPRI(v) / __set_BASEPRI_MAX(v) приоритеты, равные порогу и ниже (числом ≥ v); v — значение регистра, то есть приоритет, сдвинутый в старшие 4 бита (5 → 0x50); 0 — не маскирует
FAULTMASK __get_FAULTMASK() / __set_FAULTMASK(v) всё, включая HardFault; только для обработки отказов

__set_BASEPRI_MAX(v) — только повысить порог, не понизить: безопасна во вложенных секциях. FreeRTOS ставит BASEPRI = 0x50 на время работы со своими списками (раздел 28).

Байт приоритета и три маски: логический приоритет 5 в старших четырёх битах даёт 0x50; PRIMASK маскирует всё, кроме NMI и HardFault, BASEPRI = 0x50 — приоритеты 5…15, FAULTMASK — ещё и HardFault

Правильная критическая секция без ядра — с восстановлением прежнего значения, а не безусловным разрешением:

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

Прочие регистры ядра: __get_IPSR() — номер активного исключения (0 — режим потока: так проверяют, «я в прерывании?»); __get_MSP() / __get_PSP() и парные set — два указателя стека (раздел 23); __get_CONTROL() / __set_CONTROL(v) — привилегии и выбор стека.


B.4 Системный таймер

Функция Что делает
SysTick_Config(ticks) запустить с периодом ticks тактов, прерывание разрешено, приоритет — низший; возвращает 0 при успехе
SystemCoreClockUpdate() пересчитать SystemCoreClock по регистрам RCC
SystemCoreClock переменная: частота ядра в герцах — то, что вы записали, а не измерили

SysTick_Config(SystemCoreClock / 1000) — тик 1 мс. Аргумент — число тактов, 24-разрядный: при 72 МГц медленнее 4 Гц не бывает (раздел 33).

⚠️ SystemCoreClock верна, только если вызвана SystemCoreClockUpdate() после настройки RCC либо частота установлена штатным SystemInit. Разогнали вручную и не обновили — все расчёты от неё врут.


B.5 Сон и барьеры

Функция Что делает
__WFI() ждать прерывания: режим Sleep до любого прерывания
__WFE() ждать события: то же, но и по событию SEV
__SEV() послать событие
__NOP() ничего; для выравнивания кода и как место для точки останова. Время не гарантировано — ядро может выбросить NOP из конвейера; короткие задержки отсчитывают по DWT->CYCCNT или таймеру (раздел 13)
__DSB() барьер данных: дождаться завершения всех обращений к памяти
__ISB() барьер инструкций: сбросить конвейер
__DMB() барьер порядка обращений к памяти

Когда барьеры обязательны: после записи в регистры, меняющие поведение ядра или памяти, — SCB->VTOR, BASEPRI, включение MPU, смена таблицы векторов, переход в сон. Типовая пара перед __WFI(): __DSB(); __ISB();. FreeRTOS вызывает их сама в критических секциях.


B.6 Битовые операции

Одна инструкция каждая — быстрее любого цикла на C.

Функция Что делает Пример
__REV(x) перевернуть байты слова 0x12345678 → 0x78563412
__REV16(x) байты в каждом полуслове сетевой порядок для uint16_t
__REVSH(x) полуслово со знаком
__RBIT(x) перевернуть биты 1 → 0x80000000
__CLZ(x) ведущих нулей __CLZ(1) = 31; номер старшего бита = 31 − __CLZ(x)
__ROR(x, n) циклический сдвиг вправо
__SSAT(x, n) / __USAT(x, n) насыщение до n бит со знаком / без без переполнения

__REV — то, что нужно для сетевого порядка байт (раздел 37) и для протоколов с обратным порядком. __CLZ — быстрый поиск старшего бита: приоритетный планировщик FreeRTOS находит готовую задачу именно так.


B.7 Исключительный доступ

Атомарность на Cortex‑M3 без запрета прерываний:

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

__STREXW возвращает 0 при успехе и 1, если между чтением и записью было прерывание или другая запись, — тогда повторяем. Так делают атомарный инкремент (раздел 30) без критической секции. Варианты для полуслова и байта — __LDREXH/__STREXH, __LDREXB/__STREXB; __CLREX() снимает метку.

На Cortex‑M0 этих инструкций нет — там только маскирование.


B.8 Bit-banding

Не функции CMSIS, а свойство адресного пространства Cortex‑M3/M4 (раздел 1): каждому биту первых мегабайта SRAM и периферии соответствует слово в области псевдонимов.

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

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

GPIOC->ODR у F1 лежит по адресу 0x4001100C (база порта C — 0x40011000), и его бит 13 — адрес 0x422201B4, как в разделе 1. Константу надёжнее не набирать руками, а брать из заголовка — (uint32_t)&GPIOC->ODR: 0x40010C0C — это уже GPIOB->ODR, и такой макрос молча управлял бы PB13. Запись в псевдоним атомарна и не трогает соседние биты — то, чего ODR |= … не даёт. На Cortex‑M0 и M7 области нет.


B.9 Что помнить

Открыть в интерактивном справочнике