Skip to content

第六章: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:思考题

  1. ADC2 在 Wi-Fi 开启时不可用,这是为什么?如果你的项目需要 Wi-Fi 和模拟输入,应该怎么办?

  2. 12 位 ADC 的分辨率是多少 mV(量程 3.3V)?如果需要测量 1mV 的变化,12 位 ADC 够用吗?

  3. 多次采样取平均值可以提高精度,这是什么原理?取 16 次平均相当于提高了多少位的分辨率?

  4. PWM + RC 滤波器模拟 DAC 的方案有什么缺点?什么场合必须用真正的 DAC 芯片?


本章小结

知识点掌握程度自评
ADC 通道配置⬜⬜⬜⬜⬜
ADC 校准使用⬜⬜⬜⬜⬜
原始值转电压⬜⬜⬜⬜⬜
ADC 连续采样⬜⬜⬜⬜⬜
物理量转换计算⬜⬜⬜⬜⬜

上一章← I2C 与 SPI 通信下一章FreeRTOS 基础 →

个人知识库