ESP32 ESP-IDF ADC使用详解
- 一、源码分享
-
- 1、效果展示
- 2、开发环境搭建
- 3、源码分享
- 4、ADC管脚对应
- 二、ADC详解
-
- 1、 ESP32 ADC 硬件概述
- 2、 ESP-IDF ADC 驱动使用步骤
-
- 2.1、 配置 ADC 通道
- 2.2 、读取 ADC 值
- 2.3、 ADC 校准 (关键步骤)(旧版本)
- 3. 示例代码 (ADC1)(旧版本)
- 4、 重要注意事项

一、源码分享
1、效果展示

2、开发环境搭建
参考我这篇博文:VS Code 在线安装ESP-IDF,ESP32开发环境搭建详细教程
3、源码分享
/*
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/projdefs.h"
#include "freertos/task.h"
#include <sys/time.h>
#include "driver/gpio.h"
#include "driver/ledc.h"
#include "esp_timer.h"
#include "esp_task_wdt.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_log.h"
/* 管脚声明 */
#define ADC_CHAN ADC_CHANNEL_6 /* 对应管脚为GPIO14 */
adc_oneshot_unit_handle_t adc_handle = NULL; /* ADC句柄 */
/**
* @brief 初始化ADC
* @param 无
* @retval 无
*/
void adc_init(void)
{
adc_oneshot_unit_init_cfg_t adc_config = { /* 初始化配置结构体 */
.unit_id = ADC_UNIT_2, /* ADC单元:ADC1/ADC2 */
.ulp_mode = ADC_ULP_MODE_DISABLE, /* 不支持ADC在ULP模式下工作 */
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&adc_config, &adc_handle)); /* ADC初始化(单次转换模式) */
/* 配置 ADC */
adc_oneshot_chan_cfg_t config = {
.atten = ADC_ATTEN_DB_12, /* ADC衰减 */
.bitwidth = ADC_BITWIDTH_12, /* ADC分辨率 */
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHAN, &config)); /* 配置ADC通道 */
}
#define LOST_VAL 1
/**
* @brief 获取ADC转换且进行多次采样后排序去除最高和最低值再做均值滤波后的结果
* @note ESP32P4 ADC对噪声敏感,可能导致ADC读数出现较大偏差
* @note 软件上:可通过多次采样进一步降低噪声影响;硬件上:可加旁路电容连在在ADC使用引脚上
* @param ch : 通道号, 0~9
* @param times : 获取次数
* @retval 通道ch的times次转换结果平均值
*/
uint32_t adc_get_result_average(adc_channel_t ch, uint32_t times)
{
uint32_t sum = 0;
uint16_t temp_val = 0;
/* 申请存放ADC原始数据buffer */
int *rawdata = heap_caps_malloc(times * sizeof(int), MALLOC_CAP_INTERNAL);
if (NULL == rawdata)
{
ESP_LOGE("adc", "Memory for adc is not enough");
}
for (uint32_t t = 0; t < times; t++) /* 多次ADC采样 */
{
adc_oneshot_read(adc_handle, ch, &rawdata[t]); /* 读取原始数据 */
vTaskDelay(pdMS_TO_TICKS(5));
}
for (uint16_t i = 0; i < times – 1; i++) /* 对数据进行排序 */
{
for (uint16_t j = i + 1; j < times; j++)
{
if (rawdata[i] > rawdata[j]) /* 升序排列 */
{
temp_val = rawdata[i];
rawdata[i] = rawdata[j];
rawdata[j] = temp_val;
}
}
}
for (uint32_t i = LOST_VAL; i < times – LOST_VAL; i++) /* 去掉两端的丢弃值 */
{
sum += rawdata[i]; /* 累加去掉丢弃值以后的数据 */
}
return sum / (times – 2 * LOST_VAL); /* 返回平均值 */
heap_caps_free(rawdata); /* 释放内存 */
}
void app_main(void)
{
uint16_t adc_data = 0;
float adc_vol = 0;
adc_init(); /* ADC初始化 */
while(1)
{
adc_data = adc_get_result_average(ADC_CHAN, 10); /* 获取ADC值 */
adc_vol = (float)adc_data * 3.3 / 4095;
printf("adc_data = %d, adc_vol = %.2f\\n", adc_data, adc_vol);
vTaskDelay(pdTICKS_TO_MS(500));
}
}
4、ADC管脚对应

二、ADC详解
1、 ESP32 ADC 硬件概述
ESP32 芯片集成了两个 12 位 SAR(逐次逼近寄存器)型 ADC 单元:
- ADC1:可测量 8 个专用通道(GPIO 36, 37, 38, 39, 32, 33, 34, 35)。注意:GPIO 36 和 39 在 ESP32 某些封装中可能不可用。
- ADC2:可测量 10 个专用通道(GPIO 4, 0, 2, 15, 13, 12, 14, 27, 25, 26)。重要限制:当 Wi-Fi 功能被启用时,ADC2 的使用会受到很大限制,因为 Wi-Fi 驱动会占用 ADC2 的资源。此时通常无法安全地使用 ADC2。
关键特性:
- 分辨率:名义上是 12 位,但由于非线性误差,实际有效位数(ENOB)通常较低(可能只有 9-10 位)。
- 输入电压范围:通过 attenuation(衰减)设置,可以调整 ADC 可测量的输入电压范围。ESP-IDF 提供了几个预定义的衰减等级。
- 非线性:ESP32 的 ADC 存在显著的非线性(特别是在电压范围的两端)。这是 ESP32 ADC 的一个已知特点。为了获得更准确的读数,校准是强烈推荐的步骤。
2、 ESP-IDF ADC 驱动使用步骤
2.1、 配置 ADC 通道
- 选择单元 (ADC1 or ADC2):优先考虑使用 ADC1,因为它不受 Wi-Fi 状态的影响。
- 配置通道宽度 (可选):对于 ADC1,可以设置位宽(如 ADC_WIDTH_BIT_12)。注意:ADC2 的宽度在 Wi-Fi 启用时通常不能更改。
- 设置衰减 (Attenuation):衰减决定了 ADC 可以测量的最大电压。ESP-IDF 定义了 adc_atten_t 类型:typedef enum {
ADC_ATTEN_DB_0 = 0, // 约 0dB 衰减,测量范围 ~ 0-1.1V
ADC_ATTEN_DB_2_5 = 1, // 约 2.5dB 衰减,测量范围 ~ 0-1.5V
ADC_ATTEN_DB_6 = 2, // 约 6dB 衰减,测量范围 ~ 0-2.2V
ADC_ATTEN_DB_11 = 3, // 约 11dB 衰减,测量范围 ~ 0-3.9V (最常用)
} adc_atten_t;
使用 adc1_config_channel_atten(channel, atten) (ADC1) 或 adc2_config_channel_atten(channel, atten) (ADC2) 进行设置。 - 配置通道引脚:将所选 GPIO 引脚配置为 ADC 功能(通常通过 gpio_set_direction 和设置 GPIO 模式为 ANALOG)。
2.2 、读取 ADC 值
- ADC单次采样:esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t handle, adc_channel_t chan, int *out_raw)
返回一个整数,范围取决于之前设置的位宽(例如,12位时为 0-4095)。
2.3、 ADC 校准 (关键步骤)(旧版本)
为了补偿 ESP32 ADC 的非线性误差,必须进行校准。ESP-IDF 提供了 esp_adc_cal 组件来简化这个过程。
- 包含头文件:#include "esp_adc_cal.h"
- 获取校准特性:在初始化阶段(例如在 app_main 中),获取所选衰减等级和通道的校准特性。esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_characterize(adc_unit_t unit, adc_atten_t atten, adc_bits_width_t bit_width, uint32_t default_vref, esp_adc_cal_characteristics_t *chars);- unit: ADC_UNIT_1 或 ADC_UNIT_2。
- atten: 之前设置的衰减值。
- bit_width: 之前设置的位宽。
- default_vref: 如果无法从 eFuse 读取到参考电压值,则使用此默认值(通常可以传 0,让函数尝试读取 eFuse)。
- chars: 指向存储校准特性的结构体的指针。
- 将原始值转换为电压:读取到原始 ADC 值后,使用校准特性将其转换为电压值(单位:毫伏 mV)。uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars);
这个 voltage_mv 是经过校准后的电压值,比原始 ADC 值准确得多。
3. 示例代码 (ADC1)(旧版本)
#include "driver/adc.h"
#include "esp_adc_cal.h"
#define ADC1_EXAMPLE_CHAN ADC1_CHANNEL_6 // 对应 GPIO 34
#define ADC_ATTEN ADC_ATTEN_DB_11 // 约 0-3.9V 范围
void app_main() {
// 1. 配置 ADC 通道宽度 (仅 ADC1)
adc1_config_width(ADC_WIDTH_BIT_12);
// 2. 配置通道衰减
adc1_config_channel_atten(ADC1_EXAMPLE_CHAN, ADC_ATTEN);
// 3. 初始化校准
esp_adc_cal_characteristics_t *adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN, ADC_WIDTH_BIT_12, 0, adc_chars); // 尝试从 eFuse 读取 Vref
while (1) {
// 4. 读取原始 ADC 值
int raw = adc1_get_raw(ADC1_EXAMPLE_CHAN);
// 5. 转换为校准后的电压 (mV)
uint32_t voltage = esp_adc_cal_raw_to_voltage(raw, adc_chars);
// 打印结果
printf("Raw: %d\\tVoltage: %ldmV\\n", raw, voltage);
vTaskDelay(pdMS_TO_TICKS(1000)); // 延时 1 秒
}
free(adc_chars);
}
4、 重要注意事项





