feat(S-5): 收编 SD2506/TPAFE 并给 dhara 失败分类,门禁基线 19→17

前一笔立契约与门禁(`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 状态改为 🟡 主体已落地。
This commit is contained in:
2026-10-23 07:42:02 +08:00
parent 8d3349c587
commit 78248a1cdf
33 changed files with 1183 additions and 345 deletions
+5 -5
View File
@@ -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 {
+1 -1
View File
@@ -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
+79 -21
View File
@@ -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;
}
+42 -12
View File
@@ -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);
}
/*
+9 -5
View File
@@ -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 回调关联
+150 -115
View File
@@ -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);
+32 -26
View File
@@ -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 码转十进制
+1 -1
View File
@@ -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 消费者前的强制门禁」
+25 -25
View File
@@ -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++;
}
}
+20 -15
View File
@@ -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 通道数据(同一时刻
+139
View File
@@ -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 <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
* 不是时间,是自旋圈数(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 */