欢迎光临
我们一直在努力

ESP32 ESP-IDF ADC使用详解

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、 重要注意事项

  • ADC2 与 Wi-Fi:除非你的应用能保证在读取 ADC2 时 Wi-Fi 处于非活动状态(例如在 Wi-Fi 连接建立之前或之后),否则强烈建议避免使用 ADC2。如果必须使用,请务必检查 adc2_get_raw() 的返回值。
  • 校准至关重要:不进行校准,ADC 读数可能严重偏离真实值,尤其是在输入电压接近 0V 或 Vref(通常是 3.3V)时。
  • 参考电压 (Vref):校准依赖于一个相对稳定的参考电压。ESP32 的内部 Vref 可能会有变化(这也是需要校准的原因之一)。esp_adc_cal_characterize 函数会尝试从 eFuse 中读取工厂校准的 Vref 值(如果存在),这通常能提供最好的结果。如果 eFuse 中没有,则需要提供 default_vref 或使用其他校准方法(如手动提供两点校准值)。
  • 输入阻抗:ADC 输入引脚具有特定的输入阻抗。如果信号源阻抗较高,可能需要添加一个缓冲器(如运算放大器跟随器)来避免信号失真。
  • 噪声:ADC 输入可能受到噪声干扰。在实际应用中,通常需要添加硬件滤波(RC 低通滤波器)和软件滤波(如多次采样取平均)。
  • 电源噪声:确保为 ESP32 提供干净、稳定的电源。电源噪声会直接影响 ADC 精度。
  • 在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » ESP32 ESP-IDF ADC使用详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址