ESP32-S3 ADC 我是这样真正用顺的:从 SAR ADC 原理到一次完整的电压采集实战
ESP32-S3 ADC 我是这样真正用顺的:从 SAR ADC 原理到一次完整的电压采集实战

ESP32-S3 集成了 两个 12 位 SAR ADC,共支持 20 个模拟通道输入,在实际项目中非常适合用来采集:
- 电位器
- 传感器模拟输出
- 电压检测点
但在真正上手之前,有一个非常容易被忽略的问题。
ESP32-S3 的 ADC 并不是“随便一个 GPIO 都能用”。
一、ESP32-S3 ADC 硬件资源说明
ESP32-S3 内部的 SAR ADC 管脚,并不是独立存在的,而是通过 IO MUX 与多种外设功能复用,包括:
- GPIO1 ~ GPIO20
- RTC_GPIO1 ~ RTC_GPIO20
- 触摸传感器接口
- UART 接口
- SPI 接口
- USB_D- / USB_D+


因此这里有一个非常关键的结论:
并不是所有 IO 都支持 ADC 功能,只有特定 GPIO 才能作为 ADC 输入。
在画原理图或选引脚之前,一定要先对照 ADC 通道与 GPIO 的映射关系,否则软件写得再对,也永远读不到数据。
二、ESP32-S3 中使用 ADC 的基本流程
在 ESP-IDF 中,ADC 的使用并不是“一行 API 就结束”,而是一个相对规范的流程,整体可以分为五步:
- 创建 ADC 单元实例
- 配置 ADC 通道
- 读取 ADC 原始数据
- 创建校准方案
- 将原始值转换为实际电压
下面按顺序来看。
三、创建 ADC 单元实例
ESP-IDF 使用 oneshot(单次采样)模式 来完成常规 ADC 读取。
创建 ADC 单元的核心 API 是:
esp_err_t adc_oneshot_new_unit(
const adc_oneshot_unit_init_cfg_t *init_config,
adc_oneshot_unit_handle_t *out_handle
);
参数说明
*adc_oneshot_unit_init_cfg_t init_config
用于配置 ADC 单次采样单元的初始化参数。
*adc_oneshot_unit_handle_t out_handle
输出参数,返回 ADC 单元句柄,后续所有 ADC 操作都基于该句柄完成。
adc_oneshot_unit_init_cfg_t 结构体说明
typedef struct {
adc_unit_t unit_id;
adc_oneshot_clk_src_t clk_src;
adc_ulp_mode_t ulp_mode;
} adc_oneshot_unit_init_cfg_t;
字段含义如下:
-
unit_id
选择 ADC 单元- ADC_UNIT_1
- ADC_UNIT_2
-
clk_src
ADC 时钟源,设为 0 使用默认时钟 -
ulp_mode
是否支持超低功耗模式下的 ADC 使用
使用示例
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
adc_oneshot_unit_handle_t adc1_handle;
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
四、配置 ADC 通道参数
创建 ADC 单元后,还需要为具体的 ADC 通道 配置采样参数。
核心 API:
esp_err_t adc_oneshot_config_channel(
adc_oneshot_unit_handle_t handle,
const adc_channel_t *channel,
const adc_oneshot_chan_cfg_t *chan_cfg
);
参数说明
-
handle
ADC 单元句柄 -
channel
ADC 通道编号(如 ADC_CHANNEL_0) -
chan_cfg
ADC 通道配置参数
adc_oneshot_chan_cfg_t 结构体
typedef struct {
adc_atten_t atten;
adc_bitwidth_t bitwidth;
} adc_oneshot_chan_cfg_t;
atten(衰减)
用于扩展 ADC 可测量的输入电压范围:
- ADC_ATTEN_DB_0 :无衰减
- ADC_ATTEN_DB_2_5 :2.5 dB
- ADC_ATTEN_DB_6 :6 dB
- ADC_ATTEN_DB_12 :12 dB(最常用)
bitwidth(位宽)
决定 ADC 精度:
- ADC_BITWIDTH_9
- ADC_BITWIDTH_10
- ADC_BITWIDTH_11
- ADC_BITWIDTH_12(默认)
使用示例
adc_oneshot_chan_cfg_t config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(
adc1_handle,
ADC_CHANNEL_0,
&config
));
五、读取 ADC 转换结果(原始值)
ADC 配置完成后,即可读取原始采样结果:
esp_err_t adc_oneshot_read(
adc_oneshot_unit_handle_t handle,
const adc_channel_t *channel,
int32_t *out_raw
);
使用示例
static int adc_ch2_raw;
adc_oneshot_read(adc1_handle, ADC_CHANNEL_0, &adc_ch2_raw);
ESP_LOGI(TAG,
"ADC%d Channel[%d] Raw Data: %d",
ADC_UNIT_1 + 1,
ADC_CHANNEL_0,
adc_ch2_raw);
此时读到的是 ADC 原始码值,并不是真正的电压。
六、创建 ADC 校准方案
如果希望 ADC 数据具有工程意义,就必须使用校准。
ESP-IDF 提供的 ADC 校准机制,基于芯片内部 eFuse 中的出厂参数,用户无需额外烧录。
创建校准方案的 API:
esp_err_t adc_cali_create_scheme_curve_fitting(
const adc_cali_curve_fitting_config_t *config,
adc_cali_handle_t *ret_handle
);
adc_cali_curve_fitting_config_t 示例
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_12,
.chan = ADC_CHANNEL_2,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(
&cali_config,
&handle
));
注意:
校准参数必须与 ADC 实际配置完全一致,否则转换结果会不准确。
七、转换为实际电压值
完成校准后,即可将 ADC 原始值转换为真实电压(单位 mV):
esp_err_t adc_cali_raw_to_voltage(
adc_cali_handle_t handle,
int raw,
int *out_voltage
);
使用示例
int voltage;
adc_cali_raw_to_voltage(cali_handle, adc_ch2_raw, &voltage);
八、完整实战:采集烟雾传感器
硬件说明
从原理图可知,底板的 烟雾传感器的模拟输出口接在 GPIO1 上。

GPIO1 对应:
- ADC1
- ADC_CHANNEL_0

完整示例代码
"ADC%d Channel[%d] Cali Voltage: %d mV", EXAMPLE_ADC1_UNIT + 1, EXAMPLE_ADC1_CHAN0, voltage_ch0
// 初始化ADC校准
static bool example_adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit, // ADC单元编号
.chan = channel, // ADC通道编号
.atten = atten, // 衰减值
.bitwidth = ADC_BITWIDTH_DEFAULT, // 位宽
};
// 创建曲线拟合校准方案
ret = adc_cali_create_scheme_curve_fitting(&cali_config, out_handle);
if (ret == ESP_OK)
{
calibrated = true;
}
return calibrated;
}
void app_main(void)
{
//-------------ADC1 Init---------------//
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = EXAMPLE_ADC1_UNIT, // 设置ADC单元为ADC1
.ulp_mode = ADC_ULP_MODE_DISABLE, // 禁用ULP模式
};
// 初始化ADC1单元
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
//-------------ADC1 Config---------------//
adc_oneshot_chan_cfg_t config = {
.atten = EXAMPLE_ADC_ATTEN, // 设置衰减值为12dB
.bitwidth = ADC_BITWIDTH_DEFAULT, // 设置位宽为默认值
};
// 配置ADC1通道0
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));
//-------------ADC1 Calibration Init---------------//
adc_cali_handle_t adc1_cali_chan0_handle = NULL;
// 初始化ADC1通道0的校准
bool do_calibration1_chan0 = example_adc_calibration_init(EXAMPLE_ADC1_UNIT, EXAMPLE_ADC1_CHAN0, EXAMPLE_ADC_ATTEN, &adc1_cali_chan0_handle);
while (1)
{
// 读取ADC1通道0的原始数据
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw_ch0));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", EXAMPLE_ADC1_UNIT + 1, EXAMPLE_ADC1_CHAN0, adc_raw_ch0);
// 如果通道0进行了校准,将原始数据转换为电压值
if (do_calibration1_chan0)
{
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, adc_raw_ch0, &voltage_ch0));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", EXAMPLE_ADC1_UNIT + 1, EXAMPLE_ADC1_CHAN0, voltage_ch0);
}
// 延迟1秒
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
九、最终效果
烧录程序后,可以看到:
- ADC 原始值实时变化
- 电压值同步变化
![[图片]](https://i-blog.csdnimg.cn/direct/ed6e9a96ef054371b2ed7ecd3cb15645.png)
更多推荐
所有评论(0)