/* 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" /* 道G task-26:崩溃现场上报改走控制台单写者的**有界**发送口(dbg_console_write_raw)。 * 原来这里直接调 HAL_UART_Transmit,于是崩溃路径成了绕过 console_log 行级独占的 * **第三个写者**,并且带两条本仓已实证的失效:① 超时判据是 HAL_GetTick()(TIM7 停摆 * 时永不成立,第 8a/8b 轮台架实证);② 僵尸通道(ORE 锁存)下 HAL 恒快速失败 * (docs/子系统设计/控制台单写者日志设计说明.md §12)—— 而 [ERR] 崩溃行正是台架 * 「出现即不通过」的判据来源,快速失败 = 关键证据整条蒸发。 */ #include "console_log.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、不遍历指针,只用寄存器级有界轮询(dbg_console_write_raw)+ * SCB 寄存器读取,因此在内核状态已不可信时仍可用。 * 2026-10-26(道G task-26)两处改动: * ① 发送口由 HAL_UART_Transmit 换成 dbg_console_write_raw(理由见 hard_fault_write); * ② 去掉输出后的 HAL_Delay(200):dbg_console_write_raw 自身等到 TC(移位寄存器排空) * 才返回,延时本就冗余;去掉它是为了**少一条 tick 依赖**——HAL_Delay 也走 * TIM7 时基,tick 停摆时会卡死在复位之前(启动段 IWDG 尚未启动,没有兜底)。 * 同文件断言钩子早有同款理由的注释("无需 HAL_Delay:con_tx_bounded 已等到 TC")。 */ 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; } /* 道G task-26:改走 dbg_console_write_raw(原来是 HAL_UART_Transmit(&huart1, …, * 0xFFFF))。三条换它的理由,全部有仓内实证或既有判据背书: * ① **不依赖 tick**:本口的上界是 DWT CYCCNT(CON_TX_TIMEOUT_CYC = 100ms 对应 * 周期数),DWT 使能不上时退回指令计数上界(CON_TX_GUARD_MAX)——两条都与 * TIM7/uwTick 无关。HAL 那条是 UART_WaitOnFlagUntilTimeout(HAL_GetTick(), * Timeout=0xFFFF):正常上限约 65 秒,且 tick 一停摆就**永不超时**。 * ② **不受 HAL 状态机与 ORE 影响**:本口只轮询 TXE/DR/TC,不读 gState,僵尸通道 * 下不会"整条不发"(§12);也不再占用 huart1 的 HAL lock——崩溃现场不再可能 * 与被冻结的 defaultTask 所持 BUSY 互绊。USE_FULL_ASSERT 打开时也不再走 * assert_param(IS_UART_INSTANCE(NULL)) 那条二次进断言钩子的潜在递归。 * ③ **未初始化有短路**:huart1.Instance == NULL 时立即返回 HAL_ERROR,绝不解引用 * 地址 0(机器证据 = 主机单测 TC-CONLOG-100;极早期路径仍能走完全行不挂死 * = TC-CONLOG-102)。残余风险照实写在下方 hard_fault_report 函数头。 * 本口自身等到 TC 才返回,所以调用点不需要(也不该)再补 HAL_Delay。 */ dbg_console_write_raw(s, (unsigned)len); } /* 字面量输出宏:长度由 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 函数里做。 * * 关于"14 段裸发要不要包成一次整行独占"(道G task-26 定案,2026-10-26:**不包**): * 本函数确实是 14 段裸发(tag / PC / LR / xPSR / R0~R3 / CFSR / HFSR / 可选 BFAR / * 可选 MMFAR),段与段之间也确实可插字节;但 dbg_console_tx_begin()/tx_end() 在这里 * **空转**:异常上下文 IPSR != 0 时 begin 放行且**不持有**凭证("绝不等待"的必然结果), * end 又只对持有者有效。而包上的代价是 ×9 条任务链的栈记账(本函数经断言/内核钩子 * 挂在 configASSERT 子树上;实测 default/net/iec104 三条链各 +80 B,把 iec104Task 的 * margin 顶到 1,016 < 1,024 ISR 底线)。真正救这条崩溃行的是另外两件事:**发送口归一** * (hard_fault_write 改走 dbg_console_write_raw,三条理由见其函数头)与**去掉 * HAL_Delay**(少一条 tick 依赖)。整行性的真实依据与残余见 * docs/子系统设计/控制台单写者日志设计说明.md §4.4.1: * 故障上下文 IPSR != 0,begin() 是**放行**(返回 1 但**不持有**凭证——异常上下文 * 绝不允许等待或失败),所以这里的原子性**不是**靠凭证,而是靠"Fault 优先级 -1、 * 外设中断 ≥5、任务全冻结 ⇒ 没有第二个写者能跑进来"。唯一残留:被抢占任务**已经 * 在移位寄存器里的那 ≤1 个字节**,它会出现在本行**之前**(不影响 [ERR] 前缀可 grep), * 不是段间插入。已明确否决"发送循环逐字节检查让位标志"那种修法——那会把正在发的 * 启动日志行截断,换不来什么。 * * 残余风险(照实登记,别当成已消除): * - **极早期崩溃仍然静默**:若在 MX_USART1_UART_Init() 之前进 Fault,本口的 * `huart1.Instance == NULL` 短路会直接返回、一个字节都不发(换来的是"绝不解引用 * 地址 0",见 hard_fault_write ③;该短路有机器判据 TC-CONLOG-100 与 TC-CONLOG-102)。旧写法在 * 同样场景下也不发(gState==RESET 不等于 READY ⇒ HAL_BUSY),**并没有因为本次改动 * 变差**,但也别写成"早期崩溃现在能看到"。 * - **ORE/NE/FE 脏标志本身没被消除**:本笔只是不再经由 HAL 状态机,从而消除了 * "崩溃日志整条蒸发"这一类;"控制台输出可能被 RX 悬空噪声污染"那一条仍在 * §12 既有观察项名下(RX 配置是另一个风险面,本笔不动)。 */ 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) 已删:最后一个 write_raw 内部等到 TC 才返回,串口已排空; * 留着它反而多一条 TIM7 tick 依赖(tick 停摆 = 卡在复位之前)。 */ 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); } /* ==== 4 个异常入口:__asm 版 Handler(符号与向量表一致)==== * ⚠ **必须写在下面 4 个 `#define X X_Gen` 之前**——写在之后会被自己的宏改名。 * 2026-10-09 从生成区迁入本保护区:此前放在生成区内(L3),每次 CubeMX 重生成 * 都被抹掉、需 `git checkout HEAD -- Src/stm32f4xx_it.c` 复原;现在生成区保持 * CubeMX 原样,重生成零改动。 * * 原理:EXC_RETURN bit2(`TST lr, #4`)判断异常帧在 MSP 还是 PSP,取帧指针后 * `B` 到上面的 per-fault C 入口。CubeMX 默认的空 `while(1)` 版会让死机在日志上 * **完全静默**(2026-09-20 实证)。 * ⚠ ARMCC 语法:内联汇编**只能**用整函数 `__asm void f(void) {...}` 形态。 * `__asm volatile("MRS %0, lr" : "=r"(x))`(GCC 语法)报 #18; * `__asm { MRS x, lr }` 块内联会把寄存器名当 C 标识符报 #20(2026-09-29 实测)。 */ __asm void HardFault_Handler(void) { extern fault_entry_hard TST lr, #4 ITE EQ MRSEQ r0, MSP MRSNE r0, PSP B fault_entry_hard } /* MEMFAULT:访问 MPU 禁止区 / 非法特权级访问。须 SHCSR 的 MEMFAULTENA(bit16) 使能。 * 命中时 CFSR 的 MMARVALID(bit7) 置 1,MMFAR 记录出错地址(报告函数按此判定)。 */ __asm void MemManage_Handler(void) { extern fault_entry_mem TST lr, #4 ITE EQ MRSEQ r0, MSP MRSNE r0, PSP B fault_entry_mem } /* BUSFAULT:非法总线访问 / 取指(外设时钟未开、错误指针、DMA 越界等)。 * 须 SHCSR 的 BUSFAULTENA(bit17) 使能。命中时 CFSR 的 BFARVALID(bit15) 置 1。 */ __asm void BusFault_Handler(void) { extern fault_entry_bus TST lr, #4 ITE EQ MRSEQ r0, MSP MRSNE r0, PSP B fault_entry_bus } /* USAGEFAULT:未定义指令 / 非对齐访问 / 除零(须 DIV_0_TRP)/ 非法状态。 * 须 SHCSR 的 USGFAULTENA(bit18) 使能。 * ⚠ 本项目**刻意不使用硬件除零陷入**(C 代码里除零应自己判,避免把普通 bug 升级为死机)。 */ __asm void UsageFault_Handler(void) { extern fault_entry_usage TST lr, #4 ITE EQ MRSEQ r0, MSP MRSNE r0, PSP B fault_entry_usage } /* 为生成函数改名,避免与上面手工 `__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_adc1; 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 TIM_HandleTypeDef htim3; 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. */ void HardFault_Handler(void) { /* USER CODE BEGIN HardFault_IRQn 0 */ /* USER CODE END HardFault_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_HardFault_IRQn 0 */ /* USER CODE END W1_HardFault_IRQn 0 */ } } /** * @brief This function handles Memory management fault. */ void MemManage_Handler(void) { /* USER CODE BEGIN MemoryManagement_IRQn 0 */ /* USER CODE END MemoryManagement_IRQn 0 */ 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. */ void BusFault_Handler(void) { /* USER CODE BEGIN BusFault_IRQn 0 */ /* USER CODE END BusFault_IRQn 0 */ 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. */ void UsageFault_Handler(void) { /* USER CODE BEGIN UsageFault_IRQn 0 */ /* USER CODE END UsageFault_IRQn 0 */ while (1) { /* USER CODE BEGIN W1_UsageFault_IRQn 0 */ /* USER CODE END W1_UsageFault_IRQn 0 */ } } /** * @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 TIM3 global interrupt. */ void TIM3_IRQHandler(void) { /* USER CODE BEGIN TIM3_IRQn 0 */ /* USER CODE END TIM3_IRQn 0 */ HAL_TIM_IRQHandler(&htim3); /* USER CODE BEGIN TIM3_IRQn 1 */ /* USER CODE END TIM3_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 stream4 global interrupt. */ void DMA2_Stream4_IRQHandler(void) { /* USER CODE BEGIN DMA2_Stream4_IRQn 0 */ /* USER CODE END DMA2_Stream4_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_adc1); /* USER CODE BEGIN DMA2_Stream4_IRQn 1 */ /* USER CODE END DMA2_Stream4_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 */ /* 使能 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 */