前言
rtc实时时钟是一个很重要的内容,今天要配置的时RTC唤醒中断,它在很多地方都会被用到。今天需要配置的东西会有点多,代码长度也会比较长。
一、rtc实时时钟
1.介绍
实时时钟 (RTC) 是一个独立的 BCD 定时器/计数器。RTC 提供一个日历时钟、两个可编程 闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC 还包含用于管理低功耗模式的自动唤醒单元。
两个 32 位寄存器包含二进码十进数格式 (BCD) 的秒、分钟、小时(12 或 24 小时制)、星 期几、日期、月份和年份。此外,还可提供二进制格式的亚秒值。
系统可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。并且还可以进行夏令时 补偿。
其它 32 位寄存器还包含可编程的闹钟亚秒、秒、分钟、小时、星期几和日期。
此外,还可以使用数字校准功能对晶振精度的偏差进行补偿。
上电复位后,所有 RTC 寄存器都会受到保护,以防止可能的非正常写访问。
无论器件状态如何(运行模式、低
2.主要特性
● 包含亚秒、秒、分钟、小时(12/24 小时制)、星期几、日期、月份和年份的日历。
● 软件可编程的夏令时补偿。
● 两个具有中断功能的可编程闹钟。可通过任意日历字段的组合驱动闹钟。
● 自动唤醒单元,可周期性地生成标志以触发自动唤醒中断。
● 参考时钟检测:可使用更加精确的第二时钟源(50 Hz 或 60 Hz)来提高日历的精确度。
● 利用亚秒级移位特性与外部时钟实现精确同步。
● 可屏蔽中断/事件:
— 闹钟 A
— 闹钟 B
— 唤醒中断
— 时间戳
— 入侵检测
● 数字校准电路(周期性计数器调整)
— 精度为 5 ppm
— 精度为 0.95 ppm,在数秒钟的校准窗口中获得
● 用于事件保存的时间戳功能(1 个事件)
● 入侵检测:
— 2 个带可配置过滤器和内部上拉的入侵事件
● 20 个备份寄存器(80 字节)。发生入侵检测事件时,将复位备份寄存器。
3.RTC框图
二、使用步骤
1.RTC初始化
void RTC_init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
//使能lse震荡时钟
RCC_LSEConfig(RCC_LSE_ON);
//等待外部晶振
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
//选择lse外部震荡时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能rtc的硬件时钟
RCC_RTCCLKCmd(ENABLE);
//等待所有寄存器就绪
RTC_WaitForSynchro();
/* ck_spre(1Hz) = RTCCLK(LSI) /(uwAsynchPrediv + 1)*(uwSynchPrediv + 1)*/
RTC_InitStructure.RTC_AsynchPrediv = 0x7F; //异步分频
RTC_InitStructure.RTC_SynchPrediv = 0xFF; //同步分频
RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; //时间格式
RTC_Init(&RTC_InitStructure);
/* Set the date: Friday January 11th 2013 */
RTC_DateStructure.RTC_Year = 0x22;
RTC_DateStructure.RTC_Month = RTC_Month_October;
RTC_DateStructure.RTC_Date = 0x28;
RTC_DateStructure.RTC_WeekDay = RTC_Weekday_Friday;
RTC_SetDate(RTC_Format_BCD, &RTC_DateStructure);
RTC_TimeStructure.RTC_H12 = RTC_H12_PM;
RTC_TimeStructure.RTC_Hours = 0x14;
RTC_TimeStructure.RTC_Minutes = 0x20;
RTC_TimeStructure.RTC_Seconds = 0x00;
RTC_SetTime(RTC_Format_BCD, &RTC_TimeStructure);
//关闭唤醒中断
RTC_WakeUpCmd(DISABLE);
//为唤醒功能选择rtc配置好的时钟源
RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits);
//设置计数值
RTC_SetWakeUpCounter(0);
//清除rtc唤醒中断标志
RTC_ClearITPendingBit(RTC_IT_WUT);
//使能rtc唤醒中断
RTC_ITConfig(RTC_IT_WUT,ENABLE);
//使能唤醒中断
RTC_WakeUpCmd(ENABLE);
EXTI_ClearITPendingBit(EXTI_Line22);
EXTI_InitStructure.EXTI_Line = EXTI_Line22;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
上面是初始化代码,选择LSE为震荡时钟,配置年月日时分秒、唤醒中断、中断优先级。
2.唤醒中断函数
void RTC_WKUP_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_WUT) == SET)
{
g_rtc_wakeup_event=1;
//清空标志位
RTC_ClearITPendingBit(RTC_IT_WUT);
EXTI_ClearITPendingBit(EXTI_Line22);
}
}
中断只改变一个变量的值
3.完整代码
#include "stm32f4xx.h"
#include "sys.h"
#include <stdio.h>
static GPIO_InitTypeDef GPIO_InitStructure;
static NVIC_InitTypeDef NVIC_InitStructure;
static USART_InitTypeDef USART_InitStructure;
static RTC_TimeTypeDef RTC_TimeStructure;
static RTC_InitTypeDef RTC_InitStructure;
static EXTI_InitTypeDef EXTI_InitStructure;
static RTC_DateTypeDef RTC_DateStructure;
static int8_t g_rtc_wakeup_event;
#pragma import(__use_no_semihosting_swi)
struct __FILE {
int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
int fputc(int ch, FILE *f) {
USART_SendData(USART1,ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
USART_ClearFlag(USART1,USART_FLAG_TXE);
return ch;
}
void _sys_exit(int return_code) {
}
void delay_us(uint32_t n)
{
SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器
SysTick->LOAD = (168*n)-1; // 配置计数值(168*n)-1 ~ 0
SysTick->VAL = 0; // Clear current value as well as count flag
SysTick->CTRL = 5; // Enable SysTick timer with processor clock
while ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set
SysTick->CTRL = 0; // Disable SysTick
}
void delay_ms(uint32_t n)
{
while(n--)
{
SysTick->CTRL = 0; // Disable SysTick,关闭系统定时器
SysTick->LOAD = (168000)-1; // 配置计数值(168000)-1 ~ 0
SysTick->VAL = 0; // Clear current value as well as count flag
SysTick->CTRL = 5; // Enable SysTick timer with processor clock
while ((SysTick->CTRL & 0x10000)==0);// Wait until count flag is set
}
SysTick->CTRL = 0; // Disable SysTick
}
void usart1_init(uint32_t baud)
{
//打开PA硬件时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//打开串口1硬件时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//配置PA9和PA10为复用功能模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //第9 10根引脚
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_AF; //多功能模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻
GPIO_Init(GPIOA,&GPIO_InitStructure);
//将PA9和PA10引脚连接到串口1的硬件
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
//配置串口1相关参数:波特率、无校验位、8位数据位、1个停止位......
USART_InitStructure.USART_BaudRate = baud; //波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //1个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //无奇偶校验
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //允许收发数据
USART_Init(USART1, &USART_InitStructure);
//配置串口1的中断触发方法:接收一个字节触发中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//配置串口1的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//使能串口1工作
USART_Cmd(USART1, ENABLE);
}
void RTC_init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
//使能lse震荡时钟
RCC_LSEConfig(RCC_LSE_ON);
//等待外部晶振
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
//选择lse外部震荡时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能rtc的硬件时钟
RCC_RTCCLKCmd(ENABLE);
//等待所有寄存器就绪
RTC_WaitForSynchro();
/* ck_spre(1Hz) = RTCCLK(LSI) /(uwAsynchPrediv + 1)*(uwSynchPrediv + 1)*/
RTC_InitStructure.RTC_AsynchPrediv = 0x7F; //异步分频
RTC_InitStructure.RTC_SynchPrediv = 0xFF; //同步分频
RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24; //时间格式
RTC_Init(&RTC_InitStructure);
/* Set the date: Friday January 11th 2013 */
RTC_DateStructure.RTC_Year = 0x22;
RTC_DateStructure.RTC_Month = RTC_Month_October;
RTC_DateStructure.RTC_Date = 0x28;
RTC_DateStructure.RTC_WeekDay = RTC_Weekday_Friday;
RTC_SetDate(RTC_Format_BCD, &RTC_DateStructure);
RTC_TimeStructure.RTC_H12 = RTC_H12_PM;
RTC_TimeStructure.RTC_Hours = 0x14;
RTC_TimeStructure.RTC_Minutes = 0x20;
RTC_TimeStructure.RTC_Seconds = 0x00;
RTC_SetTime(RTC_Format_BCD, &RTC_TimeStructure);
//关闭唤醒中断
RTC_WakeUpCmd(DISABLE);
//为唤醒功能选择rtc配置好的时钟源
RTC_WakeUpClockConfig(RTC_WakeUpClock_CK_SPRE_16bits);
//设置计数值
RTC_SetWakeUpCounter(0);
//清除rtc唤醒中断标志
RTC_ClearITPendingBit(RTC_IT_WUT);
//使能rtc唤醒中断
RTC_ITConfig(RTC_IT_WUT,ENABLE);
//使能唤醒中断
RTC_WakeUpCmd(ENABLE);
EXTI_ClearITPendingBit(EXTI_Line22);
EXTI_InitStructure.EXTI_Line = EXTI_Line22;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
int main(void)
{
int32_t rt=0;
uint8_t buf[5]={
0};
//使能(打开)端口F的硬件时钟,就是对端口F供电
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
//串口1波特率:115200bps
usart1_init(115200);
//初始化GPIO引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //第9根引脚
GPIO_InitStructure.GPIO_Mode= GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增加输出电流能力。
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//高速响应
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使能上下拉电阻
GPIO_Init(GPIOF,&GPIO_InitStructure);
PFout(9)=0;
printf("This is rtc te st\r\n");
//备份寄存器
if(RTC_ReadBackupRegister(RTC_BKP_DR0)!=0x1688)
{
RTC_init();
//设置备份寄存器0
RTC_WriteBackupRegister(RTC_BKP_DR0, 0x1688);
}
else
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
PWR_BackupAccessCmd(ENABLE);
//使能lse震荡时钟
RCC_LSEConfig(RCC_LSE_ON);
//等待外部晶振
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);
//选择lse外部震荡时钟源
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//使能rtc的硬件时钟
RCC_RTCCLKCmd(ENABLE);
//等待所有寄存器就绪
RTC_WaitForSynchro();
//清除rtc唤醒中断标志
RTC_ClearITPendingBit(RTC_IT_WUT);
//使能rtc唤醒中断
RTC_ITConfig(RTC_IT_WUT,ENABLE);
//使能唤醒中断
RTC_WakeUpCmd(ENABLE);
EXTI_ClearITPendingBit(EXTI_Line22);
EXTI_InitStructure.EXTI_Line = EXTI_Line22;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = RTC_WKUP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
while(1)
{
if(g_rtc_wakeup_event)
{
RTC_GetDate(RTC_Format_BCD,&RTC_DateStructure);
printf("20%02x / %02x / %02x week:%x \r\n",RTC_DateStructure.RTC_Year,RTC_DateStructure.RTC_Month,RTC_DateStructure.RTC_Date,RTC_DateStructure.RTC_WeekDay);
RTC_GetTime(RTC_Format_BCD,&RTC_TimeStructure);
printf("%02x:%02x:%02x\r\n",RTC_TimeStructure.RTC_Hours,RTC_TimeStructure.RTC_Minutes,RTC_TimeStructure.RTC_Seconds);
g_rtc_wakeup_event=0;
}
}
}
void USART1_IRQHandler(void)
{
uint8_t d;
//检测标志位
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{
//接收数据
d=USART_ReceiveData(USART1);
//清空标志位
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
void RTC_WKUP_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_WUT) == SET)
{
g_rtc_wakeup_event=1;
//清空标志位
RTC_ClearITPendingBit(RTC_IT_WUT);
EXTI_ClearITPendingBit(EXTI_Line22);
}
}
三、效果演示
在完整代码中可以看到,本次实验使用串口将年月日时分秒打印出来,其中唤醒中断函数将标志位置1,在while中将值打印出来,效果如下图所示: