Files
STM32F4-Base/Drivers/BSP/SD2506/sd2506.c
T
wandering1 78248a1cdf 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 状态改为 🟡 主体已落地。
2026-10-23 07:42:02 +08:00

574 lines
23 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.
/*
* 模块名称:SD2506API-G RTC Driver
* 模块功能:SD2506API-G 高精度温补实时时钟驱动,I2C 接口
* 适用平台:STM32F407ZGTx + SD2506API-G 实时时钟芯片(I2C1: PB6-SCL, PB7-SDA)
* 作者:王建锋
* 创建日期:2026-07-17
* 修改记录:
* 2026-07-17 王建锋 创建初始版本,参考 SD2506API-G Ver2.0 手册
*/
#include <string.h>
#include <stdio.h>
#include "sd2506.h"
#include "i2c.h"
#define DBG_TAG "[RTC]"
#include "dbg_log.h"
/* ======================== 内部辅助函数 ======================== */
/*
* 函数功能:BCD 码转十进制数
* 入口参数:bcd - BCD 编码值 uint8_t 0x00 - 0x99
* 返回值:十进制数值 uint8_t
* 限定条件:输入为合法 BCD 编码(高 4 位和低 4 位均 <= 9)
* 函数说明:高 4 位为十位,低 4 位为个位,分别乘 10 和加后返回
*/
uint8_t sd2506_bcd_to_dec(uint8_t bcd) {
return ((bcd >> 4) * 10) + (bcd & 0x0FU);
}
/*
* 函数功能:十进制转 BCD 码
* 入口参数:dec - 十进制数 uint8_t 0 - 99 */
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
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:I2C 外设已初始化
* 函数说明:通过 I2C 向指定地址写入 1 字节数据
* 限定条件:I2C 外设已初始化
* 函数说明:通过 I2C 向指定地址写入 1 字节
*/
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 BSP_OK;
}
/*
* 函数功能:读单字节寄存器
* 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79
* p_val - 读取值指针 uint8_t*(非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:I2C 外设已初始化,p_val 为非空指针
* 函数说明:通过 I2C 从指定地址读取 1 字节数据
* 限定条件:I2C 外设已初始化,val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节
*/
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 BSP_OK;
}
/*
* 函数功能:写多字节寄存器(连续写)
* 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79
* p_data - 数据缓冲区指针 uint8_t*(非空)
* len - 数据长度 uint8_t 1 - N
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:I2C 外设已初始化,p_data 为非空指针
* 函数说明:从 reg 开始连续写入 len 字节数据
* 限定条件:I2C 外设已初始化,data 为非空指针
*/
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 BSP_OK;
}
/*
* 函数功能:读多字节寄存器(连续读)
* 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79
* p_data - 数据缓冲区指针 uint8_t*(非空)
* len - 数据长度 uint8_t 1 - N
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:I2C 外设已初始化,p_data 为非空指针
* 函数说明:从 reg 开始连续读取 len 字节数据
* 限定条件:I2C 外设已初始化,data 为非空指针
*/
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 BSP_OK;
}
/*
* 函数功能:开启写保护(允许写入 00H~71H 寄存器)
* 入口参数:无
* 返回值:BSP_OK - 成功;BSP_ERR_IO / BSP_ERR_TIMEOUT - I2C 失败
* 限定条件:I2C 外设已初始化
* 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1
*/
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 != BSP_OK) return ret;
ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U);
if (ret != BSP_OK) return ret;
return BSP_OK;
}
/*
* 函数功能:关闭写保护(禁止写入 00H~71H 寄存器)
* 入口参数:无
* 返回值:BSP_OK - 成功;BSP_ERR_IO / BSP_ERR_TIMEOUT - I2C 失败
* 限定条件:I2C 外设已初始化
* 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=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 != BSP_OK) return ret;
ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U);
if (ret != BSP_OK) return ret;
return BSP_OK;
}
/* ======================== 公共 API 实现 ======================== */
/*
* 函数功能:初始化 SD2506API-G RTC
* 入口参数:无
* 返回值:0-成功,-1-校验失败,-2-I2C 错误 int
* 限定条件:CubeMX 已完成 I2C1 初始化(PB6-SCL, PB7-SDA)
* 函数说明:1. 读取芯片 ID 验证通信
* 2. 充电寄存器 18H 回读校验(出厂 82H 非易失),值漂移时经写允许
* 窗口重置并回读验证(手册 §4.8:强烈建议每次上电重置 18H)
* 3. 上电即写禁止态(WRTC 全 0),无需再写 0FH
*/
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 != BSP_OK) {
/* 启动段可观测性(2026-10-02 S10):本函数原先所有失败路径均静默返回,
* 而它是启动死机最可疑的源头(I2C1 硬件不通即卡此步且无任何输出)。
* 日志字符串必须纯 ASCII(文件无 BOM,ARMCC 按 CP936 解码)。 */
DBG_ERROR("sd2506_init: ID read FAIL (I2C err=%d)", ret);
return ret;
}
/* 充电寄存器 18H:手册 §4.8"出厂均配置成 82H"(非易失),
* 先回读校验,正常路径(=82H)不再写;仅值漂移(长时掉电等)时
* 经写允许窗口重置并回读验证(手册强烈建议每次上电重置 18H)
* 0x82 = EN_Charge(Bit7) | Charge1(Bit1):开启充电,5K 电阻 */
uint8_t charge_val = 0;
ret = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val);
if (ret != BSP_OK) {
DBG_ERROR("sd2506_init: charge reg read FAIL (err=%d)", ret);
return ret;
}
if (charge_val != SD2506_CHARGE_INIT_VALUE) {
bsp_err_t ret_read = BSP_OK;
ret = sd2506_write_enable();
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 != BSP_OK) {
(void)sd2506_write_disable();
DBG_ERROR("sd2506_init: charge reg write FAIL (err=%d)", ret);
return ret;
}
ret_read = sd2506_read_reg(SD2506_REG_CHARGE, &charge_val);
ret = sd2506_write_disable();
if (ret != BSP_OK) {
DBG_ERROR("sd2506_init: write-disable FAIL (err=%d)", ret);
return ret;
}
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);
/* 回读校验不符 = 通信/介质类故障,不是调用方的参数错(旧码把两者都写成 -1) */
return BSP_ERR_IO;
}
DBG_INFO("sd2506_init: charge reg 18H reset to 0x%02X (was drifted)",
SD2506_CHARGE_INIT_VALUE);
}
DBG_INFO("sd2506_init: OK");
return BSP_OK;
}
/*
* 函数功能:设置 RTC 时间日期
* 入口参数:p_time - 时间结构体指针,包含要设置的时间
* 返回值:0 - 成功,2 - I2C 错误
* 限定条件:sd2506_init() 已调用成功
* 函数说明:1. 先开写保护
* 2. 一次性写入 7 字节时间数据 (00H~06H)
* 3. 关闭写保护
*/
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 BSP_ERR_PARAM;
}
/* 组装 7 字节时间数据 (BCD 码) */
buf[0] = sd2506_dec_to_bcd(p_time->second); /* 00H: 秒 */
buf[1] = sd2506_dec_to_bcd(p_time->minute); /* 01H: 分 */
buf[2] = sd2506_dec_to_bcd(p_time->hour) | 0x80U; /* 02H: 时(bit7=1, 24小时制) */
buf[3] = sd2506_dec_to_bcd(p_time->week); /* 03H: 星期 */
buf[4] = sd2506_dec_to_bcd(p_time->day); /* 04H: 日 */
buf[5] = sd2506_dec_to_bcd(p_time->month); /* 05H: 月 */
buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 06H: 年 */
/* 开启写保护 */
ret = sd2506_write_enable();
if (ret != BSP_OK) return ret;
/* 一次性写入 7 字节时间数据 (00H~06H)
* 手册要求: 不可单独写某一个时间寄存器,否则可能引起错误进入 */
ret = sd2506_write_regs(SD2506_REG_SEC, buf, 7);
if (ret != BSP_OK) return ret;
/* 关闭写保护 */
ret = sd2506_write_disable();
if (ret != BSP_OK) return ret;
return BSP_OK;
}
/*
* 函数功能:读取 RTC 时间日期
* 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误
* note: sd2506_init() must be called first
*/
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 BSP_ERR_PARAM;
}
/* 一次读取 7 字节时间数据 (00H~06H)
* 手册说明: 读取时所有实时数据被锁存,避免错误 */
ret = sd2506_read_regs(SD2506_REG_SEC, buf, 7);
if (ret != BSP_OK) {
return ret;
}
p_time->second = sd2506_bcd_to_dec(buf[0] & 0x7FU); /* 00H: 秒 */
p_time->minute = sd2506_bcd_to_dec(buf[1] & 0x7FU); /* 01H: 分 */
p_time->hour = sd2506_bcd_to_dec(buf[2] & 0x7FU); /* 02H: 屏蔽 bit7 (12/24标志) */
p_time->week = sd2506_bcd_to_dec(buf[3] & 0x07U); /* 03H: 星期 */
p_time->day = sd2506_bcd_to_dec(buf[4] & 0x3FU); /* 04H: 日 */
p_time->month = sd2506_bcd_to_dec(buf[5] & 0x1FU); /* 05H: 月 */
p_time->year = sd2506_bcd_to_dec(buf[6]) + 2000U; /* 06H: 年 */
return BSP_OK;
}
/*
* 函数功能:读取内部温度
* 入口参数:p_temp - 输出温度值指针 int8_t*(非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* 函数说明:读取寄存器 0x16,bit7 为符号位
* return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
* note: reads register 0x16
*/
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 BSP_ERR_PARAM;
}
ret = sd2506_read_reg(SD2506_REG_TEMP, &val);
if (ret != BSP_OK) {
return ret;
}
/* bit7 为符号位, 其余为温度值 */
if (val & 0x80U) {
/* 负温度 取补码 */
*p_temp = (int8_t)(val | 0xF0U);
} else {
/* 正温度 */
*p_temp = (int8_t)(val & 0x7FU);
}
return BSP_OK;
}
/*
* 函数功能:读取电池电压(毫伏)
* 入口参数:p_voltage - 输出电压指针 uint16_t*(非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* 函数说明:组合 9 位值 (bit8=BAT8_VAL, bit7~0=BAT_VL),转换为毫伏
* return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
* note: combines 9-bit value
*/
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 BSP_ERR_PARAM;
}
/* 读取 1AH bit7 (BAT8_VAL) 与 1BH (BAT_VL) */
ret = sd2506_read_reg(SD2506_REG_CTR5, &val_high);
if (ret != BSP_OK) return ret;
ret = sd2506_read_reg(SD2506_REG_BAT_VAL, &val_low);
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 BSP_OK;
}
/*
* 函数功能:读取 8 字节芯片 ID
* 入口参数:p_id - 输出缓冲区指针 uint8_t[8](非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* 函数说明:读取寄存器 0x72~0x79
* return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
* note: reads registers 0x72-0x79
*/
bsp_err_t sd2506_get_id(uint8_t p_id[8]) {
if (p_id == NULL) {
return BSP_ERR_PARAM;
}
return sd2506_read_regs(SD2506_REG_ID_START, p_id, SD2506_ID_SIZE);
}
/*
* 函数功能:读取 SRAM 数据
* 入口参数:addr - 起始地址 uint8_t 0-69
* p_buf - 输出缓冲区指针 uint8_t*(非空)
* len - 读取长度 uint8_t 1-N
* 返回值:0-成功,-1=参数错误,-2=I2C 错误 int
* 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70)
* 函数说明:SRAM 范围 0x2C~0x71
* return: BSP_OK / BSP_ERR_PARAM / BSP_ERR_IO / BSP_ERR_TIMEOUT
* note: SRAM range 0x2C-0x71
*/
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 BSP_ERR_PARAM;
}
if (addr + len > SD2506_SRAM_SIZE) {
return BSP_ERR_PARAM;
}
return sd2506_read_regs(SD2506_REG_SRAM_START + addr, p_buf, len);
}
/*
* 函数功能:写入 SRAM 数据
* 入口参数:addr - 起始地址 uint8_t 0-69
* p_buf - 输入数据指针 uint8_t*(非空)
* len - 写入长度 uint8_t 1-N
* 返回值:0-成功,-1=参数错误,-2=I2C 错误 int
* 限定条件:sd2506_init() 已调用成功;addr+len <= SD2506_SRAM_SIZE(70)
* 函数说明:SRAM 写入需走写保护流程(虽 SRAM 本身无需)
* return: BSP_OK / BSP_ERR_PARAM / BSP_ERR_IO / BSP_ERR_TIMEOUT
*/
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 BSP_ERR_PARAM;
}
if (addr + len > SD2506_SRAM_SIZE) {
return BSP_ERR_PARAM;
}
/* SRAM 无需开写保护即可写入 (写保护仅对 00H~71H 有效, SRAM 为 2CH~71H)
* 但为安全起见, SRAM 写入也走写保护流程 */
bsp_err_t ret = BSP_OK;
ret = sd2506_write_enable();
if (ret != BSP_OK) return ret;
ret = sd2506_write_regs(SD2506_REG_SRAM_START + addr, p_buf, len);
if (ret != BSP_OK) return ret;
ret = sd2506_write_disable();
return ret;
}
/*
* 函数功能:设置闹钟中断
* 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空)
* mask - 报警匹配掩码 uint8_t
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM
* return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
*/
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 BSP_ERR_PARAM;
}
/* 组装 8 字节报警数据 (07H~0EH) */
buf[0] = sd2506_dec_to_bcd(p_time->second) & 0x7FU; /* 07H: 秒报 */
buf[1] = sd2506_dec_to_bcd(p_time->minute) & 0x7FU; /* 08H: 分报 */
buf[2] = sd2506_dec_to_bcd(p_time->hour) & 0x3FU; /* 09H: 时报(最高位始终为0) */
buf[3] = 0x00U; /* 0AH: 星期报警 */
buf[4] = sd2506_dec_to_bcd(p_time->day) & 0x3FU; /* 0BH: 日报 */
buf[5] = sd2506_dec_to_bcd(p_time->month) & 0x1FU; /* 0CH: 月报 */
buf[6] = sd2506_dec_to_bcd(p_time->year - 2000U); /* 0DH: 年报 */
buf[7] = mask; /* 0EH: 报警允许 */
/* 开启写保护 */
ret = sd2506_write_enable();
if (ret != BSP_OK) return ret;
/* 写入报警寄存器 (07H~0EH) */
ret = sd2506_write_regs(SD2506_REG_AL_SEC, buf, 8);
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 != BSP_OK) return ret;
/* 关闭写保护 */
ret = sd2506_write_disable();
if (ret != BSP_OK) return ret;
return BSP_OK;
}
/*
* 函数功能:清除闹钟中断标志
* 入口参数:无
* 返回值: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: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
*/
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 != BSP_OK) {
return ret;
}
ret = sd2506_write_reg(SD2506_REG_CTR1,
(uint8_t)(SD2506_CTR1_WRITE_ON & (uint8_t)~SD2506_CTR1_INTAF));
ret_off = sd2506_write_disable();
if (ret == BSP_OK) {
ret = ret_off;
}
return ret;
}
/*
* 函数功能:读取控制寄存器 1 (CTR1)
* 入口参数:p_val - 输出值指针 uint8_t*(非空)
* 返回值:BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT(可重试) bsp_err_t
* 限定条件:sd2506_init() 已调用成功
* return: BSP_OK / BSP_ERR_IO / BSP_ERR_TIMEOUT (retryable)
*/
bsp_err_t sd2506_read_ctr1(uint8_t *p_val) {
if (p_val == NULL) {
return BSP_ERR_PARAM;
}
return sd2506_read_reg(SD2506_REG_CTR1, p_val);
}