Перейти к содержанию

Почему STM32F3, G4 или L4 падает в HardFault на операции с float?

Обновлено Редакция GAW

У STM32F3, STM32G4 и STM32L4 ядро Cortex-M4 с блоком плавающей точки (FPU), но после сброса он выключен. ARM в руководстве по Cortex-M4 пишет прямо: FPU нужно включить, прежде чем выполнить хотя бы одну команду с плавающей точкой. Включают его в системном регистре CPACR.

Поле CPACRБитыЗначение после сбросаНужно
CP1021:2000 — доступ запрещён11 — полный доступ
CP1123:2200 — доступ запрещён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.

Источники

  1. 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, если его обработчик не включён
  2. 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__, то есть при сборке с аппаратной плавающей точкой

Нашли ошибку?