From 78248a1cdfb9f0373314ed47da3ecd3073b7bc1d Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Fri, 23 Oct 2026 07:42:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(S-5):=20=E6=94=B6=E7=BC=96=20SD2506/TPAFE?= =?UTF-8?q?=20=E5=B9=B6=E7=BB=99=20dhara=20=E5=A4=B1=E8=B4=A5=E5=88=86?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E9=97=A8=E7=A6=81=E5=9F=BA=E7=BA=BF=2019?= =?UTF-8?q?=E2=86=9217?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前一笔立契约与门禁(`Drivers/BSP/bsp_err.h` + `bsp-err` 钩子 + RS485 试点);这一笔按用户 指定顺序 C3 → C1 → C2 把三个模块真正收编,并让存储失败第一次变得可读、可分类。 **C3 · SD2506 整模块(11 个公共 API + 6 个内部 helper)**:三枚私有宏 `SD2506_OK`/ `SD2506_ERROR`/`SD2506_I2C_ERROR` **删除而不是留别名**——留别名等于新代码还能用旧形状, 把迁移洗掉;编译器替我们找全所有调用点。新增 `sd2506_err_from_hal()`,**把 I2C 超时从 "和没人应答共用一个 −2"里拆出来归成 `BSP_ERR_TIMEOUT`(可重试)**——SD2506 是启动链第一站、 I2C1 上电时序敏感,这是本波唯一真正的语义改善,不是改名。`sd2506_init` 的 charge 寄存器 **回读校验不符**原返参数类错误,改归 `BSP_ERR_IO`(那不是调用方的错)。 **C1 · 同形两义**:`GD5F_BUSY_TIMEOUT` → `GD5F_WAIT_EXPIRED`(**值 −2 不变**,纯消"名字叫 TIMEOUT 其实是返回码")。**GD5F 私有码集整体收编刻意没做**:169 处引用里 48 处在板级套件、 还要动 Trap 记录用词,属另一波带真机+套件回归的工作,不夹带。TPAFE5160 收编 23 处: `BUSY_TIMEOUT → BSP_ERR_TIMEOUT`、`ERROR → BSP_ERR_PARAM`(**同值 −1,行为零变化**)、 `FRSTDATA_ERR → BSP_ERR_NO_DEVICE`(E-44 的本义就是"芯片没驱动 DB 总线",归到器件无响应比 私有 −3 有用,且避开与 `BSP_ERR_TIMEOUT` 撞值)。实测 `tpafe5160.o`/`gd5f2gq5ue.o` 的 Code 增量为 **0**——改名不产生一条指令。 ⚠ **`tpafe5160_snapshot()` 刻意不套成败码**:它是 1=有新帧 / 0=无新帧 / −1=参数错 的三态 **状态**返回,0 不是错误而是"这一拍没有新数据",强行收编会把慢消费者的正常路径变成失败。 顺带订正我上一版在 `bsp_err.h` 的断言——"本层只有 `net_*` 把数量与错误挤同一返回值"是错的, `snapshot`/`adc_input_poll` 就是第二族;两族现都已写明并各注保留理由。 **C2 · FTL 坍缩点分类**:`ftl_flush_cache`/`ftl_read_page` 不再把 dhara 失败压成 `GD5F_ERROR`, 按**处置差异**分三类,并在最早同时知道 LPN 与原始码的位置就地打印:`ECC`/`TOO_BAD` → `BSP_ERR_CRC`(页级,重试或换页,不代表卷坏);`JOURNAL_FULL`/`MAP_FULL` → `BSP_ERR_NO_SPACE`(介质健康但块池/日志见底,此时重格式化只是把问题推回去); `BAD_BLOCK`/`RECOVER`/`CORRUPT_MAP`/越界 → `BSP_ERR_IO`(完整性故障,保卷 + 上报,不许自愈 重格式化)。这与 **E-31** 是同一条纪律的两端。可读名用自建短表:**本仓 dhara 没有编 `error.c`,用 `dhara_strerror()` 会直接链接失败**。`disk_read`/`disk_write` 仍回 `RES_ERROR` (FatFs 契约);要让 defaultTask 卷健康探针按类分支还差跨调用存活的快照 → C4 待还。 **两次靠核实拦住的自我错误**:① 我一度认定 `DBG_ERROR("... err=%d", err)` 是把结构体塞进 varargs 的格式 bug——核 `Lib/dhara/error.h` 后确认 `dhara_error_t` 是 `enum`(标量),那行日志 本来合法,改了反而是引入错误;② `bsp-err` 一次 `replace_all` 越界把 8 处 **GD5F 码空间**的 判据改成 `!= BSP_OK`(数值同为 0、无行为变化),但那正是"把两个码空间混起来",已逐一还原。 `cppcheck` 亦抓到 `cls` 声明作用域过宽,已收进失败分支。 **验证**:`@build` 0 错 0 警告(`Code` 213,648、RO 18,860、`ER_IROM1` 232,508 B / 22.2%、 `ROM 合计` 233,696;**RAM 三项零增量**);`@test` PASS(692 ms,烧入戳 `Oct 23 01:35:32`) 且逐条读到被改模块的锚点:`SD2506 init: OK (ret=0)` 与 ID/时间/温度/电池四条读回正常、 `ADC init: OK`、`NAND init: OK`、`Map resumed: 14094/97504`、`adc=` 帧计数持续递增(证明 snapshot 三态未被改坏)、`[ERR]`/Fault/FAIL 0 条。主机单测:系统时钟 100/100、dhara NAND 移植层 149/149(两者都因本次改动需补 `Drivers/BSP` include 路径,补后在钩子内实跑 Passed)。 `pre-commit run` 全量 RC=0(20 个实跑钩子 Passed);`bsp-err` 基线 **19 → 17**(还掉两个 `*_BUSY_TIMEOUT = -2`)、`--prune` 干净;`doc-links --all --sections-strict` 147 篇 0 问题; `id-check --strict`、`layer-deps`、`mem-numbers`、`mem-budget`、`ui-golden` 全过;33 个文件 字节卫生 0 异常(无 CR / 无 U+FFFD / 无行尾空格 / 均有尾换行)。 **口径与数字联动**:新增 `docs/架构设计/STM32F4-Base存储架构说明.md` §10.4(三类码与处置 理由);`pre-commit使用说明.md` §2.27 与 `AGENTS.md` 基线计数 19 → 17;TPAFE README、 GD5F 测试指南与 Trap 记录均**保留旧名留痕**以便对旧日志;配图桶按 .map 对象级增量重算 (`dhara/FTL` 10,931 → 11,222,+291 全部落在 `nand_ftl.o`;应用代码/UI +36 落在 `app_main.o`;其余散量入"其他"7,557;桶和 = Code+RO 断言通过、文本溢出 0); `ram_baseline --update/--write`、`ram_report_20261023.md` 快照、报告 §1.3/§5.1/§5.4 与 部署文档 §3.3 数字全部同步;报告 §0.4/§9 的 S-5 状态改为 🟡 主体已落地。 --- .pre-commit-config.yaml | 15 ++ AGENTS.md | 4 +- App/app/app_main.c | 14 +- App/util/sys_clock.c | 8 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c | 10 +- Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h | 2 +- Drivers/BSP/GD5F2GQ5UE/nand_ftl.c | 100 ++++++-- Drivers/BSP/RS485/rs485.c | 54 ++++- Drivers/BSP/RS485/rs485.h | 14 +- Drivers/BSP/SD2506/sd2506.c | 265 ++++++++++++---------- Drivers/BSP/SD2506/sd2506.h | 58 ++--- Drivers/BSP/TPAFE5160/README.md | 2 +- Drivers/BSP/TPAFE5160/tpafe5160.c | 50 ++-- Drivers/BSP/TPAFE5160/tpafe5160.h | 35 +-- Drivers/BSP/bsp_err.h | 139 ++++++++++++ docs/images/内存布局_FLASH.svg | 48 ++-- docs/代码规范/pre-commit使用说明.md | 36 ++- docs/内存管理/RAM使用分析与优化方案.md | 9 +- docs/内存管理/RAM预算台账.md | 2 +- docs/内存管理/data/ram_report_20261023.md | 245 ++++++++++++++++++++ docs/架构设计/STM32F4-Base存储架构说明.md | 23 ++ docs/测试说明/GD5F2GQ5UE_Test_Guide.md | 2 +- docs/环境与部署/并行任务工作树方案.md | 2 +- docs/环境与部署/项目部署环境要求.md | 2 +- docs/问题记录/GD5F2GQ5UE_Trap_Records.md | 2 +- docs/问题记录/项目审查与优化建议报告.md | 81 ++++++- scripts/check_bsp_err.py | 173 ++++++++++++++ scripts/ram_baseline.tsv | 1 + test/board/sd2506_test_task.c | 52 ++--- test/host/nand_port/run_unit_test.py | 1 + test/host/sys_clock/run_unit_test.py | 3 +- test/host/sys_clock/sys_clock_test.c | 58 ++--- tools/arch_diagram/gen_memory_svg.py | 18 +- 33 files changed, 1183 insertions(+), 345 deletions(-) create mode 100644 Drivers/BSP/bsp_err.h create mode 100644 docs/内存管理/data/ram_report_20261023.md create mode 100644 scripts/check_bsp_err.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 623cd7fa..0eb2d288 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -567,3 +567,18 @@ repos: language: system files: ^Drivers/BSP/|^scripts/check_layer_deps\.py$|^\.pre-commit-config\.yaml$ pass_filenames: false + + # 15) BSP 错误码与超时命名约定(报告 S-5 的机械检查那一半)。 + # 三条规则:公开 API 不得用 HAL_StatusTypeDef/bool 当错误通道(谓词 is_/has_/can_/valid + # 不在此列);*_TIMEOUT 宏不得取负值(名字叫 TIMEOUT 其实是返回码,读代码的人会当成 + # 时长);非 void 的公开 API 必须有「返回值:」注释。存量钉 BASELINE、只减不增(2026-10-23 首清点 19 条,C1 已还 2 → 17 条), + # 修完跑 --prune 点名收紧基线。约定正文 = Drivers/BSP/bsp_err.h,规则说明 = + # scripts/check_bsp_err.py 文件头。注:ARMCC v5 对 warn_unused_result 不生效(实测), + # 所以"丢弃返回值必须写 (void)"这条只能靠书写约定与评审兜着。 + # 恒扫全库(要按 git 清单建公开头索引),故 pass_filenames: false。 + - id: bsp-err + name: BSP 错误码与超时命名约定 + entry: python scripts/check_bsp_err.py + language: system + files: ^Drivers/BSP/|^scripts/check_bsp_err\.py$|^\.pre-commit-config\.yaml$ + pass_filenames: false diff --git a/AGENTS.md b/AGENTS.md index c7d0a4c6..a303e10c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,7 +90,7 @@ ## 提交门禁(pre-commit) -- 任一失败即拦截,共 **38 个钩子**(逐个详解 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 6 条基线只减不增,报告 S-1) +- 任一失败即拦截,共 **39 个钩子**(逐个详解 → `docs/代码规范/pre-commit使用说明.md`):通用 5 + `gitlint` + `cppcheck` / `test-cppcheck` + `no-mojibake` + `coding-standard` + 文档三件套 `doc-links` / `task-table` / `mem-numbers` + `id-check`(**编号门禁**,`docs/编号规范.md` §8 八项校验;**已关闸为【增量拦截】**:只拦 `scripts/id_known_backlog.json` 台账之外的新错误级问题,存量按指纹豁免、台账只减不增;另有一根恒生效的线——可判定比例跌破 `id_registry.json` 基线即失败)+ `lv-conf-sync`(两份 `lv_conf.h` 特性子集一致)+ `cubemx-survival` + 主机侧单测 16 个(`-unit-test` 后缀)+ `test-suite-switch` + `registration-check`(双端注册反向校验 + UI 页面描述符表接线,N-23/U4)+ `mem-budget`(RAM 92% 预算门禁,N-27)+ `font-charset`(UI 字符集差集检查,防缺字白框,U4)+ `ui-golden`(UI 像素回归,菜单 8 状态 + 内容页 11 页黄金参考,U4)+ `layer-deps`(**分层方向**:`Drivers/BSP/**` 不得 include `App/**` 的头;存量 6 条基线只减不增,报告 S-1)+ `bsp-err`(**BSP 错误码与超时命名约定**:公开 API 不得拿 `HAL_StatusTypeDef`/`bool` 当错误通道、`*_TIMEOUT` 宏不得取负值、非 void 须写「返回值:」;约定正文 `Drivers/BSP/bsp_err.h`,存量基线只减不增(2026-10-23 清点 19 条 → **已还 2 条、现 17 条**),报告 S-5) - **范围 = 项目代码**:第三方(`Lib/`、`Middlewares/`、CMSIS、HAL、`lftpd`、`dhara`、`MDK-ARM`)排除;`cppcheck`/`coding-standard` 另排除 `test/`,规范检查再排除 CubeMX 生成的 `Src/`/`Inc/`。**`simulator/` 已纳入全部钩子**(2026-10-11)——生成物只靠 `.gitignore` 挡,**不在 `exclude` 里写目录名**(教训见 `docs/代码规范/pre-commit使用说明.md` §5.5) - ⚠ **pre-commit 4.x 已移除 `always: true`**(实测对 `local` 钩子只告警 `Unexpected key(s)` 并跳过)→ 想"总是跑"的钩子**必须让 `files:` 覆盖到所有会触发它的文件**,没有兜底开关 - **改写型钩子(`trailing-whitespace` + `end-of-file-fixer`)豁免 CubeMX/Keil 生成文件**(配置里 YAML 锚点 `&cubemx-generated`);⚠ **只读检查不受影响**:`cppcheck` / 乱码 / `cubemx-survival` / `task-table` / `mem-numbers` 仍覆盖 `Src/`、`Inc/` @@ -173,7 +173,7 @@ | ADC 采样节拍 / 通道映射 / V0.1 硬件缺陷 | `docs/子系统设计/ADC采样节拍设计说明.md`、`docs/硬件平台/ADC通道映射说明.md` | | 存储栈(FatFs / dhara / lftpd / 两条 FatFs 陷阱) | `docs/架构设计/STM32F4-Base存储架构说明.md` | | RAM / 栈预算 / 容量台账 | `docs/内存管理/RAM使用分析与优化方案.md`、`内存内容分配策略.md` | -| pre-commit 38 个钩子逐个详解 | `docs/代码规范/pre-commit使用说明.md` | +| pre-commit 39 个钩子逐个详解 | `docs/代码规范/pre-commit使用说明.md` | | 实体编号怎么写 / 前缀总表 / `id-check` 八项校验 | `docs/编号规范.md`(§4 总表、§8 门禁与实测基线) | | 代码规范 V1.0 / cppcheck 口径 | `docs/代码规范/嵌入式C语言代码规范_V1.0.md`、`cppcheck使用说明.md` | | 自动化测试 / 实机测试 / 板级套件 | `docs/测试说明/自动化测试流程.md`、`实机测试实施指南.md`、`board-test-suite` skill | diff --git a/App/app/app_main.c b/App/app/app_main.c index 7832b454..9515c496 100644 --- a/App/app/app_main.c +++ b/App/app/app_main.c @@ -181,9 +181,9 @@ int app_main_init(void) { * 带错误码的结果回执,使 I2C 不通时能看到"在哪一步、什么错"。 */ { - int rtc_ret = sd2506_init(); + bsp_err_t rtc_ret = sd2506_init(); DBG_INFO("SD2506 init: %s (ret=%d)", - (rtc_ret == SD2506_OK) ? "OK" : "FAILED", rtc_ret); + (rtc_ret == BSP_OK) ? "OK" : "FAILED", rtc_ret); } if (sys_clock_init() != 0) { syslog_post(SYSLOG_SELFTEST, "clock chip fault"); @@ -198,7 +198,7 @@ int app_main_init(void) { DBG_INFO("NAND init: %s", (ret == GD5F_OK) ? "OK" : (ret == GD5F_ERROR) ? "ERROR" : - (ret == GD5F_BUSY_TIMEOUT) ? "BUSY TIMEOUT" : + (ret == GD5F_WAIT_EXPIRED) ? "BUSY TIMEOUT" : (ret == GD5F_ECC_ERROR) ? "ECC ERROR" : (ret == GD5F_PROGRAM_FAIL) ? "PROGRAM FAIL" : (ret == GD5F_ERASE_FAIL) ? "ERASE FAIL" : @@ -208,10 +208,12 @@ int app_main_init(void) { } /* 4) TPAFE5160 ADC 测试 */ - int tp_ret = tpafe5160_init(); + bsp_err_t tp_ret = tpafe5160_init(); DBG_INFO("ADC init: %s", - (tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT"); - if (tp_ret != TPAFE5160_OK) { + (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"); } diff --git a/App/util/sys_clock.c b/App/util/sys_clock.c index 3133d0b1..308b2cb2 100644 --- a/App/util/sys_clock.c +++ b/App/util/sys_clock.c @@ -206,7 +206,7 @@ int sys_clock_init(void) { * 驱动内部已 +2000)。此处【禁止】再 +2000,否则年份虚增到 4026, * 换算出的 Unix 秒数远超 uint32_t 上限被截断回绕,时间彻底错乱。 * 返回值必须检查:I2C 失败时字段为垃圾,不得用于建立基准。 */ - rtc_ok = (sd2506_get_time(&sd_time) == SD2506_OK) ? 1 : 0; + rtc_ok = (sd2506_get_time(&sd_time) == BSP_OK) ? 1 : 0; /* ============================================================ * Step 1.1:开发期校时(可选,由 RTC_DEFAULT_INIT_* 宏控制) @@ -235,11 +235,11 @@ int sys_clock_init(void) { init_t.week = rtc_calc_week(init_t.year, init_t.month, init_t.day); /* 写回 SD2506:断电后该时间被保留,下次上电即为有效值 */ - if (sd2506_set_time(&init_t) == SD2506_OK) { + if (sd2506_set_time(&init_t) == BSP_OK) { DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d", init_t.year, init_t.month, init_t.day, init_t.hour, init_t.minute, init_t.second); - rtc_ok = (sd2506_get_time(&sd_time) == SD2506_OK) ? 1 : 0; /* 重新读取校准后的值(同样检查返回值) */ + rtc_ok = (sd2506_get_time(&sd_time) == BSP_OK) ? 1 : 0; /* 重新读取校准后的值(同样检查返回值) */ } else { DBG_ERROR("sys_clock_init: RTC default write FAIL"); } @@ -460,7 +460,7 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) { /* Step 2: 写回 SD2506 RTC(若提供了 sd_time) */ if (sd_time != NULL) { - if (sd2506_set_time(sd_time) == SD2506_OK) { + if (sd2506_set_time(sd_time) == BSP_OK) { DBG_INFO("sys_clock_set: SD2506 RTC written"); } else { DBG_ERROR("sys_clock_set: SD2506 RTC write FAIL"); diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c index f06b4695..ce5f4286 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c @@ -170,7 +170,7 @@ int gd5f_dma_buf_ok(const void *p_buf, size_t size) { /* * 函数功能:等待芯片操作完成(轮询 OIP 位) * 入口参数:timeout_ms - 超时时间 uint32_t > 0 - * 返回值:GD5F_OK(0) - 操作完成;GD5F_BUSY_TIMEOUT(-2) - 超时(可重试); + * 返回值:GD5F_OK(0) - 操作完成;GD5F_WAIT_EXPIRED(-2) - 超时(可重试); * GD5F_ERROR(-1) - SPI 传输真故障(不可重试,须上报自检) * 限定条件:SPI 已初始化 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时。 @@ -179,7 +179,7 @@ int gd5f_dma_buf_ok(const void *p_buf, size_t size) { * 立即成立 → **把"操作完成"伪造出来**,后续命令在未就绪的芯片上执行, * 最终表现为"静默报成功但数据没落盘"。现区分两类失败: * ① SPI 层失败(HAL≠OK)→ GD5F_ERROR(真故障); - * ② OIP 长时间不落 → GD5F_BUSY_TIMEOUT(可重试)。 + * ② OIP 长时间不落 → GD5F_WAIT_EXPIRED(可重试)。 * 调用点据此分级:超时重试 / 真故障上报自检并降级(见审计 §5 第二档 #1)。 */ /* 看门狗心跳弱钩子:默认空实现(无看门狗/模拟器构建);强符号由 @@ -225,7 +225,7 @@ int32_t gd5f_wait_busy(uint32_t timeout_ms) { return GD5F_OK; } if ((HAL_GetTick() - tick_start) >= timeout_ms) { - return GD5F_BUSY_TIMEOUT; + return GD5F_WAIT_EXPIRED; } if ((uint32_t)(HAL_GetTick() - last_beat) >= 100u) { last_beat = HAL_GetTick(); @@ -355,7 +355,7 @@ int32_t gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); - return GD5F_BUSY_TIMEOUT; + return GD5F_WAIT_EXPIRED; } } } else { @@ -437,7 +437,7 @@ int32_t gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) { if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) { HAL_SPI_DMAStop(&hspi1); GD5F_CS_HIGH(); - return GD5F_BUSY_TIMEOUT; + return GD5F_WAIT_EXPIRED; } } } else { diff --git a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h index 5fb1ef38..e10ea193 100644 --- a/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h +++ b/Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h @@ -116,7 +116,7 @@ typedef char gd5f_dma_threshold_contract_check_t[ /* 返回值定义 */ #define GD5F_OK 0 #define GD5F_ERROR -1 -#define GD5F_BUSY_TIMEOUT -2 +#define GD5F_WAIT_EXPIRED -2 /* 旧名 GD5F_BUSY_TIMEOUT;S-5 R2:**返回码不许叫 *_TIMEOUT** */ #define GD5F_ECC_ERROR -3 #define GD5F_PROGRAM_FAIL -4 #define GD5F_ERASE_FAIL -5 diff --git a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c index ff744565..4ecee260 100644 --- a/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c +++ b/Drivers/BSP/GD5F2GQ5UE/nand_ftl.c @@ -34,6 +34,7 @@ #include "diskio.h" #include "nand.h" #include "map.h" +#include "bsp_err.h" /* C2:把 dhara 错误归类成跨模块可判定的失败类 */ /* FTL 分区起始地址(块 0,偏移 0,占用全部 256MB) */ #define FTL_FATFS_OFFSET 0U @@ -126,39 +127,95 @@ static uint8_t s_initialized; /* ======================== 私有函数声明 ======================== */ -static int ftl_flush_cache(void); -static int ftl_read_page(dhara_sector_t lpn); +static bsp_err_t ftl_flush_cache(void); +static bsp_err_t ftl_read_page(dhara_sector_t lpn); /* SPI 原语从 gd5f2gq5ue.h 获取 */ +/* ======================== dhara 错误的可读名与失败类 ======================== */ + +/* + * 函数功能:把 dhara 错误枚举翻成短字符串(**本仓 dhara 没编 error.c,没有 dhara_strerror()**, + * 用它会链接失败,故自己给一张小表) + * 入口参数:err - dhara 错误枚举值 + * 返回值:静态字符串常量("NONE" / "ECC" / "MAP_FULL" ...,越界返回 "OOB") + * 限定条件:只用于日志,不得用于判等 + * 函数说明:以前这里只打 `err=%d`——现场拿到一串数字还得翻 Lib/dhara/error.h 才知道是什么, + * 存储故障又大多是"看完日志就没机会复现"的那类,可读名是排障成本最低的改法。 + */ +static const char *ftl_err_name(dhara_error_t err) { + static const char *const s_names[] = { + "NONE", "BAD_BLOCK", "ECC", "TOO_BAD", "RECOVER", + "JOURNAL_FULL", "NOT_FOUND", "MAP_FULL", "CORRUPT_MAP", + }; + + if ((unsigned int)err < (sizeof(s_names) / sizeof(s_names[0]))) { + return s_names[err]; + } + return "OOB"; +} + +/* + * 函数功能:把 dhara 错误归类成统一失败类(C2,报告 S-5) + * 入口参数:err - dhara 错误枚举值 + * 返回值:BSP_ERR_CRC(位翻转不可纠 / 页太坏);BSP_ERR_NO_SPACE(journal 或映射层预算 + * 耗尽);其余(坏块 / 找回失败 / 映射损坏 / 越界)→ BSP_ERR_IO + * 限定条件:入参应是**失败**的 err(DHARA_E_NONE 会得 BSP_OK) + * 函数说明:分这三类的理由是它们在**处置上真的不同**,不是为了码好看: + * · ECC/TOO_BAD → 该重试读或标页坏,**不是**卷的问题; + * · JOURNAL_FULL/MAP_FULL → 介质还健康,是**块池/日志预算见底**,此时重格式化只是 + * 把问题推回去(E-31 的教训正是"别把非空白错误当新卷清 map"),该走容量恢复流程; + * · BAD_BLOCK/CORRUPT_MAP/RECOVER → 映射或介质完整性故障,该保卷 + 上报,不许自愈重格式化。 + * 本函数先把这三类**打出来**(就地日志,最早且唯一同时知道 LPN 与原始码的位置); + * 要让 defaultTask 的卷健康探针按类分支,还需要一个跨调用存活的快照(S-5 的 C4 待还)。 + */ +static bsp_err_t ftl_class_from_dhara(dhara_error_t err) { + switch (err) { + case DHARA_E_NONE: + return BSP_OK; + case DHARA_E_ECC: + case DHARA_E_TOO_BAD: + return BSP_ERR_CRC; + case DHARA_E_JOURNAL_FULL: + case DHARA_E_MAP_FULL: + return BSP_ERR_NO_SPACE; + default: + return BSP_ERR_IO; + } +} + /* ======================== 页面缓存管理 ======================== */ -static int ftl_flush_cache(void) { +static bsp_err_t ftl_flush_cache(void) { if (!s_cache_dirty) { - return GD5F_OK; + return BSP_OK; } dhara_error_t err; if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) { - DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err); - return GD5F_ERROR; + bsp_err_t cls = ftl_class_from_dhara(err); /* 只在失败分支需要(cppcheck variableScope) */ + DBG_ERROR("Cache flush FAIL: LPN=%lu dhara=%s class=%d", + s_cached_lpn, ftl_err_name(err), cls); + return cls; } s_cache_dirty = 0; - return GD5F_OK; + return BSP_OK; } -static int ftl_read_page(dhara_sector_t lpn) { +static bsp_err_t ftl_read_page(dhara_sector_t lpn) { dhara_error_t err; if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) { - DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err); - return GD5F_ERROR; + bsp_err_t cls = ftl_class_from_dhara(err); /* 只在失败分支需要(cppcheck variableScope) */ + DBG_ERROR("Page read FAIL: LPN=%lu dhara=%s class=%d", + lpn, ftl_err_name(err), cls); + return cls; } s_cached_lpn = lpn; s_cache_dirty = 0; - return GD5F_OK; + return BSP_OK; } /* ======================== dhara NAND HAL ======================== */ @@ -380,7 +437,7 @@ DSTATUS disk_initialize(BYTE pdrv) { * 显式格式化后);失败的 resume 已把 journal 复位为空态 → * 按新卷处理,由上层自动建卷(此处 dhara_map_clear 为空操作: * resume 失败时 count 已清零,map 本已为空)。 */ - DBG_INFO("No valid map found (err=%d), creating fresh", err); + DBG_INFO("No valid map found (dhara=%s), creating fresh", ftl_err_name(err)); dhara_map_clear(&s_map); } else { /* E-31(2026-09-21 审计 P0 修复):非"空白"错误(ECC / @@ -391,8 +448,9 @@ DSTATUS disk_initialize(BYTE pdrv) { * defaultTask 自检上报 "filesystem fault" 并保留卷;瞬态故障 * 由主循环重试/下次上电重新 resume;重格式化只走显式命令 * (nand_ftl_format)。 */ - DBG_ERROR("Map resume failed (err=%d), volume kept, " - "storage unavailable", err); + DBG_ERROR("Map resume failed (dhara=%s class=%d), volume kept, " + "storage unavailable", ftl_err_name(err), + ftl_class_from_dhara(err)); return STA_NOINIT; } } else { @@ -461,10 +519,10 @@ DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { } if (lpn != s_cached_lpn) { - if (ftl_flush_cache() != GD5F_OK) { + if (ftl_flush_cache() != BSP_OK) { return RES_ERROR; } - if (ftl_read_page(lpn) != GD5F_OK) { + if (ftl_read_page(lpn) != BSP_OK) { return RES_ERROR; } } @@ -496,10 +554,10 @@ DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { } if (lpn != s_cached_lpn) { - if (ftl_flush_cache() != GD5F_OK) { + if (ftl_flush_cache() != BSP_OK) { return RES_ERROR; } - if (ftl_read_page(lpn) != GD5F_OK) { + if (ftl_read_page(lpn) != BSP_OK) { return RES_ERROR; } } @@ -508,7 +566,7 @@ DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { s_cache_dirty = 1; if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) { - if (ftl_flush_cache() != GD5F_OK) { + if (ftl_flush_cache() != BSP_OK) { return RES_ERROR; } } @@ -531,7 +589,7 @@ DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) { switch (cmd) { case CTRL_SYNC: - if (ftl_flush_cache() != GD5F_OK) { + if (ftl_flush_cache() != BSP_OK) { return RES_ERROR; } { @@ -574,7 +632,7 @@ int nand_ftl_format(void) { } } - if (ftl_flush_cache() != GD5F_OK) { + if (ftl_flush_cache() != BSP_OK) { return GD5F_ERROR; } diff --git a/Drivers/BSP/RS485/rs485.c b/Drivers/BSP/RS485/rs485.c index 3f9a2422..a3a5b8a9 100644 --- a/Drivers/BSP/RS485/rs485.c +++ b/Drivers/BSP/RS485/rs485.c @@ -16,6 +16,27 @@ #include "rs485.h" +/* + * 函数功能:把 HAL 的状态码映射成项目统一的 bsp_err_t(边界映射,2026-10-22 试点) + * 入口参数:status - HAL_* 返回的状态 + * 返回值:对应的 bsp_err_t;HAL_BUSY 归为 BSP_ERR_BUSY(可重试),HAL_OK 归 BSP_OK + * 限定条件:仅本文件内部使用 + * 函数说明:HAL 的码**不在本模块之外出现**(见 bsp_err.h 边界纪律 1/2);所以映射写在这里、 + * 写成 static,而不是往公共头里塞一个 include HAL 的 inline。 + */ +static bsp_err_t rs485_err_from_hal(HAL_StatusTypeDef status) { + switch (status) { + case HAL_OK: + return BSP_OK; + case HAL_TIMEOUT: + return BSP_ERR_TIMEOUT; + case HAL_BUSY: + return BSP_ERR_BUSY; + default: + return BSP_ERR_IO; + } +} + /* ======================== 全局实例 ======================== */ /* RS-485 全局实例(接收缓冲内聚于句柄的 rx_buf 字段) */ @@ -60,19 +81,20 @@ void rs485_init(rs485_handle_t *p_handle, * p_data - 待发送数据缓冲区 uint8_t*(非空) * len - 待发送字节数 uint16_t 1 - N * timeout - 发送超时(ms),传 0 使用 RS485_TIMEOUT_DEFAULT uint32_t 0 / 1 - N - * 返回值:HAL_OK - 发送成功 HAL_StatusTypeDef - * HAL_TIMEOUT - 发送超时 - * HAL_ERROR - 发送错误 + * 返回值:BSP_OK - 发送成功 bsp_err_t + * BSP_ERR_PARAM - 参数非法(空指针、长度 0 或超 RS485_TX_MAX_LEN) + * BSP_ERR_TIMEOUT - 发送超时(可重试:BSP_ERR_CAN_RETRY 为真) + * BSP_ERR_IO - HAL 报总线/状态错误 * 限定条件:须在主循环或 FreeRTOS 任务中调用,不可在 UART 中断中调用 * 函数说明:发送前拉高 DE,等待 HAL 时基延时确保 RS485 收发器驱动使能稳定后再启动 * UART 发送(HAL 时基最小粒度 1ms,此处延时 1ms,远大于 50us 的 DE 建立时间)。 * HAL_UART_Transmit 内部等待 TC(发送完成)标志,确保最后一字节完全移出后 * 再拉低 DE 切回接收状态。 */ -HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, - const uint8_t *p_data, - uint16_t len, - uint32_t timeout) { +bsp_err_t rs485_transmit(rs485_handle_t *p_handle, + const uint8_t *p_data, + uint16_t len, + uint32_t timeout) { HAL_StatusTypeDef status = HAL_OK; if (timeout == 0U) { @@ -81,8 +103,8 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, /* 防御性边界(2026-09-24,UART 缓冲遍历类 HardFault 加固): * 长度必须非零且不超过收发缓冲上限——脏长度交给 HAL 会变成"按长度遍历整片内存" */ - if ((p_data == NULL) || (len == 0U) || (len > RS485_TX_MAX_LEN)) { - return HAL_ERROR; + if ((p_handle == NULL) || (p_data == NULL) || (len == 0U) || (len > RS485_TX_MAX_LEN)) { + return BSP_ERR_PARAM; } RS485_DIR_TX(p_handle); @@ -97,7 +119,8 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, RS485_DIR_RX(p_handle); - return status; + + return rs485_err_from_hal(status); } /* @@ -112,9 +135,16 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数。 * 用户须在回调中重新调用本函数重新开启接收。 */ -HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle) { +bsp_err_t rs485_receive_start(rs485_handle_t *p_handle) { + HAL_StatusTypeDef status; + + if (p_handle == NULL) { + return BSP_ERR_PARAM; + } p_handle->rx_size = 0; - return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_handle->rx_buf, RS485_RX_BUF_SIZE); + status = HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_handle->rx_buf, RS485_RX_BUF_SIZE); + + return rs485_err_from_hal(status); } /* diff --git a/Drivers/BSP/RS485/rs485.h b/Drivers/BSP/RS485/rs485.h index c43e3ab9..8df80f7c 100644 --- a/Drivers/BSP/RS485/rs485.h +++ b/Drivers/BSP/RS485/rs485.h @@ -19,6 +19,7 @@ extern "C" { #endif #include "main.h" +#include "bsp_err.h" /* bsp_err_t / BSP_WAIT_*:对外只报统一错误码(2026-10-22 试点) */ /* * 常量定义 @@ -65,12 +66,14 @@ void rs485_init(rs485_handle_t *p_handle, * data - 待发送数据缓冲区 * len - 待发送字节数 * timeout - 发送超时(ms),传 0 使用 RS485_TIMEOUT_DEFAULT - * 返回值:HAL_OK / HAL_TIMEOUT / HAL_ERROR - * 限定条件:在主循环或任务中调用,不可在 UART 中断中调用 + * 返回值:BSP_OK - 成功;BSP_ERR_PARAM - 参数非法;BSP_ERR_TIMEOUT - 发送超时(可重试, + * 见 BSP_ERR_CAN_RETRY);BSP_ERR_IO - 总线错误 + * 限定条件:在主循环或任务中调用,不可在 UART 中断中调用;timeout 单位是毫秒, + * 传 0 用 RS485_TIMEOUT_DEFAULT(不许拿 0xFFFFFFFF 当"无限",用 BSP_WAIT_FOREVER) * 函数说明:发送前自动拉高 DE,发送完成后等待 TC 标志再拉低 DE, * 确保最后一字节完全移出后再切换到接收状态 */ -HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, +bsp_err_t rs485_transmit(rs485_handle_t *p_handle, const uint8_t *p_data, uint16_t len, uint32_t timeout); @@ -78,7 +81,8 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, /* * 函数功能:启动中断方式接收(IDLE 空闲帧检测) * 入口参数:handle - RS-485 句柄指针 - * 返回值:HAL_OK / HAL_ERROR + * 返回值:BSP_OK - 已启动;BSP_ERR_PARAM - 句柄空;BSP_ERR_BUSY - 接收通道正被占用 + * BSP_ERR_STATE - 接收通道不处于可启动状态 * 限定条件:UART 须已开启全局中断,NVIC 中须使能对应 UART 中断 * 函数说明:使用 HAL_UARTEx_ReceiveToIdle_IT 实现变长帧接收, * 接收缓冲固定使用句柄内部的 rx_buf(RS485_RX_BUF_SIZE)。 @@ -86,7 +90,7 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数。 * 用户须在回调中重新调用本函数重新开启接收。 */ -HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle); +bsp_err_t rs485_receive_start(rs485_handle_t *p_handle); /* * 函数功能:将 RS-485 句柄与 HAL 回调关联 diff --git a/Drivers/BSP/SD2506/sd2506.c b/Drivers/BSP/SD2506/sd2506.c index 9482f787..698b8106 100644 --- a/Drivers/BSP/SD2506/sd2506.c +++ b/Drivers/BSP/SD2506/sd2506.c @@ -37,41 +37,69 @@ uint8_t sd2506_dec_to_bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); } +/* + * 函数功能:把 HAL 的 I2C 状态映射成统一错误码(S-5,2026-10-23) + * 入口参数:status - HAL_I2C_* 返回的状态 + * 返回值:HAL_OK → BSP_OK;HAL_TIMEOUT → BSP_ERR_TIMEOUT(可重试);其余 → BSP_ERR_IO + * 限定条件:仅本文件内部使用 + * 函数说明:**这一映射是本次迁移的实质收益**——旧码 SD2506_I2C_ERROR 把"总线没人应答" + * 和"时钟拉长/超时"合成同一个 -2,调用方想重试也不知道该不该重试;SD2506 又是启动链 + * 第一站(I2C1 上电时序敏感),归出 BSP_ERR_TIMEOUT 之后,上层才谈得上"退避再读一次"。 + */ +static bsp_err_t sd2506_err_from_hal(HAL_StatusTypeDef status) { + switch (status) { + case HAL_OK: + return BSP_OK; + case HAL_TIMEOUT: + return BSP_ERR_TIMEOUT; + case HAL_BUSY: + return BSP_ERR_BUSY; + default: + return BSP_ERR_IO; + } +} + /* * 函数功能:写单字节寄存器 * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 * val - 写入数据 uint8_t 0x00 - 0xFF - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:I2C 外设已初始化 * 函数说明:通过 I2C 向指定地址写入 1 字节数据 * 限定条件:I2C 外设已初始化 * 函数说明:通过 I2C 向指定地址写入 1 字节 */ -static int sd2506_write_reg(uint8_t reg, uint8_t val) { - if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, - I2C_MEMADD_SIZE_8BIT, &val, 1, - SD2506_I2C_TIMEOUT_MS) != HAL_OK) { - return SD2506_I2C_ERROR; +static bsp_err_t sd2506_write_reg(uint8_t reg, uint8_t val) { + HAL_StatusTypeDef status; + + status = HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, + I2C_MEMADD_SIZE_8BIT, &val, 1, + SD2506_I2C_TIMEOUT_MS); + if (status != HAL_OK) { + return sd2506_err_from_hal(status); } - return SD2506_OK; + return BSP_OK; } /* * 函数功能:读单字节寄存器 * 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79 * p_val - 读取值指针 uint8_t*(非空) - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:I2C 外设已初始化,p_val 为非空指针 * 函数说明:通过 I2C 从指定地址读取 1 字节数据 * 限定条件:I2C 外设已初始化,val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节 */ -static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { - if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, - I2C_MEMADD_SIZE_8BIT, p_val, 1, - SD2506_I2C_TIMEOUT_MS) != HAL_OK) { - return SD2506_I2C_ERROR; +static bsp_err_t sd2506_read_reg(uint8_t reg, uint8_t *p_val) { + HAL_StatusTypeDef status; + + status = HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, + I2C_MEMADD_SIZE_8BIT, p_val, 1, + SD2506_I2C_TIMEOUT_MS); + if (status != HAL_OK) { + return sd2506_err_from_hal(status); } - return SD2506_OK; + return BSP_OK; } /* @@ -79,18 +107,21 @@ static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 * p_data - 数据缓冲区指针 uint8_t*(非空) * len - 数据长度 uint8_t 1 - N - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:I2C 外设已初始化,p_data 为非空指针 * 函数说明:从 reg 开始连续写入 len 字节数据 * 限定条件:I2C 外设已初始化,data 为非空指针 */ -static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { - if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, - I2C_MEMADD_SIZE_8BIT, (uint8_t *)p_data, len, - SD2506_I2C_TIMEOUT_MS) != HAL_OK) { - return SD2506_I2C_ERROR; +static bsp_err_t sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { + HAL_StatusTypeDef status; + + status = HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, + I2C_MEMADD_SIZE_8BIT, (uint8_t *)p_data, len, + SD2506_I2C_TIMEOUT_MS); + if (status != HAL_OK) { + return sd2506_err_from_hal(status); } - return SD2506_OK; + return BSP_OK; } /* @@ -98,59 +129,62 @@ static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { * 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79 * p_data - 数据缓冲区指针 uint8_t*(非空) * len - 数据长度 uint8_t 1 - N - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:I2C 外设已初始化,p_data 为非空指针 * 函数说明:从 reg 开始连续读取 len 字节数据 * 限定条件:I2C 外设已初始化,data 为非空指针 */ -static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { - if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, - I2C_MEMADD_SIZE_8BIT, p_data, len, - SD2506_I2C_TIMEOUT_MS) != HAL_OK) { - return SD2506_I2C_ERROR; +static bsp_err_t sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { + HAL_StatusTypeDef status; + + status = HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, + I2C_MEMADD_SIZE_8BIT, p_data, len, + SD2506_I2C_TIMEOUT_MS); + if (status != HAL_OK) { + return sd2506_err_from_hal(status); } - return SD2506_OK; + return BSP_OK; } /* * 函数功能:开启写保护(允许写入 00H~71H 寄存器) * 入口参数:无 - * 返回值:0 - 成功 2 - I2C 错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO / BSP_ERR_TIMEOUT - I2C 失败 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1 */ -static int sd2506_write_enable(void) { - int ret = 0; +static bsp_err_t sd2506_write_enable(void) { + bsp_err_t ret = BSP_OK; /* 写允许顺序(SD2506/SD24xx 手册要求):先置 WRTC1(CTR2 bit7)=1, * 再置 WRTC2(CTR1 bit2) + WRTC3(CTR1 bit7)=1。顺序反了芯片不会解开写保护。 */ ret = sd2506_write_reg(SD2506_REG_CTR2, 0x80U); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; - return SD2506_OK; + return BSP_OK; } /* * 函数功能:关闭写保护(禁止写入 00H~71H 寄存器) * 入口参数:无 - * 返回值:0 - 成功 2 - I2C 错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO / BSP_ERR_TIMEOUT - I2C 失败 * 限定条件:I2C 外设已初始化 * 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0 */ -static int sd2506_write_disable(void) { - int ret = 0; +static bsp_err_t sd2506_write_disable(void) { + bsp_err_t ret = BSP_OK; /* 写禁止顺序(手册要求):先清 WRTC2/WRTC3(CTR1),再清 WRTC1(CTR2) */ ret = sd2506_write_reg(SD2506_REG_CTR1, 0x00U); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; - return SD2506_OK; + return BSP_OK; } /* ======================== 公共 API 实现 ======================== */ @@ -165,13 +199,13 @@ static int sd2506_write_disable(void) { * 窗口重置并回读验证(手册 §4.8:强烈建议每次上电重置 18H) * 3. 上电即写禁止态(WRTC 全 0),无需再写 0FH */ -int sd2506_init(void) { - int ret = 0; +bsp_err_t sd2506_init(void) { + bsp_err_t ret = BSP_OK; uint8_t a_id[8] = {0}; /* 验证 I2C 通信: 尝试读取 8 字节 ID */ ret = sd2506_read_regs(SD2506_REG_ID_START, a_id, SD2506_ID_SIZE); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { /* 启动段可观测性(2026-10-02 S10):本函数原先所有失败路径均静默返回, * 而它是启动死机最可疑的源头(I2C1 硬件不通即卡此步且无任何输出)。 * 日志字符串必须纯 ASCII(文件无 BOM,ARMCC 按 CP936 解码)。 */ @@ -186,22 +220,22 @@ int sd2506_init(void) { uint8_t charge_val = 0; ret = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { DBG_ERROR("sd2506_init: charge reg read FAIL (err=%d)", ret); return ret; } if (charge_val != SD2506_CHARGE_INIT_VALUE) { - int ret_read = 0; + bsp_err_t ret_read = BSP_OK; ret = sd2506_write_enable(); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { DBG_ERROR("sd2506_init: write-enable FAIL (err=%d)", ret); return ret; } ret = sd2506_write_reg(SD2506_REG_CHARGE, SD2506_CHARGE_INIT_VALUE); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { (void)sd2506_write_disable(); DBG_ERROR("sd2506_init: charge reg write FAIL (err=%d)", ret); return ret; @@ -209,15 +243,16 @@ int sd2506_init(void) { ret_read = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val); ret = sd2506_write_disable(); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { DBG_ERROR("sd2506_init: write-disable FAIL (err=%d)", ret); return ret; } - if (ret_read != SD2506_OK || charge_val != SD2506_CHARGE_INIT_VALUE) { + if (ret_read != BSP_OK || charge_val != SD2506_CHARGE_INIT_VALUE) { DBG_ERROR("sd2506_init: charge reg verify FAIL (read=%d val=0x%02X)", ret_read, charge_val); - return SD2506_ERROR; + /* 回读校验不符 = 通信/介质类故障,不是调用方的参数错(旧码把两者都写成 -1) */ + return BSP_ERR_IO; } DBG_INFO("sd2506_init: charge reg 18H reset to 0x%02X (was drifted)", @@ -225,7 +260,7 @@ int sd2506_init(void) { } DBG_INFO("sd2506_init: OK"); - return SD2506_OK; + return BSP_OK; } /* @@ -237,12 +272,12 @@ int sd2506_init(void) { * 2. 一次性写入 7 字节时间数据 (00H~06H) * 3. 关闭写保护 */ -int sd2506_set_time(const sd2506_time_t *p_time) { - int ret = 0; +bsp_err_t sd2506_set_time(const sd2506_time_t *p_time) { + bsp_err_t ret = BSP_OK; uint8_t buf[7] = {0}; if (p_time == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } /* 组装 7 字节时间数据 (BCD 码) */ @@ -256,40 +291,40 @@ int sd2506_set_time(const sd2506_time_t *p_time) { /* 开启写保护 */ ret = sd2506_write_enable(); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 一次性写入 7 字节时间数据 (00H~06H) * 手册要求: 不可单独写某一个时间寄存器,否则可能引起错误进入 */ ret = sd2506_write_regs(SD2506_REG_SEC, buf, 7); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 关闭写保护 */ ret = sd2506_write_disable(); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; - return SD2506_OK; + return BSP_OK; } /* * 函数功能:读取 RTC 时间日期 * 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空) - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误 * note: sd2506_init() must be called first */ -int sd2506_get_time(sd2506_time_t *p_time) { - int ret = 0; +bsp_err_t sd2506_get_time(sd2506_time_t *p_time) { + bsp_err_t ret = BSP_OK; uint8_t buf[7] = {0}; if (p_time == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } /* 一次读取 7 字节时间数据 (00H~06H) * 手册说明: 读取时所有实时数据被锁存,避免错误 */ ret = sd2506_read_regs(SD2506_REG_SEC, buf, 7); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { return ret; } @@ -301,28 +336,28 @@ int sd2506_get_time(sd2506_time_t *p_time) { p_time->month = sd2506_bcd_to_dec(buf[5] & 0x1FU); /* 05H: 月 */ p_time->year = sd2506_bcd_to_dec(buf[6]) + 2000U; /* 06H: 年 */ - return SD2506_OK; + return BSP_OK; } /* * 函数功能:读取内部温度 * 入口参数:p_temp - 输出温度值指针 int8_t*(非空) - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:读取寄存器 0x16,bit7 为符号位 - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) * note: reads register 0x16 */ -int sd2506_get_temperature(int8_t *p_temp) { - int ret = 0; +bsp_err_t sd2506_get_temperature(int8_t *p_temp) { + bsp_err_t ret = BSP_OK; uint8_t val = 0; if (p_temp == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } ret = sd2506_read_reg(SD2506_REG_TEMP, &val); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { return ret; } @@ -335,54 +370,54 @@ int sd2506_get_temperature(int8_t *p_temp) { *p_temp = (int8_t)(val & 0x7FU); } - return SD2506_OK; + return BSP_OK; } /* * 函数功能:读取电池电压(毫伏) * 入口参数:p_voltage - 输出电压指针 uint16_t*(非空) - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:组合 9 位值 (bit8=BAT8_VAL, bit7~0=BAT_VL),转换为毫伏 - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) * note: combines 9-bit value */ -int sd2506_get_battery_voltage(uint16_t *p_voltage) { - int ret = 0; +bsp_err_t sd2506_get_battery_voltage(uint16_t *p_voltage) { + bsp_err_t ret = BSP_OK; uint8_t val_high = 0; uint8_t val_low = 0; if (p_voltage == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } /* 读取 1AH bit7 (BAT8_VAL) 与 1BH (BAT_VL) */ ret = sd2506_read_reg(SD2506_REG_CTR5, &val_high); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; ret = sd2506_read_reg(SD2506_REG_BAT_VAL, &val_low); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 组合 9 位值 bit8=BAT8_VAL(1AH bit7), bit7~0=BAT_VL(1BH) * 例: 1AH=80H, 1BH=30H => 电压 = 0x130 = 304 => 3.04V => 3040mV */ uint16_t raw = ((val_high & 0x80U) << 1) | val_low; *p_voltage = raw * 10; /* 转换为毫伏 (raw 单位 0.01V) */ - return SD2506_OK; + return BSP_OK; } /* * 函数功能:读取 8 字节芯片 ID * 入口参数:p_id - 输出缓冲区指针 uint8_t[8](非空) - * 返回值:0-成功,2-I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:读取寄存器 0x72~0x79 - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) * note: reads registers 0x72-0x79 */ -int sd2506_get_id(uint8_t p_id[8]) { +bsp_err_t sd2506_get_id(uint8_t p_id[8]) { if (p_id == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } return sd2506_read_regs(SD2506_REG_ID_START, p_id, SD2506_ID_SIZE); @@ -396,16 +431,16 @@ int sd2506_get_id(uint8_t p_id[8]) { * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) * 函数说明:SRAM 范围 0x2C~0x71 - * return: 0=OK, -1=param err, -2=I2C err + * return: BSP_OK / BSP_ERR_PARAM / BSP_ERR_IO / BSP_ERR_TIMEOUT * note: SRAM range 0x2C-0x71 */ -int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) { +bsp_err_t sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) { if (p_buf == NULL || len == 0 || addr >= SD2506_SRAM_SIZE) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } if (addr + len > SD2506_SRAM_SIZE) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } return sd2506_read_regs(SD2506_REG_SRAM_START + addr, p_buf, len); @@ -419,25 +454,25 @@ int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) { * 返回值:0-成功,-1=参数错误,-2=I2C 错误 int * 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70) * 函数说明:SRAM 写入需走写保护流程(虽 SRAM 本身无需) - * return: 0=OK, -1=param err, -2=I2C err + * return: BSP_OK / BSP_ERR_PARAM / BSP_ERR_IO / BSP_ERR_TIMEOUT */ -int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { +bsp_err_t sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { if (p_buf == NULL || len == 0 || addr >= SD2506_SRAM_SIZE) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } if (addr + len > SD2506_SRAM_SIZE) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } /* SRAM 无需开写保护即可写入 (写保护仅对 00H~71H 有效, SRAM 为 2CH~71H) * 但为安全起见, SRAM 写入也走写保护流程 */ - int ret = 0; + bsp_err_t ret = BSP_OK; ret = sd2506_write_enable(); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; ret = sd2506_write_regs(SD2506_REG_SRAM_START + addr, p_buf, len); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; ret = sd2506_write_disable(); return ret; @@ -447,17 +482,17 @@ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { * 函数功能:设置闹钟中断 * 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空) * mask - 报警匹配掩码 uint8_t - * 返回值:0-成功,-2=I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) */ -int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { - int ret = 0; +bsp_err_t sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { + bsp_err_t ret = BSP_OK; uint8_t buf[8] = {0}; if (p_time == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } /* 组装 8 字节报警数据 (07H~0EH) */ @@ -472,42 +507,42 @@ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { /* 开启写保护 */ ret = sd2506_write_enable(); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 写入报警寄存器 (07H~0EH) */ ret = sd2506_write_regs(SD2506_REG_AL_SEC, buf, 8); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 使能报警中断: CTR2 中 INTAE=1, INTS1=0, INTS0=1, IM=1 (周期中断) */ ret = sd2506_write_reg(SD2506_REG_CTR2, SD2506_CTR2_INTAE | SD2506_CTR2_INTS0 | SD2506_CTR2_IM); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; /* 关闭写保护 */ ret = sd2506_write_disable(); - if (ret != SD2506_OK) return ret; + if (ret != BSP_OK) return ret; - return SD2506_OK; + return BSP_OK; } /* * 函数功能:清除闹钟中断标志 * 入口参数:无 - * 返回值:0-成功,-2=I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 * 函数说明:ARST 位于 CTR3(11H) bit7,本驱动从未写 CTR3 → ARST=0, * 故读 CTR1 不会自动清 INTAF(手册 L519/L523),须手动写 CTR1 将 INTAF 置 0; * 写 0FH 须在写允许窗口内且保持 WRTC2/WRTC3=1(手册 L521), * 故写入值取写允许态 0xFF 清 INTAF 位后的 0xDF。 - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) */ -int sd2506_clear_alarm(void) { - int ret = 0; - int ret_off = 0; +bsp_err_t sd2506_clear_alarm(void) { + bsp_err_t ret = BSP_OK; + bsp_err_t ret_off = BSP_OK; /* 写 0FH 须在写允许窗口内(手册 L519),且 WRTC2/WRTC3 必须保持 1(L521) */ ret = sd2506_write_enable(); - if (ret != SD2506_OK) { + if (ret != BSP_OK) { return ret; } @@ -515,7 +550,7 @@ int sd2506_clear_alarm(void) { (uint8_t)(SD2506_CTR1_WRITE_ON & (uint8_t)~SD2506_CTR1_INTAF)); ret_off = sd2506_write_disable(); - if (ret == SD2506_OK) { + if (ret == BSP_OK) { ret = ret_off; } @@ -525,13 +560,13 @@ int sd2506_clear_alarm(void) { /* * 函数功能:读取控制寄存器 1 (CTR1) * 入口参数:p_val - 输出值指针 uint8_t*(非空) - * 返回值:0-成功,-2=I2C 错误 int + * 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t * 限定条件:sd2506_init() 已调用成功 - * return: 0=OK, -2=I2C error + * return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable) */ -int sd2506_read_ctr1(uint8_t *p_val) { +bsp_err_t sd2506_read_ctr1(uint8_t *p_val) { if (p_val == NULL) { - return SD2506_ERROR; + return BSP_ERR_PARAM; } return sd2506_read_reg(SD2506_REG_CTR1, p_val); diff --git a/Drivers/BSP/SD2506/sd2506.h b/Drivers/BSP/SD2506/sd2506.h index a64e8f58..579965a9 100644 --- a/Drivers/BSP/SD2506/sd2506.h +++ b/Drivers/BSP/SD2506/sd2506.h @@ -136,11 +136,15 @@ extern "C" { * CTR2 需另行清除;ARST 在 CTR3 与本寄存器无关) */ #define SD2506_CTR1_WRITE_OFF 0x7BU -/* ======================== 返回值定义 ======================== */ +/* ======================== 返回值定义 ======================== + * 本模块**不再自带错误码**(2026-10-23,S-5):统一走 Drivers/BSP/bsp_err.h。 + * 旧码映射 —— SD2506_OK → BSP_OK;SD2506_ERROR(参数类)→ BSP_ERR_PARAM; + * SD2506_I2C_ERROR → BSP_ERR_IO,其中 I2C 超时刻意单独归成 **BSP_ERR_TIMEOUT + * (可重试)**:旧码把"总线上没人应答"和"超时"混成一个 -2,调用方没法规避。 + * 三处旧宏已**删除而不是留别名**——留着就还能被新代码继续用,等于把迁移洗掉。 + */ -#define SD2506_OK 0 -#define SD2506_ERROR -1 -#define SD2506_I2C_ERROR -2 +#include "bsp_err.h" /* ======================== 超时时间 ======================== */ @@ -173,118 +177,120 @@ typedef struct { /* * 函数功能:初始化 SD2506API-G RTC * 入口参数:无 - * 返回值:0 - 成功,-1 - 校验失败,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 失败或回读校验不符;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:CubeMX 已完成 I2C1 初始化 * 函数说明:1. 读取芯片 ID 验证通信 * 2. 充电寄存器 18H 回读校验(出厂 82H 非易失),值漂移时经写允许 * 窗口重置并回读验证 * 3. 上电即写禁止态(WRTC 全 0),无需再写 0FH */ -int sd2506_init(void); +bsp_err_t sd2506_init(void); /* * 函数功能:设置 RTC 时间日期 * 入口参数:time - 时间结构体指针,包含要设置的时间 - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:1. 先开写保护 * 2. 一次性写入 7 字节时间数据 (00H~06H) * 3. 关闭写保护 * 注意:不可单独写某一个时间寄存器 */ -int sd2506_set_time(const sd2506_time_t *p_time); +bsp_err_t sd2506_set_time(const sd2506_time_t *p_time); /* * 函数功能:读取 RTC 时间日期 * 入口参数:time - 时间结构体指针,用于存储读取结果 - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:1. 一次读取 7 字节时间数据 (00H~06H) * 2. BCD 转十进制 * 3. 屏蔽小时 bit7 (12/24标志位) */ -int sd2506_get_time(sd2506_time_t *p_time); +bsp_err_t sd2506_get_time(sd2506_time_t *p_time); /* * 函数功能:读取芯片内部温度 * 入口参数:temp - 温度输出指针 (整数部分, 有符号) - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:读取 16H 寄存器,bit7 为符号位,范围 -40~+85 */ -int sd2506_get_temperature(int8_t *p_temp); +bsp_err_t sd2506_get_temperature(int8_t *p_temp); /* * 函数功能:读取电池电压 (毫伏) * 入口参数:voltage - 电压输出指针 (单位: mV) - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:1. 读取 1AH bit7 (BAT8_VAL) 和 1BH (BAT_VL) * 2. 组合 9 位数据得到电压值 (如 0x135 = 309 = 3.09V) */ -int sd2506_get_battery_voltage(uint16_t *p_voltage); +bsp_err_t sd2506_get_battery_voltage(uint16_t *p_voltage); /* * 函数功能:读取芯片 8 字节 ID * 入口参数:id - 8 字节输出缓冲区 - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:读取 72H~79H 共 8 字节唯一 ID */ -int sd2506_get_id(uint8_t p_id[8]); +bsp_err_t sd2506_get_id(uint8_t p_id[8]); /* * 函数功能:读取用户 SRAM 数据 * 入口参数:addr - SRAM 起始地址 (0~69) * buf - 数据输出缓冲区 * len - 读取长度 (1~70) - * 返回值:0 - 成功,-2 - I2C 通信错误,-1 - 参数错误 + * 返回值:BSP_OK - 成功;BSP_ERR_PARAM - 参数错误;BSP_ERR_IO - I2C 通信失败; + * BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:SRAM 地址范围 0~69,对应寄存器 2CH~71H */ -int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len); +bsp_err_t sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len); /* * 函数功能:写入用户 SRAM 数据 * 入口参数:addr - SRAM 起始地址 (0~69) * buf - 数据输入缓冲区 * len - 写入长度 (1~70) - * 返回值:0 - 成功,-2 - I2C 通信错误,-1 - 参数错误 + * 返回值:BSP_OK - 成功;BSP_ERR_PARAM - 参数错误;BSP_ERR_IO - I2C 通信失败; + * BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:SRAM 地址范围 0~69,对应寄存器 2CH~71H * SRAM 无需开写保护即可写入 */ -int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len); +bsp_err_t sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len); /* * 函数功能:设置报警中断 * 入口参数:time - 报警时间结构体 * mask - 报警匹配掩码 (SD2506_AL_EN_xxx 位或组合) - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:1. 开写保护 * 2. 写入报警寄存器 (07H~0EH) * 3. 使能报警中断 INTAE * 4. 关写保护 */ -int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask); +bsp_err_t sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask); /* * 函数功能:清除报警中断标志 INTAF * 入口参数:无 - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) * 限定条件:sd2506_init() 已调用 * 函数说明:ARST(CTR3 bit7) 未启用,读 CTR1 不清 INTAF;本函数经写允许窗口 * 手动写 CTR1 将 INTAF 置 0 */ -int sd2506_clear_alarm(void); +bsp_err_t sd2506_clear_alarm(void); /* * 函数功能:读取当前 0FH 控制寄存器1 状态 * 入口参数:val - 输出值指针 - * 返回值:0 - 成功,-2 - I2C 通信错误 + * 返回值:BSP_OK - 成功;BSP_ERR_IO - I2C 通信失败;BSP_ERR_TIMEOUT - I2C 超时(可重试) */ -int sd2506_read_ctr1(uint8_t *p_val); +bsp_err_t sd2506_read_ctr1(uint8_t *p_val); /* * 函数功能:BCD 码转十进制 diff --git a/Drivers/BSP/TPAFE5160/README.md b/Drivers/BSP/TPAFE5160/README.md index 429b4760..f017462c 100644 --- a/Drivers/BSP/TPAFE5160/README.md +++ b/Drivers/BSP/TPAFE5160/README.md @@ -19,7 +19,7 @@ - **周期重触发由 TIM3 更新事件(1 kHz)驱动**(2026-10-10):`app_main_init` 末尾 `HAL_TIM_Base_Start_IT(&htim3)` 启动节拍,`Src/main.c` 的 `HAL_TIM_PeriodElapsedCallback` 在 TIM3 分支内调 `tpafe5160_start_conv_irq()`(ISR 内仅两次 GPIO 写)。B1 阶段曾由 `adcTask` 每 1 ms 软触发(已改)。⚠ **节拍源必须是 TIM3 而非 TIM6**:TIM3 的同一个更新事件还经 **TRGO** 硬件触发 STM32 ADC1,两片转换器由此同源对齐;F4 上 TIM6/TIM7 的 TRGO 只接 DAC,喂不到 ADC1 - **取数只用 `tpafe5160_snapshot(int16_t *dst, uint8_t count)`**(**仅任务上下文**,内部临界区原子拷贝;返回 1=新帧 / 0=无新帧 / -1=参数无效)。配套的 `tpafe5160_frame_seq()` 给帧序号、`tpafe5160_frstdata_err()` 给"FRSTDATA 无跳变"帧计数 - ⚠️ **不要用缓冲区指针取数**:旧的 `get_buf()` / `data_ready()` / `clear_ready()` 已于 2026-10-10 **删除**(审计 E-45 / E-58)——`get_buf` 只给指针、任务再逐通道拷,中间被 BUSY EXTI 抢占就拷回**两帧混合**数据;`s_ready` 电平标志在 `start_conv_irq` 里被清 0,等于**用重触发去丢帧**。现由**帧序号**(只增不清)+ **临界区快照**取代 -- **`tpafe5160_init()` 做首帧有效性自检**(E-44):完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `TPAFE5160_FRSTDATA_ERR`(-3) 并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片 +- **`tpafe5160_init()` 做首帧有效性自检**(E-44):完整跑一帧转换+读取,若整帧读下来 FRSTDATA **没有发生任何跳变**(芯片没在驱动并行总线;板上上拉会把它钉死为高)→ 返回 `BSP_ERR_NO_DEVICE`(旧 `TPAFE5160_FRSTDATA_ERR(-3)`,2026-10-23 S-5 收编)并打 `[ERR] Init FRSTDATA no-toggle`。判据刻意不锁死"哪个 RD 边沿拉高/拉低"(手册只给"RD 下降沿 → FRSTDATA 高/低"两个跳变,本板未实测边沿次序),只要求"一帧内必须跳变" → 既抓死芯片,也不会因极性假设错误误杀好芯片 - 采样数据的消费者 `App/task/adc_task.c` **已实现**(B1,2026-10-10:`tpafe5160_snapshot()` 原子取数 + 帧统计;`adcTask` 在 `Src/freertos.c` 的 `USER CODE` 区创建)。**v2.2(2026-10-10)**:采样节拍改由定时器更新中断硬件触发,本任务退化为纯消费者;**v2.8(2026-10-10)**:节拍源改 TIM3(同时经 TRGO 触发 ADC1 + DMA),STM32 侧改为消费 DMA 乒乓缓冲,单拍耗时 122 µs → **8 µs** - CONVST→BUSY→RD 三段时序对**建立/保持时间敏感**,改动需对照芯片手册 - ⚠️ 本项目 ADC 相关功能**新增任务/缓冲前须核算 RAM**(见 `docs/内存管理/`);接消费者前的强制门禁见根 `AGENTS.md`「接 ADC / RS-485 消费者前的强制门禁」 diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.c b/Drivers/BSP/TPAFE5160/tpafe5160.c index 72ba84e4..cb04e1d1 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.c +++ b/Drivers/BSP/TPAFE5160/tpafe5160.c @@ -61,7 +61,7 @@ /* ======================== 私有函数声明 ======================== */ -static int tpafe5160_read_channels(uint8_t count, int16_t *buf); +static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf); /* ======================== 公共函数定义 ======================== */ @@ -76,7 +76,7 @@ static int tpafe5160_read_channels(uint8_t count, int16_t *buf); * 4. 采一帧并校验 FRSTDATA 有跳变(E-44:旧实现只看 BUSY,芯片死亡 * 时照样报 Init OK,此后所有读数都是上拉/总线残值——静默死亡) */ -int tpafe5160_init(void) { +bsp_err_t tpafe5160_init(void) { /* 设置默认过采样:无过采样 (OS[2:0] = 000) */ tpafe5160_set_os(TPAFE5160_OS_NONE); @@ -86,7 +86,7 @@ int tpafe5160_init(void) { /* 等待 BUSY 释放,确保上电后无残留转换 */ int ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); - if (ret != TPAFE5160_OK) { + if (ret != BSP_OK) { DBG_ERROR("Init wait BUSY timeout"); return ret; } @@ -95,12 +95,12 @@ int tpafe5160_init(void) { int16_t probe[TPAFE5160_CH_NUM]; tpafe5160_start_conv(); ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); - if (ret != TPAFE5160_OK) { + if (ret != BSP_OK) { DBG_ERROR("Init probe BUSY timeout"); return ret; } - if (tpafe5160_read_channels(TPAFE5160_CH_NUM, probe) != TPAFE5160_OK) { - ret = TPAFE5160_FRSTDATA_ERR; + if (tpafe5160_read_channels(TPAFE5160_CH_NUM, probe) != BSP_OK) { + ret = BSP_ERR_NO_DEVICE; DBG_ERROR("Init FRSTDATA no-toggle (chip not driving DB bus)"); return ret; } @@ -140,17 +140,17 @@ void tpafe5160_start_conv(void) { * 限定条件:已调用 tpafe5160_start_conv() * 函数说明:轮询 BUSY 引脚等待下降沿 */ -int tpafe5160_wait_busy(uint32_t timeout_ms) { +bsp_err_t tpafe5160_wait_busy(uint32_t timeout_ms) { uint32_t tick_start = HAL_GetTick(); /* 等待 BUSY 释放(低电平表示空闲) */ while (TPAFE5160_BUSY_READ() == GPIO_PIN_SET) { if ((HAL_GetTick() - tick_start) >= timeout_ms) { - return TPAFE5160_BUSY_TIMEOUT; + return BSP_ERR_TIMEOUT; } } - return TPAFE5160_OK; + return BSP_OK; } /* @@ -173,11 +173,11 @@ uint8_t tpafe5160_is_busy(void) { * 2. 连续8次拉低RD读取各通道数据 * 3. 校验 FRSTDATA 在这一帧内发生跳变(E-44) */ -int tpafe5160_read_all(int16_t *buf) { - int ret = 0; +bsp_err_t tpafe5160_read_all(int16_t *buf) { + bsp_err_t ret = BSP_OK; if (buf == NULL) { - return TPAFE5160_ERROR; + return BSP_ERR_PARAM; } /* 启动转换 */ @@ -185,19 +185,19 @@ int tpafe5160_read_all(int16_t *buf) { /* 等待转换完成 */ ret = tpafe5160_wait_busy(TPAFE5160_CONV_TIMEOUT_MS); - if (ret != TPAFE5160_OK) { + if (ret != BSP_OK) { DBG_ERROR("read_all BUSY timeout"); return ret; } /* 连续读取8个通道(内含 FRSTDATA 有效性校验) */ ret = tpafe5160_read_channels(TPAFE5160_CH_NUM, buf); - if (ret != TPAFE5160_OK) { + if (ret != BSP_OK) { DBG_ERROR("read_all FRSTDATA no-toggle (data not trustworthy)"); - return TPAFE5160_FRSTDATA_ERR; + return BSP_ERR_NO_DEVICE; } - return TPAFE5160_OK; + return BSP_OK; } /* @@ -208,21 +208,21 @@ int tpafe5160_read_all(int16_t *buf) { * 限定条件:GPIO 已初始化 * 函数说明:启动转换并等待完成后,连续读取至指定通道 */ -int tpafe5160_read_channel(uint8_t channel, int16_t *value) { - int ret = 0; +bsp_err_t tpafe5160_read_channel(uint8_t channel, int16_t *value) { + bsp_err_t ret = BSP_OK; int16_t buf[TPAFE5160_CH_NUM] = {0}; if (channel >= TPAFE5160_CH_NUM || value == NULL) { - return TPAFE5160_ERROR; + return BSP_ERR_PARAM; } ret = tpafe5160_read_all(buf); - if (ret != TPAFE5160_OK) { + if (ret != BSP_OK) { return ret; } *value = buf[channel]; - return TPAFE5160_OK; + return BSP_OK; } /* @@ -274,7 +274,7 @@ uint16_t tpafe5160_read_bus(void) { * 旧实现完全不看这根脚(宏 TPAFE5160_FRSTDATA_READ 有定义、代码零引用), * 却在注释里承诺"通过 FRSTDATA 验证第一通道",芯片死亡时返回 OK + 常量垃圾。 */ -static int tpafe5160_read_channels(uint8_t count, int16_t *buf) { +static bsp_err_t tpafe5160_read_channels(uint8_t count, int16_t *buf) { uint8_t i; uint8_t lvl0 = (TPAFE5160_FRSTDATA_READ() == GPIO_PIN_SET) ? 1u : 0u; uint8_t toggled = 0; @@ -295,7 +295,7 @@ static int tpafe5160_read_channels(uint8_t count, int16_t *buf) { } } - return toggled ? TPAFE5160_OK : TPAFE5160_FRSTDATA_ERR; + return toggled ? BSP_OK : BSP_ERR_NO_DEVICE; } /* ======================== 中断模式实现 ======================== */ @@ -383,7 +383,7 @@ int tpafe5160_snapshot(int16_t *dst, uint8_t count) { uint32_t seq; if (dst == NULL || count == 0u || count > TPAFE5160_CH_NUM) { - return TPAFE5160_ERROR; + return BSP_ERR_PARAM; } seq = s_frame_seq; @@ -427,7 +427,7 @@ void tpafe5160_exti_handler(uint16_t GPIO_Pin) { s_frame_seq++; /* E-44:中断里不打日志,只累计;消费者据 tpafe5160_frstdata_err() 上报自检 */ - if (ret != TPAFE5160_OK && s_frstdata_err < 0xFFFFu) { + if (ret != BSP_OK && s_frstdata_err < 0xFFFFu) { s_frstdata_err++; } } diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.h b/Drivers/BSP/TPAFE5160/tpafe5160.h index 3cb54a11..2686c65c 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.h +++ b/Drivers/BSP/TPAFE5160/tpafe5160.h @@ -69,12 +69,16 @@ typedef enum { } tpafe5160_range_t; /* 返回值定义 */ -#define TPAFE5160_OK 0 -#define TPAFE5160_ERROR -1 -#define TPAFE5160_BUSY_TIMEOUT -2 -/* 整帧读完 FRSTDATA 无跳变 = 芯片没在驱动数据线(死亡 / 未响应 RD), - * 此时读回的是上拉/总线残值而非转换结果(E-44,见 read_channels 注释) */ -#define TPAFE5160_FRSTDATA_ERR -3 +/* 本模块的成败码已收编到 Drivers/BSP/bsp_err.h(2026-10-23,S-5 C1): + * 旧 TPAFE5160_OK(0) → BSP_OK;旧 TPAFE5160_ERROR(-1) → BSP_ERR_PARAM(三处全是参数校验, + * 数值也同为 -1,**行为零变化**);旧 TPAFE5160_BUSY_TIMEOUT(-2) → BSP_ERR_TIMEOUT, + * 于是 BUSY 不释放终于变成跨模块可判定的"可重试"; + * 旧 TPAFE5160_FRSTDATA_ERR(-3) → BSP_ERR_NO_DEVICE——它就是 E-44 的"芯片没驱动 DB 总线", + * 语义是**器件不在响应**,归到 NO_DEVICE 比留在私有 -3 更有用(且避开与 BSP_ERR_TIMEOUT 撞值)。 + * ⚠ 例外:**tpafe5160_snapshot() 不是成败 API**,它是 1=有新帧 / 0=无新帧 / -1=参数错 的 + * 三态**状态**返回(0 不是错误,是"这一拍没有新数据"),因此**不套 bsp_err_t**, + * 只把参数错那一支写成 BSP_ERR_PARAM(同值)。强行收编会把"无新帧"变成失败。 */ +#include "bsp_err.h" /* 默认超时时间 (ms) */ #define TPAFE5160_CONV_TIMEOUT_MS 10 @@ -84,13 +88,13 @@ typedef enum { /* * 函数功能:初始化 TPAFE5160(设置过采样、等待就绪) * 入口参数:无 - * 返回值:0 - 成功,-2 - BUSY 超时 + * 返回值:BSP_OK - 成功;BSP_ERR_TIMEOUT - BUSY 不释放(可重试) * 限定条件:CubeMX 已完成 GPIO 初始化 * 函数说明:1. 设置过采样为无过采样 (000) * 2. 确保 RD 为高、CONVST 为低 * 3. 等待 BUSY 释放(转换空闲) */ -int tpafe5160_init(void); +bsp_err_t tpafe5160_init(void); /* * 函数功能:设置过采样率 @@ -113,11 +117,11 @@ void tpafe5160_start_conv(void); /* * 函数功能:等待转换完成 * 入口参数:timeout_ms - 超时时间 uint32_t > 0 - * 返回值:0 - 转换完成,-2 - 超时 + * 返回值:BSP_OK - 转换完成;BSP_ERR_TIMEOUT - 超时(可重试:BSP_ERR_CAN_RETRY) * 限定条件:已调用 tpafe5160_start_conv() * 函数说明:轮询 BUSY 引脚等待下降沿 */ -int tpafe5160_wait_busy(uint32_t timeout_ms); +bsp_err_t tpafe5160_wait_busy(uint32_t timeout_ms); /* * 函数功能:查询当前是否正在转换 @@ -131,23 +135,24 @@ uint8_t tpafe5160_is_busy(void); /* * 函数功能:读取全部8通道转换结果 * 入口参数:buf - 8个int16_t的输出缓冲区 int16_t* 不为 NULL - * 返回值:0 - 成功,-2 - BUSY 超时,-3 - FRSTDATA 无跳变(读回值不可信) + * 返回值:BSP_OK - 成功;BSP_ERR_TIMEOUT - BUSY 超时;BSP_ERR_NO_DEVICE - FRSTDATA 无跳变 + * (E-44:芯片没驱动 DB 总线,读回的是上拉残值,不可信) * 限定条件:GPIO 已初始化 * 函数说明:1. 启动转换并等待 BUSY 释放 * 2. 连续8次拉低RD读取各通道数据 * 3. 校验 FRSTDATA 在这一帧内发生过跳变(= 芯片确实在驱动数据线,E-44) */ -int tpafe5160_read_all(int16_t *buf); +bsp_err_t tpafe5160_read_all(int16_t *buf); /* * 函数功能:读取指定通道的转换结果 * 入口参数:channel - 通道号 uint8_t 0 - 7 * value - 输出指针 int16_t* 不为 NULL - * 返回值:0 - 成功,-1 - 通道号无效,-2 - BUSY 超时 + * 返回值:BSP_OK - 成功;BSP_ERR_PARAM - 通道号无效或指针空;BSP_ERR_TIMEOUT - BUSY 超时 * 限定条件:GPIO 已初始化 * 函数说明:启动转换并等待完成后,连续读取至指定通道 */ -int tpafe5160_read_channel(uint8_t channel, int16_t *value); +bsp_err_t tpafe5160_read_channel(uint8_t channel, int16_t *value); /* * 函数功能:原始ADC值转电压值 @@ -213,7 +218,7 @@ uint32_t tpafe5160_frame_seq(void); * 函数功能:原子快照最新一帧(消费者取数的唯一正确姿势) * 入口参数:dst - 输出缓冲区 int16_t* 不为 NULL * count - 拷贝通道数 uint8_t 1 - 8 - * 返回值:1 - 取到新帧,0 - 无新帧,-1 - 参数无效 + * 返回值:1 - 取到新帧,0 - 无新帧,BSP_ERR_PARAM(-1) - 参数无效(**三态状态返回,故意不套成败码**:0 表示"这一拍没有新数据",不是错误) * 限定条件:**任务上下文**调用(内部用 taskENTER_CRITICAL,不可在中断里调) * 函数说明:E-58——旧 get_buf() 只返回缓冲区指针,任务再逐通道拷;两步之间被 BUSY * EXTI 抢占并跨过一帧周期,拷回的就是**两帧混合**的 8 通道数据(同一时刻 diff --git a/Drivers/BSP/bsp_err.h b/Drivers/BSP/bsp_err.h new file mode 100644 index 00000000..df2b0134 --- /dev/null +++ b/Drivers/BSP/bsp_err.h @@ -0,0 +1,139 @@ +/* + * 模块名称:BSP 统一错误与等待约定 + * 模块功能:给 Drivers/BSP 下的所有驱动一个共用的返回码语义与等待时长语义, + * 使「这次失败能不能重试」成为跨模块可判定的信息,而不是每个模块各自猜。 + * 适用平台:STM32F4 系列(ARMCC v5);本头文件不依赖 HAL,模拟器/主机侧同样可编译。 + * 作者:王建锋 + * 创建日期:2026-10-23 + * 修改记录: + * 2026-10-23 王建锋 创建:错误码 + 可重试判定 + 等待值契约(首个试点 RS485;同日挂 bsp-err 钩子) + */ + +#ifndef BSP_ERR_H +#define BSP_ERR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* ============================================================================ + * 一、错误码:0 = 成功,负值 = 失败 + * ==========================================================================*/ + +/* + * 为什么 0 必须是成功:本仓现存调用点绝大多数已经写成 != 0 / == 0(实测跨模块调用 + * 点约 798 处),反向约定要把每个判据都改一遍,纯赔本。 + * + * ⚠ **一个数量与一个错误码不许挤在同一个返回值里**(面向新 API 的原则)。 + * 现存两族,都**有意保留**、都已核对: + * · net_*(返回字节数,0=对端关闭,-1=错误,-2=超时无数据)——刻意的 BSD 形状, + * 已裁定不纳入(见文末边界纪律 3); + * · tpafe5160_snapshot() / adc_input_poll()(1=取到新帧,0=无新帧,BSP_ERR_PARAM=参数错) + * ——**0 不是错误**,是"这一拍没有新数据",套成败码会把慢消费者的正常路径变成失败。 + * 反例订正:gd5f2gq5ue_read/write/erase 核过实现与头注释是"0=成功、其它=错误码", + * **不是**字节数返回,别再拿它当反面例子(本头文件初稿错过这个错)。 + * 新 API 要报"做了多少"就走出参:bsp_err_t xxx(..., size_t *p_done)。 + * 为什么值得写死这条:三态返回(>0 / 0 / <0)要求调用方把三种形态都判到,而实测调用点 + * 大多是"先赋值再判 != 0"——第三种形态就是这么被吞掉的。 + */ +typedef int bsp_err_t; + +#define BSP_OK ((bsp_err_t)0) /* 成功 */ +#define BSP_ERR_PARAM ((bsp_err_t)-1) /* 参数非法:指针空、长度越界 */ +#define BSP_ERR_STATE ((bsp_err_t)-2) /* 调用时机不对:未初始化、状态机不允许 */ +#define BSP_ERR_TIMEOUT ((bsp_err_t)-3) /* 超时 —— 可重试 */ +#define BSP_ERR_IO ((bsp_err_t)-4) /* 总线/介质读写失败 */ +#define BSP_ERR_NO_DEVICE ((bsp_err_t)-5) /* 芯片不在位、无响应(上电时序或硬件) */ +#define BSP_ERR_BUSY ((bsp_err_t)-6) /* 对端或本侧忙 —— 可重试 */ +#define BSP_ERR_CRC ((bsp_err_t)-7) /* 校验失败:ECC / CRC / 和校验 */ +#define BSP_ERR_NO_SPACE ((bsp_err_t)-8) /* 容量或块池耗尽 */ +#define BSP_ERR_DENIED ((bsp_err_t)-9) /* 被策略拒绝:写保护、鉴权、门未开 */ +#define BSP_ERR_PARTIAL ((bsp_err_t)-10) /* 部分完成,配合 *p_done 判断做到哪一步 */ + +/* + * 函数功能:判断一个失败码是否「重试可能成功」 + * 入口参数:e - 待判定的 bsp_err_t + * 返回值:非 0 = 可重试;0 = 重试也没用(参数、状态、策略、无器件这类确定性失败) + * 限定条件:只用于已确认失败的码(传 BSP_OK 得 0,不算错) + * 函数说明:这才是本头文件真正值钱的一条。现状是把「可重试」写在注释里—— + * gd5f2gq5ue.c 的返回值注释就写着「GD5F_BUSY_TIMEOUT(-2) - 超时(可重试)」, + * 语义在,但跨模块不可判定:上层想统一退避,就得为每个驱动各写一遍 if。有了这条, + * 「超时/忙退避、确定性失败立刻放弃」可以只写在一处。不要据此无上限重试: + * 上限、退避节奏、喂看门狗都由调用方负责(见文末约定)。 + */ +#define BSP_ERR_CAN_RETRY(e) (((e) == BSP_ERR_TIMEOUT) || ((e) == BSP_ERR_BUSY)) + +/* ============================================================================ + * 二、等待时长契约:把「这个数到底是多久」写死 + * ==========================================================================*/ + +/* + * 实测现状(同形不同义,这就是本契约存在的全部理由): + * · SD2506_I2C_TIMEOUT_MS 100、CH395F_SPI_BYTE_TIMEOUT_MS 100U + * 真的毫秒:喂 HAL 的 tick 域,本仓 HAL 时基 1ms。 + * · osWaitForever 与 HAL_MAX_DELAY 同为 0xFFFFFFFFU + * 值相同、域不同:一个进 CMSIS-RTOS2、一个进 HAL。于是「把上层拿到的超时值直接 + * 转手传给另一层」这种代码既不报错也不变慢,直到某天真等死。 + * · GD5F_SPI_TIMEOUT 100 + * 不是时间,是自旋圈数(DMA 等待循环)。改 SPI 分频或换主频,实际时长跟着变, + * 而且没有任何地方报警。 + * · GD5F_BUSY_TIMEOUT、TPAFE5160_BUSY_TIMEOUT = -2(**2026-10-23 已消**:前者改名 + * GD5F_WAIT_EXPIRED(值不变),后者收编成 BSP_ERR_TIMEOUT;留此记录是因为 + * "时长宏与返回码共用一个名字形状"这类坑只写规则不会自己消失,得留个实证锚点) + * + * 因此约定: + * 一、公开 API 的超时参数一律 uint32_t 毫秒; + * 二、要无限等就显式传 BSP_WAIT_FOREVER,不许写 0xFFFFFFFFU、-1 或 0(0 是不等); + * 三、自旋圈数型超时不许叫 *_TIMEOUT_MS,命名须含 LOOP/COUNT 且注释写明非时间量; + * 返回码一律不许叫 *_TIMEOUT,改返回 BSP_ERR_TIMEOUT。 + * + * ⚠ 两条硬纪律(AGENTS 铁律,这里只复述到最近的使用点): + * · 内核启动前不得传 BSP_WAIT_FOREVER:osKernelInitialize 之前任何阻塞调用都会挂; + * · 长等待与重试循环必须在循环内喂看门狗(本仓 IWDG 有 required_mask 机制)。 + */ +#define BSP_WAIT_NONE ((uint32_t)0U) /* 不等待,立即返回 */ +#define BSP_WAIT_FOREVER ((uint32_t)0xFFFFFFFFU) /* 无限等待,仅限任务上下文 */ + +/* + * 函数功能:把毫秒表达成超时入参(本仓 tick = 1ms,故等价) + * 入口参数:ms - 毫秒数 + * 返回值:可直接传给等待类 API 的 uint32_t + * 限定条件:仅当 configTICK_RATE_HZ = 1000 时与 tick 数等价 + * 函数说明:留着它的理由是换算点必须只有一处——哪天 tick 改了只改这里,而不是去 + * 各驱动里翻 0xFFFFFFFF 和裸数字。 + */ +#define BSP_WAIT_MS(ms) ((uint32_t)(ms)) + +/* ============================================================================ + * 三、边界纪律(写在这里是为了说清什么算「不该改」) + * ==========================================================================*/ + +/* + * 1. 第三方的码不改、不吞、就地映射:HAL_StatusTypeDef、dhara_error_t、FRESULT、 + * CH395F_ERR_*(芯片寄存器原生码)、FTP 的 226/425/450/550 协议码,全部保留原样, + * 只在跨越我们模块边界那一行映射成 bsp_err_t。它们是外部契约或协议本身,改名等于 + * 伪造语义。映射函数由各驱动自己写一个 static——本头文件刻意不 include HAL, + * 以保持主机侧可编译。 + * 2. 公开 API 面不得返回 HAL_StatusTypeDef 或 bool 表示成败:bool 把「为什么失败」整条 + * 丢掉,HAL 枚举把 HAL 焊进调用方的 include 链。 + * 3. net_* 的 BSD 兼容层保持 BSD 形状(返回字节数 + 每任务 errno 槽),**不进本约定** + * (用户确认 2026-10-22):它的调用方要的就是那个形状,模拟器的 Winsock 后端也照同一 + * 形状实现;归一只到芯片层 ch395f_*。要收口的是"芯片码到 bsp_err_t"这一层,不是把 + * BSD 形状改成另一套——那只会让 net_* 的调用方在每个判据上多绕一道。 + * 4. **丢弃返回值必须写 (void)**。为什么不能靠编译器:实测 ARMCC v5 对 + * __attribute__((warn_unused_result)) 完全不生效——2026-10-23 凌晨在 rs485_transmit 上做过 + * 实验,属性在位、调用点裸调不接返回值,构建仍然 0 错 0 警告。所以这条只能靠门禁脚本 + * (scripts/check_bsp_err.py)与"写 (void) 表示故意丢弃"的书写约定来兜;也意味着 + * 评审时看见没有 (void) 也没接返回值的调用,就是漏判嫌疑。 + * 5. 重试由调用方决定:必须有上限、必须喂看门狗、不得跨越就绪屏障盲目等。本头文件 + * 不提供 retry helper——一个通用 while 重试包装太容易被拿去包住无限等待;每个日志 + * 调用点内联约 216 B 栈的教训在前,栈预算不欢迎「顺手加一层」。 + */ + +#ifdef __cplusplus +} +#endif + +#endif /* BSP_ERR_H */ diff --git a/docs/images/内存布局_FLASH.svg b/docs/images/内存布局_FLASH.svg index 7b32ae2f..793143ac 100644 --- a/docs/images/内存布局_FLASH.svg +++ b/docs/images/内存布局_FLASH.svg @@ -8,28 +8,28 @@ Flash (1MB) 使用布局 — 2026-10-22 @build - - 代码段 (Code) 208.4KB - - 只读数据 (RO) 18.2KB - - Flash 空闲 796.2KB -已用: 227.8KB (22.2%) 空闲: 796.2KB (77.8%) ER_IROM1(Code+RO): 226.6KB (22.1%) 总量: 1MB (= ROM 合计口径) -Code + RO 分布(ER_IROM1 226.6KB,按 .map 对象归类) - - 85.7KB - - 55.7KB - - 25.3KB - - 17.5KB - - - - - - + + 代码段 (Code) 208.6KB + + 只读数据 (RO) 18.4KB + + Flash 空闲 795.8KB +已用: 228.2KB (22.3%) 空闲: 795.8KB (77.7%) ER_IROM1(Code+RO): 227.1KB (22.2%) 总量: 1MB (= ROM 合计口径) +Code + RO 分布(ER_IROM1 227.1KB,按 .map 对象归类) + + 85.7KB + + 55.7KB + + 25.3KB + + 17.5KB + + + + + + LVGL 库 85.7KB @@ -39,7 +39,7 @@ HAL 驱动 17.5KB - dhara / FTL 10.7KB + dhara / FTL 11.0KB FreeRTOS 内核 10.5KB @@ -49,5 +49,5 @@ startup 0.5KB - 其他 (长尾) 7.3KB + 其他 (长尾) 7.4KB diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index 68f31827..5aa385c2 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -474,6 +474,39 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法 | **运行器** | `scripts/check_layer_deps.py`(纯标准库;`--verbose` 列全部边与判定、`--prune` 只报可删基线;退出码 0=通过 / 1=新增反向依赖) | | **实测自证** | 落地时做过一次"故意违规"验证:临时给 `Drivers/BSP/RS485/rs485.c` 加一行 `#include "cfg_file.h"` → 钩子当场 FAIL 并点名该文件与该头(退出码 1),删掉后转绿。**没有当场触发过的门禁等于没有门禁** | +### 2.27 bsp-err — BSP 错误码与超时命名约定(2026-10-23 加,报告 S-5) + +| 项 | 说明 | +|---|---| +| **为什么需要** | 报告原文说"无统一错误码",实测更准的说法是**六套并存**:0/−1(SD2506、NETCFG、nand_ftl)、`int32_t`; +自研枚举(`CH395F_STATUS_*`)、芯片寄存器原生码(`CH395F_ERR_*`,与前一套同模块并存)、 +`HAL_StatusTypeDef`(`rs485.h` 曾直接暴露)、`bool`,外加第三方 dhara 的出参 `dhara_error_t` +与 FatFs 的 `FRESULT`。超时侧更乱:**同形两义**的 `*_TIMEOUT` 有四个(毫秒 / 自旋圈数 / +其实是返回码),`osWaitForever` 与 `HAL_MAX_DELAY` 又**同为 0xFFFFFFFFU**(值同域不同, +跨层转手既不报错也不变慢)。**没有机械检查的约定等于没有约定**(分层方向那条的教训) | +| **约定正文在哪** | `Drivers/BSP/bsp_err.h`(**header-only,无 `.c`** → 不占体积、不需双端注册)。 +`0 = BSP_OK`、负值失败、`BSP_ERR_CAN_RETRY(e)` 把"能不能重试"变成跨模块可判定(这是本契约 +真正值钱的一条:以前"可重试"只写在个别注释里) | +| **三条规则** | **R1** 公开 API 不得用 `HAL_StatusTypeDef` / `bool` 当错误通道(`is_/has_/can_/valid` +这类**谓词**白名单放行——第一版误判了 7 个正当谓词,我们把规则改窄,**不拿基线去洗规则**); +**R2** `*_TIMEOUT` 宏不得取负值(名字叫 TIMEOUT 却是返回码 → 一律改 `BSP_ERR_TIMEOUT`); +**R3** 非 void 的公开 API 必须有「返回值:」注释(约定要能被读到才谈得上审查) | +| **范围** | 只扫 `git ls-files` 里 `Drivers/BSP/**/*.h`(对外 API 面);**lftpd 按第三方排除**—— +它的 `226/425/450/550` 是 FTP 协议码不是内部错误码;**`net_*` BSD 兼容层不纳入**(用户裁定 +2026-10-23):调用方要的就是"返回字节数 + 每任务 errno 槽"那个形状,模拟器的 Winsock +后端也照同一形状实现 | +| **增量拦截** | 存量 **17 条**(首清点 19,2026-10-23 C1 还掉 2)钉 `BASELINE`(一个 `bool ui_content_page_open()` + 16 个缺「返回值:」),**只减不增**、脚本没有 `--write`;修完跑 `--prune` 会点名"这条已消失, +请从基线删除" | +| **配置** | `files: ^Drivers/BSP/\|^scripts/check_bsp_err\.py$\|^\.pre-commit-config\.yaml$`、 +`pass_filenames: false`(恒扫全库,要按 git 清单建公开头索引) | +| **实测负结果(顺手记一条)** | `__attribute__((warn_unused_result))` **在 ARMCC v5 上不生效**: +属性挂上、调用点裸调不接返回值,构建仍 0 错 0 警告。所以"返回值必须被消费"只能靠 +本脚本 + 书写约定"故意丢弃必须写 `(void)`",评审时看到既没 `(void)` 也没接住的调用就是漏判嫌疑 | +| **踩坑提醒** | pre-commit 会 **stash 未暂存改动**再跑钩子。所以"改代码 + 同时更新基线"要在 +一次提交里**把改动 `git add` 了再跑**,否则钩子看到的是没有你改动的树,会假报警 | +| **运行器** | `scripts/check_bsp_err.py`(纯标准库;`--verbose` 列全部命中归类、`--prune` 只报 +可删基线、`--emit-baseline` 建基线用;退出码 0/1/2) | + --- ## 3. 日常工作流 @@ -601,7 +634,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env | 文件 | 位置 | 说明 | |---|---|---| -| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**38 个钩子**,2026-10-22 清点;本文 §2 详解非单测类——N-19 新增的两个单测钩子见 §2.16/§2.17,N-23 双端注册反向校验(含 U4 补强的 UI 页面描述符表接线 ④)见 §2.18,N-27 RAM 预算门禁见 §2.19,B4 IEC104 协议栈单测见 §2.20,B8 频率测量单测见 §2.21,B9 SOE 动作记录单测见 §2.22,N-21 dhara NAND 移植层单测见 §2.23,U4 残余字符集差集检查见 §2.24,编号门禁八项校验见 §2.25,分层方向门禁见 §2.26,其余主机侧单测见 AGENTS.md 门禁条;主机侧单测共 16 个;UI 门禁另有 `font-charset` 与 `ui-golden`) | +| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**39 个钩子**,2026-10-23 清点;本文 §2 详解非单测类——N-19 新增的两个单测钩子见 §2.16/§2.17,N-23 双端注册反向校验(含 U4 补强的 UI 页面描述符表接线 ④)见 §2.18,N-27 RAM 预算门禁见 §2.19,B4 IEC104 协议栈单测见 §2.20,B8 频率测量单测见 §2.21,B9 SOE 动作记录单测见 §2.22,N-21 dhara NAND 移植层单测见 §2.23,U4 残余字符集差集检查见 §2.24,编号门禁八项校验见 §2.25,分层方向门禁见 §2.26,BSP 错误码与超时命名约定门禁见 §2.27,其余主机侧单测见 AGENTS.md 门禁条;主机侧单测共 16 个;UI 门禁另有 `font-charset` 与 `ui-golden`) | | `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) | | `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) | | `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 | @@ -614,6 +647,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env | `scripts/check_test_suite.py` | scripts/ | 测试套件开关门禁(防测试固件静默提交) | | `scripts/check_font_charset.py` | scripts/ | UI 字符集差集检查(复用 `tools/font_gen/` 提取与 cmap 解析规则,防缺字白框) | | `scripts/check_layer_deps.py` | scripts/ | 分层方向门禁(BSP 反向 include App 的增量拦截,存量基线只减不增) | +| `scripts/check_bsp_err.py` | scripts/ | BSP 错误码与超时命名门禁(三条规则的增量拦截,存量 19 条基线只减不增;`--emit-baseline` 建基线) | | `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(CODE-RULE-01~CODE-RULE-19 规则来源) | | `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) | | `docs/文档规范.md` | docs/ | 文档体系规范(链接形态/命名/门禁要求) | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index e5fb0cb9..fcf868f9 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -3,6 +3,11 @@ > 版本:v1.3 日期:2026-10-22(v1.2 2026-09-21) > 变更:v1.3 §4 的阶段子项改为**层级编号**(见下"编号约定");同步 2026-10-22 现状数字与 > `data/ram_report_20261022.md` / `data/stack_report_20261022.md` 两份新快照。 +> 变更:2026-10-23 补 `data/ram_report_20261023.md` 快照(S-5 试点:RS485 改报 `bsp_err_t`, +> `Code` 213,436 → **213,648**(S-5 全波 +212:契约试点 +48、C3 SD2506 +44、C1 GD5F/TPAFE +36、 +C2 dhara 失败类 +84;RO 另 +208,全是 C2 的失败类名表)→ `ER_IROM1` **232,508 B / 22.2%**; +> **RAM 全部字段零增量**;受管表已由 +> `check_mem_numbers.py --write` 同步,配图常量同步)。 > 变更:v1.2 数据现状全量同步——§2.1/§2.2/§5/§6/§8.6 等"现状"数据全部更新为 2026-09-21 lftpd 静态化后的最新 map 与实测值;清除 2026-09-11 历史口径残留(历史快照保留于 `data/`) > 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2) > 数据来源:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(`@build` 重建)+ `scripts/ram_report.py`(静态台账)+ `scripts/stack_report.py`(静态调用图 × 运行时栈水位)+ 源码静态分析 + 网络调研 @@ -38,10 +43,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 232,088 B (226.65 KB) | 1,048,576 B (1024 KB) | 22.1% | +| ER_IROM1 | 232,508 B (227.06 KB) | 1,048,576 B (1024 KB) | 22.2% | | RW_IRAM1 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **233,276 B (227.8 KB)** | 1,048,576 B (1024 KB) | **22.2%** | +| **Flash (ROM 合计)** | **233,696 B (228.2 KB)** | 1,048,576 B (1024 KB) | **22.3%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index a69e0afa..eb1f0886 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 232,088 B | 1,048,576 B | 22.1% | 参考 | +| ER_IROM1 (Flash) | 232,508 B | 1,048,576 B | 22.2% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 75,264 B | 131,072 B | 57.4% | PASS | diff --git a/docs/内存管理/data/ram_report_20261023.md b/docs/内存管理/data/ram_report_20261023.md new file mode 100644 index 00000000..f0e79ad1 --- /dev/null +++ b/docs/内存管理/data/ram_report_20261023.md @@ -0,0 +1,245 @@ +# STM32F4-Base RAM/Flash Usage Report + +> Generated: 2026-10-23 01:35 Map: `STM32F407-Demo.map` + +## 1. Grand Totals (全局总量) + +| Metric | Value | +|---|---| +| Code (inc. data) | 213,648 B (208.6 KB) | +| RO Data | 18,860 B (18.4 KB) | +| RW Data (initialized) | 1,188 B (1.16 KB) | +| ZI Data (zero-init) | 131,500 B (128.4 KB) | +| **Total RO (Code + RO)** | **232,508 B (227.1 KB)** | +| **Total RW (RW + ZI) = SRAM used** | **132,688 B (129.58 KB)** | +| **Total ROM (Code + RO + RW init)** | **233,696 B (228.2 KB)** | + +## 2. 执行区水位 (Execution Regions) + +| Region | Base | Size | Max | Util% | +|---|---|---|---|---| +| ER_IROM1 | 0x08000000 | 232,508 B (227.06 KB) | 1,048,576 B (1024 KB) | 22.2% | +| RW_CCM | 0x10000000 | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | +| UNINIT_CCM | 0x1000FF00 | 48 B (0.05 KB) | 256 B (0 KB) | 18.8% | +| RW_IRAM1 | 0x20000000 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% | + +## 3. RW_IRAM1 逐符号明细(ZI + RW,按大小降序) + +| # | Address | Size (B) | Size (KB) | Type | Section | Object | +|---|---|---|---|---|---|---| +| 1 | 0x2000ADC8 | 16,384 | 16.00 | Zero | .bss | lv_mem_core_builtin.o | +| 2 | 0x20007EB4 | 7,724 | 7.54 | Zero | .bss | lftpd.o | +| 3 | 0x200107A4 | 6,746 | 6.59 | Zero | .bss | ui_net_sink.o | +| 4 | 0x2000EDC8 | 6,408 | 6.26 | Zero | .bss | ui_port.o | +| 5 | 0x2000667C | 6,200 | 6.05 | Zero | .bss | nand_ftl.o | +| 6 | 0x20004E18 | 4,152 | 4.05 | Zero | .bss | ch395f.o | +| 7 | 0x20002058 | 4,096 | 4.00 | Zero | .bss | console_log.o | +| 8 | 0x20001278 | 3,552 | 3.47 | Zero | .bss | sys_log_mcu.o | +| 9 | 0x20003644 | 2,724 | 2.66 | Zero | .bss | default_task.o | +| 10 | 0x2000A524 | 1,720 | 1.68 | Zero | .bss | cmsis_os2.o | +| 11 | 0x2000444C | 1,440 | 1.41 | Zero | .bss | sample_ring.o | +| 12 | 0x20009F48 | 1,220 | 1.19 | Zero | .bss | tasks.o | +| 13 | 0x20000D78 | 1,104 | 1.08 | Zero | .bss | cfg_file_store_mcu.o | +| 14 | 0x20003058 | 1,064 | 1.04 | Zero | .bss | soe_store.o | +| 15 | 0x20012200 | 1,024 | 1.00 | Zero | | startup_stm32f407xx.o | +| 16 | 0x20005E50 | 800 | 0.78 | Zero | .bss | gd5f2gq5ue.o | +| 17 | 0x200040E8 | 712 | 0.70 | Zero | .bss | iec104_serv.o | +| 18 | 0x20000AB8 | 704 | 0.69 | Zero | .bss | cfg_file.o | +| 19 | 0x200005D0 | 560 | 0.55 | Zero | .bss | spi.o | +| 20 | 0x20000848 | 552 | 0.54 | Zero | .bss | usart.o | +| 21 | 0x20009CE0 | 552 | 0.54 | Zero | .bss | lftpd_io.o | +| 22 | 0x2000ABDC | 492 | 0.48 | Zero | .bss | lv_init.o | +| 23 | 0x20004C38 | 480 | 0.47 | Zero | .bss | freq.o | +| 24 | 0x2000629C | 416 | 0.41 | Zero | .bss | net_socket.o | +| 25 | 0x200064DC | 416 | 0.41 | Zero | .bss | net_pending.o | +| 26 | 0x200034C4 | 384 | 0.38 | Zero | .bss | task_prof.o | +| 27 | 0x20004A8C | 320 | 0.31 | Zero | .bss | prot_set.o | +| 28 | 0x2000A40C | 280 | 0.27 | Zero | .bss | timers.o | +| 29 | 0x20006190 | 268 | 0.26 | Zero | .bss | rs485.o | +| 30 | 0x200011C8 | 176 | 0.17 | Zero | .bss | prot_set_store.o | +| 31 | 0x200004C8 | 168 | 0.16 | Zero | .bss | adc.o | +| 32 | 0x200002B4 | 160 | 0.16 | Data | .data | lftpd.o | +| 33 | 0x200049EC | 160 | 0.16 | Zero | .bss | phasor.o | +| 34 | 0x200106D0 | 152 | 0.15 | Zero | .bss | ui_menu_view.o | +| 35 | 0x2000643C | 128 | 0.12 | Zero | .bss | net_listener.o | +| 36 | 0x20004BCC | 108 | 0.11 | Zero | .bss | prot_elm.o | +| 37 | 0x20000570 | 84 | 0.08 | Zero | .bss | i2c.o | +| 38 | 0x200043F8 | 84 | 0.08 | Zero | .bss | adc_input.o | +| 39 | 0x20000250 | 83 | 0.08 | Data | .data | ftp_cfg.o | +| 40 | 0x20000800 | 72 | 0.07 | Zero | .bss | tim.o | +| 41 | 0x20000A70 | 72 | 0.07 | Zero | .bss | stm32f4xx_hal_timebase_tim.o | +| 42 | 0x20003480 | 68 | 0.07 | Zero | .bss | task_watchdog.o | +| 43 | 0x20000484 | 65 | 0.06 | Zero | .bss | freertos.o | +| 44 | 0x20009F08 | 64 | 0.06 | Zero | .bss | queue.o | +| 45 | 0x20000358 | 60 | 0.06 | Data | .data | tasks.o | +| 46 | 0x20000410 | 60 | 0.06 | Data | .data | ui_page_selftest.o | +| 47 | 0x20010768 | 60 | 0.06 | Zero | .bss | ui_page_runlog.o | +| 48 | 0x20000150 | 48 | 0.05 | Data | .data | adc_health.o | +| 49 | 0x200043C8 | 48 | 0.05 | Zero | .bss | iec104_pts.o | +| 50 | 0x20000000 | 44 | 0.04 | Data | .data | freertos.o | +| 51 | 0x2000009C | 44 | 0.04 | Data | .data | task_watchdog.o | +| 52 | 0x20000044 | 36 | 0.04 | Data | .data | sys_log_mcu.o | +| 53 | 0x2000022C | 36 | 0.04 | Data | .data | net_cfg.o | +| 54 | 0x2000044C | 32 | 0.03 | Data | .data | ui_standby.o | +| 55 | 0x20006170 | 32 | 0.03 | Zero | .bss | tpafe5160.o | +| 56 | 0x200064BC | 32 | 0.03 | Zero | .bss | net_events.o | +| 57 | 0x20000128 | 28 | 0.03 | Data | .data | adc_input.o | +| 58 | 0x20000068 | 24 | 0.02 | Data | .data | console_log.o | +| 59 | 0x200000E4 | 24 | 0.02 | Data | .data | adc_task.o | +| 60 | 0x20000180 | 24 | 0.02 | Data | .data | prot_set.o | +| 61 | 0x2000019C | 24 | 0.02 | Data | .data | freq.o | +| 62 | 0x20000200 | 24 | 0.02 | Data | .data | net_listener.o | +| 63 | 0x200043B0 | 24 | 0.02 | Zero | .bss | protect_task.o | +| 64 | 0x20000030 | 20 | 0.02 | Data | .data | cfg_file.o | +| 65 | 0x20000080 | 20 | 0.02 | Data | .data | soe_store.o | +| 66 | 0x200001D0 | 20 | 0.02 | Data | .data | gd5f2gq5ue.o | +| 67 | 0x20000394 | 20 | 0.02 | Data | .data | timers.o | +| 68 | 0x2000046C | 20 | 0.02 | Data | .data | ui_net_sink.o | +| 69 | 0x200003D4 | 18 | 0.02 | Data | .data | ui_menu.o | +| 70 | 0x200000FC | 16 | 0.02 | Data | .data | iec104_serv.o | +| 71 | 0x20000118 | 16 | 0.02 | Data | .data | iec104_pts.o | +| 72 | 0x200001F0 | 16 | 0.02 | Data | .data | net_socket.o | +| 73 | 0x20000218 | 16 | 0.02 | Data | .data | net_events.o | +| 74 | 0x200002A4 | 16 | 0.02 | Data | .data | nand_ftl.o | +| 75 | 0x200003F0 | 16 | 0.02 | Data | .data | ui_content_page.o | +| 76 | 0x200000D4 | 12 | 0.01 | Data | .data | default_task.o | +| 77 | 0x200001B4 | 12 | 0.01 | Data | .data | stm32f4xx_hal.o | +| 78 | 0x200001E4 | 12 | 0.01 | Data | .data | tpafe5160.o | +| 79 | 0x200003AC | 12 | 0.01 | Data | .data | port.o | +| 80 | 0x200003C8 | 12 | 0.01 | Data | .data | ui_port.o | +| 81 | 0x20000400 | 12 | 0.01 | Data | .data | ui_page_password.o | +| 82 | 0x200005C4 | 12 | 0.01 | Zero | .bss | iwdg.o | +| 83 | 0x200001C4 | 10 | 0.01 | Data | .data | ch395f.o | +| 84 | 0x20000094 | 8 | 0.01 | Data | .data | sys_clock.o | +| 85 | 0x200000C8 | 8 | 0.01 | Data | .data | task_prof.o | +| 86 | 0x2000010C | 8 | 0.01 | Data | .data | protect_task.o | +| 87 | 0x20000144 | 8 | 0.01 | Data | .data | sample_ring.o | +| 88 | 0x200003B8 | 8 | 0.01 | Data | .data | ff.o | +| 89 | 0x200003C0 | 8 | 0.01 | Data | .data | ffsystem.o | +| 90 | 0x200003E8 | 8 | 0.01 | Data | .data | ui_menu_view.o | +| 91 | 0x2000002C | 4 | 0.00 | Data | .data | app_main.o | +| 92 | 0x200000D0 | 4 | 0.00 | Data | .data | vol_lock.o | +| 93 | 0x200000E0 | 4 | 0.00 | Data | .data | net_task.o | +| 94 | 0x200001C0 | 4 | 0.00 | Data | .data | system_stm32f4xx.o | +| 95 | 0x20000228 | 4 | 0.00 | Data | .data | net_pending.o | +| 96 | 0x20000354 | 4 | 0.00 | Data | .data | lftpd_io.o | +| 97 | 0x200003A8 | 4 | 0.00 | Data | .data | cmsis_os2.o | +| 98 | 0x20000480 | 4 | 0.00 | Data | .data | mc_w.l(errno.o) | +| 99 | 0x2000014C | 2 | 0.00 | Data | .data | phasor.o | +| 100 | 0x2000040C | 2 | 0.00 | Data | .data | ui_page_net_cfg.o | +| 101 | 0x20000198 | 1 | 0.00 | Data | .data | prot_elm.o | +| | | **75,243** | **73.48** | | | **Total** | + +## 4. Image Component Sizes(RW + ZI > 0,按大小降序) + +| # | Object | Code | RO | RW | ZI | RW+ZI | +|---|---|---|---|---|---|---| +| 1 | heap_4.o | 512 | 69 | 32 | 57,344 | 57,376 | +| 2 | lv_mem_core_builtin.o | 410 | 0 | 0 | 16,384 | 16,384 | +| 3 | lftpd.o | 6,352 | 509 | 160 | 7,724 | 7,884 | +| 4 | ui_net_sink.o | 1,126 | 67 | 20 | 6,746 | 6,766 | +| 5 | ui_port.o | 418 | 0 | 12 | 6,408 | 6,420 | +| 6 | nand_ftl.o | 2,434 | 384 | 16 | 6,200 | 6,216 | +| 7 | ch395f.o | 3,978 | 603 | 10 | 4,152 | 4,162 | +| 8 | console_log.o | 980 | 55 | 24 | 4,096 | 4,120 | +| 9 | sys_log_mcu.o | 3,074 | 83 | 36 | 3,552 | 3,588 | +| 10 | default_task.o | 2,332 | 280 | 12 | 2,724 | 2,736 | +| 11 | cmsis_os2.o | 1,496 | 0 | 4 | 1,720 | 1,724 | +| 12 | sample_ring.o | 292 | 0 | 8 | 1,440 | 1,448 | +| 13 | tasks.o | 4,342 | 0 | 60 | 1,220 | 1,280 | +| 14 | cfg_file_store_mcu.o | 380 | 24 | 0 | 1,104 | 1,104 | +| 15 | soe_store.o | 816 | 28 | 20 | 1,064 | 1,084 | +| 16 | startup_stm32f407xx.o | 36 | 392 | 0 | 1,024 | 1,024 | +| 17 | gd5f2gq5ue.o | 3,954 | 478 | 20 | 800 | 820 | +| 18 | iec104_serv.o | 1,188 | 16 | 16 | 712 | 728 | +| 19 | cfg_file.o | 5,970 | 810 | 20 | 704 | 724 | +| 20 | spi.o | 584 | 0 | 0 | 560 | 560 | +| 21 | lftpd_io.o | 1,198 | 0 | 4 | 552 | 556 | +| 22 | usart.o | 884 | 0 | 0 | 552 | 552 | +| 23 | freq.o | 764 | 8 | 24 | 480 | 504 | +| 24 | lv_init.o | 156 | 0 | 0 | 492 | 492 | +| 25 | net_socket.o | 4,944 | 443 | 16 | 416 | 432 | +| 26 | net_pending.o | 1,400 | 0 | 4 | 416 | 420 | +| 27 | task_prof.o | 638 | 185 | 8 | 384 | 392 | +| 28 | prot_set.o | 844 | 640 | 24 | 320 | 344 | +| 29 | timers.o | 1,232 | 0 | 20 | 280 | 300 | +| 30 | rs485.o | 168 | 0 | 0 | 268 | 268 | +| 31 | prot_set_store.o | 236 | 74 | 0 | 176 | 176 | +| 32 | adc.o | 580 | 0 | 0 | 168 | 168 | +| 33 | phasor.o | 420 | 258 | 2 | 160 | 162 | +| 34 | task_watchdog.o | 1,110 | 137 | 44 | 116 | 160 | +| 35 | ui_menu_view.o | 2,034 | 16 | 8 | 152 | 160 | +| 36 | net_listener.o | 948 | 207 | 24 | 128 | 152 | +| 37 | adc_input.o | 592 | 27 | 28 | 84 | 112 | +| 38 | freertos.o | 716 | 592 | 44 | 65 | 109 | +| 39 | prot_elm.o | 708 | 227 | 1 | 108 | 109 | +| 40 | i2c.o | 168 | 0 | 0 | 84 | 84 | +| 41 | ftp_cfg.o | 152 | 225 | 83 | 0 | 83 | +| 42 | stm32f4xx_hal_timebase_tim.o | 152 | 0 | 0 | 72 | 72 | +| 43 | tim.o | 168 | 0 | 0 | 72 | 72 | +| 44 | iec104_pts.o | 1,082 | 132 | 16 | 48 | 64 | +| 45 | queue.o | 2,840 | 0 | 0 | 64 | 64 | +| 46 | ui_page_runlog.o | 28 | 105 | 0 | 60 | 60 | +| 47 | ui_page_selftest.o | 28 | 109 | 60 | 0 | 60 | +| 48 | adc_health.o | 432 | 0 | 48 | 0 | 48 | +| 49 | net_events.o | 1,994 | 809 | 16 | 32 | 48 | +| 50 | tpafe5160.o | 808 | 67 | 12 | 32 | 44 | +| 51 | net_cfg.o | 832 | 103 | 36 | 0 | 36 | +| 52 | protect_task.o | 840 | 68 | 8 | 24 | 32 | +| 53 | ui_standby.o | 1,208 | 67 | 32 | 0 | 32 | +| 54 | adc_task.o | 1,520 | 234 | 24 | 0 | 24 | +| 55 | (incl. Padding) | 288 | 95 | 16 | 5 | 21 | +| 56 | ui_menu.o | 760 | 137 | 18 | 0 | 18 | +| 57 | ui_content_page.o | 1,664 | 81 | 16 | 0 | 16 | +| 58 | iwdg.o | 28 | 0 | 0 | 12 | 12 | +| 59 | port.o | 762 | 73 | 12 | 0 | 12 | +| 60 | stm32f4xx_hal.o | 116 | 0 | 12 | 0 | 12 | +| 61 | ui_page_password.o | 686 | 175 | 12 | 0 | 12 | +| 62 | ff.o | 7,962 | 282 | 8 | 0 | 8 | +| 63 | ffsystem.o | 204 | 20 | 8 | 0 | 8 | +| 64 | sys_clock.o | 1,788 | 286 | 8 | 0 | 8 | +| 65 | app_main.o | 2,308 | 65 | 4 | 0 | 4 | +| 66 | net_task.o | 1,460 | 72 | 4 | 0 | 4 | +| 67 | system_stm32f4xx.o | 16 | 24 | 4 | 0 | 4 | +| 68 | vol_lock.o | 92 | 0 | 4 | 0 | 4 | +| 69 | errno.o | 20 | 0 | 4 | 0 | 4 | +| 70 | ui_page_net_cfg.o | 1,004 | 105 | 2 | 0 | 2 | +| | **All .o files** | **213,648** | **18,860** | **1,188** | **131,500** | **132,688** | + +## 5. Flash (Code + RO Data) Top-30(按大小降序) + +| # | Object | Code | RO Data | Code+RO | +|---|---|---|---|---| +| 1 | ff.o | 7,962 | 282 | 8,244 | +| 2 | lftpd.o | 6,352 | 509 | 6,861 | +| 3 | cfg_file.o | 5,970 | 810 | 6,780 | +| 4 | net_socket.o | 4,944 | 443 | 5,387 | +| 5 | lv_indev.o | 4,928 | 0 | 4,928 | +| 6 | ch395f.o | 3,978 | 603 | 4,581 | +| 7 | ui_font_cn_12.o | 0 | 4,545 | 4,545 | +| 8 | lv_obj_pos.o | 4,526 | 16 | 4,542 | +| 9 | lv_refr.o | 4,486 | 0 | 4,486 | +| 10 | gd5f2gq5ue.o | 3,954 | 478 | 4,432 | +| 11 | tasks.o | 4,342 | 0 | 4,342 | +| 12 | lv_obj_scroll.o | 4,126 | 0 | 4,126 | +| 13 | lv_obj_style.o | 4,106 | 0 | 4,106 | +| 14 | lv_draw_sw_mask.o | 3,862 | 0 | 3,862 | +| 15 | stm32f4xx_hal_spi.o | 3,586 | 0 | 3,586 | +| 16 | lv_draw_sw_box_shadow.o | 3,246 | 0 | 3,246 | +| 17 | sys_log_mcu.o | 3,074 | 83 | 3,157 | +| 18 | lv_indev_scroll.o | 3,068 | 0 | 3,068 | +| 19 | lv_obj.o | 2,982 | 43 | 3,025 | +| 20 | lv_draw_sw_img.o | 2,960 | 0 | 2,960 | +| 21 | queue.o | 2,840 | 0 | 2,840 | +| 22 | journal.o | 2,834 | 0 | 2,834 | +| 23 | iec104_link.o | 2,824 | 0 | 2,824 | +| 24 | nand_ftl.o | 2,434 | 384 | 2,818 | +| 25 | net_events.o | 1,994 | 809 | 2,803 | +| 26 | default_task.o | 2,332 | 280 | 2,612 | +| 27 | app_main.o | 2,308 | 65 | 2,373 | +| 28 | printfa.o | 2,312 | 0 | 2,312 | +| 29 | stm32f4xx_hal_i2c.o | 2,294 | 0 | 2,294 | +| 30 | lv_display.o | 2,284 | 0 | 2,284 | +| | **All .o files** | **213,648** | **18,860** | **232,508** | + +> 自洽校验:per-file 213,648/18,860/1,188/131,500 vs Grand Totals 213,648/18,860/1,188/131,500(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS** diff --git a/docs/架构设计/STM32F4-Base存储架构说明.md b/docs/架构设计/STM32F4-Base存储架构说明.md index 3cb5a955..77746cf1 100644 --- a/docs/架构设计/STM32F4-Base存储架构说明.md +++ b/docs/架构设计/STM32F4-Base存储架构说明.md @@ -621,3 +621,26 @@ lftpd 原先把全部异常路径一律按 error 级打(串口上带 `[ERR]` - 排障信息量不减:peer 级照旧打印,且比原来更准(带 `ret=` / `sk=` / `timeout(ms)`,措辞从 "error reading next command"改成"control channel closed by peer"——原先那句把正常收尾 写成了故障,误导过排查方向。 + +### 10.4 dhara 失败类:ECC / 预算耗尽 / 完整性故障(2026-10-23,S-5 C2) + +一次 NAND 读写要穿过 **SPI → `gd5f_*` → dhara(`dhara_error_t`)→ FTL glue → FatFs `RES_ERROR`** +五段,而 glue 以前把 dhara 的所有失败**压成一个 `GD5F_ERROR`**——上层分不清"该重试"、 +"该标坏块"、还是"块池见底了,重格式化也没用"。现在 `nand_ftl.c` 在**最早同时知道 +LPN 与原始码的位置**就地分类并打印(`ftl_err_name()` 给可读名;**本仓 dhara 没有编 +`error.c`,所以没有 `dhara_strerror()`,用它直接链接失败**——表是自己的一张短表): + +| dhara 错误 | 归类 | 处置上为什么不同 | +|---|---|---| +| `ECC` / `TOO_BAD` | `BSP_ERR_CRC` | 位翻转不可纠或页太坏:**是页级问题**,读重试 / 由 dhara 换页,不代表卷坏 | +| `JOURNAL_FULL` / `MAP_FULL` | `BSP_ERR_NO_SPACE` | 介质还健康,是 **journal / 映射层块池见底**:该走容量恢复(`../测试说明/存储维护与容量恢复.md`),此时重格式化只是把问题推回去 | +| `BAD_BLOCK` / `RECOVER` / `NOT_FOUND` / `CORRUPT_MAP` / 越界 | `BSP_ERR_IO` | 映射或介质**完整性**故障:保卷 + 上报,不许自愈重格式化 | + +- 这条分类与 **E-31** 是同一条纪律的两端:E-31 规定"只有扫描全程 I/O 干净且无 + checkpoint(`DHARA_E_TOO_BAD`)才算真空白卷",本表则保证失败**可读**——两者合起来 + 才是"别把瞬态毛刺当卷损坏去 `f_mkfs`"(旧行为:单次 I2C/SPI 抖动 → 清 map → 整卷 + 重格式化 → 静默销毁全部用户数据)。 +- **仍未闭环**:分类目前**只进日志**;`disk_read`/`disk_write` 对 FatFs 仍只能回 `RES_ERROR` + (那是 FatFs 的契约)。要让 defaultTask 的卷健康探针**按类分支**,还需要一个跨调用 + 存活的失败类快照(`nand_ftl_last_error()`,8 B;代价是要先定多写者纪律—— + defaultTask 与 testTask 都会碰存储)→ 报告 S-5 的 C4 待还项。 diff --git a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md index 949a5edc..9ebe9bce 100644 --- a/docs/测试说明/GD5F2GQ5UE_Test_Guide.md +++ b/docs/测试说明/GD5F2GQ5UE_Test_Guide.md @@ -5,7 +5,7 @@ > 详细芯片行为见 `docs/芯片手册/GD5F2GQ5UExxG.md`,已知陷阱见 `docs/问题记录/GD5F2GQ5UE_Trap_Records.md`。 > > **2026-10-03 错误通道硬化**(E-10/E-42/E-53/E-54/E-56/E-57,见 `问题记录/GD5F2GQ5UE_Trap_Records.md` TRAP-GD5F-06): -> `wait_busy` 区分"超时"(`GD5F_BUSY_TIMEOUT`)与"真故障"(`GD5F_ERROR`)、`bbt_scan` 失败**放弃持久化**、 +> `wait_busy` 区分"超时"(`GD5F_WAIT_EXPIRED`,旧名 `GD5F_BUSY_TIMEOUT`)与"真故障"(`GD5F_ERROR`)、`bbt_scan` 失败**放弃持久化**、 > init 全链路检查返回值 + **回读 B0h 校验 ECC 使能**、`read` 的 raw 契约文档化、公开 API 统一 `int32_t`。 > 套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`。 > ⚠ **编排器注意**:`TC-GD5F-704` 会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable`(回落轮询,用例 PASS)—— diff --git a/docs/环境与部署/并行任务工作树方案.md b/docs/环境与部署/并行任务工作树方案.md index 7c70fa09..6eeb9bf0 100644 --- a/docs/环境与部署/并行任务工作树方案.md +++ b/docs/环境与部署/并行任务工作树方案.md @@ -397,7 +397,7 @@ docs\内存管理\RAM使用分析与优化方案.md:585 [路径引用] docs/内 | 主题 | 去处 | |---|---| -| 提交门禁 38 个钩子 | `docs/代码规范/pre-commit使用说明.md` | +| 提交门禁 39 个钩子 | `docs/代码规范/pre-commit使用说明.md` | | 自动化测试与台架流程 | `docs/测试说明/自动化测试流程.md` | | 文档写作规范(编码/链接/登记) | `docs/文档规范.md` | | 工具路径环境变量(换机) | `docs/环境与部署/本地工具路径环境变量方案.md` | diff --git a/docs/环境与部署/项目部署环境要求.md b/docs/环境与部署/项目部署环境要求.md index cd4e67a2..c5e5f385 100644 --- a/docs/环境与部署/项目部署环境要求.md +++ b/docs/环境与部署/项目部署环境要求.md @@ -504,7 +504,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级 | 资源 | 已用 | 总量 | 利用率 | 余量 | 红线说明 | |---|---|---|---|---|---| -| Flash(`ER_IROM1` = Code + RO) | 232,088 B(226.6 KB) | 1 MB | 22.1% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 | +| Flash(`ER_IROM1` = Code + RO) | 232,508 B(227.06 KB) | 1 MB | 22.2% | ≈804 KB | **充裕**:产品需求无 OTA / 双镜像预算,Flash 不是卡点;死代码裁剪簇已收口 → `docs/问题记录/项目审查与优化建议报告.md` §5.4 | | 主 SRAM1(静态区;FreeRTOS 堆已迁 CCM) | 75,264 B(73.5 KB) | 128 KB | 57.4% | 55,808 B | 已不紧张——2026-10-11 堆迁 CCM 与 2026-10-15 `LV_MEM_SIZE` 40→16 KB 两步的结果 | | CCM(`RW_CCM` 段) | 57,376 B(56.0 KB) | 65,280 B | **87.9%** | **7,904 B** | **当前真正的红线**:加任务 / 抬栈 / 加堆会先在这里撞墙;`mem-budget` 钩子在 92% 拦提交 | diff --git a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md index 8f08ed06..0fbce713 100644 --- a/docs/问题记录/GD5F2GQ5UE_Trap_Records.md +++ b/docs/问题记录/GD5F2GQ5UE_Trap_Records.md @@ -216,7 +216,7 @@ if (ok) { - 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响)。 **修复(2026-10-03)**: -- `gd5f_wait_busy` 检查三次 HAL 调用,失败即返 `GD5F_ERROR`(与 `GD5F_BUSY_TIMEOUT` 区分)。 +- `gd5f_wait_busy` 检查三次 HAL 调用,失败即返 `GD5F_ERROR`(与 `GD5F_WAIT_EXPIRED` 区分;后者旧名 `GD5F_BUSY_TIMEOUT`,2026-10-23 按 S-5 R2 改名——返回码不许叫 `*_TIMEOUT`,因为同名形状里还有"毫秒数"和"自旋圈数"两种东西)。 - `gd5f_bbt_scan` 返回错误码、每轮 `spare` 清零、读失败中止;`bbt_rebuild` 扫描失败 **放弃持久化**(`gd5f_bbt_load()` 回滚 RAM 后直接返回,不 `bbt_save`)。 - init 全链路检查返回值;init 后**回读 B0h 校验 ECC 使能(bit4)**,未使能即报错。 diff --git a/docs/问题记录/项目审查与优化建议报告.md b/docs/问题记录/项目审查与优化建议报告.md index a7f09d47..cdf1d037 100644 --- a/docs/问题记录/项目审查与优化建议报告.md +++ b/docs/问题记录/项目审查与优化建议报告.md @@ -16,7 +16,7 @@ | 维度 | 评级 | 今天的实测依据 | 相比 2026-10-15 的变化 | |---|---|---|---| | 结构与分层 | 🟢 良好 | 单向依赖未被破坏;双端注册反向校验 PASS;326 个编译单元全部可追溯 | 无退化 | -| 文档与治理 | 🟢 良好 | 38 个提交钩子 + 16 套主机单测;工程 md 91 份;`doc-links` 全库 0 问题 | 新增 `编号门禁增量拦截` 与内存快照回填常态化 | +| 文档与治理 | 🟢 良好 | 39 个提交钩子 + 16 套主机单测;工程 md 91 份;`doc-links` 全库 0 问题 | 新增 `编号门禁增量拦截` 与内存快照回填常态化 | | 内存与 Flash | 🟡 可控但已换位置 | SRAM1 **57.4%**(余 55,808 B)、Flash **22.1%**、**CCM 87.9%(余 7,904 B)** | **红线从 SRAM1 迁到 CCM**(堆 2026-10-11 迁 CCM + 池 40→16 KB 后 SRAM1 已宽松) | | 可靠性与自恢复 | 🟢 良好 | 4 个 Fault 全采集现场;IWDG 五面 `required_mask`;复位原因与 `[STACK]` 水位常开 | 台架复跑持续 PASS(今日真机 708 ms 起网、无 `[ERR]`/Fault) | | 测试与验证 | 🟡 半办 | 16 套主机单测 + `ui-golden`/`font-charset` 入门禁;TC 双向追溯 593∶271 交集 265 | **仍无覆盖率**、`coding-standard` **仍无 `malloc` 正则** | @@ -55,7 +55,7 @@ `net_msg_queue_adopt()` 绑定(创建点不能外移——`osKernelInitialize()` 之前的创建类 API 会杀死启动;**故意不新建第二个队列**:那会白占 CCM 里 8×`net_msg_t` 的堆空间,而 CCM 正是当前红线)。**两类从来不算债**:`stm32f4xx_it.c:241-256` 是 CubeMX「External variables」生成块;`:183/195/207/220` 是 `__asm` Handler 里的汇编标签引用(ARMCC `__asm` 取不到 C 数据符号)。 | 无(后续由 `layer-deps` 与 `cubemx-survival` 两条门禁守着不回退:新增 `net-msgq-adopt` 存活项) | -| 驱动规范 | S-5 ❌ | `bsp_err_t` 至今只出现在本报告里(无统一错误码/超时/重试约定) | 定义后进 `docs/代码规范/` | +| 驱动规范 | **S-5 🟡 主体已落地**(2026-10-23:契约 + 门禁 + RS485/SD2506/TPAFE 收编 + GD5F 改名 + dhara 三类码) | 我原先写的"无统一错误码"**不准**,实测是 **六套并存**:0/−1(SD2506、NETCFG、nand_ftl)、自研枚举 `CH395F_STATUS_*`、**芯片寄存器原生码** `CH395F_ERR_*`(与前一套同模块并存)、`HAL_StatusTypeDef`(`rs485.h` 曾直接暴露)、`bool`,外加第三方 dhara 出参 `dhara_error_t` 与 FatFs `FRESULT`。超时侧是**同形两义**:`*_TIMEOUT` 里 100 是毫秒、`GD5F_SPI_TIMEOUT 100` 是**自旋圈数**、`GD5F/TPAFE5160_BUSY_TIMEOUT = -2` 其实是**返回码**;`osWaitForever` 与 `HAL_MAX_DELAY` **同为 0xFFFFFFFFU**(值同域不同,跨层转手不报错也不变慢)。真正值钱的不是码名,是"**能不能重试**"以前只写在个别注释里 | `Drivers/BSP/bsp_err.h`(header-only,含 `BSP_ERR_CAN_RETRY`)+ `bsp-err` 钩子(三条规则,基线 **19 → 17**);已收编 **RS485(HAL 枚举不再外泄)/ SD2506(11 个公共 API,I2C 超时终于归成可重试)/ TPAFE5160(含 E-44 的 FRSTDATA → `BSP_ERR_NO_DEVICE`)**,GD5F 的 `GD5F_BUSY_TIMEOUT` 改名 `GD5F_WAIT_EXPIRED` 消掉同形两义,FTL 坍缩点按 **ECC / 预算耗尽 / 完整性** 三类就地分类并打可读日志(§10.4)。`net_*` BSD 层与 `tpafe5160_snapshot()` 三态按裁定**不纳入**。**待还**:GD5F 私有码集整体收编(169 处、含板级套件 48 处,需真机 + 套件回归)、C4 失败类快照(让卷健康探针按类分支)、16 条缺「返回值:」注释 | | 网络 | N-09 / PHY ❌(N-25) | `NET_MAX_SOCKETS` 仍为 8(与单连接复用语义不符);PHY 强制 100M 无自协商回退;PASV 每次新建监听 | 见 §4.2;随 N-25 一并处置 | | 存储 | N-15 🟡(N-25) | `nand_ftl.o` 静态 6,216 B——三块 2 KB 级页缓冲并存未合并 | 与 R-3 同批评估 | | 存储/日志 | P2-10 ❌(N-25) | 卷锁现有 5 个使用者(配置原子写 / 日志 / SOE / lftpd_io / defaultTask),容量轮转删除与 UI 读并发仍未全覆盖 | 登记簿 S-5 面(卷锁覆盖面) | @@ -71,7 +71,7 @@ - 体量:`MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(2026-10-22 `@build`)→ `data/ram_report_20261022.md` - 栈:`STM32F407-Demo.htm`(静态调用图)+ 同日真机 `[STACK]` 心跳 → `data/stack_report_20261022.md` - 仓库:`git ls-files` 逐个 `stat`(必须 `-z`,否则中文名被控制台代码页破坏) -- 门禁:`.pre-commit-config.yaml`(38 个 `- id:`)+ 各钩子今日实跑输出 +- 门禁:`.pre-commit-config.yaml`(39 个 `- id:`)+ 各钩子今日实跑输出 - 待办:`docs/任务规划/登记簿.md`(在策 18 条)与 `docs/任务规划/README.md` §2 ### 1.2 复现命令(本报告所有数字可重跑) @@ -93,7 +93,7 @@ git ls-files -z | xargs -0 stat -c '%s' | 名词 | 口径 | |---|---| -| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **232,088 B(22.1%)**;`ROM 合计` 再加 `RW` 初始化表 = 233,276 B(22.2%) | +| Flash 占用 | `Code + RO-data` = `ER_IROM1` = **232,508 B(22.2%)**;`ROM 合计` 再加 `RW` 初始化表 = 233,696 B(22.3%) | | SRAM 占用 | `RW_IRAM1`(DMA 可达区)与 `RW_CCM`(仅 CPU)分开计,二者不可互换(规则 R2/R3) | | 最坏栈 | `max(armlink 静态调用图深度, 真机 [STACK] 高水位)`;判定线 `余量 ≥ max(1024 B ISR, 配置×30%)` | | 编译单元 | uvprojx 的 `` 计数(今日 **326 个 `.c`**,其中 LVGL 201);"自研 `.c`" 另按目录口径 114 个 | @@ -228,10 +228,10 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 ### 5.1 实测基线 -| 指标 | 2026-10-22 | 2026-10-15 | 说明 | +| 指标 | 2026-10-23 | 2026-10-15 | 说明 | |---|---|---|---| -| `Code` | 213,436 B | 213,520 B | 当日累计:F-6 −332、SPI 回调 +16、消息队列 +32、FTP 日志级别 +28(RO 另 +72)、**LIST 满块续发 +172** | -| `ER_IROM1`(Code+RO) | **232,088 B / 22.1%** | 232,280 B / 22.2% | 余 ≈803 KB | +| `Code` | 213,648 B | 213,520 B | 10-23 S-5 全波 +212:契约试点 +48、C3 SD2506 迁移 +44、C1 GD5F/TPAFE 收编 +36、C2 dhara 失败类 +84(RO 另 +208 = 失败类名表);10-22 当日累计:F-6 −332、SPI 回调 +16、消息队列 +32、FTP 日志级别 +28(RO 另 +72)、LIST 满块续发 +172 | +| `ER_IROM1`(Code+RO) | **232,508 B / 22.2%** | 232,280 B / 22.2% | 余 ≈803 KB;**RAM 三项 10-23 零增量** | | `RW_IRAM1` | **75,264 B / 57.4%**(余 55,808) | 73,568 B / 56.1% | 增量来自 B5/B8 功能码 | | `RW_CCM` | **57,376 B / 87.9%**(余 7,904) | 57,376 B / 87.9% | **当前唯一红线** | | FreeRTOS 堆 | 56 KB(57,344 B)@CCM;实测 `free=15,800` / `min_ever=8,464` | 同 | 2026-10-22 真机 | @@ -271,7 +271,7 @@ defaultTask 单属主;原子写经卷锁;旧档迁移已删除。**仍列为 | **F-6** 双精度软浮点 | ≈1 KB | **归因错**:全库 0 处 `%f`;剩余 `dadd/dmul/ddiv/depilogue/dfixul` 1,018 B 锁在 MicroLIB `printfa.o` 的 `_printf_core → _fp_digits`,只要用 `snprintf` 就删不掉。但**根因侧确有可修点**:`App/algo/prot_set.h` 两个 Q10 宏写成 `double` | ✅ **已落地 2026-10-22**:改单精度 → `@build` −332 B、5 个 helper(300 B)消失;数值逐位等价(穷举 42 个字面量 + int32 全域 ÷1024 往返 4.49×10⁶ 例零差异);真机 `@test` PASS | **当前可回收 Flash = 0 B**(F-4/F-6 已定论,图片链同理不打补丁)。LVGL 仍占镜像 **37.8%** -(87,706 / 232,088 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。 +(87,706 / 232,508 B)——这是"用 LVGL 画单色菜单"的结构性代价,F-1/F-2/F-3 已省掉 64,576 B。 ### 5.5 内存治理长效机制(常开,不受暂缓影响) @@ -324,7 +324,7 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止 | 波次 | 本报告条目(今天状态) | 备注 | |---|---|---| | ~~W3 预算与内存~~ ⏸ | R-9 ✅ / R-2 后半、R-3、R-4、R-6 暂缓 | 暂缓期间无验收动作,观测照常 | -| W4 结构债 | N-14 ⏸、F-5 🟡、S-1/S-2/S-4/S-5、N-25 | `--strict` 已默认;`bsp_err_t` 仍未进规范 | +| W4 结构债 | N-14 ⏸、F-5 🟡、S-1/S-2/S-4/S-5、N-25 | `--strict` 已默认;S-5 契约/门禁已落地并收编三模块(基线 19→17,剩 GD5F 码集 + C4 快照) | | W5 验证常态 | 覆盖率、`malloc` 正则、TC 差异 13 处升门禁 | 逢窗口顺手做 | | W6 UI 扩展 | U2 残余、U5 页量 | 随产品需求,不再单列专项 | | W7 仓库决策 | D-1(N-29) | 已决策暂不处理;对外交付前重评 | @@ -383,7 +383,7 @@ W6 簇按 U4 → U1 → U3 → U2 的顺序执行完毕(U2 停在"够用即止 | **S-2** | 跨模块裸 `extern` | ✅ **已闭环 2026-10-22**(24 处手写全消;两类本就不算债见 §0.4) | 本文变更记录 2026-10-22 三行;契约见 `App/task/task_handles.h`、`gd5f_spi1_notify_*`、`net_msg_queue_adopt` | | **S-4** | `app_cfg.h` 未接线占位开关 | ✅ **已闭环 2026-10-22**(4 个零消费点宏删除) | `App/app/app_cfg.h` 修改记录 v1.2;口径同步见 `../环境与部署/项目部署环境要求.md` §3.2/§3.5、`../产品需求/产品需求设计文档.md`、`../任务规划/排期详述.md` C5 | | **S-3** | `.ioc` 侧 USART1 配置 | ❌ 在策 | 登记簿 S-3 | -| **S-5** | 统一 `bsp_err_t` 错误码约定 | ❌ 未做 | 本文 §0.4、`docs/代码规范/`(待建) | +| **S-5** | 统一 `bsp_err_t` 错误码与超时命名约定 | 🟡 主体已落地(2026-10-23:契约 + `bsp-err` 钩子 + RS485/SD2506/TPAFE 收编 + GD5F 改名 + dhara 三类码;基线 19 → 17) | 本文 §0.4、`pre-commit使用说明.md` §2.27、`STM32F4-Base存储架构说明.md` §10.4、`Drivers/BSP/bsp_err.h` | | R-1 ~ R-9 | 内存优化项(R-9 已落地、R-5 判定不可行、其余随簇暂缓) | 见 §5.3 | 本文 §5.3、`../内存管理/RAM预算台账.md` | | F-1 / F-2 / F-3 | LVGL 死代码裁剪,省 64,576 B | ✅ 已落地 2026-10-11 | `../内存管理/RAM使用分析与优化方案.md` §2.6.4 | | **F-4 / F-5 / F-6** | Flash 双精度与死代码簇 | ✅/🟡/❌ 见 §5.4 | 本文 §5.4、`App/algo/prot_set.h` 注释 | @@ -442,6 +442,67 @@ admin/admin(SHA-256 `pwd_hash`)→ 该脚本**必然登录失败**;它是 | | 数字联动照做:两份 `data/` 快照重跑、`ram_baseline --update/--write`、`check_mem_numbers --write`、FLASH 与 任务栈两张配图重算重生成(逐文本复查越界 = 0)、本文 §1.3/§5.1/§5.4 与部署文档 §3.3 同步;§0.4 的 S-2 行按 「完成即销项」改写、§9 拆出 S-2 行标 ✅。 | +| **2026-10-23** | **S-5 第二波:C3 SD2506 整模块收编 + C1 GD5F/TPAFE 同形两义 + C2 dhara 失败类**(用户指定顺序 C3 → C1 → C2) | **C3**:`sd2506.h` 三枚私有宏(`SD2506_OK/ERROR/I2C_ERROR`)**删除而不是留别名**(留别名 = 新代码还能继续用旧形状,等于把迁移洗掉),11 个公共 API + 6 个内部 helper 改 `bsp_err_t`;新增 `sd2506_err_from_hal()`,**把 I2C 超时从"和没人应答同一个 −2"里拆出来归成 `BSP_ERR_TIMEOUT`(可重试)**——SD2506 是启动链第一站、I2C1 上电时序敏感,这是本波最实在的一处语义改善; +`sd2506_init` 的 charge 寄存器**回读校验不符**原返 `SD2506_ERROR`(参数类)→ 改归 `BSP_ERR_IO`。连带迁移 `App`(`sys_clock.c`、`app_main.c`)、板级 RTC 套件、 +主机单测(`test/host/sys_clock/` 桩 + `run_unit_test.py` 补 `Drivers/BSP` include 路径)→ **单测 100/100**。 | +| | **C1**:GD5F 的 `GD5F_BUSY_TIMEOUT` 改名 `GD5F_WAIT_EXPIRED`(**值 −2 不变**,纯消"名字叫 TIMEOUT 其实是返回码"),GD5F 私有码集**整体**收编**没做**——169 处引用里 48 处在板级套件、 +还要动 Trap 记录用词,得单独一波配真机回归,不夹带。TPAFE5160 收编(23 处):`BUSY_TIMEOUT → BSP_ERR_TIMEOUT`、`ERROR → BSP_ERR_PARAM`(**同值 −1,行为零变化**)、 +`FRSTDATA_ERR → BSP_ERR_NO_DEVICE`(E-44 的本义就是"芯片没驱动 DB 总线",归到**器件无响应**比留在私有 −3 有用,且避开与 `BSP_ERR_TIMEOUT` 撞值)。 +**`tpafe5160_snapshot()` 刻意不套成败码**:它 1=有新帧 / 0=无新帧 / −1=参数错,**0 不是错误**,是"这一拍没有新数据",强行收编会把慢消费者的正常路径变成失败。 | +| | **也纠我第二次写错的一条**:我在 `bsp_err.h` 里断言"本层只有 `net_*` 一族把数量与错误挤在同一返回值"——`snapshot`/`adc_input_poll` 就是第二族,已把两族都写明并各注原因。 | +| | **C2**:FTL 坍缩点(`ftl_flush_cache`/`ftl_read_page`)不再把 dhara 失败压成 `GD5F_ERROR`,按处置差异分三类并**就地**打日志(最早同时知道 LPN 与原始码的位置):`ECC/TOO_BAD → +BSP_ERR_CRC`(页级,重试/换页)、`JOURNAL_FULL/MAP_FULL → BSP_ERR_NO_SPACE`(**块池见底,重格式化只是把问题推回去**)、其余 `→ BSP_ERR_IO`(完整性,保卷 + +上报,不许自愈重格式化)——与 **E-31** 是同一条纪律的两端。可读名用自建短表:**本仓 dhara 没有编 `error.c`,`dhara_strerror()` 会直接链接失败**。`disk_read`/`disk_write` +仍回 `RES_ERROR`(那是 FatFs 契约),要让 defaultTask 卷健康探针**按类分支**还差一个跨调用存活的快照 → **C4 待还**。口径 → `存储架构说明.md` §10.4。 | +| | **又一次"凭猜测就会改错代码"的自查**:我一度认定 `DBG_ERROR("... err=%d", err)` 是把结构体塞进 varargs 的格式 bug,准备顺手修——**核过 `Lib/dhara/error.h`:`dhara_error_t` +是 `enum`(标量)**,那行日志本来就合法。若没核实,我会把一条正常日志"修"成访问 `err.type` 而编译失败或语义错。 | +| | **验收(这一波有真验证面,和 RS485 试点不同)**:`@build` **0 错 0 警告**(`Code` 213,648、RO 18,860、`ER_IROM1` **232,508 B / 22.2%**、**RAM 三项零增量**);`@test` +PASS(681 ms)且逐条读到被改模块的锚点:`SD2506 init: OK (ret=0)` + ID/时间/温度/电池四条读回正常、`ADC init: OK`、`NAND init: OK`、`Map resumed: 14094/97504`、 +`adc=20803→28875` 帧计数持续递增(**证明 snapshot 三态语义没被改坏**)、`[ERR]/Fault/FAIL` 0 条;主机单测 100/100;`bsp-err` 基线 **19 → 17**、`--prune` 干净; +`doc-links --all --sections-strict` 147 篇 0 问题;`layer-deps`、`id-check --strict`、`mem-numbers`、`mem-budget`、`task-table`、`ui-golden`、`registration-check` 全过。 | +| | 口径与数字联动:`TPAFE5160/README.md`、`GD5F2GQ5UE_Test_Guide.md`、`GD5F2GQ5UE_Trap_Records.md`(旧名均留痕,便于对旧日志);`存储架构说明.md` **新增 §10.4**; +`AGENTS.md` 与 `pre-commit使用说明.md` §2.27 的基线计数 19 → 17;配图桶按 .map 对象级增量大改(**dhara/FTL 10,931 → 11,222 = +291 全在 `nand_ftl.o`**、应用代码/UI ++36 在 `app_main.o`、其余散量入"其他"7,557,桶和 = `Code+RO` 断言通过);`ram_baseline`/`check_mem_numbers --write`/`ram_report_20261023.md` 快照与本文 +§1.3/§5.1/§5.4、部署文档 §3.3 数字全部同步。**TPAFE5160/GD5F 两个对象的 Code 增量实测为 0**(纯改名,不产生一条指令)——这也是"改名不该被当成性能风险"的实证。 | +| **2026-10-23** | **S-5 起步:BSP 错误码契约 + `bsp-err` 门禁 + RS485 试点**(钩子 38 → **39**) | 先纠本报告自己:原文「无统一错误码/超时/重试约定」**不准**,实测是**六套并存**——0/−1(SD2506、NETCFG、nand_ftl)、自研枚举 `CH395F_STATUS_*`、芯片寄存器原生码 `CH395F_ERR_*`(与前一套同模块并存)、`HAL_StatusTypeDef`(`rs485.h` 曾直接外泄)、`bool`, +再加第三方 dhara 的出参 `dhara_error_t` 与 FatFs 的 `FRESULT`。超时侧是**同形两义**:`SD2506_I2C_TIMEOUT_MS 100`/`CH395F_SPI_BYTE_TIMEOUT_MS 100U` 是毫秒,`GD5F_SPI_TIMEOUT 100` 是 +**自旋圈数**(改 SPI 分频或换主频,时长跟着变且无人报警),`GD5F_BUSY_TIMEOUT` / `TPAFE5160_BUSY_TIMEOUT = -2` 其实是**返回码**;`osWaitForever` 与 `HAL_MAX_DELAY` **同为 0xFFFFFFFFU** +(值同域不同 → 跨层转手既不报错也不变慢)。**契约真正值钱的是把「能不能重试」提到公共层**: +以前它只写在个别注释里(`gd5f2gq5ue.c`:「GD5F_BUSY_TIMEOUT(-2) - 超时(可重试)」), +语义在、跨模块不可判定。 | +| | 新增 `Drivers/BSP/bsp_err.h`——**header-only、无 `.c`**(不占体积、不需双端注册):`0=BSP_OK`、 +11 个负值码、`BSP_ERR_CAN_RETRY(e)`、`BSP_WAIT_NONE/FOREVER/MS` 与边界纪律。边界纪律明确三件事: +第三方码**就地映射不改名**(改名等于伪造语义);**`net_*` BSD 兼容层不纳入**(用户裁定:调用方 +要的就是「返回字节数 + 每任务 errno 槽」那个形状,模拟器 Winsock 后端同形状);**lftpd 按第三方 +排除**(它的 226/425/450/550 是 FTP 协议码,不是内部错误码)。 | +| | **也纠我自己写错的一条**:我一度在 `bsp_err.h` 里把 `gd5f2gq5ue_read/write/erase` 当「计数或错误挤 +同一返回值」的反例——**核过实现与头注释,它们是「0=成功、其它=错误码」**,本层真正的 count-or-error +只有 `net_*`(已裁定不纳入)。原文已改正,并把「数量走出参」降级为**面向新 API 的原则**。 | +| | **试点 = RS485**:`rs485_transmit` / `rs485_receive_start` 不再外泄 `HAL_StatusTypeDef`,改 `bsp_err_t` ++ 模块内 `static rs485_err_from_hal()` 映射(HAL 码不出模块),顺带补 `p_handle` 判空。 +`@build` **0 错 0 警告**(`Code` 213,436 → **213,484,+48 B**;RO 不变、**RAM 一字未动**)、`@test` +PASS(702 ms)。**诚实标注**:这两个函数当前**没有生产调用点**(B2 冻结未接线),所以 `@test` +只证明「没破坏启动」,**不证明新错误码在运行时被正确使用**——下一个试点该挑在启动链上的模块 +(建议 SD2506,41 处调用点)。 | +| | **门禁 `bsp-err`**(`scripts/check_bsp_err.py`,三条规则:R1 不得拿 `HAL_StatusTypeDef`/`bool` 当错误 +通道、R2 `*_TIMEOUT` 宏不得取负值、R3 非 void 公开 API 必须有「返回值:」)。**规则改窄而不是 +拿基线洗规则**:第一版把 7 个正当谓词(`ui_..._is_active` / `has_children`)误判成违规,遂加 +`is_/has_/can_/valid` 白名单。存量 **19 条**钉基线、只减不增(两个 `*_BUSY_TIMEOUT = -2`、一个 +`bool ui_content_page_open()`、16 个缺注释)。**当场自证**:临时埋 3 条违规 → `命中 22(在册 19 / +新增 3)`、**RC=1**,三条规则各自点名并给出路;删掉探针 → `19/19/新增 0`、RC=0。 | +| | **一条实测负结果**(值得记):`__attribute__((warn_unused_result))` **在 ARMCC v5 上不生效**—— +属性挂上、调用点裸调不接返回值,构建仍 0 错 0 警告(第一次实验我误用 `(void)`,那本就是「故意 +丢弃」的写法,重做了一次)。所以「返回值必须被消费」只能靠门禁脚本 + 书写约定「故意丢弃必须 +写 `(void)`」。 | +| | **pre-commit 的坑**:钩子跑之前会 **stash 未暂存改动**——我先把配置 `git add` 了但没加 rs485, +钩子看到的树里 rs485 还是 `HAL_StatusTypeDef`,于是**假报警 2 条**。口径:改代码 + 同时更新基线 +时,**先把改动 `git add` 再跑钩子**。已写进 `pre-commit使用说明.md` §2.27。 | +| | 口径同步:`AGENTS.md`(钩子数 + 枚举 + 索引行)、`pre-commit使用说明.md`(新增 §2.27 + 计数 + +脚本清单行)、`并行任务工作树方案.md`、本文 §0.1/§0.4/§7 与 §9 的 S-5 行。数字联动(`ram_baseline +--update/--write`、`check_mem_numbers --write`、FLASH 配图常量、本文 §1.3/§5.1/§5.4 与部署文档 §3.3) +随本波提交一并做。**待还**:两个 `*_BUSY_TIMEOUT = -2` 改名(波及板级套件与 Trap 记录用词,需真机 ++ 套件回归)、FTL 坍缩点拆三类码(ECC / 介质 / 映射层坏块预算,让上层能区分「重试 / 标坏块 / +别再 mkfs」)、其余模块逐个迁移。 | | **2026-10-22** | **S-1 只做「防扩大」那一半:新增 `layer-deps` 分层方向门禁**(推荐清单第 5 步拆分执行,钩子 37 → **38**) | 上一轮的结论是「反转暂缓、门禁现在就该上」,这次照做。 为什么门禁比搬迁值钱:AGENTS 与分层图早就写着「六层单向依赖」,可从来没有机械检查——于是同一件事的记载从 2026-09 的 「NETCFG 一处」漂到今天的「8 文件 9 处」,全靠人读代码才发现。边界的静默腐蚀只能靠门禁止住。 | diff --git a/scripts/check_bsp_err.py b/scripts/check_bsp_err.py new file mode 100644 index 00000000..e54d3a18 --- /dev/null +++ b/scripts/check_bsp_err.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""BSP 错误码与超时命名约定检查(报告 S-5 的机械检查那一半)。 + +为什么要有这个脚本:约定写在 docs 里不会自己生效。同一层的驱动今天并存六套返回约定, +而"能不能重试"只写在个别注释里;同形两义的 *_TIMEOUT 已经出现三类(毫秒 / 自旋圈数 / +其实是返回码)。没有机械检查,新代码只会继续按各自旧习惯长。 + +三条规则(只看 Drivers/BSP 下的公开头文件 = 对外 API 面;lftpd 按第三方排除): + R1 公开 API 不得返回 HAL_StatusTypeDef / bool + bool 把"为什么失败"整条丢掉;HAL 枚举把 HAL 焊进调用方的 include 链。 + R2 *_TIMEOUT 宏不得取负值 + 名字叫 TIMEOUT 却是返回码,读代码的人会当成时长;超时应返回 BSP_ERR_TIMEOUT。 + R3 非 void 的公开 API 必须在紧邻注释块里写「返回值:」 + 约定要能被读到才算约定,也才谈得上审查。 + +增量拦截(与 layer-deps / id-check 同构):BASELINE 钉住存量,只减不增,没有 --write。 +修完一条跑 --prune,它会点名"这条已消失,请从 BASELINE 删除"。 + +退出码:0 = 通过;1 = 有新增违规;2 = 用法或扫描错误。 +""" + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +EXCLUDE = ("lftpd/",) + +# 存量基线(2026-10-23 清点):"文件|规则|名字"。修掉一条删一行,不要往里加。 +BASELINE = frozenset( + line.strip() for line in """ +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_block_erase +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_check_ecc +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_page_read +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_program_exec +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_program_load +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_read_from_cache +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_read_status +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_wait_busy +Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h|R3|gd5f_write_enable +Drivers/BSP/GD5F2GQ5UE/nand_ftl.h|R3|nand_ftl_format +Drivers/BSP/GD5F2GQ5UE/nand_ftl.h|R3|nand_ftl_init +Drivers/BSP/UI/ui_content_page.h|R1|ui_content_page_open +Drivers/BSP/UI/ui_menu_tab.h|R3|ui_menu_tab_children +Drivers/BSP/UI/ui_menu_tab.h|R3|ui_menu_tab_has_children +Drivers/BSP/UI/ui_menu_tab.h|R3|ui_menu_tab_has_pwd +Drivers/BSP/UI/ui_menu_tab.h|R3|ui_menu_tab_name +Drivers/BSP/UI/ui_menu_tab.h|R3|ui_menu_tab_valid +""".splitlines() if line.strip() +) + +DEFINE_RE = re.compile(r"^\s*#define\s+([A-Z][A-Z_0-9]*TIMEOUT[A-Z_0-9]*)\s+(-[0-9]+)", + re.MULTILINE); +# bool 用在"提问"上是合法的(is_editable / has_children / is_active…);只有拿 bool 当 +# **错误通道**才是问题。用名字形状区分,避免把 7 个正当谓词误判成违规、再拿基线去洗规则。 +PREDICATE_RE = re.compile(r"(^|_)(is|has|can|should|need|valid|active|open|empty)_|" + + r"_(is|has|can|valid)$") +DECL_RE = re.compile(r"^\s*(?:extern\s+)?([A-Za-z_][A-Za-z_0-9_ ]*[ ]+\**)" + r"([a-z_][a-z_0-9]*)\s*\([^;{]*\)\s*;", re.MULTILINE); + + +def tracked_headers(): + """git 跟踪的 BSP 公开头文件(排除第三方 lftpd)。""" + out = subprocess.run(["git", "ls-files", "--", "Drivers/BSP"], + cwd=str(ROOT), capture_output=True, text=True) + if out.returncode != 0: + print("git ls-files 失败:", out.stderr.strip()) + raise SystemExit(2) + files = [] + for rel in out.stdout.split(): + rel = rel.replace("\\", "/") + if not rel.endswith(".h"): + continue + if any(part in rel for part in EXCLUDE): + continue + files.append(rel) + return sorted(files) + + +def doc_block_before(lines, idx): + """取声明上方紧邻的注释块文本(向上直到 /* 或空行为止)。""" + buf = [] + j = idx - 1 + while j >= 0: + t = lines[j].strip() + if t.endswith("*/") or t.startswith("*") or t.startswith("/*"): + buf.append(t) + if t.startswith("/*"): + break + j -= 1 + continue + break + return "\n".join(buf) + + +def scan(): + hits = [] + for rel in tracked_headers(): + try: + text = (ROOT / rel).read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as exc: + print("读不了", rel, exc) + raise SystemExit(2) + lines = text.splitlines() + for m in DEFINE_RE.finditer(text): + hits.append((rel, "R2", m.group(1))) + for m in DECL_RE.finditer(text): + ret = m.group(1).replace("const", "").strip() + name = m.group(2) + flat = ret.replace(" ", ""); + if name in ("void", "return", "if", "while", "sizeof", "typedef", "static"): + continue + is_bool = flat.endswith("bool") or flat.endswith("_Bool") + if flat.endswith("HAL_StatusTypeDef") or (is_bool and not PREDICATE_RE.search(name)): + hits.append((rel, "R1", name)) + if flat == "void": + continue + idx = text[:m.start()].count("\n") + if "返回值" not in doc_block_before(lines, idx): + hits.append((rel, "R3", name)) + return hits + + +def main(): + ap = argparse.ArgumentParser(description="BSP 错误码/超时命名约定(增量拦截)") + ap.add_argument("--verbose", action="store_true", help="列出全部命中与归类") + ap.add_argument("--prune", action="store_true", help="只报已消失、可从 BASELINE 删除的条目") + ap.add_argument("--emit-baseline", action="store_true", + help="打印当前全部命中的键(建基线用)") + args = ap.parse_args() + + keys = {"%s|%s|%s" % h for h in scan()} + + if args.emit_baseline: + for key in sorted(keys): + print(key) + print("# 共 %d 条" % len(keys)) + return 0 + + gone = sorted(set(BASELINE) - keys) + fresh = sorted(keys - set(BASELINE)) + + if args.prune: + if not gone: + print("[prune] 基线 %d 条全部仍在" % len(BASELINE)) + for key in gone: + print("[prune] 这条已消失,请从 BASELINE 删除:", key) + return 0 + + if args.verbose: + for key in sorted(keys): + print(" [%s] %s" % ("基线" if key in BASELINE else "新增", key)) + + print("BSP 约定:命中 %d(在册 %d / 新增 %d),基线共 %d" + % (len(keys), len(keys & set(BASELINE)), len(fresh), len(BASELINE))) + if fresh: + print("[FAIL] 新增违反 BSP 约定的条目:") + for key in fresh: + print(" -", key) + print(' 出路:① 返回 bsp_err_t(Drivers/BSP/bsp_err.h,0=成功、负值=失败,') + print(' 可重试判定用 BSP_ERR_CAN_RETRY);② 时长宏一律 *_MS 且必须是时间,') + print(' 自旋圈数改名 *_LOOP 并注释写明非时间量;③ 返回码不许叫 *_TIMEOUT。') + return 1 + if gone: + print("[提醒] 有 %d 条基线已还清,请用 --prune 列出并从 BASELINE 删除" % len(gone)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/ram_baseline.tsv b/scripts/ram_baseline.tsv index 73e89db2..d7a4e65c 100644 --- a/scripts/ram_baseline.tsv +++ b/scripts/ram_baseline.tsv @@ -5,3 +5,4 @@ date code ro rw zi er_irom1 er_pct iram1 iram1_pct ccm ccm_pct top_rwzi 2026-10-15 213540 18740 1136 131520 232280 22.2 75232 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 2026-10-20 213356 18580 1184 131480 231936 22.1 75240 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 2026-10-22 213436 18652 1188 131500 232088 22.1 75264 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 +2026-10-23 213648 18860 1188 131500 232508 22.2 75264 57.4 57376 87.9 heap_4.o=57376;lv_mem_core_builtin.o=16384;lftpd.o=7884;ui_net_sink.o=6766;ui_port.o=6420;nand_ftl.o=6216;ch395f.o=4162;console_log.o=4120;sys_log_mcu.o=3588;default_task.o=2736 diff --git a/test/board/sd2506_test_task.c b/test/board/sd2506_test_task.c index b0617020..6af8347a 100644 --- a/test/board/sd2506_test_task.c +++ b/test/board/sd2506_test_task.c @@ -36,13 +36,13 @@ static void sd2506_phase_basic(void) DBG_INFO("=== SD2506 Phase 1: basic read/write (public API) ==="); /* TC-RTC-001: init + 读取当前时间 */ - int ret = sd2506_init(); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_init() ret=%d (expect 0)", ret); + bsp_err_t ret = sd2506_init(); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_init() ret=%d (expect 0)", ret); sd2506_time_t r; ret = sd2506_get_time(&r); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_time() ret=%d (expect 0)", ret); + if (ret == BSP_OK) { DBG_INFO(" current RTC: %04u-%02u-%02u %02u:%02u:%02u wk=%u", (unsigned int)r.year, (unsigned int)r.month, (unsigned int)r.day, (unsigned int)r.hour, (unsigned int)r.minute, @@ -59,11 +59,11 @@ static void sd2506_phase_basic(void) t.year = 2026; t.month = 8; t.day = 28; t.hour = 12; t.minute = 0; t.second = 0; t.week = 4; ret = sd2506_set_time(&t); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_time() ret=%d (expect 0)", ret); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_set_time() ret=%d (expect 0)", ret); ret = sd2506_get_time(&r); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret); + if (ret == BSP_OK) { DBG_INFO(" set : %04u-%02u-%02u %02u:%02u:%02u", (unsigned int)t.year, (unsigned int)t.month, (unsigned int)t.day, (unsigned int)t.hour, (unsigned int)t.minute, (unsigned int)t.second); @@ -81,11 +81,11 @@ static void sd2506_phase_basic(void) #endif /* TC-RTC-003: 走时。写后等 3s,应前进约 3 秒 */ - if (ret == SD2506_OK) { + if (ret == BSP_OK) { osDelay(3000); sd2506_time_t r2; - int ret2 = sd2506_get_time(&r2); - if (ret2 == SD2506_OK) { + bsp_err_t ret2 = sd2506_get_time(&r2); + if (ret2 == BSP_OK) { int adv = (r2.second + 60 * (r2.minute + 60 * (r2.hour + 24 * r2.day))) - (r.second + 60 * (r.minute + 60 * (r.hour + 24 * r.day))); if (adv < 0) adv += 60 * 60 * 24 * 32; @@ -128,9 +128,9 @@ static void sd2506_phase_api(void) /* TC-RTC-011: 内部温度读取 */ int8_t temp = 0; - int ret = sd2506_get_temperature(&temp); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_temperature() ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + bsp_err_t ret = sd2506_get_temperature(&temp); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_temperature() ret=%d (expect 0)", ret); + if (ret == BSP_OK) { DBG_INFO(" temperature: %d C", (int)temp); SD2506_TEST_CHECK(temp >= -40 && temp <= 85, "temperature in range [-40,85]: %d", (int)temp); @@ -139,8 +139,8 @@ static void sd2506_phase_api(void) /* TC-RTC-012: 电池电压(毫伏) */ uint16_t volt = 0U; ret = sd2506_get_battery_voltage(&volt); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_battery_voltage() ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_battery_voltage() ret=%d (expect 0)", ret); + if (ret == BSP_OK) { DBG_INFO(" battery: %u mV", (unsigned int)volt); SD2506_TEST_CHECK(volt > 2000U && volt < 5000U, "battery voltage plausible: %u mV", (unsigned int)volt); @@ -149,8 +149,8 @@ static void sd2506_phase_api(void) /* TC-RTC-013: 芯片 8 字节 ID */ uint8_t id[8] = {0}; ret = sd2506_get_id(id); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_id() ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_id() ret=%d (expect 0)", ret); + if (ret == BSP_OK) { DBG_INFO(" id: %02X%02X%02X%02X%02X%02X%02X%02X", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]); } @@ -161,10 +161,10 @@ static void sd2506_phase_api(void) uint8_t wbuf[8] = {0xA1U, 0xB2U, 0xC3U, 0xD4U, 0xE5U, 0xF6U, 0x07U, 0x18U}; uint8_t rbuf[8] = {0}; ret = sd2506_write_sram(0U, wbuf, 8U); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_write_sram() ret=%d (expect 0)", ret); - if (ret == SD2506_OK) { + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_write_sram() ret=%d (expect 0)", ret); + if (ret == BSP_OK) { ret = sd2506_read_sram(0U, rbuf, 8U); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_read_sram() ret=%d (expect 0)", ret); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_read_sram() ret=%d (expect 0)", ret); int sram_ok = (memcmp(wbuf, rbuf, 8U) == 0); SD2506_TEST_CHECK(sram_ok, "SRAM write==readback"); } @@ -179,9 +179,9 @@ static void sd2506_phase_api(void) sd2506_time_t at; at.year = 2026; at.month = 8; at.day = 28; at.hour = 12; at.minute = 0; at.second = 30; at.week = 4; ret = sd2506_set_alarm(&at, (uint8_t)(SD2506_AL_EN_EAS | SD2506_AL_EN_EAMN)); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret); ret = sd2506_clear_alarm(); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_clear_alarm() ret=%d (expect 0)", ret); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_clear_alarm() ret=%d (expect 0)", ret); } #else DBG_INFO("TC-RTC-015 skipped: destructive tests disabled (RTC alarm write)"); @@ -202,12 +202,12 @@ void sd2506_test_run(void) "ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)"); #endif - int ret = sd2506_init(); - SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_init() ret=%d (expect 0)", ret); + bsp_err_t ret = sd2506_init(); + SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_init() ret=%d (expect 0)", ret); /* 信息:读取 CTR1 默认状态(确认 WRTC 初始/可写能力,纯诊断) */ uint8_t c1 = 0x00U; - if (sd2506_read_ctr1(&c1) == SD2506_OK) { + if (sd2506_read_ctr1(&c1) == BSP_OK) { DBG_INFO(" CTR1=0x%02X (WRTC2=CTR1.2=%u WRTC3=CTR1.7=%u)", (unsigned int)c1, (unsigned int)((c1 >> 2) & 1U), @@ -217,7 +217,7 @@ void sd2506_test_run(void) #ifdef ENABLE_DESTRUCTIVE_TESTS /* 记录原始时间,测试结束恢复,避免把板子时间留在测试值 */ sd2506_time_t orig; - int have_orig = (sd2506_get_time(&orig) == SD2506_OK); + int have_orig = (sd2506_get_time(&orig) == BSP_OK); if (!have_orig) { DBG_INFO(" WARN: cannot read original time; restore step will be skipped"); } diff --git a/test/host/nand_port/run_unit_test.py b/test/host/nand_port/run_unit_test.py index 169fe6a6..c4a231cf 100644 --- a/test/host/nand_port/run_unit_test.py +++ b/test/host/nand_port/run_unit_test.py @@ -50,6 +50,7 @@ BUILD = TestBuild( src=os.path.join(HERE, "nand_port_test.c"), include_dirs=[ os.path.join(HERE, "stub"), + os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(nand_ftl.c 依赖,2026-10-23 S-5) os.path.join(ROOT, "Drivers", "BSP", "GD5F2GQ5UE"), os.path.join(ROOT, "Lib", "dhara"), os.path.join(ROOT, "Lib", "FatFs"), diff --git a/test/host/sys_clock/run_unit_test.py b/test/host/sys_clock/run_unit_test.py index af4af928..261427d4 100644 --- a/test/host/sys_clock/run_unit_test.py +++ b/test/host/sys_clock/run_unit_test.py @@ -39,13 +39,14 @@ from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402 # 顺序敏感:stub 目录必须**先于**生产目录,才能覆盖 main.h / FreeRTOS.h / task.h / # dbg_log.h(使 sys_clock.c 可在 PC 上编译)。sd2506.h 用真实头(其内部 #include -# "main.h" 命中 stub,故无需桩),保留原汁的 sd2506_time_t 与 SD2506_OK。 +# "main.h" 命中 stub,故无需桩),保留原汁的 sd2506_time_t 与 BSP_OK(sd2506.h 现直接 include "bsp_err.h")。 BUILD = TestBuild( name="System Clock Tests", src=os.path.join(HERE, "sys_clock_test.c"), include_dirs=[ os.path.join(HERE, "stub"), os.path.join(ROOT, "App", "util"), + os.path.join(ROOT, "Drivers", "BSP"), # bsp_err.h(sd2506.h 依赖,2026-10-23 S-5) os.path.join(ROOT, "Drivers", "BSP", "SD2506"), HERE, ], diff --git a/test/host/sys_clock/sys_clock_test.c b/test/host/sys_clock/sys_clock_test.c index 899fbf93..ee30c53a 100644 --- a/test/host/sys_clock/sys_clock_test.c +++ b/test/host/sys_clock/sys_clock_test.c @@ -61,7 +61,7 @@ #include "dbg_log.h" /* - * sd2506_time_t 与 SD2506_OK 必须先可见(桩函数签名 / 阴影寄存器用它)。 + * sd2506_time_t 与 BSP_OK 必须先可见(桩函数签名 / 阴影寄存器用它)。 * 真实 sd2506.h 内部 #include "main.h" 命中本目录 stub/main.h,故可安全使用; * sys_clock.h 提供被测 API 原型(与 sys_clock.c 内部 include 走同一 guard)。 */ @@ -85,27 +85,27 @@ uint32_t HAL_GetTick(void) { return s_tick; } * - s_rtc_set_calls :写次数(观测 init/set 是否写回) */ static sd2506_time_t s_rtc; -static int s_rtc_read_rc = SD2506_OK; -static int s_rtc_write_rc = SD2506_OK; +static bsp_err_t s_rtc_read_rc = BSP_OK; +static bsp_err_t s_rtc_write_rc = BSP_OK; static int s_rtc_get_calls = 0; static int s_rtc_set_calls = 0; int sd2506_get_time(sd2506_time_t *p_time) { s_rtc_get_calls++; - if (s_rtc_read_rc != SD2506_OK) { + if (s_rtc_read_rc != BSP_OK) { return s_rtc_read_rc; /* 故障注入:不动 p_time */ } *p_time = s_rtc; - return SD2506_OK; + return BSP_OK; } int sd2506_set_time(const sd2506_time_t *p_time) { s_rtc_set_calls++; - if (s_rtc_write_rc != SD2506_OK) { + if (s_rtc_write_rc != BSP_OK) { return s_rtc_write_rc; } s_rtc = *p_time; /* 成功则阴影寄存器被更新(复读可见) */ - return SD2506_OK; + return BSP_OK; } /* DBG_ERROR 计数 */ @@ -203,8 +203,8 @@ static void tc_001_init_ok(void) { int rc; printf("TC-SYSCLK-001 init 正常(RTC 合法)\n"); rtc_set(2026, 8, 24, 0, 0, 0); - s_rtc_read_rc = SD2506_OK; - s_rtc_write_rc = SD2506_OK; + s_rtc_read_rc = BSP_OK; + s_rtc_write_rc = BSP_OK; clear_obs(); s_tick = 12345u; @@ -219,7 +219,7 @@ static void tc_002_init_leap(void) { printf("TC-SYSCLK-002 init 闰年换算\n"); /* 2024 是闰年:2/29 存在 */ rtc_set(2024, 2, 29, 12, 0, 0); - s_rtc_read_rc = SD2506_OK; + s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "2024-02-29 合法"); CHECK_EQ_U32(sys_clock_get(), ref_unix(2024, 2, 29, 12, 0, 0), "2024-02-29 12:00:00 换算正确"); @@ -241,15 +241,15 @@ static void tc_002_init_leap(void) { static void tc_003_init_month_end(void) { printf("TC-SYSCLK-003 init 月末边界\n"); - rtc_set(2026, 1, 31, 0, 0, 0); s_rtc_read_rc = SD2506_OK; + rtc_set(2026, 1, 31, 0, 0, 0); s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "1/31 合法"); CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 1, 31, 0, 0, 0), "1/31 换算"); - rtc_set(2026, 2, 28, 23, 59, 59); s_rtc_read_rc = SD2506_OK; + rtc_set(2026, 2, 28, 23, 59, 59); s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "2/28 合法(平年)"); CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 2, 28, 23, 59, 59), "2/28 末端换算"); - rtc_set(2026, 4, 30, 0, 0, 0); s_rtc_read_rc = SD2506_OK; + rtc_set(2026, 4, 30, 0, 0, 0); s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "4/30 合法(30 天月)"); CHECK_EQ_U32(sys_clock_get(), ref_unix(2026, 4, 30, 0, 0, 0), "4/30 换算"); @@ -269,12 +269,12 @@ static void tc_004_init_epoch_bounds(void) { * Unix 0 经由 set 路径可达,见 tc_017(base_at(0) 等价)。 * 这里验证 init 可接受的最早年月,以及 uint32 上限。 */ - rtc_set(2000, 1, 1, 0, 0, 0); s_rtc_read_rc = SD2506_OK; + rtc_set(2000, 1, 1, 0, 0, 0); s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "2000-01-01 合法(可接受最早年)"); CHECK_EQ_U32(sys_clock_get(), ref_unix(2000, 1, 1, 0, 0, 0), "2000-01-01 换算"); /* 2038 上限:2038-01-19 03:14:07 = 0x7FFFFFFF */ - rtc_set(2038, 1, 19, 3, 14, 7); s_rtc_read_rc = SD2506_OK; + rtc_set(2038, 1, 19, 3, 14, 7); s_rtc_read_rc = BSP_OK; CHECK(sys_clock_init() == 0, "2038-01-19 合法(uint32 上限内)"); CHECK_EQ_U32(sys_clock_get(), 0x7FFFFFFFu, "2038-01-19 03:14:07 → 0x7FFFFFFF"); @@ -292,8 +292,8 @@ static void tc_004_init_epoch_bounds(void) { static void tc_005_init_rtc_read_fail(void) { printf("TC-SYSCLK-005 init RTC 读失败 → 降级默认时间并返回 -1\n"); - s_rtc_read_rc = SD2506_ERROR; /* 注入读故障 */ - s_rtc_write_rc = SD2506_OK; + s_rtc_read_rc = BSP_ERR_IO; /* 注入读故障 */ + s_rtc_write_rc = BSP_OK; clear_obs(); s_tick = 777u; @@ -305,7 +305,7 @@ static void tc_005_init_rtc_read_fail(void) { RTC_DEFAULT_MINUTE, RTC_DEFAULT_SECOND), "降级基准 = 编译期默认时间(墙钟仍可用)"); CHECK(g_sclk_err_count >= 1, "打了 DBG_ERROR(RTC 异常)"); - s_rtc_read_rc = SD2506_OK; /* 复位注入 */ + s_rtc_read_rc = BSP_OK; /* 复位注入 */ } static void tc_006_init_out_of_range_selfheal(void) { @@ -316,8 +316,8 @@ static void tc_006_init_out_of_range_selfheal(void) { * (只有写回/复读仍失败才走降级 -1,见 tc_005 与 tc_020。) */ rtc_set(1999, 5, 5, 0, 0, 0); /* year < 2000 → 越界 */ - s_rtc_read_rc = SD2506_OK; - s_rtc_write_rc = SD2506_OK; + s_rtc_read_rc = BSP_OK; + s_rtc_write_rc = BSP_OK; clear_obs(); CHECK(sys_clock_init() == 0, "year=1999 → 自愈写回成功 → 返回 0"); CHECK(s_rtc_set_calls == 1, "越界触发一次写回"); @@ -341,8 +341,8 @@ static void tc_020_init_selfheal_fail_degrades(void) { * rtc_ok 清 0 → 走降级分支(启用默认值时建基准 + 返回 -1)。 */ rtc_set(1999, 5, 5, 0, 0, 0); - s_rtc_read_rc = SD2506_OK; - s_rtc_write_rc = SD2506_ERROR; /* 写回失败 */ + s_rtc_read_rc = BSP_OK; + s_rtc_write_rc = BSP_ERR_IO; /* 写回失败 */ clear_obs(); s_tick = 42u; int rc = sys_clock_init(); @@ -352,14 +352,14 @@ static void tc_020_init_selfheal_fail_degrades(void) { CHECK_EQ_U32(sys_clock_get(), ref_unix(RTC_DEFAULT_YEAR, RTC_DEFAULT_MONTH, RTC_DEFAULT_DAY, 0, 0, 0), "降级基准 = 编译期默认时间(墙钟仍可用)"); - s_rtc_write_rc = SD2506_OK; + s_rtc_write_rc = BSP_OK; } static void tc_007_init_corrects_rtc(void) { printf("TC-SYSCLK-007 init 越界 → 自动写回默认时间并复验\n"); rtc_set(1999, 1, 1, 0, 0, 0); /* 越界,触发校时写回 */ - s_rtc_read_rc = SD2506_OK; - s_rtc_write_rc = SD2506_OK; + s_rtc_read_rc = BSP_OK; + s_rtc_write_rc = BSP_OK; clear_obs(); int rc = sys_clock_init(); CHECK(s_rtc_set_calls == 1, "越界 → 写回 RTC 一次"); @@ -477,7 +477,7 @@ static void tc_016_set_writes_rtc(void) { sd.year = 2027; sd.month = 3; sd.day = 15; sd.hour = 6; sd.minute = 7; sd.second = 8; sd.week = 1; - s_rtc_write_rc = SD2506_OK; + s_rtc_write_rc = BSP_OK; clear_obs(); s_tick = 100u; CHECK(sys_clock_set(7000000u, &sd) == 0, "set 返回 0"); @@ -489,11 +489,11 @@ static void tc_016_set_writes_rtc(void) { /* 写失败:基准仍更新,但打错误日志 */ clear_obs(); - s_rtc_write_rc = SD2506_ERROR; + s_rtc_write_rc = BSP_ERR_IO; sys_clock_set(7000001u, &sd); CHECK_EQ_U32(sys_clock_get(), 7000001u, "RTC 写失败仍更新基准"); CHECK(g_sclk_err_count >= 1, "RTC 写失败打 DBG_ERROR"); - s_rtc_write_rc = SD2506_OK; + s_rtc_write_rc = BSP_OK; } static void tc_017_set_no_rtc(void) { @@ -509,7 +509,7 @@ static void tc_018_set_unix_fields(void) { sd2506_time_t expect_week; uint32_t u = ref_unix(2026, 10, 2, 15, 30, 45); clear_obs(); - s_rtc_write_rc = SD2506_OK; + s_rtc_write_rc = BSP_OK; s_tick = 0u; CHECK(sys_clock_set_unix(u) == 0, "set_unix 返回 0"); CHECK(s_rtc_set_calls == 1, "写回 RTC 一次"); diff --git a/tools/arch_diagram/gen_memory_svg.py b/tools/arch_diagram/gen_memory_svg.py index b40cecf6..738cbec5 100644 --- a/tools/arch_diagram/gen_memory_svg.py +++ b/tools/arch_diagram/gen_memory_svg.py @@ -45,13 +45,13 @@ RAM2_CATEGORIES = [ ("free", CCM_FREE, "#E0E0E0"), ] -# ── Flash breakdown (1MB = 1,048,576 B; 2026-10-22 @build) ────────────────── +# ── Flash breakdown (1MB = 1,048,576 B; 2026-10-23 @build) ────────────────── FLASH_TOTAL = 1048576 -FLASH_CODE = 213436 -FLASH_RO = 18652 +FLASH_CODE = 213648 +FLASH_RO = 18860 FLASH_RW = 1188 FLASH_USED = FLASH_CODE + FLASH_RO + FLASH_RW # ROM 合计(含 RW 初始化表) -FLASH_IROM = FLASH_CODE + FLASH_RO # ER_IROM1 区用量 = 232,088 B(22.1%) +FLASH_IROM = FLASH_CODE + FLASH_RO # ER_IROM1 区用量 = 232,508 B(22.2%) FLASH_FREE = FLASH_TOTAL - FLASH_USED FLASH_CATEGORIES = [ @@ -61,19 +61,19 @@ FLASH_CATEGORIES = [ ("Flash 空闲", FLASH_FREE, "#E0E0E0"), ] -# Flash Code+RO 分布(= ER_IROM1 232,088 B;2026-10-22 按 .map 对象表逐对象归类, -# 口径同 data/ram_report_20261022.md;"其他"含链接器填充) +# Flash Code+RO 分布(= ER_IROM1 232,508 B;2026-10-23 按 .map 对象表逐对象归类, +# 口径同 data/ram_report_20261023.md;"其他"含链接器填充) FLASH_CODE_SUB = [ ("LVGL 库", 87706, "#4CAF50"), - ("应用代码/UI", 57011, "#2196F3"), + ("应用代码/UI", 57047, "#2196F3"), ("CH395F+FTP", 25916, "#F44336"), ("HAL 驱动", 17876, "#607D8B"), - ("dhara / FTL", 10931, "#FF9800"), + ("dhara / FTL", 11222, "#FF9800"), ("FreeRTOS 内核", 10762, "#00BCD4"), ("FatFS", 8468, "#9C27B0"), ("C 库 (microlib)", 5486, "#795548"), ("startup", 468, "#B0BEC5"), - ("其他 (长尾)", 7464, "#E0E0E0"), # remainder + ("其他 (长尾)", 7557, "#E0E0E0"), # remainder(2026-10-23 S-5 C3/C1/C2 的零散增量) ] # ── Task stack budgets (2026-10-22 @build + 同日真机 [STACK] 实测; data/stack_report_20261022.md) ──