RTC代表的是实时时钟的意思。因为它提供的时钟基准比较准确所以用处还是很多的。本文章主要讲解基于STM32F103上的RTC闹钟中断功能以及用闹钟中断唤醒STM32的待机模式。
需要注意RTC的几个要点:1.RTC的值被设定后它就会一直按照设定的基准时间自己递增,如果你的硬件设备上发现一个纽扣电池,那就是给RTC功能供电用的。因此就算关闭掉设备的主电源,RTC的运行也是不受影响的,当然如果扣掉纽扣电池或者不带纽扣电池它就不工作了。2.每次STM32复位后这个RTC值它重新计数还是继续计数要考虑清楚3.RTC的值是有上限的,它的最大值就是2的32次方减一,这个数字很大因此不用太关心。
本文章的第一个例程是让RTC一次设定值后一秒递增一个单位,每过40秒就触发一个闹钟并进入中断,每经过一秒就将秒数输出到计算机上。下面就是RTC的初始化配置。(第一个程序从正点原子的程序上修改得来)
u8 RTC_Init(){u8 temp = 0;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);//电源时钟和背部时钟PWR_BackupAccessCmd(ENABLE);//允许背部区域写if (BKP_ReadBackupRegister(BKP_DR1) != 0xC0B4){ BKP_DeInit();RCC_LSEConfig(RCC_LSE_ON);while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){temp++;delay_ms(10);}if(temp>=250)return 1; RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForLastTask();RTC_WaitForSynchro();RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR, ENABLE);//打开RTC的秒中断和闹钟中断RTC_WaitForLastTask();RTC_EnterConfigMode(); //进入配置RTC模式RTC_SetPrescaler(32767); RTC_SetCounter(0); //初始值设定为0sRTC_WaitForLastTask();RTC_SetAlarm(40); //闹钟值设定为40sRTC_WaitForLastTask(); //等待上述配置完成RTC_ExitConfigMode();//退出配置模式BKP_WriteBackupRegister(BKP_DR1, 0XC0B4);PWR_BackupAccessCmd(DISABLE);//不允许背部区域写操作}else{PWR_BackupAccessCmd(DISABLE);RTC_WaitForSynchro();RTC_ITConfig(RTC_IT_SEC|RTC_IT_ALR,ENABLE); //打开RTC的秒中断和闹钟中断RTC_WaitForLastTask();}NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;//RTC全局中断的中断配置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);return 0;}//此初始化函数在主函数中的用法while(RTC_Init()){printf("INIT Programing is ERROR!!\r\n");}
if (BKP_ReadBackupRegister(BKP_DR1) != 0xC0B4) 的意思就是让STM32上电后自检是不是第一次运行这个程序。BKP_ReadBackupRegister(BKP_DR1)代表读取BKP_DR1的值,如果第一次运行这个程序那这个值一定是0X0000,值和0XC0B4不相等就进入配置初始化程序。BKP_WriteBackupRegister(BKP_DR1, 0XC0B4)代表往BKP_DR1这个寄存器中写入0XC0B4,注意BKP_DR1这个值被写入之后就算复位他也不会被清除成0000。这样的话就算复位或者重新上电,初始程序也不会再执行一遍,所以RTC的值就不会再重新设置了。如果想要RTC的值重新从0开始计数,那就可以吧0XC0B4改成一个新的数字,重新下载一次程序就可以了。
接下来的时钟配置选择打开外部低速时钟LSE,它向RTC提供时钟频率。它频率为是32.768KHZ.我们需要分频后使用它。分成1HZ。
根据上图1HZ = 32768/(32767+1) 所以RTC_SetPrescaler(32767)就能明白什么意思了吧。
为RTC提供时钟频率还是有两种选择的,这里我们用一种就够了。
RTC_WaitForLastTask()和RTC_WaitForSynchro()都是等待最近的写操作完成的意思,在RTC配置的时候这两条尤其第一条都是很重要的,如果添加位置不当或缺失程序有时候会卡在一个地方。
接下来打开RTC的秒中断和闹钟中断。在接下来就是对RTC的一些具体配置比如初始值和计数频率以及闹钟数值的配置。按照ST的说法:
要用到RTC_WaitForLastTask()和RTC_EnterConfigMode(进入配置)和RTC_ExitConfigMode(退出配置); 这三个库函数。
最后往BKP_DR1这个寄存器中写入0XC0B4代表第一次RTC配置已经完成。当程序复位或者设备重新上电后它都不会在进入这个配置程序了。还要关闭背部寄存器的写操作允许位。
else的程序就是第二次运行这个程序的时候执行的命令。主要是打开RTC的两个中断。
之后的程序就是配置RTC全局中断。这个中断包括了闹钟和秒中断。还有一个返回值要在主程序里面才能明白他的意义,简单来说返回0代表RTC的配置没有问题,返回1则相反。可以不用返回值的方法,因为一般没问题。
下面就是RTC全局中断的介绍:
void RTC_IRQHandler(){if(RTC_GetITStatus(RTC_IT_ALR)!=RESET) //是否闹钟中断发生{printf("THE ALARM READY =%d \r\n",RTC_GetCounter());//输出此时的秒数RTC_ClearITPendingBit(RTC_IT_ALR);PWR_BackupAccessCmd(ENABLE);RTC_EnterConfigMode();RTC_WaitForLastTask();RTC_SetAlarm(40+RTC_GetCounter()); //配置下次闹钟为40s后RTC_WaitForLastTask();RTC_ExitConfigMode(); PWR_BackupAccessCmd(DISABLE);}if(RTC_GetITStatus(RTC_IT_SEC)!=RESET) //是否秒中断发生{printf("Time is =%d \r\n",RTC_GetCounter()); //输出此时的秒数}RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清除秒中断标志位和溢出位RTC_WaitForLastTask();}
下面就是程序运行的结果。
要注意的一点就是在中断函数里面如果把判断秒中断的函数写在了判断闹钟中断的前面,就会得到THE ALARM READY=41。。。还有一点就是就算没有写反,下一次闹钟的时间是81秒,如图
下面就是待机唤醒:
待机模式是功耗最低的一种模式。外部复位(NRST 引脚)、 IWDG 复位、 WKUP 引脚上的上升沿或 RTC 闹钟事件
发生时,STM32从待机模式退出。其中外部复位和WKUP的方法比较简单。主要讲一下RTC闹钟唤醒。
这两个程序一个是让STM32的一个管脚接按键,按键按下的时候STM32进入待机状态,当40秒的闹钟到来时唤醒STM32.
另一个是当按键按下时候进入待机状态,闹钟设置为5S秒后,闹钟来临时唤醒STM32.
void RTC_Init(){NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);PWR_WakeUpPinCmd(ENABLE);PWR_BackupAccessCmd(ENABLE);BKP_DeInit();RCC_LSEConfig(RCC_LSE_ON);while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){}RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);RCC_RTCCLKCmd(ENABLE);RTC_WaitForLastTask();RTC_WaitForSynchro();RTC_ITConfig(RTC_IT_SEC, ENABLE);RTC_WaitForLastTask();RTC_EnterConfigMode(); RTC_SetCounter(0); //设置计数初始值RTC_WaitForLastTask();RTC_SetPrescaler(32767);RTC_WaitForLastTask();RTC_SetAlarm(40);//闹钟定时40SRTC_WaitForLastTask();RTC_ExitConfigMode();NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;//RTC全局中断 它的优先级要小于按键NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}void EXTIX_Init(void){EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource5);//配置按键PC5为外部中断EXTI_InitStructure.EXTI_Line=EXTI_Line5;EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd = ENABLE;EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;//外部中断的优先级高于RTCNVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure); }void EXTI9_5_IRQHandler() //按键对应的外部中断{EXTI_ClearITPendingBit(EXTI_Line5);PWR_EnterSTANDBYMode(); //进入待机模式}void RTC_IRQHandler() //RTC的中断{if(RTC_GetITStatus(RTC_IT_SEC)!=RESET){printf("TIME IS =%d \r\n",RTC_GetCounter());}RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW);RTC_WaitForLastTask();}
以上的例程就是让STM32运行模式下每秒都讲时间显示到计算机上,在按下连接PC5的按键后STM32进入待机模式,计时40秒后闹钟触发,STM32退出待机模式。要注意的是本程序没有设置保存RTC的值,因此每次从待机退出后时间从零开始重新计数。而且按键触发待机模式要在STM32上电40S之前,否则40S之后闹钟错过没法唤醒STM32.
13秒时候按下按键等待计数到40S的时候都是在待机过程中所以STM32串口没法发送数据。40s闹钟来临时计数又从0开始,退出待机模式相当于复位。
第二个例程当按键按下时候进入待机状态,闹钟设置为5S秒后,闹钟来临时唤醒STM32.运行的时候将秒数显示到计算机。本程序在上面那个上更改少些地方即可。
void RTC_Init(){NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP|RCC_APB1Periph_PWR,ENABLE);PWR_WakeUpPinCmd(ENABLE);PWR_BackupAccessCmd(ENABLE);if(PWR_GetFlagStatus(PWR_FLAG_SB)!=RESET) //如果现在处于待机模式{PWR_ClearFlag(PWR_FLAG_SB); //清除待机模式RTC_ITConfig(RTC_IT_SEC, ENABLE); //打开RTC中断RTC_WaitForSynchro();}else{BKP_DeInit();RCC_LSEConfig(RCC_LSE_ON);while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET){}RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);RCC_RTCCLKCmd(ENABLE);RTC_WaitForLastTask();RTC_WaitForSynchro();RTC_ITConfig(RTC_IT_SEC, ENABLE);RTC_WaitForLastTask();RTC_EnterConfigMode(); RTC_SetCounter(0);RTC_WaitForLastTask();RTC_SetPrescaler(32767);RTC_WaitForLastTask();//RTC_SetAlarm(35); //取消这个闹钟设置RTC_WaitForLastTask();RTC_ExitConfigMode();}NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);}void EXTI9_5_IRQHandler(){EXTI_ClearITPendingBit(EXTI_Line5);RTC_ClearFlag(RTC_FLAG_SEC);while(RTC_GetFlagStatus(RTC_FLAG_SEC)==RESET);RTC_SetAlarm(RTC_GetCounter()+5); //闹钟在此时刻加上5秒RTC_WaitForLastTask();PWR_EnterSTANDBYMode(); //进入待机模式}
其他的地方不需要更改。
这个程序的运行结果,可以看到在450s的时候进入待机模式5S之后程序接着运行。
时间并不是从0开始。