From 02a05a7a633a97f917f06ad8ee390f1c323d2fbe Mon Sep 17 00:00:00 2001 From: wandering1 <1624155937@qq.com> Date: Fri, 28 Aug 2026 21:10:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=B6=E9=97=B4=E7=BB=88=E4=BA=8E=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=EF=BC=8C=E5=AD=98=E5=82=A8=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/sys_clock.c | 72 +++++++--- Drivers/BSP/SD2506/sd2506.c | 15 +- Drivers/BSP/SD2506/sd2506.h | 2 +- MDK-ARM/STM32F407-Demo.uvprojx | 5 + MDK-ARM/build_log.txt | 91 ++++++------ docs/SD2506_Test_Guide.md | 252 +++++++++++++++++++++++++++++++++ docs/SD2506_Trap_Records.md | 41 ++++++ docs/芯片手册/SD2506API-G.md | 2 +- test/ch395f_test_task.c | 3 + test/sd2506_test.h | 48 +++++++ test/sd2506_test_task.c | 137 ++++++++++++++++++ test/test_config.h | 8 +- test/time_sync_server.py | 8 +- 13 files changed, 606 insertions(+), 78 deletions(-) create mode 100644 docs/SD2506_Test_Guide.md create mode 100644 docs/SD2506_Trap_Records.md create mode 100644 test/sd2506_test.h create mode 100644 test/sd2506_test_task.c diff --git a/App/sys_clock.c b/App/sys_clock.c index 2389f6e..00a5545 100644 --- a/App/sys_clock.c +++ b/App/sys_clock.c @@ -25,7 +25,7 @@ * 修改日期/时间时同步更新下列宏。 * ============================================================ */ #define RTC_DEFAULT_INIT_ENABLE 1 -#define RTC_DEFAULT_INIT_FORCE 1 +#define RTC_DEFAULT_INIT_FORCE 0 #define RTC_DEFAULT_YEAR 2026 #define RTC_DEFAULT_MONTH 8 #define RTC_DEFAULT_DAY 24 @@ -134,10 +134,10 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) (uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) + (uint32_t)(d % 100uLL)) % 7uLL; - /* SD2506 year 字段为 0~99(+2000) */ - uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000); - - p_sd_time->year = sd_year; + /* sd2506_time_t.year 采用完整年份(如 2026),与 sd2506_set_time + * 中 year-2000、sd2506_get_time 中 +2000 的约定保持一致; + * 此处绝不能再减 2000,否则写 RTC 时会被二次减 2000 导致年份错乱。 */ + p_sd_time->year = (uint16_t)y; p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */ p_sd_time->day = d; p_sd_time->hour = h; @@ -169,22 +169,31 @@ int sys_clock_init(void) int64_t unix_secs = 0; int i; - /* Step 1: 从 SD2506 RTC 读取初始时间 - * 注意:sd2506_get_time() 返回的 year 已是完整年份(如 2026,驱动内 +2000), - * 此处不得再加 2000,否则年份虚增 → unix 秒数超 uint32 被截断回绕。 */ + /* ============================================================ + * Step 1:从 SD2506 硬件 RTC 读取初始时间 + * ============================================================ + * 注意:sd2506_get_time() 返回的 year 已是完整年份(如 2026, + * 驱动内部已 +2000)。此处【禁止】再 +2000,否则年份虚增到 4026, + * 换算出的 Unix 秒数远超 uint32_t 上限被截断回绕,时间彻底错乱。 */ sd2506_get_time(&sd_time); - /* RTC 校时(可选):时间无效时,或 FORCE=1 强制时,写入编译期默认时间 */ + /* ============================================================ + * Step 1.1:开发期校时(可选,由 RTC_DEFAULT_INIT_* 宏控制) + * ============================================================ + * 作用:RTC 出厂/掉电异常导致时间非法时,写入一个可用初始值; + * FORCE=1 时每次启动都强制写入,便于首次烧录或硬件时钟丢失。 + * 判定"时间无效":年份越界、月/日越界(仅粗判,不细校 2 月 29 日等)。 */ #if defined(RTC_DEFAULT_INIT_ENABLE) { int rtc_invalid = (sd_time.year < 2000 || sd_time.year > 2099 || sd_time.month < 1 || sd_time.month > 12 || sd_time.day < 1 || sd_time.day > 31); #if (RTC_DEFAULT_INIT_FORCE == 1) - rtc_invalid = 1; /* 强制每次启动校时 */ + rtc_invalid = 1; /* 强制每次启动校时:会覆盖用户/网络已设的正确时间,仅限开发期 */ #endif if (rtc_invalid) { sd2506_time_t init_t; + /* 用编译期默认宏构造初始时间;week 由 Zeller 公式(rtc_calc_week)推算 */ init_t.year = RTC_DEFAULT_YEAR; init_t.month = RTC_DEFAULT_MONTH; init_t.day = RTC_DEFAULT_DAY; @@ -193,11 +202,12 @@ int sys_clock_init(void) init_t.second = RTC_DEFAULT_SECOND; init_t.week = rtc_calc_week(init_t.year, init_t.month, init_t.day); + /* 写回 SD2506:断电后该时间被保留,下次上电即为有效值 */ if (sd2506_set_time(&init_t) == SD2506_OK) { DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d", init_t.year, init_t.month, init_t.day, init_t.hour, init_t.minute, init_t.second); - sd2506_get_time(&sd_time); /* 重新读取校准后的时间 */ + sd2506_get_time(&sd_time); /* 重新读取校准后的值,确保后续换算用真实 RTC 数据 */ } else { DBG_ERROR("sys_clock_init: RTC default write FAIL"); } @@ -205,6 +215,7 @@ int sys_clock_init(void) } #endif + /* 取出各时间字段(year 已是完整年份,直接用于下文 Unix 换算) */ year = (uint32_t)sd_time.year; month = (uint32_t)sd_time.month; day = (uint32_t)sd_time.day; @@ -212,13 +223,19 @@ int sys_clock_init(void) minute = (uint32_t)sd_time.minute; second = (uint32_t)sd_time.second; - DBG_INFO("sys_clock_init: RTC initial time %04d-%02d-%02d %02d:%02d:%02d", - year, month, day, hour, minute, second); - - /* Step 2: Unix epoch → seconds(简化算法,适用于 1970~2038) */ + /* ============================================================ + * Step 2:将 RTC 日历时间换算为 Unix epoch 秒数 + * ============================================================ + * Unix 时间定义:自 1970-01-01 00:00:00 起经过的秒数。 + * 算法:总天数(1970~去年整年 + 今年到上月 + 本月已过天)× 86400 + * + 当天时分秒。 + * 用 int64_t / uint64_t 累加避免中间溢出;最终结果(受 uint32_t 限制) + * 有效范围约 1970-01-01 ~ 2038-01-19,超出将回绕(见文档第 12 节)。 */ { uint64_t total_days = 0; + /* 2.1 累加 1970 至(今年-1)的整年天数:闰年 366、平年 365 + * 闰年规则:能被 4 整除但不能被 100 整除,或能被 400 整除 */ for (i = 1970; i < year; i++) { if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) { total_days += 366uLL; @@ -227,22 +244,34 @@ int sys_clock_init(void) } } + /* 2.2 判断今年是否闰年,构造各月天数表(2 月特判) */ int is_leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)); uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - if (is_leap) mdays[1] = 29; + if (is_leap) mdays[1] = 29; /* 闰年 2 月 29 天 */ + /* 2.3 累加今年 1 月至(本月-1)月的天数 */ for (i = 0; i < month - 1; i++) { total_days += mdays[i]; } + /* 2.4 加上本月已过天数(day-1:从今天 0 点起算) */ total_days += day - 1uL; + /* 2.5 总天数×86400(一天秒数)+ 当日时分秒 = 绝对 Unix 秒数 */ unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second; } - /* Step 3: 设置系统墙钟基准 */ + /* ============================================================ + * Step 3:建立"墙钟基准"(基准法核心) + * ============================================================ + * 记录当前 HAL Tick 与对应的 Unix 秒数。此后 sys_clock_get() 仅用 + * now_unix = s_base_unix_secs + (HAL_GetTick() - s_base_tick)/1000 + * 推算当前时间,运行期不再访问 I2C RTC,零阻塞、无累积误差。 */ s_base_tick = HAL_GetTick(); s_base_unix_secs = (uint32_t)unix_secs; + DBG_INFO("sys_clock_init: RTC initial time %04d-%02d-%02d %02d:%02d:%02d", + year, month, day, hour, minute, second); + DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu", (unsigned long)s_base_unix_secs, (unsigned long)s_base_tick); @@ -293,7 +322,7 @@ char *sys_clock_get_str(char *buf, size_t buf_size) unix_to_sd2506(unix_secs, &sd); snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d", - sd.year + 2000uL, sd.month, sd.day, + sd.year, sd.month, sd.day, sd.hour, sd.minute, sd.second); return buf; } @@ -318,8 +347,11 @@ int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time) /* Step 2: 写回 SD2506 RTC(若提供了 sd_time) */ if (sd_time != NULL) { - sd2506_set_time(sd_time); - DBG_INFO("sys_clock_set: SD2506 RTC written"); + if (sd2506_set_time(sd_time) == SD2506_OK) { + DBG_INFO("sys_clock_set: SD2506 RTC written"); + } else { + DBG_ERROR("sys_clock_set: SD2506 RTC write FAIL"); + } } return 0; diff --git a/Drivers/BSP/SD2506/sd2506.c b/Drivers/BSP/SD2506/sd2506.c index bfba8f2..1737a24 100644 --- a/Drivers/BSP/SD2506/sd2506.c +++ b/Drivers/BSP/SD2506/sd2506.c @@ -121,12 +121,12 @@ static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { static int sd2506_write_enable(void) { int ret = 0; - /* 先置 WRTC1=1 (bit6=1), 其它位参考手册0x84 */ - ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); + /* 写允许顺序(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; - /* 再置 WRTC2=1(bit5) + WRTC3=1(bit4): 0x8C */ - ret = sd2506_write_reg(SD2506_REG_CTR2, 0x8CU); + ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); if (ret != SD2506_OK) return ret; return SD2506_OK; @@ -142,12 +142,11 @@ static int sd2506_write_enable(void) { static int sd2506_write_disable(void) { int ret = 0; - /* 先清 WRTC2=0, WRTC3=0: 0x00 */ - ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U); + /* 写禁止顺序(手册要求):先清 WRTC2/WRTC3(CTR1),再清 WRTC1(CTR2) */ + ret = sd2506_write_reg(SD2506_REG_CTR1, 0x00U); if (ret != SD2506_OK) return ret; - /* 再清 WRTC1=0, 同时 ARST=1 使能自动复位: 0x20 */ - ret = sd2506_write_reg(SD2506_REG_CTR1, SD2506_CTR1_WRITE_OFF); + ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U); if (ret != SD2506_OK) return ret; return SD2506_OK; diff --git a/Drivers/BSP/SD2506/sd2506.h b/Drivers/BSP/SD2506/sd2506.h index d937002..a34fee9 100644 --- a/Drivers/BSP/SD2506/sd2506.h +++ b/Drivers/BSP/SD2506/sd2506.h @@ -50,7 +50,7 @@ extern "C" { #define SD2506_REG_AL_EN 0x0EU /* 报警允许寄存器 */ /* 控制寄存器 */ -#define SD2506_REG_CTR1 0x0FH /* 控制寄存器1 */ +#define SD2506_REG_CTR1 0x0FU /* 控制寄存器1 */ #define SD2506_REG_CTR2 0x10U /* 控制寄存器2 */ #define SD2506_REG_CTR3 0x11U /* 控制寄存器3 */ diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index 37ffcf6..ec454e3 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -2029,6 +2029,11 @@ 1 ..\test\storage_test_task.c + + sd2506_test_task.c + 1 + ..\test\sd2506_test_task.c + diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt index b66aae7..b6d4adb 100644 --- a/MDK-ARM/build_log.txt +++ b/MDK-ARM/build_log.txt @@ -3,73 +3,74 @@ Rebuild target 'STM32F407-Demo' assembling startup_stm32f407xx.s... compiling ringbuf.c... compiling crc.c... -compiling stm32f4xx_hal_flash_ramfunc.c... -compiling stm32f4xx_hal_flash_ex.c... -compiling stm32f4xx_hal_timebase_tim.c... -compiling spi.c... -compiling gpio.c... -compiling dma.c... -compiling i2c.c... -compiling stm32f4xx_hal_rcc.c... -compiling stm32f4xx_hal_flash.c... -compiling adc_task.c... -compiling stm32f4xx_hal_msp.c... -compiling stm32f4xx_it.c... -compiling usart.c... -compiling app_main.c... -compiling net_task.c... -compiling sys_clock.c... -compiling rs485_task.c... -compiling stm32f4xx_hal_rcc_ex.c... -compiling stm32f4xx_hal_tim_ex.c... -compiling time_sync.c... -compiling main.c... compiling freertos.c... -compiling stm32f4xx_hal_gpio.c... -compiling stm32f4xx_hal_tim.c... +compiling i2c.c... +compiling stm32f4xx_hal_tim_ex.c... +compiling dma.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_it.c... +compiling main.c... compiling stm32f4xx_hal_dma_ex.c... -compiling lftpd_string.c... -compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_gpio.c... compiling stm32f4xx_hal_dma.c... +compiling app_main.c... +compiling stm32f4xx_hal_pwr.c... +compiling spi.c... +compiling stm32f4xx_hal_timebase_tim.c... +compiling stm32f4xx_hal_msp.c... +compiling gpio.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling stm32f4xx_hal_flash.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling usart.c... +compiling adc_task.c... +compiling stm32f4xx_hal_rcc.c... +compiling time_sync.c... +compiling sys_clock.c... +compiling net_task.c... +compiling rs485_task.c... compiling croutine.c... +compiling lftpd_string.c... +compiling stm32f4xx_hal_tim.c... compiling list.c... compiling event_groups.c... compiling stream_buffer.c... compiling queue.c... +compiling stm32f4xx_hal_pwr_ex.c... compiling tasks.c... compiling timers.c... -compiling stm32f4xx_hal_cortex.c... -compiling stm32f4xx_hal_pwr_ex.c... compiling heap_4.c... -compiling system_stm32f4xx.c... compiling port.c... compiling journal.c... compiling map.c... -compiling lftpd_io.c... -compiling stm32f4xx_hal.c... -compiling stm32f4xx_hal_i2c_ex.c... -compiling stm32f4xx_hal_exti.c... -compiling ch395f.c... -compiling rs485.c... -compiling lftpd_inet.c... compiling ff.c... -compiling tpafe5160.c... -compiling sd2506.c... -compiling gd5f2gq5ue.c... +compiling stm32f4xx_hal_cortex.c... +compiling rs485.c... +compiling lftpd_io.c... compiling nand_ftl.c... -compiling stm32f4xx_hal_uart.c... -compiling stm32f4xx_hal_spi.c... +compiling lftpd_inet.c... +compiling stm32f4xx_hal_exti.c... +compiling net_socket.c... +compiling cmsis_os2.c... compiling net_select.c... compiling lftpd.c... -compiling net_socket.c... -compiling stm32f4xx_hal_i2c.c... -compiling cmsis_os2.c... -compiling gd5f_test_task.c... +compiling stm32f4xx_hal_i2c_ex.c... +compiling sd2506.c... compiling net_test_task.c... compiling ch395f_test_task.c... +compiling system_stm32f4xx.c... +compiling tpafe5160.c... +compiling stm32f4xx_hal.c... +compiling ch395f.c... +compiling gd5f2gq5ue.c... +compiling gd5f_test_task.c... +compiling stm32f4xx_hal_i2c.c... +compiling stm32f4xx_hal_uart.c... +compiling stm32f4xx_hal_spi.c... compiling storage_test_task.c... +compiling sd2506_test_task.c... linking... -Program Size: Code=59744 RO-data=2556 RW-data=412 ZI-data=51868 +Program Size: Code=61836 RO-data=3356 RW-data=420 ZI-data=51868 FromELF: creating hex file... ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). Build Time Elapsed: 00:00:15 diff --git a/docs/SD2506_Test_Guide.md b/docs/SD2506_Test_Guide.md new file mode 100644 index 0000000..eaed8fe --- /dev/null +++ b/docs/SD2506_Test_Guide.md @@ -0,0 +1,252 @@ +# SD2506 RTC 测试规范 + +## 1. 概述 + +本文档定义 SD2506API-G 高精度温补实时时钟驱动(STM32F407ZGTx,I2C1 接口 PB6-SCL / PB7-SDA)的测试规范。RTC 为本地外设,测试**无需 PC 配合**(仅串口日志观察),覆盖时间读写、写保护(WRTC)解锁时序、跨重启持久化三大主题。测试按阶段(`TM-RTC-NN`)组织,每阶段含独立用例(`TC-RTC-NNN`)。 + +> **背景**:本驱动曾出现"时间同步后写入不生效、重启回到旧时间"的缺陷。根因是 `sd2506_write_enable()` 的 WRTC 解锁时序与 SD2506API-G 实际位布局不符——写操作 I2C 返回 OK,但芯片因写保护未真正打开而**静默忽略**数据。本测试 Phase 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 | + +### SD2506 关键参数 + +| 参数 | 值 | 说明 | +|------|-----|------| +| I2C 器件地址(7 位) | `0x32` | HAL 用 8 位:写 `0x64`、读 `0x65`(`SD2506_I2C_ADDR_WRITE/READ`) | +| 接口 | I2C1(PB6-SCL / PB7-SDA) | CubeMX 已完成初始化 | +| 时间寄存器 | `00H` SEC ~ `06H` YEAR(BCD) | 一次性连续读写 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 网络端 +``` + +### 构建模式与执行顺序 + +测试固件复用既有 `ch395fTestTask`(`StartCh395fTestTask`)调度框架,SD2506 作为独立 `TEST_SUITE_RTC` 套件接入,不新增 FreeRTOS 任务;三个相位顺序执行(Phase 1 → 2 → 3)。各相位由 `test/sd2506_test.h` 的 `ENABLE_RTC_*` 宏独立开关,套件整体由 `test/test_config.h` 的 `TEST_SUITE_RTC` 选择。 + +| 构建宏 | 相位 | 说明 | +|--------|------|------| +| `ENABLE_RTC_BASIC_TESTS` | Phase 1 | 公共 API 基本读写 / 走时 | +| `ENABLE_RTC_WRTC_PROBE_TESTS` | Phase 2 | WRTC 解锁时序穷举探测 | +| `ENABLE_RTC_PERSIST_TESTS` | Phase 3 | 跨重启持久化(需人工重启核验) | + +**执行顺序严格递增**:先确认 Phase 1(若 FAIL 即证明当前驱动写无效),再 Phase 2(定位正确 WRTC 时序),最后 Phase 3(用命中时序验证持久化)。 + +### 启用测试 + +本测试复用既有 `ch395fTestTask`(`StartCh395fTestTask`)调度框架,作为独立 `TEST_SUITE_RTC` 套件接入,不新增 FreeRTOS 任务。 + +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。 + +--- + +## 2. 阶段 1:基本读写(公共 API) + +| 阶段 ID | TM-RTC-01 | +|---------|-----------| +| **类型** | 独立运行 | +| **耗时** | ~5 秒 | +| **入口** | `StartCh395fTestTask → TEST_SUITE_RTC → sd2506_test_run()` → `sd2506_phase_basic()` | +| **出口** | 串口 `Phase 1 basic: N/N PASSED (failed=0)` | + +### TC-RTC-001: 初始化与读取 + +| 字段 | 值 | +|------|-----| +| **ID** | TC-RTC-001 | +| **优先级** | P0 | +| **类型** | 功能测试 | +| **标题** | 验证 `sd2506_init()` 通信正常并可读取当前时间 | +| **前置条件** | 1. I2C1 已初始化
2. 未启用其他会并发访问 I2C 的测试 | + +**测试步骤与判定标准**: + +| 步 | 操作 | 成功标准 | 失败标准 | +|----|------|----------|----------| +| 1 | `sd2506_init()` | 返回 `0`(`SD2506_OK`) | 返回 `-2`(`SD2506_I2C_ERROR`,I2C 通信失败/芯片未响应) | +| 2 | `sd2506_get_time(&r)` 读取当前时间 | 返回 `0` 且 `r.year` 落在 `2000~2099`、月/日/时/分/秒为合法 BCD 范围 | 返回非零,或字段越界(如月>12、秒>59) | + +**通过标准**:2/2 通过,0 失败。任一失败 → 阶段标记 FAIL + +**覆盖原则**: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. 阶段 2:WRTC 解锁时序探测 + +| 阶段 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 正常
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 | 仅 CTR2(WRTC1) | +| 5 | `0x80` | `0x00` | CTR1→CTR2 | 仅 CTR1 bit7(WRTC1) | +| 6 | `0x70` | `0x00` | CTR1→CTR2 | CTR1 bit6/5/4 | +| 7 | `0x86` | `0x00` | CTR1→CTR2 | CTR1 bit7+bit2(WRTC3+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 | diff --git a/docs/SD2506_Trap_Records.md b/docs/SD2506_Trap_Records.md new file mode 100644 index 0000000..fa33290 --- /dev/null +++ b/docs/SD2506_Trap_Records.md @@ -0,0 +1,41 @@ +# SD2506API-G 陷阱记录 (Trap Records) + +## 陷阱 1:KEIL/ARMCC 下十六进制字面量 `H` 后缀被编译为 0(严重) + +### 现象 +SD2506 RTC 时间写不进去:`sd2506_set_time()` 返回 OK,但回读 / 示波器 / 重启后时间不更新。 +诊断时读 `SD2506_REG_CTR1`(0x0F) 得到的值像是秒寄存器内容(PMF / BLF / RTCF / WRTC 位全是反的), +曾据此误判为「芯片处于 VBAT 模式、写保护硬禁止、需要抬高 VDD 电压」——**全部是假象**。 + +### 根因 +在 `Drivers/BSP/SD2506/sd2506.h` 中,控制寄存器 1 曾写成: + +```c +#define SD2506_REG_CTR1 0x0FH /* 控制寄存器1 */ +``` + +KEIL/ARMCC (armcc v5) **不识别 `H` 整数后缀**。遇到 `0x0FH` 会报语法错并把它当作 **0** 处理, +于是 `SD2506_REG_CTR1` 实际等于 `0`(即秒寄存器 00H),而不是 `0x0F`。 + +后果链: +1. `sd2506_write_enable()` 把 WRTC 位写到了 **00H(秒寄存器)**,而非真正的 CTR1(0x0F); +2. 真正的 CTR1 里 WRTC 永远是 0 → 写保护永不解除; +3. 任何对 00H~79H 的「时间写」都被芯片静默忽略; +4. 所有通过 `SD2506_REG_CTR1` 读取的诊断都实际读到秒寄存器,于是 PMF / BLF / RTCF / WRTC 解码全错。 + +### 修复 +把整数后缀统一改成 `U`(或纯 `0xNN`,绝不能带 `H`): + +```c +#define SD2506_REG_CTR1 0x0FU /* 控制寄存器1 */ +``` + +### 排查要点(铁律) +- KEIL 工程里 **所有** 十六进制寄存器 / 常量宏都必须用 `0xNNU` / `0xNNUL` / `0xNN`,**禁止 `0xNNH`**。 +- 全工程已扫描确认仅此一处 `0x0FH` 非法字面量(`.c/.h` 非注释行);凡是出现 + 「寄存器读出来位全反 / WRTC 置位无效 / 时间写不进」,**先排查寄存器宏后缀**,再怀疑硬件。 +- 改代码只用 Edit 工具,绝不用 PowerShell `Get/Set-Content` 重写含中文文件(会 UTF-8/GBK 互转乱码)。 + +### 验证 +修复后 `TEST_SUITE_RTC` Phase 1:6/6 PASS,`set==readback` PASS,CTR1 真实读出 `PMF=0`(VDD 模式)。 +VDD 3.2V 完全正常,无需抬高电压;之前的「VBAT / 电压不够」推断作废。 diff --git a/docs/芯片手册/SD2506API-G.md b/docs/芯片手册/SD2506API-G.md index 44b1388..71e53bd 100644 --- a/docs/芯片手册/SD2506API-G.md +++ b/docs/芯片手册/SD2506API-G.md @@ -120,7 +120,7 @@ GND 注: $" \mathbb { X } ^ { 3 1 }$ 表示随机值,可以为 0或者 1。 -## 4.2 实时时钟数据寄存器 $( 0 0 mathsf { H } \widetilde { \mathsf { \Omega } } 0 6 \mathsf { H } )$ +## 4.2 实时时钟数据寄存器 (00H~06H) 实时时钟数据寄存器是 7字节的存储器,它以 BCD码方式存贮包括年、月、日、星期、时、分、秒的数据。 diff --git a/test/ch395f_test_task.c b/test/ch395f_test_task.c index 17d13f6..46e8420 100644 --- a/test/ch395f_test_task.c +++ b/test/ch395f_test_task.c @@ -20,6 +20,7 @@ #include "test_config.h" #include "storage_test.h" #include "gd5f_test.h" +#include "sd2506_test.h" #define DBG_TAG "[TEST_TASK]" #include "dbg_log.h" @@ -1395,6 +1396,8 @@ void StartCh395fTestTask(void *argument) storage_test_run(); #elif defined(TEST_SUITE_GD5F) gd5f_test_run(); +#elif defined(TEST_SUITE_RTC) + sd2506_test_run(); #endif for (;;) { diff --git a/test/sd2506_test.h b/test/sd2506_test.h new file mode 100644 index 0000000..9929c8b --- /dev/null +++ b/test/sd2506_test.h @@ -0,0 +1,48 @@ +#ifndef SD2506_TEST_H +#define SD2506_TEST_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============ Phase 使能开关(取消注释即启用,默认全开) ============ */ +#define ENABLE_RTC_BASIC_TESTS /* Phase 1: 基本读写 / 走时 */ +#define ENABLE_RTC_WRTC_PROBE_TESTS /* Phase 2: WRTC 解锁时序探测(定位正确时序) */ +#define ENABLE_RTC_PERSIST_TESTS /* Phase 3: 跨重启持久化 */ + +/* ============ 测试统计(与 STORAGE/GD5F 套件一致的独立统计) ============ */ +typedef struct { + uint16_t total; + uint16_t passed; + uint16_t failed; +} sd2506_test_stats_t; + +extern sd2506_test_stats_t g_sd2506_test_stats; + +#define SD2506_TEST_CHECK(cond, fmt, ...) do { \ + g_sd2506_test_stats.total++; \ + if (cond) { \ + g_sd2506_test_stats.passed++; \ + DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); \ + } else { \ + g_sd2506_test_stats.failed++; \ + DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); \ + } \ +} while (0) + +#define SD2506_TEST_REPORT(name) do { \ + DBG_INFO("=== %s: %u/%u PASSED (failed=%u) ===", \ + name, g_sd2506_test_stats.passed, g_sd2506_test_stats.total, \ + g_sd2506_test_stats.failed); \ +} while (0) + +/* 测试入口:由 StartCh395fTestTask 经 TEST_SUITE_RTC 调度,顺序执行 Phase 1~3 */ +void sd2506_test_run(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SD2506_TEST_H */ diff --git a/test/sd2506_test_task.c b/test/sd2506_test_task.c new file mode 100644 index 0000000..5c01e25 --- /dev/null +++ b/test/sd2506_test_task.c @@ -0,0 +1,137 @@ +/****************************************************************************** + * 模块名称: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()(由 StartCh395fTestTask 经 TEST_SUITE_RTC 调度) + * 创建日期:2026-08-28 + ******************************************************************************/ + +#include +#include +#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: 写固定时间,立即回读比对。 + * 若 FAIL -> 先查 sd2506.h 寄存器宏后缀是否为 H(曾因 0x0FH 被 KEIL 编译为 0 + * 导致 SD2506_REG_CTR1 指向秒寄存器、WRTC 写错地址)。详见 docs/SD2506_Trap_Records.md。 */ + sd2506_time_t t; + 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); + } + int 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)"); + + /* 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 */ + +/* ===================== 套件入口 ===================== */ +void sd2506_test_run(void) +{ + DBG_INFO("SD2506 test suite started"); + + 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)); + } + + /* 记录原始时间,测试结束恢复,避免把板子时间留在测试值 */ + 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"); + } + + memset(&g_sd2506_test_stats, 0, sizeof(g_sd2506_test_stats)); + +#ifdef ENABLE_RTC_BASIC_TESTS + sd2506_phase_basic(); +#endif + + /* 恢复原始时间(用公共 API,写保护逻辑已由 sd2506.c 正确处理) */ + if (have_orig) { + int r = sd2506_set_time(&orig); + DBG_INFO(" restored original time (ret=%d)", r); + } + + DBG_INFO("SD2506 test suite done"); + for (;;) { + osDelay(1000); + } +} + +#endif /* TEST_SUITE_RTC */ diff --git a/test/test_config.h b/test/test_config.h index f9f1774..9cbe648 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -26,8 +26,9 @@ extern "C" { //#define TEST_SUITE_CH395F //#define TEST_SUITE_STORAGE //#define TEST_SUITE_GD5F +#define TEST_SUITE_RTC -#if defined(TEST_SUITE_CH395F) + defined(TEST_SUITE_STORAGE) + defined(TEST_SUITE_GD5F) > 1 +#if defined(TEST_SUITE_CH395F) + defined(TEST_SUITE_STORAGE) + defined(TEST_SUITE_GD5F) + defined(TEST_SUITE_RTC) > 1 #error "测试套件互斥:一次构建只能启用一个 TEST_SUITE_*" #endif @@ -62,6 +63,11 @@ extern "C" { #define TEST_SUITE_NAME "GD5F" #endif +#ifdef TEST_SUITE_RTC +#include "sd2506_test.h" +#define TEST_SUITE_NAME "RTC" +#endif + #ifndef TEST_SUITE_NAME #define TEST_SUITE_NAME "NONE" #endif diff --git a/test/time_sync_server.py b/test/time_sync_server.py index c276527..a28eb07 100644 --- a/test/time_sync_server.py +++ b/test/time_sync_server.py @@ -24,6 +24,7 @@ import socket import struct import sys import time +import calendar DEFAULT_MCU_IP = "192.168.1.100" DEFAULT_PORT = 8888 @@ -44,8 +45,11 @@ def main(): while True: try: - # 本地 wall-clock 的 epoch(与电脑显示一致;板子按本地时间分解) - local_epoch = int(time.mktime(time.localtime())) + # 本地 wall-clock 当作 UTC 算出的 epoch(与电脑显示一致)。 + # 注意:板子 RTC 存本地时间且 unix_to_sd2506 无时区处理, + # 故必须发"本地墙钟当 UTC"的伪 epoch(calendar.timegm), + # 不能用 time.mktime(返回真正 UTC epoch,会导致板子慢 8 小时)。 + local_epoch = int(calendar.timegm(time.localtime())) pkt = b"TIME" + struct.pack(">I", local_epoch) with socket.create_connection((mcu_ip, port), timeout=5) as sock: