Files

485 lines
14 KiB
C
Raw Permalink 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.
#include "gd32h7xx.h"
#include "app_rtc/app_rtc.h"
#include "publicdata/publicdata.h"
#include <stdio.h>
#include <string.h>
/* 秒换算常量(用于时间戳转换函数)。 */
#define xMINUTE (60U)
#define xHOUR (60U * xMINUTE)
#define xDAY (24U * xHOUR)
#define xYEAR (365U * xDAY)
/* 备份寄存器标记值:用于判断 RTC 是否已经做过首次配置。 */
#define BKP_VALUE 0x32F0U
/* RTC 首次默认时间(未配置/备份域失效时使用,BCD 格式)。 */
#define RTC_DEFAULT_YEAR_BCD 0x26U
#define RTC_DEFAULT_MONTH_BCD 0x01U
#define RTC_DEFAULT_DAY_BCD 0x01U
#define RTC_DEFAULT_HOUR_BCD 0x12U
#define RTC_DEFAULT_MINUTE_BCD 0x00U
#define RTC_DEFAULT_SECOND_BCD 0x00U
#define RTC_DEFAULT_WEEKDAY 0x04U /* 1=周一 ... 7=周日;2026-01-01 为周四 */
/* RTC 预分频配置(LXTAL=32.768kHz 的标准配置)。 */
static __IO uint32_t s_prescaler_a = 0U;
static __IO uint32_t s_prescaler_s = 0U;
/* RTC 时钟源选择标志(来自 RCU_BDCTL[9:8])。 */
static uint32_t s_rtcsrc_flag = 0U;
/* 前置声明:RTC 初始化与校验辅助函数。 */
static void rtc_pre_config(void);
static void rtc_setup_default_if_needed(void);
static uint8_t rtc_is_leap_year_u16(uint16_t year);
static uint8_t rtc_days_in_month_u16(uint16_t year, uint8_t month);
static uint8_t rtc_calc_weekday(uint16_t year, uint8_t month, uint8_t day);
static uint8_t rtc_validate_comm_time(const Comm_Time *time);
/**
* @brief RTC 模块初始化入口。
* @details
* - 打开 PMU 与备份域写保护;
* - 选择并配置 RTC 时钟源(当前默认 LXTAL);
* - 首次上电或备份标记缺失时写入默认时间。
*/
void v_rtc_init(void)
{
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
rtc_tamper_disable(RTC_TAMPER0);
s_rtcsrc_flag = GET_BITS(RCU_BDCTL, 8, 9);
rtc_pre_config();
rtc_setup_default_if_needed();
}
/**
* @brief 配置 RTC 时钟源与预分频。
* @details 默认使用 LXTAL32.768kHz),对应 A=127/S=255。
*/
static void rtc_pre_config(void)
{
rcu_osci_on(RCU_LXTAL);
(void)rcu_osci_stab_wait(RCU_LXTAL);
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
s_prescaler_s = 0xFFU;
s_prescaler_a = 0x7FU;
rcu_periph_clock_enable(RCU_RTC);
(void)rtc_register_sync_wait();
}
/**
* @brief 首次配置 RTC 默认时间并写入备份标记。
*/
static void rtc_setup_default_if_needed(void)
{
rtc_parameter_struct rtc_initpara;
if ((RTC_BKP0 == BKP_VALUE) && (s_rtcsrc_flag != 0x00U)) {
return;
}
memset(&rtc_initpara, 0, sizeof(rtc_parameter_struct));
rtc_initpara.year = RTC_DEFAULT_YEAR_BCD;
rtc_initpara.day_of_week = RTC_DEFAULT_WEEKDAY;
rtc_initpara.month = RTC_DEFAULT_MONTH_BCD;
rtc_initpara.date = RTC_DEFAULT_DAY_BCD;
rtc_initpara.factor_asyn = s_prescaler_a;
rtc_initpara.factor_syn = s_prescaler_s;
rtc_initpara.display_format = RTC_24HOUR;
rtc_initpara.am_pm = RTC_AM;
rtc_initpara.hour = RTC_DEFAULT_HOUR_BCD;
rtc_initpara.minute = RTC_DEFAULT_MINUTE_BCD;
rtc_initpara.second = RTC_DEFAULT_SECOND_BCD;
if (rtc_init(&rtc_initpara) == SUCCESS) {
RTC_BKP0 = BKP_VALUE;
}
rcu_all_reset_flag_clear();
}
/**
* @brief 判断闰年(完整年份,如 2026)。
*/
static uint8_t rtc_is_leap_year_u16(uint16_t year)
{
return (uint8_t)(((year % 4U == 0U) && (year % 100U != 0U)) || (year % 400U == 0U));
}
/**
* @brief 获取指定年月的天数。
*/
static uint8_t rtc_days_in_month_u16(uint16_t year, uint8_t month)
{
static const uint8_t days_in_month[] = {31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};
if ((month < 1U) || (month > 12U)) {
return 0U;
}
if ((month == 2U) && (rtc_is_leap_year_u16(year) != 0U)) {
return 29U;
}
return days_in_month[month - 1U];
}
/**
* @brief 计算星期(返回 1~7,周一~周日)。
*/
static uint8_t rtc_calc_weekday(uint16_t year, uint8_t month, uint8_t day)
{
static const uint8_t t[] = {0U, 3U, 2U, 5U, 0U, 3U, 5U, 1U, 4U, 6U, 2U, 4U};
uint16_t y = year;
uint8_t w;
if (month < 3U) {
y--;
}
w = (uint8_t)((y + y / 4U - y / 100U + y / 400U + t[month - 1U] + day) % 7U);
return (w == 0U) ? 7U : w;
}
/**
* @brief 校验 Comm_Time 时间合法性。
*/
static uint8_t rtc_validate_comm_time(const Comm_Time *time)
{
uint8_t max_day;
if (time == NULL) {
return 0U;
}
if ((time->iYear < 2000U) || (time->iYear > 2099U)) {
return 0U;
}
if ((time->ucMonth < 1U) || (time->ucMonth > 12U)) {
return 0U;
}
max_day = rtc_days_in_month_u16(time->iYear, time->ucMonth);
if ((time->ucDay < 1U) || (time->ucDay > max_day)) {
return 0U;
}
if ((time->ucHour > 23U) || (time->ucMin > 59U) || (time->ucSec > 59U)) {
return 0U;
}
return 1U;
}
/**
* @brief 读取 RTC 当前时间(转换到 Comm_Time)。
*/
void GetCurrentTime(Comm_Time *curTime)
{
rtc_parameter_struct rtc_initpara;
if (curTime == NULL) {
return;
}
memset(&rtc_initpara, 0, sizeof(rtc_parameter_struct));
rtc_current_time_get(&rtc_initpara);
curTime->iYear = (U16_T)(2000U + bcd_to_dec(rtc_initpara.year));
curTime->ucMonth = bcd_to_dec(rtc_initpara.month);
curTime->ucDay = bcd_to_dec(rtc_initpara.date);
curTime->ucHour = bcd_to_dec(rtc_initpara.hour);
curTime->ucMin = bcd_to_dec(rtc_initpara.minute);
curTime->ucSec = bcd_to_dec(rtc_initpara.second);
}
/**
* @brief 设置 RTC 时间(输入为 Comm_Time)。
*/
void v_rtc_set_time(Comm_Time *curTime)
{
uint8_t weekday;
rtc_parameter_struct rtc_initpara;
if (rtc_validate_comm_time(curTime) == 0U) {
return;
}
/* Ensure backup domain write access is enabled before touching RTC registers. */
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
weekday = rtc_calc_weekday(curTime->iYear, curTime->ucMonth, curTime->ucDay);
memset(&rtc_initpara, 0, sizeof(rtc_initpara));
rtc_initpara.year = dec_to_bcd((uint8_t)(curTime->iYear - 2000U));
rtc_initpara.month = dec_to_bcd(curTime->ucMonth);
rtc_initpara.date = dec_to_bcd(curTime->ucDay);
rtc_initpara.day_of_week = weekday;
rtc_initpara.hour = dec_to_bcd(curTime->ucHour);
rtc_initpara.minute = dec_to_bcd(curTime->ucMin);
rtc_initpara.second = dec_to_bcd(curTime->ucSec);
rtc_initpara.display_format = RTC_24HOUR;
rtc_initpara.am_pm = RTC_AM;
rtc_initpara.factor_asyn = s_prescaler_a;
rtc_initpara.factor_syn = s_prescaler_s;
if (rtc_init(&rtc_initpara) != SUCCESS) {
return;
}
(void)rtc_register_sync_wait();
}
/**
* @brief 本地时间(UTC+8)转 Unix 秒(UTC)。
*/
unsigned int xDate2Seconds(Comm_Time *time)
{
static const unsigned int month_sec[12] = {
0U * xDAY, 31U * xDAY, 59U * xDAY, 90U * xDAY, 120U * xDAY, 151U * xDAY,
181U * xDAY, 212U * xDAY, 243U * xDAY, 273U * xDAY, 304U * xDAY, 334U * xDAY
};
unsigned int year;
unsigned int seconds;
if ((time == NULL) || (rtc_validate_comm_time(time) == 0U) || (time->iYear < 1970U)) {
return 0U;
}
year = (unsigned int)time->iYear - 1970U;
seconds = xYEAR * year + xDAY * ((year + 1U) / 4U);
seconds += month_sec[time->ucMonth - 1U];
if ((time->ucMonth > 2U) && (((unsigned int)time->iYear % 4U) == 0U)) {
seconds += xDAY;
}
seconds += xDAY * ((unsigned int)time->ucDay - 1U);
seconds += xHOUR * (unsigned int)time->ucHour;
seconds += xMINUTE * (unsigned int)time->ucMin;
seconds += (unsigned int)time->ucSec;
if (seconds >= (8U * xHOUR)) {
seconds -= (8U * xHOUR);
}
return seconds;
}
/**
* @brief Unix 秒(UTC)转本地时间(UTC+8)。
*/
void xSeconds2Date(unsigned long seconds, Comm_Time *time)
{
static const unsigned int month_day[12] = {31U, 28U, 31U, 30U, 31U, 30U, 31U, 31U, 30U, 31U, 30U, 31U};
unsigned int days;
unsigned short leap_y_count;
if (time == NULL) {
return;
}
time->ucSec = (U8_T)(seconds % 60UL);
seconds /= 60UL;
time->ucMin = (U8_T)(seconds % 60UL);
seconds += 8UL * 60UL;
seconds /= 60UL;
time->ucHour = (U8_T)(seconds % 24UL);
days = (unsigned int)(seconds / 24UL);
leap_y_count = (unsigned short)((days + 365U) / 1461U);
if (((days + 366U) % 1461U) == 0U) {
time->iYear = (U16_T)(1970U + (days / 366U));
time->ucMonth = 12U;
time->ucDay = 31U;
return;
}
days -= leap_y_count;
time->iYear = (U16_T)(1970U + (days / 365U));
days %= 365U;
days += 1U;
if ((time->iYear % 4U) == 0U) {
if (days > 60U) {
--days;
} else if (days == 60U) {
time->ucMonth = 2U;
time->ucDay = 29U;
return;
}
}
for (time->ucMonth = 0U; month_day[time->ucMonth] < days; time->ucMonth++) {
days -= month_day[time->ucMonth];
}
time->ucMonth += 1U;
time->ucDay = (U8_T)days;
}
/**
* @brief 根据时区偏移调整时间(支持跨天/月/年)。
*/
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset)
{
const int minutes_per_day = 24 * 60;
int total_minutes;
int days_adjust;
int year;
U8_T month;
int day;
U8_T max_days;
if (ctTime == NULL) {
return;
}
total_minutes = (int)ctTime->ucHour * 60 + (int)ctTime->ucMin;
total_minutes += hour_offset * 60 + min_offset;
days_adjust = total_minutes / minutes_per_day;
total_minutes %= minutes_per_day;
if (total_minutes < 0) {
total_minutes += minutes_per_day;
days_adjust--;
}
ctTime->ucHour = (U8_T)(total_minutes / 60);
ctTime->ucMin = (U8_T)(total_minutes % 60);
if (days_adjust == 0) {
return;
}
year = (int)ctTime->iYear;
month = ctTime->ucMonth;
day = (int)ctTime->ucDay + days_adjust;
while (day > (max_days = rtc_days_in_month_u16((uint16_t)year, month))) {
day -= max_days;
month++;
if (month > 12U) {
month = 1U;
year++;
}
}
while (day < 1) {
month--;
if (month < 1U) {
month = 12U;
year--;
}
day += rtc_days_in_month_u16((uint16_t)year, month);
}
ctTime->ucDay = (U8_T)day;
ctTime->ucMonth = month;
ctTime->iYear = (U16_T)year;
}
/**
* @brief 解析 OCPP 时间戳字符串到 Comm_Time。
* @param ctTime 解析结果输出。
* @param time_str 输入字符串,支持 `Z` 与 `+/-HH:MM`。
* @param time_type 0=输出 UTC1=输出北京时间(UTC+8)。
* @return 0 成功,1 失败。
*/
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type)
{
char time_copy[64];
char *time_part;
int hour;
int min;
int sec;
int millisec = 0;
char tz_sign = 0;
int tz_hour = 0;
int tz_min = 0;
int parsed;
if ((ctTime == NULL) || (time_str == NULL) || (strlen(time_str) >= sizeof(time_copy))) {
return 1U;
}
strcpy(time_copy, time_str);
time_part = strchr(time_copy, 'T');
if (time_part == NULL) {
return 1U;
}
*time_part = '\0';
time_part++;
if (sscanf(time_copy, "%hu-%hhu-%hhu", &ctTime->iYear, &ctTime->ucMonth, &ctTime->ucDay) != 3) {
return 1U;
}
parsed = sscanf(time_part, "%d:%d:%d.%d%c%d:%d", &hour, &min, &sec, &millisec, &tz_sign, &tz_hour, &tz_min);
if (parsed != 7) {
parsed = sscanf(time_part, "%d:%d:%d.%dZ", &hour, &min, &sec, &millisec);
if (parsed == 4) {
tz_sign = 'Z';
} else {
parsed = sscanf(time_part, "%d:%d:%d%c%d:%d", &hour, &min, &sec, &tz_sign, &tz_hour, &tz_min);
if (parsed != 6) {
parsed = sscanf(time_part, "%d:%d:%dZ", &hour, &min, &sec);
if (parsed == 3) {
tz_sign = 'Z';
} else {
return 1U;
}
}
}
}
ctTime->ucHour = (U8_T)hour;
ctTime->ucMin = (U8_T)min;
ctTime->ucSec = (U8_T)sec;
if (time_type == 1U) {
if ((tz_sign == 'Z') || (tz_sign == 0)) {
v_adjust_time_with_timezone(ctTime, 8, 0);
} else {
int off_h = (tz_sign == '+') ? tz_hour : -tz_hour;
int off_m = (tz_sign == '+') ? tz_min : -tz_min;
v_adjust_time_with_timezone(ctTime, -off_h, -off_m);
v_adjust_time_with_timezone(ctTime, 8, 0);
}
} else if ((tz_sign != 'Z') && (tz_sign != 0)) {
int off_h = (tz_sign == '+') ? tz_hour : -tz_hour;
int off_m = (tz_sign == '+') ? tz_min : -tz_min;
v_adjust_time_with_timezone(ctTime, -off_h, -off_m);
}
return (rtc_validate_comm_time(ctTime) != 0U) ? 0U : 1U;
}
/**
* @brief RTC 自测:读取当前时间并打印。
*/
void test_rtc(void)
{
Comm_Time now_time;
GetCurrentTime(&now_time);
printf("[RTC] now: %04u-%02u-%02u %02u:%02u:%02u\r\n",
now_time.iYear,
now_time.ucMonth,
now_time.ucDay,
now_time.ucHour,
now_time.ucMin,
now_time.ucSec);
}
//获取两个时间相差多少分钟,忽略年月日
int CalculateTimeDiffInMinutes(const Comm_Time time1, const Comm_Time time2) {
// 步骤1: 将时间转换为分钟数 (忽略秒)
int minutes1 = time1.ucHour * 60 + time1.ucMin;
int minutes2 = time2.ucHour * 60 + time2.ucMin;
// 步骤2: 计算绝对差值
int diff = abs(minutes1 - minutes2);
// 步骤3: 处理跨天情况 (一天共1440分钟)
if (diff > 720) { // 超过720分钟则跨天更短
diff = 1440 - diff;
}
return diff;
}