STM32CUBE下STM32F103 RTC实现日历的分享(HAL库实现)

2019-07-21 02:31发布

有时候会看到技术交流群里有人问到CUBE建立的RTC工程怎么日期保存不住呢。还有人说F4的可以啊。其实这就要去看手册的描述了先来看看F1参考手册怎么说的RTC实时时钟
F1RTC描述.png
再来看看F4参考手册RTC实时时钟的描述
F4RTC描述.png
这就可以看出区别了。其实F1就是一个计数器,并没有所谓的日历功能。然后又有人质疑,那为什么原子的可以啊。那我就应该让你去仔细去看看代码了。[mw_shl_code=c,true]u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
        u16 t;
        u32 seccount=0;
        if(syear<1970||syear>2099)return 1;          
        for(t=1970;t<syear;t++)        //把所有年份的秒钟相加
        {
                if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
                else seccount+=31536000;                          //平年的秒钟数
        }
        smon-=1;
        for(t=0;t<smon;t++)           //把前面月份的秒钟数相加
        {
                seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
                if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数          
        }
        seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
        seccount+=(u32)hour*3600;//小时秒钟数
    seccount+=(u32)min*60;         //分钟秒钟数
        seccount+=sec;//最后的秒钟加上去

        RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);        //使能PWR和BKP外设时钟  
        PWR_BackupAccessCmd(ENABLE);        //使能RTC和后备寄存器访问
        RTC_SetCounter(seccount);        //设置RTC计数器的值

        RTC_WaitForLastTask();        //等待最近一次对RTC寄存器的写操作完成         
        return 0;            
}[/mw_shl_code]
其中最关键的一句就是“RTC_SetCounter(seccount);        //设置RTC计数器的值”
来看看是怎么实现的
void RTC_SetCounter(uint32_t CounterValue)
{
  RTC_EnterConfigMode();
  /* Set RTC COUNTER MSB word */
  RTC->CNTH = CounterValue >> 16;
  /* Set RTC COUNTER LSB word */
  RTC->CNTL = (CounterValue & RTC_LSB_MASK);
  RTC_ExitConfigMode();
}

这样就明白了吧,其实就是通过日期计算出从1970年1月1日(一般都是这个时间具体为什么百度吧)到现在所有的秒数,然后写入计数器中。
然后我们在看看CUBE(4.22.1版本)生成的代码
[mw_shl_code=c,true]/* RTC init function */
void MX_RTC_Init(void)
{
  RTC_TimeTypeDef sTime;
  RTC_DateTypeDef DateToUpdate;

    /**Initialize RTC Only
    */
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**Initialize RTC and set the Time and Date
    */
  if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x32F2){
  sTime.Hours = 1;
  sTime.Minutes = 0;
  sTime.Seconds = 0;

  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
  DateToUpdate.Month = RTC_MONTH_SEPTEMBER;
  DateToUpdate.Date = 30;
  DateToUpdate.Year = 17;

  if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0x32F2);
  }

}[/mw_shl_code]
其中if(HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1) != 0x32F2)来判断是否设置过RTC
但是内部对日期的操作确实没有实质性东西,然我们看看日期操作是怎么操作的
[mw_shl_code=c,true]HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
{
  uint32_t counter_time = 0U, counter_alarm = 0U, hours = 0U;
  
  /* Check input parameters */
  if((hrtc == NULL) || (sDate == NULL))
  {
     return HAL_ERROR;
  }
  
  /* Check the parameters */
  assert_param(IS_RTC_FORMAT(Format));
  
/* Process Locked */
__HAL_LOCK(hrtc);
  
  hrtc->State = HAL_RTC_STATE_BUSY;
  
  if(Format == RTC_FORMAT_BIN)
  {   
    assert_param(IS_RTC_YEAR(sDate->Year));
    assert_param(IS_RTC_MONTH(sDate->Month));
    assert_param(IS_RTC_DATE(sDate->Date));

    /* Change the current date */
    hrtc->DateToUpdate.Year  = sDate->Year;
    hrtc->DateToUpdate.Month = sDate->Month;
    hrtc->DateToUpdate.Date  = sDate->Date;
  }
  else
  {   
    assert_param(IS_RTC_YEAR(RTC_Bcd2ToByte(sDate->Year)));
    assert_param(IS_RTC_MONTH(RTC_Bcd2ToByte(sDate->Month)));
    assert_param(IS_RTC_DATE(RTC_Bcd2ToByte(sDate->Date)));
   
    /* Change the current date */
    hrtc->DateToUpdate.Year  = RTC_Bcd2ToByte(sDate->Year);
    hrtc->DateToUpdate.Month = RTC_Bcd2ToByte(sDate->Month);
    hrtc->DateToUpdate.Date  = RTC_Bcd2ToByte(sDate->Date);
  }

  /* WeekDay set by user can be ignored because automatically calculated */
  hrtc->DateToUpdate.WeekDay = RTC_WeekDayNum(hrtc->DateToUpdate.Year, hrtc->DateToUpdate.Month, hrtc->DateToUpdate.Date);
  sDate->WeekDay = hrtc->DateToUpdate.WeekDay;

  /* Reset time to be aligned on the same day */
  /* Read the time counter*/
  counter_time = RTC_ReadTimeCounter(hrtc);

  /* Fill the structure fields with the read parameters */
  hours = counter_time / 3600U;
  if (hours > 24U)
  {
    /* Set updated time in decreasing counter by number of days elapsed */
    counter_time -= ((hours / 24U) * 24U * 3600U);
    /* Write time counter in RTC registers */
    if (RTC_WriteTimeCounter(hrtc, counter_time) != HAL_OK)
    {
      /* Set RTC state */
      hrtc->State = HAL_RTC_STATE_ERROR;
      
      /* Process Unlocked */
      __HAL_UNLOCK(hrtc);
      
      return HAL_ERROR;
    }

    /* Read current Alarm counter in RTC registers */
    counter_alarm = RTC_ReadAlarmCounter(hrtc);

    /* Set again alarm to match with new time if enabled */
    if (counter_alarm != RTC_ALARM_RESETVALUE)
    {
      if(counter_alarm < counter_time)
      {
        /* Add 1 day to alarm counter*/
        counter_alarm += (uint32_t)(24U * 3600U);
        
        /* Write new Alarm counter in RTC registers */
        if (RTC_WriteAlarmCounter(hrtc, counter_alarm) != HAL_OK)
        {
          /* Set RTC state */
          hrtc->State = HAL_RTC_STATE_ERROR;
         
          /* Process Unlocked */
          __HAL_UNLOCK(hrtc);
         
          return HAL_ERROR;
        }
      }
    }
   

  }

  hrtc->State = HAL_RTC_STATE_READY ;
  
  /* Process Unlocked */
  __HAL_UNLOCK(hrtc);
  
  return HAL_OK;   
}[/mw_shl_code]
仔细看看并没有把日期的秒数计算出来写入计数器寄存器里;并且日期只定义了一个临时结构体变量,当第二次上电的时候。日期并不会被赋值。
////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
下面我们来根据原子例程进行一些修改
将初始化修改如下
[mw_shl_code=c,true]uint8_t MY_RTC_Init(void)
{
   
    hrtc.Instance = RTC;
    hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
    hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
    //检查是不是第一次配置时钟
    if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1) != 0xA5A5)//从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎
    {
            
        if (HAL_RTC_Init(&hrtc) != HAL_OK)
        {
            Error_Handler();
        }
        HAL_RTCEx_SetSecond_IT(&hrtc);//使能RTC秒中断
        RTC_Set(2017,9,30,15,46,40);
        HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR1,0xA5A5);
        
    }
    else
    {
        if (HAL_RTC_Init(&hrtc) != HAL_OK)
        {
            Error_Handler();
        }
        HAL_RTCEx_SetSecond_IT(&hrtc);
    }
    RTC_Get();//更新时间
    return 0;
}[/mw_shl_code]

时间设置 其中原子原来是1970年开始   我稍微改了一下从2000年开始
[mw_shl_code=c,true]//设置时钟
//把输入的时钟转换为秒钟
//以1970年1月1日为基准
//1970~2099年为合法年份
//返回值:0,成功;其他:错误代码.
//月份数据表                        
//u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表          
//平年的月份日期表
//const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
HAL_StatusTypeDef RTC_Set(uint16_t syear,uint8_t smon,uint8_t sday,uint8_t hour,uint8_t min,uint8_t sec)
{
  uint16_t t;
        uint32_t seccount=0;
  if(syear<2000||syear>2129)
    return HAL_ERROR;          
        for(t=2000;t<syear;t++)        //°把所有年份的秒钟相加
        {
                if(Is_Leap_Year(t))
      seccount+=31622400;//闰年的秒钟数
                else
      seccount+=31536000;                          //平年的秒钟数
        }
        smon-=1;
        for(t=0;t<smon;t++)           //把前面月份的秒钟数相加
        {
                seccount+=(uint32_t)mon_table[t]*86400;//月份秒钟数相加
                if(Is_Leap_Year(syear)&&t==1)
      seccount+=86400;//闰年2月份增加一天的秒钟数   
        }
        seccount+=(uint32_t)(sday-1)*86400;//把前面日期的秒钟数相加
        seccount+=(uint32_t)hour*3600;//小时秒钟数
  seccount+=(uint32_t)min*60;         //分钟秒钟数
        seccount+=sec;//最后的秒钟加上去  
  hrtc.State = HAL_RTC_STATE_BUSY;
  __HAL_LOCK(&hrtc);
  if (RTC_SetCounter(&hrtc, seccount) != HAL_OK)
  {
    hrtc.State = HAL_RTC_STATE_ERROR;
    return HAL_ERROR;       
  }
  else
  {
    hrtc.State = HAL_RTC_STATE_READY;
  }
  __HAL_UNLOCK(&hrtc);
  return HAL_OK;
  
}[/mw_shl_code]

还有获取时间
[mw_shl_code=c,true]void RTC_Get(void)
{
  static uint16_t daycnt=0;
        uint32_t timecount=0;
        uint32_t temp=0;
        uint16_t temp1=0;          
  timecount=RTC_ReadTimeCounter(&hrtc);
        temp=timecount/86400;   //得到天数(秒钟数对应的)
        if(daycnt!=temp)//超过一天了
        {          
                daycnt=temp;
                temp1=2000;        //从2000年开始
                while(temp>=365)
                {                                 
                        if(Is_Leap_Year(temp1))//是闰年
                        {
                                if(temp>=366)
          temp-=366;//闰年的秒钟数
                                else
        {
          temp1++;
          break;
        }  
                        }
                        else
        temp-=365;          //平年
                        temp1++;  
                }   
                calendar.w_year=temp1;//得到年份
                temp1=0;
                while(temp>=28)//超过了一个月
                {
                        if(Is_Leap_Year(calendar.w_year)&&temp1==1)//当年是不是闰年/2月份
                        {
                                if(temp>=29)
          temp-=29;//闰年的秒钟数
                                else
          break;
                        }
                        else
                        {
                                if(temp>=mon_table[temp1])
          temp-=mon_table[temp1];//平年
                                else
          break;
                        }
                        temp1++;  
                }
                calendar.w_month=temp1+1;        //得到月份
                calendar.w_date=temp+1;          //得到日期
        }
        temp=timecount%86400;                     //得到秒钟数                       
        calendar.hour=temp/3600;             //小时
        calendar.min=(temp%3600)/60;         //分钟       
        calendar.sec=(temp%3600)%60;         //秒钟
        calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//获取星期   
       
}[/mw_shl_code]

由于static HAL_StatusTypeDef RTC_WriteTimeCounter(RTC_HandleTypeDef* hrtc, uint32_t TimeCounter);和static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef* hrtc)是库里内部代码,但是就自己按照库写了一个一样的函数来取代他们;
[mw_shl_code=c,true]static HAL_StatusTypeDef RTC_SetCounter(RTC_HandleTypeDef* hrtc, uint32_t TimeCounter)
{
  HAL_StatusTypeDef status = HAL_OK;
  
  /* Set Initialization mode */
  if(RTC_EnterInitMode(hrtc) != HAL_OK)
  {
    status = HAL_ERROR;
  }
  else
  {
    /* Set RTC COUNTER MSB word */
    WRITE_REG(hrtc->Instance->CNTH, (TimeCounter >> 16));
    /* Set RTC COUNTER LSB word */
    WRITE_REG(hrtc->Instance->CNTL, (TimeCounter & RTC_CNTL_RTC_CNT));
   
    /* Wait for synchro */
    if(RTC_ExitInitMode(hrtc) != HAL_OK)
    {      
      status = HAL_ERROR;
    }
  }
  
  return status;
}
static uint32_t RTC_ReadTimeCounter(RTC_HandleTypeDef* hrtc)
{
  uint16_t high1 = 0, high2 = 0, low = 0;
  uint32_t timecounter = 0;
  
  high1 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT);
  low   = READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT);
  high2 = READ_REG(hrtc->Instance->CNTH & RTC_CNTH_RTC_CNT);
  
  if (high1 != high2)
  { /* In this case the counter roll over during reading of CNTL and CNTH registers,
    read again CNTL register then return the counter value */
    timecounter = (((uint32_t) high2 << 16 ) | READ_REG(hrtc->Instance->CNTL & RTC_CNTL_RTC_CNT));
  }
  else
  { /* No counter roll over during reading of CNTL and CNTH registers, counter
    value is equal to first value of CNTL and CNTH */
    timecounter = (((uint32_t) high1 << 16 ) | low);
  }
  
  return timecounter;
}
static HAL_StatusTypeDef RTC_EnterInitMode(RTC_HandleTypeDef* hrtc)
{
  uint32_t tickstart = 0;
  
  tickstart = HAL_GetTick();
  /* Wait till RTC is in INIT state and if Time out is reached exit */
  while((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {      
      return HAL_TIMEOUT;
    }
  }
  
  /* Disable the write protection for RTC registers */
  __HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);
  
  
  return HAL_OK;  
}
static HAL_StatusTypeDef RTC_ExitInitMode(RTC_HandleTypeDef* hrtc)
{
  uint32_t tickstart = 0;
  
  /* Disable the write protection for RTC registers */
  __HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);
  
  tickstart = HAL_GetTick();
  /* Wait till RTC is in INIT state and if Time out is reached exit */
  while((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET)
  {
    if((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE)
    {      
      return HAL_TIMEOUT;
    }
  }
  
  return HAL_OK;  
}[/mw_shl_code]

这样需要改的地方就差不多了;我上传工程让大家参考一下,有什么不足之处,多多提出,我再修改修改;
下面是测试结果
QQ图片20170930174526.png 第一次输出时间;
QQ图片20170930174722.png 第二次上电

日期还在,时间也在走,基本测试成功了。
第一次发技术贴,拍砖轻一点=0=!

F1_RTC.rar (9.71 MB, 下载次数: 32953) 2017-9-30 17:51 上传 点击文件名下载附件







友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
3条回答
启滔
1楼-- · 2019-07-21 03:47
本帖最后由 启滔 于 2018-10-18 18:02 编辑

哥们,读时间这块是不是有问题?你可以用2000年12月31号去试一下,你的读出来应该是2001年了:闰年里面else好像不用temp1++    if(Is_Leap_Year(temp1))//是闰年             {                 if(temp>=366)           temp-=366;//闰年的秒钟数                 else         {           temp1++;           break;         }
启滔
2楼-- · 2019-07-21 07:33
启滔 发表于 2018-10-18 17:54
哥们,读时间这块是不是有问题?你可以用2000年12月31号去试一下,你的读出来应该是2001年了:闰年里面else ...

说错了,是2000年12月30日设置会出错,
deyagu
3楼-- · 2019-07-21 13:20
启滔,顶你!

一周热门 更多>