尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

用 ULP 协处理器做“永远在线却几乎不耗电“的传感采集

用 ULP 协处理器做“永远在线却几乎不耗电“的传感采集 适用人群已经看过《低功耗_01》把 STM32 Stop / ESP32 Deep Sleep 跑通现在想进一步——主核睡死也能持续盯传感器的同学读完你能得到① 搞懂主核深度睡眠时看门狗/传感监测谁来干② ESP32 ULP 协处理器RISC-V 版的可跑代码③ STM32 LPTIM / LPUART 在 Stop 里当看门人的写法④ 一套只在必要时唤醒主核的节流思路⑤ 避开 ULP 只能碰 RTC 域、LPTIM 时钟源选错就停摆这些坑。一、问题主核睡着了谁来看门《低功耗_01》我们做到了每分钟醒一次采集。但有些场景更狠烟雾/水浸报警节点要求只要传感器超阈值几秒内必须上报。你不可能让主核每分钟才看一次——万一中途着了它还在睡。运动手环计步要一直数步数但 MCU 全速跑又太费电。矛盾就在这想要永远在线监测 vs 想要主核深度睡眠省电 \ / \ / ✗ 这两个直接冲突 ✗解决思路只有一条把看门这件轻活从主核挪到低功耗单元上。主核继续 deep sleepµA 级ULP 协处理器 / 低功耗外设µW 级替它盯着传感器只有真出事了才把主核叫醒干重活联网、报警、存日志。二、核心思路监测下沉到低功耗单元角色状态干啥功耗量级主核CPU深度睡眠啥也不干等被叫醒ESP32 Deep Sleep ~5µASTM32 Stop ~1.5µAULP / 低功耗外设一直在跑周期性读传感器、做阈值比较ESP32 ULP-RISC-V ~几十 µASTM32 LPTIM 1µA唤醒主核的条件事件触发超阈值 / 累计 N 次 / 定时上报—— 核心直觉省电的关键不是主核睡多久而是主核被叫醒的频率。把 99% 的无效轮询挡在 ULP 里主核只在 1% 的有效事件醒来电池寿命才拉得满。三、ESP32 ULP 协处理器实战3.1 ESP32 有两种 ULP别搞混类型用在哪些芯片编程方式能力ULP-FSM经典 ESP32ESP32-D0WD汇编式 .S或用ulp组件封装8 位有限状态机能读 RTC ADC/触摸/温度做简单比较ULP-RISC-VESP32-S2 / S3 / C3 / C6标准 C独立 RISC-V 工具链编译32 位能跑 C 逻辑、访问 RTC 域、唤醒主核现在新项目基本是ULP-RISC-V下面以它为例最贴近写 C 就能用的体验。3.2 一个完整能跑的套路流程是主核先编译并把 ULP 程序烧进 RTC_SLOW_MEM → 启动 ULP → 主核自己进 Deep Sleep → ULP 按自己的定时器周期性跑超阈值才wakeup_main_processor()。主核侧main.c#includeulp_riscv.h#includeulp_main.h// 由构建系统生成的头含 ULP 入口符号// ULP 二进制由 CMake 的 ulp_embed_binary 嵌入链接器给出这两个符号externconstuint8_tulp_main_bin_start[]asm(_binary_ulp_main_bin_start);externconstuint8_tulp_main_bin_end[]asm(_binary_ulp_main_bin_end);voidapp_main(void){// 1. 把 ULP 程序载入 RTC_SLOW_MEM这块内存在 Deep Sleep 里不掉电ESP_ERROR_CHECK(ulp_riscv_load_binary(ulp_main_bin_start,(ulp_main_bin_end-ulp_main_bin_start)));// 2. 可选配置 RTC ADC 通道、IO 等 ULP 要用的底层资源// ulp_riscv_adc_cfg_t ...; 具体看 esp-idf/components/ulp/ulp_riscv/ulp_adc.h// 3. 启动 ULP启动后 ULP 按自己的定时器周期运行ESP_ERROR_CHECK(ulp_riscv_run());// 4. 配置唤醒源ULP 叫醒主核ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());// 5. 主核进 Deep SleepULP 继续从 RTC 内存跑esp_deep_sleep_start();}ULP 侧ulp_main.c独立 RISC-V 核上跑#includeulp_riscv.h#includeulp_riscv/ulp_riscv_adc.h// 以读 RTC ADC 为例#defineTHRESHOLD800// 阈值定点整数ULP 默认无浮点intmain(void){// ULP 自带一个周期定时器main() 返回后 ULP 休眠定时器到期再跑一次// 周期在编译/加载时配置ulp_riscv_timer_period 相关 APIintrawulp_riscv_adc_read(ADC_CHANNEL_0);// 读 RTC 域 ADC不会唤醒主核if(rawTHRESHOLD){// 只在出事了才叫醒主核ulp_riscv_wakeup_main_processor();}return0;// 返回即休眠等下一个定时器 tick}⚠️ 上面是套路骨架具体 API 名字随 ESP-IDF 版本略有差异ULP 自带的周期定时器需ulp_riscv_timer_period()/ulp_riscv_timer_start()之类配置名字随版本。ULP 读 ADC 前要先ulp_riscv_adc_init()并按版本配置通道且只能读 RTC 域的 ADC 通道详见components/ulp/ulp_riscv/include/ulp_riscv_adc.hulp_riscv_adc_read在较新版本里是ulp_riscv_adc_read_channel等。写之前idf.py menuconfig打开Component config → ULP → ULP RISC-V并确认ulp_main.c被ulp_embed_binary编进工程。3.3 ESP32 这版的功耗账状态电流量级主核运行 80MHz 轮询 ADC~15mA主核 Deep Sleep ULP 停着~5µA主核 Deep Sleep ULP 每 100ms 读一次 ADC30100µA取决于采样时长把每 100ms 轮询从主核挪到 ULP省了约 150~500 倍。而且只有在超阈值时主核才醒来联网绝大多数时间它都在睡。四、STM32 的低功耗看门人LPTIM LPUARTESP32 有独立 ULP 核STM32尤其 F4/L4没有独立协处理器但有一堆能在 Stop 里继续跑的低功耗外设当看门人。最常用两个4.1 LPTIM低功耗定时器—— 周期性把主核从 Stop 叫醒LPTIM 可以由LSE32.768kHz 外部晶振或 LSI内部低速驱动这两个时钟在 Stop 模式里不会停。它就能当心跳定时把核叫醒干活。#includestm32l4xx_hal.h// 以 L4 为例F4 用 stm32f4xx_hal.hAPI 类似LPTIM_HandleTypeDef hlptim1;voidlptim_wakeup_init(void){hlptim1.InstanceLPTIM1;// 关键LPTIM 要在 Stop 里继续跑必须选 ULPTIM 时钟源LSE/LSI。// 不能选 APBCLOCK——APB 总线时钟在 Stop 下被关定时器直接停摆。hlptim1.Init.ClockSourceLPTIM_CLOCKSOURCE_ULPTIM;hlptim1.Init.UltraLowPowerClockLPTIM_ULPTIM_CLOCKSOURCE_LSE;// LSE32768Hz用 LSI 改对应宏hlptim1.Init.ClockPrescalerLPTIM_PRESCALER_DIV1;hlptim1.Init.CounterSourceLPTIM_COUNTERSOURCE_INTERNAL;hlptim1.Init.TriggerSourceLPTIM_TRIGSOURCE_SOFTWARE;// ⚠️ 具体字段名/ULPTIM 分频位置随 HAL 版本略有差异以 stm32l4xx_hal_lptim.h 为准if(HAL_LPTIM_Init(hlptim1)!HAL_OK){Error_Handler();// 实际工程必须检查返回值不能忽略}// 启动超时Timeout模式每 (compare1) 个 LPTIM 时钟产生一次中断并唤醒// 例LSE32768Hz分频1compare32767 → 约 1 秒一次// ⚠️ 必须先使能 NVIC 中断HAL_NVIC_EnableIRQ(LPTIM1_IRQn)否则进不了 ISRif(HAL_LPTIM_TimeOut_Start_IT(hlptim1,0,32767)!HAL_OK){Error_Handler();}}// LPTIM 中断 → 从 Stop 唤醒后在这里干活voidLPTIM1_IRQHandler(void){HAL_LPTIM_IRQHandler(hlptim1);}voidHAL_LPTIM_CompareMatchCallback(LPTIM_HandleTypeDef*hlptim){// 1. 醒来先重配系统时钟Stop 把 HSE/PLL 关了和 低功耗_01 一样SystemClock_Config();// 2. 读传感器ADC / I2C做阈值判断intvread_sensor();// 读一次就好别常驻运行if(vALARM_THRESHOLD){do_real_alarm();// 真出事才干重活报警/上报}// 3. 干完立刻回 StopLPTIM 已经在跑下个周期再来HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);// L4 用 STOP2LPTIM 仍运行}4.2 LPUART —— “串口一直听喊到名字才醒”如果节点要被外部主机随时叫可以用LPUART 的地址匹配唤醒主核在 StopLPUART 收到匹配本机地址的字节才把核叫醒普通广播数据它自己丢掉、不吵醒主核。Stop 中的主核 ↑ 唤醒仅当地址匹配 LPUARTLSE 驱动Stop 里监听 ↑ 收到串口帧 外部主机 [地址0x0A][数据...] ← 只有 0x0A 这台才醒 选型口诀定时轮询用 LPTIM被动接收用 LPUART 地址匹配极致省电且能接受醒来当复位用 Standby RTC。三者可以叠加。代码上 LPUART 地址匹配唤醒用HAL_UARTEx_EnableStopMode()UART_WAKEUP_ON_ADDRESS配置详见stm32l4xx_hal_uart.h。五、功耗账再算一遍ULP / 低功耗外设版还是 2 节 AA2000mAh烟雾报警节点要求超阈值 3 秒内上报方案平均电流电池寿命主核常跑轮询15mA15mA~5.5 天主核每分钟醒低功耗_01~30µA~6.7 年ULP/LPTIM 监测 仅超阈值唤醒主核~50µA含偶尔联网~4.5 年且响应延迟 轮询周期注意最后一行它既保住了秒级响应又把寿命拉到年量级。这才是 ULP 的价值——不是比 Stop 更省那一丁点而是在省电的同时不牺牲实时性。六、5 个新手坑都是真踩过的ULP 只能碰 RTC 域寄存器ESP32 ULP 跑在 RTC_SLOW_MEM只能访问 RTC 控制器、RTC ADC、RTC GPIO。你去读普通 GPIO / SPI 外设——要么读不到要么直接挂。需要普通外设的数据主核醒来再读。ULP-RISC-V 默认没有浮点单元阈值比较、缩放一律用整数或定点如raw * 100 / 4095。写if (temp 30.5)编译能过运行结果是 UB。LPTIM 时钟源选错就停摆想让 LPTIM 在 Stop 里当心跳时钟源必须选LPTIM_CLOCKSOURCE_ULPTIM配UltraLowPowerClock LPTIM_ULPTIM_CLOCKSOURCE_LSE/LSI。若误选LPTIM_CLOCKSOURCE_APBCLOCK来自 HSI/HSE 的 APB 时钟一进 Stop 那俩时钟被关LPTIM 直接不动主核永远睡死。唤醒后主核当复位状态要挪到低功耗域ULP 累计的计数、上次读数放RTC_DATA_ATTRESP32或备份寄存器STM32RTC-BKRx。放普通 RAMDeep Sleep/Stop 一掉电全没。阈值判断必须放在 ULP / 唤醒 ISR 里做如果你每次 LPTIM 唤醒都把主核拉起来、跑完 FreeRTOS 再睡那和主核轮询没区别白省。ULP 把无效事件挡在门外主核才值得醒。七、练手题建议真烧一遍ESP32用 ULP-RISC-V 周期性读片内温度传感器ulp_riscv_adc或ulp_riscv_temperature_sensor只有 30°C 才wakeup_main_processor()主核醒来后打印一次温度再睡。用idf.py monitor观察唤醒次数。STM32L4用 LPTIM 每 2 秒从 STOP2 唤醒读一次 ADC连续 5 次超阈值才进do_real_alarm()计数放备份寄存器否则回 Stop。用电流表看平均电流。进阶把上面两个的唤醒条件改成窗口比较低于下限或高于上限都报体会 ULP 做边缘检测的便利。
返回列表