#include "gd32h7xx.h" #include "app_rtc/app_rtc.h" #include "publicdata/publicdata.h" #include #include /* 秒换算常量(用于时间戳转换函数)。 */ #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 默认使用 LXTAL(32.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=输出 UTC,1=输出北京时间(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; }