龙空技术网

【STM32F103ZET6开发板】第2-9讲:RTC时钟

电子友人章 173

前言:

此刻我们对“stm32rtc时钟转换算法”都比较珍视,朋友们都想要分析一些“stm32rtc时钟转换算法”的相关内容。那么小编在网摘上收集了一些对于“stm32rtc时钟转换算法””的相关资讯,希望朋友们能喜欢,小伙伴们快快来了解一下吧!

RTC时钟

实验目的掌握STM32 实时时钟RTC的原理。掌握RTC硬件电路设计及实时时钟相关算法的程序设计。实验内容编写程序配置RTC为秒中断模式,并控制指示灯D1每1s状态翻转一次。编写程序实现串口调试助手实时显示当前时间信息。硬件设计RTC概念介绍

看到RTC,我们首先想到的是实时时钟芯片,比如DS1302、PCF8563等时钟芯片。其实现在的许多MCU本身是有RTC外设的,这样就不需要外接RTC时钟芯片了。接下来说下MCU自带RTC外设的含义,其并非都是代表实时时钟的含义,还有一些MCU自带的RTC指的是实时计数器(比如nRF52832)。实时时钟和实时计数器区别如下。

Real Time Clock:实时时钟,该外设拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。有的MCU的RTC外设是具备时间寄存器和日期寄存器的(通过程序可从直接读出年、月、日、时、分、秒的值),可用电池供电,即使主机电源断开,也能正常运行。Real Time Counter:实时计数器。对振荡源的脉冲计数,每个脉冲计数值加1,它没有日期寄存器,所以不能从实时计数器直接获取时间。注:STM32F103系列MCU的RTC是Real-time clock的缩写,指的是实时时钟,但需要指出的是STM32F103系列MCU的RTC是没有时间寄存器和日期寄存器的。STM32F4系列MCU的RTC带有时间寄存器和日期寄存器。RTC的晶振频率为什么通常会选择32.768KHz呢?

原因主要有以下几点。

1、由公式215 = 32768,可知32768Hz分频15次后正好1Hz。

2、RTC时间是以振荡频率来计算的。震荡频率越高,时间的准确性越高。

3、工程师长期实践得出32.768KHZ可以获得非常准确的时钟信息。

4、形成规范和统一。

STM32关于RTC原理介绍

STM32F103系列MCU提供了一个秒中断源和一个闹钟中断源,修改计数器的值可以重新设置系统当前的时间和日期。RTC模块之所以具有实时时钟功能,是因为它内部维持了一个独立的定时器,通过配置,可以让它准确地每秒钟中断一次。但实际上,RTC就只是一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器。其在掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。

STM32F103系列MCU时钟系统中清晰地描述了RTC时钟来源,如下图时钟树所示。

图1:STM32时钟树示意图

注:RTC时钟来源有3个,一般会选择LSE,即外部晶振提供的32.768KHz时钟。

下面再介绍下STM32F103系列RTC的内部结构图:

图2:STM32 RTC内部结构图

注:关于RTC_CR寄存器包括2个16位的寄存器,即RTC_CRH寄存器和RTC_CRL寄存器。开发板RTC部分硬件电路

IK-ZET6开发板上设计了LSE晶振电路和后备电池电路,LSE晶振电路选取的晶振是32.768KHz,电池使用的是CR1220纽扣电池。如下图所示。

图3:开发板RTC硬件电路

软件设计RTC寄存器汇集

STM32F103提供了10个用于操作RTC的寄存器,如下表所示:

表1:RTC相关寄存器

序号

寄存器名

读/写

功能描述

1

RTC_CRH

读/写

RTC控制寄存器高位。

2

RTC_CRL

读/写

RTC控制寄存器低位。

3

RTC_PRLH

只写

RTC预分频装载寄存器高位。

4

RTC_PRLL

只写

RTC预分频装载寄存器低位。

5

RTC_DIVH

只读

RTC预分频器余数寄存器高位。

6

RTC_DIVL

只读

RTC预分频器余数寄存器低位。

7

RTC_CNTH

读/写

RTC计数器寄存器高位。

8

RTC_CNTL

读/写

RTC计数器寄存器低位。

9

RTC_ALRH

只写

RTC闹钟寄存器高位。

10

RTC_ALRL

只写

RTC闹钟寄存器低位。

关于RTC_CRH寄存器:

RTC_CRH寄存器是用来控制中断的。中断可选择允许秒中断、允许闹钟中断或允许溢出中断。下图是示例使能RTC秒中断。

图4:RTC_CRH寄存器

关于RTC_CRL寄存器:

RTC_CRL寄存器只用到低6位,含义如下图。当进入RTC中断是后需通过该寄存器低3位的标志位判断是进入的秒中断、闹钟中断还是溢出中断。在修改RTC_CRH/ RTC_CRL寄存器之前,必须先判断RSF位是否已经同步了。只有同步了,才能修改RTC_CRH/ RTC_CRL寄存器的值,否则必须等待同步。CNF位置1对应库函数RTC_EnterConfigMode,CNF位置0对应库函数RTC_ExitConfigMode。

图5:RTC_CRL寄存器

关于RTC_PRLL寄存器:

根据下图RTC_PRLL寄存器说明部分公式,如果PRL[19:0]值为32767,则计数器的时钟频率为1HZ,得到的即是周期为1秒钟的信号。十六进制7FFF换成十进制是32767。

图6:RTC_PRLL寄存器

STM32F103提供了4个用于操作BKP的寄存器,如下表所示:

表2:BKP相关寄存器

序号

寄存器名

读/写

功能描述

1

BKP_DRx

读/写

备份数据寄存器x。 (x=1,2,3,…,10)

2

BKP_RTCCR

读/写

RTC时钟校准寄存器。

3

BKP_CR

读/写

备份控制寄存器。

4

BKP_CSR

读/写

备份控制/状态寄存器。

其他寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”“2 - 芯片资料”中“STM32英文参考手册_V15”或“STM32中文参考手册_V10”对应的RTC章节或BKP章节的寄存器部分认真研读。

RTC库函数汇集

ST官方提供的最终库函数版本是V3.5版本,该版本库函数提供了14个与RTC操作有关的库函数,如下表所示:

表3:RTC相关库函数汇集

序号

函数名

功能描述

1

RTC_ITConfig

使能或者失能指定的RTC中断。

2

RTC_EnterConfigMode

进入RTC配置模式。

3

RTC_ExitConfigMode

退出RTC配置模式。

4

RTC_GetCounter

获取RTC计数器的值。

5

RTC_SetCounter

设置RTC计数器的值。

6

RTC_SetPrescaler

设置RTC预分频的值。

7

RTC_SetAlarm

设置RTC闹钟的值。

8

RTC_GetDivider

获取RTC预分频分频因子的值。

9

RTC_WaitForLastTask

等待最近一次对RTC寄存器的写操作完成。

10

RTC_WaitForSynchro

等待RTC寄存器(RTC_CNT,RTC_ALR,RTC_PRL)与RTC的APB时钟同步。

11

RTC_GetFlagStatus

检查指定的RTC标志位设置与否。

12

RTC_ClearFlag

清除RTC的待处理标志位。

13

RTC_GetITStatus

检查指定的RTC中断发生与否。

14

RTC_ClearITPendingBit

清除RTC的中断待处理位。

表4:BKP相关库函数汇集

序号

函数名

功能描述

1

BKP_DeInit

将外设BKP的全部寄存器重设为缺省值。

2

BKP_TamperPinLevelConfig

设置侵入检测管脚的有效电平。

3

BKP_TamperPinCmd

使能或者失能管脚的侵入检测功能。

4

BKP_ITConfig

使能或者失能侵入检测中断。

5

BKP_RTCOutputConfig

选择在侵入检测管脚上输出的RTC时钟源。

6

BKP_SetRTCCalibrationValue

设置RTC时钟校准值。

7

BKP_WriteBackupRegister

向指定的后备寄存器中写入用户程序数据。

8

BKP_ReadBackupRegister

从指定的后备寄存器中读出数据。

9

BKP_GetFlagStatus

检查侵入检测管脚事件的标志位被设置与否。

10

BKP_ClearFlag

清除侵入检测管脚事件的待处理标志位。

11

BKP_GetITStatus

检查侵入检测中断发生与否。

12

BKP_ClearITPendingBit

清除侵入检测中断的待处理位。

其他库函数详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32固件库使用手册的中文翻译版”对应的RTC章节或BKP章节的库函数部分认真研读。

RTC配置过程

针对STM32F103的RTC,软件的配置过程一般如下:

图7:RTC配置步骤

RTC秒中断实验注:本节的实验源码是在“实验2-1-3:流水灯”的基础上修改。本节对应的实验源码是:“实验2-9-1: RTC中断 - 指示灯”。工程需要用到的库文件

本例需要用到的c文件如下表所示,工程需要添加下表中的c文件。

表5:实验需要用到的c文件

序号

文件名

后缀

功能描述

1

stm32f10x_rcc

.c

复位与时钟控制器。

2

stm32f10x_gpio

.c

通用输入输出。

3

stm32f10x_rtc

.c

实时时钟。

4

stm32f10x_bkp

.c

备份寄存器。

5

stm32f10x_pwr

.c

电源管理。

6

misc

.c

中断向量控制器。

按下图所示将需要的c文件添加到工程。

图8:在新建工程中添加所需库函数c文件

头文件引用和路径设置需要引用的头文件

因为在“main.c”文件中使用了标准库和我们自己建的软件延时函数,所以需要引用下面的头文件。

#include "stm32f10x.h" //delay这里报错的原因是:delay函数用汇编实现的,导致了MDK误报。 #include "delay.h" 需要包含的头文件路径

本例需要包含的头文件路径如下表:

表6:头文件包含路径

序号

路径

描述

1

..\Lib\F10x_FWLIB\inc

标准库头文件路径。

2

..\User

stm32f10x_conf.h头文件在该路径,所以要包含。

3

..\User\bsp

自建的板卡相关的驱动文件路径。

MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

图9:添加头文件包含路径

编写代码

首先是按照RTC配置步骤对RTC进行初始化和中断优先级设置,具体代码如下。

代码清单:RTC初始化并使能秒中断

/************************************************************************** * 描 述 : 初始化RTC并配置RTC中断优先级 * 入 参 : 无 * 返回值 : 无 **************************************************************************/ void RTC_Configuration_Init(void) { NVIC_InitTypeDef NVIC_InitStructure;

/* Enable the PWR/BKP Clock */ RCC_APB1PeriphClockCmd( RCC_APB1Periph_PWR | RCC_APB1Periph_BKP , ENABLE); PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 BKP_DeInit(); //将外设BKP的全部寄存器重设为缺省值 RCC_LSEConfig(RCC_LSE_ON); //开启LSE while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET); //等待LSE起振 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择LSE为RTC时钟源 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 RTC_WaitForSynchro(); //等待RTC寄存器与RTC的APB时钟同步完成 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能RTC秒中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_SetPrescaler(32767); //设置RTC时钟分频值为32767 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //NVIC_Group:先占优先级2位,从优先级2位 NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //配置为RTC中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级为1 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级为2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断通道 NVIC_Init(&NVIC_InitStructure);

}

代码清单:RTC中断服务函数

/************************************************************************** * 描 述 : RTC中断服务函数 * 入 参 : 无 * 返回值 : 无 **************************************************************************/ void RTC_IRQHandler(void) { if(RTC_GetITStatus(RTC_IT_SEC)!=RESET) //检查指定的RTC中断发生与否(秒中断) { led_toggle(LED_1); //1s执行一次翻转指示灯D1的操作 RTC_ClearITPendingBit(RTC_IT_SEC); //清除RTC的中断待处理位(秒中断) } }

然后主函数中会对指示灯和RTC进行初始化,在主循环中没有用户代码,可以判断如果用户指示灯D1闪烁则来自于中断服务函数。

代码清单:主函数

int main(void) { //初始化用于驱动指示灯D1、D2、D3、D4的引脚,并熄灭4个用户LED leds_init(); //初始化RTC配置并设置RTC中断优先级,配置RTC进行一次计时的时间是1s RTC_Configuration_Init();

//主循环 while(1) { ; //空命令,说明用户LED的闪烁来自于RTC中断 } } 实验步骤解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-9-1: RTC中断 - 指示灯”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。启动MDK5.23。在MDK5中执行“Project→Open Project”打开“…\RTC\projec”目录下的工程“RTC.uvproj”。点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“RTC.hex”位于工程目录下的“Objects”文件夹中。点击下载按钮下载程序。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。程序运行后,可以观察到D1指示灯间隔1s状态翻转一次。RTC实时时钟 - 串口调试助手显示时钟实验注:本节的实验源码是在“实验2-9-1: RTC中断 - 指示灯”的基础上修改。本节对应的实验源码是:“实验2-9-2: RTC实时时钟 - 串口调试助手显示时钟”。工程需要用到的库文件

本实验需要用到的c文件以及添加头文件包含路径的方法与介绍“实验2-9-1: RTC中断 - 指示灯”基本一致,因本实验是串口上传信息,所以需增加“通信同步/异步收发器”的库文件,在此不再介绍。

编写代码

首先对RTC函数进行初始化,为保证在后备区域供电下不重复配置时间信息,在本函数中会在对RTC基本配置后写入一个特殊字符到备份寄存器中,具体代码如下。

代码清单:RTC初始化

/**************************************************************************** * 描 述 : 初始化RTC * 入 参 : 待配置的时间信息(年份、月份、日期、时、分、秒) * 返回值 : 无 ****************************************************************************/ void RTC_Init(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) { if(BKP_ReadBackupRegister(BKP_DR2) != 0xA587) //读取BKP数据寄存器2中数据是不是0xA587 { RCC_APB1PeriphClockCmd( RCC_APB1Periph_PWR | RCC_APB1Periph_BKP , ENABLE); //使能RTC和BKP时钟 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 BKP_DeInit(); //将外设BKP的全部寄存器重设为缺省值 RCC_LSEConfig(RCC_LSE_ON); //开启LSE while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET); //等待LSE起振 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择LSE为RTC时钟源 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 RTC_WaitForSynchro(); //等待RTC寄存器与RTC的APB时钟同步完成 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_ITConfig(RTC_IT_SEC,ENABLE); //使能RTC秒中断 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_SetPrescaler(32767); //设置RTC时钟分频值为32767 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成

RTC_WriteClock(year, month, day, hour, min, sec); //设置时间 BKP_WriteBackupRegister(BKP_DR2, 0xA587); //配置完成后,向BKP数据寄存器2中写特殊字符0xA587 } else //读取BKP数据寄存器2中数据是0xA587(说明后备寄存器一直有电) { RCC_APB1PeriphClockCmd( RCC_APB1Periph_PWR | RCC_APB1Periph_BKP , ENABLE); //使能RTC和BKP时钟 if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET) //检查是否掉电重启 { printf("\r\n\r\n Power On Reset occurred...."); } else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET) //检查是否Reset管脚复位 { printf("\r\n\r\n External Reset occurred...."); } RCC_ClearFlag(); //清除RCC中复位标志 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 } }

然后介绍下设置RTC时钟函数RTC_WriteClock和读RTC当前时钟函数RTC_ReadClock。设置RTC时钟函数是将待配置的时间信息按照一定的算法得出一个值,并将该值写入到RTC计数器中,而读RTC当前时钟函数则是读取到RTC计数器中的数,按照一定的算法将该值解析成年份、月份、日期、时、分、秒信息,具体代码如下:

代码清单:设置RTC时钟函数

//枚举平年的每月天数表:1月,2月,3月,4月,5月,6月,7月,8月,9月,10月,11月,12月 const uint8_t month_table[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/************************************************************************ * 描 述 : 设置RTC时钟函数 * 入 参 : 无 * 返回值 : ENABLE:成功 DISABLE:错误 * 备 注 : 设置年份范围1970-2099 *************************************************************************/ FunctionalState RTC_WriteClock(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec) { uint16_t temp; uint32_t seccount=0;

//判断设置的年份是否在有效范围内[1970,2099] if (year < 1970 || year > 2099) { return DISABLE; //年份不在[1970,2099]区间内,返回错误 }

//把前面年份的秒钟相加(年份起始是1970年) for (temp = 1970; temp < year; temp++) { if (((0 == temp%4)&&(0 != temp%100)) ||(0 == temp %400)) // 判断是否为闰年 { seccount += 31622400; //闰年的秒钟数 } else { seccount += 31536000; //平年的秒钟数 } }

//把前面月份的秒钟数相加 month = month - 1; //先把月份减一,因为month_table[0]对应1月份 for (temp = 0; temp < month; temp++) { seccount += (uint32_t)month_table[temp] * 86400; // 月份秒钟数相加

if ((((0 == year%4)&&(0 != year%100)) ||(0 == year %400)) && temp == 1) //判断是否是闰年2月 { seccount += 86400; //如果是闰年2月则增加一天的秒钟数 } }

//把前面日期的秒钟数相加 seccount += (uint32_t)(day - 1) * 86400;

//把前面小时的秒钟数相加 seccount += (uint32_t)hour * 3600;

//把前面分钟的秒钟数相加 seccount += (uint32_t)min * 60;

//把所剩的秒钟数加上 seccount += sec;

PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 RTC_SetCounter(seccount); //设置RTC计数器的值 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成

return ENABLE; }

代码清单:读RTC当前时钟函数

/************************************************************************* * 描 述 : 得到当前时钟。 * 入 参 : 无 * 返回值 : 无 * 备 注 : 本算法的核心是分两部分进行: 1、通过读取RTC计数器值得到整天数计算出准确的年份、月份和日期; 2、通过除去整天数剩余的秒数计算出准确的时、分和秒信息。 **************************************************************************/ void RTC_ReadClock(void) { static uint16_t daycnt = 0; uint32_t timecnt = 0; uint16_t temp1 = 0; uint32_t temp2 = 0;

timecnt = RTC_GetCounter(); //获取RTC计数器的值 temp2 = timecnt / 86400; //得到天数

if (daycnt != temp2) //如果计算的天数不为0,即超过一天了 { daycnt = temp2; //将得到的天数赋值给daycnt temp1 = 1970; //从1970年开始

while (temp2 >= 365) //在得到的天数大于364天的情况下 { if (((0 == temp1%4)&&(0 != temp1%100)) ||(0 == temp1%400)) //判断是否是闰年 { if (temp2 >= 366) //是闰年,并且得到的天数大于365 { temp2 -= 366; //确定是闰年的话,递减366 } else //是闰年,但得到的天数小于366 { break; //错误,退出 } } else //不是闰年,则是平年 { temp2 -= 365; //平年的话,递减365 } temp1++; //从1970年开始递加 } g_tRTC.Year = temp1; //将得到年份存放到全局变量g_tRTC中

temp1 = 0; //临时变量清零 while (temp2 >= 28) //在得到的天数去掉整年剩下的天数超过了28天 { if((((0 == g_tRTC.Year%4)&&(0 != g_tRTC.Year%100)) ||(0 == g_tRTC.Year %400))&&temp1 == 1) //判断当年是不是闰年的2月份 { if (temp2 >= 29) //是闰年的2月份,并且2月天数大于28 { temp2 -= 29; //确定是闰年2月份,递减29 } else //是闰年的2月份,但2月天数小于29 { break; //错误,退出 } } else //当年是平年,或者是闰年的非2月份 { if (temp2 >= month_table[temp1]) //在得到的天数去掉整年剩下的天数依次与各月份天数比较(从1月到12月顺序) { temp2 -= month_table[temp1]; //大于等于比较的月份天数则减去与之比较的月份天数 } else { break; //小于与之比较的月份天数则退出,说明找到了月份 } } temp1++; } g_tRTC.Month = temp1 + 1; //将得到月份存放到全局变量g_tRTC中 g_tRTC.Day = temp2 + 1; //将得到日期存放到全局变量g_tRTC中 }

temp2 = timecnt % 86400; //得到秒钟数 g_tRTC.Hour = temp2 / 3600; //将得到小时存放到全局变量g_tRTC中 g_tRTC.Min = (temp2 % 3600) / 60; //将得到分钟存放到全局变量g_tRTC中 g_tRTC.Sec = (temp2 % 3600) % 60; //将得到秒钟存放到全局变量g_tRTC中 }

最后主函数中会对串口1和RTC初始化,在主循环中会500ms读取一次实时时钟信息并串口打印出来。

代码清单:主函数

int main(void) { //初始化用于驱动指示灯D1、D2、D3、D4的引脚,并熄灭4个用户LED leds_init(); //初始化串口1 USART1_Init(); //初始化RTC,设置默认时间(在后备电池供电的情况下,再次上电开发板也不会重新配置默认时间) RTC_Init(2018, 12, 31, 23, 59, 50);

//主循环 while(1) { RTC_ReadClock(); //实时读取当前时钟,结果存放在全局变量g_tRTC中 //实时时钟年份、月份、日期、时、分、秒信息串口显示 printf("%4d-%02d-%02d %02d:%02d:%02d\r\n", g_tRTC.Year, g_tRTC.Month, g_tRTC.Day, g_tRTC.Hour, g_tRTC.Min, g_tRTC.Sec);

led_toggle(LED_1); //读取一次当前时钟,翻转一次用户指示灯D1 sw_delay_ms(500); //每500ms读取一次当前时钟 } } 实验步骤解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-9-2: RTC实时时钟 - 串口调试助手显示时钟”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。启动MDK5.23。在MDK5中执行“Project→Open Project”打开“…\RTC\projec”目录下的工程“RTC.uvproj”。点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“RTC.hex”位于工程目录下的“Objects”文件夹中。点击下载按钮下载程序。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。程序运行后,可以观察到指示灯D1每间隔0.5s被点亮一次,每次被点亮的时间是0.5s。BT1座电池不安装的情况下,上电后打开串口调试助手(选择正确的串口号,波特率设置为19200,数据位为8、停止位为1),可以看到实时时钟从2018-12-31 23:59:50开始计数,只要不断电实时时钟正常显示。BT1座电池不安装的情况下,断电再上电后打开串口调试助手(选择正确的串口号,波特率设置为19200,数据位为8、停止位为1),可以看到实时时钟仍然从2018-12-31 23:59:50开始计数。示例见图10。BT1座安装有电池,上电后打开串口调试助手(选择正确的串口号,波特率设置为19200,数据位为8、停止位为1),可以看到实时时钟从2018-12-31 23:59:50开始计数,时间走到一定时间比如2019-01-01 00:00:01后开发板断电。BT1座安装有电池,断电再上电后打开串口调试助手(选择正确的串口号,波特率设置为19200,数据位为8、停止位为1),可以看到实时时钟没有从2018-12-31 23:59:50开始计数,而是按照之前的配置时钟信息一直在走。示例见图11。实验现象分析: BT1座安装有电池,即使开发板断电,MCU的后备区域依然通过电池在供电,RTC计数器在后备区域,RTC计数器依然在工作计数,故开发板在此上电可以读取最新的实时时钟信息。而如果BT1座没有安装电池,则开发板断电后,后备寄存器也是没有电的,程序会重新配置时间信息,所以每次上电看到的时间都是2018-12-31 23:59:50。sw_delay_ms延时函数与RTC秒时钟的对比在本实验中可以体现。

图10:BT1座安装有电池实验现象

图11:BT1座没有安装电池实验现象

标签: #stm32rtc时钟转换算法 #stm32f103 dma2时钟使能