ESP32ADC采样率配置(基于ESP-IDF)

最近要使用ESP32进行ADC采样,需要对ESP32的ADC采样率进行设置,查阅后发现网上这方面的资料非常少,所以把配置过程写下来以供大家参考

文章目录

  • 一、ESP32的ADC外设
  • 二、示例代码修改
  • 三、ADC采样率获取
  • 四、采样率配置
  • 五、实验验证
  • 六、可能出现的问题

  • 一、ESP32的ADC外设

    打开ESP32的技术规格书第34页,可见ESP32具有2个12位的逐次逼近型ADC,他有RTC和DIG两个控制器,其中RTC控制器最大采样率为200KSPS,DIG控制器为2MSPS,如果我们需要采样频率较高的信号,就必须使用DIG控制器。


    打开ESP32的技术参考手册第577页找到DIG控制器,可见我们为了追求最大的采样率,应该考虑使用DMA配合ADC使用。

    二、示例代码修改

    打开ESP-IDF的官方ADC_DMA示例程序,这里我用的是VSCODE+ESP-IDF(v4.4)插件,由于我使用的是ESP32芯片,所以我将所有用于适配其他芯片部分的代码删除,删除后的示例代码如下:

    #include <string.h>
    #include <stdio.h>
    #include "sdkconfig.h"
    #include "esp_log.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/semphr.h"
    #include "driver/adc.h"
    
    #define TIMES 1000 //一次的采样点数*2
    #define GET_UNIT(x) ((x >> 3) & 0x1)
    
    #if CONFIG_IDF_TARGET_ESP32
    #define ADC_RESULT_BYTE 2
    #define ADC_CONV_LIMIT_EN 1                  // For ESP32, this should always be set to 1
    #define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 // ESP32 only supports ADC1 DMA mode
    #define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1
    #endif
    
    #if CONFIG_IDF_TARGET_ESP32
    static uint16_t adc1_chan_mask = BIT(7);
    static uint16_t adc2_chan_mask = 0;
    static adc_channel_t channel[1] = {ADC1_CHANNEL_7}; //设置ADC引脚
    #endif
    
    static uint32_t ret_num = 0;
    static uint8_t result[TIMES] = {0}; //采样结果存放数组
    
    static const char *TAG = "{adc}";
    int countNum;
    
    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,
            .conv_limit_num = 250,
            .sample_freq_hz = 100000, //采样率
            .conv_mode = ADC_CONV_MODE,
            .format = ADC_OUTPUT_TYPE,
        };
    
        adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
        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_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));
    }
    
    #if !CONFIG_IDF_TARGET_ESP32
    static 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;
    }
    #endif
    
    void app_main(void)
    {
        
    
        memset(result, 0xcc, TIMES);
    
        continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));
        adc_digi_start();
    
        
      while(1)
      {
            adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);
            for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
            {
                adc_digi_output_data_t *p = (void *)&result[i];
                // ESP_LOGI(TAG, "%d", p->type1.data);
                // printf("{value}%d\n", p->type1.data);
                // vTaskDelay(10/ portTICK_PERIOD_MS);
            }
      }
        
    }
    
    
    

    三、ADC采样率获取

    首先ADC的采样率等于单位时间采样的点数,所以需要定义一个变量为countNum,这个变量用于累计单位时间的采样点的个数,把countNum放到ADC采样部分,每完成一次ADC采样就加一,使用freertos中的任务创建函数xTaskCreate(countTask, “countTask”, 1024 * 10, NULL, 2, NULL);,在这个函数中,使用死循环,每间隔一秒打印一次countNum的值,因为这个任务与ADC采样并行运行,所以countNum即为ADC的采样率,代码如下

    
    void countTask(void *param)//创建获取采样率任务
    {
    
        while (1)
        {
    
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            printf("{count}%d\n", countNum);
            countNum = 0;
        }
    }
    
    
     adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);//ADC采样数据读取
            for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
            {
                adc_digi_output_data_t *p = (void *)&result[i];
                value[i] = p->type1.data;
    
                // ESP_LOGI(TAG, "%d", p->type1.data);
                // printf("{value}%d\n", p->type1.data);
                countNum++;//采样率计数
                // vTaskDelay(10/ portTICK_PERIOD_MS);
            }
    

    四、采样率配置

    对ESP32ADC的配置实际上就是对ADC的DIG控制器的配置,在adc.h中可以找到adc_digi_init_config_t和adc_digi_configuration_t结构体的定义,修改这些结构体中的成员就可以改变ADC的相关参数。

    typedef struct adc_digi_init_config_s {
        uint32_t max_store_buf_size;    ///< Max length of the converted data that driver can store before they are processed.
        uint32_t conv_num_each_intr;    ///< Bytes of data that can be converted in 1 interrupt.
        uint32_t adc1_chan_mask;        ///< Channel list of ADC1 to be initialized.
        uint32_t adc2_chan_mask;        ///< Channel list of ADC2 to be initialized.
    } adc_digi_init_config_t;
    
    
    typedef struct {
        bool conv_limit_en;                      ///< To limit ADC conversion times. Conversion stops after finishing `conv_limit_num` times conversion
        uint32_t conv_limit_num;                 ///< Set the upper limit of the number of ADC conversion triggers. Range: 1 ~ 255.
        uint32_t pattern_num;                    ///< Number of ADC channels that will be used
        adc_digi_pattern_config_t *adc_pattern;    ///< List of configs for each ADC channel that will be used
        uint32_t sample_freq_hz;  /*!< The expected ADC sampling frequency in Hz. Range: 611Hz ~ 83333Hz
                                       Fs = Fd / interval / 2
                                       Fs: sampling frequency;
                                       Fd: digital controller frequency, no larger than 5M for better performance
                                       interval: interval between 2 measurement trigger signal, the smallest interval should not be smaller than the ADC measurement period, the largest interval should not be larger than 4095 */
        adc_digi_convert_mode_t conv_mode;      ///< ADC DMA conversion mode, see `adc_digi_convert_mode_t`.
        adc_digi_output_format_t format;        ///< ADC DMA conversion output format, see `adc_digi_output_format_t`.
    } adc_digi_configuration_t;
    

    我在初始化代码中做出了如下修改
    (1)增大max_store_buf_size使得ADC一次可以采集更多的点数
    (2)增加TIMES的值,方便观察现象
    (3)修改sample_freq_hz,即修改采样率,最大可到达2000000hz

    adc_digi_init_config_t adc_dma_config = {
            .max_store_buf_size = 4096,//最大采样缓存
            .conv_num_each_intr = TIMES,
            .adc1_chan_mask = adc1_chan_mask,
            .adc2_chan_mask = adc2_chan_mask,
        };
    
    adc_digi_configuration_t dig_cfg = {
            .conv_limit_en = ADC_CONV_LIMIT_EN,
            .conv_limit_num = 250,
            .sample_freq_hz = 100000, //采样率
            .conv_mode = ADC_CONV_MODE,
            .format = ADC_OUTPUT_TYPE,
        };
    

    最后附上完整代码

    #include <string.h>
    #include <stdio.h>
    #include "sdkconfig.h"
    #include "esp_log.h"
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/semphr.h"
    #include "driver/adc.h"
    
    #define TIMES 2000 //一次的采样点数*2
    #define GET_UNIT(x) ((x >> 3) & 0x1)
    
    #if CONFIG_IDF_TARGET_ESP32
    #define ADC_RESULT_BYTE 2
    #define ADC_CONV_LIMIT_EN 1                  // For ESP32, this should always be set to 1
    #define ADC_CONV_MODE ADC_CONV_SINGLE_UNIT_1 // ESP32 only supports ADC1 DMA mode
    #define ADC_OUTPUT_TYPE ADC_DIGI_OUTPUT_FORMAT_TYPE1
    #endif
    
    #if CONFIG_IDF_TARGET_ESP32
    static uint16_t adc1_chan_mask = BIT(7);
    static uint16_t adc2_chan_mask = 0;
    static adc_channel_t channel[1] = {ADC1_CHANNEL_7}; //设置ADC引脚
    #endif
    
    static uint32_t ret_num = 0;
    static uint8_t result[TIMES] = {0}; //采样结果存放数组
    static uint16_t value[2000] = {0};
    
    static const char *TAG = "{adc}";
    int countNum;
    
    void countTask(void *param)
    {
    
        while (1)
        {
    
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            printf("{count}%d\n", countNum);
            // printf("{value}%d\n",p->type1.data);
            countNum = 0;
        }
    }
    
    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 = 4096,//最大采样缓存
            .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,
            .conv_limit_num = 250,
            .sample_freq_hz = 100000, //采样率
            .conv_mode = ADC_CONV_MODE,
            .format = ADC_OUTPUT_TYPE,
        };
    
        adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};
        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_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));
    }
    
    #if !CONFIG_IDF_TARGET_ESP32
    static 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;
    }
    #endif
    
    void app_main(void)
    {
        xTaskCreate(countTask, "countTask", 1024 * 10, NULL, 2, NULL); //创建任务
    
        memset(result, 0xcc, TIMES);
    
        continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));
        adc_digi_start();
    
        
      
            adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);
            for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
            {
                adc_digi_output_data_t *p = (void *)&result[i];
                value[i] = p->type1.data;
    
                // ESP_LOGI(TAG, "%d", p->type1.data);
                // printf("{value}%d\n", p->type1.data);
                countNum++;
                // vTaskDelay(10/ portTICK_PERIOD_MS);
            }
            for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
            {
                printf("{value}%d\n", value[i]);
            }
        
    }
    
    

    五、实验验证

    首先将采样率配置为1000000hz,即sample_freq_hz = 100000,将esp32与信号发生器连接,信号发生器设置频率为1kHz,一次采样点数为1000,按下复位键开始一次采样1000个点,存入数组中,接着将数组中的值输出至串口绘图软件观察

    可见esp32采样到了10个波形,这说明adc的采样率已经配置为了100000hz

    六、可能出现的问题

    (1)不能将串口输出与ADC采集放在同一个循环中,否则ADC采样率会被串口发送程序拉低,必须独立开。
    (2)对于ESP32来说,最好先关闭看门狗,防止ADC读取数据时重启。
    (3)数组建立时前面必须加上static,否则单片机内存会爆炸。
    (4)rtc控制器与dig控制器最大的区别在于采样率,dig可以配合DMA达到非常高的采样率,但是无法将采样率配置的很低,rtc控制器则相反,可以根据需要使用不同的控制器。
    (5)如果将sample_freq_hz = 200000,会发现countNum并不是200000,这是正常现象,因为freertos的多任务系统会降低采样率,但得益于ESP32的240MHz主频,影响一般不大。
    (6)ADC2无法和WiFi同时使用。

    物联沃分享整理
    物联沃-IOTWORD物联网 » ESP32ADC采样率配置(基于ESP-IDF)

    发表回复