Files
STM32F4-Base/test/board/sd2506_test_task.c
wandering1 f754d59218 docs(测试说明): 按用途分六个子目录并同步全库引用
分层:流程与规范/ 单元测试/ 板级测试/ 专项验证/ 测试工具/ 测试数据/,
顶层只留新增的目录导航 README.md(归层判据 + 硬约束 + 本次处置记录)。

- 搬迁 28 个路径:测试数据三个目录 58 个文件为纯改名(R100,内容逐字节不变)
- 全库引用同步 124 个文件:AGENTS / docs 索引 / skills / 门禁注释 / scripts
- 门禁面:scripts/check_test_traceability.py 的 glob 改 rglob
  (分层后不递归扫描=追溯门禁直接瞎掉)
- scripts/rename_ref.py:收尾的 ℹ 提示在 GBK 代码页抛 UnicodeEncodeError,
  让进程以退出码 1 收场而改写其实已落盘(RC 与真实结果相反);改为把
  stdout/stderr reconfigure 成 UTF-8,与 check_doc_links.py 等同法
- docs/文档规范.md §1.1 补「子目录文件过多时再分一层」;§3.6 补 rename_ref
  搬整目录的两个实测坑:① 会改写目录内部文件的内容 ② 内部文件的 ../ 出链
  不补层级(本次 23 个被误改的证据文件已按 HEAD 原始字节逐字节还原)
- docs/README.md §6 改为与六个子目录一一对应,并补登原先漏登记的
  「启动日志原子性复验流程」一行
- .cubemx_survival_baseline.json:App/util/task_watchdog.c 的注释路径也随分层更新,
  该文件是存活审计的 watchdog-module 漂移锚点,按脚本自己的提示跑 --write 重建基线
  (实测重建后仅 watchdog-module 一条哈希变化)
- 铁律 4 例外(本次在任务树里改了单写者文件):AGENTS.md 与
  .pre-commit-config.yaml 只被 rename_ref 改写路径字面量(后者全在注释行),
  任务表块与钩子声明一字未动

验证:check_doc_links.py --sections-strict 覆盖全部 88 个改动 .md 全绿;
check_test_traceability.py 输出与主树逐行一致;196 个文本文件无 BOM、无新增
CR、无 U+FFFD、无行尾空白;陈旧引用面由主树 25 处降到 6 处(无新增断链)。
2026-10-30 07:16:07 +08:00

275 lines
11 KiB
C
Raw Permalink 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.
/******************************************************************************
* 模块名称:SD2506 RTC Test Suite
* 模块功能:SD2506API-G RTC 驱动功能自测(黑盒,仅用公共 API)。
*
* 历史背景:本驱动曾出现"时间写不进"问题,根因是 sd2506.h 中寄存器宏写成
* "0x0FH"(H 后缀)。KEIL/ARMCC 不识别 H 整数后缀,将其编译为 0,
* 导致 SD2506_REG_CTR1 实际等于 0(秒寄存器),sd2506_write_enable()
* 把 WRTC 写到了错误寄存器,写保护永不解除。修正为 "0x0FU" 后恢复。
* 详见 docs/问题记录/SD2506_Trap_Records.md。
*
* 当前测试:Phase 1 基本读写(init / 读 / 写 / 回读一致 / 走时),
* 结束后自动恢复板子原始时间。
* 入口:sd2506_test_run()(由 start_test_task 经 TEST_SUITE_RTC 调度)
* 创建日期:2026-08-28
******************************************************************************/
#include <string.h>
#include <stdint.h>
#include "sd2506_test.h"
#include "test_config.h"
#include "cmsis_os.h"
#include "sd2506.h"
#define DBG_TAG "[RTC_TEST]"
#include "dbg_log.h"
#ifdef TEST_SUITE_RTC
/* 全局测试统计(宏自动管理) */
sd2506_test_stats_t g_sd2506_test_stats;
/* ===================== Phase 1:基本读写 ===================== */
#ifdef ENABLE_RTC_BASIC_TESTS
static void sd2506_phase_basic(void)
{
DBG_INFO("=== SD2506 Phase 1: basic read/write (public API) ===");
/* TC-RTC-001: init + 读取当前时间 */
bsp_err_t ret = sd2506_init();
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_init() ret=%d (expect 0)", ret);
sd2506_time_t r;
ret = sd2506_get_time(&r);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_time() ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
DBG_INFO(" current RTC: %04u-%02u-%02u %02u:%02u:%02u wk=%u",
(unsigned int)r.year, (unsigned int)r.month, (unsigned int)r.day,
(unsigned int)r.hour, (unsigned int)r.minute,
(unsigned int)r.second, (unsigned int)r.week);
}
/* TC-RTC-002: 写固定时间,立即回读比对(破坏性:改设备 RTC 时间,套件结束会复原)。
* 若 FAIL -> 先查 sd2506.h 寄存器宏后缀是否为 H(曾因 0x0FH 被 KEIL 编译为 0
* 导致 SD2506_REG_CTR1 指向秒寄存器、WRTC 写错地址)。详见 docs/问题记录/SD2506_Trap_Records.md。 */
#ifdef ENABLE_DESTRUCTIVE_TESTS
{
sd2506_time_t t;
int match;
t.year = 2026; t.month = 8; t.day = 28; t.hour = 12; t.minute = 0; t.second = 0; t.week = 4;
ret = sd2506_set_time(&t);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_set_time() ret=%d (expect 0)", ret);
ret = sd2506_get_time(&r);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
DBG_INFO(" set : %04u-%02u-%02u %02u:%02u:%02u",
(unsigned int)t.year, (unsigned int)t.month, (unsigned int)t.day,
(unsigned int)t.hour, (unsigned int)t.minute, (unsigned int)t.second);
DBG_INFO(" readback: %04u-%02u-%02u %02u:%02u:%02u",
(unsigned int)r.year, (unsigned int)r.month, (unsigned int)r.day,
(unsigned int)r.hour, (unsigned int)r.minute, (unsigned int)r.second);
}
match = (r.year == t.year && r.month == t.month && r.day == t.day &&
r.hour == t.hour && r.minute == t.minute &&
(r.second == t.second || r.second == ((t.second + 1U) % 60U)));
SD2506_TEST_CHECK(match, "set==readback (in-session write takes effect)");
}
#else
DBG_INFO("TC-RTC-002 skipped: destructive tests disabled (RTC time write)");
#endif
/* TC-RTC-003: 走时。写后等 3s,应前进约 3 秒 */
if (ret == BSP_OK) {
osDelay(3000);
sd2506_time_t r2;
bsp_err_t ret2 = sd2506_get_time(&r2);
if (ret2 == BSP_OK) {
int adv = (r2.second + 60 * (r2.minute + 60 * (r2.hour + 24 * r2.day)))
- (r.second + 60 * (r.minute + 60 * (r.hour + 24 * r.day)));
if (adv < 0) adv += 60 * 60 * 24 * 32;
DBG_INFO(" 3s later: %04u-%02u-%02u %02u:%02u:%02u (advanced %d s)",
(unsigned int)r2.year, (unsigned int)r2.month, (unsigned int)r2.day,
(unsigned int)r2.hour, (unsigned int)r2.minute, (unsigned int)r2.second, adv);
SD2506_TEST_CHECK(adv >= 2 && adv <= 5, "RTC ticking forward (~3s)");
}
}
SD2506_TEST_REPORT("Phase 1 basic");
}
#endif /* ENABLE_RTC_BASIC_TESTS */
/* ===================== Phase 2:全功能 API 覆盖 ===================== */
#ifdef ENABLE_RTC_API_TESTS
static void sd2506_phase_api(void)
{
DBG_INFO("=== SD2506 Phase 2: full API coverage ===");
/* TC-RTC-010: BCD<->DEC 互转(纯算法,确定性,无需硬件交互) */
int conv_ok = 1;
uint8_t samples[][2] = {
{0, 0x00U}, {1, 0x01U}, {9, 0x09U}, {10, 0x10U}, {23, 0x23U},
{59, 0x59U}, {60, 0x60U}, {99, 0x99U}, {42, 0x42U}, {88, 0x88U}
};
for (uint32_t i = 0U; i < (sizeof(samples) / sizeof(samples[0])); i++) {
uint8_t dec = samples[i][0];
uint8_t bcd = samples[i][1];
if (sd2506_dec_to_bcd(dec) != bcd) {
conv_ok = 0;
DBG_ERROR(" dec_to_bcd(%u) != 0x%02X", (unsigned int)dec, (unsigned int)bcd);
}
if (sd2506_bcd_to_dec(bcd) != dec) {
conv_ok = 0;
DBG_ERROR(" bcd_to_dec(0x%02X) != %u", (unsigned int)bcd, (unsigned int)dec);
}
}
SD2506_TEST_CHECK(conv_ok, "bcd<->dec round-trip (10 samples)");
/* TC-RTC-011: 内部温度读取 */
int8_t temp = 0;
bsp_err_t ret = sd2506_get_temperature(&temp);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_temperature() ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
DBG_INFO(" temperature: %d C", (int)temp);
SD2506_TEST_CHECK(temp >= -40 && temp <= 85,
"temperature in range [-40,85]: %d", (int)temp);
}
/* TC-RTC-012: 电池电压(毫伏) */
uint16_t volt = 0U;
ret = sd2506_get_battery_voltage(&volt);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_battery_voltage() ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
DBG_INFO(" battery: %u mV", (unsigned int)volt);
SD2506_TEST_CHECK(volt > 2000U && volt < 5000U,
"battery voltage plausible: %u mV", (unsigned int)volt);
}
/* TC-RTC-013: 芯片 8 字节 ID */
uint8_t id[8] = {0};
ret = sd2506_get_id(id);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_get_id() ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
DBG_INFO(" id: %02X%02X%02X%02X%02X%02X%02X%02X",
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]);
}
/* TC-RTC-014: 用户 SRAM 读写回环(addr=0, len=8;破坏性:写芯片 SRAM) */
#ifdef ENABLE_DESTRUCTIVE_TESTS
{
uint8_t wbuf[8] = {0xA1U, 0xB2U, 0xC3U, 0xD4U, 0xE5U, 0xF6U, 0x07U, 0x18U};
uint8_t rbuf[8] = {0};
ret = sd2506_write_sram(0U, wbuf, 8U);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_write_sram() ret=%d (expect 0)", ret);
if (ret == BSP_OK) {
ret = sd2506_read_sram(0U, rbuf, 8U);
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_read_sram() ret=%d (expect 0)", ret);
int sram_ok = (memcmp(wbuf, rbuf, 8U) == 0);
SD2506_TEST_CHECK(sram_ok, "SRAM write==readback");
}
}
#else
DBG_INFO("TC-RTC-014 skipped: destructive tests disabled (RTC SRAM write)");
#endif
/* TC-RTC-015: 报警设置 + 清除(破坏性:写报警寄存器;验证 API 通路,不影响时间寄存器) */
#ifdef ENABLE_DESTRUCTIVE_TESTS
{
sd2506_time_t at;
at.year = 2026; at.month = 8; at.day = 28; at.hour = 12; at.minute = 0; at.second = 30; at.week = 4;
ret = sd2506_set_alarm(&at, (uint8_t)(SD2506_AL_EN_EAS | SD2506_AL_EN_EAMN));
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret);
ret = sd2506_clear_alarm();
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_clear_alarm() ret=%d (expect 0)", ret);
}
#else
DBG_INFO("TC-RTC-015 skipped: destructive tests disabled (RTC alarm write)");
#endif
SD2506_TEST_REPORT("Phase 2 API");
}
#endif /* ENABLE_RTC_API_TESTS */
/* ===================== 套件入口 ===================== */
void sd2506_test_run(void)
{
DBG_INFO("SD2506 test suite started");
#ifdef ENABLE_DESTRUCTIVE_TESTS
DBG_INFO("mode: DESTRUCTIVE (RTC time / SRAM / alarm writes enabled)");
#else
DBG_INFO("mode: READ-ONLY (destructive cases skipped; set "
"ENABLE_DESTRUCTIVE_TESTS in test_config.h to run them)");
#endif
bsp_err_t ret = sd2506_init();
SD2506_TEST_CHECK(ret == BSP_OK, "sd2506_init() ret=%d (expect 0)", ret);
/* 信息:读取 CTR1 默认状态(确认 WRTC 初始/可写能力,纯诊断) */
uint8_t c1 = 0x00U;
if (sd2506_read_ctr1(&c1) == BSP_OK) {
DBG_INFO(" CTR1=0x%02X (WRTC2=CTR1.2=%u WRTC3=CTR1.7=%u)",
(unsigned int)c1,
(unsigned int)((c1 >> 2) & 1U),
(unsigned int)((c1 >> 7) & 1U));
}
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 记录原始时间,测试结束恢复,避免把板子时间留在测试值 */
sd2506_time_t orig;
int have_orig = (sd2506_get_time(&orig) == BSP_OK);
if (!have_orig) {
DBG_INFO(" WARN: cannot read original time; restore step will be skipped");
}
#endif
/* 套件级累计:每个 Phase 进入前自清零,阶段结束后把本阶段结果累加进来。
* 目的有二:
* 1) 各 Phase 的 `=== Phase N ... ===` 汇总行只反映"本阶段"的断言数,
* 不再出现"Phase 2 显示的是含 Phase 1 的累计值"这种易误读输出;
* 2) 末尾输出套件级 `=== RTC Tests: N/M PASSED (failed=0) ===` 汇总行——
* 它是 run_all_suites.py --require-suite-summary 的判定标记
* (见 test_common.h / docs/测试说明/流程与规范/测试开发标准流程.md §3.2)。 */
uint16_t suite_total = 0U;
uint16_t suite_passed = 0U;
uint16_t suite_failed = 0U;
#ifdef ENABLE_RTC_BASIC_TESTS
memset(&g_sd2506_test_stats, 0, sizeof(g_sd2506_test_stats));
sd2506_phase_basic();
suite_total += g_sd2506_test_stats.total;
suite_passed += g_sd2506_test_stats.passed;
suite_failed += g_sd2506_test_stats.failed;
#endif
#ifdef ENABLE_RTC_API_TESTS
memset(&g_sd2506_test_stats, 0, sizeof(g_sd2506_test_stats));
sd2506_phase_api();
suite_total += g_sd2506_test_stats.total;
suite_passed += g_sd2506_test_stats.passed;
suite_failed += g_sd2506_test_stats.failed;
#endif
#ifdef ENABLE_DESTRUCTIVE_TESTS
/* 恢复原始时间(用公共 API,写保护逻辑已由 sd2506.c 正确处理) */
if (have_orig) {
int r = sd2506_set_time(&orig);
DBG_INFO(" restored original time (ret=%d)", r);
}
#endif
/* 套件级汇总行:名称固定 "RTC Tests",与 run_all_suites.py 的
* --require-suite-summary 约定一致。复用公共报告宏,保证格式统一。 */
g_sd2506_test_stats.total = suite_total;
g_sd2506_test_stats.passed = suite_passed;
g_sd2506_test_stats.failed = suite_failed;
SD2506_TEST_REPORT("RTC Tests");
DBG_INFO("SD2506 test suite done");
for (;;) {
osDelay(1000);
}
}
#endif /* TEST_SUITE_RTC */