Приложение 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 оставляют промежуточное состояние.
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).
Порядок при настройке: группировка → приоритет → разрешение.
Приоритет по умолчанию — 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).
Правильная критическая секция без ядра — с восстановлением прежнего значения, а не безусловным разрешением:
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 Что помнить
READ_BITвозвращает маску,MODIFY_REG— единственный способ сменить поле.- Логический приоритет
p→ регистрp << 4; порядок: группировка, приоритет, разрешение. PRIMASK— всё;BASEPRI— порог и всё ниже;__set_BASEPRI_MAXтолько повышает.- Секция без ядра восстанавливает
PRIMASK, а не включает безусловно. SysTick_Configвозвращает 0 при успехе;SystemCoreClock— то, что записано, а не измерено.- Перед
__WFI()—__DSB(); __ISB(). __REVдля сетевого порядка,__CLZдля старшего бита.__LDREXW/__STREXW— атомарность без запрета прерываний, кроме M0.