Files
STM32F4-Base/App/sys_clock.c
2026-08-24 16:16:18 +08:00

264 lines
8.5 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称System Clock — Wall Clock Time Maintenance
* 模块功能:实现系统墙钟时间维护,包括 RTC 初始读取、HAL Tick 漂移维持
* 适用平台STM32F407ZGTx (Cortex-M4 FPU, 168MHz)
* 作者:王建锋
* 创建日期2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#include "sys_clock.h"
#include "main.h"
#include <string.h>
#include <stdio.h>
#define DBG_TAG "[CLK]"
#include "dbg_log.h"
/* ============================================================
* RTC 校时(开发期一次性)
* 使能 RTC_DEFAULT_INIT_ENABLE 后:
* RTC_DEFAULT_INIT_FORCE=1每次启动都写入默认时间首次/覆盖校时用)
* RTC_DEFAULT_INIT_FORCE=0仅当 RTC 时间无效(越界)时写入
* 校时完成后建议将 FORCE 置 0或注释 ENABLE避免每次开机覆盖用户设的时间。
* 修改日期/时间时同步更新下列宏。
* ============================================================ */
#define RTC_DEFAULT_INIT_ENABLE 1
#define RTC_DEFAULT_INIT_FORCE 1
#define RTC_DEFAULT_YEAR 2026
#define RTC_DEFAULT_MONTH 8
#define RTC_DEFAULT_DAY 24
#define RTC_DEFAULT_HOUR 0
#define RTC_DEFAULT_MINUTE 0
#define RTC_DEFAULT_SECOND 0
/* ============================================================
* 内部状态:系统墙钟
* ============================================================ */
static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准 */
static volatile uint32_t s_base_unix_secs = UINT32_MAX;
/* ============================================================
* 内部辅助函数Unix time ↔ sd2506_time_t 转换
* ============================================================ */
/* 计算星期Zeller 公式0=Sunday与下方 unix_to_sd2506 中算法一致 */
static uint8_t rtc_calc_week(uint16_t y, uint8_t m, uint8_t d)
{
uint32_t wy = y;
uint32_t wm = m;
if (wm <= 2) {
wy--;
wm += 12u;
}
return (uint8_t)(((uint32_t)(wy % 100u + wy / 100u / 4u - wy / 100u / 15u) +
(uint32_t)((26u * (wm + 1u)) / 10u) + (uint32_t)(d % 100u)) % 7u);
}
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
{
uint32_t y, m, d;
uint8_t h, mi, s;
int is_leap;
/* 时分秒 */
{
uint64_t total = (uint64_t)unix_secs;
s = (uint8_t)(total % 60uLL);
total /= 60uLL;
mi = (uint8_t)(total % 60uLL);
total /= 60uLL;
h = (uint8_t)(total % 24uL);
}
/* 年月日 */
{
uint64_t day_count = ((uint64_t)unix_secs / 86400uL);
y = 1970u;
while (day_count >= 365uLL) {
uint64_t diy = 365uLL;
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {
diy = 366uLL;
}
if (day_count < diy) break;
day_count -= diy;
y++;
}
is_leap = ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0));
uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (is_leap) mdays[1] = 29;
m = 0;
while (m < 12u) {
if (day_count < (uint64_t)mdays[m]) break;
day_count -= mdays[m];
m++;
}
d = (uint32_t)(day_count + 1uL);
/* 星期Zeller's congruence0=Sunday */
uint64_t wy = y;
uint64_t wm = m;
if (wm <= 2) {
wy--;
wm += 12uL;
}
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;
/* SD2506 year 字段为 0~99+2000 */
uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000);
p_sd_time->year = sd_year;
p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */
p_sd_time->day = d;
p_sd_time->hour = h;
p_sd_time->minute = mi;
p_sd_time->second = s;
p_sd_time->week = week;
}
}
/* ============================================================
* 系统墙钟时间维护
* ============================================================ */
int sys_clock_init(void)
{
sd2506_time_t sd_time;
uint32_t year, month, day, hour, minute, second;
int64_t unix_secs = 0;
int i;
/* Step 1: 从 SD2506 RTC 读取初始时间
* 注意sd2506_get_time() 返回的 year 已是完整年份(如 2026驱动内 +2000
* 此处不得再加 2000否则年份虚增 → unix 秒数超 uint32 被截断回绕。 */
sd2506_get_time(&sd_time);
/* RTC 校时(可选):时间无效时,或 FORCE=1 强制时,写入编译期默认时间 */
#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; /* 强制每次启动校时 */
#endif
if (rtc_invalid) {
sd2506_time_t init_t;
init_t.year = RTC_DEFAULT_YEAR;
init_t.month = RTC_DEFAULT_MONTH;
init_t.day = RTC_DEFAULT_DAY;
init_t.hour = RTC_DEFAULT_HOUR;
init_t.minute = RTC_DEFAULT_MINUTE;
init_t.second = RTC_DEFAULT_SECOND;
init_t.week = rtc_calc_week(init_t.year, init_t.month, init_t.day);
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); /* 重新读取校准后的时间 */
} else {
DBG_ERROR("sys_clock_init: RTC default write FAIL");
}
}
}
#endif
year = (uint32_t)sd_time.year;
month = (uint32_t)sd_time.month;
day = (uint32_t)sd_time.day;
hour = (uint32_t)sd_time.hour;
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 */
{
uint64_t total_days = 0;
for (i = 1970; i < year; i++) {
if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) {
total_days += 366uLL;
} else {
total_days += 365uLL;
}
}
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;
for (i = 0; i < month - 1; i++) {
total_days += mdays[i];
}
total_days += day - 1uL;
unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second;
}
/* Step 3: 设置系统墙钟基准 */
s_base_tick = HAL_GetTick();
s_base_unix_secs = (uint32_t)unix_secs;
DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu",
(unsigned long)s_base_unix_secs, (unsigned long)s_base_tick);
return 0;
}
uint32_t sys_clock_get(void)
{
uint32_t now_tick = HAL_GetTick();
int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL);
if (unix_secs < 0) {
return UINT32_MAX; /* 未初始化 */
}
return (uint32_t)unix_secs;
}
char *sys_clock_get_str(char *buf, size_t buf_size)
{
uint32_t unix_secs = sys_clock_get();
if (unix_secs == UINT32_MAX) {
snprintf(buf, buf_size, "----/--/-- --:--:--");
return buf;
}
sd2506_time_t sd;
unix_to_sd2506(unix_secs, &sd);
snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d",
sd.year + 2000uL, sd.month, sd.day,
sd.hour, sd.minute, sd.second);
return buf;
}
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
{
/* Step 1: 更新墙钟基准 */
s_base_tick = HAL_GetTick();
s_base_unix_secs = unix_secs;
DBG_INFO("sys_clock_set: wall clock updated to %lu", (unsigned long)unix_secs);
/* Step 2: 写回 SD2506 RTC若提供了 sd_time */
if (sd_time != NULL) {
sd2506_set_time(sd_time);
DBG_INFO("sys_clock_set: SD2506 RTC written");
}
return 0;
}