提示:今天是STM32学习的第四天,今天的学习笔记是RTC时钟,待机唤醒实验。
目录
第一讲 USMART 调试组件实验
1 USMART 调试组件简介
2.USMART 组件的移植
第二讲 RTC实时时钟+BKP
1.RTC实时时钟特征与原理图
1.1RTC (Real Time Clock):实时时钟
1.2 RTC时钟源编辑
2.RTC常用寄存器+库函数讲解
2.1常用寄存器
2.2uRTC相关库函数讲解
3.实验讲解
第三讲 待机唤醒实验
1.STM32低功耗模式讲解
1.1低功耗模式
1.2待机模式
2. 寄存器和库函数配置
2.1 寄存器
2.2库函数
3. 实验程序讲解
USMART 是由 ALIENTEK 开发的一个灵巧的串口调试互交组件,通过它你可以通过串口 助手调用程序里面的任何函数,并执行。因此,你可以随意更改函数的输入参数(支持数字(10/16 进制)、字符串、函数入口地址等作为参数),单个函数最多支持 10 个输入参数,并支持函数返 回值显示,目前最新版本为 V3.1。
USMART 的特点如下:
有了 USMART,可以轻易的修改函数参数、查看函数运行结果,从而快速解决问题。比如调试一个摄像头模块,需要修改其中的几个参数来得到最佳的效果,普通的做法:写函数 ->修改参数->下载->看结果->不满意->修改参数->下载->看结果->不满意….不停的循环,直到满 意为止。这样做很麻烦不说,单片机也是有寿命的啊,老这样不停的刷,很折寿。而利用 USMART,则只需要在串口调试助手里面输入函数及参数,然后直接串口发送给单片机,就执行了一次参数调整,不满意的话,你在串口调试助手修改参数在发送就可以了,直到你满意为止。这样,修改参数十分方便,不需要编译、不需要下载、不会让单片机折寿。
USMART 支持的参数类型基本满足任何调试了,支持的类型有:10 或者 16 进制数字、字 符串指针(如果该参数是用作参数返回的话,可能会有问题!)、函数指针等。因此绝大部分函 数,可以直接被 USMART 调用,对于不能直接调用的,你只需要重写一个函数,把影响调用 的参数去掉即可,这个重写后的函数,即可以被 USMART 调用了。
USMART 的实现流程简单概括就是:第一步,添加需要调用的函数(在 usmart_config.c 里 面的 usmart_nametab 数组里面添加);第二步,初始化串口;第三步,初始化 USMART(通过 usmart_init 函数实现);第四步,轮询 usmart_scan 函数,处理串口数据。
经过以上简单介绍,我们对 USMART 有了个大概了解,接下来我们来简单介绍下 USMART 组件的移植。USMART 组件总共包含 6 文件。其中 redeme.txt 是一个说明文件,不参与编译。其他五个文件,usmart.c 负责与外部互交等。 usmat_str.c 主要负责命令和参数解析。usmart_config.c 主要由用户添加需要由 usmart 管理的函 数。 usmart.h 和 usmart_str.h 是两个头文件,其中 usmart.h 里面含有几个用户配置宏定义,可以 用来配置 usmart 的功能及总参数长度(直接和 SRAM 占用挂钩)、是否使能定时器扫描、是否使 用读写函数等。
USMART 的移植,只需要实现 5 个函数。其中 4 个函数都在 usmart.c 里面,另外一个是串 口接收函数,必须有由用户自己实现,用于接收串口发送过来的数据。
第一个函数,串口接收函数。该函数,我们是通过 SYSTEM 文件夹默认的串口接收来实现 的,该函数在有介绍过。SYSTEM 文件夹里面的串口接收函 数,最大可以一次接收 200 字节,用于从串口接收函数名和参数等。大家如果在其他平台移植, 请参考 SYSTEM 文件夹串口接收的实现方式进行移植。
第二个是 void usmart_init(void)函数,该函数的实现代码如下:
//初始化串口控制器
//sysclk:系统时钟(Mhz)
void usmart_init(u8 sysclk)
{
#if USMART_ENTIMX_SCAN==1
Timer4_Init(1000,(u32)sysclk*100-1); //分频,时钟为 10K ,100ms 中断一次
//注意,计数频率必须为 10Khz,以和 runtime 单位(0.1ms)同步.
#endif
usmart_dev.sptype=1; //十六进制显示参数
}
第三和第四个函数仅用于服务 USMART 的函数执行时间统计功能(串口指令:runtime 1), 分别是:usmart_reset_runtime 和 usmart_get_runtime,这两个函数代码如下:
//复位 runtime
//需要根据所移植到的 MCU 的定时器参数进行修改
void usmart_reset_runtime(void)
{
TIM_ClearFlag(TIM4,TIM_FLAG_Update);//清除中断标志位
TIM_SetAutoreload(TIM4,0XFFFF);//将重装载值设置到最大
TIM_SetCounter(TIM4,0); //清空定时器的 CNT
usmart_dev.runtime=0; }
//获得 runtime 时间
//返回值:执行时间,单位:0.1ms,最大延时时间为定时器 CNT 值的 2 倍*0.1ms
//需要根据所移植到的 MCU 的定时器参数进行修改
u32 usmart_get_runtime(void)
{
if(TIM_GetFlagStatus(TIM4,TIM_FLAG_Update)==SET)//在运行期间产生定时器溢出
{
usmart_dev.runtime+=0XFFFF;
}
usmart_dev.runtime+=TIM_GetCounter(TIM4);
return usmart_dev.runtime; //返回计数值}
最后一个是 usmart_scan 函数,该函数用于执行 usmart 扫描,该函数需要得到两个参量, 第一个是从串口接收到的数组(USART_RX_BUF),第二个是串口接收状态(USART_RX_STA)。 接收状态包括接收到的数组大小,以及接收是否完成。
完成这几个函数的移植,你就可以使用 USMART 了。不过,需要注意的是,usmart 同外 部的互交,一般是通过 usmart_dev 结构体实现,所以 usmart_init 和 usmart_scan 的调用分别是 通过:usmart_dev.init 和 usmart_dev.scan 实现的。
RTC工作原理框图
RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。
BKP备份寄存器原理
提醒:一共有42个16位备份寄存器。常用来保存一些系统配置信息和相关标志位。
RTC控制寄存器高位(RTC_CRH)
①修改CRH/CRL寄存器,必须先判断RSF位,确定已经同步。
②修改CNT,ALR,PRL的时候,必须先配置CNF位进入配置模式,修改完之后,设置CNF位为0退出配置模式
③同时在对RTC相关寄存器写操作之前,必须判断上一次写操作已经结束,也就是判断RTOFF位是否置位。
库函数所在文件:
stm32f10x_rtc.c / stm32f10x_rtc.h
void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择
void RCC_RTCCLKCmd(FunctionalState NewState)//时钟使能
void RTC_SetPrescaler(uint32_t PrescalerValue);//预分频配置:PRLH/PRLL
void RTC_SetCounter(uint32_t CounterValue);//设置计数器值:CNTH/CNTL
void RTC_SetAlarm(uint32_t AlarmValue);//闹钟设置:ALRH/ALRL
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
void RTC_EnterConfigMode(void);//允许RTC配置 :CRL位 CNF
void RTC_ExitConfigMode(void);//退出配置模式:CRL位 CNF
void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
void RTC_WaitForSynchro(void);//等待时钟同步:CRL位RSF
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
其他相关函数(BKP等)
PWR_BackupAccessCmd();//BKP后备区域访问使能
RCC_APB1PeriphClockCmd();//使能PWR和BKP时钟
RCC_LSEConfig();//开启LSE,RTC选择LSE作为时钟源
PWR_BackupAccessCmd();//BKP后备区域访问使能
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读BKP寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//写BKP
RTC配置一般步骤
1.使能PWR和BKP时钟:RCC_APB1PeriphClockCmd();
2.使能后备寄存器访问: PWR_BackupAccessCmd();
3.配置RTC时钟源,使能RTC时钟:
RCC_RTCCLKConfig();
RCC_RTCCLKCmd();
如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
4.设置RTC预分频系数:RTC_SetPrescaler();
5.设置时间:RTC_SetCounter();
6.开启相关中断(如果需要):RTC_ITConfig();
7.编写中断服务函数:RTC_IRQHandler();
8.部分操作要等待写操作完成和同步。
RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
本实验用到的硬件资源有: 1) 指示灯 DS0 2) 串口 3) TFTLCD 模块 4) RTC
RTC 属于 STM32 内部资源,其配置也是通过软件设置好就可以 了。不过 RTC 不能断电,否则数据就丢失了,我们如果想让时间在断电后还可以继续走,那么 必须确保有电池。
在这三种低功耗模式中,最低功耗的是待机模式,在此模式下,最低只需要 2uA 左右的电 流。停机模式是次低功耗的,其典型的电流消耗在 20uA 左右。最后就是睡眠模式了。
通过以上介绍,我们了解了进入待机模式的方法,以及设置 WK_UP 引脚用于把 STM32 从待机模式唤醒的方法。具体步骤如下:
1)使能电源时钟。 因为要配置电源控制寄存器,所以必须先使能电源时钟。 在库函数中,使能电源时钟的方法是: RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //使能 PWR 外设时钟
2) 设置 WK_UP 引脚作为唤醒源。 使能时钟之后后再设置 PWR_CSR 的 EWUP 位,使能 WK_UP 用于将 CPU 从待机模式唤 醒。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是: PWR_WakeUpPinCmd(ENABLE); //使能唤醒管脚功能
3)设置 SLEEPDEEP 位,设置 PDDS 位,执行 WFI 指令,进入待机模式。 进入待机模式,首先要设置 SLEEPDEEP 位(该位在系统控制寄存器(SCB_SCR)的第 二位,详见《CM3 权威指南》,第 182 页表 13.1),接着我们通过 PWR_CR 设置 PDDS 位,使 得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP 中断的到来。在库函数中,进行上面三个功能进入待机模式是在函数 PWR_EnterSTANDBYMode 中实现的: void PWR_EnterSTANDBYMode(void);
4)最后编写 WK_UP 中断函数。 因为我们通过 WK_UP 中断(PA0 中断)来唤醒 CPU,所以我们有必要设置一下该中断函 数,同时我们也通过该函数里面进入待机模式。
通过以上几个步骤的设置,我们就可以使用 STM32 的待机模式了,并且可以通过 WK_UP 来唤醒 CPU,我们最终要实现这样一个功能:通过长按(3 秒)WK_UP 按键开机,并且通过 DS0 的闪烁指示程序已经开始运行,再次长按该键,则进入待机模式,DS0 关闭,程序停止运 行。类似于手机的开关机。
wkup.c 和 wkup.h 文件相关的用 户代码写在这两个文件中。我们需要引入 stm32f10x_pwr.c 和 stm32f0x_pwr.h 文件。
void Sys_Standby(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //使能PWR外设时钟
PWR_WakeUpPinCmd(ENABLE); //使能唤醒管脚功能
PWR_EnterSTANDBYMode(); //进入待命(STANDBY)模式
}
//系统进入待机模式
void Sys_Enter_Standby(void)
{
RCC_APB2PeriphResetCmd(0X01FC,DISABLE); //复位所有 IO 口
Sys_Standby();
}
//检测 WKUP 脚的信号
//返回值 1:连续按下 3s 以上
// 0:错误的触发
u8 Check_WKUP(void)
{
u8 t=0; //记录按下的时间
LED0=0; //亮灯 DS0
while(1)
{
if(WKUP_KD)
{
t++; //已经按下了
delay_ms(30);
if(t>=100) //按下超过 3 秒钟
{
LED0=0; //点亮 DS0
return 1; //按下 3s 以上了
}
}else
{
LED0=1;
return 0; //按下不足 3 秒
}
}
}
//中断,检测到 PA0 脚的一个上升沿.
//中断线 0 线上的中断检测
void EXTI0_IRQHandler(void)
{
EXTI_ClearITPendingBit(EXTI_Line0); // 清除 LINE10 上的中断标志位
if(Check_WKUP()) //关机?
{
Sys_Enter_Standby();
}
}
//PA0 WKUP 唤醒初始化
void WKUP_Init(void)
{ GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_AFIO, ENABLE); //使能 GPIOA 和复用功能时钟
GPIO_InitStructure.GPIO_Pin =GPIO_Pin_0; //PA.0
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPD; //上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 IO
//使用外部中断方式
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
//中断线 0 连接 GPIOA.0
EXTI_InitStructure.EXTI_Line = EXTI_Line0; //设置按键所有的外部线路
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //外部中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); // 初始化外部中断
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级 2 级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级 2 级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //外部中断通道使能
NVIC_Init(&NVIC_InitStructure); //初始化 NVIC
if(Check_WKUP()==0) Sys_Standby(); //不是开机,进入待机模式
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置 NVIC 中断分组 2
uart_init(115200); //串口初始化波特率为 115200
LED_Init(); //LED 端口初始化
WKUP_Init(); //待机唤醒初始化
LCD_Init(); //LCD 初始化
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Warship STM32");
LCD_ShowString(30,70,200,16,16,"WKUP TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/1/14");
while(1)
{ LED0=!LED0;
delay_ms(250);
}
}