ESP32S2 ADC

我会详细的解释各个模块的作用,如果只需要代码,则直接看完整代码章节。

ESP32S2 ADC简介

ESP32S2的ADC有两个控制器,一个是RTC控制器,一个是DIG控制器。官方的adc1_get_raw是基于RTC控制器,是已经设置过的最快的采集速度。由于RTC模式下的最大采样速度只有200kHZ,正常情况下是能满足需求的,但是如果需要更快的速度,则需要使用DIG控制器,并且使用DMA进行传输。DIG控制器可达到2MHZ。ADC特性

RTC控制器

RTC控制器下的初始化就比较简单了,只需要使用两个函数就能初始化完成。

adc1_config_width(ADC_WIDTH_BIT_13);//RTC控制器下只支持13位
adc1_config_channel_atten(ADC1_CHANNEL_8, ADC_ATTEN_DB_11); //最大量程为2.6V

RTC控制下的取值也很简单

value = (adc1_get_raw(ADC1_CHANNEL_8) & 0x1FFF);//取末尾13位数据

DIG控制器

TEST_CASE测试

DIG控制器下工作就比较繁杂了,因为esp-idf-4.2版本并没有给出该模式下的API,因此需要自己进行移植。根据TEST_CASE进行移植。
ADC_DMA的TEST_CASE使用

#编译driver的test_case ,烧录并进行监控
idf.py -T "driver" build && idf.py flash && idf.py monitor

监控模式下按下 98+Enter就是ADC的测试输出结果。
监控例程源码在components/driver/test/adc_dma_test目录下。记得查看文件名带s2的文件。

移植

ADC的初始化代码

int adc_dig_bsp_init()
{
    adc_digi_config_t config = {
        .conv_limit_en = false,
        .conv_limit_num = 0,
        /** Sample rate = APB_CLK(80 MHz) / (dig_clk.div_num+ 1) / TRIGGER_INTERVAL / 2. */
        .interval = 40,
        .dig_clk.use_apll = 0,  // APB clk
        .dig_clk.div_num = 9,
        .dig_clk.div_b = 0,
        .dig_clk.div_a = 0,
        .dma_eof_num = SAR_SIMPLE_NUM*2,//采样的数目*2
    };
    /**
    所谓的样式表就是ADC的采集列表。设置样式表数组的话,就是根据数组依次采集,然后依次放入buf中,比如你创建了样式表有通道1和通道2两个元素,则buf[0]存放通道1的值,buf[1]存放通道2的值,buf[2]再存放通道1的值,依次循环。我只用了一个通道因此长度位1,当然样式表可以先放两个通道2再放一个通道1的话,buf[0],buf[1]是通道2,buf[2]是通道1,再循环。
    总的来说,这个样式表对于多通道无顺序采集还是挺方便的
    */
    //设置样式表,可数组
    adc_digi_pattern_table_t adc1_patt = {0};
    //样式表长度
    config.adc1_pattern_len = 1;
    //样式表地址配置赋值
    config.adc1_pattern = &adc1_patt;
    //样式表的量程
    adc1_patt.atten = ADC_ATTEN_11db;
    //样式表的通道
    adc1_patt.channel = ADC1_CHANNEL_8;  
    //该通道的引脚初始化
    adc_gpio_init(ADC_UNIT_1,ADC1_CHANNEL_8 );
    //转换模式,单次,见下面的转换模式图
    config.conv_mode = ADC_CONV_SINGLE_UNIT_1;
    //DMA模式下使用数据格式1因此为12为的ADC
    config.format = ADC_DIGI_FORMAT_12BIT;
    //配置的初始化
    adc_digi_controller_config(&config);
    //中断队列
    if (que_adc == NULL) {
        que_adc = xQueueCreate(5, sizeof(adc_dma_event_t));
    } else {
        xQueueReset(que_adc);
    }
    //DMA的中断函数寄存器等
    uint32_t int_mask = SPI_IN_SUC_EOF_INT_ENA;
    uint32_t dma_addr = adc_dma_linker_init();
    adc_dac_dma_isr_register(adc_dma_isr, NULL, int_mask); 
    return 0;
}

模式解释以及数据格式图

DMA

很明显,上面有的函数,是没有定义的,需要自己写。

 uint32_t adc_dma_linker_init(void)
{
    dma1 = (lldesc_t) {
        .size = SAR_SIMPLE_NUM*2*2,  //DMA采样数据的两倍
        .owner = 1,//我也不知道,写1就对了,哈哈哈哈,如果你知道,可以评论告诉我
        .buf = &link_buf[0],//DMA的buf,需是8位,但是最后采集的数据需要转成16位
        .qe.stqe_next = NULL,//如果循环采样的话,该值为&dma1 
    };
    return (uint32_t)&dma1;
}

中断函数注册

typedef struct adc_dac_dma_isr_handler_ {
    uint32_t mask;
    intr_handler_t handler;
    void* handler_arg;
    SLIST_ENTRY(adc_dac_dma_isr_handler_) next;
} adc_dac_dma_isr_handler_t;

static SLIST_HEAD(adc_dac_dma_isr_handler_list_, adc_dac_dma_isr_handler_) s_adc_dac_dma_isr_handler_list =
        SLIST_HEAD_INITIALIZER(s_adc_dac_dma_isr_handler_list);
portMUX_TYPE s_isr_handler_list_lock = portMUX_INITIALIZER_UNLOCKED;
static intr_handle_t s_adc_dac_dma_isr_handle;

static IRAM_ATTR void adc_dac_dma_isr_default(void* arg)
{
    uint32_t status = REG_READ(SPI_DMA_INT_ST_REG(3));
    adc_dac_dma_isr_handler_t* it;
    portENTER_CRITICAL_ISR(&s_isr_handler_list_lock);
    SLIST_FOREACH(it, &s_adc_dac_dma_isr_handler_list, next) {
        if (it->mask & status) {
            portEXIT_CRITICAL_ISR(&s_isr_handler_list_lock);
            (*it->handler)(it->handler_arg);
            portENTER_CRITICAL_ISR(&s_isr_handler_list_lock);
        }
    }
    portEXIT_CRITICAL_ISR(&s_isr_handler_list_lock);
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), status);
}


static esp_err_t adc_dac_dma_isr_ensure_installed(void)
{
    esp_err_t err = ESP_OK;
    portENTER_CRITICAL(&s_isr_handler_list_lock);
    if (s_adc_dac_dma_isr_handle) {
        goto out;
    }
    REG_WRITE(SPI_DMA_INT_ENA_REG(3), 0);
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), UINT32_MAX);
    err = esp_intr_alloc(ETS_SPI3_DMA_INTR_SOURCE, 0, &adc_dac_dma_isr_default, NULL, &s_adc_dac_dma_isr_handle);
    if (err != ESP_OK) {
        goto out;
    }

out:
    portEXIT_CRITICAL(&s_isr_handler_list_lock);
    return err;
}

esp_err_t adc_dac_dma_isr_register(intr_handler_t handler, void* handler_arg, uint32_t intr_mask)
{
    esp_err_t err = adc_dac_dma_isr_ensure_installed();//确认是否安装,该函数也需
    if (err != ESP_OK) {
        return err;
    }

    adc_dac_dma_isr_handler_t* item = malloc(sizeof(*item));
    if (item == NULL) {
        return ESP_ERR_NO_MEM;
    }
    item->handler = handler;
    item->handler_arg = handler_arg;
    item->mask = intr_mask;
    portENTER_CRITICAL(&s_isr_handler_list_lock);
    SLIST_INSERT_HEAD(&s_adc_dac_dma_isr_handler_list, item, next);
    portEXIT_CRITICAL(&s_isr_handler_list_lock);
    return ESP_OK;
}

关于SPI3的问题,因为SPI3和ADC是公用的DMA,因此不能同时进行,所以再使用的时候,需要关闭SPI3相关寄存器。也需要包含SPI的一些头文件
DMA

数据处理

DMA的中断处理

/** ADC-DMA ISR handler. */
static IRAM_ATTR void adc_dma_isr(void *arg)
{
    uint32_t int_st = REG_READ(SPI_DMA_INT_ST_REG(3));
    int task_awoken = pdFALSE;
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), int_st);
    if (int_st & SPI_IN_SUC_EOF_INT_ST_M) {
        adc_evt.int_msk = int_st;
        xQueueSendFromISR(que_adc, &adc_evt, &task_awoken);
    }
    if (int_st & SPI_IN_DONE_INT_ST) {//完成DMA采集
        adc_evt.int_msk = int_st;
        xQueueSendFromISR(que_adc, &adc_evt, &task_awoken);
    }

    if (task_awoken == pdTRUE) {
        portYIELD_FROM_ISR();
    }
}

中断完成之后的数据处理

esp_err_t adc_get_value(uint16_t *buf,const int num)
{
    adc_dma_event_t evt;
	//链接DMA采样
    adc_dac_dma_linker_start((void *)&dma1, SPI_IN_SUC_EOF_INT_ENA);
    //开始采样
    adc_digi_start();
     while (1) {//等待采样完成,此处有bug,DMA不完成则会一直卡在这里,但是我当前还没有进行修改。
        xQueueReceive(que_adc, &evt, 10 / portTICK_RATE_MS);
        if (evt.int_msk & SPI_IN_SUC_EOF_INT_ENA) {
            break;
        }
    }
    //停止采样
    adc_digi_stop();
    //指针转换,因为DMA的buf只能为8位,但是根据数据格式1,采集到的数据是16为数据,因此需要进行转换
    uint16_t *buf1 = (uint16_t *)link_buf;
    for(int i=0;i<num;i++){
    	//DMA数据格式1,忽略通道,直接进行取值。
        buf[i] = (buf1[i] & 0xFFF);
    }
    return ESP_OK;
}

完整代码

C文件

#include "esp_system.h"
#include "esp_intr_alloc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/adc.h"
#include "driver/dac.h"
#include "driver/rtc_io.h"
#include "driver/gpio.h"
#include "unity.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "adc.h"
#include "soc/adc_periph.h"

#include "soc/system_reg.h"
#include "soc/lldesc.h"
#include "adc.h"

#include "sysCfg.h"
#include "esp_log.h"




static const char *TAG = "adc";
#define debug_i(format,...)			ESP_LOGI(TAG,format,##__VA_ARGS__)
#define debug_w(format,...)			ESP_LOGW(TAG,format,##__VA_ARGS__)
#define debug_e(format,...)			ESP_LOGE(TAG,format,##__VA_ARGS__)


uint8_t link_buf[SAR_SIMPLE_NUM*2*2] = {0};
static lldesc_t dma1 = {0};
static QueueHandle_t que_adc = NULL;
static adc_dma_event_t adc_evt;

/** ADC-DMA ISR handler. */
static IRAM_ATTR void adc_dma_isr(void *arg)
{
    uint32_t int_st = REG_READ(SPI_DMA_INT_ST_REG(3));
    int task_awoken = pdFALSE;
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), int_st);
    if (int_st & SPI_IN_SUC_EOF_INT_ST_M) {
        adc_evt.int_msk = int_st;
        xQueueSendFromISR(que_adc, &adc_evt, &task_awoken);
    }
    if (int_st & SPI_IN_DONE_INT_ST) {
        adc_evt.int_msk = int_st;
        xQueueSendFromISR(que_adc, &adc_evt, &task_awoken);
    }

    if (task_awoken == pdTRUE) {
        portYIELD_FROM_ISR();
    }
}

 uint32_t adc_dma_linker_init(void)
{
    dma1 = (lldesc_t) {
        .size = SAR_SIMPLE_NUM*2*2,  
        .owner = 1,
        .buf = &link_buf[0],
        .qe.stqe_next = NULL,
    };
    return (uint32_t)&dma1;
}

typedef struct adc_dac_dma_isr_handler_ {
    uint32_t mask;
    intr_handler_t handler;
    void* handler_arg;
    SLIST_ENTRY(adc_dac_dma_isr_handler_) next;
} adc_dac_dma_isr_handler_t;

static SLIST_HEAD(adc_dac_dma_isr_handler_list_, adc_dac_dma_isr_handler_) s_adc_dac_dma_isr_handler_list =
        SLIST_HEAD_INITIALIZER(s_adc_dac_dma_isr_handler_list);
portMUX_TYPE s_isr_handler_list_lock = portMUX_INITIALIZER_UNLOCKED;
static intr_handle_t s_adc_dac_dma_isr_handle;

static IRAM_ATTR void adc_dac_dma_isr_default(void* arg)
{
    uint32_t status = REG_READ(SPI_DMA_INT_ST_REG(3));
    adc_dac_dma_isr_handler_t* it;
    portENTER_CRITICAL_ISR(&s_isr_handler_list_lock);
    SLIST_FOREACH(it, &s_adc_dac_dma_isr_handler_list, next) {
        if (it->mask & status) {
            portEXIT_CRITICAL_ISR(&s_isr_handler_list_lock);
            (*it->handler)(it->handler_arg);
            portENTER_CRITICAL_ISR(&s_isr_handler_list_lock);
        }
    }
    portEXIT_CRITICAL_ISR(&s_isr_handler_list_lock);
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), status);
}

static esp_err_t adc_dac_dma_isr_ensure_installed(void)
{
    esp_err_t err = ESP_OK;
    portENTER_CRITICAL(&s_isr_handler_list_lock);
    if (s_adc_dac_dma_isr_handle) {
        goto out;
    }
    REG_WRITE(SPI_DMA_INT_ENA_REG(3), 0);
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), UINT32_MAX);
    err = esp_intr_alloc(ETS_SPI3_DMA_INTR_SOURCE, 0, &adc_dac_dma_isr_default, NULL, &s_adc_dac_dma_isr_handle);
    if (err != ESP_OK) {
        goto out;
    }

out:
    portEXIT_CRITICAL(&s_isr_handler_list_lock);
    return err;
}
esp_err_t adc_dac_dma_isr_register(intr_handler_t handler, void* handler_arg, uint32_t intr_mask)
{
    esp_err_t err = adc_dac_dma_isr_ensure_installed();
    if (err != ESP_OK) {
        return err;
    }

    adc_dac_dma_isr_handler_t* item = malloc(sizeof(*item));
    if (item == NULL) {
        return ESP_ERR_NO_MEM;
    }
    item->handler = handler;
    item->handler_arg = handler_arg;
    item->mask = intr_mask;
    portENTER_CRITICAL(&s_isr_handler_list_lock);
    SLIST_INSERT_HEAD(&s_adc_dac_dma_isr_handler_list, item, next);
    portEXIT_CRITICAL(&s_isr_handler_list_lock);
    return ESP_OK;
}

void adc_dac_dma_linker_start( void *dma_addr, uint32_t int_msk)
{
    REG_SET_BIT(DPORT_PERIP_CLK_EN_REG, DPORT_APB_SARADC_CLK_EN_M);
    REG_SET_BIT(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_DMA_CLK_EN_M);
    REG_SET_BIT(DPORT_PERIP_CLK_EN_REG, DPORT_SPI3_CLK_EN);
    REG_CLR_BIT(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_DMA_RST_M);
    REG_CLR_BIT(DPORT_PERIP_RST_EN_REG, DPORT_SPI3_RST_M);
    REG_WRITE(SPI_DMA_INT_CLR_REG(3), 0xFFFFFFFF);
    REG_WRITE(SPI_DMA_INT_ENA_REG(3), int_msk | REG_READ(SPI_DMA_INT_ENA_REG(3)));

    REG_SET_BIT(SPI_DMA_IN_LINK_REG(3), SPI_INLINK_STOP);
    REG_CLR_BIT(SPI_DMA_IN_LINK_REG(3), SPI_INLINK_START);
    SET_PERI_REG_BITS(SPI_DMA_IN_LINK_REG(3), SPI_INLINK_ADDR, (uint32_t)dma_addr, 0);
    REG_SET_BIT(SPI_DMA_CONF_REG(3), SPI_IN_RST);
    REG_CLR_BIT(SPI_DMA_CONF_REG(3), SPI_IN_RST);
    REG_CLR_BIT(SPI_DMA_IN_LINK_REG(3), SPI_INLINK_STOP);
    REG_SET_BIT(SPI_DMA_IN_LINK_REG(3), SPI_INLINK_START);
}

esp_err_t adc_get_value_group(uint16_t *buf,const int num)
{
    adc_dma_event_t evt;

    adc_dac_dma_linker_start((void *)&dma1, SPI_IN_SUC_EOF_INT_ENA);
    adc_digi_start();
     while (1) {
        xQueueReceive(que_adc, &evt, 10 / portTICK_RATE_MS);
        if (evt.int_msk & SPI_IN_SUC_EOF_INT_ENA) {
            break;
        }
    }
    adc_digi_stop();
    uint16_t *buf1 = (uint16_t *)link_buf;
    for(int i=0;i<num;i++){
        buf[i] = (buf1[i] & 0xFFF);
    }
    return ESP_OK;
}

int adc_dig_bsp_init()
{
    adc_digi_config_t config = {
        .conv_limit_en = false,
        .conv_limit_num = 0,
        .interval = 40,
        .dig_clk.use_apll = 0,  // APB clk
        .dig_clk.div_num = 9,
        .dig_clk.div_b = 0,
        .dig_clk.div_a = 0,
        .dma_eof_num = SAR_SIMPLE_NUM*2,
    };
    adc_digi_pattern_table_t adc1_patt = {0};
    config.adc1_pattern_len = 1;
    config.adc1_pattern = &adc1_patt;
    adc1_patt.atten = ADC_ATTEN_11db;
    adc1_patt.channel = ADC1_CHANNEL_8;  
    adc_gpio_init(ADC_UNIT_1,ADC1_CHANNEL_8 );
    config.conv_mode = ADC_CONV_SINGLE_UNIT_1;
    config.format = ADC_DIGI_FORMAT_12BIT;
    adc_digi_controller_config(&config);
    if (que_adc == NULL) {
        que_adc = xQueueCreate(5, sizeof(adc_dma_event_t));
    } else {
        xQueueReset(que_adc);
    }
    uint32_t int_mask = SPI_IN_SUC_EOF_INT_ENA;
    uint32_t dma_addr = adc_dma_linker_init();
    adc_dac_dma_isr_register(adc_dma_isr, NULL, int_mask);
    //先采集一次
    uint16_t buf[SAR_SIMPLE_NUM];
    adc_dac_dma_linker_start((void *)dma_addr, int_mask);
    adc_get_value_group(buf, 6);
    
    return 0;
}

h文件

#pragma once 

#include "stdint.h"

#define SAR_SIMPLE_NUM  12 //需要采样的bug

typedef struct dma_msg {
    uint32_t int_msk;
    uint8_t *data;
    uint32_t data_len;
} adc_dma_event_t;//用于队列的事件
//外部获取的ADC值的API
esp_err_t adc_get_value_group(uint16_t *buf,const int num);
//外部初始化函数
int adc_dig_bsp_init(void);

总结

移植ADC_DMA在浏览器上相关的移植记录很少,而官方又默认使用速率较慢的RTC控制器,在某些应用场景在并不能适用,因此需要使用DIG控制器。目前这个代码是使用的保证准确度的分频系数。但是在config初始化的值dig_clk.div_num为9,官方介绍该值可以为0~255,低于9可能会导致数据不准,如果还想再提高速率的话,可以考虑将该值减少。
初始化的DMA的大小时,需要为采样值的四倍以上,否则会进入不到中断,导致卡死在读值的死循环。这个大小让我调试了很久才找出来的。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!