文章目录
一、STM32F103中的RTC(实时时钟)简介
1、RTC(Real Time Clock)实时时钟
RTC是一个独立的定时器。 RTC模块拥有一个连续计数的计数器,在相应的软件配置下,可以提供时钟日历的功能。修改计数器的值可以重新设置当前时间和日期 RTC还包含用于管理低功耗模式的自动唤醒单元。
2、RTC主要特征
- 可编程的预分频系数:分频系数高为220;
- 32位的可编程计数器,可用于较长时间段的测量;
- 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上);
- 三种RTC的时钟源:
- HSE时钟除以128;
- LSE振荡器时钟;
- LSI振荡器时钟;
- 2个独立的复位类型:
- APB1接口由系统复位;
- RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位;
- 3个专门的可屏蔽中断:
- 闹钟中断:用于产生一个软件可编程的闹钟中断;
- 秒中断:用于产生一个可编程的周期性中断信号(长可达1秒);
- 溢出中断:指示内部可编程计数器溢出并回转为0的状态。
3、RTC组成
RTC时钟的框图可以分为两个部分:
-
APB1:用于和 APB1 总线相连。 此单元还包含一组 16 位寄存器,可通过 APB1 总线对其进行读写操作。APB1 接口由 APB1 总 线时钟驱动,用来与 APB1 总线连接。
-
RTC核心接口:由一组可编程计数器组成,分为
RTC的预分频模块
和一个32位的可编程计数器(RTC_CNT)
。-
RTC 的 预分频模块:
它可编程产生 1 秒的 RTC 时间基准 TR_CLK。RTC 的预分频模块包含了一个 20 位的可编程分频器(RTC 预分频器)。如果在 RTC_CR 寄存器中设置了相应的允许位,则在每个 TR_CLK 周期中 RTC 产生一个中断(秒中断)。 -
一个 32 位的可编程计数器:
可被初始化为当前的系统时间,一个 32 位的时钟计数器,按秒钟计算,可以记 录 4294967296 秒,约合 136 年左右,作为一般应用,这已经是足够了的。
-
4、RTC寄存器
- RTC控制寄存器高位——RTC_CRH
用于配置三个专门的可屏蔽中断:溢出中断、闹钟中断、秒中断使能
-
RTC控制寄存器低位——RTC_CRL
一般用到该寄存器的3、4、5位
-
RTC预分频装载寄存器(RTC_PRLH,RTC_PRLL)
用于配置RTC时钟的分频数
-
RTC预分频余数寄存器 (RTC_DIVH, RTC_DIVL)
用于获得余数,也就是获取更精确的计时,比如:0.1s ,0.01 s等。该寄存器为只读寄存器,其值在RTC_PRL或RTC_CNT寄存器中的值发生改变后,由硬件重新装载。
-
RTC计数寄存器——RTC_CNTX,RTC_CNTL
用于存放计数器内的计数值,也就是用来记录时钟时间。该寄存器由 2 个 16 位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位
,当进行读操作时,直接返回计数器内的计数值(系统时间)。
-
RTC 计数器寄存器——RTC 闹钟寄存器(RTC_ALRH、RTC_ALRL)
RTC时钟中断控制寄存器。该寄存器也是由 2 个 16 位的寄存器组成 RTC_ALRH 和 RTC_ALRL,也就是32位,当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟中断。
5、配置RTC步骤
-
使能PWR和BKP时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
-
使能后备寄存器访问
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
-
复位备份区域,开启外部低速振荡器
BKP_DeInit();//复位备份区域
-
配置RTC时钟源,使能RTC时钟
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟(RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128) RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
-
设置RTC预分频系数:RTC_SetPrescaler()
RTC_EnterConfigMode();/// 允许配置 RTC_SetPrescaler(32767); //设置RTC预分频的值 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
-
设置时间:RTC_SetCounter()
RTC_EnterConfigMode();/// 允许配置 void RTC_SetCounter(uint32_t CounterValue); RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
-
开启相关中断(可选)
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中断
-
编写中断服务函数
RTC_IRQHandler();
-
部分操作要等待写操作完成和同步
RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成 RTC_WaitForSynchro(); //等待RTC寄存器同步
二、读取内部时钟并输出
1、任务要求
读取STM32F103C8T6 内部的时钟(年月日时分秒),日历(星期x),1秒周期,通过串口输出到PC上位机;
2、程序设计要点
初始化 RTC 外设;
设置时间以及添加配置标志;
获取当前时间;
3、代码实现
-
定义时间结构体
//时间结构体 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; //日历结构体
-
对RTC进行初始化,配置实时时钟
设置初始时间为2023/11/18/16:49:00
//实时时钟配置 //初始化RTC时钟,同时检测时钟是否工作正常 //BKP->DR1用于保存是否第一次配置的设置 //返回0:正常 //其他:错误代码 u8 RTC_Init(void) { //检查是不是第一次配置时钟 u8 temp=0; RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 BKP_DeInit(); //复位备份区域 RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪 { temp++; Delay_ms(10); } if(temp>=250)return 1;//初始化时钟失败,晶振有问题 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_WaitForSynchro(); //等待RTC寄存器同步 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_EnterConfigMode();/// 允许配置 RTC_SetPrescaler(32767); //设置RTC预分频的值 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_Set(2023,11,18,16,49,00); //设置时间 2023/22/18/16:49 RTC_ExitConfigMode(); //退出配置模式 BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据 RTC_NVIC_Config();//RCT中断分组设置 RTC_Get();//更新时间 return 0; //ok }
-
中断服务函数
//RTC时钟中断 //每秒触发一次 //extern u16 tcnt; 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(); }
-
主函数
#include "sys.h" #include "usart.h" #include "rtc.h" #include "stm32f10x.h" #include "delay.h" #include "bmp.h" #include "oled.h" void main(void) { u8 t; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* 设置NVIC中断分组2:2位抢占优先级,2位响应优先级 */ //delay_init(); /* 延时函数初始化 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); /* 关闭jtag,使能SWD,可以用SWD模式调试 */ Delay_ms(500); /* 等待稳定 */ uart_init(115200); IIC_Init(); RTC_Init(); //RTC初始化 while (1) { if (t != calendar.sec) { t = calendar.sec; printf("%d-%02d-%02d %02d:%02d:%02d\r\n", calendar.w_year, calendar.w_month, calendar.w_date, calendar.hour, calendar.min, calendar.sec); switch (calendar.week) { case 0: printf("Sunday \r\n"); break; case 1: printf("Monday \r\n"); break; case 2: printf("Tuesday \r\n"); break; case 3: printf("Wednesday\r\n"); break; case 4: printf("Thursday \r\n"); break; case 5: printf("Friday \r\n"); break; case 6: printf("Saturday \r\n"); break; } } Delay_ms(10); } }
4、完整代码
具体代码过长,我打包放在了博客结尾
5、硬件实现
三、显示年月份时分秒、日历和实时温度、湿度
1、任务要求
读取AHT20的温度和湿度,通过OLED,把年月份时分秒、日历和实时温度、湿度显示出来,2秒周期。
2、代码实现
温湿度读取的具体代码分析可以参考我之前的博客:STM32+OLED屏显应用实例
-
在oledfont.h文件中的**cfont16[]**函数中添加需要用得到字模
const typFNT_GB16 cfont16[] = { /*-- 文字: 温 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "温",0x00,0x00,0x23,0xF8,0x12,0x08,0x12,0x08,0x83,0xF8,0x42,0x08,0x42,0x08,0x13,0xF8,0x10,0x00,0x27,0xFC,0xE4,0xA4,0x24,0xA4,0x24,0xA4,0x24,0xA4,0x2F,0xFE,0x00,0x00, /*-- 文字: 湿 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "湿",0x00,0x00,0x27,0xF8,0x14,0x08,0x14,0x08,0x87,0xF8,0x44,0x08,0x44,0x08,0x17,0xF8,0x11,0x20,0x21,0x20,0xE9,0x24,0x25,0x28,0x23,0x30,0x21,0x20,0x2F,0xFE,0x00,0x00, /*-- 文字: 度 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "度",0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x3F,0xFC,0x22,0x20,0x22,0x20,0x23,0xE0,0x20,0x00,0x2F,0xF0,0x24,0x10,0x42,0x20,0x41,0xC0,0x86,0x30,0x38,0x0E, /*-- 文字: 显 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "显",0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x04,0x40,0x44,0x44,0x24,0x44,0x14,0x48,0x14,0x50,0x04,0x40,0xFF,0xFE,0x00,0x00, /*-- 文字: 示 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "示",0x00,0x00,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x41,0x02,0x81,0x02,0x05,0x00,0x02,0x00, /*-- 文字: 星 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "星",0x00,0x00,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x01,0x00,0x11,0x00,0x1F,0xF8,0x21,0x00,0x41,0x00,0x1F,0xF0,0x01,0x00,0x01,0x00,0x7F,0xFC,0x00,0x00, /*-- 文字: 期 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "期",0x22,0x00,0x22,0x7C,0x7F,0x44,0x22,0x44,0x22,0x44,0x3E,0x7C,0x22,0x44,0x22,0x44,0x3E,0x44,0x22,0x7C,0x22,0x44,0xFF,0x44,0x04,0x84,0x22,0x84,0x41,0x14,0x82,0x08, /*-- 文字: 时 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "时",0x00,0x08,0x00,0x08,0x7C,0x08,0x44,0x08,0x45,0xFE,0x44,0x08,0x44,0x08,0x7C,0x08,0x44,0x88,0x44,0x48,0x44,0x48,0x44,0x08,0x7C,0x08,0x44,0x08,0x00,0x28,0x00,0x10, /*-- 文字: 分 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "分",0x00,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10,0x20,0x08,0x40,0x04,0x9F,0xE2,0x04,0x20,0x04,0x20,0x04,0x20,0x08,0x20,0x08,0x20,0x10,0x20,0x21,0x40,0x40,0x80, /*-- 文字: 秒 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ "秒",0x08,0x20,0x1C,0x20,0xF0,0x20,0x10,0xA8,0x10,0xA4,0xFC,0xA2,0x11,0x22,0x31,0x20,0x3A,0x24,0x54,0x24,0x54,0x28,0x90,0x08,0x10,0x10,0x10,0x20,0x10,0xC0,0x13,0x00, //以及文字:一二三四五六年月日 };
-
主函数程序
void main(void) { u8 t; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); /* 设置NVIC中断分组2:2位抢占优先级,2位响应优先级 */ //delay_init(); /* 延时函数初始化 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); /* 关闭jtag,使能SWD,可以用SWD模式调试 */ Delay_ms(500); /* 等待稳定 */ uart_init(115200); IIC_Init(); RTC_Init(); //RTC初始化 OLED_Init(); //初始化OLED OLED_Clear(0); //清屏(全黑) while (1) { read_AHT20_once(); OLED_Clear(0); if (t != calendar.sec) { t = calendar.sec; GUI_ShowNum(00,00,calendar.w_year,4,16,1); GUI_ShowCHinese(32,00,16,"年",1); GUI_ShowNum(48,00,calendar.w_month,2,16,1); GUI_ShowCHinese(64,00,16,"月",1); GUI_ShowNum(80,00,calendar.w_date,2,16,1); GUI_ShowCHinese(96,00,16,"日",1); GUI_ShowNum(00,20,calendar.hour,2,16,1); GUI_ShowCHinese(16,20,16,"时",1); GUI_ShowNum(32,20,calendar.min,2,16,1); GUI_ShowCHinese(48,20,16,"分",1); GUI_ShowNum(64,20,calendar.sec,2,16,1); GUI_ShowCHinese(80,20,16,"秒",1); switch (calendar.week) { case 0: GUI_ShowCHinese(00,40,16,"星期日",1); //printf("Sunday \r\n"); break; case 1: GUI_ShowCHinese(00,40,16,"星期一",1); //printf("Monday \r\n"); break; case 2: GUI_ShowCHinese(00,40,16,"星期二",1); //printf("Tuesday \r\n"); break; case 3: GUI_ShowCHinese(00,40,16,"星期三",1); //printf("Wednesday\r\n"); break; case 4: GUI_ShowCHinese(00,40,16,"星期四",1); //printf("Thursday \r\n"); break; case 5: GUI_ShowCHinese(00,40,16,"星期五",1); //printf("Friday \r\n"); break; case 6: GUI_ShowCHinese(00,40,16,"星期六",1); //printf("Saturday \r\n"); break; } } Delay_ms(1500); Delay_ms(1500); OLED_Clear(0); }
3、完整代码
具体工程文件在博客结尾。
4、硬件实现
四、参考链接
1、【STM32】RTC实时时钟,步骤超细详解,一文看懂RTC
2、STM32F103C8T6配置RTC显示年月日时分秒(日历)
五、完整工程
链接:https://pan.baidu.com/s/1HfljcwSDTqAPlSdqpR9IkQ
提取码:rhq4