最近工作上需要利用STM32的自带RTC编写日历功能。其实对于公历万年历并不是很复杂,但是由于自带RTC断电后只能做32位二进制秒计数不能更新日期信息,要从根本上解决这个问题就需要编写一个算法可以根据计数器RTC_CNT的当前值计算出当前日期。由于32位的RTC_CNT的最大计数值0xFFFFFFFF/秒,即135年左右,因此这个算法只适用于2000年~2099年。由于日历的时间跨度大短时间内很难通过测试发现bug,所以恳请大家帮忙排查一下是否存在bug并跟帖回复
编写思路是这样的:在100年范围内刚好是4年1闰,也就是每4年1个周期=366+365*3天,当前总天数days=RTC_CNT/(24*3600),年year=days/(366+365*3)*4 + days%(366+365*3)/365,月和日的算法请直接参照下列代码:
const u8 yizhou[]={"六日一二三四五"};
struct RtcReg
{
u32 days;
u8 date;
u8 month;
u8 year;
};
void TranslateYMD(struct RtcReg *RtcRegs)//在调用此函数前,RtcRegs->days需赋值当前天数值RTC_CNT/(24*3600)
{
u8 i,j;u16 u16buf=RtcRegs->days;
RtcRegs->year = u16buf/(366+365*3)*4;
u16buf %= (366+365*3);//4年为一个周期,当前周期内的天数
RtcRegs->year += u16buf/365;//加上当前4年周期内的年
if (u16buf==365)RtcRegs->year--;//4年周期中的第一年为闰年366天,如果天数u16buf等于365则上一句多算了1年//年计算完成,开始月和日计算:
else if (u16buf>365)u16buf = (u16buf-1)%365;//得到当前年中的天数,如u16buf超过365则经过了1个闰年u16buf减1再对365求余
i = (RtcRegs->year&3)?0:1;//当前年份为 平年i=0/闰年i=1
RtcRegs->month = u16buf/(181+i);//1年分为两个181+i天,即以7月1日为界限,分别计算月份
RtcRegs->month = RtcRegs->month*6;
if (RtcRegs->month<12)RtcRegs->month++;
if (RtcRegs->month<7)
{
// 大于59+i即经过了2月份需补3-i天 大于120+i即经过了4月份需补1天
j = u16buf + ((u16buf>59+i)?(3-i):0) + ((u16buf>i+120)?1:0);
RtcRegs->month += j/31;RtcRegs->date = j%31+1;
}
else if (RtcRegs->month==7)
{
u16buf -= 181+i;
// 大于92即经过了9月需补1天 大于153即经过了11月需补1天
j = u16buf + ((u16buf>92)?1:0) + ((u16buf>153)?1:0);
RtcRegs->month += j/31;RtcRegs->date = j%31+1;
}
//12月份减去1-11月的天数(334+i)由于日是从1开始没有0因此需要加1即-(334+i)+1=-(333+i)
else RtcRegs->date = u16buf - (333+i);
}
void DisplayYMDW(struct RtcReg *RtcRegs)//日期显示子程序
{
Disp1Char(2,3,yizhou[RtcRegs->days%7*2]);//星期
LCD_Wdata(yizhou[RtcRegs->days%7*2+1]);//星期,由于一个汉字占用2个字节,因此星期需发送两个字节
LCD_Wdata('2');
LCD_Wdata('0');
LCD_Wdata((RtcRegs->year/10)+'0');
LCD_Wdata((RtcRegs->year%10)+'0');
LCD_Wdata('-');
LCD_Wdata((RtcRegs->month/10)+'0');
LCD_Wdata((RtcRegs->month%10)+'0');
LCD_Wdata('-');
LCD_Wdata((RtcRegs->date/10)+'0');
LCD_Wdata((RtcRegs->date%10)+'0');
}
本帖最后由 shipeng 于 2017-9-9 09:29 编辑
顶楼发现一个严重bug,在小月转到大月的第一天会计算错误,例如12月1日会计算成11月31日,以下代码已更正:
void TranslateYMDW(struct RtcReg *RtcRegs)
{
u8 i,j;u16 u16buf=RtcRegs->days;
RtcRegs->year = u16buf/(366+365*3)*4;
u16buf %= (366+365*3);
RtcRegs->year += u16buf/365;
if (u16buf==365)RtcRegs->year--;
else if (u16buf>365)u16buf = (u16buf-1)%365;
i = (RtcRegs->year&3)!=0?0:1;
RtcRegs->month = u16buf/(181+i);
RtcRegs->month = RtcRegs->month*6;
if (RtcRegs->month<12)RtcRegs->month++;
if (RtcRegs->month<7)
{
j = u16buf + ((u16buf<59+i)?0:(3-i))+((u16buf59+i)?(3-i):0)+((u16buf>i+120)?1:0);
RtcRegs->month += j/31;RtcRegs->date = j%31+1;
}
else if (RtcRegs->month==7)
{
u16buf -= 181+i;
j = u16buf + ((u16buf<92)?0:1)+((u16buf<153)?0:1);// + ((u16buf>92)?1:0)+((u16buf>153)?1:0);
RtcRegs->month += j/31;RtcRegs->date = j%31+1;
}
else RtcRegs->date = u16buf - (333+i);//RtcRegs->date = u16buf - (334+i)+1;
}
/*
** ===================================================================
** Method : RTC1_GetTime (component RTC_LDD)
*/
/*!
** @brief
** Returns actual time and date.
** Note: Fields not supported by HW are calculated in software.
** @param
** DeviceDataPtr - Pointer to device data
** structure pointer returned by [Init] method.
** @param
** TimePtr - Pointer to the time structure to
** fill with current time.
*/
/* ===================================================================*/
void RTC1_GetTime(LDD_TDeviceData *DeviceDataPtr, LDD_RTC_TTime *TimePtr)
{
uint32_t x;
uint32_t Seconds, Days;
(void)DeviceDataPtr; /* Parameter is not used, suppress unused argument warning */
Seconds = RTC_PDD_ReadTimeSecondsReg(RTC_BASE_PTR); /* Seconds since 2000-01-01 */
Seconds--;
Days = Seconds / 86400U; /* Days */
Seconds = Seconds % 86400U; /* Seconds left */
TimePtr->Hour = Seconds / 3600U; /* Hours */
Seconds = Seconds % 3600u; /* Seconds left */
TimePtr->Minute = Seconds / 60U; /* Minutes */
TimePtr->Second = Seconds % 60U; /* Seconds */
TimePtr->DayOfWeek = (Days + 6U) % 7U; /* Day of week */
TimePtr->Year = (4U * (Days / ((4U * 365U) + 1U))) + 2000U; /* Year */
Days = Days % ((4U * 365U) + 1U);
if (Days == ((0U * 365U) + 59U)) { /* 59 */
TimePtr->Day = 29U;
TimePtr->Month = 2U;
return;
} else if (Days > ((0U * 365U) + 59U)) {
Days--;
} else {
}
x = Days / 365U;
TimePtr->Year += x;
Days -= x * 365U;
for (x=1U; x <= 12U; x++) {
if (Days < ULY[x]) {
TimePtr->Month = x;
break;
} else {
Days -= ULY[x];
}
}
TimePtr->Day = Days + 1U;
}
/*
** ===================================================================
** Method : RTC1_SetTime (component RTC_LDD)
*/
/*!
** @brief
** Sets new time and date.
** Note: All LDD_RTC_TTime structure members must be correctly
** filled in.
** @param
** DeviceDataPtr - Pointer to device data
** structure pointer returned by [Init] method.
** @param
** TimePtr - Pointer to the time structure with
** new time to set.
** @return
** - Error code, possible codes:
** - ERR_OK - OK.
** - ERR_DISABLED - Component is disabled.
** - ERR_SPEED - Component does not work in
** the active clock configuration.
** - ERR_RANGE - Parameter out of range.
*/
/* ===================================================================*/
LDD_TError RTC1_SetTime(LDD_TDeviceData *DeviceDataPtr, LDD_RTC_TTime *TimePtr)
{
uint32_t Seconds;
(void)DeviceDataPtr; /* Parameter is not used, suppress unused argument warning */
if ((TimePtr->Year < 2000U) || (TimePtr->Year > 2099U) || (TimePtr->Month > 12U) || (TimePtr->Month == 0U) || (TimePtr->Day > 31U) || (TimePtr->Day == 0U)) { /* Test correctness of given parameters */
return ERR_RANGE; /* If not correct then error */
}
if (TimePtr->Year & 3U) { /* Is given year non-leap-one? */
if (ULY[TimePtr->Month] < TimePtr->Day) { /* Does the obtained number of days exceed number of days in the appropriate month & year? */
return ERR_RANGE; /* If yes (incorrect date inserted) then error */
}
} else { /* Is given year leap-one? */
if (LY[TimePtr->Month] < TimePtr->Day) { /* Does the obtained number of days exceed number of days in the appropriate month & year? */
return ERR_RANGE; /* If yes (incorrect date inserted) then error */
}
}
Seconds = ((TimePtr->Year - 2000U) * 365U) + (((TimePtr->Year - 2000U) + 3U) / 4U); /* Compute number of days from 2000 till given year */
Seconds += MONTH_DAYS[TimePtr->Month]; /* Add number of days till given month */
Seconds += TimePtr->Day; /* Add days in given month */
if ((TimePtr->Year & 3U) || (TimePtr->Month <= 2U)) { /* For non-leap year or month <= 2, decrement day counter */
Seconds--;
}
Seconds = (Seconds * 86400U) + (TimePtr->Hour * 3600U) + (TimePtr->Minute * 60U) + TimePtr->Second;
Seconds++;
RTC_PDD_EnableCounter(RTC_BASE_PTR, PDD_DISABLE); /* Disable counter */
RTC_PDD_WriteTimePrescalerReg(RTC_BASE_PTR, 0x00U); /* Clear prescaler */
RTC_PDD_WriteTimeSecondsReg(RTC_BASE_PTR, Seconds); /* Set seconds counter */
RTC_PDD_EnableCounter(RTC_BASE_PTR, PDD_ENABLE); /* Enable counter */
return ERR_OK;
}
软件生成的代码,关于RTC部分有这么两个函数。具体的我也没验证过。