时间终于同步,存储成功

This commit is contained in:
2026-08-28 21:10:51 +08:00
parent 4e9ac0d9c3
commit 02a05a7a63
13 changed files with 606 additions and 78 deletions

View File

@@ -25,7 +25,7 @@
* 修改日期/时间时同步更新下列宏。 * 修改日期/时间时同步更新下列宏。
* ============================================================ */ * ============================================================ */
#define RTC_DEFAULT_INIT_ENABLE 1 #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_YEAR 2026
#define RTC_DEFAULT_MONTH 8 #define RTC_DEFAULT_MONTH 8
#define RTC_DEFAULT_DAY 24 #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)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
(uint32_t)(d % 100uLL)) % 7uLL; (uint32_t)(d % 100uLL)) % 7uLL;
/* SD2506 year 字段为 0~99+2000 */ /* sd2506_time_t.year 采用完整年份(如 2026与 sd2506_set_time
uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000); * 中 year-2000、sd2506_get_time 中 +2000 的约定保持一致;
* 此处绝不能再减 2000否则写 RTC 时会被二次减 2000 导致年份错乱。 */
p_sd_time->year = sd_year; p_sd_time->year = (uint16_t)y;
p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */ p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */
p_sd_time->day = d; p_sd_time->day = d;
p_sd_time->hour = h; p_sd_time->hour = h;
@@ -169,22 +169,31 @@ int sys_clock_init(void)
int64_t unix_secs = 0; int64_t unix_secs = 0;
int i; int i;
/* Step 1: 从 SD2506 RTC 读取初始时间 /* ============================================================
* 注意sd2506_get_time() 返回的 year 已是完整年份(如 2026驱动内 +2000 * Step 1从 SD2506 硬件 RTC 读取初始时间
* 此处不得再加 2000否则年份虚增 → unix 秒数超 uint32 被截断回绕。 */ * ============================================================
* 注意sd2506_get_time() 返回的 year 已是完整年份(如 2026
* 驱动内部已 +2000。此处【禁止】再 +2000否则年份虚增到 4026
* 换算出的 Unix 秒数远超 uint32_t 上限被截断回绕,时间彻底错乱。 */
sd2506_get_time(&sd_time); 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) #if defined(RTC_DEFAULT_INIT_ENABLE)
{ {
int rtc_invalid = (sd_time.year < 2000 || sd_time.year > 2099 || int rtc_invalid = (sd_time.year < 2000 || sd_time.year > 2099 ||
sd_time.month < 1 || sd_time.month > 12 || sd_time.month < 1 || sd_time.month > 12 ||
sd_time.day < 1 || sd_time.day > 31); sd_time.day < 1 || sd_time.day > 31);
#if (RTC_DEFAULT_INIT_FORCE == 1) #if (RTC_DEFAULT_INIT_FORCE == 1)
rtc_invalid = 1; /* 强制每次启动校时 */ rtc_invalid = 1; /* 强制每次启动校时:会覆盖用户/网络已设的正确时间,仅限开发期 */
#endif #endif
if (rtc_invalid) { if (rtc_invalid) {
sd2506_time_t init_t; sd2506_time_t init_t;
/* 用编译期默认宏构造初始时间week 由 Zeller 公式rtc_calc_week推算 */
init_t.year = RTC_DEFAULT_YEAR; init_t.year = RTC_DEFAULT_YEAR;
init_t.month = RTC_DEFAULT_MONTH; init_t.month = RTC_DEFAULT_MONTH;
init_t.day = RTC_DEFAULT_DAY; init_t.day = RTC_DEFAULT_DAY;
@@ -193,11 +202,12 @@ int sys_clock_init(void)
init_t.second = RTC_DEFAULT_SECOND; init_t.second = RTC_DEFAULT_SECOND;
init_t.week = rtc_calc_week(init_t.year, init_t.month, init_t.day); init_t.week = rtc_calc_week(init_t.year, init_t.month, init_t.day);
/* 写回 SD2506断电后该时间被保留下次上电即为有效值 */
if (sd2506_set_time(&init_t) == SD2506_OK) { if (sd2506_set_time(&init_t) == SD2506_OK) {
DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d", DBG_INFO("sys_clock_init: RTC set %04d-%02d-%02d %02d:%02d:%02d",
init_t.year, init_t.month, init_t.day, init_t.year, init_t.month, init_t.day,
init_t.hour, init_t.minute, init_t.second); init_t.hour, init_t.minute, init_t.second);
sd2506_get_time(&sd_time); /* 重新读取校准后的时间 */ sd2506_get_time(&sd_time); /* 重新读取校准后的值,确保后续换算用真实 RTC 数据 */
} else { } else {
DBG_ERROR("sys_clock_init: RTC default write FAIL"); DBG_ERROR("sys_clock_init: RTC default write FAIL");
} }
@@ -205,6 +215,7 @@ int sys_clock_init(void)
} }
#endif #endif
/* 取出各时间字段year 已是完整年份,直接用于下文 Unix 换算) */
year = (uint32_t)sd_time.year; year = (uint32_t)sd_time.year;
month = (uint32_t)sd_time.month; month = (uint32_t)sd_time.month;
day = (uint32_t)sd_time.day; day = (uint32_t)sd_time.day;
@@ -212,13 +223,19 @@ int sys_clock_init(void)
minute = (uint32_t)sd_time.minute; minute = (uint32_t)sd_time.minute;
second = (uint32_t)sd_time.second; 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将 RTC 日历时间换算为 Unix epoch 秒数
* ============================================================
/* Step 2: Unix epoch → seconds简化算法适用于 1970~2038 */ * 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; uint64_t total_days = 0;
/* 2.1 累加 1970 至(今年-1的整年天数闰年 366、平年 365
* 闰年规则:能被 4 整除但不能被 100 整除,或能被 400 整除 */
for (i = 1970; i < year; i++) { for (i = 1970; i < year; i++) {
if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) { if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) {
total_days += 366uLL; 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)); 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}; 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++) { for (i = 0; i < month - 1; i++) {
total_days += mdays[i]; total_days += mdays[i];
} }
/* 2.4 加上本月已过天数day-1从今天 0 点起算) */
total_days += day - 1uL; total_days += day - 1uL;
/* 2.5 总天数×86400一天秒数+ 当日时分秒 = 绝对 Unix 秒数 */
unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second; 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_tick = HAL_GetTick();
s_base_unix_secs = (uint32_t)unix_secs; 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", DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu",
(unsigned long)s_base_unix_secs, (unsigned long)s_base_tick); (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); unix_to_sd2506(unix_secs, &sd);
snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d", 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); sd.hour, sd.minute, sd.second);
return buf; 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 */ /* Step 2: 写回 SD2506 RTC若提供了 sd_time */
if (sd_time != NULL) { if (sd_time != NULL) {
sd2506_set_time(sd_time); if (sd2506_set_time(sd_time) == SD2506_OK) {
DBG_INFO("sys_clock_set: SD2506 RTC written"); DBG_INFO("sys_clock_set: SD2506 RTC written");
} else {
DBG_ERROR("sys_clock_set: SD2506 RTC write FAIL");
}
} }
return 0; return 0;

View File

@@ -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) { static int sd2506_write_enable(void) {
int ret = 0; int ret = 0;
/* 先置 WRTC1=1 (bit6=1), 其它位参考手册0x84 */ /* 写允许顺序SD2506/SD24xx 手册要求):先置 WRTC1(CTR2 bit7)=1
ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); * 再置 WRTC2(CTR1 bit2) + WRTC3(CTR1 bit7)=1。顺序反了芯片不会解开写保护。 */
ret = sd2506_write_reg(SD2506_REG_CTR2, 0x80U);
if (ret != SD2506_OK) return ret; if (ret != SD2506_OK) return ret;
/* 再置 WRTC2=1(bit5) + WRTC3=1(bit4): 0x8C */ ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U);
ret = sd2506_write_reg(SD2506_REG_CTR2, 0x8CU);
if (ret != SD2506_OK) return ret; if (ret != SD2506_OK) return ret;
return SD2506_OK; return SD2506_OK;
@@ -142,12 +142,11 @@ static int sd2506_write_enable(void) {
static int sd2506_write_disable(void) { static int sd2506_write_disable(void) {
int ret = 0; int ret = 0;
/* 先清 WRTC2=0, WRTC3=0: 0x00 */ /* 写禁止顺序(手册要求):先清 WRTC2/WRTC3(CTR1),再清 WRTC1(CTR2) */
ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U); ret = sd2506_write_reg(SD2506_REG_CTR1, 0x00U);
if (ret != SD2506_OK) return ret; if (ret != SD2506_OK) return ret;
/* 再清 WRTC1=0, 同时 ARST=1 使能自动复位: 0x20 */ ret = sd2506_write_reg(SD2506_REG_CTR2, 0x00U);
ret = sd2506_write_reg(SD2506_REG_CTR1, SD2506_CTR1_WRITE_OFF);
if (ret != SD2506_OK) return ret; if (ret != SD2506_OK) return ret;
return SD2506_OK; return SD2506_OK;

View File

@@ -50,7 +50,7 @@ extern "C" {
#define SD2506_REG_AL_EN 0x0EU /* 报警允许寄存器 */ #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_CTR2 0x10U /* 控制寄存器2 */
#define SD2506_REG_CTR3 0x11U /* 控制寄存器3 */ #define SD2506_REG_CTR3 0x11U /* 控制寄存器3 */

View File

@@ -2029,6 +2029,11 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\test\storage_test_task.c</FilePath> <FilePath>..\test\storage_test_task.c</FilePath>
</File> </File>
<File>
<FileName>sd2506_test_task.c</FileName>
<FileType>1</FileType>
<FilePath>..\test\sd2506_test_task.c</FilePath>
</File>
</Files> </Files>
</Group> </Group>
<Group> <Group>

View File

@@ -3,73 +3,74 @@ Rebuild target 'STM32F407-Demo'
assembling startup_stm32f407xx.s... assembling startup_stm32f407xx.s...
compiling ringbuf.c... compiling ringbuf.c...
compiling crc.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 freertos.c...
compiling stm32f4xx_hal_gpio.c... compiling i2c.c...
compiling stm32f4xx_hal_tim.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 stm32f4xx_hal_dma_ex.c...
compiling lftpd_string.c... compiling stm32f4xx_hal_gpio.c...
compiling stm32f4xx_hal_pwr.c...
compiling stm32f4xx_hal_dma.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 croutine.c...
compiling lftpd_string.c...
compiling stm32f4xx_hal_tim.c...
compiling list.c... compiling list.c...
compiling event_groups.c... compiling event_groups.c...
compiling stream_buffer.c... compiling stream_buffer.c...
compiling queue.c... compiling queue.c...
compiling stm32f4xx_hal_pwr_ex.c...
compiling tasks.c... compiling tasks.c...
compiling timers.c... compiling timers.c...
compiling stm32f4xx_hal_cortex.c...
compiling stm32f4xx_hal_pwr_ex.c...
compiling heap_4.c... compiling heap_4.c...
compiling system_stm32f4xx.c...
compiling port.c... compiling port.c...
compiling journal.c... compiling journal.c...
compiling map.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 ff.c...
compiling tpafe5160.c... compiling stm32f4xx_hal_cortex.c...
compiling sd2506.c... compiling rs485.c...
compiling gd5f2gq5ue.c... compiling lftpd_io.c...
compiling nand_ftl.c... compiling nand_ftl.c...
compiling stm32f4xx_hal_uart.c... compiling lftpd_inet.c...
compiling stm32f4xx_hal_spi.c... compiling stm32f4xx_hal_exti.c...
compiling net_socket.c...
compiling cmsis_os2.c...
compiling net_select.c... compiling net_select.c...
compiling lftpd.c... compiling lftpd.c...
compiling net_socket.c... compiling stm32f4xx_hal_i2c_ex.c...
compiling stm32f4xx_hal_i2c.c... compiling sd2506.c...
compiling cmsis_os2.c...
compiling gd5f_test_task.c...
compiling net_test_task.c... compiling net_test_task.c...
compiling ch395f_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 storage_test_task.c...
compiling sd2506_test_task.c...
linking... 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... FromELF: creating hex file...
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:15 Build Time Elapsed: 00:00:15

252
docs/SD2506_Test_Guide.md Normal file
View File

@@ -0,0 +1,252 @@
# SD2506 RTC 测试规范
## 1. 概述
本文档定义 SD2506API-G 高精度温补实时时钟驱动STM32F407ZGTxI2C1 接口 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` |
| 接口 | 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 网络端
```
### 构建模式与执行顺序
测试固件复用既有 `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 已初始化<br>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. 阶段 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 |

View File

@@ -0,0 +1,41 @@
# SD2506API-G 陷阱记录 (Trap Records)
## 陷阱 1KEIL/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 16/6 PASS`set==readback` PASSCTR1 真实读出 `PMF=0`VDD 模式)。
VDD 3.2V 完全正常无需抬高电压之前的「VBAT / 电压不够」推断作废。

View File

@@ -120,7 +120,7 @@ GND
注: $" \mathbb { X } ^ { 3 1 }$ 表示随机值,可以为 0或者 1。 注: $" \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码方式存贮包括年、月、日、星期、时、分、秒的数据。 实时时钟数据寄存器是 7字节的存储器它以 BCD码方式存贮包括年、月、日、星期、时、分、秒的数据。

View File

@@ -20,6 +20,7 @@
#include "test_config.h" #include "test_config.h"
#include "storage_test.h" #include "storage_test.h"
#include "gd5f_test.h" #include "gd5f_test.h"
#include "sd2506_test.h"
#define DBG_TAG "[TEST_TASK]" #define DBG_TAG "[TEST_TASK]"
#include "dbg_log.h" #include "dbg_log.h"
@@ -1395,6 +1396,8 @@ void StartCh395fTestTask(void *argument)
storage_test_run(); storage_test_run();
#elif defined(TEST_SUITE_GD5F) #elif defined(TEST_SUITE_GD5F)
gd5f_test_run(); gd5f_test_run();
#elif defined(TEST_SUITE_RTC)
sd2506_test_run();
#endif #endif
for (;;) { for (;;) {

48
test/sd2506_test.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef SD2506_TEST_H
#define SD2506_TEST_H
#include <stdint.h>
#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 */

137
test/sd2506_test_task.c Normal file
View File

@@ -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 <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: 写固定时间,立即回读比对。
* 若 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 */

View File

@@ -26,8 +26,9 @@ extern "C" {
//#define TEST_SUITE_CH395F //#define TEST_SUITE_CH395F
//#define TEST_SUITE_STORAGE //#define TEST_SUITE_STORAGE
//#define TEST_SUITE_GD5F //#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_*" #error "测试套件互斥:一次构建只能启用一个 TEST_SUITE_*"
#endif #endif
@@ -62,6 +63,11 @@ extern "C" {
#define TEST_SUITE_NAME "GD5F" #define TEST_SUITE_NAME "GD5F"
#endif #endif
#ifdef TEST_SUITE_RTC
#include "sd2506_test.h"
#define TEST_SUITE_NAME "RTC"
#endif
#ifndef TEST_SUITE_NAME #ifndef TEST_SUITE_NAME
#define TEST_SUITE_NAME "NONE" #define TEST_SUITE_NAME "NONE"
#endif #endif

View File

@@ -24,6 +24,7 @@ import socket
import struct import struct
import sys import sys
import time import time
import calendar
DEFAULT_MCU_IP = "192.168.1.100" DEFAULT_MCU_IP = "192.168.1.100"
DEFAULT_PORT = 8888 DEFAULT_PORT = 8888
@@ -44,8 +45,11 @@ def main():
while True: while True:
try: try:
# 本地 wall-clock 的 epoch与电脑显示一致;板子按本地时间分解 # 本地 wall-clock 当作 UTC 算出的 epoch与电脑显示一致
local_epoch = int(time.mktime(time.localtime())) # 注意:板子 RTC 存本地时间且 unix_to_sd2506 无时区处理,
# 故必须发"本地墙钟当 UTC"的伪 epochcalendar.timegm
# 不能用 time.mktime返回真正 UTC epoch会导致板子慢 8 小时)。
local_epoch = int(calendar.timegm(time.localtime()))
pkt = b"TIME" + struct.pack(">I", local_epoch) pkt = b"TIME" + struct.pack(">I", local_epoch)
with socket.create_connection((mcu_ip, port), timeout=5) as sock: with socket.create_connection((mcu_ip, port), timeout=5) as sock: