? Что такое 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)


  1. Tzimie
    05.07.2025 19:05

    Нейронка узнается сразу


  1. SIISII
    05.07.2025 19:05

    Вообще, и обращение к несуществующей памяти, и выполнение недопустимой команды приводят к другим прерываниям, если это архитектура ARMv7-M или ARMv8-M Mainline. В HardFault они превращаются из-за того, что обычно остаются запрещёнными, но, вообще говоря, ничто не мешает их разрешить и обрабатывать каждый такой случай отдельно. Вот на ARMv6-M (Cortex-M0 и -M1) и ARMv8-M Baseline других прерываний нет, поэтому всегда имеет место HardFault.

    Кстати говоря, на "полных" микроконтроллерах есть и другие регистры, показывающие причины прерываний -- MMFSR, BFSR, UFSR. Тамошние битики тоже интерес представляют.