Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
LPUART
PM2.0
PM2.0组件爬坑记录
发布于 2023-07-07 17:16:25 浏览:654
订阅该版
硬件环境:潘多拉开发板(STM32L475VET6) 软件环境:RT-Thread4.1.x、bsp\stm32\stm32l475-atk-pandora、scons、MDK5.33.0 参考资料: 1、张世争大佬(PM2.0的主要开发者)的低功耗分享帖子(https://club.rt-thread.org/ask/article/a875f009b6e99e64.html) 2、张世争大佬的示例代码(https://gitee.com/zhangsz0516/rtt_pm2.git) 本文中的大部分问题都能从大佬的帖子里找到答案,我是从一个使用者角度记录一下遇到的问题,顺便做个总结方便以后查看。 PM2.0组件采用了空闲任务中进行低功耗管理策略,使用时要把空闲任务的堆栈扩大到2048。建议刚使用低功耗组件的先去阅读该部分源码部分,这部分代码并不长,半天时间足够分析清楚组件的运行逻辑了。 组件默认在无请求时进入低功耗深睡眠模式,被特殊事件唤醒才能继续工作。我在这里面踩得最大的坑就是从深睡眠中唤醒。 关于这部分首先要去看STM32L4的官方RM手册,了解如何进入/退出深睡眠,各个电源模式如何切换,再写裸机代码验证之后再去看PM2.0组件会容易很多。 在记录问题之前着重提醒:`在刷完固件验证之前一定要重新给板子上电再看结果!!!重要的事情说三遍!!!` ###Q1:无法进入低功耗模式 A1:系统默认是有个电源ID默认请求不睡眠,需要释放后才可以进入通过电源管理策略进入深睡眠。可以在main.c添加如下代码即可。 ```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。 ```c // __HAL_RCC_LPTIM1_CLK_ENABLE(); // 使能LPTIM1时钟 extern int stm32l4_hw_lptim_init(void); stm32l4_hw_lptim_init(); // 初始化LPTIM1 ``` board.c中检查时钟重新配置 ```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需要的超时时间源 ```c 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模式。 ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230707/321702b683d03dd1cba59e2677717a6e.png.webp) 在..\board\pm_cfg.h中添加要管理的KEYID ```c 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 ```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=%d\n", 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); ``` 修改..\applications\SConscript脚本,把key.c加入工程 ```c src = ['main.c','key.c'] ``` 最后用scons重新构建工程即可 这里实现了通过三个不同的按键进入不同的电源模式。可以发现离开深睡眠模式之后系统的控制台又可以正常工作了! ###Q4:如何通过低功耗串口唤醒芯片起来进入正常工作模式? A4:原理与Q3的按键唤醒类似,只不过通过串口唤醒需要去做一些硬件配置。 配置方式放在..\libraries\HAL_Drivers\drv_usart.c中 ```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. ![screenshot_image.png](https://oss-club.rt-thread.org/uploads/20230707/57bff3f3e8d2d0a4f43b35d97a6d2978.png.webp) 生成lpuart的HAL驱动之后就是对接到rtt的设备框架。注意SCons中使能宏定义 BSP_USING_LPUART1 可能会提示缺少相关定义,在uart_config.h中添加即可 ```c #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的选项卡 ```python config BSP_USING_LPUART1 bool "Enable LPUART1" default n ``` 至此驱动部分已经添加完成。 接下来是使用示例,使用PB10和PB11作为低功耗串口的收发脚。代码copy的大佬的,我做了一些修改方便验证。代码的运行逻辑是:上电5秒后深度睡眠,接收到低功耗串口的数据唤醒,并把数据加1后原样返回;当接收到数据0x0BB时认为通讯完成,再次释放持有的电源模式,进入深度睡眠。 ```python #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!\r\n"; /* 查找系统中的串口设备 */ 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); ``` 这样写只是方便测试低功耗串口唤醒设备,实际使用中还是把接收到的数据校验后封装成事件处理,事件处理完之后再释放电源模式比较合理。
1
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
zx595
这家伙很懒,什么也没写!
文章
1
回答
12
被采纳
0
关注TA
发私信
相关文章
1
反馈rtt串口驱动对低功耗串口lpuart1不兼容的问题
2
stm32l475 LPUART1使用DMA接收打开错误-RT_EIO
3
H743的LPUART1还是不能适配吗?
4
【PM2.0组件优化】优化建议集思广益交流
5
RT-Thread PM2.0 新框架已经上线,欢迎大家使用,提建议
6
PM2.0 组件调试请教
7
RT-Thread :PM不能休眠
8
STM32L4下PM组件测试无法进入STOP2
9
关于STM32L4 PM组件使用的疑问
10
PM2.0组件函数 rt_pm_notify_set 后 notify 回调影响进入休眠
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
rt_mq_消息队列_msg_queue
keil_MDK
ulog
MicroPython
C++_cpp
本月问答贡献
a1012112796
20
个答案
3
次被采纳
张世争
11
个答案
3
次被采纳
踩姑娘的小蘑菇
7
个答案
3
次被采纳
rv666
9
个答案
2
次被采纳
用户名由3_15位
13
个答案
1
次被采纳
本月文章贡献
程序员阿伟
9
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
RTT_逍遥
1
篇文章
6
次点赞
大龄码农
1
篇文章
5
次点赞
ThinkCode
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部