☰
STM32L0低功耗ADC采集:STOP模式+RTC唤醒的电池供电方案
2026/10/19 11:11:50 网站建设 项目流程

简介:低功耗STM32L0x1系列基于ARM Cortex-M0+内核,专为电池供电的物联网设备设计,其ADC模块支持多通道采集、12位分辨率与多种转换模式,尤其注重待机状态下的能耗控制。这份ADC示例工程为STM32L0x1用户提供了从初始化配置、转换启动到中断读取、数据处理及低功耗电源管理的完整参考流程,适合初学者入门,也可作为工程师快速移植到具体项目的模板。压缩包共178个文件,约4.6MB,以C源码、对应头文件、编译中间文件及Keil工程配置为主,涵盖HAL/LL库驱动例程与基础启动代码,结构清晰便于直接导入开发环境使用。目前已有295人学习下载,适合需要快速掌握低功耗ADC采集应用开发的嵌入式开发者。

1. 低功耗STM32L0x1-ADC程序:给电池供电的采集节点准备的工程样板

前段时间我在做一个用CR2032钮扣电池供电的环境监测节点,主控需要每隔几秒醒来一次,采集一路模拟量再继续睡。整机待机电流要求压在几微安以内,这种场景下如果还按照常规“轮询+delay”的思路来写ADC程序,功耗根本压不住。拆了这个名为“低功耗STM32L0x1-ADC程序”的压缩包后,我发现它正好补齐了一个关键环节:用STOP模式配合RTC定时唤醒,唤醒后ADC以单次转换模式采集数据,转换完自动断电,整套逻辑闭环做得非常干净,适合直接套用到物联网传感节点、便携仪表或电池供电的数据记录仪上。

这类工程适合两种人:一种是刚用CubeMX生成好基础工程、但不知道低功耗ADC该怎么配的新手;另一种是用惯了F1系列想看看L0在超低功耗和内置参考电压上有哪些独特写法的开发者。下面我按工程结构、核心代码、避坑记录到二次改造依次拆开讲。

2. 工程结构:HAL库与L0外设的低功耗组织方式

2.1 为什么选STM32L0x1搭配HAL库

STM32L0x1系列基于Cortex-M0+内核,最高主频32MHz,Flash容量从16KB到192KB不等,属于意法半导体超低功耗产品线的入门档。它的低功耗特性主要体现在三个地方:一是STOP模式掉电功耗可以做到微安级,二是带内置参考电压VREFINT,不用外接精密基准源也能测量电池电压,三是RTC模块在待机状态下仍可独立运行,用来做周期唤醒非常方便。

这份工程默认采用HAL库而不是LL库,原因有两方面。HAL对外设的封装粒度适合应用层快速开发,尤其是L0上ADC、RTC、PWR这几个模块的初始化参数比较多,直接写寄存器容易漏配置;另外HAL库里已经处理了不少L0特有的细节,比如ADC校准、STOP模式唤醒后的时钟恢复,用熟了能省掉很多查勘手册的时间。LL库更轻量,但在这类低功耗项目中,代码可读性和维护性更重要。

我个人的习惯是混合使用:工程初始化走HAL,碰到需要精确控制时间窗口的地方再用LL直接操作寄存器。第一次跑通项目不必追求极限性能,把HAL的逻辑读明白就算入门了。

2.2 工程目录与文件职责

打开压缩包,工程主体由CubeMX生成,目录结构大致如下:

Project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── adc.h │ │ └── rtc.h │ └── Src/ │ ├── main.c // 主循环、低功耗状态机 │ ├── adc.c // ADC初始化、单次转换、校准 │ ├── rtc.c // RTC配置与周期唤醒 │ └── stm32l0xx_hal_msp.c // 外设底层的GPIO/时钟配置 ├── Drivers/ │ ├── CMSIS/ │ ├── STM32L0xx_HAL_Driver/ │ └── BSP/ ├── MDK-ARM/ │ ├── project.uvprojx // Keil工程文件 │ └── startup_stm32l0xx.s └── STM32CubeMX/ └── project.ioc // CubeMX配置备份

main.c是整体入口,承载低功耗状态机的流转;adc.c是本资源的核心,负责ADC外设的初始化和采样函数;rtc.c提供周期睡眠的时基;stm32l0xx_hal_msp.c存放HAL库回调的底层配置,比如ADC引脚的GPIO模式、RTC时钟使能,改引脚时主要动这个文件。

拿到工程后我建议先打开.ioc文件,用CubeMX看一眼图形化配置,可以快速弄清引脚分配和时钟树,再对照代码理解就更容易了。

2.3 时钟树与低功耗时钟选型

L0系列上电后默认使用MSI内部时钟,频率大约2.1MHz。工程里在SystemClock_Config函数中将系统时钟切换到PLL,最高跑到32MHz,但重点在于STOP模式唤醒后的时钟处理策略。

低功耗采集场景下,RTC需要独立时钟源,常见选择是LSE外部32.768kHz晶振或LSI内部低速时钟。LSE精度高,适合需要精确定时的场景;LSI不需要外部元件,但频率精度较差,可能要配合软件校准。Clock_Config函数里会检查并等待对应的时钟源就绪,如果板子上没焊晶振而代码又配置了LSE,启动时会卡死在等待标志位上。

2.4 电源管理和PWR模块配置

L0的PWR模块与F1系列差异很大。STOP模式下需要选择主稳压器还是低功耗稳压器,还需要配置唤醒后稳压器的恢复时间。工程里在进入低功耗前会调用PWR的配置接口,把VREFINT在STOP模式下的供电打开,保证唤醒后可以用内部参考电压做校准。

一个值得注意的配置是在stm32l0xx_hal_conf.h中打开HAL_RTC_MODULE_ENABLED、HAL_ADC_MODULE_ENABLED和HAL_PWR_MODULE_ENABLED三个宏。这三者是低功耗采集链路的基础,缺了任何一个,调用对应外设的函数都会编译报错。

3. 核心模块拆解:一次ADC采样从启动到返回的完整链路

3.1 ADC引脚模拟输入配置

ADC外设初始化分两层:一是ADC转换器本身的参数,二是底层的GPIO和时钟配置。HAL库通过MspInit回调把这两层分开,好处是换引脚时不用动转换器参数。

static void ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_InitStruct = {0}; if (hadc->Instance == ADC1) { __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); // 以PA0为模拟输入通道 GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } }

参数说明:Mode设置成模拟模式后,引脚的数字输入缓冲器会被禁用,避免引入额外漏电;Pull必须为NOPULL,如果配置成上拉或下拉,会改变输入阻抗,直接影响ADC采样结果的精度。MspInit由HAL_ADC_Init函数在内部自动调用,不需要在应用层手动执行。

3.2 ADC转换器初始化与校准

ADC转换器本身的参数配置集中在ADC_Init函数中,这里有几个对低功耗影响很大的选项。

void ADC_Init(void) { hadc.Instance = ADC1; hadc.Init.ClockSource = ADC_CLOCK_SYNC_PCLK_DIV1; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.SamplingTime = ADC_SAMPLINGTIME_41CYCLES_5; hadc.Init.LowPowerAutoPowerOff = ENABLE; hadc.Init.LowPowerAutoWait = DISABLE; hadc.Init.OversamplingMode = DISABLE; if (HAL_ADC_Init(&hadc) != HAL_OK) { Error_Handler(); } HAL_ADCEx_Calibration_Start(&hadc); }

逻辑说明:ClockSource选择PCLK分频作为ADC时钟,分辨率配置为12位,单次转换模式通过LowPowerAutoWait和OversamplingMode的组合体现。LowPowerAutoPowerOff置ENABLE是整份工程的核心思路,转换完成后ADC的模拟电压自动切断,功耗降到接近零,下次启动时再重新上电。

校准动作在每次初始化后执行一次,HAL_ADCEx_Calibration_Start会读取芯片出厂时存储的校准参数并写入ADC校准寄存器。L0系列的ADC校准值会在芯片生产时写入系统存储器,HAL内部自动处理,不需要手动读取地址。校准完成后才能开始转换,否则读数会有偏差。

3.3 单次转换的完整流程

单次转换模式下,一次采样的代码很短,但每一步都有讲究。

uint16_t ADC_ReadOnce(uint32_t channel) { ADC_ChannelConfTypeDef sConfig = {0}; uint16_t raw = 0; sConfig.Channel = channel; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLINGTIME_41CYCLES_5; HAL_ADC_ConfigChannel(&hadc, &sConfig); HAL_ADC_Start(&hadc); HAL_ADC_PollForConversion(&hadc, 100); raw = HAL_ADC_GetValue(&hadc); HAL_ADC_Stop(&hadc); return raw; }

逻辑说明:首先配置通道和排序位置,然后启动转换,PollForConversion会阻塞等待转换完成,最多等待100ms。读取结果后立刻停止ADC,配合前面配置的LowPowerAutoPowerOff,这时ADC进入省电状态。

参数说明:PollForConversion的超时参数100代表100ms,正常情况下单次转换只需要几十微秒,这个超时值主要是防止硬件异常时函数卡死。如果传感器输出阻抗较高,采样时间可以增大到ADC_SAMPLINGTIME_192CYCLES_5,否则采样保持电容充不满,读数会偏低。HAL_ADC_Stop之后,校准寄存器会保留,但如果进入STOP模式,唤醒后需要重新校准,这个在下一节细说。

3.4 VREFINT内置参考电压的应用

L0系列的ADC内置了一个连接到内部参考电压VREFINT的通道,通过读取这个通道,可以反推出当前VDD的真实电压值。这个功能对电池供电设备很实用,省去外部电阻分压电路,直接用ADC读电池电压。

float ADC_GetVDD(void) { uint16_t vref_raw = 0; float vdd = 0.0f; vref_raw = ADC_ReadOnce(ADC_CHANNEL_VREFINT); if (vref_raw != 0) { // VREFINT典型值为1.2V,12位ADC满量程4095对应VDD vdd = 1.2f * 4095.0f / (float)vref_raw; } return vdd; }

代码说明:VREFINT通道上读到的原始值会随VDD升高而成比例增大,用1.2V作为已知参考电压反推VDD。这个结果可以用于电池电压监测,也可以作为采集外部信号时的电压基准,防止电池电压下降导致采样值失真。

实际使用中有一个经验:VREFINT通道的采样结果受温度影响,如果产品工作环境温度变化大,建议在固件里做温度补偿,否则推算出的VDD电压会有几十毫伏的误差。

3.5 STOP模式唤醒后的恢复路径

STOP模式下,除了RTC等少数外设,大部分模块的时钟被关闭。唤醒后如果不恢复ADC,采集出来的值是乱的。工程里的处理方式是在唤醒流程中重新执行ADC初始化和校准。

void HandleWakeUp(void) { SystemClock_Config(); ADC_Init(); VREFINT_Calibration_Init(); }

RTC闹钟触发中断后,系统从STOP模式唤醒,首先恢复系统时钟,然后重新初始化ADC。这里必须重新校准的原因在于,STOP模式切断了ADC模块的时钟,校准寄存器在掉电后可能丢失,直接读取会得到偏离真实值的原始数据。

需要提醒的是,唤醒后不要立刻读取ADC,建议等MSI时钟稳定后再启动转换。HAL库的SystemClock_Config里已经包含等待时钟就绪的逻辑,但如果你自己写了时钟切换代码,要特别注意这个时序问题。

4. 编译烧录与避坑指南:低功耗采集的几个常见坑

4.1 编译环境与烧录

工程默认使用Keil MDK-ARM打开,双C文件后直接用ST-Link下载即可。低功耗验证时要注意烧录器的影响,ST-Link和目标板连接状态下,目标板不会进入真正的深度睡眠,测出的电流会偏高。测量整机低功耗电流前,需要将调试器断开,改用独立电源供电。

另外还需要留意调试器和目标板共地问题,无论用电流表还是万用表测电流,都要先断掉调试器的供电引脚,否则电流会通过调试器旁路,读数会明显失真。

4.2 ADC采集值跳动范围大

现象:输入电压固定,连续读取10次,结果能跳动几十个LSB,尤其在靠近满量程时更明显。

原因:大多是GPIO未正确配置为模拟模式,或者引脚内部上下拉没有关掉。还有一个容易被忽视的因素是采样时间设置过短,传感器输出阻抗较高时采样电容充不满。

解决:检查MspInit中GPIO的Mode是否为GPIO_MODE_ANALOG,Pull是否为GPIO_NOPULL。如果这两个没问题,把采样时间从41.5周期改到192周期再试。还不稳定就需要软件滤波,常见做法是连续采5次取中值。

4.3 唤醒后ADC读数整体偏移

现象:上电复位后采集值正常,RTC唤醒一次之后,同一电压的采样结果整体偏大或偏小。

原因:STOP模式掉电后ADC校准寄存器失效,但软件只是简单唤醒并读取,没有重新校准。L0系列的校准值存在系统存储区,重新调用HAL_ADCEx_Calibration_Start会重新加载。

解决:在每次唤醒后、转换前,一定要执行ADC_Init或至少单独调用校准函数。我测试过跳过校准的情况,读数偏移量能达到几十LSB,对12位采样来说是不可接受的。

4.4 整机待机电流高于预期

现象:理论上应该进入微安级待机,但实际测量有5mA甚至更高。

原因:最常见的是未使用的GPIO引脚悬空。悬空引脚在STOP模式下没有确定的电平状态,会通过输入端产生额外漏电流,某些引脚能贡献数十微安。另一个常见原因是调试器没有断开,或者PVD电压检测配置成低于VDD时,会反复触发唤醒。

解决:把未使用的GPIO全部配置为模拟模式或带上拉/下拉的输入模式,拔掉调试器再测量。进入STOP前检查PVD配置,确保检测阈值低于最低工作电压。另外L0的闪存掉电模式下要保持低功耗稳压器开启,如果配置成主稳压器,唤醒后功耗会偏高。

4.5 RTC闹钟到期但没唤醒

现象:代码执行到HAL_PWR_EnterSTOPMode后再无反应,RTC闹钟标志位置位但MCU没有退出STOP模式。

原因:RTC时钟源配置错误。如果在CubeMX中选择LSE但板子上没有焊接晶振,RTC初始化会一直在等待LSE ready,程序虽然显示运行,实际卡死在时钟就绪循环里。

解决:用示波器测量PC14/PC15引脚是否有32.768kHz波形。没有波形就改用LSI内部低速时钟,LSI频率精度约为±5%,红外闹钟时间会存在误差。如果允许外部晶振成本,优先补焊晶振来保证长期精度。

还有一个玄学问题:RTC唤醒中断优先级配置不当也可能导致唤醒失败。如果RTC中断优先级高于SysTick,且中断处理里调用了HAL_Delay,会因SysTick被阻塞而死等。我的建议是RTC中断优先级不要高过SysTick,唤醒后尽量用寄存器操作或简单标志位。

5. 二次开发:从单通道扩展到多通道自动上报

5.1 改造目标

拿到这份工程后,最普遍的诉求是从单通道扩展成多通道采集,并且把数据通过串口或无线模块定时上报。改造的关键不在ADC本体的代码,而在整体状态机的扩展。

设想场景:一个采集节点需要读取电池电压、环境温度和光照三个信号,每10秒唤醒一次,采集完成后通过串口发送出去,然后继续睡眠。这就需要在现有基础上改成多通道轮询,并增加串口发送逻辑。

5.2 多通道ADC采集改造

将原来的single_read函数扩展为轮询多通道,每个通道单独配置采样时间,通过结果数组存储原始值。

#define ADC_CH_NUM 3 uint32_t channel_table[ADC_CH_NUM] = { ADC_CHANNEL_0, // PA0:电池分压 ADC_CHANNEL_1, // PA1:温度传感 ADC_CHANNEL_2 // PA2:光敏电阻 }; uint16_t adc_raw[ADC_CH_NUM]; void ADC_MultiRead(void) { for (int i = 0; i < ADC_CH_NUM; i++) { adc_raw[i] = ADC_ReadOnce(channel_table[i]); } }

代码说明:每次调用ADC_ReadOnce时都会重新配置通道,转换完成后自动停止,下一次循环再切换到下一通道。由于每次是单次转换,不会出现多个通道间转换结果互相污染的问题,但通道切换后需要预留一小段稳定时间,特别是传感器经过分压电阻接入的情况,建议在配置Channel后加几十微秒的延时。

改造时强烈建议保留原有的校准流程,多通道情况下校准值对整个转换链路都有影响,跳过一次,后面三个通道的数据都会偏离。

5.3 串口上报与触发策略

上报部分按需编写,低功耗场景下串口不应常开。可以在唤醒后仅发送本次采集结果,发送完成后立即关闭串口并进入STOP模式。

void SensorReport(void) { char buf[64]; snprintf(buf, sizeof(buf), "BAT:%d TMP:%d LIGHT:%d\r\n", adc_raw[0], adc_raw[1], adc_raw[2]); HAL_UART_Transmit(&huart2, (uint8_t*)buf, strlen(buf), 100); }

发送完成后关闭UART时钟调用,这样串口在睡眠期间不耗电。如果接的是无线模块,发送时要考虑射频模块的电流尖峰,采集和发送共用一个电源轨时,PVD阈值要设置合理,防止电压跌落触发复位。

5.4 验证和二次开发的检查清单

改造完成后,验证方法是测量整机的功耗曲线。用电流探头观察唤醒周期内的电流波形,正常情况是两个尖峰(采集尖峰和发送尖峰)之间有一段极低的平线,平线的高度对应STOP模式的电流。如果没有看到尖峰,多半是调试器没断开或GPIO配置漏了。

我后来每次接手这类带低功耗的工程,都会把“GPIO模拟模式检查”和“唤醒后重新校准”放在验收清单第一页,这两个点不出问题,剩下的功耗和精度指标基本就稳了。每个工程都有自己的脾气,但这两个坑几乎是同款芯片项目的通病。希望帮到你,按照这份思路跑一遍,你也能快速把低功耗ADC采集的框架搭起来。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询