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

本文详细介绍了如何在ESP32上通过ADC DMA进行高精度采样,包括ADC外设配置、示例代码修改、采样率设置与验证,以及可能遇到的问题和解决方案。重点讲解了如何利用DIG控制器配合DMA以达到1MSPS的采样速率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文章已经生成可运行项目,

最近要使用ESP32进行ADC采样,需要对ESP32的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同时使用。

本文章已经生成可运行项目
<think>好的,我现在需要给用户介绍一下ESP32-S3的ADC功能。首先,我得回忆一下ESP32-S3的基本信息,尤其是ADC部分的技术规格。ESP32系列芯片的ADC功能我之前了解过,但具体到S3型号可能会有一些差异,需要确认一下。 首先,ESP32-S3是乐鑫推出的一个型号,属于ESP32系列,主要特点包括双核处理器、低功耗、Wi-Fi和蓝牙功能。ADC是模拟数字转换器,用于将模拟信号转换为数字信号,这在物联网设备中非常常见,比如读取传感器数据。 接下来,我需要明确ESP32-S3的ADC参数。比如,ADC的位数、通道数量、输入电压范围、采样速率等。之前的ESP32型号如ESP32ESP32-S2的ADC可能有所不同,所以必须确认S3的具体参数。根据乐鑫的文档,ESP32-S3的ADC应该是12位的,支持多个通道,可能有差分输入模式,但需要核实。 另外,ESP32系列的ADC可能存在线性度问题或者噪声问题,这在应用时需要注意。比如,ADC的测量结果可能不够准确,尤其是在高电压或低电压时,可能需要校准或者软件处理来改善精度。ESP32-S3是否有改进这方面的问题呢?需要查证是否有内部校准机制或者推荐的外部电路设计。 然后,ADC的通道分配也很重要。ESP32-S3有多少个ADC通道?是否和GPIO引脚复用?比如,某些GPIO引脚可以配置ADC输入,用户需要知道哪些引脚可用。可能涉及ADC1和ADC2两个控制器,但具体到S3是否有变化?例如,ESP32之前有ADC1和ADC2,但ADC2在Wi-Fi使用时可能受限,S3是否也存在类似的问题? 此外,ESP32-S3是否支持ADC的DMA传输,这对于高速采样或连续采样非常重要。DMA可以减轻CPU负担,提高效率。如果有的话,用户可能需要了解如何配置DMA来实现高效的数据采集。 软件方面,使用ESP-IDF开发时,ADC配置步骤是怎样的?是否需要设置衰减参数、位宽等?比如,设置衰减器可以调整输入电压的范围,例如默认的0-1V,通过设置不同的衰减档位,可以测量更高的电压,但需要注意不超过允许的最大电压。 另外,ESP32-S3可能引入了新的特性,比如数字滤波器或软件校准功能,这些都可能影响ADC的性能和使用方法。例如,软件过采样技术可以提高有效分辨率,或者使用平均滤波减少噪声。 用户可能关心的还有实际应用中的注意事项,比如电源噪声对ADC的影响,布局布线时的建议,如何接地以减少干扰,以及在使用Wi-Fi或蓝牙时ADC的精度是否会受影响。需要指出这些潜在的问题,并提供解决方案的建议,比如使用旁路电容、远离高频信号线等。 总结来说,回答的结构应该包括以下几个部分: 1. ESP32-S3 ADC的基本参数(位数、通道数、电压范围、采样率) 2. 硬件配置(可用GPIO、衰减设置、参考电压) 3. 软件配置(API使用、校准方法) 4. 常见问题及解决方法(噪声处理、精度提升) 5. 实际应用建议(布局、滤波方法) 需要确保信息准确,参考乐鑫的官方文档,避免错误。同时,用中文清晰表达,步骤分明,方便用户理解。可能还需要比较ESP32-S3与其他型号的ADC差异,突出其改进或特点。</think>以下是关于ESP32-S3 ADC的详细介绍,分步骤说明其关键特性和使用要点: --- ### 1. **ADC基本参数** - **分辨率**:12位(可配置为9~12位,通过软件调整)。 - **通道数量**:最多20个ADC通道(具体取决于芯片型号和引脚分配)。 - 支持单端输入和差分输入模式。 - **输入电压范围**: - 单端模式:默认0~1V(需设置衰减器扩展范围)。 - 通过设置衰减档位(0dB/2.5dB/6dB/11dB),可扩展至约0~3.3V。 - **采样速率**:最高达2MSPS(每秒百万次采样),但实际速率受配置和滤波影响。 --- ### 2. **硬件配置** - **可用GPIO引脚**: - ADC1控制器:支持GPIO1~GPIO10(部分型号可能不同)。 - ADC2控制器:部分引脚在Wi-Fi启用时受限(与ESP32类似)。 - **衰减设置**: - 通过`adc_atten_t`配置衰减档位,例如: ```c adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // 扩展电压至3.3V ``` - **参考电压**: - 内部参考电压约1.1V,但实际值可能存在偏差,建议校准。 --- ### 3. **软件配置与校准** - **ESP-IDF开发流程**: 1. 初始化ADC: ```c adc1_init(); adc1_config_width(ADC_WIDTH_BIT_12); ``` 2. 配置通道和衰减: ```c adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); ``` 3. 读取数值: ```c int raw_value = adc1_get_raw(ADC1_CHANNEL_0); ``` - **校准方法**: - 使用`esp_adc_cal`库进行两点校准,提升精度。 - 示例代码: ```c esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, &adc_chars); uint32_t voltage = esp_adc_cal_raw_to_voltage(raw_value, &adc_chars); ``` --- ### 4. **常见问题与优化** - **噪声与精度问题**: - **原因**:电源波动、Wi-Fi/BT射频干扰、PCB布局不当。 - **解决方案**: - 添加旁路电容(0.1μF)靠近ADC引脚。 - 使用软件滤波(如移动平均、中值滤波)。 - 避免高频操作时采样(例如在Wi-Fi传输间隙采集)。 - **提升有效分辨率**: - 过采样技术:通过多次采样取平均,例如16次采样可提升至14位分辨率。 --- ### 5. **实际应用建议** - **布局设计**: - ADC走线尽量短,远离高频信号线(如时钟、射频)。 - 使用独立的地平面,减少数字噪声耦合。 - **动态功耗管理**: - 在低功耗场景下,关闭未使用的ADC模块以节省能耗。 --- ### 6. **ESP32-S3的改进** - **差分输入模式**:支持更高抗噪能力,适合传感器信号采集- **DMA支持**:可配置DMA连续采样,实现高速数据流(需结合`esp_adc`库或第三方驱动)。 - **温度传感器**:内置温度传感器通道(需通过特定API访问)。 --- 通过以上配置和优化,ESP32-S3的ADC可满足大多数中精度模拟信号采集需求,如温度传感器、光照检测等。如需更高精度,建议外接专用ADC芯片。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值