之前在 学习笔记10文章 做了一个简易的,使用定时器计时的简单时钟,现在使用RTC实时时钟同步代替定时器来实现一下OLED手表日历,接着上个实验文章进行完善~~
文章提供源码、测试工程下载、测试效果图。
目录
RTC实时时钟:
简介:
主要特性:
RTC框图:
UNIX时间戳:
程序设计:
配置RTC初始化过程分为以下几步:
RTC日历初始化相关代码:
主函数代码:
测试效果:
测试工程下载:
RTC实时时钟:
简介:
STM32F10x-中文参考手册 有关于RTC实时时钟的介绍是从 P308页开始的:
RTC时钟与DS1302时钟芯片不同,DS1302时钟芯片是通过读取寄存器实现读取年月日等信息的
而RTC时钟是作为STM32F103单片机中的一个时钟定时器模块(其余系列不一定),主电源掉电后会继续使用 后备电池(由 Vbat 引脚接 电源 继续供电 )继续运行的模块,它本质是一个32位的向上计数器。
因此我们在STM32F103单片机中 读取RTC时 本质是得到一个计数值,对其进行处理。
主要特性:
我们一般在LSE接一个 32.768k ( 2^15=32768 )的晶振作为RTC的时钟源,便于分频产生1HZ的时钟基准,
RTC框图:
我们从RTC的框图可以了解到,它的秒、闹钟都是有中断的,但溢出事件(计数到达最大值)时没有中断。
UNIX时间戳:
在设计到日历时,我们就需要注意这个时间戳:
程序设计:
首先注意一下这些头文件,都是需要用到的,别忘记了添加:
#include "stm32f10x_rtc.h" //RTC相关库
#include "stm32f10x_pwr.h"
#include "stm32f10x_bkp.h"
配置RTC初始化过程分为以下几步:
1.配置中断,配置中断优先级
2.检查寄存器BKP_DR1,根据其值确定是否为第一次上电,V BAT是否有后备电池,第一次上电就要初始化时间。(V BAT没电池 以及 V BAT有电池 但寄存器没被写入值都算第一次上电)(后备寄存器区由V BAT引脚供电,因此当V BAT引脚有电时主电源断不会使得后备寄存器区的寄存器BKP_DR1的值丢失 )
3.定义时间结构体,用来存放改变时间等.
4.复制编写RTC_Configuration()函数,配置相关时钟源,外部时钟还是内部,分频等。
5.编写Time_Adjust()函数,给RTC时钟附上初始值(通过将 小时、分钟和秒都转换成秒 加起来 来实现设置当前计数值)(小时是 24小时制)
此处需要注意一个小细节:
就是我的程序设计使用上没用到串口,因此没有初始化串口,但在移植官方代码时,他们使用串口打印测试,各阶段初始化情况,起初我保留了这些printf()语句,认为会跳过,但实际上程序会因为没有初始化串口而在printf那卡住~·
RTC日历初始化相关代码:
#include "RTC.h"
void RTC_init(void)
{
NVIC_InitTypeDef NVIC_Initstructure;
/*1. NVIC 中断配置 */
/*Configure one bit for preemption priority 中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/*Enable the RTC Interrupt */
NVIC_Initstructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_Initstructure.NVIC_IRQChannelSubPriority = 0;
NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init (&NVIC_Initstructure);
/*检查V BAT引脚是否为第一次上电(是否有后备电源)没有就要初始化时间*/
/*在启动时检查备份寄存器BKP_DR1,如果内容不是0xA5A5,
则需重新配置时间并询问用户调整时间*/
if (BKP_ReadBackupRegister( BKP_DR1) != 0xA5A5)
{
//配置RTC与设置初值:
RTC_Configuration();
Time_Adjust(&time1);
/*向BKP_DR1寄存器写入标志,说明RTC已在运行,只要后备有电,这个值就不会掉*/
BKP_WriteBackupRegister( BKP_DR1, 0xA5A5);
}
else
{
/* 使能 PWR 和 Backup 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* 允许访问 Backup 区域 */
PWR_BackupAccessCmd(ENABLE);
/*LSE启动无需设置新时钟*/
#ifdef RTC_CLOCK_SOURCE_LSI
/* 使能 LSI */
RCC_LSICmd(ENABLE);
/* 等待 LSI 准备好 */
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
{}
#endif
/*检查是否是系统掉电重启*/
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{ ;}
/*检查是否Reset复位引脚引起的 复位*/
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{;}
/*等待寄存器同步*/
RTC_WaitForSynchro();
/*允许RTC秒中断*/
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/*等待上次RTC寄存器写操作完成*/
RTC_WaitForLastTask();
}
/*定义了时钟输出宏,则配置校正时钟输出到PC13*/
#ifdef RTCClockOutput_Enable
/* 使能 PWR 和 Backup 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* 允许访问 Backup 区域 */
PWR_BackupAccessCmd(ENABLE);
/* 禁止 Tamper 引脚 */
/* 要输出 RTCCLK/64 到 Tamper 引脚, tamper 功能必须禁止 */
BKP_TamperPinCmd(DISABLE);
/* 使能 RTC 时钟输出到 Tamper 引脚 */
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
/* 清除复位标志 flags */
RCC_ClearFlag();
}
/*
* 函数名:RTC_Configuration
* 描述 :配置RTC
* 输入 :无
* 输出 :无
* 调用 :外部调用
*/
void RTC_Configuration(void)
{
/* 使能 PWR 和 Backup 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* 允许访问 Backup 区域 */
PWR_BackupAccessCmd(ENABLE);
/* 复位 Backup 区域 */
BKP_DeInit();
//使用外部时钟还是内部时钟(在bsp_rtc.h文件定义)
//使用外部时钟时,在有些情况下晶振不起振
//批量产品的时候,很容易出现外部晶振不起振的情况,不太可靠
#ifdef RTC_CLOCK_SOURCE_LSE
/* 使能 LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* 等待 LSE 准备好 */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
/* 选择 LSE 作为 RTC 时钟源 */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* 使能 RTC 时钟 */
RCC_RTCCLKCmd(ENABLE);
/* 等待 RTC 寄存器 同步
* 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
*/
RTC_WaitForSynchro();
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
/* 使能 RTC 秒中断 */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
/* 设置 RTC 分频: 使 RTC 周期为1s */
/* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) = 1HZ */
RTC_SetPrescaler(32767);
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
#else
/* 使能 LSI */
RCC_LSICmd(ENABLE);
/* 等待 LSI 准备好 */
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET){}
/* 选择 LSI 作为 RTC 时钟源 */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
/* 使能 RTC 时钟 */
RCC_RTCCLKCmd(ENABLE);
/* 等待 RTC 寄存器 同步
* 因为RTC时钟是低速的,内环时钟是高速的,所以要同步
*/
RTC_WaitForSynchro();
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
/* 使能 RTC 秒中断 */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
/* 设置 RTC 分频: 使 RTC 周期为1s ,LSI约为40KHz */
/* RTC period = RTCCLK/RTC_PR = (40 KHz)/(40000-1+1) = 1HZ */
RTC_SetPrescaler(40000-1);
/* 确保上一次 RTC 的操作完成 */
RTC_WaitForLastTask();
#endif
}
#ifndef _RTC_h_
#define _RTC_h_
#include "headfire.h"
//使用LSE外部时钟 或 LSI内部时钟
//#define RTC_CLOCK_SOURCE_LSE
#define RTC_CLOCK_SOURCE_LSI
//北京时间的时区秒数差
#define TIME_ZOOM (8*60*60)
//初始化时间结构体
extern struct rtc_time time1;
void RTC_init(void); //初始化 与 配置RTC
void RTC_Configuration(void); //配置RTC
#endif
#include "RTC_day.h"
/*时间结构体,初始化默认时间 2023-08-21 17:55:55*/
struct rtc_time time1= {55,55,17,21,8,2023,1} ;//初始化时间结构体
uint16_t BMP_cnt,BMP_FLAG;
#define FEBRUARY 2
#define STARTOFTIME 1970
#define SECDAY 86400L /* 一天有多少s */
#define SECYR (SECDAY * 365)
#define leapyear(year) ((year) % 4 == 0)
#define days_in_year(a) (leapyear(a) ? 366 : 365)
#define days_in_month(a) (month_days[(a) - 1])
static int month_days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
/*
* This only works for the Gregorian calendar - i.e. after 1752 (in the UK)
*/
/*计算公历*/
void GregorianDay(struct rtc_time * tm)
{
int leapsToDate;
int lastYear;
int day;
int MonthOffset[] = { 0,31,59,90,120,151,181,212,243,273,304,334 };
lastYear=tm->tm_year-1;
/*计算从公元元年到计数的前一年之中一共经历了多少个闰年*/
leapsToDate = lastYear/4 - lastYear/100 + lastYear/400;
/*如若计数的这一年为闰年,且计数的月份在2月之后,则日数加1,否则不加1*/
if((tm->tm_year%4==0) &&
((tm->tm_year%100!=0) || (tm->tm_year%400==0)) &&
(tm->tm_mon>2)) {
/*
* We are past Feb. 29 in a leap year
*/
day=1;
} else {
day=0;
}
day += lastYear*365 + leapsToDate + MonthOffset[tm->tm_mon-1] + tm->tm_mday; /*计算从公元元年元旦到计数日期一共有多少天*/
tm->tm_wday=day%7;
}
/* Converts Gregorian date to seconds since 1970-01-01 00:00:00.
* Assumes input in normal date format, i.e. 1980-12-31 23:59:59
* => year=1980, mon=12, day=31, hour=23, min=59, sec=59.
*
* [For the Julian calendar (which was used in Russia before 1917,
* Britain & colonies before 1752, anywhere else before 1582,
* and is still in use by some communities) leave out the
* -year/100+year/400 terms, and add 10.]
*
* This algorithm was first published by Gauss (I think).
*
* WARNING: this function will overflow on 2106-02-07 06:28:16 on
* machines were long is 32-bit! (However, as time_t is signed, we
* will already get problems at other places on 2038-01-19 03:14:08)
*
*/
u32 mktimev(struct rtc_time *tm)
{
if (0 >= (int) (tm->tm_mon -= 2)) { /* 1..12 -> 11,12,1..10 */
tm->tm_mon += 12; /* Puts Feb last since it has leap day */
tm->tm_year -= 1;
}
return (((
(u32) (tm->tm_year/4 - tm->tm_year/100 + tm->tm_year/400 + 367*tm->tm_mon/12 + tm->tm_mday) +
tm->tm_year*365 - 719499
)*24 + tm->tm_hour /* now have hours */
)*60 + tm->tm_min /* now have minutes */
)*60 + tm->tm_sec; /* finally seconds */
}
void to_tm(u32 tim, struct rtc_time * tm)
{
register u32 i;
register long hms, day;
day = tim / SECDAY; /* 有多少天 */
hms = tim % SECDAY; /* 今天的时间,单位s */
/* Hours, minutes, seconds are easy */
tm->tm_hour = hms / 3600;
tm->tm_min = (hms % 3600) / 60;
tm->tm_sec = (hms % 3600) % 60;
/* Number of years in days */ /*算出当前年份,起始的计数年份为1970年*/
for (i = STARTOFTIME; day >= days_in_year(i); i++) {
day -= days_in_year(i);
}
tm->tm_year = i;
/* Number of months in days left */ /*计算当前的月份*/
if (leapyear(tm->tm_year)) {
days_in_month(FEBRUARY) = 29;
}
for (i = 1; day >= days_in_month(i); i++) {
day -= days_in_month(i);
}
days_in_month(FEBRUARY) = 28;
tm->tm_mon = i;
/* Days are what is left over (+1) from all that. *//*计算当前日期*/
tm->tm_mday = day + 1;
/*
* Determine the day of week
*/
GregorianDay(tm);
}
/*
* 函数名:Time_Adjust
* 描述 :时间调节
* 输入 :用于读取RTC时间的结构体指针(北京时间)
* 输出 :无
* 调用 :外部调用
*/
void Time_Adjust(struct rtc_time* tm)
{
/* 等待确保上一次操作完成 */
RTC_WaitForLastTask();
//更新日期
GregorianDay(tm);
// /* 设置当前时间 通过将 小时、分钟和秒都转换成秒 加起来 来实现设置当前计数值 (小时是 24小时制) */
// RTC_SetCounter(tm->tm_hour*3600+tm->tm_min*60+tm->tm_sec); //Tmp HH*3600 Tmp MM*60 Tmp SS
/* 由日期计算时间戳并写入到RTC计数寄存器 */
RTC_SetCounter(mktimev(tm)-TIME_ZOOM);
/* 等待确保上一次操作完成 */
RTC_WaitForLastTask();
}
void Time_Display(uint32_t TimeVar,struct rtc_time *tm)
{
char buf[20]; //用于暂存oled数据
uint32_t BJ_TimeVar;
/* 把标准时间转换为北京时间*/
BJ_TimeVar =TimeVar + TIME_ZOOM;
to_tm(BJ_TimeVar,tm);/*把定时器的值转换为北京时间*/
//打印年
sprintf(buf,"%d",tm->tm_year);
OLED_ShowString(75,0,(u8 *)buf,16);
OLED_ShowCHinese(75+16*2,0,0); //打印中文“年”
//打印时间:
sprintf(buf,"%02d:%02d:%02d",tm->tm_hour,tm->tm_min,tm->tm_sec);
OLED_ShowString(64,2,(u8 *)buf,16);
//打印日期:
sprintf(buf,"%02d",tm->tm_mon);
OLED_ShowString(75,4,(u8 *)buf,12); //打印月
OLED_ShowCHinese_small(75+14,4,0); //打印中文月
sprintf(buf,"%02d",tm->tm_mday);
OLED_ShowString(75+14+12,4,(u8 *)buf,12); //打印日
OLED_ShowCHinese_small(75+14+12+14,4,1); //打印中文日
//打印星期:
OLED_ShowCHinese(70,5,1); //打印中文“星”
OLED_ShowCHinese(70+16,5,2); //打印中文“期”
sprintf(buf,"%d",tm->tm_wday);
OLED_ShowString(70+16+16,5,(u8 *)buf,16);
}
#ifndef _RTC_day_h_
#define _RTC_day_h_
#include "headfire.h"
typedef unsigned int u32;
//定义时间结构体
struct rtc_time {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
};
//初始化时间结构体
extern struct rtc_time time1;
extern uint16_t BMP_cnt,BMP_FLAG;
void GregorianDay(struct rtc_time * tm);
uint32_t mktimev(struct rtc_time *tm);
void to_tm(uint32_t tim, struct rtc_time * tm);
void Time_Display(uint32_t TimeVar,struct rtc_time *tm);
void Time_Adjust(struct rtc_time* tm);
#endif
主函数代码:
#include "main.h"
//时间结构体 在RTC_day.h中初始化了
//时间结构体定义在 RTC_day.h
//刷新时间标志
uint16_t TimeDisplay_cnt,TimeDisplay;
int main(void)
{
init_ALL(); //初始化所有函数
while(1)
{
if(TimeDisplay==1)
{
Time_Display(RTC_GetCounter(),&time1);
TimeDisplay=0;
}
switch(BMP_FLAG)
{
case 1:OLED_DrawBMP(0,0,64,8,BMP1); break;
case 2:OLED_DrawBMP(0,0,64,8,BMP2); break;
case 3:OLED_DrawBMP(0,0,64,8,BMP3); break;
case 4:OLED_DrawBMP(0,0,64,8,BMP4); break;
case 5:OLED_DrawBMP(0,0,64,8,BMP5); break;
case 6:OLED_DrawBMP(0,0,64,8,BMP6); break;
case 7:OLED_DrawBMP(0,0,64,8,BMP7); break;
case 8:OLED_DrawBMP(0,0,64,8,BMP8); break;
case 9:OLED_DrawBMP(0,0,64,8,BMP9); break;
case 10:OLED_DrawBMP(0,0,64,8,BMP10); break;
case 11:OLED_DrawBMP(0,0,64,8,BMP11); break;
case 12:OLED_DrawBMP(0,0,64,8,BMP12); break;
case 13:OLED_DrawBMP(0,0,64,8,BMP13); break;
case 14:OLED_DrawBMP(0,0,64,8,BMP14); break;
case 15:OLED_DrawBMP(0,0,64,8,BMP15); break;
case 16:OLED_DrawBMP(0,0,64,8,BMP16); break;
case 17:OLED_DrawBMP(0,0,64,8,BMP17); break;
case 18:OLED_DrawBMP(0,0,64,8,BMP18); break;
case 19:OLED_DrawBMP(0,0,64,8,BMP19); break;
case 20:OLED_DrawBMP(0,0,64,8,BMP20); break;
case 21:OLED_DrawBMP(0,0,64,8,BMP21); break;
case 22:OLED_DrawBMP(0,0,64,8,BMP22); break;
case 23:OLED_DrawBMP(0,0,64,8,BMP23); break;
case 24:OLED_DrawBMP(0,0,64,8,BMP24); break;
case 25:OLED_DrawBMP(0,0,64,8,BMP25); break;
case 26:OLED_DrawBMP(0,0,64,8,BMP26); break;
case 27:OLED_DrawBMP(0,0,64,8,BMP27); break;
case 28:OLED_DrawBMP(0,0,64,8,BMP28); break;
}
}
}
//初始化所有函数:
void init_ALL(void)
{
SysTick_Init(72); //初始化滴答计时器
Timer2_Init(); //初始化定时器2
i2c_GPIO_Config(); //IIC初始化
OLED_Init(); //初始化OLED屏幕
OLED_Clear(); //清空屏幕数据
RTC_init();
}
//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
if(++TimeDisplay_cnt==100) //定时器刷新时间
{
TimeDisplay_cnt=0;TimeDisplay=1;
}
if(++BMP_cnt==10) //定时器 刷新太空人图片
{
BMP_cnt=0;BMP_FLAG++;
if(BMP_FLAG==29){BMP_FLAG=1;}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清出中断寄存器标志位,用于退出中断
}
}
//RTC每秒进的中断服务函数
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* Clear the RTC Second interrupt */
RTC_ClearITPendingBit(RTC_IT_SEC);
// /* Enable time update */
// TimeDisplay = 1;
//
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
}
测试效果:
测试工程下载:
https://download.csdn.net/download/qq_64257614/88237879?spm=1001.2014.3001.5503