时钟测试完毕

This commit is contained in:
2026-08-28 21:59:04 +08:00
parent 02a05a7a63
commit 565f23ea18
6 changed files with 259 additions and 324 deletions

View File

@@ -1,252 +1,105 @@
# SD2506 RTC 测试规范
# SD2506_RTC 测试指南
> 对应驱动:`Drivers/BSP/SD2506/sd2506.c`
> 测试代码:`test/sd2506_test_task.c` + `test/sd2506_test.h`
## 1. 概述
文档定义 SD2506API-G 高精度温补实时时钟驱动STM32F407ZGTxI2C1 接口 PB6-SCL / PB7-SDA的测试规范。RTC 为本地外设,测试**无需 PC 配合**仅串口日志观察覆盖时间读写、写保护WRTC解锁时序、跨重启持久化三大主题。测试按阶段`TM-RTC-NN`)组织,每阶段含独立用例(`TC-RTC-NNN`
测试套件对 `sd2506.c` 做**黑盒功能自测**,只调用公共 API不触碰驱动内部函数
每个用例通过 `SD2506_TEST_CHECK` 记录 pass/fail最终由 `SD2506_TEST_REPORT` 汇总。
> **背景**:本驱动曾出现"时间同步后写入不生效、重启回到旧时间"的缺陷。根因是 `sd2506_write_enable()` 的 WRTC 解锁时序与 SD2506API-G 实际位布局不符——写操作 I2C 返回 OK但芯片因写保护未真正打开而**静默忽略**数据。本测试 Phase 2 用穷举探测法定位正确时序。
历史背景:本驱动曾出现时间写不进”问题,根因是寄存器宏写成 `0x0FH`KEIL/ARMCC
不识别 `H` 整数后缀,被编译为 0导致 `SD2506_REG_CTR1` 实际等于 0秒寄存器
`sd2506_write_enable()` 把 WRTC 写错地址、写保护永不解除。修正为 `0x0FU` 后恢复。
详见 `docs/SD2506_Trap_Records.md`
### 设计原则
## 2. 启用与编译
| 原则 | 描述 | 验证用例 |
|------|------|----------|
| DP-01 | 写保护时序正确:写时间前必须用正确的 WRTC 解锁时序打开 CTR1/CTR2 写保护否则写被芯片静默忽略I2C 仍返回 OK | TC-RTC-002、TC-RTC-101、TC-RTC-201 |
| DP-02 | 时间须一次性 7 字节写入00H~06H不可单独写某一时间寄存器 | TC-RTC-002 |
| DP-03 | 跨重启持久化:写入后由纽扣电池/充电维持,重启后读回一致 | TC-RTC-201 |
| DP-04 | 24 小时制 + 自动复位ARST配置正确时间寄存器可读 | TC-RTC-001 |
`test/test_config.h` 中一次只能启用一个 `TEST_SUITE_*`(互斥):
### SD2506 关键参数
| 参数 | 值 | 说明 |
|------|-----|------|
| I2C 器件地址7 位) | `0x32` | HAL 用 8 位:写 `0x64`、读 `0x65``SD2506_I2C_ADDR_WRITE/READ` |
| 接口 | I2C1PB6-SCL / PB7-SDA | CubeMX 已完成初始化 |
| 时间寄存器 | `00H` SEC ~ `06H` YEARBCD | 一次性连续读写 7 字节 |
| 控制寄存器 | `0FH` CTR1、`10H` CTR2 | WRTC写 RTC 保护)位在此 |
| 24 小时制 | HOUR 寄存器 bit7 = `1` | `sd2506_set_time``hour \| 0x80` |
| YEAR 存储 | 后两位 BCD`year % 100` | `get_time` 回读 `+2000` 还原 |
| 写保护宏(头文件注释) | `SD2506_CTR1_WRITE_ON = 0xFF``SD2506_CTR1_WRITE_OFF = 0x7B` | `sd2506.h:124-126` 记载的"使能/禁止"建议值 |
> **关键约束**SD2506 的 WRTC 位布局在不同参考设计/批次间存在差异。本驱动原实现(`sd2506_write_enable()` @ `sd2506.c:121`)采用 `CTR2=0x80, CTR1=0x84`(源自 SD2403 参考),但实测未生效;头文件宏则指向 `CTR1=0xFF`。**正确时序须由 Phase 2 探测确认,不得臆测**。
### 测试环境
```
MCU: STM32F407ZGTx @ 168MHz, Keil MDK-ARM v5 (ARMCC)
外设: SD2506API-G (I2C1, PB6/PB7),纽扣电池或充电供电
工具: 串口终端(观察 DBG_INFO 日志),无 PC 网络端
```c
//#define TEST_SUITE_CH395F
//#define TEST_SUITE_STORAGE
//#define TEST_SUITE_GD5F
#define TEST_SUITE_RTC /* 启用 RTC 测试套件 */
```
### 构建模式与执行顺序
`test/sd2506_test.h` 中控制各阶段(默认全开):
测试固件复用既有 `ch395fTestTask``StartCh395fTestTask`调度框架SD2506 作为独立 `TEST_SUITE_RTC` 套件接入,不新增 FreeRTOS 任务三个相位顺序执行Phase 1 → 2 → 3。各相位由 `test/sd2506_test.h``ENABLE_RTC_*` 宏独立开关,套件整体由 `test/test_config.h``TEST_SUITE_RTC` 选择。
```c
#define ENABLE_RTC_BASIC_TESTS /* Phase 1: 基本读写 / 走时 */
#define ENABLE_RTC_API_TESTS /* Phase 2: 全功能 API 覆盖 */
```
| 构建宏 | 相位 | 说明 |
|--------|------|------|
| `ENABLE_RTC_BASIC_TESTS` | Phase 1 | 公共 API 基本读写 / 走时 |
| `ENABLE_RTC_WRTC_PROBE_TESTS` | Phase 2 | WRTC 解锁时序穷举探测 |
| `ENABLE_RTC_PERSIST_TESTS` | Phase 3 | 跨重启持久化(需人工重启核验) |
编译(要求 0 错误 0 警告):
**执行顺序严格递增**:先确认 Phase 1若 FAIL 即证明当前驱动写无效),再 Phase 2定位正确 WRTC 时序),最后 Phase 3用命中时序验证持久化
```bat
MDK-ARM\build.bat
```
### 启用测试
烧录后在串口USART1, 115200bps观察 `[RTC_TEST]` 日志。
本测试复用既有 `ch395fTestTask``StartCh395fTestTask`)调度框架,作为独立 `TEST_SUITE_RTC` 套件接入,不新增 FreeRTOS 任务。
## 3. 测试用例
1. **Keil 工程**:把 `test/sd2506_test_task.c` 加入 `test/` 组(与 `ch395f_test_task.c` 等同级);`Options → C/C++ → Include Paths` 确认已含 `..\test`(其它测试套件已配置,无需重复添加)。
2. **选择套件**:编辑 `test/test_config.h`,注释掉其它 `TEST_SUITE_*`,取消注释 `#define TEST_SUITE_RTC`。一次构建仅能启用一个套件(互斥 `#error` 保护)。
3. **阶段开关**:编辑 `test/sd2506_test.h`,按需取消注释 `ENABLE_RTC_BASIC_TESTS` / `ENABLE_RTC_WRTC_PROBE_TESTS` / `ENABLE_RTC_PERSIST_TESTS`(默认全开)。
4. `@build` 验证 `0 Error(s), 0 Warning(s)` 后烧录,板子启动后 `StartCh395fTestTask` 自动进入 RTC 套件并顺序执行 Phase 1→2→3。
| 编号 | 目标 | 覆盖函数 | 判定 |
|------|------|----------|------|
| TC-RTC-001 | 初始化并读取当前时间 | `sd2506_init` / `sd2506_get_time` | init 返回 0get 返回 0 |
| TC-RTC-002 | 写固定时间立即回读一致 | `sd2506_set_time` / `sd2506_get_time` | set==readback允许 +1s 进位) |
| TC-RTC-003 | 走时验证 | `sd2506_get_time` | 延时 3s 后秒数前进 2~5s |
| TC-RTC-010 | BCD↔DEC 互转 | `sd2506_dec_to_bcd` / `sd2506_bcd_to_dec` | 10 组样本双向一致 |
| TC-RTC-011 | 内部温度读取 | `sd2506_get_temperature` | 返回 0值 ∈ [-40, 85] |
| TC-RTC-012 | 电池电压mV | `sd2506_get_battery_voltage` | 返回 0值 ∈ (2000, 5000) |
| TC-RTC-013 | 芯片 8 字节 ID | `sd2506_get_id` | 返回 0读出 8 字节 |
| TC-RTC-014 | 用户 SRAM 回环 | `sd2506_write_sram` / `sd2506_read_sram` | 写入 8 字节后回读一致 |
| TC-RTC-015 | 报警设置 + 清除 | `sd2506_set_alarm` / `sd2506_clear_alarm` | 两者均返回 0 |
---
## 4. 覆盖矩阵
## 2. 阶段 1基本读写公共 API
| 公共 API | 是否覆盖 | 用例 |
|----------|----------|------|
| `sd2506_init` | ✅ | TC-RTC-001 |
| `sd2506_get_time` | ✅ | TC-RTC-001/002/003 |
| `sd2506_set_time` | ✅ | TC-RTC-002 |
| `sd2506_get_temperature` | ✅ | TC-RTC-011 |
| `sd2506_get_battery_voltage` | ✅ | TC-RTC-012 |
| `sd2506_get_id` | ✅ | TC-RTC-013 |
| `sd2506_read_sram` | ✅ | TC-RTC-014 |
| `sd2506_write_sram` | ✅ | TC-RTC-014 |
| `sd2506_set_alarm` | ✅ | TC-RTC-015 |
| `sd2506_clear_alarm` | ✅ | TC-RTC-015 |
| `sd2506_read_ctr1` | △(诊断) | run() 内打印 CTR1 |
| `sd2506_bcd_to_dec` / `sd2506_dec_to_bcd` | ✅ | TC-RTC-010 |
| 阶段 ID | TM-RTC-01 |
|---------|-----------|
| **类型** | 独立运行 |
| **耗时** | ~5 秒 |
| **入口** | `StartCh395fTestTask → TEST_SUITE_RTC → sd2506_test_run()``sd2506_phase_basic()` |
| **出口** | 串口 `Phase 1 basic: N/N PASSED (failed=0)` |
> 未覆盖(需专项验证,不在本自测范围):倒计时寄存器、温度报警历史、
> 跨重启持久化(原 Phase 3 已废弃,依赖实际掉电/上电,建议手动或另写用例)。
### TC-RTC-001: 初始化与读取
## 5. 运行与判定
| 字段 | 值 |
|------|-----|
| **ID** | TC-RTC-001 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 验证 `sd2506_init()` 通信正常并可读取当前时间 |
| **前置条件** | 1. I2C1 已初始化<br>2. 未启用其他会并发访问 I2C 的测试 |
- `sd2506_test_run()` 由测试调度器调用,先 init 并记录原始时间,最后用
`sd2506_set_time` 把板子时间**恢复**为测试前的值(不污染真实时钟)。
- 输出形如:
```
[RTC_TEST] === SD2506 Phase 1: basic read/write (public API) ===
[RTC_TEST] [PASS] sd2506_init() ret=0 (expect 0)
...
[RTC_TEST] === Phase 1 basic: 6/6 PASSED (failed=0) ===
[RTC_TEST] === SD2506 Phase 2: full API coverage ===
...
[RTC_TEST] === Phase 2 API: 9/9 PASSED (failed=0) ===
```
- 通过标准:所有 Phase 的 `failed=0`,且总量 `X/Y PASSED` 中 Y 与 PASS 数相等。
**测试步骤与判定标准**
## 6. 失败排查
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `sd2506_init()` | 返回 `0``SD2506_OK` | 返回 `-2``SD2506_I2C_ERROR`I2C 通信失败/芯片未响应) |
| 2 | `sd2506_get_time(&r)` 读取当前时间 | 返回 `0``r.year` 落在 `2000~2099`、月/日/时/分/秒为合法 BCD 范围 | 返回非零,或字段越界(如月>12、秒>59 |
- **TC-RTC-002 FAILset≠readback**:优先查 `sd2506.h` 中各寄存器宏是否被写成
`0xNNH`H 后缀陷阱)。例如 `SD2506_REG_CTR1` 必须是 `0x0FU` 而非 `0x0FH`。
- **init/get/set 返回 -2I2C 错误)**:查 I2C1 接线PB6-SCL / PB7-SDA
上拉电阻、芯片供电与器件地址 `0x32`。
- **温度/电压越界**:多为芯片未上电或 I2C 读到全 0xFF按 I2C 链路排查。
**通过标准**2/2 通过0 失败。任一失败 → 阶段标记 FAIL
## 7. 已知限制
**覆盖原则**DP-04
---
### TC-RTC-002: 写入后回读比对(核心判定)
| 字段 | 值 |
|------|-----|
| **ID** | TC-RTC-002 |
| **优先级** | P0 |
| **类型** | 功能测试 |
| **标题** | 写固定时间 `2026-08-28 12:00:00`,立即回读并比对 |
| **前置条件** | TC-RTC-001 通过 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | `sd2506_set_time(&t)`t = 2026-08-28 12:00:00 | 返回 `0` | 返回非零I2C 错误) |
| 2 | `sd2506_get_time(&r)` 立即回读 | 返回 `0``r``t` 的 年月日时分 完全一致、`秒` 允许 `±1`(回读期间走 1 秒) | 回读字段不符(尤其时分秒) |
**通过标准**:回读与写入一致(秒允许 +1。若 FAIL → **证明当前 `sd2506_write_enable()` WRTC 时序无效,写被芯片静默忽略**,直接进入 Phase 2 定位。
**覆盖原则**DP-01、DP-02
---
### TC-RTC-003: 走时验证
| 字段 | 值 |
|------|-----|
| **ID** | TC-RTC-003 |
| **优先级** | P1 |
| **类型** | 功能测试 |
| **标题** | 写后等待 3 秒,确认时间向前推进约 3 秒 |
| **前置条件** | TC-RTC-002 通过(写已生效) |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 记录 `set` 后回读时间,调用 `osDelay(3000)` 后再 `sd2506_get_time` | 时间前进 `2~5` 秒(含测试开销) | 时间不前进 / 回退(振荡器或配置异常) |
**通过标准**:时间单调前进且增量合理。任一异常 → 阶段标记 FAIL
**覆盖原则**DP-04
---
## 3. 阶段 2WRTC 解锁时序探测
| 阶段 ID | TM-RTC-02 |
|---------|-----------|
| **类型** | 独立运行 |
| **耗时** | ~3 秒 |
| **入口** | `StartCh395fTestTask → TEST_SUITE_RTC → sd2506_test_run()``sd2506_phase_wrtc_probe()` |
| **出口** | 串口 `Phase 2 WRTC probe: N/N PASSED (failed=0)``BEST sequence: ...` |
**分层说明**:本阶段为**黑盒 + 原始 I2C 诊断**,不依赖 `sd2506_write_enable()` 内部实现。直接以 `HAL_I2C_Mem_Write` 按不同 CTR1/CTR2 组合解锁→写 7 字节时间→立即回读,哪组能让写生效即为正确时序。每组写一组**互不相同的哨兵时间**,避免误判。
> 探测结论用于修正 `sd2506.c` 的 `sd2506_write_enable()`@ `sd2506.c:121`)与 `sd2506_write_disable()`@ `sd2506.c:142`)。
### TC-RTC-101: WRTC 候选序列穷举
| 字段 | 值 |
|------|-----|
| **ID** | TC-RTC-101 |
| **优先级** | P0 |
| **类型** | 功能测试 / 诊断 |
| **标题** | 穷举 8 组 CTR1/CTR2 解锁序列,找出令时间写生效的那组 |
| **前置条件** | 1. I2C1 正常<br>2. 无其它任务并发写 RTC |
**候选序列表**(索引 i每组写哨兵 `2020+i` 年避免互相干扰):
| idx | CTR1 | CTR2 | 顺序 | 说明 |
|-----|------|------|------|------|
| 0 | `0xFF` | `0x00` | CTR1→CTR2 | 仅 CTR1依照 `sd2506.h``SD2506_CTR1_WRITE_ON`)— **最可能正确** |
| 1 | `0x84` | `0x80` | CTR2→CTR1 | 当前驱动 `sd2506_write_enable` 实现SD2403 参考,待测) |
| 2 | `0x84` | `0x80` | CTR1→CTR2 | 同上,调换写顺序 |
| 3 | `0xFF` | `0x80` | CTR2→CTR1 | CTR1=0xFF 叠加 CTR2=0x80 |
| 4 | `0x00` | `0x80` | CTR2→CTR1 | 仅 CTR2WRTC1 |
| 5 | `0x80` | `0x00` | CTR1→CTR2 | 仅 CTR1 bit7WRTC1 |
| 6 | `0x70` | `0x00` | CTR1→CTR2 | CTR1 bit6/5/4 |
| 7 | `0x86` | `0x00` | CTR1→CTR2 | CTR1 bit7+bit2WRTC3+WRTC2 |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 对每组候选:按指定顺序写 CTR1/CTR2 → 写 7 字节哨兵时间 → 立即回读 | 回读与哨兵一致(秒允许 +1 | 回读不符(写被忽略,该组无效) |
| 2 | 汇总 8 组结果,记录首个 PASS 的组为 `BEST` | 至少 1 组 PASS串口打印 `BEST sequence: CTR1=.. CTR2=.. ctr1_first=..` | 0 组 PASS |
**通过标准**:至少 1 组 PASS且打印出 `BEST sequence`。若 0 组 PASS → WRTC 不是根因,转查电池/充电/其它写入路径。
**覆盖原则**DP-01
---
## 4. 阶段 3跨重启持久化
| 阶段 ID | TM-RTC-03 |
|---------|-----------|
| **类型** | 需物理操作(重启板卡) |
| **耗时** | ~10 秒 + 人工重启 |
| **入口** | `StartCh395fTestTask → TEST_SUITE_RTC → sd2506_test_run()``sd2506_phase_persist()` |
| **出口** | 串口 `Phase 3 persist: N/N PASSED (failed=0)` |
**前提**Phase 2 必须已命中 `BEST sequence`(即 `s_best_found == 1`)。若未命中,本阶段跳过并打印指引。
### TC-RTC-201: 写哨兵时间 + 重启核验
| 字段 | 值 |
|------|-----|
| **ID** | TC-RTC-201 |
| **优先级** | P0 |
| **类型** | 恢复测试 / 持久化 |
| **标题** | 用命中时序写哨兵 `2026-09-09 09:09:09`,重启后读回应一致 |
| **前置条件** | TC-RTC-101 已命中 `BEST sequence` |
**测试步骤与判定标准**
| 步 | 操作 | 成功标准 | 失败标准 |
|----|------|----------|----------|
| 1 | 用 `BEST` 序列写哨兵时间 `2026-09-09 09:09:09` | 写返回 OK 且立即回读一致 | 写失败 / 回读不符(`BEST` 序列存疑) |
| 2 | 串口提示 `USER ACTION: reboot board, check boot log 'SD2506 time:' == 2026-09-09 09:09:09` | 用户重启后,板子开机日志 `SD2506 time:` 行显示 `2026-09-09 09:09:09` | 重启后读回为旧时间(电池/充电路径异常,或 `BEST` 序列仍不正确) |
**通过标准**:重启后开机日志时间 == 哨兵值。若不符 → 持久化链路(电池/充电/`sd2506_init` 上电逻辑)存在缺陷,需进一步排查。
**覆盖原则**DP-03
> 测试任务结束前会自动用 `BEST` 序列恢复测试开始时的原始时间,避免板子停留在哨兵值。
---
## 5. 已知陷阱
| 陷阱 | 现象 | 修复 |
|------|------|------|
| WRTC 解锁时序错误 | `sd2506_set_time` 返回 OK但时间不变化、重启回到旧值 | 以 Phase 2 `BEST sequence` 重写 `sd2506_write_enable()`/`sd2506_write_disable()` |
| 误以为 I2C 返回 OK 即写入成功 | 写保护未开时芯片静默忽略,`HAL_I2C_Mem_Write` 仍返回 `HAL_OK` | 必须**写后回读比对**确认TC-RTC-002 |
| YEAR 双偏移 | `unix_to_sd2506``sd2506_set_time` 各减一次 2000读出年份变成 20xx→溢出为 20xx+2000 | `set_time``year%100``get_time``+2000`;上层只传完整年 |
---
## 附录 A通过/失败汇总矩阵
| 阶段 | TC ID | 优先级 | 类型 | 状态 |
|------|-------|--------|------|------|
| TM-RTC-01 | TC-RTC-001 | P0 | 功能测试 | 待测 |
| TM-RTC-01 | TC-RTC-002 | P0 | 功能测试 | 待测 |
| TM-RTC-01 | TC-RTC-003 | P1 | 功能测试 | 待测 |
| TM-RTC-02 | TC-RTC-101 | P0 | 诊断 | 待测 |
| TM-RTC-03 | TC-RTC-201 | P0 | 恢复测试 | 待测 |
## 附录 B测试覆盖 vs 设计原则
| 原则 | 覆盖用例 |
|------|----------|
| DP-01 写保护时序正确 | TC-RTC-002、TC-RTC-101、TC-RTC-201 |
| DP-02 一次性 7 字节写入 | TC-RTC-002 |
| DP-03 跨重启持久化 | TC-RTC-201 |
| DP-04 24H + ARST 配置 | TC-RTC-001、TC-RTC-003 |
- TC-RTC-015 会让 `sd2506_set_alarm` 置 `CTR2.INTAE=1`(报警中断允许)。
测试固件未挂 INT 中断处理,且 INT 引脚在板上未连接,对运行无影响;
若要完全复位,可手动再调用一次 `set_alarm(..., mask=0)`。
- SRAM 回环会在 `addr=0` 写入测试数据8 字节),属用户 SRAM 区域,不影响时间/控制寄存器。