Files
STM32F4-Base/Drivers/BSP/bsp_err.h
T
wandering1 cc2966319a docs(S-5): 补齐 16 条返回值约定,bsp-err 基线 19 → 1,并修门禁自身误判
A(还债)先行,B(GD5F 的 SPI 失败被吞成成功)另波处理。本笔不改行为:@build 的
Program Size 与上一笔逐字段相同(Code 213,648、RO 18,860、RW 1,188、ZI 131,500)。

**补齐 16 条「返回值:」约定,bsp-err 存量基线 19 → 1**:
· gd5f2gq5ue.h 九个 SPI 原语(wait_busy / write_enable / read_status / page_read /
  read_from_cache / program_load / program_exec / block_erase / check_ecc)
  写全规范五要素,
  并把**每个函数能返回哪些码**写死:哪一个是"可重试"的 GD5F_WAIT_EXPIRED、哪一个才是
  器件自认坏块的 P_FAIL/E_FAIL、失败时出参为什么不可信(E-42 同族)。
· nand_ftl.h 从零散三行补成带模块头,并明写 **nand_ftl_init() 的 −1 抹掉了失败类**:
  分类目前只在失败点进日志,要按类分支需要 C4 快照(现无消费者故未做);同时警告"不得
  把 −1 解读成卷坏了可以重格式化"(E-31 的教训正在这条上)。
· ui_menu_tab.h 五个表查询 API 由行尾短注释升级为完整块(含"越界返回 false 是保守取
  只读"、"宁可按一张错表导航失败也不要按错表导航成功"这类判断依据)。

**修掉门禁自己的一条误判口径**(本笔最该记的):R3 原本要求紧邻注释块里出现字面"返回值",
而规范 V1.0 允许列对齐写法「返 回 值:」。后果有两层:① 合规写法被判违规;② 反向更糟——
gd5f_write_enable / gd5f_read_status 两条**早就"通过"了**,原因竟是它们的**函数说明**里顺口
写了"返回值"三个字,而不是真有一行返回值说明。**一条会因为散文里出现关键词而放行的规则,
等于在奖励运气**。修法是在块内忽略空格后再匹配关键词(排版选择不是违规),并在注释里写明
为什么不许把"必须写成不空格的形式"写进规则。

剩余 1 条刻意保留:bool ui_content_page_open()。它与"补注释"不是一档事——要改签名、要处理
3 个调用点(ui_menu.c:138/192/196 **今天三处全部丢弃返回值**,即"按 ENTER 开页失败是静默的"
这件事现在没人知道)、且 UI 源码双端编译(模拟器同一份),验证面是"重建模拟器 + 像素回归 +
真机逐页进菜单"。这不是还债,是独立一波改动,留档待决策。UI 三个文件带 BOM,Edit 会静默
剥掉 → 已按原样补回 3 字节(AGENTS 铁律:丢 BOM 后 ARMCC 按 CP936 解码,中文报 #870-D)。

**更正我自己写进文档的一条错话**:S-5 各条里我把 GD5F_SPI_TIMEOUT 100 记成"自旋圈数(改
分频时长会变)"。这次逐行核它 12 处用法,**全部是 HAL_* 超时参数或 HAL_GetTick() 差值 →
它也是毫秒**。真问题降级为"单位没写进名字(缺 _MS 后缀),读代码必须回实现里查单位"。
osWaitForever 与 HAL_MAX_DELAY 同为 0xFFFFFFFFU、以及 *_BUSY_TIMEOUT = -2 其实是返回码这两条
仍然成立(后者已改)。现状类条目(报告 §0.4、bsp_err.h、pre-commit使用说明.md §2.27、脚本
文件头)已全部改准;**已提交的变更记录行仍写着旧说法——按 §8.1 冻结历史不回改,以本笔新增
的更正行为准**。

验证:@build 0 错 0 警告(尺寸同上,故**不跑 @test**——无指令变化,跑它没有新信息);受影响
文件的 17 个钩子全 Passed(含 ui-golden 像素回归、cppcheck、编码规范、bsp-err 1/1/0 且
--prune 干净);check_doc_links --all --sections-strict 147 篇 0 问题;id-check
  --strict 通过;
9 个文件字节卫生 0 异常(UI 头 BOM 仍在、无 CR、无 U+FFFD、无行尾空格、均有尾换行)。
口径同步:AGENTS.md、.pre-commit-config.yaml、pre-commit使用说明.md §2.27 的基线计数 19 → 1;
报告 §0.4 / §9 / 附录 A 与变更记录两条(还债一条、更正一条)。
2026-10-23 08:25:58 +08:00

141 lines
9.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称: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 <stdint.h>
/* ============================================================================
* 一、错误码: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(**核对后的准确说法,我先前记成"自旋圈数"是错的**)
* 它其实也是毫秒(全部用法都是 HAL_* 超时参数或 HAL_GetTick() 差值,逐行核过),
* 真正的问题是**名字没带 _MS 后缀**:读代码的人必须回实现里查一遍才知道单位,
* 而同名的 *_TIMEOUT 在别的模块里又是返回码——单位与用途都靠"点进去看"。
* · 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 */