Files
STM32F4-Base/App/app/app_main.c
T
wandering1 e4d70f9fcb docs(真值): 勘查 13 条文档/代码不一致全部处置(10 改 3 核实)+ 补 3 处
证据源 = 主树 ci-artifacts/archify/architecture-dtu-overview-20261030-095132/
evidence.md §11。该目录不入库,故把每条的判据与实测源抄进正文,别让证据只活在
一台机器上。处置口径:能改的改成与代码同源,核实后确认文档没问题的明确写「不改」。

判停点与就绪屏障(条 1、条 3)
- AGENTS 原写 `protect_task.c:98` 直接停走判据:`:98` 是 `s_prev_cal_lock` 变量,
  判停点是 `App/task/protect_task.c:268 if (adc_cal_prot_blocked() != 0u)`,
  判定本体在标定层 `adc_cal_prot_blocked()`、protectTask 只是消费者 → 改引用,
  同步 `ui_task.c:240` 页面注释与 `docs/任务规划/数据可信.md` 的同一处行号。
- `SYS_EVT_FTP_READY` 有生产者(`Src/freertos.c:490`,注释自陈预留)无消费者;
  `SYS_EVT_TIMESYNC_READY` 全仓无生产者也无消费者(只有定义与测试桩)。
  AGENTS、`sys_events.h` 位注释、屏障配图(生成器 + 产物)三处从「四个就绪位
  并列」改成「bit0/bit1 在用 + bit2/bit3 预留」,并指向
  `docs/架构设计/项目启动顺序管理.md` §3.2 的逐位对照表。ftpTask 实际等的是
  `NET|STORAGE`(`Src/freertos.c:477-479`,WaitAll|NoClear,100 ms 分片喂狗)。

/log 不是「三本」(条 5)
- `App/log/sys_log_mcu.c` 的 `s_paths[]` = run / selftest / accid / alarm /
  start / oper / yk 七本 64B 定长文本 + `soe.log`(64B 定长二进制)+ `wdev.log`
  (变长取证行,`App/fs/vol_lock` 侧投,不走定长格式)。
  → AGENTS 两处、总览 §2.1 与 §4 的「三本」改成「七本 + SOE(+ wdev 取证行)」。

主机单测 44 套 → 47 套(条 2,两把机器尺互证)
- `scripts/check_test_coverage.py --list` 打印 47 行;`ls -d test/host/*/` = 48 个
  一级目录,唯一没有 `run_unit_test.py` 的是 `common/` ⇒ 47 套。
- 差额三名 = 屏板链路套件:`sb_proto`、`sb_link`(2026-10-29 入库)、
  `sb_led_map`(2026-10-30 入库)。专属钩子仍是 22 根 ⇒ 无专属钩子的从 22 套变 25 套。
- 同步面:AGENTS、`docs/门禁说明/README.md`、`host-unit-tests.md` §6 台账(名单补
  三名)、`docs/代码规范/pre-commit使用说明.md`、`主机单测覆盖率口径.md`(含耗时句)、
  `scripts/README.md`、总览目录树、`.pre-commit-config.yaml` 三处注释、
  `test/host/sys_log_mcu/sys_log_mcu_test.c` 一处注释。
- 带日期戳的历史测量值按纪律冻结不动:28 套、41 套 ~105 s、40 套/18 套那三处。

本仓 LVGL 版本(条 6)
- vendored 版本 = `Lib/lvgl/lv_version.h:9-11` 的 9.3.0;总览两处「LVGL v9.2」与
  `gen_arch_svg.py` 图内文字改 v9.3 并重跑产物(脚本与产物同一笔,`generated-fresh`
  逐字节核)。
- **刻意不动**:`docs/内存管理/RAM使用分析与优化方案.md` 两处「LVGL v9.2 文档」+
  GitHub `release/v9.2` 分支 URL ——那是对外游某分支文档的引用,不是本仓版本声明。

触发口径与栈值(条 4、7、9、10)
- ADC 映射 §1「ADC1 为软件触发」是旧口径:`Src/adc.c:53` 实为
  `ADC_EXTERNALTRIGCONV_T3_TRGO` → 改成 TIM3 TRGO,并补一句「TIM3 与 TPAFE 的
  CONVST 之间没有硬件耦合,仍不构成共同采样触发」,缺陷结论本身不变。
- 同文档 adcTask 栈 512 → 640 words(`Src/freertos.c:159 .stack_size = 640 * 4`)。
- `docs/UI设计/UI_设计架构.md` 的 uiTask 栈「16384 words」是单位错:
  实为 4096 words = 16384 B(`Src/freertos.c:218`),顺手补上行号。
- `App/app/app_main.c:197` 注释「DE = PD0」陈旧:`:198` 实传 `ST_DIR4_*`
  (V0.1 实板 PA15,V0.0 才是 PD0)→ 注释改指 V0.1 网表报告的引脚迁移表。
- 两处已退役编号 `N-18`(ADC 映射、V0.1 网表报告)换成登记簿条目名
  「双转换器无共同采样触发」。

核实后确认「文档没问题」的三条(条 8、12、13)
- 黄金参考「菜单 8 状态 + 内容页 66 张」:`simulator/tests/ref/` 实测 66 个
  `shot_*.bin`,与 `ui-golden` 钩子显示名一致 → 不改。
- `App/task/rs485_task.c` 存在但 `Src/freertos.c` 不创建它(grep 无 rs485):
  AGENTS 与总览都已写明「尚未接线 / 暂缓」,图侧画成「实现待接线」→ 不改。
- 104 端口 `:2404` 是工厂默认、可经 `/config.ini` `[comm]` 改 → 总览索引表补
  「默认」二字;「uiTask 10 ms 节拍」核实 = `ui_tokens.h:65 UI_TASK_TICK_MS 10` ✓。

并行勘查会话的同族发现(它扫「文档声明数 vs 真值」,逐处独立复验后采纳 3 处)
- `App/protocol/iec104/iec104_link.h` 头注释写「端口:TCP 2404(§1.5 B0 定义)」,
  同文件下方已写「服务端口真源已迁 [comm] 段」= 自相矛盾 → 补「工厂默认」并指回
  [comm] 与「下次重 bind 即生效」。
- `scripts/gen_task_table.py` 的 iec104Task 描述「:2404 监听」→「工厂默认 :2404
  监听」,`--write` 重生成 README 任务表(AGENTS 表无描述列,实测未被改写)。
- `App/log/README.md` 五行写入点行号全漂:事故 181→188、告警 184→191、启动
  176→183(`App/task/protect_task.c`);操作 `iec104_pts.c` 574/602→582/610、
  `default_task.c` 242→266、`sys_log_mcu.c` 646→682;遥控 573/576/582/586/596/
  598/609 → 581/584/590/594/604/606/617(宿主 `sim_syslog.c:245` 原值正确、未动)。
- **撤销一条**:我早先记的「`docs/算法/继电保护功能复现规划.md:225` 声称记录本已
  有消费者」与原文不符——:225 实为规划表里「动作/SOE 记录 | 复用 `sys_log`」一行,
  属能力复用口径、不是当前态断言 → 不改。教训:**改引用前逐处读现文,别照自己
  上一轮的笔记盲替**(这条比改中的每一处都值钱)。
- 同一批勘查还报了约 12 处「`2404` 缺『工厂默认』限定词」(AGENTS、`App/protocol/
  README.md`、`Src/freertos.c` 注释、5 篇 docs、`test/host` 工具,含一张配图生成器
  要连产物同批重跑)与 `测试开发标准流程.md`「像素层 60 内容页」、
  `.pre-commit-config.yaml` uinav 注释「快检档 18 张黄金参考」(后者写时为真、
  描述的却是钩子现有行为):**本笔刻意不做**,另开任务空间处理——那批要同时动
  AGENTS.md 与 `.pre-commit-config.yaml` 两处单写者面,与并行会话撞车的代价更高。
- 端口生效语义(用户裁定 2026-10-30):产品基线写「公共地址与端口可配…改后需重启
  生效」,代码 `App/task/iec104_serv.c` 是「ca 改后要重启;port 下次重 bind 才生效」
  ——裁定**按代码语义要改**处理(改了端口应立刻重 bind),属固件行为变更,另开任务
  且需真机验证;`.serialtest/config.json` 写死 `i104: listening on 2404` 必检锚点
  是同题(现场改端口会让串口回归假失败),一并留给那条任务。

自查(任务树内)
- `check_doc_links.py --sections-strict`:12 个改动 .md 逐个 rc=0;
  `check_mermaid.py` 改动 .md 全 rc=0;`check_generated_fresh.py` PASS
  (25 个生成器 / 21 一致 / 4 存量基线);`check_nested_comment.py --all` PASS
  (449 文件,新发 0、存量 2);`check_bom_drift.py --all` 99 个名单文件无漂移。
- 编译:`.dsh/skills/keil-build` 退出码 0(0 错误 0 警告),
  Program Size Code=246720 RO=29836 RW=2052 ZI=149708 —— 与本树凭证同码,
  证明这批改的确实只有注释与文档。
- 字节体检:23 个文件无 CR / U+FFFD / 行尾空格 / 缺末行换行;`ui_task.c` 被编辑后
  丢的 3 字节 BOM 已补回(bom-guard 复验通过);5 处「三连空行」逐文件与
  HEAD 比对确认是存量(Python 生成器的 PEP8 双空行与个别 C 文件原有空行)。
- 一次性工具 `ci-artifacts/fix_docs2.py`:断言式规则表,按文件读一次、在演化文本上
  逐条替换、一次写盘。第一版写成「每条规则各自从启动时快照起算」,同一文件后一条
  把前一条整体覆盖,只有每文件最后一条活下来——靠「改完再全仓复扫旧形态」当场抓到,
  这条判据本身已值得写进流程。
- 待办:`git add` 后需重跑 `ram_baseline.py --update`(体积凭证按 git 索引内容生成,
  未 staged 的文件不在凭证里,`check_size_gate.py --local` 现在因此报红)。
- 未 commit、未 push(AGENTS.md 边界:提交属 Ask first)。
2026-10-30 14:19:35 +08:00

385 lines
21 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.
/*
* 模块名称:Application Main Entry
* 模块功能:应用初始化流程编排、各模块启动、主循环
* 适用平台:STM32F407ZGTx
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
* v1.1 2026-08-29 王建锋 将原 main.c 中各外设初始化统一收归 app_main_init,
* 并按"时间子系统优先"的顺序编排启动流程
* v1.2 2026-09-14 王建锋 新增自检埋点(RTC/NAND/ADC 失败 → sys_log 自检记录)+ syslog_mcu_init()
*/
#include "app_main.h"
#include "main.h" /* ST_DIR4_GPIO_Port / ST_DIR4_Pin 等 CubeMX 宏 */
#include "stm32f4xx_it.h" /* fault_handler_enable() */
#include "usart.h" /* huart5 */
#include "tim.h" /* htim3:1 kHz 采样节拍(TIM3 更新中断触发 CONVST + TRGO 触发 ADC1) */
#include "adc.h" /* adc1_disable():挂 DMA 前把 ADC 关掉,让 Start_DMA 自己使能 */
#include "rs485.h"
#include "sb_link.h" /* 屏板链路传输层:sb_link_init()(USART1 收中断,批 1 的 1-3) */
#include "sd2506.h"
#include "boot_stats.h" /* 开机账本:上电次数(RTC 用户 SRAM)+ 本次复位原因快照(道G task-12) */
#include "sys_clock.h"
#include "gd5f2gq5ue.h"
#include "tpafe5160.h"
#include "adc_input.h" /* 语义取数与有效性层:adc_input_init()(语义映射 + STM32 ADC1 扫描) */
#include "adc_cal.h" /* 标定折算层:adc_cal_init()(ROM 系数 + cfg 两段注册,道A 标定折算与落盘整定) */
#include "run_par.h" /* 运行参数段:run_par_init()([run_par] 注册 + 104 地址映射真源,道D 这一项) */
#include "iec104_comm.h" /* [comm] 段:iec104_comm_init()(ca/端口注册,道G task-8) */
#include "phasor.h" /* 全周傅氏相量层:phasor_init()(系数表 + 实例 + 双区交换) */
#include "freq.h" /* 全周傅氏相量的派生量:freq_init()(相位解缠绕测频,B8) */
#include "prot_set.h" /* 保护判据定值模型:prot_set_init()(B5,内核前封存) */
#include "prot_elm.h" /* 保护判据元件框架:prot_elm_init()(B5,解析相量索引) */
#include "sys_log.h"
#include "task_watchdog.h" /* task_wd_seg_report():第 8 轮复位不丢"进段打点"上报 */
#include "test_config.h" /* 套件开关:身份戳的构建变体串(FW_BUILD_VARIANT)依赖本头,
* 改 TEST_SUITE_* / ENABLE_DESTRUCTIVE_TESTS 必重编本 TU →
* __DATE__/__TIME__ 随之刷新,见 app_main_init() 的戳注释 */
#define DBG_TAG "[APP]"
#include "dbg_log.h"
/* g_rs485 由 rs485.c 定义,经 rs485.h 的 extern 引用;接收缓冲内聚于句柄 rx_buf */
/* 复位原因锁存值(Src/main.c 在 HAL_Init() 后立即写入并清除 RCC_CSR)。
* 放这里而非 main.c,是因为需要与其它应用级全局量同层、且日志侧更好引用。 */
volatile uint32_t g_rcc_csr_latched;
/*
* 构建变体串(2026-10-11):把"测试套件开关"编进固件身份戳,见 app_main_init() 的戳注释。
* TEST_SUITE_NAME 由 test/board/test_config.h 定义(产品构建 = "NONE");
* 模拟器等未纳入该头的编译单元退化为空串,不影响戳的可搜索性。
*/
#if defined(TEST_SUITE_NAME)
#define FW_BUILD_SUITE " suite=" TEST_SUITE_NAME
#else
#define FW_BUILD_SUITE ""
#endif
#if defined(ENABLE_DESTRUCTIVE_TESTS)
#define FW_BUILD_DSTR " dstr=on"
#else
#define FW_BUILD_DSTR " dstr=off"
#endif
/* 身份戳后缀:一段连续 ASCII,保证 .hex 里可原样搜到(run_boot_test.py 抠戳用) */
#define FW_BUILD_VARIANT FW_BUILD_SUITE FW_BUILD_DSTR
/*
* 函数功能:把 RCC_CSR 锁存值翻译为可读的复位原因串(见手册 RM0090 §6.3.22)
* 入口参数:csr - 锁存的 RCC_CSR 值
* buf - 输出缓冲
* len - 缓冲容量
* 返回值:无
* 函数说明:按"最可信归因"的优先级组织——IWDG/WWDG 优先于软件复位
* (看门狗复位是我们真正关心的自动恢复事件;软件复位多为崩溃钩子
* 兜底,两者同时置位时报告看门狗更有价值)。纯 ASCII,防编码坑。
*/
static void reset_cause_str(uint32_t csr, char *buf, unsigned int len) {
/* 优先级链**不在这里再写一遍**:改由 boot_stats 出一份(IWDG > WWDG > 软件 >
* POR > PIN > BOR)。原先这条 if-else 链与页面上要显示的编码各写一份,两份早晚
* 会漂——漂了的症状是串口说 "IWDG"、屏幕写 "software",两条却都"看着有据"。
* 顺序本身的理由见本函数上方原注释与 App/util/boot_stats.h 的函数头。 */
const char *cause = boot_stats_cause_str(boot_stats_cause_from_csr(csr));
/* 简单有界拼接(不引 snprintf,避免依赖与栈开销) */
if (buf == NULL || len == 0u) {
return;
}
{
unsigned int i = 0u;
while ((i < (len - 1u)) && (cause[i] != '\0')) {
buf[i] = cause[i];
i++;
}
buf[i] = '\0';
}
}
/*
* 函数功能:应用层初始化
* 入口参数:无
* 返 回 值:0 - 成功,-1 - 失败
* 限定条件:CubeMX 外设初始化已完成
* 函数说明:在 CubeMX 外设初始化之后、FreeRTOS 调度器启动之前调用。
* 启动顺序以"时间子系统优先"为原则,使后续所有日志均带时间戳:
* 1) 时间子系统(SD2506 RTC → 墙钟基准)
* 2) RS-485
* 3) GD5F NAND
* 4) TPAFE5160 ADC → ADC 输入层(语义取数与有效性,语义映射 + STM32 ADC1)
* 5) SD2506 诊断信息
* 6) 启动 TIM3 1 kHz 采样节拍(ISR 内脉冲 CONVST;同一更新事件经 TRGO 硬件触发 ADC1 + DMA)
*/
int app_main_init(void) {
/* 0) 使能 MemManage / BusFault / UsageFault(复位后默认只有 HardFault 使能)。
* 必须尽早——放在所有业务初始化**之前**,这样从第一行业务代码起,
* 任何内存/总线/非法指令类故障都会被独立捕获并打出故障名,
* 而不是被升级成 HardFault 后只看到笼统的 "HARDFAULT"。
* 纯寄存器写(SCB->SHCSR),不依赖日志/时钟,故放在最前无副作用。 */
fault_handler_enable();
/* 日志引擎初始化(创建待发队列;须先于任何 syslog_post) */
(void)syslog_mcu_init();
/* 启动锚点(2026-10-02 加):本函数在内核启动前执行、不创建任何 RTOS 对象,
* 恒成功且无副作用;本行是"启动进度轨迹"的起点——之后的每一步都有结果日志,
* 故某次启动卡死时,最后一行日志即可指向卡住的步骤。 */
DBG_INFO("boot: syslog init OK");
/* 固件身份戳(2026-10-10):__DATE__/__TIME__ 由编译器展开,与前面的字面量做
* C 字符串拼接 → .rodata 里是一段连续 ASCII,可直接在 .hex 里原样搜到。
* 用途:/serial-boot-test 把"芯片里正在跑的镜像"和"刚编出来的镜像"对上号。此前开机标记
* 全是固定串(App init OK / Network Init / ...),旧镜像也照样全中,于是
* "反复重下载、芯片一直是旧镜像"这类事故在自动闭环里完全不可见
* (AGENTS.md 烧录铁律记的正是它)。代价:1 条 DBG ≈ 90 B Flash + 30 B 常量。
* ⚠ 戳只在**本 TU 被重编**时才刷新(增量构建),故本文件必须 #include
* "test_config.h":否则只切套件宏(TEST_SUITE_* / ENABLE_DESTRUCTIVE_TESTS)
* 不重编本 TU,产品镜像与测试镜像顶着同一个戳,戳校验对"两种构建互换"失明
* (2026-10-11 实测:切宏后的测试构建与产品构建的戳一模一样,只有回读校验
* 才发现镜像不同)。两条防线:① 头依赖 → 改宏必重编、戳必刷新;
* ② FW_BUILD_VARIANT 把套件名与破坏性开关编进戳内容 → 即便戳时间相同,
* 产品串与测试串也不同。代价:约 20 B 常量。 */
DBG_INFO("fw build: " __DATE__ " " __TIME__ FW_BUILD_VARIANT);
/* 0) 复位原因上报(锁存值由 Src/main.c 在 HAL_Init() 后立即采得)。
* 放在日志引擎之后、业务初始化之前:任何一次非上电复位都值得在
* 开机第一时间看到——尤其 IWDG,它意味着上一轮运行有任务卡死。
* 注意:RCC_CSR 已被 main.c 清零,本处只读锁存副本。 */
{
char cause[40];
uint32_t csr = g_rcc_csr_latched;
reset_cause_str(csr, cause, sizeof(cause));
DBG_INFO("reset cause: %s (CSR=0x%08lX)", cause, (unsigned long)csr);
/* 看门狗复位记入自检记录(只记载故障,符合既有约定) */
if ((csr & (RCC_CSR_IWDGRSTF | RCC_CSR_WWDGRSTF)) != 0u) {
syslog_post(SYSLOG_SELFTEST, "watchdog reset");
}
}
/* 0b) 第 8 轮取证:上报上一轮 defaultTask 主循环的**最后进段**。
* 轨迹槽在 .noinit(scatter UNINIT_CCM),IWDG/引脚复位不清 SRAM →
* 误复位后开机第一时间可见 `PREVRUN seg=N`,直接指向主循环阻塞点
* (第 7 轮观察"判定分支未执行"经第 8b 轮更正:真因是 IWDG 超时被静默
* 配成复位默认值,见 docs/子系统设计/看门狗设计说明.md 2026-10-02 变更记录)。
* 裸 UART 直发,且一次复位只报一次(内部清 magic)。 */
task_wd_seg_report();
/*
* 1) 时间子系统初始化(尽早完成,后续所有日志带时间戳)
* sd2506_init() 从 RTC 读取初始时间
* sys_clock_init() 将 RTC 时间转为 Unix epoch,建立墙钟基准
* 启动段可观测性(2026-10-02 加):sd2506_init 原先所有失败路径静默返回,是启动段
* 唯一无日志的初始化;现内部已逐路径打 DBG_ERROR,此处再补一行
* 带错误码的结果回执,使 I2C 不通时能看到"在哪一步、什么错"。
*/
{
bsp_err_t rtc_ret = sd2506_init();
DBG_INFO("SD2506 init: %s (ret=%d)",
(rtc_ret == BSP_OK) ? "OK" : "FAILED", rtc_ret);
/* 1b) 开机账本(道G task-12):紧接 sd2506_init 之后,是**唯一**的调用点。
* 为什么放这儿而不是 defaultTask 的存储 bring-up:
* ① 这一步只做 1 次 I2C 读 + 至多 1 次 I2C 写,落在内核启动前——
* 单线程、非 ISR、调度器还没跑(本函数不调任何创建类内核 API,
* AGENTS「内核前禁调创建类 API」那条与此无关);
* ② 保持介质是 RTC 用户 SRAM,不是文件系统:不必等 STORAGE_READY,
* 也就不必在喂狗点上碰卷锁(这条链上根本没有卷操作可言);
* ③ sd2506_init 失败时这里自然走"读不到"那一支:状态标成不可信、
* 且不回写,不会把可能还完好的记录覆盖掉。
* 失败一律不打日志(可观测性事件不进 [ERR]),状态留在 boot_stats_get()。 */
boot_stats_init();
}
if (sys_clock_init() != 0) {
syslog_post(SYSLOG_SELFTEST, "clock chip fault");
}
/* 2) RS-485 初始化(UART4 + 方向脚 = ST_DIR4 宏:V0.1 板实测 PA15,V0.0 曾是 PD0,
* 轮换记录 = docs/硬件平台/V0.1_网表详细分析报告.md 的 V0.0→V0.1 引脚迁移表),
* 初始化即自动开启接收 */
rs485_init(&g_rs485, &huart4, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
DBG_INFO("RS485 init: OK (UART4, DE=PA15)");
/* 2b) 屏板链路初始化(USART1,700 kbaud 8N1,IDLE 中断接收)。
* 放在 NAND 全片扫描之前:这一步只是挂接收(µs 级),挂上之后屏板的周期召唤
* 就已经能被收下来并计数,台架不必等启动跑完才能看到链路状态。
* 波特率不在本调用里设——在 Src/usart.c 的 USER CODE 区(CubeMX 重生成只保护那里),
* 本调用只做一次交叉核对,对不上会报 BSP_ERR_STATE 并打 ERR 行。 */
{
bsp_err_t sb_ret = sb_link_init(&huart1);
/* BRR 直接读出来打:1-2 的判据是"实测位宽误差 < 1 %",而 BRR 是位宽的唯一决定量——
* 84 MHz / (16 x 700 kHz) = 7.5 → 期望 0x0078(整数 7、小数 0.5x16 = 8),误差 0 %。
* 有这一行就不必为了核波特率去架示波器:BRR 对不上就说明 USER CODE 覆盖没生效。 */
DBG_INFO("ScreenLink init: %s (ret=%d, USART1 %lu baud, BRR=0x%04lX)",
(sb_ret == BSP_OK) ? "OK" : "FAILED",
sb_ret, (unsigned long)SB_LINK_BAUD,
(unsigned long)huart1.Instance->BRR);
}
/* 3) GD5F 初始化测试(BBT 全片扫描,启动段最耗时的一步) */
int ret = gd5f2gq5ue_init();
DBG_INFO("NAND init: %s",
(ret == GD5F_OK) ? "OK" :
(ret == GD5F_ERROR) ? "ERROR" :
(ret == GD5F_WAIT_EXPIRED) ? "BUSY TIMEOUT" :
(ret == GD5F_ECC_ERROR) ? "ECC ERROR" :
(ret == GD5F_PROGRAM_FAIL) ? "PROGRAM FAIL" :
(ret == GD5F_ERASE_FAIL) ? "ERASE FAIL" :
(ret == GD5F_ID_MISMATCH) ? "ID MISMATCH" : "UNKNOWN");
if (ret != GD5F_OK) {
syslog_post(SYSLOG_SELFTEST, "NAND fault");
}
/* 4) TPAFE5160 ADC 测试 */
bsp_err_t tp_ret = tpafe5160_init();
DBG_INFO("ADC init: %s",
(tp_ret == BSP_OK) ? "OK" :
(tp_ret == BSP_ERR_TIMEOUT) ? "TIMEOUT" :
(tp_ret == BSP_ERR_NO_DEVICE) ? "NO CHIP" : "FAIL");
if (tp_ret != BSP_OK) {
syslog_post(SYSLOG_SELFTEST, "ADC fault");
}
/* 4b-pre) 挂 DMA 之前**先把 ADC 关掉**,让使能动作由 HAL_ADC_Start_DMA 内部完成
* (它自己 __HAL_ADC_ENABLE + 等 Tstab ~20 µs,然后紧接着开 CR2.DMA 并启流)。
*
* 为什么必须这样做:MX_ADC1_Init() 在 main() 里就调用了,而挂 DMA 在这里,
* 中间隔着 GD5F 全片 BBT 扫描(数秒)。台架实测(同一次开机内用无 DMA 单通道
* 探针取真值对照,9/9 完美置换):**ADC 使能后闲置数秒才挂 DMA,DMA 乒乓缓冲
* 会整体 +2 格旋转** —— 槽 i 装的是 rank ((i+1) mod 9)+1,9 路通道按 rank 序
* 整体错开 2 位。表象是"接地 AIN2、UB 却读到别的通道的值",极易被误判成
* 映射表写错或夹子位置不对。
* 把使能与挂 DMA 压到同一时刻后旋转消失(台架 4/4 次,同开机对照差值 ≤4 码)。
* 证据与复现方法见 docs/子系统设计/ADC采样节拍设计说明.md §4.5。
*
* ⚠ 顺序不可调换:本调用必须严格早于下面的 adc_input_init()。
* 若 ADC 在本步之后仍处于使能态,旋转会重新出现。 */
if (adc1_disable() != 0) {
DBG_ERROR("ADC1 disable before DMA arm: TIMEOUT");
}
/* 4b) ADC 输入层(语义取数与有效性)初始化:清原始码值缓冲与统计 + **语义映射表自洽性自检**
* (映射指向了不会被扫描的通道 → 返回 -1,避免"读到恒为陈旧值"的静默错)
* + 挂载一次 STM32 ADC1 的 DMA(失败返回 -2)。
* ADC1 外设已由 MX_ADC1_Init()(main.c)配好 9 通道 / 144 cycles /
* **TIM3 TRGO 外部触发 + DMA 连续请求**,本层不重复配置、也不触发转换。
* 须在启动采样节拍(步骤 6)之前——先挂 DMA 再放节拍,才不会漏第一拍。 */
{
int adc_rc = adc_input_init();
if (adc_rc == -1) {
DBG_ERROR("ADC input layer: map self-check FAILED");
syslog_post(SYSLOG_SELFTEST, "ADC map fault");
} else if (adc_rc != 0) {
DBG_ERROR("ADC input layer: DMA arm FAILED (rc=%d)", adc_rc);
syslog_post(SYSLOG_SELFTEST, "ADC dma fault");
} else {
DBG_INFO("ADC input layer init: OK");
}
}
/* 4c) 相量层(全周傅氏相量)初始化:清双区结果与有效标志 + 复位采样环缓冲。
* 纯算法层(无外设 / 无 RTOS 依赖,见 docs/算法/继电保护功能复现规划.md
* §3.2),须在启动采样节拍(步骤 6)之前。幂等、当前无失败路径
* (返回值保留供后续自检扩展)。 */
(void)phasor_init();
DBG_INFO("phasor layer init: OK (inst=%u)", (unsigned)phasor_inst_count());
/* 4c-2) 测频层(全周傅氏相量的派生量,B8)初始化:清平均窗/相位基准/有效标志。
* 纯算法(输入 = phasor 使用区),消费在 adcTask(freq_run 每拍)
* 与 protectTask(OF/UF 判据经 prot_elm_run 取数)。 */
(void)freq_init();
DBG_INFO("freq layer init: OK");
/* 4c-3) 通道标定层(标定折算,「数据可信」标定折算与落盘整定)初始化:RAM 系数副本置
* ROM 默认(量程口径冻结定死的引脚侧 LSB,docs/硬件平台/ADC通道映射说明.md §7.1)
* + 向配置引擎注册 [adc_cal] / [adc_cal_ofs] 两段。注册必须先于
* defaultTask 的 cfg_file_load(「先注册后 load」契约)→ 只能落内核前;
* 折算消费在 adcTask 每拍(adc_cal_apply),标定文件值随 load/poll 原地
* 生效,无回调。 */
adc_cal_init();
DBG_INFO("adc cal init: OK (k_q16 tpafe=%ld stm32=%ld)",
(long)ADC_CAL_K_Q16_TPAFE, (long)ADC_CAL_K_Q16_STM32);
/* 4c2) 运行参数段初始化(「数据可信」这一项):死区 9 点工厂默认 + 注册
* [run_par] 段。同 adc_cal 的「先注册后 load」契约 → 必须内核前;
* 104 消费(COV 实时读 / 0x8020 段读写映射)在 iec104Task 启动后进行。 */
run_par_init();
DBG_INFO("run par init: OK (db9 k/b via adc_cal, evt=1<<8)");
/* 4c3) 通讯参数段初始化(道G task-8,「UI 补齐」第 5 项非 UI 前置):ca/端口
* 工厂默认 + 注册 [comm] 段。与 adc_cal / run_par 同「先注册后 load」契约
* → 必须内核前:defaultTask 在 storage_bring_up 里才做 net_cfg_load →
* cfg_file_load(引擎唯一读点),本注册必须排在它前面。生效语义 =
* 改后重启(iec104Task 启动读 getter,不挂回调,见 iec104_comm.h)。 */
if (iec104_comm_init() != 0) {
DBG_ERROR("comm init FAIL (dup/full?) - file values inactive");
} else {
DBG_INFO("comm init: OK (ca=%u port=%u)",
(unsigned)iec104_comm_ca(), (unsigned)iec104_comm_port());
}
/* 4d) 保护定值 + 元件层初始化(B5):出厂默认定值封存校验和 + 元件状态
* 复位并解析相量索引(须在 phasor_init 之后)。内核前完成 → iec104
* 定值读/protectTask 首拍前定值必已装载。判据消费在 protectTask。 */
prot_set_init();
prot_elm_init();
DBG_INFO("protection init: OK (set=%u elm=%u)",
(unsigned)PS_ID_COUNT, (unsigned)PE_COUNT);
/* 5) SD2506 RTC 诊断信息 */
{
/* 读取 ID */
uint8_t sd_id[8];
sd2506_get_id(sd_id);
DBG_INFO("SD2506 ID: %02X%02X%02X%02X%02X%02X%02X%02X",
sd_id[0], sd_id[1], sd_id[2], sd_id[3],
sd_id[4], sd_id[5], sd_id[6], sd_id[7]);
/* 读取当前时间 */
sd2506_time_t sd_time;
sd2506_get_time(&sd_time);
DBG_INFO("SD2506 time: %04d-%02d-%02d %02d:%02d:%02d (week %d)",
sd_time.year, sd_time.month, sd_time.day,
sd_time.hour, sd_time.minute, sd_time.second,
sd_time.week);
/* 读取温度 */
int8_t sd_temp;
sd2506_get_temperature(&sd_temp);
DBG_INFO("SD2506 temp: %d C", sd_temp);
/* 读取电池电压 */
uint16_t sd_vbat;
sd2506_get_battery_voltage(&sd_vbat);
DBG_INFO("SD2506 battery: %d.%02d V",
sd_vbat / 1000, (sd_vbat % 1000) / 10);
}
/* 6) 启动 1 kHz 采样节拍:TIM3 更新事件 → ① ISR 内脉冲 CONVST(Src/main.c)
* ② 同一事件经 TRGO 硬件触发 STM32 ADC1 一轮 9 通道转换(DMA 自动搬运)。
* ⚠ 必须在 tpafe5160_init() 成功之后启动,否则 ISR 会在驱动首帧自检
* (有效性探针)期间乱发 CONVST。消费端 adcTask 退化为纯消费者。
* ⚠ 节拍源必须能触发 ADC1,故用 **TIM3**:F4 上 ADC1 的外部触发只认
* TIM1/TIM2/TIM3/TIM4/TIM5/TIM8 的 CC 或 TRGO,**TIM6/TIM7 的 TRGO 只接
* DAC**(查 CubeMX DB 与本仓库 stm32f4xx_hal_adc.h 的 EXTSEL 表证实)
* → 要让两片转换器由同一个硬件事件驱动,节拍源必须换成 TIM3。 */
(void)HAL_TIM_Base_Start_IT(&htim3);
DBG_INFO("App init OK");
return 0;
}
/*
* 函数功能:应用主循环
* 入口参数:无
* 返 回 值:无
* 限定条件:FreeRTOS 调度器启动后不再返回
* 函数说明:空闲任务可调用的应用层处理入口
*/
void app_main_run(void) {
for (;;) {
/* 应用层空闲处理 */
}
}