029437be89
步骤 3/4(IWDG V2/V4/V5 + 堆低水位 S11):
- V2 任务饿死 stall 后 ≤2.5s IWDG 复位;V4 关中断死循环 ~4.9s 稳定复位;
V5 30min 120×FTP 全 PASS、零 IWDG 复位。堆告警全程未触发(min_ever=4728B)。
- task_watchdog.c:stall 证据写盘会经 nand_ftl_wd_feed 喂狗 → 增 s_stall_evidenced
每次 stall 仅取一次证据,避免"取证据"本身阻止复位。
- default_task.c:堆低水位告警(HEAP_LOW_WATER_B=3072)。
步骤 5(GD5F 错误通道 S9):gd5f2gq5ue.c/.h + nand_ftl.c 错误码统一为 int32_t 并
全量检查返回值;套件回归 16/16 PASSED。
- test/run_all_suites.py:--forbidden 改为按套件覆盖(GD5F 用 "HardFault;assert")——
TC-GD5F-704 故意触发 [ERR] 回落轮询,统一默认值会误判 FAIL。
步骤 6(网络稳定性):7.1 正常路径 ✅(V5 证据);7.2 N-2 丢弃可观测性代码已具备、
49s 静默在 V5 未复现(降级低风险观察项);7.3 N-3 待 PC peer。
步骤 7(工程卫生):
- S5:新增 test-cppcheck 钩子(仅 warning,排除模拟器桩)。
- S4:新增 scripts/check_test_traceability.py(默认仅告警)。
- 待核实项 TEST_SUITE_SELECTED 实机核实 = 死代码(无使用点);RTC 套件实际正常执行,
真缺口 = 缺套件级汇总行 → sd2506_test_task.c 补 === RTC Tests: N/M PASSED === 并
修正 Phase 级汇总累计问题;test_config.h 补齐 RTC 并注释"当前无引用"。
验证:产品 @build 0/0;pre-commit --all-files 17/17 Passed;
run_all_suites.py --suites RTC → PASS。
文档:实机测试实施指南 / SD2506_Test_Guide / 待处理内容 / 测试与架构改进建议 同步。
275 lines
12 KiB
C
275 lines
12 KiB
C
/******************************************************************************
|
||
* 模块名称: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 + 读取当前时间 */
|
||
int ret = sd2506_init();
|
||
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_init() ret=%d (expect 0)", ret);
|
||
|
||
sd2506_time_t r;
|
||
ret = sd2506_get_time(&r);
|
||
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_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 == SD2506_OK, "sd2506_set_time() ret=%d (expect 0)", ret);
|
||
|
||
ret = sd2506_get_time(&r);
|
||
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_time() after set ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_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 == SD2506_OK) {
|
||
osDelay(3000);
|
||
sd2506_time_t r2;
|
||
int ret2 = sd2506_get_time(&r2);
|
||
if (ret2 == SD2506_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;
|
||
int ret = sd2506_get_temperature(&temp);
|
||
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_get_temperature() ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_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 == SD2506_OK, "sd2506_get_battery_voltage() ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_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 == SD2506_OK, "sd2506_get_id() ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_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 == SD2506_OK, "sd2506_write_sram() ret=%d (expect 0)", ret);
|
||
if (ret == SD2506_OK) {
|
||
ret = sd2506_read_sram(0U, rbuf, 8U);
|
||
SD2506_TEST_CHECK(ret == SD2506_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 == SD2506_OK, "sd2506_set_alarm() ret=%d (expect 0)", ret);
|
||
ret = sd2506_clear_alarm();
|
||
SD2506_TEST_CHECK(ret == SD2506_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
|
||
|
||
int ret = sd2506_init();
|
||
SD2506_TEST_CHECK(ret == SD2506_OK, "sd2506_init() ret=%d (expect 0)", ret);
|
||
|
||
/* 信息:读取 CTR1 默认状态(确认 WRTC 初始/可写能力,纯诊断) */
|
||
uint8_t c1 = 0x00U;
|
||
if (sd2506_read_ctr1(&c1) == SD2506_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) == SD2506_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 */
|