diff --git a/docs/子系统设计/系统墙钟时间维护说明.md b/docs/子系统设计/系统墙钟时间维护说明.md index 1e0d08f3..f2a84f62 100644 --- a/docs/子系统设计/系统墙钟时间维护说明.md +++ b/docs/子系统设计/系统墙钟时间维护说明.md @@ -22,6 +22,25 @@ - 运行期间不再频繁访问 I2C RTC,而是以 **HAL Tick 流逝量** 推算当前时间,避免每次取时都走 I2C 总线; - 仅在 `sys_clock_set()` 被调用(如用户校时、SNTP 同步)时,才把时间写回 SD2506 实现掉电保持。 +### 1.1 时基口径:内部一律**本地钟面**(用户裁定 2026-10-27) + +**裁定原文:「时基先本地为准」。** 定的是**冲突时谁赢**:面板时钟页与 8888 对时把「本地钟面当 epoch」写入, +而 104 的 `C_CS NA`(对时命令)携的是 UTC,两条写入口**后写者赢** ⇒ 装置墙钟与主机本地时刻恒差 8 小时。 +本裁定结束「后写者赢」:**内部真源 = 本地钟面**,任何来源写入前都要先换算成本地钟面再落库。 + +这条同时也纠正了**本文自己的矛盾**(这才是 8 小时的根):§2 曾写 epoch = 「自 1970-01-01 00:00:00 **UTC** +起的秒数」,而 §15 的网络校时协议字段写的是「**本地** Unix 时间戳,与电脑显示一致」——两处口径不同,代码 +按后者走。现在 §2 已改成「本地时间戳」口径。 + +**为什么选本地**:日志时标、UI 显示、FAT 文件时间三者都是**给现场电气工程师读的**,跟装置所在地钟面一致 +才没有解释成本;装置不存时区、不做夏令时,也就没有「时区参数配错」这一类故障。 + +⚠ **本裁定没有回答的部分(送检前必须单独定)**:104 **上行**时标 `CP56Time2a` 按标准应发 **UTC**,而现在 +`iec104_epoch_ms_to_cp56()` 是把内部值**直接按 UTC 分解**发出(即发出去的是本地钟面值)。两条出路: +① 上行前 **−时区偏移转 UTC**(标准符合,但要给装置一个时区常量,与「不存时区」相冲);② 维持现状发本地 +值(现场与主站同时区时看着正确,**第三方主站按 UTC 解读会差 8 小时**)。**在送检/第三方主站验收方向定下来 +之前,本仓不改 `iec104_asdu.c` 的分解逻辑**——它不是 bug 修复,是产品对外语义决定。 + --- ## 2. 角色与依赖 @@ -30,7 +49,7 @@ |------|--------|------| | 硬件实时钟(RTC) | SD2506API-G | 温补晶振,带备份电池,掉电后继续走时;I2C1(PB6-SCL/PB7-SDA) | | 系统节拍 | `HAL_GetTick()` | 毫秒级计数,HAL 时基由 TIM7 提供(168MHz 主频派生,精度远高于走时需求) | -| Unix 时间戳 | 本模块计算 | 自 1970-01-01 00:00:00 UTC 起的秒数(`uint32_t`) | +| Unix 时间戳 | 本模块计算 | **本地时间戳**:把本地钟面**按 UTC 记法**数出来的秒数(`uint32_t`),**内部不加任何时区偏移**(口径见 §1.1) | | 时间结构 | `sd2506_time_t` | SD2506 驱动定义,`year` 为**完整年份(2000~2099)**,含 `week` | **调用顺序(见 `Src/main.c`):** @@ -281,7 +300,27 @@ uint32_t now = sys_clock_get(); /* Unix 秒,可用于 FatFS 文件时间、 | 时间范围 | 受 `uint32_t` 限制至 2038 | 改用 `uint64_t`/`time_t` 扩展 | | 运行期校准 | 30 天惰性再基准化已实现(§7):纯自我参照、零 I2C;**有意不**周期性重读 RTC 重锚——RTC 漂移(±20ppm ≈ ±1.7 s/天)远大于 Tick 漂移,重锚反而降低精度;时间纠偏由时间同步(§15)负责 | | 网络校时 | 已实现(PC 推送 / TCP,见第 15 节) | SNTP/NTP 公网校时仍可作为后续扩展 | -| 时区 | 按 RTC 本地时间处理 | 如需 UTC/时区,可在 API 层增加偏移参数 | +| 时区 | **口径已定:内部一律本地钟面,不存时区**(用户裁定 2026-10-27,见 §1.1) | 「API 层加偏移参数」这条旧建议**作废**;唯一待决的是 104 上行是否要转 UTC(§1.1 末,送检前定) | + +### 13.1 用户 SRAM 的占用分配(共享资源,用之前先在这里登记) + +SD2506 有 **70 字节电池保持用户 SRAM**(寄存器 `2CH~71H`,`Drivers/BSP/SD2506/sd2506.h` +的 `SD2506_REG_SRAM_START/END`)。它是全仓唯一"零 NAND 磨损、零卷锁、跨掉电保持"的 +小介质,**但没有任何机制防止两个消费者写同一段**——所以占用只能靠本表登记, +新占用请在此加一行再动代码(偏移从低往高排,已用段永不复用)。 + +| 偏移(SRAM 内) | 寄存器 | 长度 | 属主与内容 | 失效判读 | +|---|---|---|---|---| +| 0..7 | `2CH~33H` | 8 B | `App/util/boot_stats.c`:上电次数账本(magic 2B + 计数 4B + 格式版本 1B + 校验 1B) | 换电池 / 电池欠压 ⇒ 整段丢失属**正常降级**,故口径是「自 RTC 电池周期起」,不是终身次数 | +| 8..69 | `34H~71H` | 62 B | **空闲**(日后新占用从这里起,别在 0..7 里挤) | — | + +- 为什么这一格归 RTC 而不是文件系统:上电次数每上电才变一次,落 NAND 要么每次开机 + 一次 FTL 写、要么做成周期近似写;两者都不如 8 字节 I2C 写干净。写点在**内核启动前** + (`App/app/app_main.c` 紧接 `sd2506_init()`),所以既不占喂狗点、也不碰卷锁。 +- ⚠ 同一条 I2C1 总线还挂着别的器件:**不要把这里的读写搬进任何关键链或 ISR**, + 本表的属主全部只在启动段单线程访问一次。 +- 机理与四态判读(不可信 / 首次启用 / 延续 / 电池域掉过电)的唯一出处是 + `App/util/boot_stats.h` 的文件头与函数头,本文不复制第二份。 --- @@ -292,7 +331,8 @@ uint32_t now = sys_clock_get(); /* Unix 秒,可用于 FatFS 文件时间、 | `App/util/sys_clock.c` | 墙钟维护实现(基准法、换算、校时宏) | | `App/util/sys_clock.h` | 对外 API 声明 | | `Drivers/BSP/SD2506/sd2506.c` / `.h` | SD2506 RTC I2C 驱动(`sd2506_get_time` / `sd2506_set_time`) | -| `App/app/app_main.c` | 初始化调用顺序:`sd2506_init()` → `sys_clock_init()` | +| `App/app/app_main.c` | 初始化调用顺序:`sd2506_init()` → `boot_stats_init()`(用户 SRAM 上电账本,§13.1)→ `sys_clock_init()` | +| `App/util/boot_stats.c` / `.h` | 用户 SRAM 上电账本 + 本次开机复位原因快照(占用见 §13.1;单测 `test/host/boot_stats/`) | | `App/sync/time_sync.c` / `time_sync.h` | 时间同步实现(TCP Server,`timeSyncTask`) | | `test/host/time_sync_server.py` | PC 端时间推送客户端(TCP) | @@ -384,6 +424,7 @@ PC 端 `python test/host/time_sync_server.py` 每 `interval` 秒推送一次,e | 日期 | 变更 | |------|------| +| 2026-10-27 | **时基口径定稿:内部一律本地钟面(用户裁定「时基先本地为准」)**:新增 §1.1;§2 的 epoch 行原写「自 1970 UTC 起」与 §15 网络校时字段「本地 Unix 时间戳」互相矛盾(代码按后者走),现统一为本地口径;§13 时区行的旧建议「API 层加偏移参数」作废。**未决**:104 上行 `CP56Time2a` 现把内部值直接按 UTC 分解发出(= 发的是本地钟面值),要不要上行前转 UTC 属产品对外语义决定,送检前单独裁定,本轮不改 `iec104_asdu.c` | | 2026-09-15 | v1.3:① `sys_clock_get` 30 天惰性再基准化——修复 49.7 天墙钟回绕(§3/§7/§12);② `sys_clock_init` 检查 RTC 读返回值/写回复验,失败降级为默认时间基准并返回 -1——激活"时钟芯片异常"自检(§5/§6/§9);③ 修正未初始化哨兵判断(§12-6);④ 时间同步任务启动/Socket 占用描述同步为事件屏障 + 单连接实现(§15);⑤ 新增日志串用 ASCII——本机 ARMCC 按 CP936 解读无 BOM 源文件,奇数汉字字符串会吞掉结束引号致编译失败(见 AGENTS.md 编码铁律) | *文档依据 `App/util/sys_clock.c` / `App/sync/time_sync.c` 实现与《嵌入式C语言代码规范_V1.0》整理。*