? Что такое Hard Fault простыми словами
Hard Fault — это критическая ошибка процессора.
Проще говоря, это ситуация, когда микроконтроллер встречает что-то настолько «невозможное» для себя, что не может продолжить выполнение программы.
Типичный пример — попытка обратиться к памяти, которой не существует, или выполнение запрещённой инструкции.
Когда это происходит, процессор сразу передаёт управление специальному обработчику — Hard Fault Handler.
? Когда чаще всего возникает Hard Fault
? 1. Разыменование NULL-указателя
int *ptr = NULL;
*ptr = 123; // Опа! Памяти по адресу 0 нет -> Hard Fault.
? 2. Выход за границы массива
int arr[4] = {1, 2, 3, 4};
arr[10] = 55; // Запись туда, где памяти нет или там чужие данные.
? 3. Неправильно настроенные регистры периферии
Например, не инициализировал SPI, но уже обращаешься к его регистрам.
Процессор не может выполнить доступ — бац, Hard Fault.
? 4. Деление на ноль
int a = 10;
int b = 0;
int c = a / b; // На ARM Cortex-M это UsageFault, который может перейти в Hard Fault.
? 5. Переполнение стека
void recursive() {
recursive();
}
Если стек маленький, через пару тысяч вызовов он перезапишет чужую память — и ты получишь Hard Fault.
⚡ Почему это больно
Если не настроить обработчик:
MCU зависнет,
перезагрузится,
или уйдёт в бесконечный «reset loop».
Ты не увидишь никакой полезной информации, кроме «ну, упало…».
? Как правильно готовить Hard Fault в Zephyr
✅ 1. Настроить собственный обработчик
Пример для ARM Cortex-M (CMSIS):
#include <zephyr/kernel.h>
#include <zephyr/arch/arm/aarch32/cortex_m/cmsis.h>
#include <zephyr/sys/printk.h>
void HardFault_Handler(void)
{
uint32_t *stack_pointer;
__asm volatile("mrs %0, msp" : "=r"(stack_pointer));
uint32_t r0 = stack_pointer[0];
uint32_t r1 = stack_pointer[1];
uint32_t r2 = stack_pointer[2];
uint32_t r3 = stack_pointer[3];
uint32_t r12 = stack_pointer[4];
uint32_t lr = stack_pointer[5];
uint32_t pc = stack_pointer[6];
uint32_t psr = stack_pointer[7];
printk("\n=== HARD FAULT ===\n");
printk("R0 = 0x%08X\n", r0);
printk("R1 = 0x%08X\n", r1);
printk("R2 = 0x%08X\n", r2);
printk("R3 = 0x%08X\n", r3);
printk("R12 = 0x%08X\n", r12);
printk("LR = 0x%08X\n", lr);
printk("PC = 0x%08X\n", pc);
printk("PSR = 0x%08X\n", psr);
printk("CFSR = 0x%08X\n", SCB->CFSR);
printk("HFSR = 0x%08X\n", SCB->HFSR);
k_fatal_halt(0);
}
❓ Что это даёт
PC
(Program Counter) — инструкция, где всё сломалось.LR
(Link Register) — откуда пришёл вызов.PSR
— статус процессора (флаги).CFSR
,HFSR
— что пошло не так.
✅ 2. Проверь заранее
void main(void)
{
printk("Triggering hard fault for test...\n");
int *null_ptr = NULL;
*null_ptr = 42; // Ловим сбой!
}
✅ 3. Понимай регистры
CFSR
— Configurable Fault Status Register.HFSR
— Hard Fault Status Register.
? Логирование
Логируй в NVRAM или по UART.
Используй watchdog и assert.
Добавляй проверку стека:
CONFIG_THREAD_STACK_INFO=y
CONFIG_THREAD_STACK_OVERFLOW_CHECK=y
✅ Чеклист
✔ Свой обработчик
✔ Лог PC, LR, PSR, CFSR, HFSR
✔ Assert и sanity checks
✔ Лог в NVRAM/UART
✔ Тест сбоя заранее
? Итог
Hard Fault — не мистика. Больше инфы = быстрее фиксим баг.
Комментарии (2)
SIISII
05.07.2025 19:05Вообще, и обращение к несуществующей памяти, и выполнение недопустимой команды приводят к другим прерываниям, если это архитектура ARMv7-M или ARMv8-M Mainline. В HardFault они превращаются из-за того, что обычно остаются запрещёнными, но, вообще говоря, ничто не мешает их разрешить и обрабатывать каждый такой случай отдельно. Вот на ARMv6-M (Cortex-M0 и -M1) и ARMv8-M Baseline других прерываний нет, поэтому всегда имеет место HardFault.
Кстати говоря, на "полных" микроконтроллерах есть и другие регистры, показывающие причины прерываний -- MMFSR, BFSR, UFSR. Тамошние битики тоже интерес представляют.
Tzimie
Нейронка узнается сразу