文章参考:STM32学习笔记一一RTC实时时钟、STM32之RTC实时时钟、【STM32】RTC实时时钟,步骤超细详解,一文看懂RTC、超透彻的STM32讲解资料——RTC时钟、stm32——RTC实时时钟、STM32的RTC的原理与使用(附代码)
一、RTC定义
1. 定义和特征
RTC(Real Time Clock 实时时钟):是个独立的BCD定时器/计数器。RTC 提供一个日历时钟,两个可编程闹钟中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC还包含用于管理低功耗模式的自动唤醒单元。主要有如下特性:
- 可编程的分频器:分频因子可以高达220
- 用于长期测量的32位 可编程的计数器
- 2个独立的时钟: PCLK1 时钟(用于APB1接口)和RTC 时钟 (必须比PCLK1时钟慢至少4倍)
- 2个单独的复位类型:
APB1接口由系统复位复位
RTC内核 (分频器,警告,计数器和除法器)通过备份区域复位来复位。
- 3个专用的可屏蔽中断线:
警告中断线,用来产生一个软件可编程的警告中断。
秒中断线,用来产生一个周期性(周期长度可编程,高达1s)的中断信号
溢出中断线,用来检测内部可编程计数器什么时候翻滚到0。
2. RTC的时钟源
STM32的RTC外设,实质是一个掉电后还继续运行的定时器。所谓掉电,是指电源Vpp断开的情况下,为了RTC外设掉电可以继续运行,必须给STM32芯片通过VBAT引脚接上锂电池。当主电源VDD有效时,由VDD给RTC外设供电。当VDD掉电后,由VBAT给RTC外设供电。无论由什么电源供电,RTC中的数据始终都保存在属于RTC的备份域中,如果主电源和VBA都掉电,那么备份域中保存的所有数据都将丢失。(备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数序,系统复位或电源复位时,这些数据也不会被复位)。
从RTC的定时器特性来说,它是一个32位的计数器,只能向上计数。它使用的时钟源有三种,分别为:
- 高速外部时钟HSE**(8MHz)的128分频:HSE/128;**
- 低速内部时钟LSI(40kHz);
- 低速外部时钟LSE;
使用HSE分频时钟或者LSI的时候,在主电源VDD掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC正常工作。所以RTC一般都时钟低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,因为32768 = 2^15,分频容易实现,所以被广泛应用到RTC模块。(在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式)。
3. RTC的工作原理
RTC由2个主要部分组成,第一部分(APB1接口)用来和APB1总线相连。此单元还包含一组16位寄存器,可通过APB1总线对其进行读写操作。APB1接口由APB1总线时钟驱动,用来与APB1总线接口。第二部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一模块是RTC的预分频模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。RTC的预分频模块包含了一个20位的可编程分频器(RTC_PRL)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个TR_CLK周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始化为当前的系统时间,一个32位的时钟计数器,按秒钟计算,可以记录4,294,967,296秒,约合136年左右,作为一般应用,这已经是足够了的。
RTC 还有一个闹钟寄存器 RTC_ALR,用于产生闹钟。系统时间按 TR_CLK 周期累加并与存储在 RTC_ALR 寄存器中的可编程时间相比较,如果 RTC_CR 控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。RTC 内核完全独立于 RTC APB1 接口,而软件是通过 APB1 接口访问 RTC 的预分频值、计数器值和闹钟值的。但是相关可读寄存器只在 RTC APB1 时钟进行重新同步的 RTC 时钟的上升沿被更新, RTC 标志也是如此。这就意味着,如果 APB1 接口刚刚被开启之后,在第一次的内部寄存器更新之前,从 APB1 上读取的 RTC 寄存器值可能被破坏了(通常读到 0)。因此,若在读取 RTC 寄存器曾经被禁止的 RTC APB1 接口,软件首先必须等待 RTC_CRL 寄存器的 RSF位(寄存器同步标志位, bit3)被硬件置 1。
图中浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行。这部分仅包括RTC的分频器,计数器和闹钟控制器。若VDD电源有效,RTC可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断)。闹钟事件是在计数器RTC_CNT的值等于闹钟寄存器RTC_ALR的值时触发的。
因为RTC的寄存器是属于备份域,所以它的所有寄存器都是16位的。它的计数RTC_CNT的32位由RTC_CNTL和RTC_CNTH两个寄存器组成,分别保存计数值的低16位和高16位。在配置RTC模块的时钟时,把输入的32768Hz的RTCCLK进行32768分频得到实际驱动计数器的时钟TR_CLK = RTCCLK/37768 = 1Hz,计时周期为1秒,计时器在TR_CLK的驱动下计数,即每秒计数器RTC_CNT的值加1(常用)。
由于备份域的存在,使得RTC核具有了完全独立于APB1接口的特性,也因此对RTC寄存器的访问要遵守一定的规则。
系统复位后,禁止访问后备寄存器和RCT,防止对后卫区域(BKP)的意外写操作。(执行以下操作使能对后备寄存器好RTC的访问):
- 设置RCC_APB1ENR寄存器的PWREN和BKPEN位来使能电源和后备接口时钟.
- 设置PWR_CR寄存器的DBP位使能对后备寄存器和RTC的访问.
设置为可访问后,在第一次通过APB1接口访问RTC时,必须等待APB1与RTC外设同步,确保被读取出来的RTC寄存器值是正确的,如果在同步之后,一直没有关闭APB1的RTC外设接口,就不需要再次同步了。
如果内核要对RTC寄存器进行任何的写操作,在内核发出写指令后,RTC模块在3个RTCCLK时钟之后,才开始正式的写RTC寄存器操作。我们知道RTCCLK的频率比内核主频低得多,所以必须要检查RTC关闭操作标志位RTOFF当这个标志被置1时,写操作才正式完成。
二、RTC的寄存器
typedef struct
{
__IO uint16_t CRH;
uint16_t RESERVED0;
__IO uint16_t CRL;
uint16_t RESERVED1;
__IO uint16_t PRLH;
uint16_t RESERVED2;
__IO uint16_t PRLL;
uint16_t RESERVED3;
__IO uint16_t DIVH;
uint16_t RESERVED4;
__IO uint16_t DIVL;
uint16_t RESERVED5;
__IO uint16_t CNTH;
uint16_t RESERVED6;
__IO uint16_t CNTL;
uint16_t RESERVED7;
__IO uint16_t ALRH;
uint16_t RESERVED8;
__IO uint16_t ALRL;
uint16_t RESERVED9;
} RTC_TypeDef;
2.1 RTC 的控制寄存器——RTC_CRH 寄存器
该寄存器用来控制中断的。
2.2 RTC 的控制寄存器——RTC_CRL 寄存器
RTC 用到的是该寄存器的 0、 3~5 这几个位,第 0 位是秒钟标志位,我们在进入闹钟中断的时候,通过判断这位来决定是不是发生了秒钟中断,然后必须通过软件将该位清零(写 0)。第 3 位为寄存器同步标志位,我们在修改控制寄存器 RTC_CRH``/CRL 之前,必须先判断该位,是否已经同步了,如果没有则等待同步,在没同步的情况下修改RTC_CRH/CRL 的值是不行的。第 4 位为配置标位,在软件修改 RTC_CNT/RTC_ALR/RTC_PRL 的值的时候,必须先软件置位该位,以允许进入配置模式。第 5 位为 RTC 操作位,该位由硬件操作,软件只读。通过该位可以判断上次对 RTC 寄存器的操作是否完成,如果没有,我们必须等待上一次操作结束才能开始下一次操作。
RTC模块的各个寄存器的访问规则为:首先确认RTOFF位为1,然后,置CNF位为1进入配置模式,接着,配置各个RTC寄存器(包括RTC_CRH),之后,清零CNF位退出配置模式,最后,等待RTOFF位为1。
2.3 RTC 预分频装载寄存器——RTC_PRLH 寄存器和RTC_PRLL 寄存器
RTC_PRLH和RTC_PRLL是两个16位的寄存器,RTC_PRLH的高14位保留,RTC_PRLH的第[3:0]位(作为PRL[19:16])与RTC_PRLL的第[15:0]位(作为PRL[15:0])组合成PRL[19:0],TR_CLK = RTCCLK / (PRL[19:0]+1)。
这两个寄存器用来配置 RTC 时钟的分频数的,比如我们使用外部32.768K 的晶振作为时钟的输入频率,那么我们要设置这两个寄存器的值为32767,以得到一秒钟的计数频率。
2.4 RTC 预分频器余数寄存器——RTC_DIVH 寄存器和RTC_DIVL 寄存器
这两个寄存器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到 0.1 秒,或者 0.01 秒等。该寄存器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新装载。其减计数到0后,RTC_PRLH和RTC_PRLL中的预装值将自动装入RTC_DIVH和RTC_DIVL中。
2.5 RTC 计数器寄存器——RTC_CNTH寄存器和RTC_CNTL寄存器
RTC_CNT寄存器由 2 个 16 位的寄存器组成 RTC_CNTH 和 RTC_CNTL,总共 32 位,用来记录秒钟值(一般情况下)。在修改这个寄存器的时候要先进入配置模式。
2.6 RTC 闹钟寄存器——RTC_ALRH寄存器和 RTC_ALRL寄存器
该寄存器也是由 2 个 16 位的寄存器组成 RTC_ALRH 和 RTC_ALRL。总共也是 32 位,用来标记闹钟产生的时间(以秒为单位),如果 RTC_CNT 的值与 RTC_ALR的值相等,并使能了中断的话,会产生一个闹钟中断。该寄存器的修改也要进入配置模式才能进行。
附加:2.7 备份区域控制寄存器RCC_BDCR
因为我们使用到备份寄存器来存储RTC的相关信息(我们这里主要用来标记时钟是否已经经过了配置),这里顺便介绍以下STM32的备份寄存器。
备份寄存器是42个16位的寄存器(大容量产品才有,ALIENTEK Mini STM32开发板使用的是STM32F103RBT6,属于小容量产品,只有10个16位的寄存器),可用来存储 84 个字节的用户应用程序数据。它们处在备份域里,当 VDD 电源被切断,它们仍然由 VBAT 维持供电。即使系统在待机模式下被唤醒,或系统复位或电源复位时,它们也不会被复位。
此外,BKP控制寄存器用来管理侵入检测和RTC校准功能。
复位后,对备份寄存器和 RTC 的访问被禁止,并且备份域被保护以防止可能存在的意外的
写操作。执行以下操作可以使能对备份寄存器和 RTC 的访问:
1)通过设置寄存器 **RCC_APB1ENR** 的 **PWREN** 和 **BKPEN** 位来打开电源和后备接口的时钟;
2)电源控制寄存器 (**PWR_CR**) 的 **DBP** 位来使能对后备寄存器和 RTC 的访问。
一般用 BKP 来存储 RTC 的校验值或者记录一些重要的数据,相当于一个 EEPROM,不过这个 EEPROM 并不是真正的 EEPROM,而是需要电池来维持它的数据。
RTC 的时钟源选择及使能设置都是通过这个备份区域控制寄存器RCC_BDCR来实现的,所以我们在 RTC 操作之前先要通过这个寄存器选择 RTC 的时钟源,然后才能开始其他的操作。
三、RTC的初始化配置
"RTC"是Real Time Clock 的简称,意为实时时钟,修改计数器的值可以重新设置系统当前的时间和日期。RTC模块之所以具有实时时钟功能,是因为它内部维持了一个独立的定时器,通过配置,可以让它准确地每秒钟中断一次。但实际上,RTC就只是一个定时器而已,掉电之后所有信息都会丢失,因此我们需要找一个地方来存储这些信息,于是就找到了备份寄存器。其在掉电后仍然可以通过纽扣电池供电,所以能时刻保存这些数据。
1. 配置RTC前须知:
BKP:
RTC模块和时钟配置系统的寄存器是在后备区域的(即BKP),通过BKP后备区域来存储RTC配置的数据可以让其在系统复位或待机模式下唤醒后,RTC里面配置的数据维持不变。
PWR:
PWR为电源的寄存器,我们需要用到的是电源控制寄存器(PWR_CR),通过使能PWR_CR的DBP位来取消后备区域BKP的写保护。
2. 初始化配置RTC
(1) 使能电源时钟和备份区域时钟
要访问RTC和备份区域就必须先使能电源时钟和备份区域时钟。这个通过RCC_APB1ENR寄存器来设置。
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
(2) 取消备份区写保护
要向备份区域写入数据,就要先取消备份区域写保护(写保护在每次硬复位之后被使能,因为后备寄存器中放的是重要数据,默认是不允许往里面写入值的),否则是无法向备份区域写入数据的。我们需要用到向备份区域写入一个字节,来标志时钟已经配置过了,这样避免每次复位之后重新配置时钟。
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后备寄存器访问
(3) 复位备份区域,开启外部低速振荡器。
在取消备份区域写保护之后,我们可以先对这个区域复位,以清除前面的设置,当然这个操作不要每次都执行,因为备份区域的复位将导致之前存在的数据丢失,所以要不要复位,要看情况而定。然后我们使能外部低速振荡器LSE,注意这里一般要先判断RCC_BDCR的LSERDY位来确定外部低速振荡器已经就绪了才开始下面的操作。
/* Reset Backup Domain */
BKP_DeInit();//复位备份区域
/* Enable LSE */
RCC_LSEConfig(RCC_LSE_ON);//开启外部低速振荡器
/* Wait till LSE is ready */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)//判断外部低速振荡器是否已经就绪
{}
(4) 选择 RTC 时钟,并使能
将通过RCC_BDCR的RTCSEL来选择外部LSE作为RTC的时钟。然后通过RTCEN位使能RTC时钟。RCC_RTCCLKSource包括RCC_RTCCLKSource_LSE、RCC_RTCCLKSource_LSI 和 RCC_RTCCLKSource_HSE_Div128这三种选择。
/* Select LSE as RTC Clock Source */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择 LSE 作为 RTC 时钟
/* Enable RTC Clock */
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
(5) 设置 RTC 的分频,以及配置 RTC 时钟
在开启了 RTC 时钟之后,我们要做的是设置 RTC 时钟的分频数,通过 RTC_PRLH 和RTC_PRLL 来设置,然后等待 RTC 寄存器操作完成,并同步之后,设置秒钟中断。然后设置 RTC 的允许配置位(RTC_CRH 的 CNF 位),设置时间(其实就是设置RTC_CNTH 和 RTC_CNTL两个寄存器)。
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();//等待RTC寄存器与APB1同步
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();//等待对RTC的写操作完成
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);//使能RTC秒中断
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();//等待对RTC的写操作完成
/* Set RTC prescaler: set RTC period to 1sec */
RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
//设置RT 时钟分频: 使RTC定时周期为1秒
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();//等待对RTC的写操作完成
(6) 编写中断服务函数
在这个函数中并没有任何对RTC_CNT的操作,如果VDD掉电,RTC是无法触发秒中断的,所以想利用秒中断的方案实现实时时钟是不现实的,秒中断最适合用在类似本例程的触发显示的时间更新场合,而不是用于计数。
TimeDisplay是一个标志位,只有等于1时才让串口发送时间数据,即让串口一秒发一次时间值。
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* Clear the RTC Second interrupt */
//清除秒中断标志
RTC_ClearITPendingBit(RTC_IT_SEC);
/* Toggle LED1 */
STM_EVAL_LEDToggle(LED1);
/* Enable time update */
//把标志位置 1
TimeDisplay = 1;
/* Wait until last write operation on RTC registers has finished */
//等待RTC写操作完成
RTC_WaitForLastTask();
/* Reset RTC Counter when Time is 23:59:59 */
// 当时间走到23:59:59时,RTC计数器中的值清零,0x15180=Dec(23*3600+59*60+59+1)
if (RTC_GetCounter() == 0x00015180)
{
RTC_SetCounter(0x0);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
}
}
四、RTC的应用
1. main函数
main函数流程:
(1)配置串口(代码和之前的例程一样);
(2)配置RTC秒中断优先级,这里设置主优先级为1,次优先级为0(只用到一个RTC,中断随便写都可以)。(代码和之前的中断例程相似,只不过中断通道不一样,这里使用的中断通道是RTC_IRQn);
(3)查看RTC外设是否在本次VDD上电前被配置过,如果没有被配置过,则需要输入当前时间,重新初始化RTC和配置时间;
(4)配置好RTC后,根据秒中断设置的标志位,每隔1秒向终端更新一次。
int main(void)
{
//系统时钟初始化设置
SystemInit();
//配置串口
USART2_Config();
//配置RTC秒中断优先级
NVIC_Configuration();
//RTC检测及配置
RTC_CheckAndConfig();
//刷新时间
Time_Show();
}
2. 串口配置(见STM32的UART串口通信协议)
配置串口。
void USART2_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_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;
//STM_EVAL_COMInit(COM1, &USART_InitStructure);
GPIO_InitTypeDef GPIO_InitStructure;
/* Enable GPIO clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD| RCC_APB2Periph_AFIO, ENABLE);
/* Enable the USART2 Pins Software Remapping */
GPIO_PinRemapConfig(GPIO_Remap_USART2, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
/* Configure USART Tx as alternate function push-pull */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* Configure USART Rx as input floating */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_Init(GPIOD, &GPIO_InitStructure);
/* USART configuration */
USART_Init(USART2, USART_InitStruct);
/* Enable USART */
USART_Cmd(USART2, ENABLE);
}
3. 配置RTC秒中断优先级
配置RTC秒中断优先级,这里设置主优先级为1,次优先级为0,这里使用的中断通道是RTC_IRQn。
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* Enable the RTC Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
4. 输出时间到终端Time_Show():
时间的初始值设置好之后,让时间正常的走起来,同时在超级终端上显示当前时间。
TimeDisplay是RTC秒中断标志,RTC的秒中断被触发后,进入中断服务函数,把这个变量 TimeDisplay置1。这个函数是死循环检查这个标志,变为1时,调用Time_Display()显示最新时间,实现每隔1秒向终端更新一次时间,更新完后再把 TimeDisplay置0,等待下次秒中断。
void Time_Show(void)
{
printf("\n\r");
/* Infinite loop */
while (1)
{
/* If 1s has paased */
if (TimeDisplay == 1)
{
/* Display current time */
Time_Display(RTC_GetCounter());
TimeDisplay = 0;
}
}
}
4.1 显示时间Time_Display():
void Time_Display(uint32_t TimeVar)
{
uint32_t THH = 0, TMM = 0, TSS = 0;
//将计数器中的值换成时分秒分别赋值给THH,TMM,TSS
/* Compute hours */
THH = TimeVar / 3600;
/* Compute minutes */
TMM = (TimeVar % 3600) / 60;
/* Compute seconds */
TSS = (TimeVar % 3600) % 60;
//将时间传到超级终端上显示
printf("Time: %0.2d:%0.2d:%0.2d\r", THH, TMM, TSS);
}
5. RTC检测及配置
查看RTC外设是否在本次VDD上电前被配置过(先读取 BKP_DR1 的值,然后判断是否是 0XA5A5 来决定是不是要配置,需要配置的话,我们在备份区域 BKP_DR1 中写入 0XA5A5 代表我们已经初始化过时钟了),如果没有被配置过,则需要输入当前时间,重新初始化RTC和配置时间。
对于RTC的使用,首先我们要判断是否是第一次使用RTC,如果是第一次使用,那么肯定要设置时间的初始值以及对RTC进行相应的配置,如果不是第一次使用,那么我们就无需再设置时间的初始值以及对RTC进行相应的配置,只需让RTC计数器继续技术就可以了。那么如何才能判断RTC是否为第一次使用呢?STM32中有一个后备寄存器,寄存器中的值不会因为掉电而改变,那么我们第一次使用RTC时,往后备寄存器中写入一个值,下次再使用时,只要判断后备寄存器中的值是否为我第一次使用RTC写入的值,如果相等,说明我之前已经用过了RTC了,现在无需对RTC进行配置了,因为第一次都配置好了(RTC和后备寄存器一样,RTC寄存器中设置的值不会因为掉电而改变),但是要注意的是RTC的允许中断这一位再每次复位后会回到默认值,所以每次复位后我们都要再次设置允许RTC中断。
void RTC_CheckAndConfig(void)
{
//检查备份寄存器BKP_DR1,内容不为0xA5A5,则需要重新配置时间并且询问用户调整时间
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) {
/* Backup data register value is not correct or not yet programmed (when
the first time the program is executed) */
printf("\rRTC not yet configured....");
printf("\r\nconnect UART to configure");
/* RTC Configuration */
//RTC初始化配置
RTC_Configuration();
printf("\r\nRTC configured....");
/* Adjust time by values entred by the user on the hyperterminal */
Time_Adjust();
//再往备份寄存器BKP_DR1写入0xA5A5
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
//启动无需设置新时钟
else
{
/* Check if the Power On Reset flag is set */
//检查是否掉电重启
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
printf("\rPower On Reset occurred....");
}
/* Check if the Pin Reset flag is set */
//检查是否Reset复位
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
printf("\rExternal Reset occurred....");
}
printf("\rNo need to configure RTC....");
/* Wait for RTC registers synchronization */
//等待寄存器同步
RTC_WaitForSynchro();
/* Enable the RTC Second */
//允许RTC秒中断
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
//等待上次RTC寄存器写操作完成
RTC_WaitForLastTask();
}
//定义了时钟输出宏,则配置校正时钟输出到 PC13(tamper_RTC),用于RTC时钟频率的校准或调整时间补偿#ifdef RTCClockOutput_Enable
/* Enable PWR and BKP clocks */
//使能PWR和BKP的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */
//允许访问BKP备份域
PWR_BackupAccessCmd(ENABLE);
/* Disable the Tamper Pin */
BKP_TamperPinCmd(DISABLE); /* To output RTCCLK/64 on Tamper pin, the tamper
functionality must be disabled */
/* Enable RTC Clock Output on Tamper Pin */
//输出64分频时钟
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
/* Clear reset flags */
RCC_ClearFlag();
}
5.1 第一个if语句:RTC未被配置或者配置已经失效的情况
第一个if语句if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){}:调用BKP_ReadBackupRegister()读取RTC备份域寄存器里面的值,判断备份寄存器里面的值是否正确,根据后面代码,如果配置成功,会向备份域寄存器写入数值0xA5A5。(这个数值在VDD掉电后仍然会保存,如果VBAT也掉电,那么备份域,RTC所有寄存器将被复位,这时这个寄存器的值就不会等于0xA5A5了,RTC的计数器的值也是无效的。简单的说,就是写入的这个数值用作标志RTC是否从未被配置或配置是否已经失效,然后写入任何数值到任何一个备份域寄存器,只要检查的时候与写入值匹配就行了)。
第一个if语句显示 RTC未被配置或者配置已经失效的情况:
1)如果RTC从未被配置或者配置已经失效(备份域寄存器写入值不等于0xA5A5)这两种情况其中一种为真的话,则调用RTC_Configuration()来初始化RTC,配置RTC外设的控制参数,时钟分频等,并往电脑的超级终端打印出相应的调试信息;
2)初始化好RTC之后,调用函数 Time_Adjust() 让用户键入(通过超级终端输入)时间值;
3)输入时间值后,Time_Adjust() 函数把用户输入的北京时间转化为UNIX时间戳,并把这个UNIX时间戳写入到RTC外设的计数寄存器RTC_CNT。接着RTC外设在这个时间戳的基础上,每秒对RTC_CNT加1,RTC时钟就运行起来了,并且在VDD掉电还运行,以后需要知道时间就直接读取RTC的计时值,就可以计算出时间了;
4)设置好时间后,调用BKP_WriteBackupRegister()把0xA5A5这个值写入备份域寄存器,作为配置成功的标志。
5.1.1 RTC 初始化配置RTC_Configuration()
void RTC_Configuration(void)
{
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE);
/* Reset Backup Domain */
BKP_DeInit();
/* Enable LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* Wait till LSE is ready */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
/* Select LSE as RTC Clock Source */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* Enable RTC Clock */
RCC_RTCCLKCmd(ENABLE);
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Set RTC prescaler: set RTC period to 1sec */
RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
5.1.2 时间调节Time_Adjust():
使用Time_Regulate()从终端获取设置的初始时间值,调用库函数RTC_SetCounter()把初始时间值戳写入到计数器RTC_CNT,RTC就正式运行了。
void Time_Adjust(void)
{
/* Wait until last write operation on RTC registers has finished */
// 等待前面可能的 RTC 写操作完成
RTC_WaitForLastTask();
/* Change the current time */
//将设置的初始时间值装入RTC计数器,RTC开始运行时,计数器里面的值会在初始值的基础上自动一秒加一次
RTC_SetCounter(Time_Regulate());
/* Wait until last write operation on RTC registers has finished */
//等待 RTC 写操作完成
RTC_WaitForLastTask();
}
5.1.3 获取时间Time_Regulate():
从终端获取用户输入的时间,并将值储存在RTC计数寄存器中。要留意的是,从终端输入的是ASCII码,而不是实际数值(在USART_Scanf里面做处理)。
uint32_t Time_Regulate(void)
{
uint32_t Tmp_HH = 0xFF, Tmp_MM = 0xFF, Tmp_SS = 0xFF;
printf("\r\n===Time Settings==");
printf("\r\n Please Set Hours");
while (Tmp_HH == 0xFF)
{
//将串口接收的数据给Tmp_HH,23便是传递给USART_Scanf中的形参Value的 Tmp_HH = USART_Scanf(23);
}
printf(": %d", Tmp_HH);
printf("\r\n Please Set Minutes");
while (Tmp_MM == 0xFF)
{
Tmp_MM = USART_Scanf(59);
}
printf(": %d", Tmp_MM);
printf("\r\n Please Set Seconds");
while (Tmp_SS == 0xFF)
{
Tmp_SS = USART_Scanf(59);
}
printf(": %d", Tmp_SS);
/* Return the value to store in RTC counter register */
//将时分秒转换成秒放入RTC计数器中
return((Tmp_HH*3600 + Tmp_MM*60 + Tmp_SS));
}
5.1.4 ASCII 码转换为数字的USART_Scanf()
串口从超级终端中接收设置的时间初始值,转换为数字。
uint8_t USART_Scanf(uint32_t value)
{
uint32_t index = 0;
uint32_t tmp[2] = {0, 0};
while (index < 2)
{
/* Loop until RXNE = 1 */
//等待数据接收完成
while (USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET)
{}
//将数据给数组tmp,从串口终端里面输进去的数是ASCII码值
tmp[index++] = (USART_ReceiveData(USART2));
//数字0到9的ASCII码为0x30至0x39,判断接收到的数据是否在0-9之间
if ((tmp[index - 1] < 0x30) || (tmp[index - 1] > 0x39))
{
printf("\n\rPlease enter valid number between 0 and 9");
index--;
}
}
/* Calculate the Corresponding value */
//计算输入字符的 ASCII 码转换为数字,将接收到的两个数据组成一个两位数
index = (tmp[1] - 0x30) + ((tmp[0] - 0x30) * 10);
/* Checks */
//判断组成后的值是否有效,比如组成的小时不能超过23,分秒不能超过59,Value即23或59
if (index > value)
{
printf("\n\rPlease enter valid number between 0 and %d", value);
return 0xFF;
}
return index;
}
5.2 第一个else语句:确认RTC曾经被配置过的情况
1)调用RCC_GetFlagStatus检测是上电复位还是按键复位,根据不同的复位情况在超级终端中打印出不同的调试信息(两种复位都不需要重新设置RTC里面的时间值);
2)调用RTC_WaitForSynchro等待APB1接口与RTC外设同步,上电后第一次通过APB1接口访问RTC时必须要等待同步;
3)同步完成后调用RTC_ITConfig()使能RTC外设的秒中断(使能RTC的秒中断是一个对RTC外设寄存器的写操作);
4)进行写操作以后,必须调用RTC_WaitForLastTask()来等待,确保写操作完成;
5.3 时钟输出宏定义
在下面有一个条件编译选项询问是否需要output RTCCLK/64 on Tamper pin,这是RTC的时钟输出配置,在rtc的头文件定义 RTCClockOutput_Enable这个宏,PC13引脚会输出RTCCLK的64分频时钟,主要是用于RTC时钟频率的校准或调整时间补偿。
五、工程建立
1. 新建工程添加的文件夹和子文件
硬件:STM32F107VC( Cortex-M3)
软件:Keil μVision4,JLINK
2. 完整的main程序
/**
******************************************************************************
* @file RTC/Calendar/main.c
* @brief Main program body
*/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "stm3210c_eval_lcd.h"
#include "stm32_eval.h"
#include "fonts.h"
#include <stdio.h>
/** @addtogroup STM32F10x_StdPeriph_Examples
* @{
*/
/** @addtogroup RTC_Calendar
* @{
*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define RTCClockOutput_Enable /* RTC Clock/64 is output on tamper pin(PC.13) */
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
__IO uint32_t TimeDisplay = 0;
USART_InitTypeDef USART_InitStructure;
extern uint16_t Xaddr;
extern uint8_t Yaddr;
extern const uint16_t ASCII_Table[];
/* Private function prototypes -----------------------------------------------*/
void RTC_Configuration(void);
void NVIC_Configuration(void);
uint32_t Time_Regulate(void);
void Time_Adjust(void);
void Time_Show(void);
void Time_Display(uint32_t TimeVar);
uint8_t USART_Scanf(uint32_t value);
#ifdef __GNUC__
/* With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/* Private functions ---------------------------------------------------------*/
/**
* @brief Main program.
* @param None
* @retval None
*/
int main(void)
{
/* Setup the microcontroller system. Initialize the Embedded Flash Interface,
initialize the PLL and update the SystemFrequency variable. */
SystemInit();
/* Initialize the LCD */
STM3210C_LCD_Init();
/* Clear the LCD */
LCD_Clear(White);
/* Set the LCD Text Color */
LCD_SetTextColor(Black);
/* Initialize LED1 mounted on STM3210X-EVAL board */
STM_EVAL_LEDInit(LED1);
/* USARTx configured as follow:
- BaudRate = 115200 baud
- Word Length = 8 Bits
- One Stop Bit
- No parity
- Hardware flow control disabled (RTS and CTS signals)
- Receive and transmit enabled
*/
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_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;
STM_EVAL_COMInit(COM1, &USART_InitStructure);
printf(" STM3210C-EVAL \n\r");
printf(" RTC Caslendar \n\r");
/* NVIC configuration */
NVIC_Configuration();
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
/* Backup data register value is not correct or not yet programmed (when
the first time the program is executed) */
printf("\rRTC not yet configured....");
printf("\r\nconnect UART to configure");
/* RTC Configuration */
RTC_Configuration();
printf("\r\nRTC configured....");
/* Adjust time by values entred by the user on the hyperterminal */
Time_Adjust();
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
else
{
/* Check if the Power On Reset flag is set */
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
printf("\rPower On Reset occurred....");
}
/* Check if the Pin Reset flag is set */
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
printf("\rExternal Reset occurred....");
}
printf("\rNo need to configure RTC....");
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
#ifdef RTCClockOutput_Enable
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE);
/* Disable the Tamper Pin */
BKP_TamperPinCmd(DISABLE); /* To output RTCCLK/64 on Tamper pin, the tamper
functionality must be disabled */
/* Enable RTC Clock Output on Tamper Pin */
BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
#endif
/* Clear reset flags */
RCC_ClearFlag();
/* Display time in infinite loop */
Time_Show();
}
/**
* @brief Configures the nested vectored interrupt controller.
* @param None
* @retval None
*/
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* Configure one bit for preemption priority */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
/* Enable the RTC Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/**
* @brief Configures the RTC.
* @param None
* @retval None
*/
void RTC_Configuration(void)
{
/* Enable PWR and BKP clocks */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */
PWR_BackupAccessCmd(ENABLE);
/* Reset Backup Domain */
BKP_DeInit();
/* Enable LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* Wait till LSE is ready */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{}
/* Select LSE as RTC Clock Source */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* Enable RTC Clock */
RCC_RTCCLKCmd(ENABLE);
/* Wait for RTC registers synchronization */
RTC_WaitForSynchro();
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Enable the RTC Second */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Set RTC prescaler: set RTC period to 1sec */
RTC_SetPrescaler(32767); /* RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1) */
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
/**
* @brief Returns the time entered by user, using Hyperterminal.
* @param None
* @retval Current time RTC counter value
*/
uint32_t Time_Regulate(void)
{
uint32_t Tmp_HH = 0xFF, Tmp_MM = 0xFF, Tmp_SS = 0xFF;
printf("\r\n===Time Settings==");
printf("\r\n Please Set Hours");
while (Tmp_HH == 0xFF)
{
Tmp_HH = USART_Scanf(23);
}
printf(": %d", Tmp_HH);
printf("\r\n Please Set Minutes");
while (Tmp_MM == 0xFF)
{
Tmp_MM = USART_Scanf(59);
}
printf(": %d", Tmp_MM);
printf("\r\n Please Set Seconds");
while (Tmp_SS == 0xFF)
{
Tmp_SS = USART_Scanf(59);
}
printf(": %d", Tmp_SS);
/* Return the value to store in RTC counter register */
return((Tmp_HH*3600 + Tmp_MM*60 + Tmp_SS));
}
/**
* @brief Adjusts time.
* @param None
* @retval None
*/
void Time_Adjust(void)
{
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Change the current time */
RTC_SetCounter(Time_Regulate());
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
/**
* @brief Displays the current time.
* @param TimeVar: RTC counter value.
* @retval None
*/
void Time_Display(uint32_t TimeVar)
{
uint32_t THH = 0, TMM = 0, TSS = 0;
/* Compute hours */
THH = TimeVar / 3600;
/* Compute minutes */
TMM = (TimeVar % 3600) / 60;
/* Compute seconds */
TSS = (TimeVar % 3600) % 60;
printf("Time: %0.2d:%0.2d:%0.2d\r", THH, TMM, TSS);
}
/**
* @brief Shows the current time (HH:MM:SS) on the Hyperterminal.
* @param None
* @retval None
*/
void Time_Show(void)
{
printf("\n\r");
/* Infinite loop */
while (1)
{
/* If 1s has paased */
if (TimeDisplay == 1)
{
/* Display current time */
Time_Display(RTC_GetCounter());
TimeDisplay = 0;
}
}
}
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
uint8_t Ascii;
/* Place your implementation of fputc here */
/* e.g. write a character to the USART */
USART_SendData(EVAL_COM1, (uint8_t) ch);
/* Loop until the end of transmission */
while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TC) == RESET)
{}
Ascii = ch-32;
switch(ch)
{case('\n'):
Xaddr=0;
Yaddr+=24;
if(Yaddr>216)
{Yaddr=Line0;
LCD_Clear(White);
}
break;
case('\r'):
Xaddr=0;
break;
case('\b'):
if(Xaddr==0)
{Xaddr=304;
if(Yaddr>0)
Yaddr-=24;
}
Xaddr-=16;
break;
default:
if(Xaddr==320)
{Xaddr=0;
Yaddr+=24;
}
if(Yaddr>216)
{Yaddr=Line0;
LCD_Clear(White);
}
LCD_DrawChar(Yaddr, Xaddr+15, &ASCII_Table[Ascii * 24]);
Xaddr+=16;
break;
}
LCD_SetCursor(Xaddr,Yaddr);
return ch;
}
/**
* @brief Gets numeric values from the hyperterminal.
* @param None
* @retval None
*/
uint8_t USART_Scanf(uint32_t value)
{
uint32_t index = 0;
uint32_t tmp[2] = {0, 0};
while (index < 2)
{
/* Loop until RXNE = 1 */
while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_RXNE) == RESET)
{}
tmp[index++] = (USART_ReceiveData(EVAL_COM1));
if ((tmp[index - 1] < 0x30) || (tmp[index - 1] > 0x39))
{
printf("\n\rPlease enter valid number between 0 and 9");
index--;
}
}
/* Calculate the Corresponding value */
index = (tmp[1] - 0x30) + ((tmp[0] - 0x30) * 10);
/* Checks */
if (index > value)
{
printf("\n\rPlease enter valid number between 0 and %d", value);
return 0xFF;
}
return index;
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{}
}
#endif
/**
* @}
*/
/**
* @}
*/
/******************* (C) COPYRIGHT 2009 STMicroelectronics *****END OF FILE****/
3. 完整的stm32f10x_it.c
/**
******************************************************************************
* @file RTC/Calendar/stm32f10x_it.c
* @brief Main Interrupt Service Routines.
* This file provides template for all exceptions handler and
* peripherals interrupt service routine.
*/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_it.h"
#include "stm32_eval.h"
/** @addtogroup STM32F10x_StdPeriph_Examples
* @{
*/
/** @addtogroup RTC_Calendar
* @{
*/
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern __IO uint32_t TimeDisplay;
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/******************************************************************************/
/* Cortex-M3 Processor Exceptions Handlers */
/******************************************************************************/
/**
* @brief This function handles NMI exception.
* @param None
* @retval None
*/
void NMI_Handler(void)
{
}
/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
void HardFault_Handler(void)
{
/* Go to infinite loop when Hard Fault exception occurs */
while (1)
{}
}
/**
* @brief This function handles Memory Manage exception.
* @param None
* @retval None
*/
void MemManage_Handler(void)
{
/* Go to infinite loop when Memory Manage exception occurs */
while (1)
{}
}
/**
* @brief This function handles Bus Fault exception.
* @param None
* @retval None
*/
void BusFault_Handler(void)
{
/* Go to infinite loop when Bus Fault exception occurs */
while (1)
{}
}
/**
* @brief This function handles Usage Fault exception.
* @param None
* @retval None
*/
void UsageFault_Handler(void)
{
/* Go to infinite loop when Usage Fault exception occurs */
while (1)
{}
}
/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
}
/**
* @brief This function handles SVCall exception.
* @param None
* @retval None
*/
void SVC_Handler(void)
{
}
/**
* @brief This function handles PendSV_Handler exception.
* @param None
* @retval None
*/
void PendSV_Handler(void)
{
}
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
}
/******************************************************************************/
/* STM32F10x Peripherals Interrupt Handlers */
/******************************************************************************/
/**
* @brief This function handles RTC global interrupt request.
* @param None
* @retval None
*/
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
/* Clear the RTC Second interrupt */
RTC_ClearITPendingBit(RTC_IT_SEC);
/* Toggle LED1 */
STM_EVAL_LEDToggle(LED1);
/* Enable time update */
TimeDisplay = 1;
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
/* Reset RTC Counter when Time is 23:59:59 */
if (RTC_GetCounter() == 0x00015180)
{
RTC_SetCounter(0x0);
/* Wait until last write operation on RTC registers has finished */
RTC_WaitForLastTask();
}
}
}
/******************************************************************************/
/* STM32F10x Peripherals Interrupt Handlers */
/* Add here the Interrupt Handler for the used peripheral(s) (PPP), for the */
/* available peripheral interrupt handler's name please refer to the startup */
/* file (startup_stm32f10x_xx.s). */
/******************************************************************************/
/**
* @brief This function handles PPP interrupt request.
* @param None
* @retval None
*/
/*void PPP_IRQHandler(void)
{
}*/
/**
* @}
*/
/**
* @}
*/
/******************* (C) COPYRIGHT 2009 STMicroelectronics *****END OF FILE****/