Files
STM32F4-Base/Src/stm32f4xx_it.c
T
wandering1 8a18e388c7 四个 Fault 全部采集现场:MemManage/BusFault/UsageFault 由空死循环升级
原状:HardFault 已有崩溃采集,但 MemManage / BusFault / UsageFault 仍是空
while(1) —— 这三类故障发生时日志完全静默,且因 SHCSR 默认未使能,实际会
被升级成 HardFault,报告出笼统的 "HARDFAULT"、看不出根因。

改动:

- Src/stm32f4xx_it.c:三个 Handler 升级为手工 __asm 版(取 MSP/PSP 帧指针)
  -> B 到 per-fault C 入口 fault_entry_{mem,bus,usage} -> hard_fault_report()。
  hard_fault_report 增加 tag 参数,日志统一为
  "[ERR] <TAG> PC=.. LR=.. xPSR=.. R0-R3=.. CFSR=.. HFSR=..[/BFAR][/MMFAR]"。
  生成实体经 #define X X_Gen 改名保留,链接器各移除 2 字节(.map 已验)。
  三个各带独立锚点 MEMFAULT/BUSFAULT/USAGEFAULT-ANCHOR。
- 新增 fault_handler_enable()(USER CODE BEGIN 1 区):使能 SHCSR 的
  MEMFAULTENA/BUSFAULTENA/USGFAULTENA。声明入 Inc/stm32f4xx_it.h,调用在
  app_main_init() 第一行(纯寄存器写,不依赖日志/时钟)。
- 刻意不启用 CCR.UNALIGN_TRP / CCR.DIV_0_TRP:会把普通 bug 升级成死机,
  对本项目弊大于利(说明写进函数注释)。

实现约束(本轮踩坑):ARMCC 的 __asm 块取不到 C 的数据符号(ADR r1, xxx_tag
报 A1516E: Bad symbol,去 static 也无效),也无法给 C 函数传字符串参数,
故故障名必须由 C 侧 per-fault 入口带上。

配套:

- check_cubemx_survival.py:新增 fault-handlers-3 / fault-handler-enable 两项,
  UNPROTECTED_LEGACY 清掉三个 Fault(WARN 2->1),清单 16 -> 18 项。
- AGENTS.md:新增"4 个 Fault 全部采集"条目;ARMCC 汇编条目补 A1516E 第 3 坑;
  校验标记补 [ERR](四个 Fault 日志都以它开头 -> 一律判 FAIL,刻意)。
- 看门狗设计说明.md:§6.1 补 4 条 Fault 标记与 @test 禁项关系;
  §10.2 L3 清单与四个 Fault 采集小节;§11 结论表与变更记录。
- 待处理内容.md:新增 N-03d(已完成),N-03c 补第二次重生成结论。

验证:@build 0 错误 0 警告(Flash +156 B,RAM 无变化);pre-commit 13 钩子
全通过;审计 18 通过 / 1 警告 / 0 失败。
2026-09-29 09:16:19 +08:00

594 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file stm32f4xx_it.c
* @brief Interrupt Service Routines.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */
/* USER CODE END TD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* HardFault 现场报告实现(2026-09-29 迁移至此,**保护区**内):
* 原放在紧随 HardFault_Handler 之后的自造区 `HardFault_Report` 里——
* 实测 CubeMX 重生成不会保留"生成区之外的自造标记区"(它整段按新模板重写),
* 故迁入本文件本来就有的 `USER CODE BEGIN 0`(生成区内的标准保护位),
* 从此重生成不会丢失。
*
* 说明:本函数由 __asm void HardFault_Handler 用 B 直接跳入(见下方),
* 不经内核、不 printf、不遍历指针,只用 HAL_UART_Transmit 轮询 + SCB 寄存器读取,
* 因此在内核状态已不可信时仍可用。输出后 HAL_Delay(200) 排空 UART 再复位。
*/
static void hard_fault_write(const char *s, uint16_t len)
{
/* 显式长度 + 有界单次发送:上报路径自身不得再崩(2026-09-24 加固;
* 不做 '\0' 遍历,长度由调用方 sizeof 给出) */
if ((s == NULL) || (len == 0u)) {
return;
}
if (len > 64u) {
len = 64u;
}
HAL_UART_Transmit(&huart1, (const uint8_t *)s, len, 0xFFFFu);
}
/* 字面量输出宏:长度由 sizeof 定,编译期即可确定 */
#define HF_PUTS(lit) hard_fault_write((lit), (uint16_t)(sizeof(lit) - 1u))
static void hard_fault_puthex(uint32_t v)
{
static const char digits[] = "0123456789ABCDEF";
char out[9];
int i;
for (i = 0; i < 8; i++) {
out[i] = digits[(v >> (28 - i * 4)) & 0xFu];
}
out[8] = '\0';
hard_fault_write(out, 8u);
}
/* 崩溃现场报告核心(四个 Fault 共用)。
* 入口参数:tag - 故障名(ASCII 字面量,如 "HARDFAULT" / "MEMFAULT")
* frame - 异常栈帧 {R0,R1,R2,R3,R12,LR,PC,xPSR}
* 说明:CFSR 一次性打出,其子域已含 MMARVALID(bit7)/BFARVALID(bit15),
* 故 MMFAR/BFAR 的"是否有意义"由 CFSR 判定,四个 Fault 都能看全。
*
* ⚠ 本函数**不直接**被异常向量调用——异常入口是下方的 `__asm` 版 Handler,
* 它们只负责取异常帧指针,然后 `B` 到下面 4 个 per-fault 入口之一
* (每个入口自带 tag 字面量后再调本函数)。
* 这样做的原因:ARMCC 的 `__asm` 块**取不到 C 的 static 数据符号**
* (实测:`ADR r1, xxx_tag` 报 `A1516E: Bad symbol`,去掉 static 也无效),
* 而 `__asm` 里也无法给 C 函数传结构体/字符串参数。故把"带 tag"这一步
* 放在普通 C 函数里做。
*/
void hard_fault_report(const char *tag, uint32_t *frame)
{
HF_PUTS("\r\n[ERR] ");
hard_fault_write(tag, 16u);
HF_PUTS(" PC=");
hard_fault_puthex(frame[6]);
HF_PUTS(" LR=");
hard_fault_puthex(frame[5]);
HF_PUTS(" xPSR=");
hard_fault_puthex(frame[7]);
HF_PUTS(" R0=");
hard_fault_puthex(frame[0]);
HF_PUTS(" R1=");
hard_fault_puthex(frame[1]);
HF_PUTS(" R2=");
hard_fault_puthex(frame[2]);
HF_PUTS(" R3=");
hard_fault_puthex(frame[3]);
HF_PUTS(" CFSR=");
hard_fault_puthex(SCB->CFSR);
HF_PUTS(" HFSR=");
hard_fault_puthex(SCB->HFSR);
/* BFAR/MMFAR 仅在对应 valid 位为 1 时有意义 */
if ((SCB->CFSR & (1UL << 15)) != 0U) {
HF_PUTS(" BFAR=");
hard_fault_puthex(SCB->BFAR);
}
if ((SCB->CFSR & (1UL << 7)) != 0U) {
HF_PUTS(" MMFAR=");
hard_fault_puthex(SCB->MMFAR);
}
HAL_Delay(200);
NVIC_SystemReset();
}
/* 四个 per-fault 入口:__asm Handler 取到帧指针后 `B` 到这里,由 C 补上故障名。
* 名字用 `fault_entry_*` 前缀,与向量符号(`*_Handler`)区分开。 */
void fault_entry_hard(uint32_t *frame)
{
hard_fault_report("HARDFAULT", frame);
}
void fault_entry_mem(uint32_t *frame)
{
hard_fault_report("MEMFAULT", frame);
}
void fault_entry_bus(uint32_t *frame)
{
hard_fault_report("BUSFAULT", frame);
}
void fault_entry_usage(uint32_t *frame)
{
hard_fault_report("USAGEFAULT", frame);
}
/* 为生成函数改名,避免与下方手工 `__asm` 实现符号冲突。
* 生成实体改名为 <原名>_Gen(永不执行,仅为占名字不冲突,
* 保持生成区代码零删除;未被引用 → 链接器自动移除)。
*
* 覆盖的 4 个 Cortex-M4 异常:HardFault / MemManage / BusFault / UsageFault。
* 后三者须在 SHCSR 中使能对应位才会真正触发(见 fault_handler_enable)。 */
#define HardFault_Handler HardFault_Handler_Gen
#define MemManage_Handler MemManage_Handler_Gen
#define BusFault_Handler BusFault_Handler_Gen
#define UsageFault_Handler UsageFault_Handler_Gen
/* USER CODE END 0 */
/* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_spi1_rx;
extern DMA_HandleTypeDef hdma_spi1_tx;
extern DMA_HandleTypeDef hdma_spi2_rx;
extern DMA_HandleTypeDef hdma_spi2_tx;
extern SPI_HandleTypeDef hspi1;
extern SPI_HandleTypeDef hspi2;
extern DMA_HandleTypeDef hdma_usart1_tx;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern UART_HandleTypeDef huart4;
extern UART_HandleTypeDef huart5;
extern UART_HandleTypeDef huart1;
extern UART_HandleTypeDef huart2;
extern UART_HandleTypeDef huart3;
extern TIM_HandleTypeDef htim7;
/* USER CODE BEGIN EV */
/* USER CODE END EV */
/******************************************************************************/
/* Cortex-M4 Processor Interruption and Exception Handlers */
/******************************************************************************/
/**
* @brief This function handles Non maskable interrupt.
*/
void NMI_Handler(void)
{
/* USER CODE BEGIN NonMaskableInt_IRQn 0 */
/* USER CODE END NonMaskableInt_IRQn 0 */
/* USER CODE BEGIN NonMaskableInt_IRQn 1 */
while (1)
{
}
/* USER CODE END NonMaskableInt_IRQn 1 */
}
/**
* @brief This function handles Hard fault interrupt.
*/
/* HARD-FAULT-ANCHOR — 本锚点与下方改动一体,重生成会一并消失,
* 供 scripts/check_cubemx_survival.py 检出(勿删):
*
* 崩溃现场采集(手工改写)。EXC_RETURN bit2 判断异常帧在 MSP 还是 PSP,
* 取帧指针交给 C 报告函数 hard_fault_report()(实现在文件头 USER CODE BEGIN 0
* 保护区内)。空 while(1) 版会让死机在日志上完全静默(2026-09-20 实证)。
*
* 命名:`#undef HardFault_Handler` 后重定义为 __asm 版本,与向量表符号一致;
* 为避免与下方生成实体"重复定义"(ARMCC 报 #247,其"已定义"检查
* 早于 C 作用域),已在文件头用 `#define HardFault_Handler
* HardFault_Handler_Gen` 把生成实体改名为不冲突的符号。
*
* ⚠ ARMCC 语法:内联汇编**只能**用整函数 `__asm void f(void) {...}` 形态。
* `__asm volatile("MRS %0, lr" : "=r"(x))`(GCC 语法)报 #18;
* `__asm { MRS x, lr }` 块内联会把寄存器名当 C 标识符报 #20(2026-09-29 实测)。 */
#undef HardFault_Handler
__asm void HardFault_Handler(void)
{
extern fault_entry_hard
TST lr, #4
ITE EQ
MRSEQ r0, MSP
MRSNE r0, PSP
B fault_entry_hard
}
void HardFault_Handler_Gen(void)
{
while (1)
{
/* USER CODE BEGIN W1_HardFault_IRQn 0 */
/* USER CODE END W1_HardFault_IRQn 0 */
}
}
/**
* @brief This function handles Memory management fault.
*/
/* MEMFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
* 触发条件:访问 MPU 禁止区 / 非法特权级访问。须 SHCSR 的 MEMFAULTENA(bit16) 使能。
* 命中时 CFSR 的 MMARVALID(bit7) 置 1,MMFAR 记录出错地址(报告函数按此判定)。 */
#undef MemManage_Handler
__asm void MemManage_Handler(void)
{
extern fault_entry_mem
TST lr, #4
ITE EQ
MRSEQ r0, MSP
MRSNE r0, PSP
B fault_entry_mem
}
void MemManage_Handler_Gen(void)
{
while (1)
{
/* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
/* USER CODE END W1_MemoryManagement_IRQn 0 */
}
}
/**
* @brief This function handles Pre-fetch fault, memory access fault.
*/
/* BUSFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
* 触发条件:非法总线访问 / 取指(外设时钟未开、错误指针、DMA 越界等)。
* 须 SHCSR 的 BUSFAULTENA(bit17) 使能。命中时 CFSR 的 BFARVALID(bit15) 置 1。 */
#undef BusFault_Handler
__asm void BusFault_Handler(void)
{
extern fault_entry_bus
TST lr, #4
ITE EQ
MRSEQ r0, MSP
MRSNE r0, PSP
B fault_entry_bus
}
void BusFault_Handler_Gen(void)
{
while (1)
{
/* USER CODE BEGIN W1_BusFault_IRQn 0 */
/* USER CODE END W1_BusFault_IRQn 0 */
}
}
/**
* @brief This function handles Undefined instruction or illegal state.
*/
/* USAGEFAULT-ANCHOR — 见 HARD-FAULT-ANCHOR(同一套采集方案,勿删本锚点)
* 触发条件:未定义指令 / 非对齐访问 / 除零(须 DIV_0_TRP)/ 非法状态。
* 须 SHCSR 的 USGFAULTENA(bit18) 使能。
* ⚠ 本项目**刻意不使用硬件除零陷入**(C 代码里除零应自己判,避免把普通 bug 升级为死机)。 */
#undef UsageFault_Handler
__asm void UsageFault_Handler(void)
{
extern fault_entry_usage
TST lr, #4
ITE EQ
MRSEQ r0, MSP
MRSNE r0, PSP
B fault_entry_usage
}
void UsageFault_Handler_Gen(void)
{
while (1)
{
/* USER CODE BEGIN W1_UsageFault_IRQn 0 */
/* USER CODE END W1_UsageFault_IRQn 0 */
}
}
/* USER CODE BEGIN 1 */
/* 使能 MemManage / BusFault / UsageFault 三个异常。
*
* 为什么必须做:复位后 SCB->SHCSR 只开了 HardFault,后三者处于**禁用**状态——
* 它们触发时会被升级成 HardFault,`hard_fault_report` 打出来的是 "HARDFAULT"
* 且看不出根因。这里逐个置使能位,日志里才能直接看到 MEMFAULT / BUSFAULT /
* USAGEFAULT 字样。
*
* ⚠ 刻意**不**打开以下两项(都会把普通 bug 升级成死机,对本项目弊大于利):
* - CCR.UNALIGN_TRP :非对齐访问陷入。Cortex-M4 硬件本可处理非对齐访问,
* 打开后协议解析里常见的非对齐取字段会直接死机。
* - CCR.DIV_0_TRP :除零陷入。除零应由业务代码自己判,不要靠硬件兜。
* 打开它们只会让"本该只是算错个值"变成"整机复位"。
*
* 调用时机:app_main_init() 早期(日志可用前即可,越早越好)。
*/
void fault_handler_enable(void)
{
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk |
SCB_SHCSR_BUSFAULTENA_Msk |
SCB_SHCSR_USGFAULTENA_Msk;
}
/* USER CODE END 1 */
/**
* @brief This function handles Debug monitor.
*/
void DebugMon_Handler(void)
{
/* USER CODE BEGIN DebugMonitor_IRQn 0 */
/* USER CODE END DebugMonitor_IRQn 0 */
/* USER CODE BEGIN DebugMonitor_IRQn 1 */
/* USER CODE END DebugMonitor_IRQn 1 */
}
/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f4xx.s). */
/******************************************************************************/
/**
* @brief This function handles DMA1 stream3 global interrupt.
*/
void DMA1_Stream3_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream3_IRQn 0 */
/* USER CODE END DMA1_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi2_rx);
/* USER CODE BEGIN DMA1_Stream3_IRQn 1 */
/* USER CODE END DMA1_Stream3_IRQn 1 */
}
/**
* @brief This function handles DMA1 stream4 global interrupt.
*/
void DMA1_Stream4_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream4_IRQn 0 */
/* USER CODE END DMA1_Stream4_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi2_tx);
/* USER CODE BEGIN DMA1_Stream4_IRQn 1 */
/* USER CODE END DMA1_Stream4_IRQn 1 */
}
/**
* @brief This function handles EXTI line[9:5] interrupts.
*/
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(TP_BUSY_Pin);
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
/**
* @brief This function handles SPI1 global interrupt.
*/
void SPI1_IRQHandler(void)
{
/* USER CODE BEGIN SPI1_IRQn 0 */
/* USER CODE END SPI1_IRQn 0 */
HAL_SPI_IRQHandler(&hspi1);
/* USER CODE BEGIN SPI1_IRQn 1 */
/* USER CODE END SPI1_IRQn 1 */
}
/**
* @brief This function handles SPI2 global interrupt.
*/
void SPI2_IRQHandler(void)
{
/* USER CODE BEGIN SPI2_IRQn 0 */
/* USER CODE END SPI2_IRQn 0 */
HAL_SPI_IRQHandler(&hspi2);
/* USER CODE BEGIN SPI2_IRQn 1 */
/* USER CODE END SPI2_IRQn 1 */
}
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
/**
* @brief This function handles USART3 global interrupt.
*/
void USART3_IRQHandler(void)
{
/* USER CODE BEGIN USART3_IRQn 0 */
/* USER CODE END USART3_IRQn 0 */
HAL_UART_IRQHandler(&huart3);
/* USER CODE BEGIN USART3_IRQn 1 */
/* USER CODE END USART3_IRQn 1 */
}
/**
* @brief This function handles UART4 global interrupt.
*/
void UART4_IRQHandler(void)
{
/* USER CODE BEGIN UART4_IRQn 0 */
/* USER CODE END UART4_IRQn 0 */
HAL_UART_IRQHandler(&huart4);
/* USER CODE BEGIN UART4_IRQn 1 */
/* USER CODE END UART4_IRQn 1 */
}
/**
* @brief This function handles UART5 global interrupt.
*/
void UART5_IRQHandler(void)
{
/* USER CODE BEGIN UART5_IRQn 0 */
/* USER CODE END UART5_IRQn 0 */
HAL_UART_IRQHandler(&huart5);
/* USER CODE BEGIN UART5_IRQn 1 */
/* USER CODE END UART5_IRQn 1 */
}
/**
* @brief This function handles TIM7 global interrupt.
*/
void TIM7_IRQHandler(void)
{
/* USER CODE BEGIN TIM7_IRQn 0 */
/* USER CODE END TIM7_IRQn 0 */
HAL_TIM_IRQHandler(&htim7);
/* USER CODE BEGIN TIM7_IRQn 1 */
/* USER CODE END TIM7_IRQn 1 */
}
/**
* @brief This function handles DMA2 stream0 global interrupt.
*/
void DMA2_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
/* USER CODE END DMA2_Stream0_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi1_rx);
/* USER CODE BEGIN DMA2_Stream0_IRQn 1 */
/* USER CODE END DMA2_Stream0_IRQn 1 */
}
/**
* @brief This function handles DMA2 stream2 global interrupt.
*/
void DMA2_Stream2_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream2_IRQn 0 */
/* USER CODE END DMA2_Stream2_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_rx);
/* USER CODE BEGIN DMA2_Stream2_IRQn 1 */
/* USER CODE END DMA2_Stream2_IRQn 1 */
}
/**
* @brief This function handles DMA2 stream3 global interrupt.
*/
void DMA2_Stream3_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream3_IRQn 0 */
/* USER CODE END DMA2_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi1_tx);
/* USER CODE BEGIN DMA2_Stream3_IRQn 1 */
/* USER CODE END DMA2_Stream3_IRQn 1 */
}
/**
* @brief This function handles DMA2 stream7 global interrupt.
*/
void DMA2_Stream7_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream7_IRQn 0 */
/* USER CODE END DMA2_Stream7_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_usart1_tx);
/* USER CODE BEGIN DMA2_Stream7_IRQn 1 */
/* USER CODE END DMA2_Stream7_IRQn 1 */
}
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */