最近在试验中想用RTC闹钟做低STM32单片机的低功耗唤醒,在设置闹钟中断时很走了一段弯路。网上搜到到资料基本也没得到太多帮助,遇到问题其实很简单,现在整理一下,发出来,希望对遇到这问题的朋友有帮助。程序我特意简化了一下,超简单。
主程序,只包含基本的时钟设置、RTC初始化、LED端口设置,循环体只有两条,其实完全可以是一条,T++是本人喜好,不加难受。PCout(13)=LED;
完成将LED状态输出到GPIOC13口,驱动指示灯。
#include "stm32f10x.h" //STM32头文件
#include "sys.h"
extern u8 LED;
u32 T;
int main (void)//主程序
{
RCC_Configuration(); //时钟设置
RTC_Config(); //RTC初始化
/*开始使能程序中需要使用的外设时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //GPIOC外设时钟使能
GPIOC->CRH |=0X00100000; //LED端口设置GPIOC13针,输出模式
RTC_Alarm_Init(); //RTC闹钟初始化
RTC_Alarm_Set(5); //RTC闹钟设置,5秒后闹钟中断
T=0;
while(1)
{
PCout(13)=LED;
T++;
}//while
}//mian
RCC_Configuration()
、 RTC_Config()
这两个不必说,大家都有,这里不涉及到RTC时钟的具体时间设置,所以也不去列出。现在主要讲讲 RTC_Alarm_Init() ;RTC_Alarm_Set();
这两个程序
void RTC_Alarm_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure; //定义优先级结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
RTC->CRH |= 0x02; //ALRIE:允许闹钟中断
}
RTC_Alarm_Init()
这个程序完全可以加到RTC_Config()
中,但为了清晰问题,所以我单独做 了一个子程序。前面的几句为设置RTC全局中断优先级,最后一句作用为设置RTC->CRH的ALRIE位,来使能闹钟中断 。
void RTC_Alarm_Set(u16 PY)
{
uint32_t tmp = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP | RCC_APB1Periph_PWR,ENABLE);
PWR_BackupAccessCmd(ENABLE);
RTC->CRL |= 0x10; //RTC进入配置模式
tmp = RTC->CNTL+PY;
RTC->ALRH = RTC->CNTH +(tmp>>16); //取出进位
RTC->ALRL = tmp;
RTC->CRL &= 0xEF; //RTC退出配置模式
RTC_WaitForLastTask(); //等待寄存器写入完成
PWR_BackupAccessCmd(DISABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP | RCC_APB1Periph_PWR,DISABLE);
}
RTC_Alarm_Set(u16 PY)
这个程序完成闹钟设定,其中
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP | RCC_APB1Periph_PWR,ENABLE); //开启PWR、BKP时钟
PWR_BackupAccessCmd(ENABLE); //解锁后备域读写
这两句最重要,必须加上,因为我们在使用RTC时钟时,基本上电源时钟和后备域使能这两项是禁止的,所以后来想使用闹钟时,直接设置闹钟是不能成功的,因为RTC的特殊性,本身有自己的时钟,所以有无系统时钟和后备域的使能在RTC运行中并无影响。
tmp = RTC->CNTL+PY;
RTC->ALRH = RTC->CNTH +(tmp>>16); //取出进位
RTC->ALRL = tmp;
中间三句这完成将当前时间(CNT计数值)读出加上闹钟需要延时的时间(单位秒),再写入闹钟寄存器(ALRH,YALRL);这里对RTC_WaitForLastTask();
说一下我的见解,通过实验及查阅手册,发现现在网上一些代码在使用中存在误区,这个语句其实不必每次写RTC寄存器时都跟上一句,只要在退出配置模式后(RTC->CRL &= 0xEF;
),加一句就可以了,因为RTC只有在退出配置模式后才后执行一次写操作。
最后 关闭后备域写保护,关闭PWR、BKP时钟。建议这两句要加上。
下面的一个重要部分就是,在中断服务程序stm32f10x_it.c里的RTC闹钟中断处理程序,
void RTC_IRQHandler(void){ //RTC时钟全局中断函数(名称固定不可修改)
if(RTC_GetITStatus(RTC_IT_ALR) != RESET)
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清Alarm 中断标志位(ALRF)
LED=(LED==0); //LED状态翻转
RTC_Alarm_Set(2); //设定2s后闹钟
}
}
这里没什么说的了,效果就是连续设置闹钟,通过LED显示效果。整个过程就是复位后LED亮(低电平亮)5S,后续每隔大约每2秒LED指示灯亮灭切换一次。
下面把RCC_Configuration();
、RTC_Config();
这两个RTC初始化程序附上,保证大家程序调通,这里没什么讲的了。
void RCC_Configuration(void){ //RCC时钟的设置
ErrorStatus HSEStartUpStatus;
RCC_DeInit(); /* RCC system reset(for debug purpose) RCC寄存器恢复初始化值*/
RCC_HSEConfig(RCC_HSE_ON); /* Enable HSE 使能外部高速晶振*/
HSEStartUpStatus = RCC_WaitForHSEStartUp(); /* Wait till HSE is ready 等待外部高速晶振使能完成*/
if(HSEStartUpStatus == SUCCESS){
/*设置PLL时钟源及倍频系数*/
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); //RCC_PLLMul_x(枚举2~16)是倍频值。当HSE=8MHZ,RCC_PLLMul_9时PLLCLK=72MHZ
/*设置AHB时钟(HCLK)*/
RCC_HCLKConfig(RCC_SYSCLK_Div1); //RCC_SYSCLK_Div1——AHB时钟 = 系统时钟(SYSCLK) = 72MHZ(外部晶振8HMZ)
RCC_PCLK1Config(RCC_HCLK_Div2); //设置低速AHB时钟(PCLK1),RCC_HCLK_Div2——APB1时钟 = HCLK/2 = 36MHZ(外部晶振8HMZ)
RCC_PCLK2Config(RCC_HCLK_Div1); //设置高速AHB时钟(PCLK2),RCC_HCLK_Div1——APB2时钟 = HCLK = 72MHZ(外部晶振8HMZ)
FLASH_SetLatency(FLASH_Latency_2); //设置FLASH存储器延时时钟周期数
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //选择FLASH预取指缓存的模式,预取指缓存使能
RCC_PLLCmd(ENABLE); //使能PLL
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); //等待PLL输出稳定
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择SYSCLK时钟源为PLL
while(RCC_GetSYSCLKSource() != 0x08); //等待PLL成为SYSCLK时钟源
}
}
void RTC_First_Config(void){ //首次启用RTC的设置
RCC->APB1ENR|=(0x10000000+0x08000000);//启用PWR和BKP的时钟(from APB1)
PWR->CR|=0x0100; //后备域解锁
RCC->BDCR|=0x10000; //备份寄存器模块复位
RCC->BDCR=0x0001; //外部32.768KHZ晶振开启
while ((RCC->BDCR&0x02) != 0x02);//等待稳定
RCC->BDCR|=0x00000100;
RCC->BDCR|=0x8000;//RTC开启
while ((RTC->CRL&0x08) != 0x08);//开启后需要等待APB1时钟与RTC时钟同步,才能读写寄存器
//RTC_SetPrescaler(32762);//设置RTC分频器,使RTC时钟为1Hz,RTC period = RTCCLK/RTC_PR = (32.768 KHz)/(32767+1)
RTC->CRL |= 0x10; //RTC进入配置模式
RTC->PRLH = 0x00;
RTC->PRLL = 32767;
RTC->CRL &= 0xEF; //RTC退出配置模式
RTC_WaitForLastTask();//等待寄存器写入完成
}
void RTC_Config(void){ //实时时钟初始化
if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)//判断寄存数据是否丢失
RTC_First_Config();//重新配置RTC
else
{
RCC_ClearFlag();//清除RCC中复位标志
RCC_RTCCLKCmd(ENABLE);//使能RTCCLK
RTC_WaitForSynchro();//等待RTC时钟与APB1时钟同步
}
}
不差这个了,一并送上
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
//IO口地址映射
#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
#define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
#define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
#define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
#define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
#define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
#define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
#define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
#define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
#define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
#define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
#define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
#define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
#define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
void NVIC_Configuration(void); //嵌套中断控制器的设置
void RCC_Configuration(void); //RCC时钟类的设置
#endif