测试:新增 sys_clock 主机侧单测并修复 Zeller 星期缺陷

This commit is contained in:
2026-10-02 11:40:35 +08:00
parent 9f52731ed8
commit fac9d1c409
24 changed files with 5584 additions and 3792 deletions
+5 -7
View File
@@ -6,11 +6,14 @@
| 文件 | 作用 |
|---|---|
| `crc.c/h` | 校验算法(CRC16/CRC32 等,供 RS-485 协议与数据校验使用) |
| `ringbuf.c/h` | 环形缓冲(SPSC 无锁环形队列,用于串口/数据流收发) |
| `sys_clock.c/h` | **墙钟时间管理**:RTC 基准、tick 回绕防护、每 30 天惰性再基准化 |
| `task_watchdog.c/h` | **独立看门狗(IWDG)判定状态机**:任务心跳位图 + defaultTask 单一喂狗者 |
> **已删除(2026-10-02)**:`crc.c/h`(CRC-16 Modbus / CRC-8)与 `ringbuf.c/h`(SPSC 环形缓冲)
> 两模块**零调用者**且未编入任何构建——CRC 唯一使用点 `net_cfg_legacy.c` 自带静态
> `s_crc16_modbus()`,环形缓冲由 `App/log/console_log.c` 内联实现(`s_con_ring`)。
> 保留只会造成"有统一实现"的误导,故整体移除(详见 `docs/待处理内容.md` E-15 销项)。
## 设计要点
### sys_clock
@@ -20,11 +23,6 @@
- RTC 故障时降级到默认时间基准并返回 -1,触发"时钟芯片异常"自检上报
- 设计详见 `docs/子系统设计/系统墙钟时间维护说明.md`
### ringbuf
- 单生产者单消费者(SPSC)无锁设计,配合 DMA/中断收发
- 容量为 2 的幂,用掩码取模(避免除法开销)
### task_watchdog
- **任务心跳位图**:各任务只置位(`task_wd_beat`),校验者取走并清零(单字原子,无锁)
-120
View File
@@ -1,120 +0,0 @@
/*
* 模块名称:CRC Utility
* 模块功能:CRC-16 Modbus (poly=0x8005, init=0xFFFF, refin=true, refout=true, xorout=0x0000)
* CRC-8 (poly=0x07, init=0x00, refin=false, refout=false, xorout=0x00)
* 适用平台:通用嵌入式平台
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#include "crc.h"
static const uint16_t s_crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040,
};
static const uint8_t s_crc8_table[256] = {
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3,
};
/*
* 函数功能:计算 CRC-16 Modbus
* 入口参数:data - 数据指针
* len - 数据长度
* 返 回 值:16 位 CRC 值
* 限定条件:data 为非空指针
* 函数说明:多项式 0x8005,初始值 0xFFFF,结果异或 0x0000
*/
uint16_t crc16_modbus(const uint8_t *data, size_t len) {
uint16_t crc = 0xFFFF;
size_t i;
for (i = 0; i < len; i++) {
crc = (crc >> 8) ^ s_crc16_table[(crc ^ data[i]) & 0xFF];
}
return crc;
}
/*
* 函数功能:计算 CRC-8
* 入口参数:data - 数据指针
* len - 数据长度
* 返 回 值:8 位 CRC 值
* 限定条件:data 为非空指针
* 函数说明:多项式 0x07,初始值 0x00
*/
uint8_t crc8(const uint8_t *data, size_t len) {
uint8_t crc = 0;
size_t i;
for (i = 0; i < len; i++) {
crc = s_crc8_table[crc ^ data[i]];
}
return crc;
}
-28
View File
@@ -1,28 +0,0 @@
/*
* 模块名称:CRC Utility
* 模块功能:CRC-16 Modbus & CRC-8 查表计算
* 适用平台:通用嵌入式平台
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#ifndef CRC_H
#define CRC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
uint16_t crc16_modbus(const uint8_t *data, size_t len);
uint8_t crc8(const uint8_t *data, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* CRC_H */
-154
View File
@@ -1,154 +0,0 @@
/*
* 模块名称:Ring Buffer
* 模块功能:字节环形缓冲区实现,head == tail 为空,head + 1 == tail 为满
* 适用平台:通用嵌入式平台
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#include "ringbuf.h"
/*
* 函数功能:初始化环形缓冲区
* 入口参数:rb - 缓冲区控制块指针
* pool - 存储池指针
* size - 存储池大小(字节)
* 返 回 值:无
* 限定条件:rb 和 pool 均为非空指针
* 函数说明:size 保留一个空位用于区分空/满状态
*/
void ringbuf_init(ringbuf_t *rb, uint8_t *pool, size_t size) {
rb->buf = pool;
rb->size = size;
rb->head = 0;
rb->tail = 0;
}
/*
* 函数功能:写入数据到环形缓冲区
* 入口参数:rb - 缓冲区控制块指针
* data - 源数据指针
* len - 待写入长度
* 返 回 值:实际写入字节数
* 限定条件:rb 和 data 均为非空指针
* 函数说明:空间不足时写入能容纳的最大字节数
*/
size_t ringbuf_put(ringbuf_t *rb, const uint8_t *data, size_t len) {
size_t cap = 0;
size_t n = 0;
size_t i = 0;
cap = rb->size - 1 - ringbuf_avail(rb);
n = (len < cap) ? len : cap;
for (i = 0; i < n; i++) {
rb->buf[rb->head] = data[i];
rb->head = (rb->head + 1) % rb->size;
}
return n;
}
/*
* 函数功能:从环形缓冲区读取数据
* 入口参数:rb - 缓冲区控制块指针
* data - 目标数据指针
* len - 期望读取长度
* 返 回 值:实际读取字节数
* 限定条件:rb 和 data 均为非空指针
* 函数说明:读取后数据从缓冲区移除
*/
size_t ringbuf_get(ringbuf_t *rb, uint8_t *data, size_t len) {
size_t n = 0;
size_t i = 0;
n = ringbuf_avail(rb);
n = (n < len) ? n : len;
for (i = 0; i < n; i++) {
data[i] = rb->buf[rb->tail];
rb->tail = (rb->tail + 1) % rb->size;
}
return n;
}
/*
* 函数功能:预读取数据(不移除)
* 入口参数:rb - 缓冲区控制块指针
* data - 目标数据指针
* len - 期望读取长度
* 返 回 值:实际可读取字节数
* 限定条件:rb 和 data 均为非空指针
* 函数说明:与 ringbuf_get 的区别是不移动 tail 指针
*/
size_t ringbuf_peek(const ringbuf_t *rb, uint8_t *data, size_t len) {
size_t n = 0;
size_t i = 0;
n = ringbuf_avail(rb);
n = (n < len) ? n : len;
for (i = 0; i < n; i++) {
size_t idx = (rb->tail + i) % rb->size;
data[i] = rb->buf[idx];
}
return n;
}
/*
* 函数功能:获取缓冲区中有效数据长度
* 入口参数:rb - 缓冲区控制块指针
* 返 回 值:有效数据字节数
* 限定条件:rb 为非空指针
*/
size_t ringbuf_avail(const ringbuf_t *rb) {
if (rb->head >= rb->tail) {
return rb->head - rb->tail;
}
return rb->size - rb->tail + rb->head;
}
/*
* 函数功能:获取缓冲区剩余空间
* 入口参数:rb - 缓冲区控制块指针
* 返 回 值:剩余空间字节数(保留一个空位)
* 限定条件:rb 为非空指针
*/
size_t ringbuf_space(const ringbuf_t *rb) {
return rb->size - 1 - ringbuf_avail(rb);
}
/*
* 函数功能:判断缓冲区是否为空
* 入口参数:rb - 缓冲区控制块指针
* 返 回 值:1 - 空,0 - 非空
* 限定条件:rb 为非空指针
*/
int ringbuf_is_empty(const ringbuf_t *rb) {
return (rb->head == rb->tail) ? 1 : 0;
}
/*
* 函数功能:判断缓冲区是否为满
* 入口参数:rb - 缓冲区控制块指针
* 返 回 值:1 - 满,0 - 非满
* 限定条件:rb 为非空指针
*/
int ringbuf_is_full(const ringbuf_t *rb) {
return (ringbuf_avail(rb) == rb->size - 1) ? 1 : 0;
}
/*
* 函数功能:重置缓冲区
* 入口参数:rb - 缓冲区控制块指针
* 返 回 值:无
* 限定条件:rb 为非空指针
*/
void ringbuf_reset(ringbuf_t *rb) {
rb->head = 0;
rb->tail = 0;
}
-42
View File
@@ -1,42 +0,0 @@
/*
* 模块名称:Ring Buffer
* 模块功能:线程安全的字节环形缓冲区(单生产者-单消费者模型)
* 适用平台:通用嵌入式平台
* 作者:王建锋
* 创建日期:2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#ifndef RINGBUF_H
#define RINGBUF_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stddef.h>
typedef struct {
uint8_t *buf;
size_t size;
size_t head;
size_t tail;
} ringbuf_t;
void ringbuf_init(ringbuf_t *rb, uint8_t *pool, size_t size);
size_t ringbuf_put(ringbuf_t *rb, const uint8_t *data, size_t len);
size_t ringbuf_get(ringbuf_t *rb, uint8_t *data, size_t len);
size_t ringbuf_peek(const ringbuf_t *rb, uint8_t *data, size_t len);
size_t ringbuf_avail(const ringbuf_t *rb);
size_t ringbuf_space(const ringbuf_t *rb);
int ringbuf_is_empty(const ringbuf_t *rb);
int ringbuf_is_full(const ringbuf_t *rb);
void ringbuf_reset(ringbuf_t *rb);
#ifdef __cplusplus
}
#endif
#endif /* RINGBUF_H */
+29 -19
View File
@@ -62,24 +62,41 @@ static volatile uint32_t s_base_unix_secs = UINT32_MAX;
/*
* 函数功能:根据公历日期计算星期几
* 入口参数:y - 年份 uint16_t 1970~2099
* m - 月份 uint8_t 1~12
* m - 月份 uint8_t 1~12(**1-based**,与 sd2506_time_t.month 一致)
* d - 日 uint8_t 1~31
* 返回值:星期编号 uint8_t 0=Sunday, 1=Monday, ... 6=Saturday
* 限定条件:输入为公历合法日期
* 函数说明:1. 采用 Zeller 公式(变种),结果 0 表示周日
* 2. 与 unix_to_sd2506() 内星期算法保持一致
* 函数说明:1. 采用 Zeller 同余式(Gregorian):
* h = (d + ⌊13(m+1)/5⌋ + K + ⌊K/4⌋ + ⌊J/4⌋ + 5J) mod 7
* 其中 mm 为调整月(1/2 月按上年 13/14 月处理),
* K = wy % 100,J = wy / 100;h: 0=Saturday, 1=Sunday, ...
* 结果映射 (h+6) mod 7 → 0=Sunday(sd2506 约定)。
* 注:5J ≡ -2J (mod 7),取 5J 以保证各项非负(无符号安全)。
* 2. 与 unix_to_sd2506() 内星期算法保持一致(后者按 0-based 月
* 调用本函数前 +1,切勿再传 0-based 月)。
* 修改记录:
* 2026-10-02 修正:原式错用 (wy%100 + wy/100/4 - wy/100/15)(缺 K/4、
* 5J/世纪项,且把 1-based 月当 2-based 用),实测 1970~2099
* 全部日期星期皆错(主机侧单测 TC-CLK-014/019 发现)。
*/
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d) {
uint32_t wy = y;
uint32_t wm = m;
uint32_t wm = m; /* wm: 1-based 月,1/2 月上调为 13/14 月 */
if (wm <= 2) {
if (wm <= 2u) {
wy--;
wm += 12u;
}
/* Zeller 公式:世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
(uint32_t)((26u * (wm + 1u)) / 10u) + (uint32_t)(d % 100u)) % 7u);
{
uint32_t K = wy % 100u;
uint32_t J = wy / 100u;
uint32_t h;
/* 各项均非负:d + 月项 + K + K/4 + J/4 + 5J,模 7 得 0=Saturday */
h = (uint32_t)(d + (13u * (wm + 1u)) / 5u + K + K / 4u + J / 4u + 5u * J) % 7u;
/* h:0=Sat,1=Sun,... → 0=Sun 约定 */
return (uint8_t)((h + 6u) % 7u);
}
}
/*
@@ -136,17 +153,10 @@ static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time) {
}
d = (uint32_t)(day_count + 1uL);
/* 星期(Zeller's congruence,0=Sunday) */
uint64_t wy = y;
uint64_t wm = m;
if (wm <= 2) {
wy--;
wm += 12uL;
}
/* Zeller 公式同 rtc_calc_week():世纪项 + 月项 + 日项,模 7 得星期(0=周日) */
uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
(uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
(uint32_t)(d % 100uLL)) % 7uLL;
/* 星期:复用 rtc_calc_week()(1-based 月;此处 m 为 0-based → 传 m+1)
* 2026-10-02 修正:原内联变种公式有误(与 rtc_calc_week 同缺陷),
* 统一收敛到 rtc_calc_week,避免两处算法再次发散。 */
uint8_t week = rtc_calc_week((uint16_t)y, (uint8_t)(m + 1u), (uint8_t)d);
/* sd2506_time_t.year 采用完整年份(如 2026),与 sd2506_set_time
* 中 year-2000、sd2506_get_time 中 +2000 的约定保持一致;