Appearance
第六章:ADC 模拟信号采集
注意:ESP32-S3 没有内置 DAC(ESP32 有,S3 没有)。 需要 DAC 输出时,使用 I2S + 外部 DAC 芯片,或用 PWM 滤波模拟。
6.1 ADC 基础
ESP32-S3 ADC 特性
ADC1:GPIO1~GPIO10(10个通道)
ADC2:GPIO11~GPIO20(10个通道)
注意:ADC2 与 Wi-Fi 共用,Wi-Fi 开启时 ADC2 不可用!
分辨率:12位(0~4095)
参考电压:内部 1.1V(可衰减扩展量程)
量程(衰减配置):
ADC_ATTEN_DB_0 → 0~950mV
ADC_ATTEN_DB_2_5 → 0~1250mV
ADC_ATTEN_DB_6 → 0~1750mV
ADC_ATTEN_DB_12 → 0~3100mV(最常用)ADC 非线性问题
ESP32-S3 ADC 存在非线性误差,尤其在两端(接近 0V 和满量程时)
解决方案:
1. 使用 ESP-IDF 内置校准(esp_adc/adc_cali.h)
2. 避免使用 0~100mV 和 3000~3300mV 范围
3. 外接精密 ADC(如 ADS1115)用于高精度场合6.2 ADC 单次采样
c
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#include "esp_log.h"
#define ADC_CHANNEL ADC_CHANNEL_0 // GPIO1
#define ADC_ATTEN ADC_ATTEN_DB_12 // 0~3.1V 量程
static const char *TAG = "ADC";
static adc_oneshot_unit_handle_t adc_handle;
static adc_cali_handle_t cali_handle;
// 初始化 ADC(带校准)
void adc_init(void)
{
// 1. 创建 ADC 单元
adc_oneshot_unit_init_cfg_t unit_cfg = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&unit_cfg, &adc_handle));
// 2. 配置通道
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT, // 12位
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle,
ADC_CHANNEL, &chan_cfg));
// 3. 初始化校准(曲线拟合方案,精度更高)
adc_cali_curve_fitting_config_t cali_cfg = {
.unit_id = ADC_UNIT_1,
.chan = ADC_CHANNEL,
.atten = ADC_ATTEN,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_cfg, &cali_handle);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "ADC 校准初始化成功(曲线拟合)");
} else {
ESP_LOGW(TAG, "ADC 校准不可用,使用原始值");
cali_handle = NULL;
}
}
// 读取 ADC 电压(毫伏)
int adc_read_mv(void)
{
int raw;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL, &raw));
if (cali_handle) {
int voltage_mv;
adc_cali_raw_to_voltage(cali_handle, raw, &voltage_mv);
return voltage_mv;
}
// 无校准:简单线性换算(不精确)
return (raw * 3100) / 4095;
}
void app_main(void)
{
adc_init();
while (1) {
int mv = adc_read_mv();
float voltage = mv / 1000.0f;
ESP_LOGI(TAG, "ADC 电压: %d mV (%.3f V)", mv, voltage);
vTaskDelay(pdMS_TO_TICKS(500));
}
}6.3 ADC 连续采样(DMA)
c
#include "esp_adc/adc_continuous.h"
#define SAMPLE_FREQ_HZ 20000 // 采样率 20kHz
#define BUF_SIZE 1024 // 每次 DMA 传输字节数
static adc_continuous_handle_t cont_handle;
void adc_continuous_init(void)
{
adc_continuous_handle_cfg_t handle_cfg = {
.max_store_buf_size = 4096,
.conv_frame_size = BUF_SIZE,
};
ESP_ERROR_CHECK(adc_continuous_new_handle(&handle_cfg, &cont_handle));
adc_continuous_config_t cont_cfg = {
.sample_freq_hz = SAMPLE_FREQ_HZ,
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
.pattern_num = 1,
.adc_pattern = (adc_digi_pattern_config_t[]) {{
.atten = ADC_ATTEN_DB_12,
.channel = ADC_CHANNEL_0,
.unit = ADC_UNIT_1,
.bit_width = SOC_ADC_DIGI_MAX_BITWIDTH,
}},
};
ESP_ERROR_CHECK(adc_continuous_config(cont_handle, &cont_cfg));
ESP_ERROR_CHECK(adc_continuous_start(cont_handle));
}
// 读取一批采样数据
void adc_read_batch(void)
{
uint8_t buf[BUF_SIZE];
uint32_t out_len = 0;
esp_err_t ret = adc_continuous_read(cont_handle, buf, BUF_SIZE,
&out_len, pdMS_TO_TICKS(100));
if (ret == ESP_OK) {
int count = out_len / sizeof(adc_digi_output_data_t);
adc_digi_output_data_t *data = (adc_digi_output_data_t *)buf;
int32_t sum = 0;
for (int i = 0; i < count; i++) {
sum += data[i].type2.data;
}
int avg = sum / count;
ESP_LOGI("ADC_CONT", "平均值: %d(%d 个采样)", avg, count);
}
}6.4 实用应用:电位器 + 电压表
c
// 读取电位器值,映射到 0~100%
int read_potentiometer(void)
{
int mv = adc_read_mv();
// 3300mV 对应 100%
int percent = (mv * 100) / 3300;
if (percent > 100) percent = 100;
return percent;
}
// 简单电压表(多次采样取平均,减少噪声)
float read_voltage_avg(int samples)
{
int32_t sum = 0;
for (int i = 0; i < samples; i++) {
sum += adc_read_mv();
vTaskDelay(pdMS_TO_TICKS(1));
}
return (float)(sum / samples) / 1000.0f;
}6.5 PWM 模拟 DAC 输出
c
// ESP32-S3 无 DAC,用 PWM + RC 低通滤波器模拟
// RC 滤波器:R=10kΩ, C=10μF,截止频率 ≈ 1.6Hz
// PWM 频率需远高于截止频率(如 10kHz)
// 输出 0~3.3V 模拟电压
void dac_output_voltage(float voltage_v)
{
if (voltage_v < 0) voltage_v = 0;
if (voltage_v > 3.3f) voltage_v = 3.3f;
uint8_t percent = (uint8_t)(voltage_v / 3.3f * 100);
led_set_brightness(percent); // 复用 LEDC 函数
}📝 第六章练习题
练习 6-1:电压表(基础)
目标:掌握 ADC 基础读取
任务:
- 读取 GPIO1 的电压
- 每 500ms 打印一次
- 格式:
电压: 1.234 V | 原始值: 1536 | 百分比: 37% - 用电位器(或分压电路)改变输入电压测试
练习 6-2:电位器控制 LED 亮度(基础)
目标:ADC + PWM 联动
任务:
- 读取电位器(ADC)
- 将电压值(0~3.3V)映射到 LED 亮度(0~100%)
- 实时更新 PWM 占空比
- 串口打印:
电压: 1.65V → 亮度: 50%
练习 6-3:多通道采集(进阶)
目标:多 ADC 通道管理
任务:
- 同时采集 4 个 ADC 通道(GPIO1~GPIO4)
- 每个通道独立校准
- 每秒打印一次所有通道的电压值
- 检测哪个通道电压最高并标注
练习 6-4:简易示波器(进阶)
目标:ADC 连续采样 + 数据处理
任务:
- 使用 ADC 连续模式,采样率 10kHz
- 每 100ms 收集一批数据(1000 个采样点)
- 计算并打印:最大值、最小值、平均值、峰峰值
- 检测信号频率(过零点计数法)
练习 6-5:NTC 热敏电阻温度计(挑战)
目标:ADC + 物理量转换
背景:NTC 热敏电阻阻值随温度变化,常用 10kΩ NTC(B值 3950)
电路:3.3V → 10kΩ固定电阻 → ADC引脚 → NTC → GND
NTC 阻值计算:
R_ntc = R_fixed * V_adc / (V_cc - V_adc)
温度计算(Steinhart-Hart 简化公式):
1/T = 1/T0 + (1/B) * ln(R/R0)
T0 = 298.15K(25°C),R0 = 10000Ω,B = 3950任务:
- 实现 NTC 温度读取函数
- 每秒打印温度(°C 和 °F)
- 实现温度报警(超过 30°C 时 LED 闪烁)
- 记录最高温度和最低温度
练习 6-6:思考题
ADC2 在 Wi-Fi 开启时不可用,这是为什么?如果你的项目需要 Wi-Fi 和模拟输入,应该怎么办?
12 位 ADC 的分辨率是多少 mV(量程 3.3V)?如果需要测量 1mV 的变化,12 位 ADC 够用吗?
多次采样取平均值可以提高精度,这是什么原理?取 16 次平均相当于提高了多少位的分辨率?
PWM + RC 滤波器模拟 DAC 的方案有什么缺点?什么场合必须用真正的 DAC 芯片?
本章小结
| 知识点 | 掌握程度自评 |
|---|---|
| ADC 通道配置 | ⬜⬜⬜⬜⬜ |
| ADC 校准使用 | ⬜⬜⬜⬜⬜ |
| 原始值转电压 | ⬜⬜⬜⬜⬜ |
| ADC 连续采样 | ⬜⬜⬜⬜⬜ |
| 物理量转换计算 | ⬜⬜⬜⬜⬜ |
上一章:← I2C 与 SPI 通信下一章:FreeRTOS 基础 →