深入浅出:ESP32 ADC学习笔记(4)

文章目录

  • 前言
  • 一、ESP32 ADC相关介绍
  • 二、使用步骤
  • 1.接口函数介绍
  • 2.代码示例
  • 总结

  • 前言

    ADC即模拟数字转换器(Analog-to-digital converter)是用于将模拟形式的连续信号转换数字形式的离散信号的一类设备。一个模拟数字转换器可以提供信号用于测量。与之相对的设备成为数字模拟转换器。
    例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。那就可以用到ADC了


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、ESP32 ADC相关介绍

    一些 ADC2 引脚用作捆绑引脚(GPIO 0、2、15),因此不能自由使用。
    ESP32 DevKitC:由于外部自动编程电路,GPIO 0 无法使用。
    ESP-WROVER-KIT:GPIO 0、2、4 和 15 不能使用,因为用于不同目的的外部连接。
    由于 ADC2 模块也被 Wi-Fi 使用,因此它们一起使用时只有一个可以抢占,这意味着adc2_get_raw()可能会被阻塞直到 Wi-Fi 停止

    ESP32 集成了 2 个 SAR(逐次逼近寄存器)ADC,总共支持 18 个测量通道(模拟使能引脚)。
    ADC1,8通道:GPIO32 – GPIO39
    ADC2,10个通道:GPIO0、GPIO2、GPIO4、GPIO12 – GPIO15、GOIO25 – GPIO27
    以下介绍通过ADC1进行介绍
    2、ADC衰减
    Vref 是 ESP32 ADC 内部用于测量输入电压的参考电压。ESP32 ADC 可以测量从 0 V 到 Vref 的模拟电压。在不同的芯片中,Vref 不同,中位数为 1.1 V。为了转换大于 Vref 的电压,可以在输入 ADC 之前对输入电压进行衰减。有 4 种可用的衰减选项,衰减越高,可测量的输入电压就越高。

    3、ADC 转换
    ADC 转换是将输入模拟电压转换为数字值。ADC 驱动程序 API (adc1_get_raw())提供的 ADC 转换结果是原始数据。Single Read 模式下 ESP32 ADC 原始结果的分辨率为 12 位。
    如果根据ADC采集的原始数据来计算电压那可以用Vout(输出电压)=Dout(输出的数据)*Vmax(最大测量电压)/Dmax(最大输出数据)
    ps:如果是带有ADC校准位的板子可以直接调用esp_adc_cal_raw_to_voltage()来直接读取输出电压,单位为mv
    4、ADC 单次读取
    在配置好位宽adc1_config_width()和衰减adc1_config_channel_atten()之后就可以直接调用adc1_get_raw(),读取结果了;值得说明的是官方也提供给了从ULP直接读取ADC1通道的结果,但是这个要在配置的时候调用adc1_ulp_enable()来使能ULP

    二、使用步骤

    1.接口函数介绍

    (1)设置通道衰减

    esp_err_t adc1_config_channel_atten(adc1_channel_t channel,adc_atten_t atten )
    

    第一个参数是设置ADC通道,第二个参数是设置衰减等级;默认衰减是0dB;通过官方给的衰减设置可以看到,衰减设置的越大,那么可采集的电压范围就越大(通过对输入电压进行衰减)
    返回值:
    ESP_OK 成功
    ESP_ERR_INVALID_ARG 参数错误


    (2)配置ADC的捕获位宽

    esp_err_t adc1_config_width( adc_bits_width_t width_bit )
    

    参数:ADC1 的位捕获宽度
    返回值:
    ESP_OK 成功
    ESP_ERR_INVALID_ARG 参数错误

    (3)读取单个通道上的ADC数据

    int adc1_get_raw( adc1_channel_t channel)
    

    返回
    -1:参数错误
    其他:ADC1 通道读数。
    参数
    channel: ADC1 通道读取

    (4)初始化数字ADC

    esp_err_t adc_digi_initialize(const adc_digi_init_config_t *init_config )
    

    参数:数字ADC的初始化配置adc_digi_init_config_t 在这个结构体里面;
    返回:

  • ESP_ERR_INVALID_ARG 参数错误
  • ESP_ERR_NOT_FOUND 没有找到中断空闲
  • ESP_ERR_NO_MEM 内存不足
  • ESP_OK 成功

    结构体中参数依次是
    1)转换后的数据可以存储的最大长度。
    2)在一个中断中可以转换的数据字节数
    3)待初始化的ADC1通道列表。
    4)需要初始化的ADC2通道列表。
  • (5)通过 DMA 从数字 ADC 读取字节。

    esp_err_t adc_digi_read_bytes(uint8_t *buf, uint32_t length_max, uint32_t *out_length, uint32_t timeout_ms);
    

    [out] buf:从 ADC 读取的缓冲区。
    [in] length_max:从 ADC 读取的预期数据长度。
    [out] out_length:从 ADC 读取的数据的实际长度。
    [in] timeout_ms:等待数据的时间,以毫秒为单位。
    返回
    ESP_ERR_INVALID_STATE 驱动状态无效。通常这意味着 ADC 采样率快于任务处理率。
    ESP_ERR_TIMEOUT 操作超时
    ESP_OK 成功
    (6)启动数字ADC和 DMA

    esp_err_t adc_digi_start(void);
    

    返回
    ESP_ERR_INVALID_STATE 驱动状态无效。
    ESP_OK 成功
    (6)设置数字控制器

    esp_err_t adc_digi_controller_configure(const adc_digi_configuration_t *config);
    

    返回值:
    ESP_ERR_INVALID_STATE 驱动状态无效。
    ESP_ERR_INVALID_ARG 如果参数组合无效。
    ESP_OK 成功
    参数为adc_digi_configuration_t 结构体

    结构体中参数依次是
    1)限制ADC转换的时间,转换完成后是否停止
    2)限制ADC转换触发器上限1-255
    3)ADC通道数
    4)初始化结构体配置参数
    5)采样频率611Hz ~ 83333Hz
    6)ADC DMA传输模式:选择ADC1、ADC2、ADC1与ADC2、ADC1与ADC2轮流
    7)ADC DMA传输转换格式:12BIT转换/11BIT转换

    对于设置的输出格式来说如果设置输出12BIT的数据,那么输出的16位中存放格式是[15:12] 通道,[11:0]12 位 ADC 数据 。注意:对于单次转换模式。
    如果设置的数是11BIT的数据那么存放内容就是[15]adc 单元,[14:11]通道,[10:0]11 位 ADC 数据 。注意:对于多或交替转换模式。

    (7)检查 ADC 校准值是否烧入 eFuse

    esp_err_t esp_adc_cal_check_efuse( esp_adc_cal_value_t value_type )
    

    检查 ADC 参考电压或两点值是否已烧到当前 ESP32 的 eFuse
    返回:
    ESP_OK:eFuse 支持校准模式
    ESP_ERR_NOT_SUPPORTED:错误,eFuse 值未烧入
    ESP_ERR_INVALID_ARG:错误,无效参数(ESP_ADC_CAL_VAL_DEFAULT_VREF)
    参数:
    value_type:校准值的类型(ESP_ADC_CAL_VAL_EFUSE_VREF 或 ESP_ADC_CAL_VAL_EFUSE_TP)
    (8)以特定衰减表征 ADC,
    esp_adc_cal_value_t esp_adc_cal_characterize( adc_unit_t adc_num , adc_atten_t atten , adc_bits_width_t bit_width , uint32_t default_vref , esp_adc_cal_characteristics_t * chars )
    返回:
    ESP_ADC_CAL_VAL_EFUSE_VREF:用于表征的 eFuse Vref
    ESP_ADC_CAL_VAL_EFUSE_TP:用于表征的两点值(仅在线性模式下)
    ESP_ADC_CAL_VAL_DEFAULT_VREF:用于表征的默认 Vref
    参数:
    [in] adc_num:ADC_UNIT_1 或 ADC_UNIT_2
    [in] atten: 衰减
    [in] bit_width: ADC的位宽配置
    [in] default_vref:默认 ADC 参考电压,单位 mV
    [out] chars: 指向用于存储 ADC 特征的空结构的指针
    (9)将 ADC 读数转换为以 mV 为单位的电压。
    uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars);
    在调用此函数之前必须初始化特征结构(调用 esp_adc_cal_characterize())
    返回:
    电压 (mV)
    参数:
    [in] adc_reading: ADC 读数
    [in] chars: 指向包含 ADC 特征的初始化结构的指针
    (10)直接获取通道以 mV 为单位的电压。
    esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage);
    这个函数同(9)函数一样都是得到电压,但是(9)传参是ADC的读数,本函数传参是ADC通道
    参数
    [in] channel:要读取的 ADC 通道
    [in] chars: 指向已初始化 ADC 特征结构的指针
    [out] voltage:存储转换电压的指针
    返回
    ESP_OK:ADC 读取并转换为 mV
    ESP_ERR_INVALID_ARG:由于参数无效导致的错误
    ESP_ERR_INVALID_STATE:读取结果无效。尝试再次阅读。

    2.代码示例

    ADC+DMA
    my_adc.c

    #include "my_adc.h"
    
    #if CONFIG_IDF_TARGET_ESP32S2
    static uint16_t adc1_chan_mask = BIT(0);
    static uint16_t adc2_chan_mask = 0;
    static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
    #endif
    #if CONFIG_IDF_TARGET_ESP32
    static uint16_t adc1_chan_mask = BIT(0);//
    static uint16_t adc2_chan_mask = 0;
    static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
    #endif
    
    
    static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)
    {
        adc_digi_init_config_t adc_dma_config = {
            .max_store_buf_size = 1024,//可以传输的字节大小
            .conv_num_each_intr = TIMES,//一個中斷可以转换的字節數
            .adc1_chan_mask = adc1_chan_mask,//待初始化的通道列表
            .adc2_chan_mask = adc2_chan_mask,
        };
        ESP_ERROR_CHECK(adc_digi_initialize(&adc_dma_config));
    
        adc_digi_configuration_t dig_cfg = {
            .conv_limit_en = ADC_CONV_LIMIT_EN,//限制ADC转换的次数,在转换conv_limit_num之后就会停止,选择是否使能
            .conv_limit_num = 250,//设置ADC触发器的上限
            .sample_freq_hz = 10 * 1000,//ADC采样频率
            .conv_mode = ADC_CONV_MODE,//选择DMA转换模式,选择使用哪个ADC或者都使用
            .format = ADC_OUTPUT_TYPE,//设置数据输出格式
        };
    
        adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};//将使用ADC通道的配置列表
        dig_cfg.pattern_num = channel_num;
        for (int i = 0; i < channel_num; i++) {
            uint8_t unit = GET_UNIT(channel[i]);
            uint8_t ch = channel[i] & 0x7;//ADC通道,总共0-7八个通道,只看后三位
            adc_pattern[i].atten = ADC_ATTEN_DB_11;//衰减
            adc_pattern[i].channel = ch;//通道
            adc_pattern[i].unit = unit;//索引
            adc_pattern[i].bit_width = SOC_ADC_DIGI_MAX_BITWIDTH;//设置位宽为最大宽度
    
            ESP_LOGI(TAG, "adc_pattern[%d].atten is :%x", i, adc_pattern[i].atten);
            ESP_LOGI(TAG, "adc_pattern[%d].channel is :%x", i, adc_pattern[i].channel);
            ESP_LOGI(TAG, "adc_pattern[%d].unit is :%x", i, adc_pattern[i].unit);
        }
        dig_cfg.adc_pattern = adc_pattern;
        ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg));
    }
    
    
    bool check_valid_data(const adc_digi_output_data_t *data)//判断数据是不是有效的
    {
        const unsigned int unit = data->type2.unit;
        if (unit > 2) {
            return false;
        }
        if (data->type2.channel >= SOC_ADC_CHANNEL_NUM(unit)) {
            return false;//判断通道数是不是配置多了
        }
        
        return true;
    }
    
    void my_adc_dma_init(void)
    {
        continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
    }
    

    main.c

    #include "my_adc.h"
    
    // #define LEDC_MAX_DUTY   (8191)
    // #define LEDC_FADE_TIME  (1000)
    void app_main(void)
    {
        esp_err_t ret;
        uint32_t ret_num = 0;
        uint8_t result[TIMES] = {0};
        memset(result, 0xcc, TIMES);//把数组result里面的times个值都初始化成0xcc=204;
        // continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
        my_adc_dma_init();
        adc_digi_start();//启动转换
        while (1)
        {
            ret = adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);//读取转换数据,数据存在result里面,预期是256个,实际是ret_num个。转换超时时间限制是最大
            //判断adc_digi_read_bytes返回的参数,成功、驱动设备未安装(残阳速率大于任务处理速率)、超时
            if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE) 
            {
                if (ret == ESP_ERR_INVALID_STATE) 
                {
                    /**
                     * 对于普通的只打印任务处理速率还是很快的
                     * 
                     * @note 1
                     * Issue:
                     * As an example, we simply print the result out, which is super slow. Therefore the conversion is too
                     * fast for the task to handle. In this condition, some conversion results lost.
                     *
                     * Reason:
                     * When this error occurs, you will usually see the task watchdog timeout issue also.
                     * Because the conversion is too fast, whereas the task calling `adc_digi_read_bytes` is slow.
                     * So `adc_digi_read_bytes` will hardly block. Therefore Idle Task hardly has chance to run. In this
                     * example, we add a `vTaskDelay(1)` below, to prevent the task watchdog timeout.
                     *
                     * Solution:
                     * Either decrease the conversion speed, or increase the frequency you call `adc_digi_read_bytes`
                     */
                    printf("ERROR:请降低转换速度,或者增加调用adc_digi_read_bytes的频率\n" );
                }
                for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE) 
                {
                    adc_digi_output_data_t *p = (void*)&result[i]; //把result中的数据传到adc_digi_output_data_t中,(void*)就是可以把数据转换成任意类型的数据
        #if CONFIG_IDF_TARGET_ESP32
                    ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 1, p->type1.channel, p->type1.data);//如果选择的是ESP32那么输出使用的哪个ADC的哪个通道以及那种类型的数据
        #else
                    if (ADC_CONV_MODE == ADC_CONV_BOTH_UNIT || ADC_CONV_MODE == ADC_CONV_ALTER_UNIT) //判断ADC转换模式时ADC1、2,或者是轮流转换
                    {
                        //判断数据是否有效
                        if (check_valid_data(p))
                        {
                            ESP_LOGI(TAG, "Unit: %d,_Channel: %d, Value: %x", p->type2.unit+1, p->type2.channel, p->type2.data);//输出有效数据
                        } else 
                        {
                            // abort();
                            ESP_LOGI(TAG, "Invalid data [%d_%d_%x]", p->type2.unit+1, p->type2.channel, p->type2.data);//输出数据无效
                        }
                    }
        #if CONFIG_IDF_TARGET_ESP32S2
                    else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_2) 
                    {
                        ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 2, p->type1.channel, p->type1.data);
                    } else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_1) 
                    {
                        ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %d", 1, p->type1.channel, p->type1.data);
                    }
        #endif  //#if CONFIG_IDF_TARGET_ESP32S2
        #endif
                }
                //See `note 1`
                vTaskDelay(1);//程序运行太快,防止任务看门狗超时
            } else if (ret == ESP_ERR_TIMEOUT) {
                /**
                 * ``ESP_ERR_TIMEOUT``: If ADC conversion is not finished until Timeout, you'll get this return error.
                 * Here we set Timeout ``portMAX_DELAY``, so you'll never reach this branch.
                 */
                ESP_LOGW(TAG, "No data, increase timeout or reduce conv_num_each_intr");
                vTaskDelay(1000);
            }
        }  
        adc_digi_stop();
        ret = adc_digi_deinitialize();
        assert(ret == ESP_OK);
    }
    
    
    

    总结

    完整代码工程在这里:代码

    物联沃分享整理
    物联沃-IOTWORD物联网 » 深入浅出:ESP32 ADC学习笔记(4)

    发表评论