你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32F1的CAN通信之RTC

[复制链接]
攻城狮Melo 发布时间:2023-10-23 20:16
一、RTC简介
RTC(Real Time Clock)实时时钟,它是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。

RTC模块和时钟配置都是在后备区域,无论单片机处于何种状态,只要保证后备区正常供电,RTC就会一直工作。

二、STM32的RTC
2.1 主要特性

• 可编程的预分频系数:分频系数最高为2^20
• 32位的可编程计数器,可用于较长时间段的测量
• 可以选择以下三种RTC的时钟源 ─ HSE时钟除以128 ─ LSE振荡器时钟 ─ LSI振荡器时钟
• 3个专门的可屏蔽中断 ─ 闹钟中断,用来产生一个软件可编程的闹钟中断 ─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒) ─ 溢出中断,指示内部可编程计数器溢出并回转为0的状态

22.2 RTC框图介绍

微信图片_20231023201600.png

RTC框图


• RTCCLK通常选择低功耗32.768kHz外部晶振(LSE)
• RTC预分频器通常设置为32768,LES时钟经过RTC预分频器,输入频率变为1Hz,也就是1秒
• RTC_CNT输入时钟为1Hz时,1s加1次
• RTC_ALR是用来做闹钟的,RTC_CNT的值会与RTC_ALR的值进行比较,二者相等时,会产生闹钟中断

三、访问后备区域步骤
STM32系统复位之后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作,可以访问后备区域寄存器

• 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
• 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问

完成上面的设置之后,就可以操作后备寄存器。第一次通过APB1总线访问RTC时,需要等待APB1和RTC同步,确保读取出来的RTC的寄存器值是正确的。如果同步之后,一直没有关闭APB1和RTC外设接口,就不需要再同步了。

如果内核需要对RTC寄存器写入数据,在内核发送指令后,RTC会在3个RTCCLK时钟之后,开始写入数据。每次写入时,必须要检查RTC关闭操作标志位RTOFF是否置1来判断是否写操作完成。

四、RTC配置步骤
• 使能电源时钟和后备域时钟,开启RTC后备寄存器写访问
• 复位备份区域,开启外部低速振荡器(LSE)
• 选择RTC时钟,并使能
• 设置RTC的分频系数,配置RTC时钟
• 更新配置,设置RTC中断分组
• 编写RTC中断服务函数


五、RTC程序配置
55.1 RTC结构体定义

  1. // RTC结构体
  2. typedef struct
  3. {
  4.     // 时分秒
  5.     u8 hour;
  6.     u8 min;
  7.     u8 sec;
  8.    
  9.     // 年月日周
  10.     u16 w_year;
  11.     u8  w_month;
  12.     u8  w_date;
  13.     u8  week;   
  14. }_calendar;
复制代码


5.2 RTC初始化函数
  1. /*
  2. *==============================================================================
  3. *函数名称:RTC_Init
  4. *函数功能:初始化RTC
  5. *输入参数:无
  6. *返回值:0:成功;1:失败
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. u8 RTC_Init (void)
  11. {
  12.     u8 temp=0;   // 超时监控变量
  13.     // 结构体定义
  14.     NVIC_InitTypeDef NVIC_InitStructure;
  15.    
  16.     // 使能PWR和BKP外设时钟  
  17.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
  18.     PWR_BackupAccessCmd(ENABLE);   // 使能后备寄存器访问
  19.    
  20.     // 检测是否是第一次配置RTC
  21.     // 配置时会想RTC寄存器写入0xA0A0,如果读出的数据不是0xA0A0,认为是第一次配置RTC
  22.     if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0)
  23.     {   
  24.         BKP_DeInit();   // 复位备份区域  
  25.         RCC_LSEConfig(RCC_LSE_ON);   // 设置外部低速晶振(LSE),使用外设低速晶振
  26.         
  27.         // 等待低速晶振就绪
  28.         while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)
  29.         {
  30.             temp++;
  31.             delay_ms(10);
  32.         }
  33.         // 初始化时钟失败,晶振有问题
  34.         if(temp>=250)
  35.         {
  36.             return 1;
  37.         }   
  38.         
  39.         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);   // 设置RTC时钟(RTCCLK),选择LSE作为RTC时钟   
  40.         RCC_RTCCLKCmd(ENABLE);   // 使能RTC时钟  
  41.         RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成
  42.         
  43.         RTC_WaitForSynchro();   // 等待RTC寄存器同步
  44.         RTC_ITConfig(RTC_IT_SEC, ENABLE);   // 使能RTC秒中断
  45.         RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成
  46.         
  47.         RTC_EnterConfigMode();   // 允许配置
  48.         RTC_SetPrescaler(32767);   // 设置RTC预分频的值
  49.         RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成
  50.         
  51.         RTC_Set_Date(2023,6,26,11,15,00);   // 设置初始时间
  52.         RTC_ExitConfigMode();   // 退出配置模式  
  53.         BKP_WriteBackupRegister(BKP_DR1, 0XA0A0);   // 向指定的后备寄存器中写入用户程序数据
  54.     }
  55.     // 系统继续计时
  56.     else
  57.     {
  58.         RTC_WaitForSynchro();   // 等待最近一次对RTC寄存器的写操作完成
  59.         RTC_ITConfig(RTC_IT_SEC, ENABLE);   // 使能RTC秒中断
  60.         RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成
  61.     }
  62.    
  63.   // 配置RTC中断分组
  64.     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;   // RTC全局中断
  65.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;   // 先占优先级1位,从优先级3位
  66.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;   // 先占优先级0位,从优先级4位
  67.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   // 使能该通道中断
  68.     NVIC_Init(&NVIC_InitStructure);   // 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  69.    
  70.     RTC_Get_CurDate();   // 获取当前时间
  71.     return 0;   // 配置成功
  72. }
复制代码


初始化函数使用时,可以用while等待初始化成功,但是需要增加一个超时检测,这里简单给出一个写法,如果1s内,RTC没有初始化成功,直接跳过
  1. u32 tempVar = 0;   // 初始化RTC时的超时计数变量
  2.    
  3.     while (RTC_Init() && tempVar < 100)   // RTC初始化
  4.     {
  5.         delay_ms (10);
  6.         // 10ms自加1
  7.         tempVar = tempVar + 1;
  8.     }
复制代码

5.3 设置年月日,时分秒
  1. /*
  2. *==============================================================================
  3. *函数名称:RTC_Set_Date
  4. *函数功能:设置RTC的年月日,时分秒
  5. *输入参数:无
  6. *返回值:0:成功;1:失败
  7. *备  注:时间范围为1970年到2099年,可修改
  8. *==============================================================================
  9. */
  10. u8 RTC_Set_Date (u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
  11. {
  12.     u16 t;
  13.     u32 seccount=0;
  14.    
  15.     // 判断是否为合法年份
  16.     if(syear < 1970 || syear > 2099)
  17.     {
  18.         return 1;
  19.     }
  20.    
  21.     for(t = 1970;t < syear;t ++)   // 把所有年份的秒钟相加
  22.     {
  23.         // 闰年的秒钟数
  24.         if(Is_Leap_Year(t))
  25.         {
  26.             seccount += 31622400;
  27.         }
  28.         // 平年的秒钟数
  29.         else
  30.         {
  31.             seccount += 31536000;
  32.         }
  33.     }
  34.    
  35.     smon -= 1;
  36.    
  37.     for(t = 0;t < smon;t ++)   // 把前面月份的秒钟数相加
  38.     {
  39.         seccount += (u32)mon_table[t] * 86400;   // 月份秒钟数相加
  40.         // 闰年2月份增加一天的秒钟数
  41.         if(Is_Leap_Year(syear) && t == 1)
  42.         {
  43.             seccount += 86400;
  44.         }   
  45.     }
  46.     seccount += (u32)(sday-1) * 86400;   // 把前面日期的秒钟数相加
  47.    
  48.     seccount += (u32)hour * 3600;   // 小时秒钟数
  49.    
  50.     seccount += (u32)min*60;   // 分钟秒钟数
  51.    
  52.     seccount += sec;   // 最后的秒钟加上去

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

  57.     RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成   
  58.     return 0;     
  59. }
复制代码


5.4 判断闰年函数
  1. /*
  2. *==============================================================================
  3. *函数名称:Is_Leap_Year
  4. *函数功能:判断输入年份是否为闰年
  5. *输入参数:无
  6. *返回值:0:不是闰年;1:是闰年
  7. *备  注:四年一闰;百年不闰,四百年再闰
  8. *==============================================================================
  9. */
  10. u8 Is_Leap_Year (u16 year)
  11. {
  12.     // 是否能被4整除
  13.     if(year % 4 == 0)
  14.     {
  15.         // 是否能被100整除
  16.         if(year % 100 == 0)
  17.         {
  18.             // 如果以00结尾,还要能被400整除
  19.             if(year % 400 == 0)
  20.             {
  21.                 return 1;
  22.             }
  23.             // 是100的倍数,但是不是400的倍数
  24.             else
  25.             {
  26.                 return 0;
  27.             }   
  28.         }
  29.         // 是4的倍数,不是100的倍数
  30.         else
  31.         {
  32.             return 1;
  33.         }   
  34.     }
  35.     // 不是4的倍数
  36.     else
  37.     {
  38.         return 0;
  39.     }
  40. }
复制代码


5.5 获取当前年月日,时分秒
  1. /*
  2. *==============================================================================
  3. *函数名称:RTC_Get_CurDate
  4. *函数功能:获取当前年月日,时分秒
  5. *输入参数:无
  6. *返回值:0:成功;1:失败
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. u8 RTC_Get_CurDate (void)
  11. {
  12.     // 存储上一次的总天数值,用来监测时间变化是否超过一天
  13.     static u16 daycnt = 0;
  14.     u32 timecount = 0;
  15.     // 临时计算变量
  16.     u32 temp = 0;
  17.     u16 temp1 = 0;
  18.    
  19.     timecount = RTC_GetCounter();   // 获取当前总秒数
  20.    
  21.      temp = timecount / 86400;   // 得到总天数
  22.    
  23.     // 超过一天了
  24.     if(daycnt != temp)
  25.     {
  26.         daycnt = temp;   // 更新当前总天数值
  27.         
  28.         temp1 = 1970;   // 从1970年开始,计算当前年份
  29.         while(temp >= 365)
  30.         {
  31.             // 是闰年
  32.             if(Is_Leap_Year(temp1))
  33.             {
  34.                 // 已经过完了366天
  35.                 if(temp >= 366)
  36.                 {
  37.                     temp -= 366;   // 闰年的天数
  38.                 }
  39.                 // 刚过完365天,当前是第366天
  40.                 else
  41.                 {
  42.                     temp1 ++;   // 年份加1
  43.                     break;
  44.                 }
  45.             }
  46.             // 是平年
  47.             else
  48.             {
  49.                 temp -= 365;   // 平年的天数
  50.             }
  51.             temp1 ++;   // 年份加1
  52.         }
  53.         
  54.         calendar.w_year = temp1;   // 得到年份
  55.         temp1=0;   // 清零临时计算变量
  56.         
  57.         // 此时temp为小于一年的天数,开始计算当前月份
  58.         while(temp >= 28)   // 超过了一个月
  59.         {
  60.             // 当年是闰年的2月份
  61.             if(Is_Leap_Year(calendar.w_year) && temp1 == 1)
  62.             {
  63.                 // 是闰年的二月份且天数大于等于29天
  64.                 if(temp >= 29)
  65.                 {
  66.                     temp -= 29;   // 闰年的2月份天数
  67.                 }
  68.                 // 是闰年的2月份,天数小于闰年2月份天数
  69.                 else
  70.                 {
  71.                     break;
  72.                 }
  73.             }
  74.             // 是平年
  75.             else
  76.             {
  77.                 // 查询月份天数表
  78.                 if(temp >= mon_table[temp1])
  79.                 {
  80.                     // 超过当月天数,减去
  81.                     temp -= mon_table[temp1];
  82.                 }
  83.                 else
  84.                 {
  85.                     break;
  86.                 }
  87.             }
  88.             temp1 ++;   // 月份加1
  89.         }
  90.         // 加1是因为月份表索引是0~11
  91.         calendar.w_month = temp1 + 1;   // 得到月份
  92.         // 当前日期为已经过去的天数加1
  93.         calendar.w_date = temp + 1;   // 得到日期
  94.     }
  95.     temp = timecount % 86400;   // 得到秒钟数      
  96.     calendar.hour = temp / 3600;   // 小时
  97.     calendar.min = (temp % 3600) / 60;   // 分钟
  98.     calendar.sec = (temp % 3600) % 60;   // 秒钟
  99.     calendar.week = RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);   // 获取星期
  100.    
  101.     return 0;
  102. }
复制代码


5.6 获取星期几
该函数设计是根据蔡勒公式设计,程序如下
  1. /*
  2. *==============================================================================
  3. *函数名称:RTC_Get_Week
  4. *函数功能:获取当前是星期几
  5. *输入参数:year:当前年;month:当前月;day:当前日
  6. *返回值:星期几
  7. *备  注:无
  8. *==============================================================================
  9. */
  10. u8 RTC_Get_Week (u16 year,u8 month,u8 day)
  11. {
  12.     u16 temp;
  13.     u8 yearH,yearL;
  14.    
  15.     yearH = year / 100;
  16.     yearL = year % 100;
  17.    
  18.     // 如果为21世纪,年份数加100  
  19.     if (yearH > 19)
  20.     {
  21.         yearL += 100;
  22.     }
  23.    
  24.     // 所过闰年数只算1900年之后的  
  25.     temp = yearL + yearL / 4;
  26.     temp = temp % 7;
  27.     temp = temp + day + table_week[month - 1];
  28.    
  29.     if (yearL % 4 == 0 && month < 3)
  30.     {
  31.         temp --;
  32.     }
  33.    
  34.     return(temp % 7);
  35. }
复制代码

5.7 中断服务函数
  1. /*
  2. *==============================================================================
  3. *函数名称:RTC_IRQHandler
  4. *函数功能:RTC中断服务函数
  5. *输入参数:无
  6. *返回值:无
  7. *备  注:更新时间
  8. *==============================================================================
  9. */
  10. void RTC_IRQHandler(void)
  11. {
  12.     // 秒中断
  13.     if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
  14.     {      
  15.         RTC_Get_CurDate();   // 获取当前时间
  16.         // 串口打印当前时间
  17.         printf("RTC Time:%d-%d-%d %d:%d:%d   Week:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,
  18.                 calendar.hour,calendar.min,calendar.sec,calendar.week);
  19.      }            
  20.     RTC_ClearITPendingBit(RTC_IT_SEC | RTC_IT_OW);   //清除秒中断标志位
  21.     RTC_WaitForLastTask();   // 等待最近一次对RTC寄存器的写操作完成              
  22. }
复制代码


六、拓展

在实际使用时,通常会通过网络授时,也就是利用WIFI模块连接网络,请求API获得初始时间。但是可能会存在些许差异。比如请求API后,获得的时间为2023.06.26.14:48:00。实际单片机解析出时间时已经过去了几秒或者十几秒,或者其他问题导致了实际解析出时间后已经与实际值有差距。此时就需要对时间进行矫正。博主在实际应用时差了14s,这里贴一下当时的矫正程序。可能大家用不到,这里只是觉得思考的过程有意思,所以贴出来分享一下。
  1. void RTC_Time_Correct(void)   // 开机时间校正
  2. {
  3.     // 加14秒不满1分钟
  4.     if (gTimeSec < 46)
  5.     {
  6.         gTimeSec = gTimeSec + 14;
  7.     }
  8.     // 加14秒满1分钟
  9.     else if (gTimeSec >= 46)
  10.     {
  11.         gTimeSec = gTimeSec + 14 - 60;
  12.         
  13.         // 分钟数需要加1
  14.         // 加1分钟不满1小时
  15.         if (gTimeMin < 59)
  16.         {
  17.             gTimeMin = gTimeMin + 1;
  18.         }
  19.         // 分钟数加1满1小时
  20.         else if (gTimeMin == 59)
  21.         {
  22.             gTimeMin = 0;
  23.             
  24.             // 小时数需要加1
  25.             // 加1小时不满1天
  26.             if (gTimeHour < 23)
  27.             {
  28.                 gTimeHour = gTimeHour + 1;
  29.             }
  30.             // 加1小时满1天
  31.             else if (gTimeHour == 23)
  32.             {
  33.                 gTimeHour = 0;
  34.                 // 天数需要加1
  35.                 // 天数小于28直接加1
  36.                 if (gTimeDay < 28)
  37.                 {
  38.                     gTimeDay = gTimeDay + 1;
  39.                 }
  40.                 // 天数等于28
  41.                 else if (gTimeDay == 28)
  42.                 {
  43.                     // 当前为二月
  44.                     if (gTimeMon == 2)
  45.                     {
  46.                         // 闰年
  47.                         if (Is_Leap_Year(gTimeYear))
  48.                         {
  49.                             gTimeDay = gTimeDay + 1;
  50.                         }
  51.                         // 当前为2月且不是闰年
  52.                         else
  53.                         {
  54.                             gTimeDay = 0;   // 天数置零
  55.                             gTimeMon = gTimeMon + 1;   // 月份加1
  56.                         }
  57.                     }
  58.                 }
  59.                 // 天数等于30
  60.                 else if (gTimeDay == 30)
  61.                 {
  62.                     // 当前月份只有30天
  63.                     if (gTimeMon == 2 || gTimeMon == 4 || gTimeMon == 6 || gTimeMon == 9
  64.                           || gTimeMon == 11)
  65.                     {
  66.                         gTimeDay = 0;
  67.                         gTimeMon = gTimeMon + 1;
  68.                     }
  69.                     // 当前月份有31天
  70.                     else
  71.                     {
  72.                         gTimeDay = gTimeDay + 1;
  73.                     }
  74.                 }
  75.                 // 天数等于31
  76.                 else if (gTimeDay == 31)
  77.                 {
  78.                     gTimeDay = 0;
  79.                     
  80.                     // 加1月不满1年
  81.                     if (gTimeMon != 12)
  82.                     {
  83.                         gTimeMon = gTimeMon + 1;
  84.                     }
  85.                     else
  86.                     {
  87.                         gTimeMon = 1;
  88.                         gTimeYear = gTimeYear + 1;
  89.                     }
  90.                 }
  91.             }
  92.         }
  93.     }
  94. }
复制代码


转载自: 二土电子
如有侵权请联系删除

收藏 评论0 发布时间:2023-10-23 20:16

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版