上次我们学习了睡眠模式,这次我们来看一下停止模式。这也是STM32的一种低功耗模式。
停止模式只能通过外部中断触发进行唤醒。
一.了解
1.课前了解

我们首先复制一下对射红外传感器计次的代码。

起个新名字。

编译一下,方便代码提示。
2.代码前瞻

我们看到这个countsensor总是一直在get。但是如果外部一直没有中断信号的话,还是比较浪费资源,这个get是没有意义的。我们可以让他进入更为省电的停止模式。

在停止模式下1.8V区域的时钟关闭。CPU和外设都没有时钟了。但是外部中断是不需要时钟的。这一点从代码中也可以看出来。

我们初始化的时候,根本就没有开启EXTI外部中断的时钟。这也是EXTI能在时钟关闭的情况下工作的原因。因为它不需要外部时钟。
二.程序
写代码之前我们还是来了解一下库函数。上一次我们讲的睡眠模式其实都只是内核的操作。睡眠模式涉及到的几个寄存器,都是在内核里。跟PWR外设关系不大。所以我们上次没有用到PWR的库函数。现在的停止模式,涉及到内核之外的电路操作了,这就需要用到PWR外设了。
1.停止模式函数了解
(1)复位

恢复缺省配置,给PWR外设复位。
(2)使能后备区域

使能后备区域的访问。开启后后备区域才会上电。
(3)PVD函数

这两个是跟PVD相关的函数。

这个函数是配置PVD的阈值电压。

然后CMD就是使能PVD功能。如果需要使用PVD的话,就先指定阈值,然后使能PVD就可以。
(4)wakeup使能

使能位于PA0位置的WakeUp引脚。

这个是配合待机模式使用的。
(5)进入停止模式

进入停止模式
(6)进入待机模式

进入待机模式,
(7)标志位

获取标志位和清除标志位函数。
2.小测试
我们首先还是添加一个running指示。

在循环下面,加入一个指示。

编译一下。然后下载的时候,需要按住复位键不松开,才可以。下载完成后,松手。

这时候就开始闪烁了running。

然后遮挡传感器的话,数值就会增加。

但是在没有计数的情况下,还是在不断地running,这个操作是非常耗电的。
3.代码编写

首先我们开启PWR的时钟。如果外设没有开启时钟,那么写入寄存器是无效的。时钟开启之后我们需要让他在主循环的最后,进入停止模式。

我们使用这个函数,这个函数有两个参数,我们转到定义看一下。

第一个参数是指定电压调节器在停止模式里的状态。开启或者低功耗。

这两个都可以,我们暂时选择开启。
之后第二个参数是停止模式的入口参数。是选择WFI指令进入停止模式,还是WFE指令进入停止模式。

这里我们选择WFI指令进入停止模式。那么这一段代码干了什么呢,我们可以看一下函数内部。

第一步读取PWR_CR寄存器放在临时变量里面。
第二步清除PDDS和LPDS。其中清除PDDS位就是代表选择停止模式。

也就是这里,PDDS=0。这一步就会进入上面的操作,进入停机模式。
第三步,根据我们给定的参数,设置LPDAS位。如果你这个参数选择电压调节器开启,那么这里LPDS=0。
第四步,把临时变量,写入到PWR_CR寄存器参数生效。最后设置内核里面的SLEEPDEEP位。这里或等于(|=)SLEEPDEEP的掩码就是把SLEEPDEEP置1。

后面就是我们选择执行哪个指令,如果第二个阐述给的是WFI那么就执行WFI,反之执行WFE。

最后一行,就是停止模式唤醒之后,才能执行到。可以看到在退出停止模式后,他很贴心的把我们的SLEEPDEEP位清零了。
4.测试一下

我们编译下载,一定要按住复位键,要不下载不进去。

现在就可以看到在空闲时running就不会闪烁了。这说明我们的主循环时停止运行了。


我们每遮挡一下,runing就会显示一下,然后数值+1。


如果我们按一下复位键。复位之后第一次running闪烁很快。是正常的时间,我们遮挡的话,running闪烁就会变得很慢了,这是为什么呢。

就在这里。也就是我们在首次复位后,systeminit函数配置就是HSE*9倍频的72Mhz的主频。所以复位后,第一次runinig闪烁的很快。而之后进入停止模式在退出之后默认时钟就变为HSI了,HSI是8M的低速时钟。所以唤醒后的程序,就会变得很慢。

我们只需要进行在退出停止模式之后,再次调用一次systeminit函数就可以了。
我们再来测试一下。

可以看到这时running闪烁是正常的,着我们就解决了退出停止模式之后,主频变为8M的问题了。

到这里我们就完成了停止模式代码。
三.所有代码
1.main.c
cs
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "KEY.h"
#include "OLED.h"
#include "chuanganqi.h"
//uint8_t Keynum;
int main(void)
{
//开启PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
OLED_Init();
chuan_init();//外部中断初始化
OLED_ShowString(1, 1, "count:");
while(1)
{
OLED_ShowNum(1, 7, chuan_get(), 5);
//running指示
OLED_ShowString(2, 1,"Running");
Delay_ms(100);
OLED_ShowString(2, 1," ");
Delay_ms(100);
//开启停止模式
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
SystemInit();
}
}
2.chuanganqi.c
cs
#include "stm32f10x.h" // Device header
uint16_t chuan_count;
void chuan_init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);//开启GPIOB时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//开启AFIO时钟
//GPIO配置
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//AFIO配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
//EXTI配置
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line14;//指定中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//指定选择中断线的新状态,使能,失能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//指定外部中断线的模式,分为中断模式,事件模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//指定触发信号的有效边缘,上升沿,下降沿,边沿
EXTI_Init(&EXTI_InitStructure);
//NVIC配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//芯片只能分一组,一次,所有的模块都要写一样的。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;//指定中断通道开启或关闭。EXTI的15-10通道集成在这里。
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定通道使能or失能。
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;//指定抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定响应优先级
NVIC_Init(&NVIC_InitStructure);
}
//中断函数编写,因为中断函数是固定的,我们去文件夹复制一下
void EXTI15_10_IRQHandler(void)
{
//因为这函数EXTI15-10都可以进来我们需要判断是不是我们需要的中断通道。
if(EXTI_GetITStatus(EXTI_Line14) == SET)//返回值是set或reset
{
chuan_count++;
//要清除中断标志位,不清除就会一直置1从而一直中断
EXTI_ClearITPendingBit(EXTI_Line14);
}
}
int16_t chuan_get(void)
{
return chuan_count;
}
3.chuanganqi.h
cs
#include "stdint.h"
#ifndef __CHUAN_H //防止重定义
#define __CHUAN_H //那么就定义这个
void chuan_init(void);
int16_t chuan_get(void);
#endif