stm32内部集成了一个秒计数器RTC,用于显示我们日常的时间,如日期年月日,时分秒等,RTC的主要原理就是进行每秒自增,如果我们知道开始记秒的开始时间,就可以计算现在的日期,但是这不需要我们计算,我们只要调用C语言库函数即可自动完成秒数到日常时间或日常时间到秒数的转换,如果只是实现秒自增的功能,那么内部任何一个计数器都可以实现,但是时钟RTC与其他计数器不同的是其可以使用备用电源保持工作,在最小板供电停止时如果有备用电源其会继续工作;这里的备用电源供电引脚为stm32最小板的1好引脚,标注为VBT;
和时钟RTC一样,备份寄存器BKP可以在备用电源供电时保持数据不丢失,但是其却不能在备用电源和主电源同时断电时候还保持掉电不丢失,这里使用BKP主要是为了给时钟判断是否需要初始化,如果备用电源没有断开,RTC依旧在工作,那么在主电源重新供电的时候时钟就不需要初始化,直接读取其时钟值即可;
这里BKP主要是为了检测备用电源是否掉电的,我们的思路是在上电的时候在备份寄存器BKP上写入一个数据(不要写默认值0),如果备用电源没有断电,这个数据会一直保持,如果断电则会恢复为0,我们只要会初始化BKP和读写BKP即可,主要用到这几个函数
- void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);
- // 写入备份寄存器
-
- uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);
- // 读出备份寄存器
-
- void RTC_WaitForLastTask(void);
- // 等待上次任务完成,内部自带while循环和进入RTC的配置模式
-
- void RTC_WaitForSynchro(void);
- // 等待系统同步,自带while循环和进入RTC的配置模式
BKP初始化要初始BKP的时钟,还有电源管理模块的时钟RCC_APB1Periph_PWR,以便在使用备用电源时stm32可以进行电源管理进入待机模式,这些时钟都是APB1上的外设,因此我们要调动APB1的初始化函数
- void rtc_bkp_init()
- {
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
- }
RTC不想其他外设一样用初始化结构体来进行初始化,我们要对每一步手动初始化,初始化流程图如下
首先要开启RTC的时钟并选择对应通道,然后要写入重装寄存器和预分频值以使得计数器实现每秒自增(即频率为1Hz),当然我们还要打开对应的电源控制
如果在主电源断开但备用电源没有断开时,RTC仍然会工作,但是主电源开始供电时,程序会重启,这时会再次调用RTC的初始化函数,这时不希望的,因此我们在BKP中写入一个标志位,如果该标志位没有清零,表示备用电源和主电源没有同时断电,这时我们就不需要重新对RTC进行初始化,我们在RTC初始化中加入if判断即可
- void rtc_init()
- {
- PWR_BackupAccessCmd(ENABLE);
-
- if (BKP_ReadBackupRegister(BKP_DR1) != 1)
- {
- RCC_LSEConfig(RCC_LSE_ON);
- while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
-
- RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
- RCC_RTCCLKCmd(ENABLE);
-
- RTC_WaitForSynchro(); // wait synchronization
- RTC_WaitForLastTask(); // wait write
-
- RTC_SetPrescaler(32768-1);
- RTC_WaitForLastTask();
-
- rtc_set_time(2024, 8, 9, 16, 7, 0);
-
- BKP_WriteBackupRegister(BKP_DR1, 1);
- }
- }
由于RTC的时钟和系统时钟不是一个频率,在开启时钟后要等待时钟同步(synchronization),每次进行写操作时要等待上次任务完成;
这里选择的是外部低速时钟,其晶振为32.768KHz,因此要进行32768分频使其频率为1Hz
我们可以从该时钟读取到的只有秒值,且要修改时间也只有输入对应时间的秒数才能修改,但是我们可以借助time库来计算我们常见的时间格式和对应秒数的转换,主要有两个函数和一个结构体
- struct tm {
- int tm_sec; /* seconds after the minute, 0 to 60
- (0 - 60 allows for the occasional leap second) */
- int tm_min; /* minutes after the hour, 0 to 59 */
- int tm_hour; /* hours since midnight, 0 to 23 */
- int tm_mday; /* day of the month, 1 to 31 */
- int tm_mon; /* months since January, 0 to 11 */
- int tm_year; /* years since 1900 */
- int tm_wday; /* days since Sunday, 0 to 6 */
- int tm_yday; /* days since January 1, 0 to 365 */
- int tm_isdst; /* Daylight Savings Time flag */
- union { /* ABI-required extra fields, in a variety of types */
- struct {
- int __extra_1, __extra_2;
- };
- struct {
- long __extra_1_long, __extra_2_long;
- };
- struct {
- char *__extra_1_cptr, *__extra_2_cptr;
- };
- struct {
- void *__extra_1_vptr, *__extra_2_vptr;
- };
- };
- };
- // 时间结构体,对应秒、分、时、日、月、年、星期、天
-
- extern _ARMABI time_t mktime(struct tm * /*timeptr*/) __attribute__((__nonnull__(1)));
- // 将时间结构体转换为秒数,输入一个时间结构体指针输出其到1990年的秒数,结构体只要年月日即可
-
- extern _ARMABI struct tm *localtime(const time_t * /*timer*/) __attribute__((__nonnull__(1)));
- // 输入到1990年的秒数输出一个时间结构体指针
这里有一些规定与日常不合,这里的月是从0开始的,年是如今到1900年的年数,星期从星期天(0)开始
这里转换的是伦敦时间,要计算北京时间还要加上8小时(东八区),对应秒数加8*60*60
- void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
- {
- struct tm timer;
- unsigned int counter;
- timer.tm_year = year-1900;
- timer.tm_mon = month-1;
- timer.tm_mday = day;
- timer.tm_hour = hour;
- timer.tm_min = min;
- timer.tm_sec = sec;
- counter = mktime(&timer);
- counter -= 8*60*60;
- RTC_SetCounter(counter);
- RTC_WaitForLastTask();
- }
- struct tm* rtc_read_time()
- {
- struct tm* timer;
- unsigned int counter = RTC_GetCounter() + 8*60*60;
- timer = localtime(&counter);
- timer->tm_year += 1900;
- timer->tm_mon += 1;
- return timer;
- }
(4)封装与声明
最终.c 和 .h文件如下
- #include "stm32f10x.h" // Device header
- #include
-
- void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
-
- void rtc_bkp_init()
- {
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
- }
-
- void rtc_init()
- {
- PWR_BackupAccessCmd(ENABLE);
-
- if (BKP_ReadBackupRegister(BKP_DR1) != 1)
- {
- RCC_LSEConfig(RCC_LSE_ON);
- while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET);
-
- RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
- RCC_RTCCLKCmd(ENABLE);
-
- RTC_WaitForSynchro(); // wait synchronization
- RTC_WaitForLastTask(); // wait write
-
- RTC_SetPrescaler(32768-1);
- RTC_WaitForLastTask();
-
- rtc_set_time(2024, 8, 9, 16, 7, 0);
-
- BKP_WriteBackupRegister(BKP_DR1, 1);
- }
- }
-
- void rtc_set_time(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec)
- {
- struct tm timer;
- unsigned int counter;
- timer.tm_year = year-1900;
- timer.tm_mon = month-1;
- timer.tm_mday = day;
- timer.tm_hour = hour;
- timer.tm_min = min;
- timer.tm_sec = sec;
- counter = mktime(&timer);
- counter -= 8*60*60;
- RTC_SetCounter(counter);
- RTC_WaitForLastTask();
- }
-
- struct tm* rtc_read_time()
- {
- struct tm* timer;
- unsigned int counter = RTC_GetCounter() + 8*60*60;
- timer = localtime(&counter);
- timer->tm_year += 1900;
- timer->tm_mon += 1;
- return timer;
- }
- #ifndef __RTC_H__
- #define __RTC_H__
-
- void rtc_bkp_init(void);
- void rtc_init(void);
- void rtc_set_time(unsigned int year, unsigned int mon, unsigned int day, unsigned int hour, unsigned int min, unsigned int sec);
- struct tm* rtc_read_time(void);
-
- #endif
只要在主函数进行对应初始化和调用读取函数即可,初始化已经包含了开始时间的设定,如果要重新设定开始时间,可以在初始化中更改,也可以在主函数中使用rtc_set_time函数来修改,但是要注意上电后stm32复位问题,应该和初始化一样加入备用电源是否断电的判断代码
- #include "stm32f10x.h" // Device header
- #include "rtc.h"
- #include "OLED.h"
- #include
-
- int main()
- {
- struct tm* time_inf;
- OLED_Init();
- rtc_bkp_init();
- rtc_init();
- OLED_ShowString(1, 1, "time:");
- OLED_ShowString(2, 1, "xxxx-xx-xx");
- OLED_ShowString(3, 1, "xx:xx:xx");
- while(1)
- {
- time_inf = rtc_read_time();
- OLED_ShowNum(2, 1, time_inf->tm_year, 4);
- OLED_ShowNum(2, 6, time_inf->tm_mon, 2);
- OLED_ShowNum(2, 9, time_inf->tm_mday, 2);
- OLED_ShowNum(3, 1, time_inf->tm_hour, 2);
- OLED_ShowNum(3, 4, time_inf->tm_min, 2);
- OLED_ShowNum(3, 7, time_inf->tm_sec, 2);
- }
- return 0;
- }
通过实现一个时钟,我们学习了RTC和BKP两个可以使用备用电源供电工作的外设,了解了计数器RTC的原理和备份寄存器BKP的原理,通过备份寄存器实现了RTC的掉电继续计时和数据不丢失