/* * 模块名称: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 */