[讨论] STM32实时时钟RTC日历算法

shipeng   2017-9-8 17:08 楼主
最近工作上需要利用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 编辑
模电临时工

回复评论 (5)

1 来自 2楼 shipeng 

顶楼发现一个严重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;
}
模电临时工
点赞  2017-12-3 18:44
/*
** ===================================================================
**     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;
}
点赞  2017-9-8 17:52
这个是菲斯卡的
点赞  2017-9-8 17:53
引用: huo_hu 发表于 2017-9-8 17:53
这个是菲斯卡的

百度菲斯卡没有相关结果,兄弟有没有更详细的介绍?
模电临时工
点赞  2017-9-9 09:35
引用: shipeng 发表于 2017-9-9 09:35
百度菲斯卡没有相关结果,兄弟有没有更详细的介绍?

软件生成的代码,关于RTC部分有这么两个函数。具体的我也没验证过。
点赞  2017-9-10 23:18
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复