(一)时钟RTC
stm32内部集成了一个秒计数器RTC,用于显示我们日常的时间,如日期年月日,时分秒等,RTC的主要原理就是进行每秒自增,如果我们知道开始记秒的开始时间,就可以计算现在的日期,但是这不需要我们计算,我们只要调用C语言库函数即可自动完成秒数到日常时间或日常时间到秒数的转换,如果只是实现秒自增的功能,那么内部任何一个计数器都可以实现,但是时钟RTC与其他计数器不同的是其可以使用备用电源保持工作,在最小板供电停止时如果有备用电源其会继续工作;这里的备用电源供电引脚为stm32最小板的1好引脚,标注为VBT;
(二)备份寄存器BKP
和时钟RTC一样,备份寄存器BKP可以在备用电源供电时保持数据不丢失,但是其却不能在备用电源和主电源同时断电时候还保持掉电不丢失,这里使用BKP主要是为了给时钟判断是否需要初始化,如果备用电源没有断开,RTC依旧在工作,那么在主电源重新供电的时候时钟就不需要初始化,直接读取其时钟值即可;
(三)RTC时钟和BKP备份寄存器
这里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的配置模式
(1)BKP初始化
BKP初始化要初始BKP的时钟,还有电源管理模块的时钟RCC_APB1Periph_PWR,以便在使用备用电源时stm32可以进行电源管理进入待机模式,这些时钟都是APB1上的外设,因此我们要调动APB1的初始化函数
void rtc_bkp_init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);
}
(2)RTC初始化
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
(3)设置和读取时间
我们可以从该时钟读取到的只有秒值,且要修改时间也只有输入对应时间的秒数才能修改,但是我们可以借助time库来计算我们常见的时间格式和对应秒数的转换,主要有两个函数和一个结构体
objectivec
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
objectivec
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();
}
objectivec
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文件如下
objectivec
#include "stm32f10x.h" // Device header
#include <time.h>
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;
}
objectivec
#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复位问题,应该和初始化一样加入备用电源是否断电的判断代码
objectivec
#include "stm32f10x.h" // Device header
#include "rtc.h"
#include "OLED.h"
#include <time.h>
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的掉电继续计时和数据不丢失