实验目的
本实验旨在掌握STM32的实时时钟RTC的使用,利用其测量日期时间,数据手册请参看16章。
实验简介
STM32的实时时钟(RTC)是一个独立的定时器。STM32的RTC模块拥有一组连续计数的计数器,在相应软件配置下,可以提供时钟日历的功能,修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变
系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前,先要取消备份区域(BKP)写保护,
STM32的RTC具有掉电继续运行的特性,在主电源VDD断开时,为了RTC外设掉电继续运行,必须给STM32芯片通过VBAT引脚接上锂电池,当主电源VDD有效时,由VDD给RTC外设供电。当VDD掉电后,由VBAT给RTC外设供电,但无论由什么电源供电,RTC中的数据都保存在属于RTC的备份区域中,若主电源VDD和VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会复位。
从RTC的定时器特性来说,它是一个32位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的128分频:HSE/128;低速内部时钟LSI;使用HSE分频时钟或LSI的话,在主电源VDD掉电的情况下,这两个时钟源都会受到影响,因此没法保证RTC正常工作。因此RTC一般使用低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,这是因为32768=2的15次方,分频容易实现,所以它被广泛应用到RTC模块。在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式
HAL库代码
main.c
#include "MyIncludes.h"
u16 sys_cnt = 0;
void systick_isr(void)
{
if(sys_cnt <100 )
sys_cnt++;
else
{
sys_cnt = 0;
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_4|GPIO_PIN_5);
}
}
void rtc_disp(void)
{
char date[15];
//用来存储日期
char time[15];
//用来存储时间
sprintf((char*)date,"20%d%d/%d%d/%d%d ",Calendar.RTC_Year%10,Calendar.RTC_Year%10,Calendar.RTC_Mon/10,Calendar.RTC_Mon%10,Calendar.RTC_Mday/10,Calendar.RTC_Mday%10);
//sprintf函数打印到字符串中(将数值转换成对应字符串形式,就是变换成ASCALL码)
sprintf((char*)time,"%d%d:%d%d:%d%d\n",Calendar.RTC_Hour/10,Calendar.RTC_Hour%10,Calendar.RTC_Min/10,Calendar.RTC_Min%10,Calendar.RTC_Sec/10,Calendar.RTC_Sec%10);
printf(date);
//发送日期
printf(time);
//发送时间
}
int main()
{
System_Init();
//系统初始化
LED_Init();
//LED初始化
SysTick_Init(systick_isr);
//SysTick初始化
USART1_Init(115200,NULL,NULL);
//串口1初始化 波特率:115200
//发送完成回调为空 接收回调为空
RTC_Init(rtc_disp);
//实时时钟初始化
while(1)
{
RTC_Process();
//RTC处理函数
}
}
rtc.h
#ifndef __RTC_H
#define __RTC_H
#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"
typedef struct
{
u8 RTC_Sec;
//秒
u8 RTC_Min;
//分
u8 RTC_Hour;
//时
u8 RTC_Mday;
//日
u8 RTC_Mon;
//月
u8 RTC_Year;
//年
u8 RTC_Wday;
//星期
}_Calendar_obj;
//日历结构体
extern _Calendar_obj Calendar;
//结构体声明
typedef struct
{
void (*Operation)(void);
//函数指针
}_RTC_Fun;
void RTC_SetDateTime(_Calendar_obj datatime);
//设置RTC的日期时间函数
_Calendar_obj RTC_GetDateTime(void);
//获取RTC日期时间函数
void RTC_Init(void(*rtc)(void));
//RTC初始化函数
void RTC_Process(void);
//RTC处理
#endif
rtc.c
#include "rtc.h"
_Calendar_obj Calendar;
//日历时钟结构体变量声明
RTC_HandleTypeDef RTCHandle;
//时间句柄结构体变量声明
_RTC_Fun rtc_fun;
//用户函数结构体变量声明
u8 const table_week[12] = {0,3,3,6,1,4,6,2,5,0,3,5};
//月修正表数据
/*假设1月1日是星期一,那么2月1日是星期四,(4-1=3),故是3
3月1日是星期四,(4-1=3),4月1日是星期日(7-1=6),依次类推,
前提这一年是平年
*/
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
//作用算出周几
{
u16 temp2;
u8 yearH,yearL;
yearH = year/100;
yearL = year%100;
if(yearH >19 )
yearL+=100;
//这几行算出本年距离1900年的差
temp2 = yearL + yearL/4;
//算出除了一年中除正周外累计多出来的天数 365%7=1 闰年的个数:366-365=1
temp2 = temp2%7;
//除去整周的天数
temp2 = temp2+day+table_week[month-1];
//现在temp2是除去整周的天数,加上本日的天数 在加上月修正表数据
if(yearL%4 == 0 && month <3 )
temp2--;
//如果是闰年1月2月的话要减去1,因为yearl多出来的一天加上去了,也就是
//2月29日,多出来的一天再三月份以后才能加
return (temp2%7);
//这个就是本周星期几
/*在这里我们举个栗子吧:假设时间是2020年2月29日,那么首先yearH=20,
yearl=20,因为yearH = 20 >19 那么yeal+=100 那么yeal = 120天
temp2 = yearL + yearL/4; 120+30 = 150 天, 150%7 = 3,
3+29+3 = 35 35-1=34 34%7=6 也就是星期六,我们看下日历确实
是星期六*/
}
void RTC_SetDateTime(_Calendar_obj datetime)
//先看void RTC_Init(void (*rtc)(void))函数
//设置RTC的日期和时间
{
RTC_DateTypeDef SetDate;
//设置日期结构体声明
RTC_TimeTypeDef SetTime;
//设置时间结构体声明
SetDate.Date = datetime.RTC_Mday;
SetDate.Month = datetime.RTC_Mon;
SetDate.Year = datetime.RTC_Year;
//0~99
SetDate.WeekDay = datetime.RTC_Wday;
SetTime.Hours = datetime.RTC_Hour;
SetTime.Minutes = datetime.RTC_Min;
SetTime.Seconds = datetime.RTC_Sec;
HAL_RTC_SetDate(&RTCHandle,&SetDate,RTC_FORMAT_BIN);
//设置日期
HAL_RTC_SetTime(&RTCHandle,&SetTime,RTC_FORMAT_BIN);
//设置时间
return ;
}
_Calendar_obj RTC_GetDateTime(void)
//先看void RTC_Process(void)
//获取RTC日期时间
{
RTC_DateTypeDef SetDate;
RTC_TimeTypeDef SetTime;
_Calendar_obj datetime;
HAL_RTC_GetTime(&RTCHandle,&SetTime,RTC_FORMAT_BIN);
//获取时间
HAL_RTC_GetDate(&RTCHandle,&SetDate,RTC_FORMAT_BIN);
//获取日期
datetime.RTC_Sec = SetTime.Seconds;
datetime.RTC_Min = SetTime.Minutes;
datetime.RTC_Hour = SetTime.Hours;
datetime.RTC_Mday = SetDate.Date;
datetime.RTC_Mon = SetDate.Month;
datetime.RTC_Year = SetDate.Year;
datetime.RTC_Wday = SetDate.WeekDay;
return datetime;
}
void HAL_RTC_MspInit(RTC_HandleTypeDef *hrtc)
//在HAL_RTC_Init中调用
{
RCC_OscInitTypeDef RCC_OscInitStruct;
//RCC内部/外部振荡器(HSE、HSI、LSE和LSI)配置结构变量声明
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct;
//RCC扩展时钟结构变量声明
__HAL_RCC_PWR_CLK_ENABLE();
//备份电源使能
HAL_PWR_EnableBkUpAccess();
//电源使能唤醒
__HAL_RCC_BKP_CLK_ENABLE();
//使能BKP时钟
//配置时钟源
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
//要配置的振荡器
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
//PLL的状态 未配置
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
//振荡器状态 振荡器开启
HAL_RCC_OscConfig(&RCC_OscInitStruct);
//RCC配置
PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_RTC;
//要配置的拓展时钟 外部时钟 0x0000001
PeriphClkInitStruct.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
//指定RTC时钟源 用作RTC时钟的LSE振荡器时钟
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct);
//配置拓展时钟
__HAL_RCC_RTC_ENABLE();
// 使能RTC
}
void RTC_Init(void (*rtc)(void))
{
_Calendar_obj DateTime;
//日历结构体变量声明
RTCHandle.Instance = RTC;
//寄存器基址 RTC
RTCHandle. Init.AsynchPrediv = RTC_AUTO_1_SECOND;
//指定BIC 异分频系数 0xffffffff 自动获取1s时机基
RTCHandle.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
//指定那个信号路由到RTC针 0x00000000
//篡改针上没有输出
HAL_RTC_Init(&RTCHandle);
//初始化RTC参数
DateTime.RTC_Hour = 23;
DateTime.RTC_Min = 59;
DateTime.RTC_Sec = 50;
DateTime.RTC_Year = 16;
DateTime.RTC_Mon = 8;
DateTime.RTC_Mday = 21;
DateTime.RTC_Wday = RTC_Get_Week(DateTime.RTC_Year+2000,DateTime.RTC_Mon,DateTime.RTC_Mday);
//2016年8月21日23时59分50秒
RTC_SetDateTime(DateTime);
//设置RTC的时间和日期
rtc_fun.Operation = rtc;
return ;
}
u8 last_sec = 0;
void RTC_Process(void)
//RTC处理
{
Calendar = RTC_GetDateTime();
if(last_sec != Calendar.RTC_Sec)
{
last_sec = Calendar.RTC_Sec;
if(rtc_fun.Operation != NULL)
rtc_fun.Operation();
//每一秒刷新一次
}
}
寄存器代码
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
#include "key.h"
#include "rtc.h"
int main(void)
{
char t[20];
Stm32_Clock_Init(9); //ϵͳʱÖÓÉèÖÃ
uart_init(72,115200); //´®¿Ú³õʼ»¯Îª115200
delay_init(72); //ÑÓʱ³õʼ»¯
LED_Init(); //³õʼ»¯ÓëLEDÁ¬½ÓµÄÓ²¼þ½Ó¿Ú
while(RTC_Init())
{
}
while(1)
{
LED2=!LED2;
delay_ms(100);
sprintf((char*)t,"%d:%d:%d\r\n",calendar.w_year,calendar.w_month,calendar.w_date);
printf(t);
}
}
RTC.h
#ifndef __RTC_H
#define __RTC_H
#include "sys.h"
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;
//日历结构体
void Disp_Time(u8 x,u8 y,u8 size);
//在制定位置开始显示时间
void Disp_Week(u8 x,u8 y,u8 size,u8 lang);
//在指定位置显示星期
u8 RTC_Init(void);
//初始化RTC 失败返回1 成功返回0
u8 Is_Leap_Year(u16 year);
//平年瑞年判断
u8 RTC_Get(void);
//获取时间
u8 RTC_Get_Week(u16 year,u8 month,u8 day);
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
//设置时间
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec);
//设置闹钟
#endif
RTC.C
#include "delay.h"
#include "usart.h"
#include "rtc.h"
#include "LED.h"
_calendar_obj calendar;
//时钟结构体
u8 RTC_Init(void)
{
u8 temp=0;
//检查是不是第一次配置时钟
if(BKP->DR1!=0X5050)//第一次配置
{
RCC->APB1ENR|=1<<28;
//使用电源时钟
RCC->APB1ENR|=1<<27;
//使能备份时钟
PWR->CR|=1<<8;
//取消备份区写保护
RCC->BDCR|=1<<16;
//备份区域软复位
RCC->BDCR&=~(1<<16);
//备份区域软复位结束
RCC->BDCR|=1<<0;
//开启外部低速振荡器
while((!(RCC->BDCR&0X02))&&temp<250)
//等待外部时钟就绪
{
temp++;
delay_ms(10);
};
if(temp>=250)return 1;
//初始化时钟失败,晶振有问题
RCC->BDCR|=1<<8;
//LSE作为RTC时钟
RCC->BDCR|=1<<15;
//RTC时钟使能
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
while(!(RTC->CRL&(1<<3)));
//等待RTC寄存器同步
RTC->CRH|=0X01;
//允许秒中断
RTC->CRH|=0X02;
//允许闹钟中断
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
RTC->CRL|=1<<4;
//允许配置
RTC->PRLH=0X0000;
RTC->PRLL=32767;
//时钟周期设置
RTC_Set(2015,1,14,17,42,55);
//设置时间
RTC->CRL&=~(1<<4);
//配置更新
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
BKP->DR1=0X5050;
printf("FIRST TIME\n");
}else//系统继续计时
{
while(!(RTC->CRL&(1<<3)));
//等待RTC寄存器同步
RTC->CRH|=0X01;
//允许秒中断
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
printf("OK\n");
}
MY_NVIC_Init(0,0,RTC_IRQn,2);
//优先级设置
RTC_Get();
//更新时间;
return 0;
}
//RTC时钟中断
//每秒触发一次
void RTC_IRQHandler(void)
{
if(RTC->CRL&0x0001)
//秒中断
{
RTC_Get();
//更新时间
}
if(RTC->CRL&0x0002)
//闹钟中断
{
RTC->CRL&=~(0x0002);
//清除闹钟中断
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->CRL&=0X0FFA;
//清除溢出,秒钟中断标志
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
}
//判断是否是不是闰年 1是 0不是
u8 Is_Leap_Year(u16 year)
{
if(year%4==0)//必须能被4整除
{
if(year%100==0)
{
if(year%400==0)return 1; //如果以00结尾,还要能被400整除
else return 0;
}else return 1;
}else return 0;
}
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5};
//月修正数据表
const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};
//平年得月份日期表
//RTC设置
u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++) //把所有年份得秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;
//闰年的秒钟数
else seccount+=31536000;
//平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++)
//把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;
//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;
//闰年2月份增加一天的秒钟数
}
seccount+=(u32)(sday-1)*86400;
//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;
//小时秒钟数
seccount+=(u32)min*60;
//分钟秒钟数
seccount+=sec;
//最后的秒钟数加上去
RCC->APB1ENR|=1<<28;
//使能电源时钟
RCC->APB1ENR|=1<<27;
//使能备份时钟
PWR->CR|=1<<8;
//取消备份区写保护
RTC->CRL|=1<<4;
//允许配置
RTC->CNTL=seccount&0xffff;
RTC->CNTH=seccount>>16;
RTC->CRL&=~(1<<4);
//配置更新
while(!(RTC->CRL&(1<<5)));
//等待RTC寄存器操作完成
RTC_Get();
//设置完之后更新一下数据
return 0;
}
//初始化闹钟
u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec)
{
u16 t;
u32 seccount=0;
if(syear<1970||syear>2099)return 1;
for(t=1970;t<syear;t++)//把所有年份的秒钟相加
{
if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数
else seccount+=31536000; //平年的秒钟数
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒钟数相加
{
seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;
//闰年2月份增加一天秒钟数
}
seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加
seccount+=(u32)hour*3600;//小时秒钟数
seccount+=(u32)min*60; //分钟秒钟数
seccount+=sec; //最后的秒钟数加上去
RCC->APB1ENR|=1<<28;//使能电源时钟
RCC->APB1ENR|=1<<27;//使能备份时钟
PWR->CR|=1<<8; //取消备份区写保护
RTC->CRL|=1<<4; //允许配置
RTC->ALRL=seccount&0xffff;
RTC->ALRH=seccount>>16;
RTC->CRL&=~(1<<4);//配置更新
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成
return 0;
}
//得到当前时间
u8 RTC_Get(void)
{
static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC->CNTH;
//得到计数器中的值(秒钟数)
timecount<<=16;
timecount+=RTC->CNTL;
temp=timecount/86400; //得到天数
if(daycnt!=temp)//超过一天
{
daycnt=temp;
temp1=1970;//从1970年开始
while(temp>=365)
{
if(Is_Leap_Year(temp1))//是闰年
{
if(temp>=366)temp-=366;//闰年的秒钟数
else break;
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1;//得到年份
temp1=0;
while(temp>=28)//超过了一个月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)
//是不是闰年2月份
{
if(temp>=29)temp-=29;//闰年的秒钟数
else break;
}
else
{
if(temp>=mon_table[temp1])temp-=mon_table[temp1];
//平年
else break;
}
temp1++;
}
calendar.w_month=temp1+1;
//得到月份
calendar.w_date=temp+1;
//得到日期
}
temp=timecount%86400;
//得到秒钟数
calendar.hour=temp/3600;
//小时
calendar.min=(temp%3600)/60;
//分钟
calendar.sec=(temp%3600)%60;
//秒钟
calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);
return 0;
}
//获得现在是星期几
u8 RTC_Get_Week(u16 year,u8 month,u8 day)
{
u16 temp2;
u8 yearH,yearL;
yearH=year/100; yearL=year%100;
//如果是21世纪,年份加100
if (yearH>19)yearL+=100;
//所过闰年数只算1900年之后的
temp2=yearL+yearL/4;
temp2=temp2%7;
temp2=temp2+day+table_week[month-1];
if (yearL%4==0&&month<3)temp2--;
return(temp2%7);
}