Почему STM32F3, G4 или L4 падает в HardFault на операции с float?
Обновлено Редакция GAW
У STM32F3, STM32G4 и STM32L4 ядро Cortex-M4 с блоком плавающей точки (FPU), но после сброса он выключен. ARM в руководстве по Cortex-M4 пишет прямо: FPU нужно включить, прежде чем выполнить хотя бы одну команду с плавающей точкой. Включают его в системном регистре CPACR.
| Поле CPACR | Биты | Значение после сброса | Нужно |
|---|---|---|---|
| CP10 | 21:20 | 00 — доступ запрещён | 11 — полный доступ |
| CP11 | 23:22 | 00 — доступ запрещён | 11 — полный доступ |
Любая команда FPU при запрещённом доступе вызывает отказ UsageFault с флагом NOCP. Обработчик UsageFault после сброса не включён, поэтому отказ становится HardFault.
Кто обычно включает FPU
Стандартный SystemInit из пакета CMSIS ST делает это сам — но под условием:
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << (10*2)) | (3UL << (11*2)));
#endif
__FPU_USED становится единицей, только если компилятор генерирует команды FPU: для GCC заголовок CMSIS проверяет макрос __VFP_FP__ без __SOFTFP__, как при -mfloat-abi=hard. Отсюда типичные причины падения:
- Свой стартовый код без вызова
SystemInit. - Запуск без CMSIS: голый
mainна регистрах, где FPU никто не включил. - Свой загрузчик, который передаёт управление приложению, а в приложении
SystemInitубран из стартового кода.
Как включить вручную
По примеру ARM: записать 1 в биты 20–23 CPACR, затем выполнить барьеры, чтобы следующие команды увидели включённый блок:
SCB->CPACR |= (0xFUL << 20); /* CP10 and CP11: full access */
__DSB();
__ISB();
Делают это как можно раньше, до первого обращения к float, и в привилегированном режиме. Разница между регистрами, LL и HAL — в статье «HAL, LL и регистры», ядра Cortex-M и их FPU — в разделе «ARM Cortex-M», регистры семейства — на странице STM32G4.
Похожие вопросы
Почему HardFault, а не UsageFault?
По ARM отказ, обработчик которого не включён, превращается в HardFault. Обработчики UsageFault, BusFault и MemManage включаются битами регистра SHCSR, и пока бит USGFAULTENA не установлен, NOCP приходит как HardFault с флагом FORCED. Включите UsageFault в SCB->SHCSR — тогда причина будет видна сразу.
Как убедиться, что причина в FPU?
В обработчике HardFault прочитайте SCB->CFSR: бит NOCP находится в UFSR, полуслове по адресу 0xE000ED2A, — в полном регистре это бит 19 (наш пересчёт: 16 + 3). Если он установлен, команда обратилась к выключенному сопроцессору. Проверьте и SCB->CPACR: при выключенном FPU биты 20–23 равны нулю.
А если собрать проект с программной плавающей точкой?
Тогда компилятор не выдаёт команд FPU, и падения не будет, но вычисления пойдут через библиотечные функции и станут медленнее. CMSIS в этом случае и не включает FPU: __FPU_USED равен нулю. Для STM32F3, G4 и L4 выгоднее аппаратный вариант и включённый FPU.
Источники
- ARM. Cortex-M4 Devices Generic User Guide (DUI 0553B, ID012616) — разд. 4.6.1, табл. 4-49 и 4-50: CPACR по адресу 0xE000ED88, сброс 0x00000000, поля CP10 [21:20] и CP11 [23:22]: 0b00 — доступ запрещён, любая попытка даёт UsageFault NOCP, 0b11 — полный доступ; разд. 4.6.6: FPU выключен после сброса, пример включения — установить биты 20–23, затем DSB и ISB, запись только в привилегированном режиме; табл. 4-25…4-27: UFSR — полуслово по адресу 0xE000ED2A, бит NOCP [3]; разд. 2.4.2: отказ превращается в HardFault, если его обработчик не включён
- STMicroelectronics. cmsis-device-g4 v1.2.6: Source/Templates/system_stm32g4xx.c; ARM CMSIS_5 5.9.0: core_cm4.h (GitHub, Apache-2.0) — SystemInit: при __FPU_PRESENT == 1 и __FPU_USED == 1 — SCB->CPACR |= (3UL << 10·2) | (3UL << 11·2), «set CP10 and CP11 Full Access»; core_cm4.h: для GCC __FPU_USED = 1 только при __VFP_FP__ и без __SOFTFP__, то есть при сборке с аппаратной плавающей точкой
Нашли ошибку?
Смотрите также
- HardFaultИсключение Cortex-M при необработанной ошибке
- ARM Cortex-MЯдра M0–M85: архитектуры, FPU, DSP, MPU, TrustZone, выбор
- STM32G4Cortex-M4 с FPU, до 170 МГц: G431/G441, G471–G484, G491/G4A1 — линейки и распиновки
- HAL, LL или регистрыУровни STM32Cube, что делает HAL, правила совмещения, один код тремя способами
- FPU (блок плавающей точки)Аппаратные операции с float в Cortex-M4F: регистры, CPACR, ленивое сохранение