写点什么

ESP32-C3 入门教程 基础篇(一、ADC 采样)

作者:矜辰所致
  • 2022 年 9 月 19 日
    江苏
  • 本文字数:5321 字

    阅读完需:约 17 分钟

ESP32-C3入门教程 基础篇(一、ADC采样)
经过前面的折腾,设计好了自己的测试开发板 搭建好了开发环境,然后正式开始进行功能测试了,测试顺序先从简单的开始吧,一步一步来
复制代码


前言

接下来的 ESP32-C3 功能测试都是基于自己设计的开发板:


自己画一块 ESP32-C3 的开发板(第一次使用立创 EDA)

https://xie.infoq.cn/article/30387388381a0d915b2494f91


开发环境是乐鑫官方的 ESP-IDF, 基于 VScode 插件搭建好的:


ESP32-C3 VScode 开发环境搭建(基于乐鑫官方 ESP-IDF——Windows 和 Ubuntu 双环境)

https://xie.infoq.cn/article/5b639e112cabba00cc1b8941a

1、ADC 采样示例测试

新建一个 ADC 采样的工程,当然是基于官方的 ADC 示例代码建立的,建立工程的方式在上面开发环境搭建的示例测试章节有图文说明:


1.1 DMA 连续采样

示例代码有 2 个函数,单次检测 和 DMA 连续检测,分别接在如下通道上面:



在开发板上面,我们只预留了一个 ADC 接口,就是 ADC1_CHANNEL_0,连接的是一个光敏电阻:



所以需要对示例进行稍微修改,主要是对读取函数,只设置 ADC1_CHANNEL_0 ,如下图:



在主函数中只调用continuous_read(NULL);函数,测试结果如下:


1.2 单次采样

单次采样比较简单,也是直接在上面的样例中修改,下面直接上修改后的测试代码:


static void single_read(void *arg){    // esp_err_t ret;    // int adc1_reading[3] = {0xcc};    int adc1_reading[1] = {0xcc};    // int adc2_reading[1] = {0xcc};    float vout;    // const char TAG_CH[][10] = {"ADC1_CH2", "ADC1_CH3","ADC1_CH4", "ADC2_CH0"};    const char TAG_CH[1][10] = {"ADC1_CH0"};
adc1_config_width(ADC_WIDTH_BIT_DEFAULT); adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_6); // adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_0); // adc2_config_channel_atten(ADC2_CHANNEL_0, ADC_ATTEN_DB_0); // int n = 20; // while (n--) { while (1) {
adc1_reading[0] = adc1_get_raw(ADC1_CHANNEL_0); // adc1_reading[1] = adc1_get_raw(ADC1_CHANNEL_3); // adc1_reading[2] = adc1_get_raw(ADC1_CHANNEL_4); vout = (adc1_reading[0] * 2500.00)/4095.00; ESP_LOGI(TAG_CH[0], "%x vout mv is %f", adc1_reading[0],vout);
// for (int i = 0; i < 3; i++) { // ESP_LOGI(TAG_CH[i], "%x", adc1_reading[i]); // } // ret = adc2_get_raw(ADC2_CHANNEL_0, ADC_WIDTH_BIT_12, &adc2_reading[0]); // assert(ret == ESP_OK); // ESP_LOGI(TAG_CH[3], "%x", adc2_reading[0]); vTaskDelay(500 / portTICK_PERIOD_MS); }}
void app_main(void){ single_read(NULL); //continuous_read(NULL);}
复制代码


测试效果如下:


1.3 测试源码

上一份自己稍微修改的最后测试的adc_dma_example_main.c源码


  • 针对自己的板子只有一个 ADC 接口进行代码精简

  • 增加实际电压值的计算输出

  • LED 切换表示采样一次

  • 注释部分为了避免警告需要自行去掉,使用单次模式注释连续采样代码,反之一样


#include <string.h>#include <stdio.h>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "freertos/semphr.h"#include "esp_log.h"#include "driver/adc.h"#include "driver/gpio.h"
#define TIMES 256
// static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)// {// esp_err_t ret = ESP_OK;// assert(ret == ESP_OK);
// adc_digi_init_config_t adc_dma_config = {// .max_store_buf_size = 1024,// .conv_num_each_intr = 256,// .adc1_chan_mask = adc1_chan_mask,// .adc2_chan_mask = adc2_chan_mask,// };// ret = adc_digi_initialize(&adc_dma_config);// assert(ret == ESP_OK);
// adc_digi_pattern_table_t adc_pattern[10] = {0};
// //Do not set the sampling frequency out of the range between `SOC_ADC_SAMPLE_FREQ_THRES_LOW` and `SOC_ADC_SAMPLE_FREQ_THRES_HIGH`// adc_digi_config_t dig_cfg = {// .conv_limit_en = 0,// .conv_limit_num = 250,// .sample_freq_hz = 620,// };
// dig_cfg.adc_pattern_len = channel_num;// for (int i = 0; i < channel_num; i++) {// uint8_t unit = ((channel[i] >> 3) & 0x1);// uint8_t ch = channel[i] & 0x7;// adc_pattern[i].atten = ADC_ATTEN_DB_11;// adc_pattern[i].channel = ch;// adc_pattern[i].unit = unit;// }// dig_cfg.adc_pattern = adc_pattern;// ret = adc_digi_controller_config(&dig_cfg);// assert(ret == ESP_OK);// }
// 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;// }
// static void continuous_read(void *arg)// {// esp_err_t ret;// uint32_t ret_num = 0;// uint8_t result[TIMES] = {0};// memset(result, 0xcc, TIMES);// float vout;
// // uint16_t adc1_chan_mask = BIT(0) | BIT(1);// uint16_t adc1_chan_mask = BIT(0);// uint16_t adc2_chan_mask = BIT(0);// // adc_channel_t channel[3] = {ADC1_CHANNEL_0, ADC1_CHANNEL_1, (ADC2_CHANNEL_0 | 1 << 3)};// adc_channel_t channel[1] = {ADC1_CHANNEL_0};
// continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));// adc_digi_start();// // int n = 20;// while(1) {// ret = adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);// for (int i = 0; i < ret_num; i+=4) {// adc_digi_output_data_t *p = (void*)&result[i];// if (check_valid_data(p)) {// vout = (p->type2.data * 2500.00)/4095.00;// printf("ADC%d_CH%d: %x voltage is %fmv\n", p->type2.unit+1, p->type2.channel, p->type2.data,vout);// } else {// printf("Invalid data [%d_%d_%x]\n", p->type2.unit+1, p->type2.channel, p->type2.data);// }// }// vTaskDelay(1000 / portTICK_PERIOD_MS);// // If you see task WDT in this task, it means the conversion is too fast for the task to handle
// } // adc_digi_stop();// ret = adc_digi_deinitialize();// assert(ret == ESP_OK);// }
static void single_read(void *arg){ // esp_err_t ret; // int adc1_reading[3] = {0xcc}; int adc1_reading[1] = {0xcc}; // int adc2_reading[1] = {0xcc}; uint32_t etc = 2; float vout; // const char TAG_CH[][10] = {"ADC1_CH2", "ADC1_CH3","ADC1_CH4", "ADC2_CH0"}; const char TAG_CH[1][10] = {"ADC1_CH0"}; gpio_set_direction(1, GPIO_MODE_OUTPUT); adc1_config_width(ADC_WIDTH_BIT_DEFAULT); adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // adc1_config_channel_atten(ADC1_CHANNEL_3, ADC_ATTEN_DB_6); // adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_0); // adc2_config_channel_atten(ADC2_CHANNEL_0, ADC_ATTEN_DB_0);
// int n = 20; // while (n--) { while (1) { adc1_reading[0] = adc1_get_raw(ADC1_CHANNEL_0); // adc1_reading[1] = adc1_get_raw(ADC1_CHANNEL_3); // adc1_reading[2] = adc1_get_raw(ADC1_CHANNEL_4); vout = (adc1_reading[0] * 2500.00)/4095.00; ESP_LOGI(TAG_CH[0], "%x vout mv is %f", adc1_reading[0],vout);
// for (int i = 0; i < 3; i++) { // ESP_LOGI(TAG_CH[i], "%x", adc1_reading[i]); // } ret = adc2_get_raw(ADC2_CHANNEL_0, ADC_WIDTH_BIT_12, &adc2_reading[0]); // assert(ret == ESP_OK); // ESP_LOGI(TAG_CH[3], "%x", adc2_reading[0]); vTaskDelay(500 / portTICK_PERIOD_MS); etc++; if(etc%2){ gpio_set_level(1,1); } else gpio_set_level(1,0); if(etc > 60000) etc = 2; }}
void app_main(void){ single_read(NULL); //continuous_read(NULL);}
复制代码

2、 ESP32-C3 ADC 相关介绍

对于 ESP32-C3 ADC 的介绍,在乐鑫的官网有很详细的说明,官方链接如下:


乐鑫官方ESP32-C3 ADC部分说明

2.1 实际电压的计算

对于实际电压的计算,有如下计算公式:



其中 Vmax 中的 ADC Attenuation 在官方文档中如下介绍:



在 SDK 的库函数中 使用枚举类型定义的,如下(数值上有一点区别):



在示例中根据公式测试电压值的计算:



在库函数中也有关于电压转换的函数esp_adc_cal_get_voltage,其中调用了esp_adc_cal_raw_to_voltage进行计算:



源码如下:


/*esp_adc_cal_characteristics_t 结构体如下typedef struct {    adc_unit_t adc_num;                     /**< ADC number    adc_atten_t atten;                      /**< ADC attenuation    adc_bits_width_t bit_width;             /**< ADC bit width     uint32_t coeff_a;                       /**< Gradient of ADC-Voltage curve    uint32_t coeff_b;                       /**< Offset of ADC-Voltage curve    uint32_t vref;                          /**< Vref used by lookup table    const uint32_t *low_curve;              /**< Pointer to low Vref curve of lookup table (NULL if unused)    const uint32_t *high_curve;             /**< Pointer to high Vref curve of lookup table (NULL if unused)} esp_adc_cal_characteristics_t;
计算函数如下:uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars){ ADC_CALIB_CHECK(chars != NULL, "No characteristic input.", ESP_ERR_INVALID_ARG);
return adc_reading * chars->coeff_a / coeff_a_scaling + chars->coeff_b / coeff_b_scaling;}
*/esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage){ // Check parameters ADC_CALIB_CHECK(chars != NULL, "No characteristic input.", ESP_ERR_INVALID_ARG); ADC_CALIB_CHECK(voltage != NULL, "No output buffer.", ESP_ERR_INVALID_ARG);
int adc_reading; if (chars->adc_num == ADC_UNIT_1) { //Check if channel is valid on ADC1 ADC_CALIB_CHECK((adc1_channel_t)channel < ADC1_CHANNEL_MAX, "Invalid channel", ESP_ERR_INVALID_ARG); adc_reading = adc1_get_raw(channel); } else { //Check if channel is valid on ADC2 ADC_CALIB_CHECK((adc2_channel_t)channel < ADC2_CHANNEL_MAX, "Invalid channel", ESP_ERR_INVALID_ARG); if (adc2_get_raw(channel, chars->bit_width, &adc_reading) != ESP_OK) { return ESP_ERR_TIMEOUT; //Timed out waiting for ADC2 } } *voltage = esp_adc_cal_raw_to_voltage((uint32_t)adc_reading, chars); return ESP_OK;}
复制代码

2.2 连续采样步骤

官方的连续采样步骤说明:



根据说明我们对比一下示例代码:


2.3 单步采样步骤


根据说明我们对比一下示例代码:


2.4 ADC 使用注意事项

还是官方手册,简单说明一下:


  • ADC2 模块也被 WI-FI 组件使用了,所以在 esp_err_t esp_wifi_start(void)esp_err_t esp_wifi_stop(void)函数之间进行 ADC2 读取,不一定能够获得正确的值,其实这点我们在使用中尽量避免就可以

  • 一个特定的 ADC 模块在同一时间只能工作在一种工作模式下(单次和连续模式)

  • ADC1 和 ADC2 不能同时在单读模式下工作。其中一个会被阻塞,直到另一个完成。

  • 对于连续(DMA)模式,ADC 采样频率应该在SOC_ADC_SAMPLE_FREQ_THRES_LOWSOC_ADC_SAMPLE_FREQ_THRES_HIGH范围内

发布于: 刚刚阅读数: 3
用户头像

矜辰所致

关注

CSDN、知乎、微信公众号: 矜辰所致 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
ESP32-C3入门教程 基础篇(一、ADC采样)_ESP32-C3_矜辰所致_InfoQ写作社区