一、STM32F1 RTC介绍
1.1 RTC简介
STM32 的实时时钟( RTC)是一个独立的定时器。 STM32 的 RTC 模 块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的 功能。修改计数器的值可以重新设置系统当前的时间和日期。 RTC模块和时钟配置是在后备区域,无论器件状态如何(运行模式、 低功耗模式或处于复位状态),只要保证后备区域供电正常,RTC便不会 停止工作,所以通常会在后备区域供电端加一个纽扣电池,即使主电源 停止供电,后备电源也会启动供电,从而保证RTC时钟不停的运行,只有 当主电源和后备纽扣电池都没有电的时,RTC才停止工作。 从 RTC 的定时器特性来说,它是一个 32 位的计数器,只能向上计 数。它的时钟来源有三种,分别为高速外部时钟的 128 分频( HSE/128 )、 低速内部时钟 LSI 以及低速外部时钟 LSE。
1.2电源
电池备份区域
使用电池或其他电源连接到VBAT脚上,当VDD断电时,可以保存备份寄存器的内容和维持RTC的
功能。
VBAT脚也为RTC、LSE振荡器和PC13至PC15供电,这保证当主要电源被切断时RTC能继续工作。切换到VBAT供电由复位模块中的掉电复位功能控制。
如果应用中没有使用外部电池,VBAT必须连接到VDD引脚上。
1.3备份寄存器(BKP)简介
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操作。执行以下操作可以使能对备份寄存器和RTC的访问。
● 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
● 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC的访问。
1.4实时时钟(RTC)
RTC简介
实时时钟是一个独立的定时器。RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒后,RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操作。执行以下操作将使能对后备寄存器和RTC的访问:(BKP中也提到过)
● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC的访问。
二、功能描述
1、概述
RTC由两个主要部分组成(参见下图)。第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作(参见16.4节)。APB1接口由APB1总线时钟驱动,用来与APB1总线接口。
另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。
第一个模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个实时时钟(RTC)TR_CLK周期中RTC产生一个中断(秒中断)。
第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。
2、复位过程
除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器外,所有的系统寄存器都由系统复位或电源复位进行异步复位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV寄存器仅能通过备份域复位信号复位。
3、读RTC寄存器
RTC核完全独立于RTC APB1接口。
软件通过APB1接口访问RTC的预分频值、计数器值和闹钟值。但是,相关的可读寄存器只在与RTC与 APB1时钟进行重新同步的RTC时钟的上升沿被更新。RTC标志也是如此的。这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。下述几种情况下能够发生这种情形:
● 发生系统复位或电源复位
● 系统刚从待机模式唤醒(参见第4.3节:低功耗模式)。
● 系统刚从停机模式唤醒(参见第4.3节:低功耗模式)。
所有以上情况中,APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。
注:RTC的 APB1接口不受WFI和WFE等低功耗模式的影响。
4、写RTC寄存器
必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。
另外,对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器。
三、STM32F1 RTC配置步骤
- 使能PWR和BKP时钟。调用函数:RCC_APB1PeriphClockCmd();
- 使能后备寄存器访问。调用函数:PWR_BackupAccessCmd();
- 配置RTC时钟源,使能RTC时钟。调用函数:RCC_RTCCLKConfig();RCC_RTCCLKCmd();
- 如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
- 设置RTC预分频系数。调用函数:RTC_SetPrescaler();
- 设置时间。调用函数:RTC_SetCounter();
- 开启相关中断(如果需要)。调用函数:RTC_ITConfig();
- 编写中断服务函数。调用函数:RTC_IRQHandler();
- 部分操作要等待写操作完成和同步。调用函数:RTC_WaitForLastTask();RTC_WaitForSynchro();
四、程序举例
编写RTC控制程序 本章所要实现的功能是:设置RTC时间日期初值,在RTC秒中断内使用 串口打印出RTC日期和时间,D1指示灯闪烁提示系统运行。
程序框架如下 :
(1)初始化RTC,设置RTC时间日期初值 (2)开启RTC的秒中断,编写RTC中断函数, (3)在RTC中断内更新时间并打印输出 (4)编写主函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 1 #ifndef _rtc_H 2 #define _rtc_H 3 4 #include "system.h" 5 6 7 u8 RTCx_Init(void); 8 void RTC_GET(void); 9 10 typedef struct 11 { 12 u8 hour; 13 u8 min; 14 u8 sec; 15 }_calender; 16 17 extern _calender calender; 18 19 20 21 #endif
|
分析RTC_Init()函数:RTC初始化函数。
初始化时按照之前的RTC一般步骤进行配置,这里需要注意的是,为了区分是否是第一次执行RTC_Init()函数,必须判断后配寄存器中是否写如果某个值(向BKP_DR1寄存器写入0xA0A0,写入其他的数字也可以)如果写入不用再初始化。
为什么要区分是否执行过RTC_Init?
如果由于 断电/ 复位/唤醒等待 等因素,程序中断但RTC时钟以及后备寄存器区域还在执行;等恢复供电重新启动程序时,这不能再对RTC时钟进行初始化,否则一直初始化,那么RTC作为时钟就没什么实际作用。
1 2 3 4 5 6 7 8 9
| 1 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA0A0 2 { 3 4 BKP_WriteBackupRegister(BKP_DR1, 0XA0A0); 5 } 6 else 7 { 8 9 }
|
代码44:使用外部低速晶振(LSE)时需要检查指定的RCC相应的标志位是否设置,等待低速晶振就绪。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
| 1 #include "rtc.h" 2 #include "systick.h" 3 #include "ustrt.h" 4 5 6 _calender calender; 7 8 9 void RTC_NVIC_Config() 10 { 11 NVIC_InitTypeDef NVIC_InitStruct; 12 13 NVIC_InitStruct.NVIC_IRQChannel=RTC_IRQn; 14 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2; 15 NVIC_InitStruct.NVIC_IRQChannelSubPriority=3; 16 NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE ; 17 NVIC_Init(&NVIC_InitStruct); 18 19 } 20 21 void RTC_GET() 22 { 23 u32 timedata; 24 timedata=RTC_GetCounter(); 25 calender.hour=timedata/3600; 26 calender.min=timedata%3600/60; 27 calender.sec=timedata%3600%60; 28 } 29 30 31 32 u8 RTCx_Init() 33 { 34 u8 time; 35 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 36 RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); 37 PWR_BackupAccessCmd(ENABLE); 38 39 40 if(BKP_ReadBackupRegister(BKP_DR1)!=0xA0a0) 41 { 42 BKP_DeInit(); 43 RCC_LSEConfig(RCC_LSE_ON); 44 while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET && time<250) 45 { 46 time++; 47 delay_ms(10); 48 } 49 if(time>=250) 50 { 51 return 1; 52 } 53 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); 54 RCC_RTCCLKCmd(ENABLE); 55 RTC_WaitForLastTask(); 56 RTC_WaitForSynchro(); 57 RTC_ITConfig(RTC_IT_SEC, ENABLE); 58 RTC_WaitForLastTask(); 59 RTC_EnterConfigMode(); 60 RTC_SetPrescaler(32767); 61 RTC_WaitForLastTask(); 62 RTC_SetCounter(0x1111); 63 RTC_ExitConfigMode(); 64 BKP_WriteBackupRegister(BKP_DR1,0xA0a0); 65 66 } 67 else 68 { 69 RTC_WaitForLastTask(); 70 RTC_WaitForSynchro(); 71 RTC_ITConfig(RTC_IT_SEC, ENABLE); 72 } 73 74 RTC_NVIC_Config(); 75 RTC_GET(); 76 return 0; 77 } 78 79 void RTC_IRQHandler(void) 80 { 81 if(RTC_GetITStatus(RTC_IT_SEC)!=0) 82 { 83 RTC_GET(); 84 printf("RTC_Time:%d:%d:%d\r\n",calender.hour,calender.min,calender.sec); 85 } 86 RTC_ClearITPendingBit(RTC_IT_SEC); 87 }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 1 #include "system.h" 2 #include "led.h" 3 #include "systick.h" 4 #include "ustrt.h" 5 #include "rtc.h" 6 7 int main() 8 { 9 u8 i=0; 10 11 12 SysTick_Init(72); 13 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 14 LED_Init(); 15 ustrt_Init(9600); 16 RTCx_Init(); 17 18 while(1) 19 { 20 21 i++; 22 if(i%20==0) 23 { 24 led1=!led1; 25 } 26 delay_ms(10); 27 } 28 }
|
配置RCT基本步骤(参考二)-添加了程序但没接42khz晶振,因此在初始化阶段不通过
- 使能PWR和BKP外设时钟
- 使能后备寄存器访问
- 判断从指定的后备寄存器中读出数据:
如果读出数据与写入的数据相同,说明已经配置过了不需要重新配置,只要等待最近一次对RTC寄存器的写操作完成和使能RTC秒中断即可,如果读出数据与写入数据不相同,则需要重新配置。修改写入时间时一定要记得把两个0x5051修改成其他任意十六位数据。这样才可以重新修改写入时间。
- 复位备份区域
- 设置外部低速晶振(LSE),使用外设低速晶振
- 检查指定的RCC标志位设置与否,等待低速晶振就绪,这一步尤为重要,
如果晶振出现问题整个系统是不会正常运行的。如果是硬件问题不容查出来。
- 设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
- 使能RTC时钟
- 等待最近一次对RTC寄存器的写操作完成
- 等待RTC寄存器同步
(注意9、10两步很重要,每次操作RTC寄存器时都要重复一次9、10两步)
- 使能RTC秒中断
- 等待最近一次对RTC寄存器的写操作完成
- 允许配置
- 设置RTC预分频的值:
计算方式32768/(32767+1) = 1Hz 周期刚好是1秒。
- 等待最近一次对RTC寄存器的写操作完成
- 设置时间
- 退出配置模式,防止误操作
- 向指定的后备寄存器中写入用户程序数据
- RCT中断分组设置
- 更新时间
配置代码参考
RTC.c文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
| #include "sys.h" #include "delay.h" #include "usart.h" #include "rtc.h" _calendar_obj calendar; u32 timecount=0; static void RTC_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }
u8 RTC_Init(void) { u8 temp=0; RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); PWR_BackupAccessCmd(ENABLE); if (BKP_ReadBackupRegister(BKP_DR1) != 0x5051) { BKP_DeInit(); RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) { temp++; delay_ms(10); } if(temp>=250)return 1; RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForLastTask(); RTC_WaitForSynchro(); RTC_ITConfig(RTC_IT_SEC, ENABLE); RTC_WaitForLastTask(); RTC_EnterConfigMode(); RTC_SetPrescaler(32767); RTC_WaitForLastTask(); RTC_Set(2020,2,16,22,16,00); RTC_ExitConfigMode(); BKP_WriteBackupRegister(BKP_DR1, 0X5051); } else { RTC_WaitForSynchro(); RTC_ITConfig(RTC_IT_SEC, ENABLE); RTC_WaitForLastTask(); } RTC_NVIC_Config(); RTC_Get(); return 0; }
void RTC_IRQHandler(void) { if (RTC_GetITStatus(RTC_IT_SEC) != RESET) { RTC_Get(); } if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) { RTC_ClearITPendingBit(RTC_IT_ALR); RTC_Get(); printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec); } RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); RTC_WaitForLastTask(); }
u8 Is_Leap_Year(u16 year) { if(year%4==0) { if(year%100==0) { if(year%400==0)return 1; else return 0; }else return 1; }else return 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}; 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; } 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_BackupAccessCmd(ENABLE); RTC_SetCounter(seccount); RTC_WaitForLastTask(); return 0; }
u8 RTC_Alarm_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; } 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_BackupAccessCmd(ENABLE); RTC_SetAlarm(seccount); RTC_WaitForLastTask(); return 0; }
u8 RTC_Get(void) { static u16 daycnt=0; u32 temp=0; u16 temp1=0; timecount=RTC_GetCounter(); temp=timecount/86400; if(daycnt!=temp) { daycnt=temp; temp1=1970; 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) { 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); return 0; }
u8 RTC_Get_Week(u16 year,u8 month,u8 day) { u16 temp2; u8 yearH,yearL; yearH=year/100; yearL=year%100; if (yearH>19)yearL+=100; temp2=yearL+yearL/4; temp2=temp2%7; temp2=temp2+day+table_week[month-1]; if (yearL%4==0&&month<3)temp2--; return(temp2%7); }
|
RTC.h文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #ifndef __RTC_H #define __RTC_H #include "sys.h"
typedef struct { vu8 hour; vu8 min; vu8 sec; vu16 w_year; vu8 w_month; vu8 w_date; vu8 week; }_calendar_obj; extern _calendar_obj calendar; extern u8 const mon_table[12]; void Disp_Time(u8 x,u8 y,u8 size); void Disp_Week(u8 x,u8 y,u8 size,u8 lang); u8 RTC_Init(void); u8 Is_Leap_Year(u16 year); u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); u8 RTC_Get(void); u8 RTC_Get_Week(u16 year,u8 month,u8 day); u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec); #endif
|
总结
本次使用的是STM32F103开发板,很多参考程序使用的是STM32F4,因此,其中很多定义和函数都没有!本程序其实还得添加许多头文件
例如:
1 2 3
| #include "stm32f10x_pwr.h" #include "stm32f10x_bkp.h" #include "stm32f10x_rtc.h"
|
由于开发板没有32.768KHz辅助晶振,需要等以后买了再继续测试!
注意:
F1没有F4的这个功能,F1只有一个计数器,所以需要自己根据这个计数器来计算出来得到,F4就有4个寄存器,所以可以直接读出来
相关链接(侵删)
- (stm32f103学习总结)—RTC独立定时器—实时时钟实验
- STM32F103的RTC实时时钟配置
=================我是分割线=================
欢迎到公众号来唠嗑: