Initial commit: CCU621_M firmware project with BLE debug link support.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,485 @@
|
||||
#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 默认使用 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;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef APP_RTC_H
|
||||
#define APP_RTC_H
|
||||
|
||||
#ifdef __cplusplus /*C++编译环境下兼容C语言*/
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "main.h"
|
||||
#include "publicdata/type.h"
|
||||
|
||||
/* 对外接口函数声明 */
|
||||
void v_rtc_init(void);
|
||||
|
||||
/* app_rtc 兼容接口(对业务层使用 Comm_Time) */
|
||||
void GetCurrentTime(Comm_Time *curTime);
|
||||
void v_rtc_set_time(Comm_Time *curTime);
|
||||
|
||||
unsigned int xDate2Seconds(Comm_Time *time);
|
||||
void xSeconds2Date(unsigned long seconds, Comm_Time *time);
|
||||
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);
|
||||
void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);
|
||||
int CalculateTimeDiffInMinutes(const Comm_Time time1, const Comm_Time time2);
|
||||
void test_rtc(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* APP_RTC_H */
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "publicdata/type.h"
|
||||
|
||||
typedef unsigned char U8_T;
|
||||
typedef unsigned short U16_T;
|
||||
|
||||
|
||||
// 闰年检查
|
||||
static U8_T u8_is_leap_year(U16_T year)
|
||||
{
|
||||
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
|
||||
}
|
||||
|
||||
// 获取月份天数
|
||||
static U8_T u8_get_days_in_month(U16_T year, U8_T month)
|
||||
{
|
||||
static const U8_T days_in_month[] = {31,28,31,30,31,30,31,31,30,31,30,31};
|
||||
|
||||
if (month < 1 || month > 12) return 0;
|
||||
|
||||
U8_T days = days_in_month[month-1];
|
||||
|
||||
// 闰年二月特殊处理
|
||||
if (month == 2 && u8_is_leap_year(year)) {
|
||||
days = 29;
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
// 日期时间验证
|
||||
static U8_T u8_validate_datetime(const Comm_Time *ctTime)
|
||||
{
|
||||
// 年份范围检查
|
||||
if (ctTime->iYear < 2020 || ctTime->iYear > 2100) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 月份检查
|
||||
if (ctTime->ucMonth < 1 || ctTime->ucMonth > 12) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 日期检查
|
||||
U8_T max_days = u8_get_days_in_month(ctTime->iYear, ctTime->ucMonth);
|
||||
if (ctTime->ucDay < 1 || ctTime->ucDay > max_days) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 时间检查
|
||||
if (ctTime->ucHour > 23 || ctTime->ucMin > 59 || ctTime->ucSec > 59) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if 0 /* CCU621_M: duplicate of app_rtc.c — disabled to avoid L6200E */
|
||||
/*******************************************************************************
|
||||
* 名称: v_adjust_time_with_timezone
|
||||
* 功能: 按给定时区偏移量调整 Comm_Time 中的时刻与日期,用于 OCPP 时间戳与本地/UTC 转换。
|
||||
* 先将“时+分”与偏移量合并为总分钟数,归一化到 [0, 24*60) 并得到日进退数,
|
||||
* 再按日进退数正确跨月、跨年调整日期(含闰年与各月天数)。
|
||||
*
|
||||
* 参数:
|
||||
* ctTime - 输入输出,待调整的日期时间结构(会直接修改 ucHour/ucMin/ucDay/ucMonth/iYear)
|
||||
* hour_offset - 时区偏移小时数,可为负(如 UTC+8 传入 8,UTC-5 传入 -5)
|
||||
* min_offset - 时区偏移分钟数,可为负(如 +08:30 则 hour_offset=8, min_offset=30)
|
||||
*
|
||||
* 说明:
|
||||
* - 仅修改时、分、日、月、年;秒(ucSec)不参与计算也不改动。
|
||||
* - 日期跨月、跨年时按公历规则处理(含闰年二月 28/29 天)。
|
||||
* - 若调整后年份超出合理范围,由调用方 u8_validate_datetime() 等做校验或裁剪。
|
||||
******************************************************************************/
|
||||
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;
|
||||
}
|
||||
|
||||
/* 1) 将“当前时+分”与偏移量合并为总分钟数 */
|
||||
total_minutes = ctTime->ucHour * 60 + ctTime->ucMin;
|
||||
total_minutes += hour_offset * 60 + min_offset;
|
||||
|
||||
/* 2) 归一化到 [0, minutes_per_day),并得到需要进退的天数 */
|
||||
days_adjust = total_minutes / minutes_per_day;
|
||||
total_minutes = total_minutes % minutes_per_day;
|
||||
if (total_minutes < 0) {
|
||||
total_minutes += minutes_per_day;
|
||||
days_adjust--;
|
||||
}
|
||||
|
||||
/* 3) 写回时、分 */
|
||||
ctTime->ucHour = (U8_T)(total_minutes / 60);
|
||||
ctTime->ucMin = (U8_T)(total_minutes % 60);
|
||||
|
||||
/* 4) 按日进退数调整日期,并正确处理跨月、跨年 */
|
||||
if (days_adjust == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
year = (int)ctTime->iYear;
|
||||
month = ctTime->ucMonth;
|
||||
day = (int)ctTime->ucDay + days_adjust;
|
||||
|
||||
/* 向后推日期:day 超出当月则进入下月/下年 */
|
||||
while (day > (max_days = u8_get_days_in_month((U16_T)year, month))) {
|
||||
day -= max_days;
|
||||
month++;
|
||||
if (month > 12) {
|
||||
month = 1;
|
||||
year++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 向前推日期:day 小于 1 则进入上月/上年 */
|
||||
while (day < 1) {
|
||||
month--;
|
||||
if (month < 1) {
|
||||
month = 12;
|
||||
year--;
|
||||
}
|
||||
day += u8_get_days_in_month((U16_T)year, month);
|
||||
}
|
||||
|
||||
ctTime->ucDay = (U8_T)day;
|
||||
ctTime->ucMonth = month;
|
||||
ctTime->iYear = (U16_T)year;
|
||||
}
|
||||
|
||||
// 主函数:OCPP时间戳解析
|
||||
// time_type: 0-存入国际标准时间(UTC), 1-存入北京时间(UTC+8)
|
||||
// 返回:0-成功,非0-失败
|
||||
U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type)
|
||||
{
|
||||
if (ctTime == NULL || time_str == NULL) {
|
||||
return 1; // 参数错误
|
||||
}
|
||||
|
||||
// 复制输入字符串,避免修改原始数据
|
||||
char time_copy[64];
|
||||
if (strlen(time_str) >= sizeof(time_copy)) {
|
||||
return 1; // 输入过长
|
||||
}
|
||||
strcpy(time_copy, time_str);
|
||||
|
||||
// 分割日期时间部分和时区部分
|
||||
char *time_part = strchr(time_copy, 'T');
|
||||
if (time_part == NULL) {
|
||||
return 1; // 格式错误,缺少'T'
|
||||
}
|
||||
*time_part = '\0';
|
||||
time_part++;
|
||||
|
||||
// 解析日期部分 (YYYY-MM-DD)
|
||||
if (sscanf(time_copy, "%hu-%hhu-%hhu",
|
||||
&ctTime->iYear, &ctTime->ucMonth, &ctTime->ucDay) != 3) {
|
||||
return 1; // 日期解析失败
|
||||
}
|
||||
|
||||
int hour, min, sec, millisec = 0;
|
||||
char tz_sign = 0;
|
||||
int tz_hour = 0, tz_min = 0;
|
||||
|
||||
// 尝试解析带毫秒的格式
|
||||
int parsed = sscanf(time_part, "%d:%d:%d.%d%c%d:%d",
|
||||
&hour, &min, &sec, &millisec, &tz_sign, &tz_hour, &tz_min);
|
||||
if (parsed == 7) {
|
||||
// Case 1: 带毫秒和时区偏移(如 "10:00:28.990+08:00")
|
||||
} else {
|
||||
// 尝试解析带毫秒的UTC格式(Z结尾)
|
||||
parsed = sscanf(time_part, "%d:%d:%d.%dZ", &hour, &min, &sec, &millisec);
|
||||
if (parsed == 4) {
|
||||
// Case 2: 带毫秒的UTC时间(如 "10:00:28.990Z")
|
||||
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) {
|
||||
// Case 3: 无毫秒带时区偏移(如 "10:14:14+08:00")
|
||||
} else {
|
||||
parsed = sscanf(time_part, "%d:%d:%dZ", &hour, &min, &sec);
|
||||
if (parsed == 3) {
|
||||
// Case 4: 无毫秒UTC时间(如 "15:09:18Z")
|
||||
tz_sign = 'Z';
|
||||
} else {
|
||||
return 1; // 时间格式解析失败
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 填充时间部分
|
||||
ctTime->ucHour = hour;
|
||||
ctTime->ucMin = min;
|
||||
ctTime->ucSec = sec;
|
||||
|
||||
// 时区处理逻辑
|
||||
if (time_type == 1) {
|
||||
// 需要转换为北京时间 (UTC+8)
|
||||
if (tz_sign == 'Z' || tz_sign == 0) {
|
||||
// UTC时间直接加8小时
|
||||
v_adjust_time_with_timezone(ctTime, 8, 0);
|
||||
} else {
|
||||
// 处理带时区偏移的情况
|
||||
// 先将输入时间转换为UTC,再转换为北京时间
|
||||
int original_offset_hour = (tz_sign == '+') ? tz_hour : -tz_hour;
|
||||
int original_offset_min = (tz_sign == '+') ? tz_min : -tz_min;
|
||||
|
||||
// 转换为UTC时间(减去原始偏移)
|
||||
v_adjust_time_with_timezone(ctTime, -original_offset_hour, -original_offset_min);
|
||||
|
||||
// 再转换为北京时间(加上8小时)
|
||||
v_adjust_time_with_timezone(ctTime, 8, 0);
|
||||
}
|
||||
} else {
|
||||
// time_type == 0,存入国际标准时间(UTC)
|
||||
if (tz_sign != 'Z' && tz_sign != 0) {
|
||||
// 非UTC时间需要转换为UTC
|
||||
int original_offset_hour = (tz_sign == '+') ? tz_hour : -tz_hour;
|
||||
int original_offset_min = (tz_sign == '+') ? tz_min : -tz_min;
|
||||
|
||||
// 转换为UTC时间(减去原始偏移)
|
||||
v_adjust_time_with_timezone(ctTime, -original_offset_hour, -original_offset_min);
|
||||
}
|
||||
// 如果已经是UTC时间(Z结尾),则不需要转换
|
||||
}
|
||||
|
||||
// 最终数据验证
|
||||
if (!u8_validate_datetime(ctTime)) {
|
||||
return 1; // 数据验证失败
|
||||
}
|
||||
|
||||
return 0; // 成功
|
||||
}
|
||||
#endif /* CCU621_M: duplicate disabled */
|
||||
@@ -0,0 +1,93 @@
|
||||
# RTC 模块说明
|
||||
|
||||
## 1. 模块位置
|
||||
|
||||
- `BSP/rtc/rtc.c`
|
||||
- `BSP/rtc/rtc.h`
|
||||
|
||||
## 2. 模块职责
|
||||
|
||||
- 完成 MCU 内部 RTC 外设初始化(默认 `LXTAL 32.768kHz`)
|
||||
- 提供统一时间读写接口(业务层使用 `Comm_Time`)
|
||||
- 提供时间算法工具:
|
||||
- 本地时间(UTC+8)与 Unix 时间戳互转
|
||||
- OCPP 时间戳解析
|
||||
- 按时区偏移调整时间(支持跨天/月/年)
|
||||
|
||||
## 3. 硬件与掉电行为说明
|
||||
|
||||
### 3.1 `RTC_BKP0` 是什么
|
||||
|
||||
- `RTC_BKP0` 是 RTC 备份寄存器中的一个槽位(软件可读写)
|
||||
- 本项目中它作为“RTC 是否已初始化”的标记位使用
|
||||
- 典型逻辑:
|
||||
- `RTC_BKP0 == BKP_VALUE`:认为已配置过,不覆盖当前时间
|
||||
- `RTC_BKP0 != BKP_VALUE`:按默认时间初始化并写入标记
|
||||
|
||||
### 3.2 掉电后时间会不会继续走
|
||||
|
||||
- `RTC_BKP0` 只是存储标记,不会自动计时
|
||||
- 时间能否在掉电期间继续增加,取决于 RTC 域是否持续供电(VBAT)以及 RTC 时钟是否持续工作
|
||||
- 若掉电导致备份域失电,RTC 计时会停止;下次上电会按“首次初始化”逻辑处理
|
||||
|
||||
## 4. 默认时间策略
|
||||
|
||||
- 当前默认时间由 `rtc.c` 宏定义控制:
|
||||
- `RTC_DEFAULT_YEAR_BCD`
|
||||
- `RTC_DEFAULT_MONTH_BCD`
|
||||
- `RTC_DEFAULT_DAY_BCD`
|
||||
- `RTC_DEFAULT_HOUR_BCD`
|
||||
- `RTC_DEFAULT_MINUTE_BCD`
|
||||
- `RTC_DEFAULT_SECOND_BCD`
|
||||
- `RTC_DEFAULT_WEEKDAY`
|
||||
|
||||
- 默认值仅在“未初始化/备份标记无效”时生效,不会每次上电都覆盖已有时间
|
||||
|
||||
## 5. 对外接口
|
||||
|
||||
- `void v_rtc_init(void);`
|
||||
- RTC 初始化入口(由 `BSP/sys_drv_init.c` 调用)
|
||||
|
||||
- `void GetCurrentTime(Comm_Time *curTime);`
|
||||
- 读取当前时间到 `Comm_Time`
|
||||
|
||||
- `void v_rtc_set_time(Comm_Time *curTime);`
|
||||
- 写 RTC 时间(内含时间合法性检查)
|
||||
|
||||
- `unsigned int xDate2Seconds(Comm_Time *time);`
|
||||
- 本地时间(UTC+8)转 Unix 秒(UTC)
|
||||
|
||||
- `void xSeconds2Date(unsigned long seconds, Comm_Time *time);`
|
||||
- Unix 秒(UTC)转本地时间(UTC+8)
|
||||
|
||||
- `U8_T u8_ocpp_timestamp_to_tm(Comm_Time *ctTime, const char *time_str, U8_T time_type);`
|
||||
- 解析 OCPP 时间戳(支持 `Z`、`+08:00`、`-05:30` 等)
|
||||
- `time_type=0`:输出 UTC
|
||||
- `time_type=1`:输出北京时间(UTC+8)
|
||||
|
||||
- `void v_adjust_time_with_timezone(Comm_Time *ctTime, int hour_offset, int min_offset);`
|
||||
- 对 `Comm_Time` 按时区偏移修正
|
||||
|
||||
- `void test_rtc(void);`
|
||||
- RTC 自测接口(打印当前时间)
|
||||
|
||||
## 6. 启动与调用链
|
||||
|
||||
1. `main.c` -> `v_sys_hardware_init()`
|
||||
2. `BSP/sys_drv_init.c` -> `v_rtc_init()`
|
||||
3. 业务层通过 `GetCurrentTime/v_rtc_set_time` 访问时间
|
||||
4. `app/app_init/app_test.c` 在周期测试中可触发 RTC 读写测试
|
||||
|
||||
## 7. 常见问题
|
||||
|
||||
- **Q: 修改默认时间后,为什么上电看到的不是默认值?**
|
||||
A: 因为备份标记仍有效,模块判断为“已初始化”,不会覆盖当前时间。
|
||||
|
||||
- **Q: 想强制重新走默认时间怎么办?**
|
||||
A: 清空备份标记(例如清 `RTC_BKP0`)或清备份域后重启。
|
||||
|
||||
## 8. 相关文档
|
||||
|
||||
- [返回主说明 README](../../README.md)
|
||||
- [externalflash 模块说明](../externalflash/externalflash模块说明.md)
|
||||
|
||||
Reference in New Issue
Block a user