b623a406ad
这四笔在主树里已经互相纠缠(同一次 @build 编译、同一份 .htm 度量),拆成四笔就不
存在任何一个被真编过的中间态,所以合一笔入库;下面按模块分段陈述,作者与出证归属
逐段写明。
—— 一、崩溃证据路径的整行独占收尾(log-maint-writer,task-26)——
上午那笔 1d4b6a17 之后,stack_report 报 iec104Task 静态最坏链 1,544 / margin 1,016,
低于 1,024 的 ISR 底线。真因不是新增局部缓冲,而是本模块与内核临界区之间新长出
一条环:taskEXIT_CRITICAL ⇒ vPortExitCritical ⇒ configASSERT ⇒ vApplicationAssertFailed
⇒ dbg_console_write_raw ⇒ 回到本模块。armlink 把这一整坨计入**每一个含临界区的
任务链**(vApplicationAssertFailed 的 Max Depth 由约 40 B 涨到 128 B + In Cycle),
所以九条链同涨约 80 B——同涨本身就排除了任何单个调用方。
改法两处:① 本模块三处窗口(取凭证 / 还凭证 / 整行入环)改用 CON_CRIT_ENTER/EXIT
(PRIMASK 保存恢复到局部变量;注释写全为什么不能直接 __enable_irq()——嵌套时会
替别人打开任务级临界区——以及屏蔽面变宽是有意为之);② 异常/终点钩子里那对
自证空转的 begin/end 摘掉(异常态 begin 放行但不持有、end 是幂等空操作,调它只有
成本没有收益),任务态 STALL 行保留 begin/end,这个区分写进两处注释与文档 §4.4.1。
实测四棵树逐个 .htm 解析(身份钉 tree + Linker run 时刻 + 产物来源):A=77779d49
链 1,456;B=1d4b6a17 链 1,464 / margin 1,096 PASS;C=含未提交钩子接线的工作树
1,544 / 1,016 FAIL;D=本轮修复后(Linker run 22:52)回到 **1,464 / 1,096 PASS**,
九条链齐降 80 B。断环图证:vPortExitCritical 由 136 B + In Cycle 变 8 B 且不再
In Cycle,con_tx_release 子树 152→8;Error_Handler 回到 280 + In Cycle = A 树同值
(既有发送环无人拆动)。顺带删掉 Src/freertos.c 崩溃路径里最后一个 HAL_Delay(200)
(该文件真实调用现为 0)。
测试:console_log 152→**153** 条(只增不减);换钉不删防——015 现在钉「PRIMASK
窗口 2 次、深度 1、内核临界区 0 次」,新钉 015c 绝不等待,160 注入点搬到
con_pm_hook,176 强化到未调度路径连 PRIMASK 窗口都不开;红线水位
con_pm_at_tx_max 与 con_uart_crit_at_tx_max 双 0;判据⑦(延后的行与当场直发逐
字节相同)170b/171/178 仍全绿。作者自报并纠正一处:换钉时曾把 TC-CONLOG-015b 改
编号,而 015b 在生产注释与文档里都被引用——已恢复「编号不退役、语义搬过去」。
覆盖率 App/log/console_log.c 199/200 → 256/257 行、21/21 函数(只增)。
—— 二、未标定闭锁判据收成单点访问器(ui-param-writer,task-16)——
「UA/UN/IA 三相 VALID 全置位才允许跑判据」这条门控原先在两处各自镜像实现
(protect_task.c 的 s_cal_locked 与 ui_data_feed.c 的 feed_cal_block),改一处另一处
静默失效且无门禁可照。现新增 adc_cal_prot_blocked() 落在 App/algo/adc_cal,DTU_BENCH
特例收进一处;两个消费者同批改调、本地副本删除。作者还查实并纠正了我先前一句
错判:全仓 App/app→App/task 的 include 实为 0 条,故访问器不能落在 protect_task。
新增 TC-ADCCAL-018 八条(含冷启动即闭锁),adc_cal 套件 99/99。
—— 三、共享卷锁五态可观测性(ui-param-writer,task-17)——
原状比登记簿写的更糟:五个可区分态(未调用 / try 超时 / 分片超时 / fail-open /
未持锁却 release)生产侧零留痕,且 vol_lock.c 的返回值注释与代码相反——
fail-open(锁没建成、互斥已失效却在读写盘)被返 1 **谎报成取到锁**,而那正是
vol_lock.h 记录的台架实证故障(并发写坏 FTL → 喂狗点饥饿 → IWDG 误复位)的前置。
锁层加四个 volatile 计数(16 B 静态,零新局部、零新 callee、零日志、零 FatFs)与
不取锁的 vol_lock_get_stats();13 处 try 调用点一字未动。**刻意不在锁层记时刻**:
记时刻要读 tick,等于给 defaultTask 取锁链添帧——诊断不值那几个字节;消费方若
自盖时间戳,那只能是被观测到的时刻而非发生时刻,.h 里已写明不得写成首次/末次。
四套本地假件各加 s_last_wait_to(字段名逐字一致),把「喂狗点取锁一律 timeout=0」
与「ftpTask 分片一律 100 ms」从注释纪律升级成会 FAIL 的断言;红-绿反证:临时把
sys_log_mcu.c 的 0u 改 50u → TC-SYSLOG-010/012 双红,把 lftpd_io.c 的 100u 改 0u →
TC-LFTPIO-034 红,两处按原串改回并用 SHA-256 证明逐字节还原。TC-VOLLOCK-015~019
共 45/45(无新桩)。
—— 四、UI 死接口清理(ui-maint-writer,task-6 收尾)——
删掉 ui_data_maint_ready():全仓零消费者、只有它自己的用例在读。**更值得记的是
它为什么能活过一整轮评审**:头注释写的是「页面用它决定这页能不能操作」,而页面
从来没用过它——注释陈述的是意图,评审读的是意图,门禁读的是可达路径,两者都不
反驳「意图未实现」。作者对「连用例一起删」做了显式抗命:TC-UIDATA-025 的 13 条里
只有 4 条读那个死口,另 9 条钉的是真防线(六口未注入安全返回 / 残表整表拒绝 /
被拒不覆盖已生效的好表),故删冗余的 025a、把 025i/025k/025m 换成用 reset_req()
的可观察返回值判定,性质不丢。ui_data 套件 244 → 243(净减 1),黄金 40 页与
池 5448 B 未变(该刀零像素影响,参考图无 diff)。
—— 门禁与未跑项(本笔如实留账)——
cubemx-survival 基线已 --write(漂移 6 项全部是上述意图内区域:断言/栈溢出钩子
实现、看门狗模块行等),基线文件同笔入库,随后 --check 通过。
本笔未跑的钩子只有两条:mem-numbers 与 mem-budget——磁盘 .map 出自 22:52 那次
构建、含尚未入库的 UI 与工具改动,与 HEAD 台账必然不同码;按仓库既有约定允许
--no-verify + 正文留证,回填在紧随的台账一笔统一做(含 ram_baseline --update 与
覆盖率基线两条 [NOTE]:ui_data.c 65→316、iec104_comm.c 36/36)。
两条已挂着的账:① id-check 不可判定引用 1,162 / 上限 1,166,**余量只剩 4**,
主要吃进者是我今天推进的 UI 那笔(68 条文本断言的注释引用),账在 Lead;
② 崩溃注入腿(故意复位)台架复验未跑,需先由代码道提交触发器,且判据只判原子性
不判 [ERR]=0——IPSR 路径的主张归主机侧套件,硬件照不出来。
产物身份(按 D 表要求钉死,避免数字与树脱钩):本笔包含的固件树就是那张 D 表对应的
那一棵——MDK-ARM/.../STM32F407-Demo.htm 的 mtime = 2026-10-26 22:52:12(.htm/.map 按
约定不入库,故把时刻写进正文)。提交前在本树复跑 stack_report.py:exit 0,
iec104Task 1,464 / margin 1,096(42%)PASS、defaultTask 3,040 / 2,080(40%)PASS,
与 D 表逐项一致;本次运行不含任何未提交固件源码之外的新改动。
680 lines
23 KiB
C
680 lines
23 KiB
C
/* 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/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 */
|