打印
[STM32L0]

基于STM32的低功耗程序设计

[复制链接]
812|19
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
近久在开发一个基于STM32L151为主控开发的物联网设备,因为终端设备需要用到电池供电,所以选用的这款低功耗MCU并使用它的低功耗模式用来配合电池供电,以下文章用来记录开发过程中所遇到问题、总结等,希望能帮助大家,让大家加快开发效率,如果有讲解错误,望大家指出,我们共同进步,话不多说我们直接进入正题。

STM32的低功耗模式有以下几种最为常用:

1、睡眠模式:在睡眠模式,只有CPU停止,所有外设处于工作状态并可在发生中断/事件时唤醒CPU。

2、停机模式:在保持SRAM和寄存器内容不丢失的情况下,停机模式可以达到最低的电能消耗。在停机模式下,停止所有内部1.8V部分的供电,PLL、HSI的RC振荡器和HSE晶体振荡器被关闭,调压器可以被置于普通模式或低功耗模式。
可以通过任一配置成EXTI的信号把微控制器从停机模式中唤醒,EXTI信号可以是16个外部I/O
口之一、PVD的输出、RTC闹钟或USB的唤醒信号。

3、待机模式:在待机模式下可以达到最低的电能消耗。内部的电压调压器被关闭,因此所有内部1.8V部分的供电被切断;PLL、HSI的RC振荡器和HSE晶体振荡器也被关闭;进入待机模式后,SRAM和寄存器的内容将消失,但后备寄存器的内容仍然保留,待机电路仍工作。
从待机模式退出的条件是:NRST上的外部复位信号、IWDG复位、WKUP引脚上的一个上升边
沿或RTC的闹钟到时。

注: 在进入停机或待机模式时, RTC 、 IWDG 和对应的时钟不会被停止。

使用特权

评论回复
沙发
雨果喝水|  楼主 | 2023-11-27 23:26 | 只看该作者
为此我选择停机模式,并使用RTC唤醒MCU,这比较符合我所设计产品使用场景。

进入STM32CubeMX的RTC页面

选择激活时钟源,在时钟树把RTC的时钟源输入配置为LSE(32.768KHZ)

激活日历功能

选择内部闹钟A

打开内部闹钟唤醒

其他保持默认

使用特权

评论回复
板凳
雨果喝水|  楼主 | 2023-11-27 23:28 | 只看该作者


使用特权

评论回复
地板
雨果喝水|  楼主 | 2023-11-27 23:29 | 只看该作者


首先选择RTC为24小时格式

RTC的计时时基由Asynchronous Predivider value和Synchronous Predivider value分别加1分频而得32768/(127+1)/(256+1) =1HZ,即RTC每计数一次都刚好为1秒。

然后就是设置时间日期,Alarm的唤醒时间,注意的是Alarm Mask date week day 使能表示着当每天的时间和闹钟A的时间相同时就会触发闹钟中断,不然只会响一次,如果你想设置到每天的某时某分某秒响应。你可以只使能Alarm Mask date week day,那样每到你设置闹钟的时分秒时,都会触发中断

使用特权

评论回复
5
雨果喝水|  楼主 | 2023-11-27 23:30 | 只看该作者
void RTC_Set_AlarmAtime(uint8_t hour,uint8_t min,uint8_t sec)
{
        /** Enable the Alarm A
  */
        RTC_AlarmTypeDef sAlarm = {0};
       
  sAlarm.AlarmTime.Hours = hour;
  sAlarm.AlarmTime.Minutes = min;
  sAlarm.AlarmTime.Seconds = sec;
  sAlarm.AlarmTime.SubSeconds = 0;
  sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
  sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
  sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;
  sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
  sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
  sAlarm.AlarmDateWeekDay = 1;
  sAlarm.Alarm = RTC_ALARM_A;
  if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN) != HAL_OK) //重新设置定时中断
  {
    Error_Handler();
  }
}

使用特权

评论回复
6
雨果喝水|  楼主 | 2023-11-27 23:30 | 只看该作者
void RTC_SetAlarmFrec(uint8_t hour, uint8_t min, uint8_t sec)
{
        uint8_t h=0,m=0,s=0;
        RTC_TimeTypeDef gTime = {0};
        RTC_DateTypeDef gDate = {0};
        HAL_RTC_GetTime(&hrtc, &gTime, RTC_FORMAT_BIN);
        HAL_RTC_GetDate(&hrtc, &gDate, RTC_FORMAT_BIN);
        if ( (s = (sec + gTime.Seconds) ) > 59 )
        {
                s = s % 60 ;
                ++ gTime.Minutes;
        }
        if ( (m = (min + gTime.Minutes) ) > 59 )
        {
                m = m % 60 ;
                ++ gTime.Hours;
        }
        if ( (h = (hour + gTime.Hours) ) > 23 )
        {
                h = h % 24 ;
        }
        RTC_Set_AlarmAtime( h, m, s);
}

使用特权

评论回复
7
雨果喝水|  楼主 | 2023-11-27 23:30 | 只看该作者
例:RTC_SetAlarmFres(0,0,5)这样设计的话,原理是把Alarm的时间设置为当前时间加上我们设置的时间5秒,那样每隔五秒就会唤醒一次32,如果你只想在每天的某个点唤醒主控的话,可以不用设置这个,直接在HAL库中设计好就行,像上面我设置的就是每天17:00唤醒,。

使用特权

评论回复
8
雨果喝水|  楼主 | 2023-11-27 23:31 | 只看该作者
接下来时进入停止模式的函数:没什么好说的,直接调用内部函数。
    __HAL_RCC_PWR_CLK_ENABLE();                             // 使能PWR时钟
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                      // 清除唤醒标记
    HAL_PWR_EnterSTOPMode(POWER_MODE, PWR_STOPENTRY_WFI);   // 进入STOP模式,使用中断唤醒

使用特权

评论回复
9
雨果喝水|  楼主 | 2023-11-27 23:31 | 只看该作者
接下来时进入停止模式的函数:没什么好说的,直接调用内部函数。
    __HAL_RCC_PWR_CLK_ENABLE();                             // 使能PWR时钟
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                      // 清除唤醒标记
    HAL_PWR_EnterSTOPMode(POWER_MODE, PWR_STOPENTRY_WFI);   // 进入STOP模式,使用中断唤醒

使用特权

评论回复
10
雨果喝水|  楼主 | 2023-11-27 23:32 | 只看该作者
把上这三段代码放在主函数内:
  RTC_SetAlarmFrec(0, 0, 10);

  while (1)
  {                       

    /*在进入停止模式前,放置取消初始化外设的函数*/
    __HAL_RCC_PWR_CLK_ENABLE();                           // 使能PWR时钟
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);                    // 清除唤醒标记
    HAL_PWR_EnterSTOPMode(POWER_MODE, PWR_STOPENTRY_WFI); // 进入STOP模式
        /*重新初始化外设*/
                       
  }

使用特权

评论回复
11
雨果喝水|  楼主 | 2023-11-27 23:32 | 只看该作者
RTC唤醒闹钟中断回调函数:
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{
        // 刚从STOP模式唤醒时钟默认使用内部高速8M时钟,所以需要重新配置时钟
    SystemClock_Config();
        RTC_SetAlarmFrec(0,0,10);
}

使用特权

评论回复
12
雨果喝水|  楼主 | 2023-11-27 23:32 | 只看该作者
ok,代码大致就是这样,但是有几点值得注意,下面都是我开发过程中遇到的问题,希望能给大家避个坑,不要犯相同的错误:

1、建议在while(1)中前加一个延时2秒的函数,因为一但进入停止模式就不能下载程序了,会显示内部指令错误,毕竟停止模式把内部1.8V供电的部分区域给关闭了,这就导致程序在停止模式期间无法下载进去,当然你的板子上复位按键也可以按着复位键把程序下进去(大多因程序问题而不法下载程序,都可以靠这个解决),加2秒的意义是如果你的板子没有复位键(我的板子就是,上面只有32与晶振),那么重新上电在那两秒的时间内,没进入停止模式前把程序下进去(抢一手时间差,嘿嘿)。

使用特权

评论回复
13
雨果喝水|  楼主 | 2023-11-27 23:33 | 只看该作者
2、空闲的引脚全部初始化为模拟输入模式,这样可以省非常多的电,取消初始化外设后的引脚会回到默认GPIO模式,这些也可以在你取消初始化后,重新配置为模拟输入模式,对于使用了很多外设重映射到GPIO的项目也很有用,实测我的一开始没有把空闲引脚配置为模拟输入的时候,电流大概在300uA左右,正常运行在9MA左右,虽然省了不少,但是还是很大,配置好后达到了70uA,在把取消外设后的GPIO也改为模拟输入后也降了不少,还有值得注意的是,我的单片机进入停止模式后电流先是从9mA到70uA,然后慢慢下降到20uA,中间需要经过大概30多秒电表上测量的值才慢慢达到最低,我的引脚还有几个为未配置成模拟输入模式,所以只到了20uA左右,配置好后,达到10uA左右应该不是问题,好像最后在进入停机模式前可以关闭不使用的总线时钟,到停机模式结束时,在开启时钟,也可以省电。

使用特权

评论回复
14
雨果喝水|  楼主 | 2023-11-27 23:33 | 只看该作者
3、一开始一定要准备一块最小系统版,上面没有其他器件,只有晶振和32,然后检测最小系统的耗电情况,在选择其他的元器件,一定要单片机可控,例如用一个IO口,控制其他器件的供电,不然你以为你的电流可以降到几十uA。但是一测出来,好家伙怎么还有几毫安的电流,和预想的不一样了,那是就要考虑你的器件选择问题了,说不定一块DC-DC芯片就有1MA的消耗呢(我的就是这样)。

使用特权

评论回复
15
雨果喝水|  楼主 | 2023-11-27 23:33 | 只看该作者
好了,以上就是我的一些小小总结,如果帮助到你,希望可以获取你的一键三连,如果有讲解错误还望大家在评论区中指出,第一次写博客望大家支持,嘻嘻。

使用特权

评论回复
16
chenqianqian| | 2024-4-10 08:11 | 只看该作者
低功耗设计需要软硬件协同得

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

71

主题

1071

帖子

0

粉丝