PWR电源控制
实验现象
1、修改主频
修改主频不属于三种低功耗模式,但是也是降低STM32功耗的一种方法。
修改主频在system_stm32f10.c这个文件。system_stm32f10.c用来配置时钟的,在文件里面程序预留了配置时钟的宏定义,将其中的宏定义解除注释,就能直接配置时钟的主频,见下图。
2、睡眠模式+串口发送+接收
这个代码的功能就是当收到一个字节时,中断触发,置标志位,主循环查询到标志位时读取数据,并用串口发送数据,在这个功能后面,又新加了一段代码,这个就是用来配置睡眠模式的代码,执行__WFI()函数芯片就进入睡眠。睡眠的目的是,如果STM32一直没收到数据,那这个主循环也会一直查询标志位,让它睡眠,收到数据后自动退出睡眠模式,执行一遍任务后继续睡眠,这样在空闲时芯片一直在睡眠,可以降低系统功耗。
注意,芯片在三种低功耗模式下,是没法直接下载程序的,如果直接点下载,就会提示报错,不会理你调试端口了。
解决方法,第一步我们按住复位键不放,第二步点下载按钮,第三步及时松开复位键,这样就能下载成功了。在我们本节三种低功耗模式下,都需要这样下载程序。注意一下,另外如果你不小心禁用了调试端口,其实也可以这样来解决。
只有在我们发送数据时刻,OLED才会显示一次running,在空闲时芯片一直都在睡眠,OLED不显示,这样就是在不影响程序功能的前提下,使用睡眠模式节约电量。
3、停止模式+对射式红外传感器计次
下载的操作是:按住复位键不放,点击下载,在松手。每次遮挡一次,执行一次记次,也显示一下running。在没有外部中断信号时,STM32处于停止模式,可以省电。
4、待机模式+RTC实时时钟
这个程序我使用的是LSE外部低速时钟,如果你没有RTC晶振或者RTC晶振不起振,也可以使用LSI内部低速时钟,LSI在待机模式下可以继续工作。然后在这个位置可以加入唤醒后要执行的功能,在进入待机模式之前,可以关闭各个外部连接的模块,以最大化省电,目前是用Oled_Clear模拟了一下。
这个程序会用实时时钟设定闹钟,每隔一段时间会自动唤醒一次,这里演示的是每隔十秒唤醒一次,唤醒之后执行一遍程序任务,然后继续待机。
OLED上显示了当前时钟和闹钟,随后进入待机,然后等一会儿,闹钟触发之后自动唤醒一次,设定新的那种执行程序功能之后继续待机,等待下一次唤醒,这是使用RTC和闹钟配合待机模式的自动唤醒程序,非常适合那种需要每隔一段时间操作一次,空闲时间又需要最大化省电的设备。

PWR介绍
- PWR(Power Control)电源控制
- PWR负责管理STM32内部的电源供电部分,可以实现可编程电压监测器和低功耗模式的功能
- 可编程电压监测器(PVD)可以监控VDD电源电压,当VDD下降到PVD阀值以下或上升到PVD阀值之上时,PVD会触发中断,用于执行紧急关闭任务
- 低功耗模式包括睡眠模式(Sleep)、停机模式(Stop)和待机模式(Standby)
低功耗模式的作用可在系统空闲时,降低STM32的功耗,延长设备使用时间,尤其是像一些用电池供电的设备,对空闲时候的耗电量是有极大要求的。比如数据采集设备,车钥匙,遥控器,报警器等等。这些产品都有个特点,就是在它们的生命周期里,绝大部分时间都是空闲状态,但是我们知道,单片机程序一旦开始正常运行的状态下,程序永远都不会停下来,所以主程序的最后一般都是个死循环。即使需要空闲,让程序停下来,那也得来个空循环,让程序一直转圈卡住。但是,程序运行就会耗电。空循环的耗电量也是很大的,比如遥控器,如果不用它的时候,程序一直空循环,那么用不到几天电池就没电了。这显然是不行的,所以说对于这些设备,我们需要这样的低功耗模式,在空闲状态时关闭不必要的硬件。比如我直接把CPU断电或者关闭时钟,这样程序自然就不会运行了,但在低功耗模式下,我们也需要保留必要的唤醒电路,比如串口接收数据的中断唤醒,外部中断唤醒,RTC闹钟唤醒等等,在需要设备工作时,STM32能够立刻重新投入工作,这样才行。如果你只考虑进入低功耗,而不考虑唤醒,STM32一睡不醒,那不就跟直接断电没区别了吗?
所以低功耗模式我们要考虑关闭哪些硬件,保留哪些硬件,以及如何去唤醒。当然,关闭越多的硬件设备,越省电,唤醒就越麻烦,这也是这三种低功耗模式在设计时所区分的地方。
电源框图
这个图就是STM32内部的供电方案,最上面是模拟部分供电,叫做VDDA。中间是数字部分供电,包括两块区域,VDD供电区域和1.8v供电区域。下面是后备供电,叫做VBAT。
最上面:VADDA供电区域,主要负责模拟部分的供电,其中包括AD转换器、温度传感器、复位模块、PLL锁相环,这些电路的供电正极是VDDA,负极是VSSA。其中AD转换器,还有两个参考电压的供电脚,叫做VREF+和VREF-,这两个脚在引脚多的型号里会单独引出来,在引脚少的型号,比如C8T6,VREF+和VREF-在内部就已经分别接到了VDDA和VSSA了。
中间部分:这一块由两部分组成,左边部分是VDD供电区域,其中包括IO电路、待机电路、唤醒逻辑和独立看门狗,右边部分是VDD通过电压调节器降压到1.8V,提供给后面这一块的1.8V供电区域。1.8V区域包括CPU核心、存储器和内置数字外设,可以看出来STM32内部的大部分关键电路,其实都是以1.8V的低电压运行的,当这些外设需要与外界进行交流时,才会通过IO电路转换到3.3V。使用低电压运行的主要目的是降低功耗,电压越低内部电路运行的功耗就相对越低。电压调节器,它的作用是给1.8V区供电。
最下面:VBAT后备供电区域,其中包括LSE 32K晶体振荡器、后备寄存器,RCC BDCR计寄存器和RTC。
RCC BDCR是RTC的寄存器,叫做备份域控制寄存器,也是和后备区域有关的寄存器,所以也可以有VBAT供电。
低电压检测器,可以控制这个开关,VDD有电时,由VDD供电,VDD没电时,由VBAT供电。
上电复位和掉电复位
首先是上电复位和掉电复位,这个意思是当VDD或者VDDA电压过低时,内部电路直接产生复位,让STM32复位,不要乱操作。
这个复位和不复位的界限之间,设置了一个40mv的迟滞电压,大于上限POR时解除复位,小于下限PDR时复位。这是一个典型的迟滞比较器,设置两个阈值的作用,就是防止电压在某个阈值附近波动时,造成输出也来回抖动。
下面的复位信号reset是低电平有效,所以在前面和后面,电压过低时是复位的,中间电压正常的时候不复位。
电压上限和下限具体是多少伏:还有这里解除复位,还有个滞后时间是多久呢。查看STM32数据手册,在5.3.3内嵌复位和电源控制模块特性里有这个表。
这里写了上电/掉电复位阈值,下降沿也就是PDR掉电复位的阈值下限,典型值是1.88V,上升沿也就是POR上电复位的阈值上限,典型值是1.92V,1.92-1.88就是迟滞的阈值40毫伏。所以如果忽略迟滞的话,简单来说就是大于1.9V上电,低于1.9V掉电。
最后一行就是TRSTTEMPO,复位持续时间,典型值是2.5ms。这就是上电复位和掉电复位。
可编程电压监测器PVD
然后下面这个是可编程电压监测器,简称PVD,他的工作流程也是监测VDD和,VDDA的供电电压。
但是PVD的区别就是,首先它这个阈值电压是可以使用程序指定的,可以自定义调节,调节的范围可以看一下数据手册。
在这个表的上面就是PVD的阈值,配置PLS寄存器的3个位,因为这里也同样是迟滞比较,所以有两个阈值,可选范围是2.2V到2.9V左右,PVD上限和下限之间的迟滞电压是100毫伏。
可以看到,PVD的电压是比上电掉电复位的电压要高的,画个图就是3.3伏是正常的供电,当这个电压降低在2.9伏到2.2伏之间,属于PVD监测的范围,可以通过PVD设置一个警告线,之后再降低到1.9伏,就是复位电路的检测范围,低于1.9伏直接复位住不让动,就是这两个电压监测的工作任务。
PVD触发之后,芯片还是能正常工作的,只不过是电源电压过低,该提醒一下用户了。
所以看,下面这个PVD输出,这个是正逻辑,电压过低时为1,电压正常值为0,这个信号可申请中断,在上升沿或者下降沿时触发中断,提醒程序进行适当的处理。另外这个PVD的中断申请,是通过外部中断实现的。
看一下外部中断这里,可以看到PVD输出的信号是跑到这里来了,所以如果要使用PVD的话,记得要配置外部中断。
然后下面有RTC,是RTC的闹钟信号,也有接到外部中断,其实RTC自己是有中断的,那为啥还要借到外部中断:因为低功耗模式设计的是,只有外部中断可以唤醒停止模式。
后面这两个USB和ETH,也都只有他们的wake up唤醒信号接过来了,目的也是为了唤醒停止模式。
低功耗模式(重点)
第一列,就是有哪几种模式,第二列 是如何配置,才能进入我们想要的模式,第三列是对于这些模式,进入之后,如何去唤醒,也就是模式的退出。最后三列,是每种模式对电路的操作,关闭了哪些东西,就是哪些电路不能用了,保留了哪些东西,就是哪些电路还是正常工作的
首先看一下睡眠模式,这是浅睡眠,进入浅睡眠,直接调用WFI或者WFE即可进入,这是内核指令,对应库函数也有对应的函数,直接调用函数即可。
WFI的意思是wait for interrupt等待中断(意思是我先睡了,如果有中断发生的话再叫我起来),所以对应的唤醒条件是任意中断,调用WIFI进入的睡眠模式,任何外设发生任何中断时,芯片都会立刻醒来。
WFE,意思是wait for event等待事件,对应的唤醒条件是唤醒事件,这个事件可以是外部中断配置为事件模式,也可以是使能中断,但是没有配置NVIC。调用WFE进入的睡眠模式,产生唤醒事件时会立刻醒来,醒来之后一般不需要进中断函数,直接从睡的地方继续运行,这是WFI和WFE的作用。
WFI/WFE区别相同点是调用任意一个之后,芯片都进入睡眠,不同点是WFI进入的得用中断唤醒,WFE进入的得用事件唤醒。
睡眠模式对电路的影响,对1.8V区域时钟的影响是,只把CPU时钟关了,对其他时钟和ADC时钟没有影响。对VDD区域时钟的影响是,无,对电压调节器的操作是,开,所以睡眠模式对电路的影响是只把CPU时钟关了,对其他电路没有任何操作。CPU时钟关了,程序就会暂停,不会继续运行了,CPU不运行,芯片功耗就会降低。
关闭电路通常有两个做法:一个是关闭时钟,另一个是关闭电源。
关闭时钟,所有的运算和涉及时序的操作都会暂停,但是寄存器和存储器里面保存的数据还可以维持不会消失。
关闭电源就是电路直接断电,电路的操作和数据都会直接丢失,所以关闭电源比关闭时钟更省电。
这个电压调节器,实际上是1.8V区域的电源,如果电压调节器关,就代表直接把1.8V区域断电。
在省电程度上为一般省电。
第二个停机模式,进入停机模式,首先sleepdeep位设置为1,告诉CPU你可以放心的睡进入深度睡眠模式。
- PDDS,用来区分它是停机模式,还是待机模式,PDDS等于0进入停机模式,PDDS等于1进入待机模式。
- LPDS用来设置电压调节器,RPDS=0,电压调节器开启,RPDS=1,电压调节器进入低功耗模式。 最后当我们把这些位提前设置好了,最后再调用WFI或者WFE,芯片就可以进入停止模式了。
- 停止模式的唤醒,唤醒条件就苛刻一些,是任一外部中断,要求就是只有外部中断才能唤醒,其他中断唤醒不了。刚才我们还提到了,PVD、RTC闹钟、USB唤醒、ETH唤醒借道了外部中断,所以这四个信号也可以唤醒停止模式。
- 另外这里并没有区分WFI和WFE,WFI要用外部中断的中断模式唤醒,WFE要用外部中断的事件模式唤醒。
- 停止模式对电路的操作,首先关闭所有1.8伏区域的时钟,这意思就是不仅CPU不能运行,外设也运行不了,定时器在定时的会暂停,串口收发数据也会暂停,不过由于没关闭电源,所以CPU和外设的寄存器数据都是维持原状的。
- 停止模式对VDD区域时钟的影响,关闭了HSI和HSE的振荡器,既然CPU和外设时钟都关了,那这两个高速时钟显然也没用。当然没提到的是,LSI内部低速时钟和LSE外部低速时钟,这两个并不会主动关闭,如果开启过这两个时钟还可以继续运行。
- 停止模式对电压调节器的影响,可以开启和低功耗模式,电压调节器无论是开启还是低功耗,都可以维持1.8伏区域寄存器和存储器的数据内容,区别就是电压调节器处于低功耗模式更省电一些,同时低功耗模式,在唤醒时要花更多的时间。相反电压调压器开启模式的话,就是更耗电一些唤醒更快了
- 总结停机模式,主要操作就是把运行的高速时钟都关了,CPU和外设都暂停工作,但是电压调节器并没有关,存储器和寄存器数据可以维持原样,它的唤醒条件比较苛刻,只能通过外部中断唤醒。所以停止模式相当于整个人都罢工了,脑子不工作,身体也不工作,只有有人用外部中断过来敲我,我才会醒来干活,在省电程度上为非常省电。
第三种待机模式,首先sleep deep也是置1即将深度睡眠,然后PDDS置1表示即将进入待机模式,最后调用WFI或者WFE就可以进入待机模式了。
唤醒条件,普通外设的中断和外部中断都无法唤醒待机模式,只有几个指定的信号才能唤醒
- 第一个是wake up引脚的上升沿,wake up引脚,可以看一下引脚定义,这里PA0-WKUP指示了引脚的位置,就是PA0的位置。
- 第二个是RTC闹钟事件,RTC闹钟可以唤醒待机模式,应用场景就是,芯片每隔一段时间自动工作一次。
- 第三个是NRST引脚上的外部复位,意思是按一下复位键,它也是能唤醒的。
- 最后一个IWDG独立看门狗复位。可以看出待机模式,只有这指定的四个信号能唤醒,唤醒条件最为苛刻。
待机模式对电路的操作,基本上是能关的全都关了,1.8伏区域的时钟关闭,两个高速时钟关闭,电压调节器关闭意味着1.8伏区域的电源关闭,内部的存储器和寄存器的数据全部丢失。但是和停止模式一样,并不会主动关闭LSI和LSE两个低速时钟,因为这两个时钟,还要维持RTC和独立看门狗的运行,所以不会关闭,这是待机模式的介绍。
待机模式总结:主要操作就是把能关的全都关掉,只保留几个唤醒的功能,当然配合RTC和独立看门狗的低速时钟,也可以正常工作。所以待机模式相当于这个人直接下班回家睡觉了,没有指定的这几个事,他是不会轻易回来工作的。在省电程度上,待机模式评级为极为省电。
几种模式选择
当执行WFI等待中断或者WFE等待事件指令后,STM32进入低功耗模式,就说这两个指令是最终开启低功耗模式的触发条件,配置其他的寄存器都要在这两个指令之前。
看这个图,首先一旦WFI或者WFE执行了,会按照这个流程来判断,首先看看sleep deep位是1还是0,如果sleep deep=0 ,就是浅睡眠,对应的就是睡眠模式;如果sleep deep=1,表示要进入深度睡眠模式,对应的是停机或者待机模式。
在普通的睡眠模式,通过SLEEPONEXIT位来决定。这一位等于0时,无论程序在哪里调用WFI或WFE都会立刻进入睡眠,这位等于1时,执行WFI或WFE之后,它会等待中断退出,等所有中断处理完成之后,再进入睡眠。这个可能考虑到中断还有一些紧急的任务,最好不要被睡眠打断了,所以先等等也无妨。
当然这两个细分模式我们一般可以不用管,只要我们不在中断函数里调用WFI或WFE,那其实它们的效果是一样的,我们WFI、WFE可以放在主程序里,如果主程序执行到了,自然也代表中断处理完成了,如果你想在中断函数里调用WFI、WFE,并且想中断结束后再睡眠,才需要考虑下面这个等待中断退出模式。
然后继续进入深度睡眠模式,它会继续判断PDDS这一位。如果PDDS=0,就进入的是停机模式,如果PDDS=1,就进入的是待机模式。
在停机模式下,它会继续判断LPDS位,如果LPDS=0,就是停机模式且电压调节器开启,如果LPDS=1,就是停机模式且电压调节器低功耗。电压调节器低功耗的特性就是更省电,但是唤醒延迟更高,那这些就是模式选择的一个判断流程。
三种模式的特性总结
睡眠模式
- 执行完WFI/WFE指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行。一般可以在主循环的最后,执行一下WFI/WFE,主循环执行一遍,就睡眠,然后唤醒之后,主循环又会执行一遍,再睡眠。每唤醒一次,主循环执行一遍。
- SLEEPONEXIT位决定STM32执行完WFI或WFE后,是立刻进入睡眠,还是等STM32从最低优先级的中断处理程序中退出时进入睡眠
- 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态,比如你在程序里进行点灯,灯点亮了在进入睡眠,灯仍然是亮的,GPIO引脚的高低电平在睡眠时是维持原样的
- WFI指令进入睡眠模式,可被任意一个NVIC响应的中断唤醒
- WFE指令进入睡眠模式,可被唤醒事件唤醒
事件唤醒看参考手册
停止模式
- 执行完WFI/WFE指令后,STM32进入停止模式,程序暂停运行。因为睡眠模式和停止模式,存储器和寄存器的内容都可以维持,唤醒后程序从暂停的地方继续运行。
- 1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,但是没有断电,SRAM和寄存器内容被保留下来
- 在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态,这个和睡眠模式一样,GPIO在进入睡眠或者停止模式时暂停,并且高低电平维持暂停前一刻的状态。
- 当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟,这一条涉及编程的注意事项
- 当电压调节器处于低功耗模式下,系统从停止模式退出时,会有一段额外的启动延时。这就是刚才电压调节器开启和低功耗模式的区别了,电压调节器低功耗,更省电,但是从停止模式提出时,会有一段额外的启动延时。
- WFI指令进入停止模式,可被任意一个EXTI中断唤醒
- WFE指令进入停止模式,可被任意一个EXTI事件唤醒
当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟:
我们的程序默认在SystemInit函数里的配置,是使用的HSE外部高速时钟,通过PLL倍频得到72MHz主频,但是进入停止模式后,PLL和HSE都停止了,而现在退出停止模式时,它并不会再自动开启PLL和HSE,默认用HSI的8MHz直接作为主频。所以如果你忽略了这个问题,那么就会出现一个现象,你程序刚上电是72MHz的主频,但是进入停止模式,在唤醒之后就变成8MHz的主频了,这是一个问题。
解决办法:所以一般在停止模式唤醒后,第一时间就是重新启动HSE,配置主频为72MHz,这个操作也不麻烦,配置的函数他都帮我们写好了,我们只需要再调用下SystemInit就行。
待机模式
- 执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行,这个和上面两个模式就有些区别了,因为待机模式把内部大部分电路的电源都断了,数据都丢失了,唤醒之后,程序也无法继续了
- 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
- 在待机模式下,所有的I/O引脚变为高阻态(实际GPIO配置里,没有高阻态配置,其实就是浮空输入配置)。比如提前点了一个灯,进入待机模式后,无论这个灯是高电平还是低电平点亮,它都会熄灭,GPIO对外不输出高低电平,也不流过电流。
- 待机模式4个唤醒条件,WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
数据手册和参考手册:
数据手册
首先,看一下数据手册。刚才我们说了,睡眠模式是一般省电,停止模式是非常省电,待机模式是极为省电,除了这么多省电,到底有多省呢?我们看一下数据手册,用数据说话。
在手册这里,5.3.5小节供电电流特征有介绍,耗电量在不同工作条件下都是不同的,做产品的话,具体值还是以实测为准,现在我们就大概看一下官方给的一些测试表。看一下各个模式的耗电范围,首先这个表是从内部闪存flash运行,正常运行模式下的供电电流。条件是外部时钟使能所有外设和关闭所有外设,然后各个主频下的电流信号在右边。
可以看到,耗电电流区间是几到几十,单位是毫安。最高是50毫安左右,最低是七毫安左右。上下对比,使能所有外设,比关闭所有外设更耗电。所以为了省电,不需要的外设,我们可以把它的时钟关掉。另外,对于频率来说,降低主频对于省电也是很划算的,降低主频后耗电,电流下降也很明显。如果你需要设备连续运行,并且对于主频和性能没那么高要求的话,降低主频也是一个不错的选择。就是这个表的数据。
然后下面这个是从RAM运行的耗电数据吧,整体上来看比上面这个表低一些,但差别不大。
然后这个图里可以看出主频温度和耗电的关系,主频和耗电大概上是一个正比的关系。72M,耗电40毫安左右。频率降低一半,36M,耗电大概也降低一半,20毫安左右。36M再降低一半是18M,可以看一下这个相近的16M,差不多电流也降低了一半。16M再降低一半,8M电流继续降低一半,所以主频越低,耗电越低,主频每降低一半,耗电大概就也降低一半。单子是大概一半的关系啊,并不是严格对应的。
然后再看温度,温度升高,耗电量也是升高的。当然,影响并不是很大。这是这个图,可以看出来特征。
睡眠模式下供应电流
这是睡眠模式下的供应电流,它的耗电也是毫安级别的,从几毫安到几十毫安不等。但是整体上来看,比正常运行的耗电是低一些的,比如正常运行的情况下最大,刚才看的是50毫安左右。在睡眠模式下,这个最大电流降低到了30毫安左右,会省一些电,但是耗电也是毫安级别的。只能算是一般省电。
停机/待机模式供应电流
这是停机模式下的供应电流和待机模式下的供应电流。左边是测试条件,看右边的数据,首先这个电流的单位是uA微安级别。在停机模式下,3.3v供电时耗电,电流典型值是14到24微安,这个就非常省电了。在待机模式下,电流会进一步降低,典型值是2~3uA左右,可以算是极为省电了。
举个例子呢,为了方便计算,我近似取个值。假设正常运行电流是30毫安,停机模式电流是30微安,待机模式是三微安。那么,对于一个300毫安时的电池来说,正常运行能用十个小时,停机模式能用一万个小时,待机模式能用十万个小时,这个对比差别就比较大了吧。所以如果你的产品使用了电池供电,低功耗模式,还是要考虑用一下的。
最后,我们看到备份区域的供电,电流也非常低。rtc开启的情况下,也只需要1.4uA,所以备用电池接上一个基本不用担心没电了。有关STM32各个状态下的电流消耗。
参考手册:
最后,我们还是照例看一下参考手册。我们本节讲的内容位于参考手册第四节电源控制pwr。看一下,首先是这个整体的电源框图,内部电源有哪些区域?通过哪些引脚供电?上面介绍过了。
下面是电池备份区域。这里有一些警告和建议哈,这个我们上一节讲电路的时候说过。当然,这里还有一个注写的,是因为模拟开关只能通过少量的电流3mA。在输出模式下使用PC 13至PC 15的I/O口功能是有限制的,速度必须限制在2MHz以下,最大负载为30pF,而且这些I/O口绝对不能当做电流源, 如驱动LED。
它这里特意强调了PC 13到PC 15端口绝对不能驱动LED,但有意思的是,我们这个最小系统板,它上面有两个LED。一个是电源指示灯,另一个是接待GPL口的测试灯,并且这个接待GPL的测试灯,它就正好位于PC 13。所以说我们这个最小系统板的电路设计,违背了这条注意事项,不过好在这个贴片的小电流并不是很大。目前也没有什么问题,但是它要是真的导致gpu损坏,你也不能怪他没提醒你,所以我们在做产品之前还是要仔细阅读手册,知道的越多就越不容易犯错。
然后继续下面是电压调节器,有三种模式正常运转,低功耗和停止供电。
再看下面,这是上电复位和掉电复位的介绍。这个图和功能我们也讲过,下面是可编程电压监测器,大家可以看一下。之后就是低功耗模式的介绍了。低功耗一览表总共有睡眠停机待机三种模式,下面是一些省电建议,首先就是降低系统时钟,降频后功耗会明显降低,刚才通过那个数据手册也看过。然后是外部时钟的控制,不需要的外设,我们可以把时钟给关掉,以减少功耗。另外在睡眠之前也可以关闭所有外设的时钟来省电。不过,这样睡眠模式下的外设就干不了活了。
然后下面是睡眠模式执行或进入睡眠模式。可以选择立刻睡眠或者等中断结束后再睡眠。退出睡眠呢,在这里也有介绍和我们PPT讲的一样。接着停止模式如何进入?如何退出?以及一些注意事项,大家可以自己再看看。然后是待机模式再介绍,也可以再看看。
最后这里有个低功耗模式下的自动唤醒,这里的意思就是使用RTC可以在停止模式或待机模式下定期唤醒芯片。也就是我们第四个示例代码演示的现象。
然后下面就是一些寄存器描述了,控制寄存器里面是一些控制位,比如DBP位,取消后备区域写保护,PLS[2:0]选择pvd电平,PDDS选择是停止模式还是待机模式,LPDS选择停止模式下电压调节器是开启还是低功耗,这些位就在这里定义的。
然后控制状态寄存器,里面是一些控制位和标志位,比如使能WKUP引脚和一些标志位。
最后就是寄存器总表了,寄存器不多,总共就两个。
修改主频&睡眠模式&停止模式&待机模式
硬件接线图
修改主频接线图
睡眠模式+串口发送+接收接线图
CH340 USB转串口模块,GND和STM32共地,RXD接PA9,TXD接PA10。
停止模式+对射式红外传感器计次接线图
对射式红外传感器模块的VCC和GND接上供电,DO输出接STM32的PB14引脚。
待机模式+实时时钟接线图
PA0引出一根线,目的是可以手动把PA0接到3.3v或断开,因为PA0有个WKUP,可以唤醒待机模式,WKUP引脚上升沿有效,直接接一个线短接到3.3v,来手动产生一个上升沿,测试WKUP的效果。
研究system_stm32f10x.h、system_stm32f10x.c文件:
system_stm32f10x.h、system_stm32f10x.c文件是用来配置系统时钟的,也就是配置RCC时钟树。
RCC时钟树
在这里这个图就是RCC时钟树的全部电路。
左边是四个时钟源,HSI、HSE、LSE、LSI用于提供时钟源。
右边就是各个外设,就是使用时钟的地方,我们用的最多的就是AHB时钟、APB1时钟、APP2时钟。
另外还有一些时钟,它们的来源不是AHB和APB,比如I2S的时钟直接来源于SYSCLK,USB的时钟直接来源于PLL。
我们主要关心的就是这个外部的8MHz晶振,它如何进行选择,如何倍频才能得到这个72MHz的SYSCLK系统主频,然后系统主频如何去分配,才能得到AHB、APB1和APB2的时钟频率。
/**
******************************************************************************
* @file system_stm32f10x.c
* @author MCD Application Team
* @version V3.5.0
* @date 11-March-2011
* @brief CMSIS Cortex-M3 Device Peripheral Access Layer System Source File.
*
* 1. This file provides two functions and one global variable to be called from
* user application:
* - SystemInit(): Setups the system clock (System clock source, PLL Multiplier
* factors, AHB/APBx prescalers and Flash settings).
* This function is called at startup just after reset and
* before branch to main program. This call is made inside
* the "startup_stm32f10x_xx.s" file.
*
* - SystemCoreClock variable: 系统主频的值Contains the core clock (HCLK), it can be used
* by the user application to setup the SysTick
* timer or configure other parameters.
*
* - SystemCoreClockUpdate(): Updates the variable SystemCoreClock and must
* be called whenever the core clock is changed
* during program execution.
*
* 2. After each device reset the HSI (8 MHz) is used as system clock source.
* Then SystemInit() function is called, in "startup_stm32f10x_xx.s" file, to
* configure the system clock before to branch to main program.
*
* 3. If the system clock source selected by user fails to startup, the SystemInit()
* function will do nothing and HSI still used as system clock source. User can
* add some code to deal with this issue inside the SetSysClock() function.
*
* 4. The default value of HSE crystal is set to 8 MHz (or 25 MHz, depedning on
* the product used), refer to "HSE_VALUE" define in "stm32f10x.h" file.
* When HSE is used as system clock source, directly or through PLL, and you
* are using different crystal you have to adapt the HSE value to your own
* configuration.
*/
工程文件目录中带钥匙符号代表文件只读
解除只读的方法:打开工程文件夹或在system_stm32f10x.c右键-->属性,去除勾选只读,可以去除单个文件或者单个文件夹的只读属性。比如想整个文件或整个工程文件夹都取消只读,在外面对整个文件夹操作,点击属性,把只读去掉即可。
修改主频,把宏解除注释(/*),默认主频是72MHz,这里会根据不同的宏定义,调用不同的系统时钟函数
总结:
首先是是Systemlnit函数,进来启动HSI,之后就是各种缺省配置,最后调用SetSysClock;SetSysClock是一个分配函数,根据前面解除不同的宏定义,选择执行不同的配置函数:如SetSysClockTo72、To56、To48等。最后在SetSysClockTo72里配置实现系统时钟配置:选择HSE作为锁相环输入,之后锁相环进行9倍频,再选择锁相环输出,作为主频,这样主频就是72MHZ。
修改主频
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"int main(void)
{
OLED_Init(); //OLED初始化
OLED_ShowString(1, 1, "SYSCLK:"); //显示静态字符串
OLED_ShowNum(1, 8, SystemCoreClock, 8); //SystemCoreClock的值表示当前的系统主频频率
while (1)
{
OLED_ShowString(2, 1, "Running"); //闪烁Running,指示当前主循环运行的快慢
Delay_ms(500);
OLED_ShowString(2, 1, " ");
Delay_ms(500);
}
}
修改主频就是修改stm32f10x.c文件中的宏定义,比如修改成36MHz
程序现象:
第一行主频频率是36M。第二行Running闪烁周期是2s。
睡眠模式
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"uint8_t RxData; //定义用于接收串口数据的变量
int main(void)
{
OLED_Init(); //OLED初始化
OLED_ShowString(1, 1, "RxData:"); //显示静态字符串
Serial_Init(); //串口初始化
while (1)
{
if (Serial_GetRxFlag() == 1) //检查串口接收数据的标志位
{
RxData = Serial_GetRxData(); //获取串口接收的数据
Serial_SendByte(RxData); //串口将收到的数据回传回去,用于测试
OLED_ShowHexNum(1, 8, RxData, 2); //显示串口接收的数据
}
OLED_ShowString(2, 1, "Running"); //OLED闪烁Running,指示当前主循环正在运行
Delay_ms(100);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
//执行WFI指令,CPU睡眠,并等待中断唤醒
//执行WFI这时CPU会立刻睡眠,程序就停在了WFI指令这里,但是各个 外设比如USRT还是工作状态。等到我们用串口助手发送数据时,USRT外设收到数据产生中断,唤醒CPU之后,程序在暂停的地方继续运行。
__WFI();
}
}
程序现象
串口助手发送数据,每发送一个Running闪烁一次,接收区回传一次数据,说明程序在收到数据之后,可以唤醒工作一次。在不影响功能的前提下,CPU能在空闲时睡眠,节约用电。
停止模式
停止模式只能通过外部中断触发(唤醒),所以和停止模式相关的代码肯定得用外部中断。
在停止模式下1.8V区域的时钟关闭,CPU和外设都没有时钟了,但是外部中断的工作是不需要时钟的,这一点从代码里也可以看出来,初始化的时候,根本就没有开启EXTI时钟的参数,这也是EXTI能在时钟关闭的情况下工作的原因。
刚才讲的睡眠模式其实都只是内核的操作,睡眠模式涉及的几个寄存器也都是在内核里,跟PWR外设关系不大,所以刚才我们都没用到PWR的库函数,不过现在停止模式涉及到内核之外的电路操作,这就需要用到PWR外设了,看一下库函数。
PWR.h里常用函数
void PWR_DeInit(void);//恢复缺省配置
void PWR_BackupAccessCmd(FunctionalState NewState); // 使能后备区域的访问
// 配置PVD使能电压
void PWR_PVDCmd(FunctionalState NewState); //使能PVD功能
void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel);//配置PVD的阈值电压
// 使能WKUP引脚唤醒功能,在待机模式下使用
void PWR_WakeUpPinCmd(FunctionalState NewState);
// 进入停止模式
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry);// 进入待机模式
void PWR_EnterSTANDBYMode(void);//标志位函数
FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG); //获取标志位
void PWR_ClearFlag(uint32_t PWR_FLAG); //清除标志位
main .c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
CountSensor_Init(); //对射式红外传感器初始化
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR的时钟,停止模式和待机模式一定要记得开启
/*显示静态字符串*/
OLED_ShowString(1, 1, "Count:");
while (1)
{
OLED_ShowNum(1, 7, CountSensor_Get(), 5); //OLED不断刷新显示CountSensor_Get的返回值
OLED_ShowString(2, 1, "Running"); //OLED闪烁Running,指示当前主循环正在运行
Delay_ms(100);
OLED_ShowString(2, 1, " "); //把Running清除
Delay_ms(100);
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI); //STM32进入停止模式,并等待外部中断唤醒
SystemInit(); //当一个中断或唤醒事件导致退出停止模式时候,HSI被选为系统时钟。所以唤醒后,要重新配置时钟,重新启动HSE,配置72MHZ
}
}
stm32f10x_pwr.c
/**
* @brief Enters STOP mode.
* @param PWR_Regulator: specifies the regulator state in STOP mode.
* This parameter can be one of the following values:
* @arg PWR_Regulator_ON: STOP mode with regulator ON
* @arg PWR_Regulator_LowPower: STOP mode with regulator in low power mode
* @param PWR_STOPEntry: specifies if STOP mode in entered with WFI or WFE instruction.
* This parameter can be one of the following values:
* @arg PWR_STOPEntry_WFI: enter STOP mode with WFI instruction
* @arg PWR_STOPEntry_WFE: enter STOP mode with WFE instruction
* @retval None
*/
void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry)
{
uint32_t tmpreg = 0;
/* Check the parameters */
assert_param(IS_PWR_REGULATOR(PWR_Regulator));
assert_param(IS_PWR_STOP_ENTRY(PWR_STOPEntry));
/* Select the regulator state in STOP mode ---------------------------------*/
tmpreg = PWR->CR;
/* Clear PDDS and LPDS bits */
tmpreg &= CR_DS_MASK;
/* Set LPDS bit according to PWR_Regulator value */
tmpreg |= PWR_Regulator;
/* Store the new value */
PWR->CR = tmpreg;
/* Set SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR |= SCB_SCR_SLEEPDEEP;
/* Select STOP mode entry --------------------------------------------------*/
if(PWR_STOPEntry == PWR_STOPEntry_WFI)
{
/* Request Wait For Interrupt */
__WFI();
}
else
{
/* Request Wait For Event */
__WFE();
}
/* Reset SLEEPDEEP bit of Cortex System Control Register */
SCB->SCR &= (uint32_t)~((uint32_t)SCB_SCR_SLEEPDEEP);
}
等外部中断发生时,芯片唤醒。这时,芯片先进入到外部中断函数里,执行一遍中断函数,退出后程序,继续到PWR_EnterSTOPMode函数的__WFI()或__WFE()阻塞处运行,然后函数退出。最后执行配置时钟之后,外绕循环回到开头执行一遍,这个内容最后就是再执行这个函数,进入停止模式了,这是程序执行流程。
程序现象:
在空闲时,Running没有闪烁。每遮挡一次,Running闪烁一下。而复位后第一次的Running闪烁很快,是正常的时间,而遮挡之后,Running闪烁变慢。
退出停止模式时,HSI被选为系统时钟,也就是在我们首次复位后,SystemInit函数里配置的是HSE*9倍频的72M主频。 所以复位后第一次Running闪烁很快,而之后进入停止模式,再退出时默认时钟就变成HSI了,HSI是8M,所以唤醒之后的程序运行就会明显变慢。
待机模式
主要任务:
第一,设置RTC时钟;
第二,进入待机模式;
第三,使用闹钟信号,唤醒待机模式。
代码:
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyRTC.h"int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
MyRTC_Init(); //RTC初始化
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //开启PWR的时钟,停止模式和待机模式一定要记得开启 虽然MyRTC_Init里开启了,多次开启无所谓,防止其他没调用MyRTC_Init的场景 但时钟没开启外设就不会工作。
/*显示静态字符串*/
OLED_ShowString(1, 1, "CNT :"); //显示秒计数器
OLED_ShowString(2, 1, "ALR :"); //显示闹钟值
OLED_ShowString(3, 1, "ALRF:"); //闹钟标志位
/*使能WKUP引脚*/
PWR_WakeUpPinCmd(ENABLE); //使能位于PA0的WKUP引脚,这个函数作用是将wkup引脚配置成输入下拉模式,当WKUP引脚出现上升沿时候,比如本实验手动将wkup引脚接到高电平时候,会唤醒待机模式
/*设定闹钟*/
uint32_t Alarm = RTC_GetCounter() + 10; //闹钟为唤醒后当前时间的后10s
RTC_SetAlarm(Alarm); //写入闹钟值到RTC的ALR寄存器 ,这个寄存器只写不可读,所以使用变量Alarm显示到OLED上
OLED_ShowNum(2, 6, Alarm, 10); //显示闹钟值
while (1)
{
OLED_ShowNum(1, 6, RTC_GetCounter(), 10); //显示32位的秒计数器
OLED_ShowNum(3, 6, RTC_GetFlagStatus(RTC_FLAG_ALR), 1); //显示闹钟标志位,显示长度为1
OLED_ShowString(4, 1, "Running"); //OLED闪烁Running,指示当前主循环正在运行
Delay_ms(100);
OLED_ShowString(4, 1, " "); //Running清除
Delay_ms(100);
OLED_ShowString(4, 9, "STANDBY"); //OLED闪烁STANDBY,指示即将进入待机模式
Delay_ms(1000);
OLED_ShowString(4, 9, " ");
Delay_ms(100);
OLED_Clear(); //OLED清屏,模拟关闭外部所有的耗电设备,以达到极度省电
PWR_EnterSTANDBYMode(); //STM32进入停止模式,并等待指定的唤醒事件(WKUP上升沿或RTC闹钟)
/*待机模式唤醒后,程序会重头开始运行, 待机模式之后的代码执行不到,下次继续从头开始 ,在程序刚开始的时候自动调用SystemInit初始化时钟,所以待机模式我们就不用像停止模式那样,自己调用SystemInit了。并且这个while循环,实际上也只有执行一遍的机会,把这个while循环去掉也是可以的*/
}
}
程序现象:
复位后,Running闪烁一次,进入待机,当然CNT也不会进行刷新了。然后需等一下,闹钟触发,待机模式唤醒 ,CNT和闹钟值刷新一下,Running闪烁一次。并且,每次唤醒后,闹钟值都会重新设定,通过这一现象,确定待机模式唤醒后,程序是从头开始执行。stm32进入待机模式之前,要把外部接的模块全部关掉。
最开始显示信息,进入待机,OLED清屏,等待一会,闹钟触发之后,唤醒一次。
还有wkup引脚的唤醒功能:wkup引脚默认下拉,引脚悬空,就是低电平,把线插到高电平,待机模式唤醒。
小实验:验证待机模式省电
手册说待机模式的电流只有3微安左右,为此进行了测试,
这里我单独找了个stm32板子,把这个电源供电线正极给剪断,串联一个万用表测电流。
当然最开始我直接测试,待机模式的电流高达一点多毫安,远超手册里说的3微安,但首先很明显这个电源指示灯肯定是耗电的,所以我先把这个电源指示灯去掉了,再测试电流仍然有几百微安。
那说明还有别的东西耗电,之后把板子背面的这个LDO稳压器去掉了,这个LDO虽然我们并没有用到哈,但接上它就会有电流损耗,最后去掉电源指示灯和LDO之后,待机模式的电流就下降到3微安了。
首先,万用表接到200mA电流档,最开始看到正常工作的电流
之后等一会,进入待机模式,这个供电电流瞬间变为0,万用表换到200微安档,可以看到,目前待机的供电电流是3.3微安。
原文链接:
https://blog.youkuaiyun.com/m0_62005595/article/details/139840902
https://blog.youkuaiyun.com/m0_62005595/article/details/139799129