ESP32S3 ADC DMA使用记录与坑点分析(欢迎大牛给出建议)

目前测到三个问题:

一、ADC DMA采样频率sample_freq_hz取值范围611-83333,虽然可以达到83333,但是只能是在while循环里面不停采样才可以,如果想要隔一段时间采样一次则不行,假如隔一段时间使用adc_digi_read_bytes读取40byte数据,结果经常会返回ESP_ERR_INVALID_STATE,且经常读取的数据量少于40byte。

        所以ADC DMA采样方式(大约是15us)虽然比单次采样(大约是35us)速度要快,但是不能间隔采样,只能连续不停采样。

二、ADC DMA采样精度太烂,粗略使用还行,要想精确不能用。

三、ADC DMA和WIFI STA模式冲突

adc_digi_read_bytes执行的时候,如果正好wifi sta在初始化或者在连接ap,那么100%会超时,返回值ESP_ERR_TIMEOUT。而且后期无法恢复,只能调用下列函数才可以恢复。

adc_digi_stop();
adc_digi_start();

新解决方案:升级idf到4.4.3

具体见下面代码

代码:

main.c

/* Handwrite Board

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include "esp_http_client.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_wifi.h"

//local variable
static const char* TAG = "Main";
TaskHandle_t wifi_sta_ap_task_handle = NULL;
TaskHandle_t rmt_task_handle = NULL;



//extern method
extern void HWB_Wifi_Init_Sta_And_Ap();
extern void HWB_RMT_Task(void *Parameters);



void HWB_Wifi_Sta_Ap_Task(void *Parameters)
{
  HWB_Wifi_Init_Sta_And_Ap();
}


void app_main(void)
{
    /* Print chip information */
    esp_chip_info_t chip_info;
    esp_chip_info(&chip_info);
    printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
            CONFIG_IDF_TARGET,
            chip_info.cores,
            (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
            (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");

    printf("silicon revision %d, ", chip_info.revision);

    printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
            (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
     ESP_ERROR_CHECK(nvs_flash_erase());//test
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    //创建业务处理线程,启动wifi sta
	xTaskCreate(HWB_Wifi_Sta_Ap_Task, "HWB_Wifi_Sta_Ap_Task", (4*1024), NULL, 1, &wifi_sta_ap_task_handle);
    xTaskCreate(HWB_RMT_Task, "HWB_RMT_Task", (4*1024), NULL, 20, &rmt_task_handle);
}

rmt.c


#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/rmt.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#include <sys/time.h>



//extern variable
extern xQueueHandle adc_data_queue;
//extern method

//local variable
static const char *TAG = "example";
#define TIMES              40
#define GET_UNIT(x)        ((x>>3) & 0x1)
#define ADC_RESULT_BYTE     4
#define ADC_CONV_LIMIT_EN   0
#define ADC_CONV_MODE       ADC_CONV_SINGLE_UNIT_1
#define ADC_OUTPUT_TYPE     ADC_DIGI_OUTPUT_FORMAT_TYPE2

#define RMT_TX_U102_29_CHANNEL  RMT_CHANNEL_0
#define RMT_TX_U102_29_GPIO     4
#define RMT_TX_U102_16_CHANNEL  RMT_CHANNEL_1
#define RMT_TX_U102_16_GPIO     6
#define RMT_TX_U102_15_CHANNEL  RMT_CHANNEL_2
#define RMT_TX_U102_15_GPIO     7
#define ADC_DMA_MODE            1
#define         DEFAULT_VREF    1100        //Use adc2_vref_to_gpio() to obtain a better estimate
static esp_adc_cal_characteristics_t *adc_chars;

static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
static const adc_atten_t atten = ADC_ATTEN_DB_11;

#if ADC_DMA_MODE
static uint16_t adc1_chan_mask = BIT(4);
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel1[1] = {ADC1_CHANNEL_4};
#endif
/*
 * Prepare a raw table with a message in the Morse code
 *
 * The message is "ESP" : . ... .--.
 *
 * The table structure represents the RMT item structure:
 * {duration, level, duration, level}
 *
 */
 static const rmt_item32_t morse_esp_type1[] = {
     {{{ 10, 0, 60, 1 }}}, 
     {{{ 20, 0, 10, 0 }}}, 
 };

static const rmt_item32_t morse_esp2[] = {
    {{{ 100, 1, 180, 0 }}}, 
};
// static const rmt_item32_t morse_esp3[] = {
//     {{{ 100, 1, 60, 0 }}}, 
// };
extern int THRESHOLD_VALUE;
static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)
{
    printf("continuous_adc_init %d,%d\n",adc1_chan_mask,adc2_chan_mask);
    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 = 83333,
        .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;

        printf("adc_pattern[%d].atten is :%x\n", i, adc_pattern[i].atten);
        printf("adc_pattern[%d].channel is :%x\n", i, adc_pattern[i].channel);
        printf("adc_pattern[%d].unit is :%x\n", i, adc_pattern[i].unit);
        printf("adc_pattern[%d].bit_width is :%d,%d\n", i, adc_pattern[i].bit_width,SOC_ADC_DIGI_MAX_BITWIDTH);
    }
    dig_cfg.adc_pattern = adc_pattern;
    ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg));
}

static void check_efuse(void)
{
    //Check if TP is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
        printf("eFuse Two Point: Supported\n");
    } else {
        printf("eFuse Two Point: NOT supported\n");
    }
    //Check Vref is burned into eFuse
    if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
        printf("eFuse Vref: Supported\n");
    } else {
        printf("eFuse Vref: NOT supported\n");
    }
}

static void print_char_val_type(esp_adc_cal_value_t val_type)
{
    if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
        printf("Characterized using Two Point Value\n");
    } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
        printf("Characterized using eFuse Vref\n");
    } else {
        printf("Characterized using Default Vref\n");
    }
}
/*
 * Initialize the RMT Tx channel
 */
static void rmt_tx_init(void)
{
	printf("run rmt_tx_init");
    rmt_config_t config1 = RMT_DEFAULT_CONFIG_TX(RMT_TX_U102_29_GPIO, RMT_TX_U102_29_CHANNEL);
    config1.flags = RMT_CHANNEL_FLAGS_INVERT_SIG;
    config1.tx_config.carrier_en = true;
    config1.tx_config.carrier_duty_percent = 50;
    config1.tx_config.carrier_freq_hz = 500000;
    config1.clk_div = 80;

    rmt_config_t config2 = RMT_DEFAULT_CONFIG_TX(RMT_TX_U102_16_GPIO, RMT_TX_U102_16_CHANNEL);
    config2.tx_config.carrier_en = false;
    config2.tx_config.carrier_duty_percent = 50;
    config2.tx_config.carrier_freq_hz = 500000;
    config2.clk_div = 80;

    rmt_config_t config3 = RMT_DEFAULT_CONFIG_TX(RMT_TX_U102_15_GPIO, RMT_TX_U102_15_CHANNEL);
    config3.tx_config.carrier_en = false;
    config3.tx_config.carrier_duty_percent = 50;
    config3.tx_config.carrier_freq_hz = 500000;
    config3.clk_div = 80;

    ESP_ERROR_CHECK(rmt_config_self(&config1, true)); /*20221011 YB added new API to set open Drain for TX or not*/
    ESP_ERROR_CHECK(rmt_config(&config2));
    ESP_ERROR_CHECK(rmt_config(&config3));
    ESP_ERROR_CHECK(rmt_driver_install(config1.channel, 0, 0));
    ESP_ERROR_CHECK(rmt_driver_install(config2.channel, 0, 0));
    ESP_ERROR_CHECK(rmt_driver_install(config3.channel, 0, 0));

    ESP_ERROR_CHECK(rmt_add_channel_to_group(RMT_TX_U102_29_CHANNEL));
    ESP_ERROR_CHECK(rmt_add_channel_to_group(RMT_TX_U102_16_CHANNEL));
    ESP_ERROR_CHECK(rmt_add_channel_to_group(RMT_TX_U102_15_CHANNEL));
}

void HWB_RMT_Task(void *Parameters)
{
    printf("Configuring transmitter");
    uint32_t ret_num = 0;
	uint8_t result[TIMES] = {0};
    memset(result, 0xcc, TIMES);
	
    //Check if Two Point or Vref are burned into eFuse
    check_efuse();

    //Characterize ADC
    adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
    esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1, atten, width, DEFAULT_VREF, adc_chars);
    print_char_val_type(val_type);
#if ADC_DMA_MODE
    continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel1, sizeof(channel1) / sizeof(adc_channel_t));
    adc_digi_start();
#endif

    rmt_tx_init();
	
    while (1) {		
        ESP_ERROR_CHECK(rmt_write_items(RMT_TX_U102_15_CHANNEL, morse_esp2, sizeof(morse_esp2) / sizeof(morse_esp2[0]), false));
		ESP_ERROR_CHECK(rmt_write_items(RMT_TX_U102_29_CHANNEL, morse_esp_type1, sizeof(morse_esp_type1) / sizeof(morse_esp_type1[0]), false));
		ESP_ERROR_CHECK(rmt_write_items(RMT_TX_U102_16_CHANNEL, morse_esp_type1, sizeof(morse_esp_type1) / sizeof(morse_esp_type1[0]), false));
        rmt_wait_tx_done(RMT_TX_U102_16_CHANNEL,10); 

#if ADC_DMA_MODE
        esp_err_t ret = adc_digi_read_bytes(result, TIMES, &ret_num, 10/*ADC_MAX_DELAY*/);
        uint32_t max_adc_val = 0;
        if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE) {
            for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE) {
                adc_digi_output_data_t *p = (void*)&result[i];
                if (p->type2.unit == 0 && p->type2.channel == 4) {
                    max_adc_val = (max_adc_val > p->type2.data ? max_adc_val : p->type2.data);
                    printf("Unit: %d,Channel: %d, Value: %d\n", p->type2.unit+1, p->type2.channel, p->type2.data);
                }
            }
        }else if(ret == ESP_ERR_TIMEOUT){
            printf("ESP_ERR_TIMEOUT\n");
            // adc_digi_stop();
            // adc_digi_start();
        }
#endif

        rmt_wait_tx_done(RMT_TX_U102_15_CHANNEL,10); 
    }
}

wifi_ap_sta.c

/* WiFi station Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#include "lwip/err.h"
#include "lwip/sys.h"

#define     HWB_WIFI_AP_SSID                        "1TMT_HWB"
#define     HWB_WIFI_AP_PASSWORD                    "12345678" //最小长度是8,最大长度是64
#define     HWB_WIFI_AP_CHANNEL                     1    //范围1-13,默认值1    
#define     HWB_WIFI_AP_MAX_STA_CONN                1
#define     HWB_WIFI_STA_MAXIMUM_RETRY              5
//local variable
static const char *TAG = "HWB_Wifi_Sta_Ap";

//local method


static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        printf("WIFI_EVENT_STA_START");
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        printf("WIFI_EVENT_STA_DISCONNECTED");
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
        printf("WIFI_EVENT_STA_CONNECTED");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        printf("IP_EVENT_STA_GOT_IP");
    }else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STACONNECTED) {
        printf("WIFI_EVENT_AP_STACONNECTED");
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        printf("WIFI_EVENT_AP_STADISCONNECTED");
    }
}


void HWB_Wifi_Init_Sta_And_Ap()
{
    printf("HWB_Wifi_Init_Sta_And_Ap start");

    ESP_ERROR_CHECK(esp_netif_init());//创建一个 LwIP(轻量级的 TCP/IP 协议栈)核心任务,并初始化 LwIP 相关工作

    //创建一个系统事件任务,并初始化应用程序事件的回调函数。
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();//创建带有 TCP/IP 堆栈的默认网络接口实例绑定 AP
    //WIFI 驱动程序初始化配置时建议使用 IDF 默认的参数进行配置,WIFI_INIT_CONFIG_DEFAULT是一个宏,
    //这个宏的作用就是对结构体wifi_init_config_t进行初始化
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    //创建 WIFI 驱动程序任务并初始化 WIFI 驱动程序,这个API必须第一个调用,才能调用wifi的其他API
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_t instance_any_id;
    esp_event_handler_instance_t instance_got_ip;
    //向system event loop注册event handler
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    //wifi_config_t是配置WIFI的集合union。
    //wifi_ap_config_t  ap  用于配置AP
    //wifi_sta_config_t sta 用于配置STA
    //WIFI STA Config
    wifi_config_t wifi_config_sta;
    memset(&wifi_config_sta, 0, sizeof(wifi_config_sta));
    wifi_config_sta.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;

    if (strlen((char *)wifi_config_sta.sta.password) == 0) {
        printf("authmode WIFI_AUTH_OPEN");
        wifi_config_sta.sta.threshold.authmode = WIFI_AUTH_OPEN;
    }
    //WIFI AP Config
    wifi_config_t wifi_config_ap = {
        .ap = {
            .ssid = HWB_WIFI_AP_SSID,
            .ssid_len = strlen(HWB_WIFI_AP_SSID),
            .channel = HWB_WIFI_AP_CHANNEL,
            .password = HWB_WIFI_AP_PASSWORD,
            .max_connection = HWB_WIFI_AP_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    if (strlen(HWB_WIFI_AP_PASSWORD) == 0) {
        wifi_config_ap.ap.authmode = WIFI_AUTH_OPEN;
    }
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_APSTA) );//配置WIFI的工作模式为AP-STA,即AP 和STA共存模式;
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config_sta) );//对wifi的STA模式进行配置
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config_ap) );//对wifi的AP模式进行配置
    ESP_ERROR_CHECK(esp_wifi_start() );//启动WIFI

    printf("HWB_Wifi_Init_Sta_And_Ap finished.");
    while(1){
      vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

物联沃分享整理
物联沃-IOTWORD物联网 » ESP32S3 ADC DMA使用记录与坑点分析(欢迎大牛给出建议)

发表评论