【ESP32】ESP-IDF开发 | ADC模数转换器+ADC连续采集例程

1. 简介

        ESP32带有2个12位分辨率的ADC,配置有5个控制器进行控制;其中2个支持高性能多通道扫描、2个支持深度睡眠模式下的低功耗运行,另外1个专门用于PWDET / PKDET(功率检测和峰值监测)。

PWDET/PKDET控制器仅供Wi-Fi内部使用。如果Wi-Fi正在使用SAR ADC2,则用户无法使用 SAR ADC2 测量管脚的模拟信号。

1.1 RTC ADC控制器

        该控制器主要在低功耗模式下使用,通过该控制器可以使设备在深度睡眠模式下进行ADC信号的采集;同时ULP可以访问该控制器,在低功耗模式下实现更高效的ADC数据采集。

1.2 DIG ADC控制器

        该控制器的功能相比上面会强大很多,具有更快的时钟频率,并且支持DMA进行数据传输。除了支持常见的单次转换外,还支持连续扫描功能;连续扫描功能支持3种通道模式——单通道扫描、双通道扫描和双通道交替扫描

        如果使用连续扫描模式,那么是默认开启DMA传输的,DMA帧的格式有两种。

ch_sel[15:12]data[11:0]
ADC通道号ADC数据(最高12位)
sar_sel[15]ch_sel[14:11]data[10:0]
ADC号ADC通道号ADC数据(最高11位)

1.3 滤波器

        从上面的框图中可以知道ADC1中带有一个硬件滤波器,但官方文档中并没有介绍该滤波器的使用方法,固件库中倒是有对应的代码实现。观察后发现这个是IIR滤波器,然后配置项只有一个系数,因为不知道这个IIR滤波器是I型、II型还是并联型,所以不好计算系数。

        如果后面官方更新了文档,我也会更新这部分内容。

2. 例程

        例程中会配置ADC实现连续扫描转换DAC的输出管脚,DAC每500毫秒改变一次管脚电平。

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_cali.h"
#include "driver/dac_oneshot.h"
#include "esp_adc/adc_cali_scheme.h"
#include "soc/soc_caps.h"

#include <string.h>

#define TAG "app"

adc_continuous_handle_t adc_handle = NULL;
adc_cali_handle_t adc_cali_handle = NULL;
dac_oneshot_handle_t dac_handle = NULL;
SemaphoreHandle_t data_ready_noti = NULL;

static bool IRAM_ATTR adc_conv_done_cb(adc_continuous_handle_t handle, const adc_continuous_evt_data_t *edata, void *user_data)
{
    BaseType_t higher_task_woken = pdFALSE;
    xSemaphoreGiveFromISR(data_ready_noti, &higher_task_woken);
    return higher_task_woken == pdTRUE;
}

static void adc_read_task(void *args)
{
    uint8_t *buf = (uint8_t *) malloc(1024);
    uint32_t len = 0;
    uint16_t last_raw = 0;

    while (1) {
        if (pdTRUE == xSemaphoreTake(data_ready_noti, portMAX_DELAY)) {
            memset(buf, 0, 1024);
            ESP_ERROR_CHECK(adc_continuous_read(adc_handle, buf, 1024, &len, 1000));
            for (int i = 0; i < len; i += SOC_ADC_DIGI_RESULT_BYTES) {
                adc_digi_output_data_t *p = (adc_digi_output_data_t*)&buf[i];
                if (abs(last_raw - p->type1.data) > 100) {  // 等到与上一次不同时才输出log
                    int voltage = 0;
                    ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle, p->type1.data, &voltage));
                    ESP_LOGI(TAG, "channel %d get raw %d voltage %d mV", p->type1.channel, p->type1.data, voltage);
                    last_raw = p->type1.data;
                }
            }
        }
        vTaskDelay(10 / portTICK_PERIOD_MS);  // 因为ADC的采集速度高于任务调度的速度,所以要定时放弃CPU占用
    }
}

int app_main()
{
    /* 初始化DAC */
    dac_oneshot_config_t dac_cfg = {
        .chan_id = DAC_CHAN_0,
    };
    ESP_ERROR_CHECK(dac_oneshot_new_channel(&dac_cfg, &dac_handle));

    /* 初始化ADC句柄 */
    adc_continuous_handle_cfg_t adc_config = {
        .max_store_buf_size = 1024,
        .conv_frame_size = 256,
    };
    ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &adc_handle));

    /* 初始化ADC连续转换 */
    adc_digi_pattern_config_t adc_pattern[1] = {
        {
            .atten = ADC_ATTEN_DB_12,  // 衰减12dB
            .channel = ADC_CHANNEL_0,  // 通道0
            .unit = ADC_UNIT_1,  // ADC1
            .bit_width = ADC_BITWIDTH_12,  // 12位分辨率
        }
    };

    adc_continuous_config_t dig_cfg = {
        .sample_freq_hz = SOC_ADC_SAMPLE_FREQ_THRES_LOW,  // 20kHz
        .conv_mode = ADC_CONV_SINGLE_UNIT_1,  // 单通道,ADC1
        .format = ADC_DIGI_OUTPUT_FORMAT_TYPE1,  // 1型ADC DMA数据
        .pattern_num = 1,  // 1个ADC通道使用
        .adc_pattern = adc_pattern,
    };

    ESP_ERROR_CHECK(adc_continuous_config(adc_handle, &dig_cfg));

    /* 注册ADC校准 */
    adc_cali_line_fitting_efuse_val_t cali_val;
    ESP_ERROR_CHECK(adc_cali_scheme_line_fitting_check_efuse(&cali_val));
    adc_cali_line_fitting_config_t cali_cfg = {
        .unit_id = ADC_UNIT_1,
        .bitwidth = ADC_BITWIDTH_12,
        .atten = ADC_ATTEN_DB_12,
        .default_vref = cali_val == ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF ? 1100 : 0,
    };
    ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_cfg, &adc_cali_handle));

    /* 注册回调 */
    data_ready_noti = xSemaphoreCreateBinary();
    xSemaphoreTake(data_ready_noti, 0);

    adc_continuous_evt_cbs_t cbs = {
        .on_conv_done = adc_conv_done_cb,
    };
    ESP_ERROR_CHECK(adc_continuous_register_event_callbacks(adc_handle, &cbs, NULL));

    /* 创建任务 */
    xTaskCreate(adc_read_task, "adc_read_task", 2048, NULL, 5, NULL);

    /* 启动ADC */
    ESP_ERROR_CHECK(adc_continuous_start(adc_handle));

    uint8_t val = 0;
    while (1) {
        ESP_ERROR_CHECK(dac_oneshot_output_voltage(dac_handle, val));
        vTaskDelay(500 / portTICK_PERIOD_MS);
        val += 10;
    }
}

1. 初始化DAC

        例程中使用DAC生成不同的电压信号,这里使用的是DAC的单次模式,配置结构体如下。

typedef struct {
    dac_channel_t chan_id;
} dac_oneshot_config_t;
  •  chan_id:DAC通道号(0-1)。

2. 初始化ADC句柄

        初始化结构体如下。

typedef struct {
    uint32_t max_store_buf_size;
    uint32_t conv_frame_size;
    struct {
        uint32_t flush_pool: 1;
    } flags;
} adc_continuous_handle_cfg_t;
  • max_store_buf_size:缓冲区大小;
  • conv_frame_size:转换帧大小,必须是SOC_ADC_DIGI_DATA_BYTES_PER_CONV的整数倍;
  • flush_pool:缓冲区满时,是否清空。 

3. 初始化ADC转换通道

        初始化结构体如下。

typedef struct {
    uint32_t pattern_num;
    adc_digi_pattern_config_t *adc_pattern;
    uint32_t sample_freq_hz;
    adc_digi_convert_mode_t conv_mode;
    adc_digi_output_format_t format;
} adc_continuous_config_t;
  • pattern_num:需要配置的通道数;
  • adc_pattern:每个通道的配置;
typedef struct {
    uint8_t atten;  // 信号衰减
    uint8_t channel;  // 通道
    uint8_t unit;  // ADC1或ADC2
    uint8_t bit_width;  // 分辨率
} adc_digi_pattern_config_t;
  • sample_freq_hz:采样率(20kHz-2MHz),单位HZ;
  • conv_mode:转换模式;
  • format:DMA数据格式。

4. 注册回调函数和数据处理任务

        每次ADC成功转换一个转换帧会产生中断,通过注册回调函数可以在中断时提示对应的任务处理数据。数据处理任务中会读取ADC的每一个转换结果,如果结果与上一次的输出不同,就打印一次log。

        这里有一个处理上的细节,因为ADC的处理速度太快了,以至于可能刚处理完,信号量又来临,导致空闲任务一直得不到执行,然后看门狗超时;所以一处理完数据就延时几毫秒,让空闲任务能运行。另外就是对比前后两次ADC数据时会有一定的余量,因为ADC的数据是不稳定的。

5. 注册ADC校准

        这个功能主要用于原始值和电压值的转换,里面同时包含了校准操作。初始化结构体中的default_verf填写的是ADC的参考电压值,这个一般是由eFuse里面的特定位决定的,但如果eFuse里面没有配置才要手动配置,默认的参考值是1.1V

6. 启动ADC转换

        最后使能ADC转换。

        DAC的通道0对应的是IO25,ADC1的通道0对应的是IO36,把两个管脚接一起。下面是程序运行的log。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马浩同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值