这里有三个例程,相关度很高,就写在一起了。

ESP32 集成了 2 个 SAR(逐次逼近寄存器)ADC,总共支持 18 个测量通道(模拟使能引脚)。

逐次逼近ADC的原理可以看这篇博客,写的很简练明了:逐次逼近比较型adc原理_niepangu的博客-CSDN博客
基准电压的作用可以看这个:ADC参考电压有多重要? - 知乎

ADC1和ADC2的例程很相似,因此只分析前者。

ADC1

本示例说明如何配置 ADC1 并读取连接到 GPIO 引脚的电压。

代码流程

这里有一个流程图,如果没有显示,请刷新一下网页

graph TD 检查校准值是否烧录到eFuse中-->配置ADC的位宽和衰减-->创建含有ADC各项参数的结构体并初始化-->多次采样后取平均-->将读数转换为电压值

头文件

#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"//adc相关的头文件
#include "esp_adc_cal.h"//校正由芯片间ADC基准电压(Vref)变化引起的测量电压的差异

变量和宏定义

#define DEFAULT_VREF    1100        //默认基准电压,使用adc2_vref_to_gpio()获得更好的估计
#define NO_OF_SAMPLES   64          //Multisampling 多采样的次数

static esp_adc_cal_characteristics_t *adc_chars;//存储ADC相关特征的结构体

//配置ADC的通道、位宽、衰减、数量
#if CONFIG_IDF_TARGET_ESP32
static const adc_channel_t channel = ADC_CHANNEL_6;     //GPIO34 if ADC1, GPIO14 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#elif CONFIG_IDF_TARGET_ESP32S2
static const adc_channel_t channel = ADC_CHANNEL_6;     // GPIO7 if ADC1, GPIO17 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_13;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_0;
static const adc_unit_t unit = ADC_UNIT_1;

函数

检查eFuse函数

eFuse翻译为电子保险丝,可以对芯片进行动态实时重新编程。这里的作用是存储。由于本身特性的限制,eFuse的可读次数是有限的。

本函数用于从eFuse中读取芯片是否已烧录了Two Point和eFuse Vref。校准值用于生成特性曲线,以解释特定ESP32芯片的 ADC 基准电压的变化。ESP32有3个校准值来源。

  • Two Point表示ADC在150 mV和850 mV时的读数。为了获得更准确的校准结果,用户应测量这些值并将其刻录到 eFuse BLOCK3 中。
  • eFuse Vref 表示真正的 ADC 基准电压。该值在工厂校准期间测量并烧录到 eFuse BLOCK0 中。
  • 默认Vref是用户在字符化期间作为参数提供的ADC基准电压的估计值。如果两点或eFuse Vref 值不可用,将使用默认 Vref。

esp_adc_cal_check_efuse()用于检查 ADC 校准值是否已刻录到eFuse中

static void check_efuse(void)
{
#if CONFIG_IDF_TARGET_ESP32
    //Check if TP is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("eFuse Two Point: NOT supported\n");
    }
    //Check Vref is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        printf("eFuse Vref: Supported\n");
    } else {
        printf("eFuse Vref: NOT supported\n");
    }
#elif CONFIG_IDF_TARGET_ESP32S2
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("Cannot retrieve eFuse Two Point calibration values. Default calibration values will be used.\n");
    }
#else
#error "This example is configured for ESP32/ESP32S2."
#endif
}

打印特征中使用的校准值类型

static void print_char_val_type(esp_adc_cal_value_t val_type)
{
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        printf("Characterized using Two Point Value\n");
    } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        printf("Characterized using eFuse Vref\n");
    } else {
        printf("Characterized using Default Vref\n");
    }
}

主函数

void app_main(void)
{
    //Check if Two Point or Vref are burned into eFuse
    //检查校准值是否烧录到eFuse中
    check_efuse();

    //Configure ADC
    if (unit == ADC_UNIT_1) {
        adc1_config_width(width);//配置ADC1的捕获宽度
        adc1_config_channel_atten(channel, atten);//配置通道1的衰减值
    } else {
        adc2_config_channel_atten((adc2_channel_t)channel, atten);
    }

    //Characterize ADC
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));//为ADC特征结构体分配空间
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);//初始化该结构体,参数DEFAULT_VREF仅在ESP32且eFuse校准值无法使用时有效
    print_char_val_type(val_type);//打印校准值类型

    //Continuously sample ADC1
    while (1) {
        uint32_t adc_reading = 0;
        //Multisampling,按给定采样次数进行多次采样
        for (int i = 0; i < NO_OF_SAMPLES; i++) {
            if (unit == ADC_UNIT_1) {
                //从单通道中读取ADC1的读数
                adc_reading += adc1_get_raw((adc1_channel_t)channel);
            } else {
                int raw;
                //从单通道中读取ADC2的读数
                //如通过esp_wifi_start()启动Wi-Fi,则此函数将始终失败并显示ESP_ERR_TIMEOUT
                adc2_get_raw((adc2_channel_t)channel, width, &raw);
                adc_reading += raw;
            }
        }
        //计算读数的均值
        adc_reading /= NO_OF_SAMPLES;
        //Convert adc_reading to voltage in mV
        //根据ADC的特征将ADC读数转换为以mV为单位的电压
        uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars);
        printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

Single_Read

本例程包含以下内容:

  • 如何使用ADC驱动的单读取函数从GPIO引脚获取单个ADC读数
  • 如何使用ADC校准函数获得校准结果(以mV为单位)

本例程与之前部分差不多,只不过是把读取次数变为1次。

变量和宏定义

头文件与之前的部分基本相同,不再赘述。

//ADC Channels ADC通道
#if CONFIG_IDF_TARGET_ESP32
#define ADC1_EXAMPLE_CHAN0          ADC1_CHANNEL_6
#define ADC2_EXAMPLE_CHAN0          ADC2_CHANNEL_0
//通道标签
static const char *TAG_CH[2][10] = {{"ADC1_CH6"}, {"ADC2_CH0"}};
#else
#define ADC1_EXAMPLE_CHAN0          ADC1_CHANNEL_2
#define ADC2_EXAMPLE_CHAN0          ADC2_CHANNEL_0
static const char *TAG_CH[2][10] = {{"ADC1_CH2"}, {"ADC2_CH0"}};
#endif

//ADC Attenuation ADC衰减
#define ADC_EXAMPLE_ATTEN           ADC_ATTEN_DB_11

//ADC Calibration ADC校准
#if CONFIG_IDF_TARGET_ESP32
#define ADC_EXAMPLE_CALI_SCHEME     ESP_ADC_CAL_VAL_EFUSE_VREF
#elif CONFIG_IDF_TARGET_ESP32S2
#define ADC_EXAMPLE_CALI_SCHEME     ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32C3
#define ADC_EXAMPLE_CALI_SCHEME     ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32S3
#define ADC_EXAMPLE_CALI_SCHEME     ESP_ADC_CAL_VAL_EFUSE_TP_FIT
#endif

static int adc_raw[2][10];
static const char *TAG = "ADC SINGLE";

//ADC1、2的特征
static esp_adc_cal_characteristics_t adc1_chars;
static esp_adc_cal_characteristics_t adc2_chars;

检查校准值并初始化ADC特征

static bool adc_calibration_init(void)
{
    esp_err_t ret;
    bool cali_enable = false;

    //检查芯片是否支持校准值eFuse Vref
    ret = esp_adc_cal_check_efuse(ADC_EXAMPLE_CALI_SCHEME);
    if (ret == ESP_ERR_NOT_SUPPORTED) {
        ESP_LOGW(TAG, "Calibration scheme not supported, skip software calibration");
    } else if (ret == ESP_ERR_INVALID_VERSION) {
        ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
    } else if (ret == ESP_OK) {
        //初始化ADC1/2的特征
        cali_enable = true;
        esp_adc_cal_characterize(ADC_UNIT_1, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc1_chars);
        esp_adc_cal_characterize(ADC_UNIT_2, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc2_chars);
    } else {
        ESP_LOGE(TAG, "Invalid arg");
    }

    return cali_enable;
}

主函数

void app_main(void)
{
    esp_err_t ret = ESP_OK;
    uint32_t voltage = 0;
    bool cali_enable = adc_calibration_init();

    //ADC1 config
    //配置ADC1的位宽及其通道的衰减值
    ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_DEFAULT));
    ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));
    //ADC2 config
    ESP_ERROR_CHECK(adc2_config_channel_atten(ADC2_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));

    while (1) {
        //从单个通道获取ADC1读数
        adc_raw[0][0] = adc1_get_raw(ADC1_EXAMPLE_CHAN0);
        ESP_LOGI(TAG_CH[0][0], "raw  data: %d", adc_raw[0][0]);
        if (cali_enable) {
            //将读数转换为电压
            voltage = esp_adc_cal_raw_to_voltage(adc_raw[0][0], &adc1_chars);
            ESP_LOGI(TAG_CH[0][0], "cali data: %d mV", voltage);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));

        do {
            //从单个通道获取ADC2读数
            ret = adc2_get_raw(ADC2_EXAMPLE_CHAN0, ADC_WIDTH_BIT_DEFAULT, &adc_raw[1][0]);
        } while (ret == ESP_ERR_INVALID_STATE);
        ESP_ERROR_CHECK(ret);

        ESP_LOGI(TAG_CH[1][0], "raw  data: %d", adc_raw[1][0]);
        if (cali_enable) {
            //将读数转换为电压
            voltage = esp_adc_cal_raw_to_voltage(adc_raw[1][0], &adc2_chars);
            ESP_LOGI(TAG_CH[1][0], "cali data: %d mV", voltage);
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}
最后修改:2023 年 05 月 07 日
如果觉得我的文章对你有用,请随意赞赏