0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

PM2.0组件爬坑记录

冬至子 来源:zx595 作者:zx595 2023-07-22 15:10 次阅读

PM2.0组件采用了空闲任务中进行低功耗管理策略,使用时要把空闲任务的堆栈扩大到2048。建议刚使用低功耗组件的先去阅读该部分源码部分,这部分代码并不长,半天时间足够分析清楚组件的运行逻辑了。

组件默认在无请求时进入低功耗深睡眠模式,被特殊事件唤醒才能继续工作。我在这里面踩得最大的坑就是从深睡眠中唤醒。

关于这部分首先要去看STM32L4的官方RM手册,了解如何进入/退出深睡眠,各个电源模式如何切换,再写裸机代码验证之后再去看PM2.0组件会容易很多。

在记录问题之前着重提醒:在刷完固件验证之前一定要重新给板子上电再看结果!!!重要的事情说三遍!!!

Q1:无法进入低功耗模式

A1:系统默认是有个电源ID默认请求不睡眠,需要释放后才可以进入通过电源管理策略进入深睡眠。可以在main.c添加如下代码即可。

rt_pm_module_delay_sleep(PM_POWER_ID, 5000); // 注意延时进入,防止再次更新程序时找不到深睡眠中的芯片
rt_pm_module_release(PM_POWER_ID, PM_SLEEP_MODE_NONE);

Q2:板子能进入深睡眠了,也测量到功耗下降了,但是为什么程序里的心跳灯不闪了?

A2:其实是因为进入什么睡眠之后芯片需要一些外部事件才能把芯片唤醒继续工作。这个事件可以是外部中断、LPTIMER中断、LPUART中断。有一些需要额外的配置才能使用。PM2.0一个很优秀的机制就是tickless,原理可以去看大佬的帖子。如果想让心跳灯继续按照程序闪烁,需要配置lptimer才能继续让芯片定时起来看下有没有什么任务处理。这里有两个地方容易出问题:一个是lptimer没有正确配置,导致芯片无法周期性唤醒执行任务;另一个是醒来之后的时钟有问题,导致无法正常恢复工作。我更改了以下代码。

main()中重新初始化lptim。

// __HAL_RCC_LPTIM1_CLK_ENABLE(); // 使能LPTIM1时钟
extern int stm32l4_hw_lptim_init(void);
stm32l4_hw_lptim_init(); // 初始化LPTIM1
board.c中检查时钟重新配置

void SystemClock_ReConfig(uint8_t mode)
{
// SystemClock_MSI_ON(); // 把这个注释掉
switch (mode)
{
case PM_RUN_MODE_HIGH_SPEED:
case PM_RUN_MODE_NORMAL_SPEED:
SystemClock_80M();
break;
case PM_RUN_MODE_MEDIUM_SPEED:
SystemClock_24M();
break;
case PM_RUN_MODE_LOW_SPEED:
SystemClock_2M();
break;
default:
break;
}
// SystemClock_MSI_OFF();
}

在main.c中(可以添加到其他参加编译的文件中)重构tickless需要的超时时间源

rt_tick_t pm_timer_next_timeout_tick(rt_uint8_t mode)
{
switch (mode)
{
case PM_SLEEP_MODE_LIGHT:
case PM_SLEEP_MODE_DEEP:
case PM_SLEEP_MODE_STANDBY:
return rt_timer_next_timeout_tick();
}
return RT_TICK_MAX;
}

至此设备就能在深睡眠时主动醒来控制LED灯了。

Q3:如何通过按键唤醒芯片起来进入正常工作模式?

A3:解决这个问题需要了解两类重要的API:电源模式的请求和释放。如果看过电源管理的源码应该就了解了芯片时如何进入睡眠的,所以在触发外部中断之后我们需要请求持有更高等级的电源模式,以防止芯片再次进入睡眠。

scons中打开用户配置PM模式。

1.jpg

在..boardpm_cfg.h中添加要管理的KEYID

enum pm_module_id
{
PM_NONE_ID = 0,
PM_POWER_ID,
PM_BOARD_ID,
PM_LCD_ID,
PM_KEY_ID,
PM_TP_ID,
PM_OTA_ID,
PM_SPI_ID,
PM_I2C_ID,
PM_ADC_ID,
PM_RTC_ID,
PM_GPIO_ID,
PM_UART_ID,
PM_SENSOR_ID,
PM_ALARM_ID,
PM_BLE_ID,
PM_KEY0_ID,
PM_KEY1_ID,
PM_KEY2_ID,
PM_MODULE_MAX_ID, /* enum must! */
};

偷个懒,直接copy大佬的代码,保存为key.c

/*

Copyright (c) 2006-2020, RT-Thread Development Team

SPDX-License-Identifier: Apache-2.0

Change Logs:
Date Author Notes
2020-09-08 zhangsz init first
*/
#include
#include
#define DBG_ENABLE
#define DBG_SECTION_NAME "key"
#define DBG_LEVEL DBG_LOG
#include
#define PIN_KEY0 GET_PIN(D, 10)
#define PIN_KEY1 GET_PIN(D, 9)
#define PIN_KEY2 GET_PIN(D, 8)
void key0_irq_callback(void *parameter)
{
static uint8_t key0_status = 0x00;
key0_status ^= 0x01;
if (key0_status == 0x00)
rt_pm_sleep_idle_release(PM_KEY0_ID);
else
rt_pm_sleep_idle_request(PM_KEY0_ID);
LOG_D("[key0_irq]n");
}
void key1_irq_callback(void *parameter)
{
static uint8_t key1_status = 0x00;
key1_status ^= 0x01;
if (key1_status == 0x00)
rt_pm_sleep_light_release(PM_KEY1_ID);
else
rt_pm_sleep_light_request(PM_KEY1_ID);
LOG_D("[key1_irq]n");
}
void key2_irq_callback(void parameter)
{
static uint8_t key2_status = 0x00;
key2_status ^= 0x01;
if (key2_status == 0x00)
rt_pm_sleep_none_release(PM_KEY2_ID);
else
rt_pm_sleep_none_request(PM_KEY2_ID);
LOG_D("[key2_irq]n");
}
int key_gpio_init(void)
{
LOG_D("key_gpio_init.n");
/
set key pin mode to input /
LOG_D("PIN_KEY0=%d,PIN_KEY1=%d,PIN_KEY2=%dn",
PIN_KEY0, PIN_KEY1, PIN_KEY2);
rt_pin_mode(PIN_KEY0, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY1, PIN_MODE_INPUT_PULLUP);
rt_pin_mode(PIN_KEY2, PIN_MODE_INPUT_PULLUP);
/
set interrupt mode and attach interrupt callback function /
rt_pin_attach_irq(PIN_KEY0, PIN_IRQ_MODE_FALLING, key0_irq_callback, NULL);
rt_pin_attach_irq(PIN_KEY1, PIN_IRQ_MODE_FALLING, key1_irq_callback, NULL);
rt_pin_attach_irq(PIN_KEY2, PIN_IRQ_MODE_FALLING, key2_irq_callback, NULL);
/
enable interrupt */
rt_pin_irq_enable(PIN_KEY0, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY1, PIN_IRQ_ENABLE);
rt_pin_irq_enable(PIN_KEY2, PIN_IRQ_ENABLE);
return 0;
}
INIT_APP_EXPORT(key_gpio_init);
修改..applicationsSConscript脚本,把key.c加入工程

src = ['main.c','key.c']

最后用scons重新构建工程即可

这里实现了通过三个不同的按键进入不同的电源模式。可以发现离开深睡眠模式之后系统的控制台又可以正常工作了!

Q4:如何通过低功耗串口唤醒芯片起来进入正常工作模式?

A4:原理与Q3的按键唤醒类似,只不过通过串口唤醒需要去做一些硬件配置。
配置方式放在..librariesHAL_Driversdrv_usart.c中

int lpuart1_wakeup_config(void)
{
#ifdef BSP_USING_LPUART1
UART_WakeUpTypeDef WakeUpSelection;
/* make sure that no LPUART transfer is on-going /
while(__HAL_UART_GET_FLAG(&(uart_obj[LPUART1_INDEX].handle), USART_ISR_BUSY) == SET);
/
make sure that LPUART is ready to receive

  • (test carried out again later in HAL_UARTEx_StopModeWakeUpSourceConfig) /
    while(__HAL_UART_GET_FLAG(&(uart_obj[LPUART1_INDEX].handle), USART_ISR_REACK) == RESET);
    /
    set the wake-up event:
  • specify wake-up on RXNE flag /
    //UART_WAKEUP_ON_STARTBIT
    WakeUpSelection.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;
    if (HAL_UARTEx_StopModeWakeUpSourceConfig(&(uart_obj[LPUART1_INDEX].handle), WakeUpSelection)!= HAL_OK)
    {
    rt_kprintf("lpuart1_wakeup_config error!!n");
    Error_Handler();
    }
    /
    Enable the LPUART Wake UP from STOP mode Interrupt /
    __HAL_UART_ENABLE_IT(&(uart_obj[LPUART1_INDEX].handle), UART_IT_WUF);
    #endif
    return 1;
    }
    int lpuart1_wakeup_disable(void)
    {
    #ifdef BSP_USING_LPUART1
    HAL_UARTEx_DisableStopMode(&(uart_obj[LPUART1_INDEX].handle));
    #endif
    return 1;
    }
    int lpuart1_wakeup_enable(void)
    {
    #ifdef BSP_USING_LPUART1
    /
    enable MCU wake-up by LPUART */
    HAL_UARTEx_EnableStopMode(&(uart_obj[LPUART1_INDEX].handle));
    #endif
    return 1;
    }
    还需要自己实现lpuart1的驱动初始,这部分可以用CubeMX生成即可。这里不再详细介绍。需要额外注意的是:要想让LPUART在深睡眠下工作来唤醒芯片,需要把时钟配置为LSE。这里注意要把波特率设置为9600,LSE的频率下波特率上限就是9600.
    1.jpg
    生成lpuart的HAL驱动之后就是对接到rtt的设备框架。注意SCons中使能宏定义
    BSP_USING_LPUART1
    可能会提示缺少相关定义,在uart_config.h中添加即可

#if defined(BSP_USING_LPUART1)
#ifndef LPUART1_CONFIG
#define LPUART1_CONFIG
{
.name = "lpuart1",
.Instance = LPUART1,
.irq_type = LPUART1_IRQn,
}
#endif /* LPUART1_CONFIG /
#endif /
BSP_USING_LPUART1 */
Kconfig添加LPUART的选项卡

config BSP_USING_LPUART1
bool "Enable LPUART1"
default n

至此驱动部分已经添加完成。

接下来是使用示例,使用PB10和PB11作为低功耗串口的收发脚。代码copy的大佬的,我做了一些修改方便验证。代码的运行逻辑是:上电5秒后深度睡眠,接收到低功耗串口的数据唤醒,并把数据加1后原样返回;当接收到数据0x0BB时认为通讯完成,再次释放持有的电源模式,进入深度睡眠。

#include
#include
#define DBG_ENABLE
#define DBG_SECTION_NAME "lpuart"
#define DBG_LEVEL DBG_LOG
#include
#define SAMPLE_UART_NAME "lpuart1"
/* Default config for serial_configure structure /
#define LPUART1_BAUD9600_CONFIG
{
BAUD_RATE_9600, /
9600 bits/s /
DATA_BITS_8, /
8 databits /
STOP_BITS_1, /
1 stopbit /
PARITY_NONE, /
No parity /
BIT_ORDER_LSB, /
LSB first sent /
NRZ_NORMAL, /
Normal mode /
RT_SERIAL_RB_BUFSZ, /
Buffer size /
0
}
extern int lpuart1_wakeup_config(void);
/
用于接收消息的信号/
static struct rt_semaphore rx_sem;
static rt_device_t lp_serial;
static void lpuart_set_config(void)
{
struct serial_configure config = LPUART1_BAUD9600_CONFIG;
rt_device_control(lp_serial, RT_DEVICE_CTRL_CONFIG, &config);
}
extern int lpuart1_wakeup_disable(void);
extern int lpuart1_wakeup_enable(void);
/
接收数据回调函数 /
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
lpuart1_wakeup_disable();
rt_pm_module_request(PM_BOARD_ID, PM_SLEEP_MODE_NONE);
/
串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
rt_sem_release(&rx_sem);
return RT_EOK;
}
static void serial_thread_entry(void parameter)
{
char ch;
lpuart1_wakeup_enable();
while (1)
{
/
从串口读取一个字节的数据,没有读取到则等待接收信号量 /
while (rt_device_read(lp_serial, -1, &ch, 1) != 1)
{
/
阻塞等待接收信号量,等到信号量后再次读取数据 /
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
/
读取到的数据通过串口错位输出 /
ch = ch + 1;
rt_device_write(lp_serial, 0, &ch, 1);
rt_kprintf("wake from lpuart!n");
if (ch == 0xBC)
{
rt_pm_module_release_all(PM_BOARD_ID, PM_SLEEP_MODE_NONE);
lpuart1_wakeup_enable();
}
}
}
int uart_sample(void)
{
rt_err_t ret = RT_EOK;
char str[] = "hello lpuart!rn";
/
查找系统中的串口设备 /
lp_serial = rt_device_find(SAMPLE_UART_NAME);
if (!lp_serial)
{
LOG_D("find %s failed!n", SAMPLE_UART_NAME);
return RT_ERROR;
}
/
初始化信号量 /
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/
以中断接收及轮询发送模式打开串口设备 /
rt_device_close(lp_serial);
lpuart_set_config();
LOG_D("lpuart_set_config ok!n");
rt_device_open(lp_serial, RT_DEVICE_FLAG_INT_RX);
/
设置接收回调函数 /
rt_device_set_rx_indicate(lp_serial, uart_input);
/
发送字符串 /
rt_device_write(lp_serial, 0, str, (sizeof(str) - 1));
/
创建 serial 线程 /
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry, RT_NULL, 1024, 25, 10);
/
创建成功则启动线程 */
if (thread != RT_NULL)
{
rt_thread_startup(thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
INIT_APP_EXPORT(uart_sample);
INIT_APP_EXPORT(lpuart1_wakeup_config);

这样写只是方便测试低功耗串口唤醒设备,实际使用中还是把接收到的数据校验后封装成事件处理,事件处理完之后再释放电源模式比较合理。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 电源管理
    +关注

    关注

    112

    文章

    6015

    浏览量

    141113
  • 接收机
    +关注

    关注

    8

    文章

    1122

    浏览量

    52655
  • 串口中断
    +关注

    关注

    0

    文章

    63

    浏览量

    13617
  • STM32L4
    +关注

    关注

    1

    文章

    42

    浏览量

    9263
  • HAL驱动
    +关注

    关注

    0

    文章

    3

    浏览量

    1159
收藏 人收藏

    评论

    相关推荐

    电距离和电气间隙的测量与各自的界限值

    间隙 电距离 电气间隙 电距离 电气间隙 不同极性带电部件之间1)有防止污物沉积的保护2)1.0 1.0 2.0 2.0 1.0 1.0 没有防止污物沉积的保护
    发表于 09-22 22:19

    4月22日汽车电子与零组件技术论坛,领航技术攻略尽在于此

    ` 本帖最后由 Elecfans--Jelly 于 2015-3-20 15:20 编辑 在经济全球化汽车零组件产业迅速发展的潮浪下,开拓无限的创新技术不是不可能!怎样掌控车用微控制器集成化
    发表于 03-20 15:15

    请问哪里能买到一台40619组件麦克风?

    我想买一台40619组件麦克风,但我不知道在哪里可以买到,所以所有的建议都很受欢迎。 以上来自于百度翻译 以下为原文I want
    发表于 07-02 09:37

    有没有办法改变期望频率的WS28 12组件

    嗨,有没有办法改变“期望频率”的WS28 12组件?我把这个组件用在5LP PK,FIST @ 24兆赫的设计上,它运行得非常好,但是如果我改变了设计,运行48兆赫,LED都是白色的。用相同的组件
    发表于 09-16 06:58

    【微信精选】开关电源layout,电距离与电气间隙到底该留多少?

    电距离:沿绝缘表面测得的两个导电零部之间,在不同的使用情况下,由于导体周围的绝缘材料被电极化,导致绝缘材料呈现带电现象的带电区。示意图电气间隙:在两个导电零部之间或导电零部与设
    发表于 10-10 07:30

    变压器设计中的电距离与电气间隙

    电距离:沿绝缘表面测得的两个导电零部之间或导电零部与设备防护界面之间的最短路径。电气间隙:在两个导电零部之间或导电零部与设备防护界
    发表于 10-10 09:52

    Android利用SurfaceView显示Camera图像

    前言前一章《Android利用SurfaceView显示Camera图像记(一)》我们已经实现了利用SurfaceView将Camera中的实时帧图像显示出来了,我们做这个的主要目录...
    发表于 07-02 08:14

    使用RT-Thread的PM组件时的问题

    最近在使用RT-Thread的PM组件时遇到了一奇怪的现象,发现居然进不去,调用HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI)函数会直接跳过里面的__WFI
    发表于 08-13 07:14

    RT-Thread当前最新的nRF24L01组件如何使用?

    RT-Thread当前最新的nRF24L01组件如何使用?
    发表于 12-17 07:19

    如何移植RT-Thread PM组件

    一, 认识 PM 组件在上一篇的文章中,介绍了如何移植 RT-Thread PM 组件PM 组件
    发表于 02-23 06:01

    RT-Thread PM2.0管理使用指南

    前言本篇主要对RT-Thread PM2.0(2021更新版)做个使用指南,介绍PM管理的新API的使用PM管理(电源管理、功耗管理)是如今穿戴类产品比较关心的产品卖点前面的几篇文章,或多或少
    发表于 08-09 10:34

    PM2.0外部中断唤醒后程序执行顺序异常为什么会发生这种情况呢

    设备为stm32L071,rtt是github上下载的最新版4.1.1。在调试pm功能时遇到问题:1.可以进入STOP模式,tickless使用lptim,工作正常,软件定时器可以主动唤醒。2.当
    发表于 08-18 09:37

    基于PM2.0与STM32L4的LoRaWAN Class A低功耗设备设计方案介绍

    1、基于PM2.0与STM32L4的LoRaWAN Class A低功耗设备设计  RT-Thread PM2.0组件的整体设计思想是PM组件
    发表于 09-27 11:27

    PM组件如何进入standby模式呢

    应用PM组件后,release掉 none默认进入deepsleep,现在想进release 掉none后进入stanby模式,因为PM组件中默认release掉none后会进入dee
    发表于 10-19 10:53

    移植w5500组件出现多个警告问题这是什么原因呢

    移植w5500组件包以后有10个警告(如下),这是什么原因呢?W5500/w5500.h:925:0: warning: "SOCK_DGRAM" redefinedW5500
    发表于 11-24 11:32