ESP32-C3入门教程 基础篇(一、ADC采样)
经过前面的折腾,设计好了自己的测试开发板 搭建好了开发环境,
然后正式开始进行功能测试了,测试顺序先从简单的开始吧,一步一步来
- 1
- 2
前言
接下来的ESP32-C3 功能测试都是基于自己设计的开发板:
自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)
开发环境是乐鑫官方的 ESP-IDF, 基于VScode插件搭建好的:
ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF——Windows和Ubuntu双环境)
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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
测试效果如下:
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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
2、 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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
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_LOW
和SOC_ADC_SAMPLE_FREQ_THRES_HIGH
范围内。
文章来源: blog.csdn.net,作者:矜辰所致,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/weixin_42328389/article/details/122266604
- 点赞
- 收藏
- 关注作者
评论(0)