资讯详情

ESP32 之 ESP-IDF 教学(八)—— 模数转换器(ADC)

本文章 ,解释如何使用它 ESP-IDF 构建 ESP32 程序,发表文章

CSDN 请求进入目录 _ O x是否进入ESP32教学(基于ESP-IDF)? 确定


文章目录

    • 一、简介
      • 1、两个 ADC 通道简介:
      • 2、ADC 衰减 —— 改变 ADC 量程和精度
      • 3.减少测量误差 & ADC校准:
        • ① 降低噪声干扰
        • ② ★ADC 校准 —— ADC-Voltage 特征曲线
    • 二、使用 ADC1
    • 三、使用 ADC2
    • 四、使用 ADC 测量电压的校准转换
      • 1、获取 ADC-Voltage 特征曲线
      • 2、将ADC值转换位电压
      • 3、使用示例
    • 五、ESP32 霍尔传感器内部的使用
    • 六、ADC 示例

一、简介

ESP32集成了两个 12SAR(一次接近寄存器)adc ,支持18个测量通道。

1、两个 ADC 通道简介:

  • 支持 8 包括:GPIO32 - GPIO39(不按顺序)

  • 支持 10 包括:GPIO0, GPIO2, GPIO4, GPIO12 - GPIO15, GOIO25 - GPIO27(不按顺序)

ESP32 内置霍尔传感器 ADC1 的通道0和3(GPIO36 和 GPIO39)。

两个 ADC 的 API 包含在driver/adc.h

#include "driver/adc.h" 

部分开发板上的一些引脚会有特殊作用,可能有一些ADC2.占用引脚。 例如,在官方开发板中:

  • ESP32 DevKitC 开发板: 由于板上的自动下载电路被占用。
  • ESP-WROVER-KIT 开发板: 由于各种不同的作用,被占用。

另外,ADC2模块也被Wi-Fi当它们一起使用时,只有一个会被抢占,这意味着adc2_get_raw()直到Wi-Fi停下来,反之亦然。

从未连接到任何信号的引脚读取 ADC 值是 的。

ADC1_Channel GPIO(ESP32 与 ESP32-S2一致)
ADC1_CHANNEL_0 GPIO 36
ADC1_CHANNEL_1 GPIO 37
ADC1_CHANNEL_2 GPIO 38
ADC1_CHANNEL_3 GPIO 39
ADC1_CHANNEL_4 GPIO 32
ADC1_CHANNEL_5 GPIO 33
ADC1_CHANNEL_6 GPIO 34
ADC1_CHANNEL_7 GPIO 35
ADC2_Channel GPIO(ESP32) GPIO(ESP32-S2)
ADC2_CHANNEL_0 ESP32:GPIO 4 ESP32-S2:GPIO 11
ADC2_CHANNEL_1 ESP32:GPIO 0 ESP32-S2:GPIO 12
ADC2_CHANNEL_2 ESP32:GPIO 2 ESP32-S2:GPIO 13
ADC2_CHANNEL_3 ESP32:GPIO 15 ESP32-S2:GPIO 14
ADC2_CHANNEL_4 ESP32:GPIO 13 ESP32-S2:GPIO 15
ADC2_CHANNEL_5 ESP32:GPIO 12 ESP32-S2:GPIO 16
ADC2_CHANNEL_6 ESP32:GPIO 14 ESP32-S2:GPIO 17
ADC2_CHANNEL_7 ESP32:GPIO 27 ESP32-S2:GPIO 18
ADC2_CHANNEL_8 ESP32:GPIO 25 ESP32-S2:GPIO 19
ADC2_CHANNEL_9 ESP32:GPIO 26 ESP32-S2:GPIO 20

2、ADC 衰减 —— 改变 ADC 的量程和精度

ADC模块 能读取电压的范围(量程)有限,因此我们一般给某个 ADC 通道配置一定的衰减,使其读取更大的电压。但是,更大的量程会导致更小的精度。因此根据 ADC 的应用场景,选择适当的衰减级别十分必要。

ESP32 的每一个通道都有提供了4个级别的衰减等级,不同的衰减等级对于的量程在下表列出:注意, ,而是在某衰减等级下测量最精确的推荐测量范围

SoC 衰减级别(db) 推荐范围(mV)
ESP32 0 0 0 100 ∼ 950 100 \sim 950 100∼950
ESP32 2.5 2.5 2.5 100 ∼ 1250 100 \sim 1250 100∼1250
ESP32 6 6 6 150 ∼ 1750 150 \sim 1750 150∼1750
ESP32 11 11 11 150 ∼ 2450 150 \sim 2450 150∼2450

3、减小测量误差 & ADC校准:

① 最小化噪声干扰

ESP32 ADC 对噪声很敏感,导致 ADC 读数有很大差异。为了减少噪声,用户可以参考以下两种方法:

  • 物理层面 —— 在使用时可以在 ADC 输入口上连接一个0.1µF的电容。
  • 在单独测量模式下,多次测量取平均值可以减少部分干扰。
  • 多重采样模式也可以用来进一步减轻噪声的影响。

下图是 ADC 直接读取 和 采用以上减小误差方法进行读取的ADC值分布散点图

其中纵轴(ADC Reading)代表不同方式读取到的ADC值,横轴(Sample Number)是采样次数。

图例:

  • —— 直接测量
  • —— 连接电容进行测量
  • —— 在有电容的情况下,连续采样64个值求平均值

可见,使用方法减少噪声影响后,ESP32 ADC模块的测量值更精准,趋近于一条直线(残差更小)


② ★ADC 校准 —— ADC-Voltage 特征曲线

直接使用ADC读取到的值是不能直接计算出电压的,要经过重要一步 ——

#include "esp_adc_cal.h"

关于ADC校准的库为esp_adc_cal.h 这个库提供 API 函数用于校正基于 ADC 参考电压 ( V r e f V_{ref} Vref​) 。每个设计的ADC参考电压为 1100 m V 1100 \rm mV 1100mV。

—————————— 也叫做 ,作用是就确定被测信号的准确幅值。例如:倘若这个幅值来自供入芯片电源的电压,但是电源电压可能是不稳定的,这就会造成测得得值换算成电压时出现不准确的情况。因此,我们需要一个基准电压通过校正来获取准确的测量电压。

对于不同的参考电压,ADC 值与输入电压值(待测电压)的关系不同。关系如下图:

列出了参考电压分别在: V r e f = 1070 m V V_{ref} = 1070 \rm mV Vref​=1070mV (蓝色) V r e f = 1160 m V V_{ref} = 1160 \rm mV Vref​=1160mV(橙色) 下的 ADC 值和待测电压 Voltage 的关系。不难看出,这两个值的 很强。我们把这条拟合曲线称为 ADC 模块(在某参考电压下)的

在实际应用中,我们调用esp_adc_cal.h库提供的 API 函数去求得指定参考电压下的 ADC-Voltage 特征曲线,并利用这一条曲线去将 ADC 测量值转换为欲测量的电压Voltage。开发者可以选择自定义参考电压值,也可以利用ESP32 内部 eFuse)中储存的出厂参考电压校准值去获取这个曲线。

所以使用ESP32读取到模拟量电压值的步骤是:

  • 取样
  • 获取特征曲线(只需一次获取,多次使用)
  • 调用 API 根据特征曲线转换测量的 Voltage 值

如果想要获取ESP32内部eFuse中的参考电压,对于模组来说,可以连接电脑,使用以下命令(以 Windows 命令提示符 cmd 举例) 请将–port参数下的COMx替换成你的串口号,如COM6

> %IDF_PATH%/components/esptool_py/esptool/espefuse.py --port COMx adc_info

回显如: 有eFuse参考电压

ADC VRef calibration: 1093 mV

eFuse参考电压,以1100mV作为默认值

ADC VRef calibration: None (1100 mV nominal)

对于一个采用两点校准的芯片,消息将类似于:

ADC VRef calibration: 1149 mV
ADC readings stored in efuse BLK3:
    ADC1 Low reading  (150 mV): 306
    ADC1 High reading (850 mV): 3153
    ADC2 Low reading  (150 mV): 389
    ADC2 High reading (850 mV): 3206

除此之外,ESP32 内部的参考电压也可以手动测量,方法是将此电压输出到(路由到)某个GPIO口上,然后手动测量此GPIO口和GND接口之间的电压,就是eFuse内部的参考电压。将参考电压路由到GPIO的方法是调用API函数adc_vref_to_gpio(),参数是想要输出电压的GPIO口编号。

提示:此函数里边的 “_vref_”意思是参考电压,符号为 V r e f V_{ref} Vref​

/* 将参考电压路由到GPIO25上 */
adc_vref_to_gpio(ADC_UNIT_2, GPIO_NUM_25);

二、使用 ADC1

对于 ADC1 我们主要使用3个 API 函数:

  • adc1_config_width() —— 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。
  • adc1_config_channel_atten() —— 用于配置ADC1某个通道的衰减(衰减概念见简介2)
  • adc1_get_raw() —— 用于读取 1 次 ADC 值,类型为int,获取值通常为正,除非错误
函数1 adc1_config_width()
简介 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。
原型 esp_err_t adc1_config_width(adc_bits_width_t width_bit)
返回值 ESP_OK successESP_ERR_INVALID_ARG Parameter error
参数 width_bit ADC1 测量的位宽度
typedef enum adc_bit_width{ 
        
	ADC_WIDTH_BIT_9 = 0,
//ADC capture width is 9Bit.

	ADC_WIDTH_BIT_10 = 1,
//ADC capture width is 10Bit.

	ADC_WIDTH_BIT_11 = 2,
//ADC capture width is 11Bit.

	ADC_WIDTH_BIT_12 = 3,
//ADC capture width is 12Bit.

	ADC_WIDTH_MAX,
}adc_bit_width_t;
函数2 adc1_config_channel_atten()
简介 用于配置ADC1某个通道的衰减(衰减概念见简介2)
原型 esp_err_tadc1_config_channel_atten(adc1_channel_tchannel, adc_atten_tatten)
返回值 ESP_OK successESP_ERR_INVALID_ARG Parameter error
参数 channel欲配置的 ADC1 通道atten衰减等级,从枚举adc_atten_t里选
typedef enum adc_atten{ 
        
	ADC_ATTEN_DB_0 = 0,
//No input attenumation, ADC can measure up to approx. 800 mV.

	ADC_ATTEN_DB_2_5 = 1,
//The input voltage of ADC will be attenuated extending the range of measurement by about 2.5 dB (1.33 x)

	ADC_ATTEN_DB_6 = 2,
//The input voltage of ADC will be attenuated extending the range of measurement by about 6 dB (2 x)

	ADC_ATTEN_DB_11 = 3,
//The input voltage of ADC will be attenuated extending the range of measurement by about 11 dB (3.55 x)
	ADC_ATTEN_MAX,
}adc_atten_t;
函数3 adc1_get_raw()
简介 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。
原型 int adc1_get_raw(adc1_channel_tchannel)
返回值 -1错误其他值 ADC1获取到的测量值
参数 channel ADC1 通道

示例:使用 ADC1 单次读取 channel 0 (GPIO36),位宽 12 BIT,衰减 0 db

#include <driver/adc.h>
#include ...
	...
    adc1_config_width(ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
    int val = adc1_get_raw(ADC1_CHANNEL_0);
    ...

三、使用 ADC2

配置 ADC2 的衰减使用函数adc2_config_channel_atten(),与 ADC1 类似。

不同的是 ADC2 不像 ADC1 那样需要一次性配置通道的测量宽度,而是在测量时配置,因此 ADC2 没有单独的用于配置测量宽度的函数。同时,adc2_get_raw()函数有两个参数,返回值不是直接返回测量值,测量值需要用指针从参数中获取。

函数 adc2_get_raw()
函数原型 esp_err_tadc2_get_raw(adc2_channel_tchannel, adc_bits_width_twidth_bit, int *raw_out)
返回值 ESP_OK获取成功ESP_ERR_TIMEOUT ADC2正在其他外设如Wifi使用,获取超时。ESP_ERR_INVALID_STATE控制器状态无效。请再试一次。
参数 channel读取通道width_bit读取位宽*raw_out接受获取值的变量指针

使用示例:

#include <driver/adc.h>

...

    int read_raw;
    adc2_config_channel_atten( ADC2_CHANNEL_7, ADC_ATTEN_0db );
    esp_err_t err = adc2_get_raw( ADC2_CHANNEL_7, ADC_WIDTH_12Bit, &read_raw);
    // 判断读取是否成功
    if (err == ESP_OK) { 
        
        printf("读取到的值为:%d\n", read_raw );
    } else if ( r == ESP_ERR_TIMEOUT ) { 
        
        printf("ADC2 被占用\n");
    }

四、使用 ADC 校准转换测量电压

使用以下头文件:

#include "esp_adc_cal.h"

使用 ADC 校准转换测量电压需要两步:

  1. 获取 ADC-Voltage 特征曲线
  2. 调用 API 函数将 ADC 值转换位电压

1、获取 ADC-Voltage 特征曲线

函数 esp_adc_cal_characterize()
功能 获取ADC在指定参考电压下和衰减下的特性,并生成 ADC-Voltage 特征曲线
函数原型 esp_adc_cal_value_tesp_adc_cal_characterize(adc_unit_tadc_num, adc_atten_tatten, adc_bits_width_tbit_width, uint32_t default_vref, esp_adc_cal_characteristics_t *chars)
返回值 ESP_ADC_CAL_VAL_EFUSE_VREF使用eFuse中写入的参考电压ESP_ADC_CAL_VAL_EFUSE_TP使用两点表征值确定特征曲线ESP_ADC_CAL_VAL_DEFAULT_VREF使用默认参考电压( 1100 m V 1100 \rm mV 1100mV)
参数 adc_num欲获取的ADC编号,ADC1还是ADC2atten衰减值bit_width测量位宽default_vref参考电压(如果使用eFuse值,则此参数无效。所以一般填1100即可)*chars(输出)指向用于存储ADC特征的空结构体的指针

2、将ADC值转换位电压

函数 esp_adc_cal_raw_to_voltage()
功能 基于获取到的ADC-Voltage曲线,将ADC读数转换为电压mV
函数原型 uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, constesp_adc_cal_characteristics_t *chars)
返回值 转换到的电压值(mV)
参数 adc_reading读取到的 ADC 值*chars指向包含ADC特征的初始化结构的指针

3、使用示例

#include "esp_log.h"
...
#include "esp_adc_cal.h"
#include "driver/adc.h"
...

static esp_adc_cal_characteristics_t *adcChar;
...

	...
	adc1_config_width(ADC_WIDTH_BIT_12);
	adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);
	
	adcChar = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
	esp_adc_cal_value_t cal_mode = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adcChar);
	
    if (cal_mode == ESP_ADC_CAL_VAL_DEFAULT_VREF){ 
        
        ESP_LOGI("USER", "使用默认参考电压");
    }else if (cal_val == ESP_ADC_CAL_VAL_EFUSE_VREF){ 
        
        ESP_LOGI("USER", "使用 eFuse 中的电压");
    }

	uint32_t readRaw;
    uint32_t voltage;

    readRaw = adc1_get_raw(ADC1_CHANNEL_5);
    voltage = esp_adc_cal_raw_to_voltage(readRaw, adcChar);
	...
	
...

五、ESP32 内部霍尔传感器的使用

使用函数hall_sensor_read()读取霍尔传感器。读取到的值是 12 位宽 (范围0-4095)。因此 霍尔传感器使用前(即调用hall_sensor_read()之前)必须通过调用adc1_config_width()来将ADC1配置位12位宽。

注意:霍尔传感器采用 ADC1 的通道 0 和 3。不要将这些通道配置为ADC通道。同样,也不要将其他任何东西连接到这两个通道对应的 GPIO 上(GPIO36GPIO39),也不要对这两个IO口做任何配置。否则可能会影响传感器对低值信号的测量。

#include <driver/adc.h>

...

    adc1_config_width(ADC_WIDTH_BIT_12);
    int val = hall_sensor_read();

六、ADC 示例

将 ESP32 eFuse 中的参考电压输出到 ADC2 的 GPIO25上(对应 channel 8),然后用 ADC1 的Channel 5(对应GPIO33)去测量。每 1 秒测量一次,每次连续取样 12 个点求平均值,然后转换成电压。并输出读取到的电压和转换前的ADC值。

因此,将开发板上的 GPIO25 和 GPIO33连接起来即可

#include <iostream>
#include "driver/adc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_adc_cal.h"

static esp_adc_cal_value_t cal_val;
static esp_adc_cal_characteristics_t *adcChar;

namespace AdcTask { 
        
    static void adc_task(void *args);
}

static void AdcTask::adc_task(void *args) { 
        
    /*ADC1_channel_5 is GPIO33*/
    TickType_t tick = xTaskGetTickCount();
    uint32_t readRaws;
    uint32_t voltage;
    
    while (true){ 
        
        readRaws = 0;
        for (int i = 0; i < 12; i++){ 
        
            readRaws += adc1_get_raw(ADC1_CHANNEL_5);
        }
        readRaws /= 12;
        voltage = esp_adc_cal_raw_to_voltage(readRaws, adcChar);
        std::cout << "Read OK, voltage is: " << voltage << " mV" << std::endl;
        std::cout << "readRaws is: " << readRaws << std::endl;

        vTaskDelayUntil(&tick, pdMS_TO_TICKS(1000));
    }
}

extern "C" void app_main(void) { 
        
    esp_err_t status = adc_vref_to_gpio(ADC_UNIT_2, GPIO_NUM_25);
    if (status == ESP_OK) { 
        
        std::cout <<"v_ref routed to GPIO" << std::endl;
    } else { 
        
        std::cout <<"failed to route v_ref" << std::endl;
    }

    adc1_config_width(adc_bits_width_t::ADC_WIDTH_BIT_12);
    adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);
    adcChar = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
    cal_val = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adcChar);
    if (cal_val == ESP_ADC_CAL_VAL_DEFAULT_VREF){ 
        
        std::cout << "使用默认参考电压" << std::endl;
    }else if (cal_val == ESP_ADC_CAL_VAL_EFUSE_VREF){ 
        
        std::cout << "使用 eFuse 中的电压" << std::endl;
    }

    xTaskCreate(AdcTask::adc_task, "ADC", 1024, nullptr, 5, nullptr);
}

标签: 3ad00传感器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台