基于STM32实现RTC实时时钟


前言

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中将值打印出来,效果如下图所示:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46155589/article/details/128099896