Skip to content

ESP32-S3-RLCD ADC 电池采样示例拆解

03_ADC_Test 演示如何读取板载电池电压:ESP32-S3 通过 ADC_UNIT_1 / ADC_CHANNEL_3 采样电池分压点,再通过校准和倍率还原为电池实际电压。

ADC 是 Analog to Digital Converter,也就是模数转换器。它把模拟电压转换成数字值。ESP32-S3 ADC 读取到的第一手结果叫 raw,它不是电压,而是一个和输入电压相关的数字。

raw 到电池电压需要三步:

ADC raw
-> ADC 校准
-> ADC 引脚电压 mV
-> 按电阻分压倍率还原
-> 电池实际电压

电池电压通常高于 ADC 引脚适合直接测量的电压范围,所以开发板会使用电阻分压。这个 demo 中代码使用 * 3 还原电池电压,含义是 ADC 引脚看到的电压约为电池电压的三分之一。

电量百分比不是 ADC 直接测出来的,而是 demo 用线性公式粗略估算:

3.0V 以下 -> 0%
4.12V 以上 -> 100%
中间 -> 按线性比例换算

这适合示例学习,不等同于真实锂电池电量曲线。

源码阅读入口:

02_ESP-IDF/03_ADC_Test/main/main.cpp
02_ESP-IDF/03_ADC_Test/components/user_app/user_app.cpp
02_ESP-IDF/03_ADC_Test/components/port_bsp/adc_bsp.cpp
02_ESP-IDF/03_ADC_Test/components/port_bsp/adc_bsp.h

关键硬件配置:

项目配置
ADC unitADC_UNIT_1
ADC channelADC_CHANNEL_3
对应 GPIOGPIO4
位宽ADC_BITWIDTH_12
衰减ADC_ATTEN_DB_12
校准方案adc_cali_create_scheme_curve_fitting()
分压还原battery_v = adc_mv * 0.001 * 3
app_main()
-> UserApp_AppInit()
-> Adc_PortInit()
-> adc_cali_create_scheme_curve_fitting()
-> adc_oneshot_new_unit()
-> adc_oneshot_config_channel(ADC_CHANNEL_3)
-> xTaskCreate(Adc_LoopTask)
Adc_LoopTask()
-> Adc_GetBatteryVoltage()
-> adc_oneshot_read()
-> adc_cali_raw_to_voltage()
-> mV 转 V
-> 乘以 3 还原电池电压
-> 每 1000ms 打印一次
函数/方法所在文件作用初学者需要理解的点
app_main()main/main.cppESP-IDF 应用入口。只调用应用初始化,主逻辑不在这里。
UserApp_AppInit()components/user_app/user_app.cpp初始化 ADC 并创建采样任务。demo 行为层负责“多久读一次”。
Adc_LoopTask()components/user_app/user_app.cpp周期读取 ADC 并打印。ADC 不是自动上报,任务定时主动读。
Adc_PortInit()components/port_bsp/adc_bsp.cpp初始化 ADC 校准、unit、channel。板级硬件配置集中在 BSP。
adc_cali_create_scheme_curve_fitting()ESP-IDF ADC 校准 API创建曲线拟合校准句柄。用于把 raw 转成更接近真实的 mV。
adc_oneshot_new_unit()ESP-IDF ADC oneshot API创建一次性采样 ADC unit。低频电池采样适合 oneshot。
adc_oneshot_config_channel()ESP-IDF ADC oneshot API配置 channel、位宽和衰减。ADC_ATTEN_DB_12 扩大可测输入范围。
Adc_GetBatteryVoltage()components/port_bsp/adc_bsp.cpp读取 raw 并换算电池电压。这里串起 raw、校准、分压倍率。
Adc_GetBatteryLevel()components/port_bsp/adc_bsp.cpp把电压粗略映射成百分比。示例中主循环未调用它,但 FactoryProgram 会用类似逻辑。

初始化时,demo 先创建 ADC 校准句柄:

adc_cali_create_scheme_curve_fitting(...);

校准的作用是减少芯片 ADC 误差。没有校准时,raw 只能大致反映输入变化;有校准后,adc_cali_raw_to_voltage() 可以得到单位为 mV 的引脚电压。

接着创建 oneshot unit:

adc_oneshot_new_unit(...);
adc_oneshot_config_channel(...);

oneshot 的意思是需要时读一次。电池电压变化很慢,不需要高速连续采样,所以 oneshot 比连续采样更直观。

读取链路在 Adc_GetBatteryVoltage()

adc_oneshot_read(...);
adc_cali_raw_to_voltage(...);
vol = 0.001 * tage * 3;

这行换算可以拆开理解:

tage -> 校准后的 ADC 引脚电压,单位 mV
0.001 -> mV 转 V
* 3 -> 按板载分压倍率还原为电池电压

百分比估算使用线性区间:

percent = (voltage - 3.0) / (4.12 - 3.0) * 100

这只是显示层的粗略估计。真实产品中建议对电压做滑动平均或 EMA,并根据电池放电曲线调整百分比映射。

运行后,串口会先打印:

adc-example run

随后每秒打印一次类似:

Adc Value:1234,Batt Voltage:3.700000

Adc Value 是原始 ADC 数字值,Batt Voltage 是换算后的电池电压,单位是 V。

现象可能原因排查方式
把 raw 当电压raw 只是 ADC 原始数字。必须经过 adc_cali_raw_to_voltage()
电压只有真实电池的三分之一读到的是分压点电压。需要按电阻分压倍率还原,本 demo 是 * 3
电量百分比跳动ADC 采样和电池负载都会波动。后续可加多次平均或 EMA。
百分比不准锂电池电压曲线非线性。demo 公式只适合粗略显示。
想判断正在充电当前 ADC demo 没有充电状态读取。需要额外的 CHG/STAT GPIO、PMIC 或充电 IC 信息。

迁移到产品时,建议把电池能力拆成两层:

PowerService
-> 读取 ADC raw
-> 校准为电压
-> 分压还原
-> 输出 snapshot: voltage_mv / percent / valid
AppModel/UI
-> 根据 percent 显示电池图标
-> 根据 charging 字段显示充电图标
-> 根据低电阈值给出提示

底层服务只负责事实采集,不直接决定 UI 显示几格电。充电状态也不应该靠电压趋势猜测,最好来自硬件状态脚或电源管理芯片。