前言
ADC即模拟数字转换器(Analog-to-digital converter)是用于将模拟形式的连续信号转换为数字形式的离散信号的一类设备。一个模拟数字转换器可以提供信号用于测量。与之相对的设备成为数字模拟转换器。
例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。那就可以用到ADC了
提示:以下是本篇文章正文内容,下面案例可供参考
一、ESP32 ADC相关介绍
一些 ADC2 引脚用作捆绑引脚(GPIO 0、2、15),因此不能自由使用。
ESP32 DevKitC:由于外部自动编程电路,GPIO 0 无法使用。
ESP-WROVER-KIT:GPIO 0、2、4 和 15 不能使用,因为用于不同目的的外部连接。
由于 ADC2 模块也被 Wi-Fi 使用,因此它们一起使用时只有一个可以抢占,这意味着adc2_get_raw()可能会被阻塞直到 Wi-Fi 停止
ESP32 集成了 2 个 SAR(逐次逼近寄存器)ADC,总共支持 18 个测量通道(模拟使能引脚)。
ADC1,8通道:GPIO32 - GPIO39
ADC2,10个通道:GPIO0、GPIO2、GPIO4、GPIO12 - GPIO15、GOIO25 - GPIO27
以下介绍通过ADC1进行介绍
2、ADC衰减
Vref 是 ESP32 ADC 内部用于测量输入电压的参考电压。ESP32 ADC 可以测量从 0 V 到 Vref 的模拟电压。在不同的芯片中,Vref 不同,中位数为 1.1 V。为了转换大于 Vref 的电压,可以在输入 ADC 之前对输入电压进行衰减。有 4 种可用的衰减选项,衰减越高,可测量的输入电压就越高。
3、ADC 转换
ADC 转换是将输入模拟电压转换为数字值。ADC 驱动程序 API (adc1_get_raw()
)提供的 ADC 转换结果是原始数据。Single Read 模式下 ESP32 ADC 原始结果的分辨率为 12 位。
如果根据ADC采集的原始数据来计算电压那可以用Vout(输出电压)=Dout(输出的数据)*Vmax(最大测量电压)/Dmax(最大输出数据)
ps:如果是带有ADC校准位的板子可以直接调用esp_adc_cal_raw_to_voltage()
来直接读取输出电压,单位为mv
4、ADC 单次读取
在配置好位宽adc1_config_width()
和衰减adc1_config_channel_atten()
之后就可以直接调用adc1_get_raw(),
读取结果了;值得说明的是官方也提供给了从ULP直接读取ADC1通道的结果,但是这个要在配置的时候调用adc1_ulp_enable()
来使能ULP
二、使用步骤
1.接口函数介绍
(1)设置通道衰减
esp_err_t adc1_config_channel_atten(adc1_channel_t channel,adc_atten_t atten )
第一个参数是设置ADC通道,第二个参数是设置衰减等级;默认衰减是0dB;通过官方给的衰减设置可以看到,衰减设置的越大,那么可采集的电压范围就越大(通过对输入电压进行衰减)
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
(2)配置ADC的捕获位宽
esp_err_t adc1_config_width( adc_bits_width_t width_bit )
参数:ADC1 的位捕获宽度
返回值:
ESP_OK 成功
ESP_ERR_INVALID_ARG 参数错误
(3)读取单个通道上的ADC数据
int adc1_get_raw( adc1_channel_t channel)
返回
-1:参数错误
其他:ADC1 通道读数。
参数
channel: ADC1 通道读取
(4)初始化数字ADC
esp_err_t adc_digi_initialize(const adc_digi_init_config_t *init_config )
参数:数字ADC的初始化配置adc_digi_init_config_t
在这个结构体里面;
返回:
-
- ESP_ERR_INVALID_ARG 参数错误
-
- ESP_ERR_NOT_FOUND 没有找到中断空闲
-
- ESP_ERR_NO_MEM 内存不足
-
- ESP_OK 成功
结构体中参数依次是
1)转换后的数据可以存储的最大长度。
2)在一个中断中可以转换的数据字节数
3)待初始化的ADC1通道列表。
4)需要初始化的ADC2通道列表。
- ESP_OK 成功
(5)通过 DMA 从数字 ADC 读取字节。
esp_err_t adc_digi_read_bytes(uint8_t *buf, uint32_t length_max, uint32_t *out_length, uint32_t timeout_ms);
[out] buf:从 ADC 读取的缓冲区。
[in] length_max:从 ADC 读取的预期数据长度。
[out] out_length:从 ADC 读取的数据的实际长度。
[in] timeout_ms:等待数据的时间,以毫秒为单位。
返回
ESP_ERR_INVALID_STATE 驱动状态无效。通常这意味着 ADC 采样率快于任务处理率。
ESP_ERR_TIMEOUT 操作超时
ESP_OK 成功
(6)启动数字ADC和 DMA
esp_err_t adc_digi_start(void);
返回
ESP_ERR_INVALID_STATE 驱动状态无效。
ESP_OK 成功
(6)设置数字控制器
esp_err_t adc_digi_controller_configure(const adc_digi_configuration_t *config);
返回值:
ESP_ERR_INVALID_STATE 驱动状态无效。
ESP_ERR_INVALID_ARG 如果参数组合无效。
ESP_OK 成功
参数为adc_digi_configuration_t 结构体
结构体中参数依次是
1)限制ADC转换的时间,转换完成后是否停止
2)限制ADC转换触发器上限1-255
3)ADC通道数
4)初始化结构体配置参数
5)采样频率611Hz ~ 83333Hz
6)ADC DMA传输模式:选择ADC1、ADC2、ADC1与ADC2、ADC1与ADC2轮流
7)ADC DMA传输转换格式:12BIT转换/11BIT转换
对于设置的输出格式来说如果设置输出12BIT的数据,那么输出的16位中存放格式是[15:12] 通道,[11:0]12 位 ADC 数据 。注意:对于单次转换模式。
如果设置的数是11BIT的数据那么存放内容就是[15]adc 单元,[14:11]通道,[10:0]11 位 ADC 数据 。注意:对于多或交替转换模式。
(7)检查 ADC 校准值是否烧入 eFuse。
esp_err_t esp_adc_cal_check_efuse( esp_adc_cal_value_t value_type )
检查 ADC 参考电压或两点值是否已烧到当前 ESP32 的 eFuse
返回:
ESP_OK:eFuse 支持校准模式
ESP_ERR_NOT_SUPPORTED:错误,eFuse 值未烧入
ESP_ERR_INVALID_ARG:错误,无效参数(ESP_ADC_CAL_VAL_DEFAULT_VREF)
参数:
value_type:校准值的类型(ESP_ADC_CAL_VAL_EFUSE_VREF 或 ESP_ADC_CAL_VAL_EFUSE_TP)
(8)以特定衰减表征 ADC,
esp_adc_cal_value_t esp_adc_cal_characterize( adc_unit_t adc_num , adc_atten_t atten , adc_bits_width_t bit_width , uint32_t default_vref , esp_adc_cal_characteristics_t * chars )
返回:
ESP_ADC_CAL_VAL_EFUSE_VREF:用于表征的 eFuse Vref
ESP_ADC_CAL_VAL_EFUSE_TP:用于表征的两点值(仅在线性模式下)
ESP_ADC_CAL_VAL_DEFAULT_VREF:用于表征的默认 Vref
参数:
[in] adc_num:ADC_UNIT_1 或 ADC_UNIT_2
[in] atten: 衰减
[in] bit_width: ADC的位宽配置
[in] default_vref:默认 ADC 参考电压,单位 mV
[out] chars: 指向用于存储 ADC 特征的空结构的指针
(9)将 ADC 读数转换为以 mV 为单位的电压。
uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, const esp_adc_cal_characteristics_t *chars);
在调用此函数之前必须初始化特征结构(调用 esp_adc_cal_characterize())
返回:
电压 (mV)
参数:
[in] adc_reading: ADC 读数
[in] chars: 指向包含 ADC 特征的初始化结构的指针
(10)直接获取通道以 mV 为单位的电压。
esp_err_t esp_adc_cal_get_voltage(adc_channel_t channel, const esp_adc_cal_characteristics_t *chars, uint32_t *voltage);
这个函数同(9)函数一样都是得到电压,但是(9)传参是ADC的读数,本函数传参是ADC通道
参数
[in] channel:要读取的 ADC 通道
[in] chars: 指向已初始化 ADC 特征结构的指针
[out] voltage:存储转换电压的指针
返回
ESP_OK:ADC 读取并转换为 mV
ESP_ERR_INVALID_ARG:由于参数无效导致的错误
ESP_ERR_INVALID_STATE:读取结果无效。尝试再次阅读。
2.代码示例
ADC+DMA
my_adc.c
#include "my_adc.h"
#if CONFIG_IDF_TARGET_ESP32S2
static uint16_t adc1_chan_mask = BIT(0);
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
#endif
#if CONFIG_IDF_TARGET_ESP32
static uint16_t adc1_chan_mask = BIT(0);//
static uint16_t adc2_chan_mask = 0;
static adc_channel_t channel[1] = {ADC1_CHANNEL_0};
#endif
static void continuous_adc_init(uint16_t adc1_chan_mask, uint16_t adc2_chan_mask, adc_channel_t *channel, uint8_t channel_num)
{
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,//限制ADC转换的次数,在转换conv_limit_num之后就会停止,选择是否使能
.conv_limit_num = 250,//设置ADC触发器的上限
.sample_freq_hz = 10 * 1000,//ADC采样频率
.conv_mode = ADC_CONV_MODE,//选择DMA转换模式,选择使用哪个ADC或者都使用
.format = ADC_OUTPUT_TYPE,//设置数据输出格式
};
adc_digi_pattern_config_t adc_pattern[SOC_ADC_PATT_LEN_MAX] = {0};//将使用ADC通道的配置列表
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通道,总共0-7八个通道,只看后三位
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;//设置位宽为最大宽度
ESP_LOGI(TAG, "adc_pattern[%d].atten is :%x", i, adc_pattern[i].atten);
ESP_LOGI(TAG, "adc_pattern[%d].channel is :%x", i, adc_pattern[i].channel);
ESP_LOGI(TAG, "adc_pattern[%d].unit is :%x", i, adc_pattern[i].unit);
}
dig_cfg.adc_pattern = adc_pattern;
ESP_ERROR_CHECK(adc_digi_controller_configure(&dig_cfg));
}
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;
}
void my_adc_dma_init(void)
{
continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
}
main.c文章来源:https://www.toymoban.com/news/detail-417841.html
#include "my_adc.h"
// #define LEDC_MAX_DUTY (8191)
// #define LEDC_FADE_TIME (1000)
void app_main(void)
{
esp_err_t ret;
uint32_t ret_num = 0;
uint8_t result[TIMES] = {0};
memset(result, 0xcc, TIMES);//把数组result里面的times个值都初始化成0xcc=204;
// continuous_adc_init(adc1_chan_mask, adc2_chan_mask, channel, sizeof(channel) / sizeof(adc_channel_t));//初始化配置
my_adc_dma_init();
adc_digi_start();//启动转换
while (1)
{
ret = adc_digi_read_bytes(result, TIMES, &ret_num, ADC_MAX_DELAY);//读取转换数据,数据存在result里面,预期是256个,实际是ret_num个。转换超时时间限制是最大
//判断adc_digi_read_bytes返回的参数,成功、驱动设备未安装(残阳速率大于任务处理速率)、超时
if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE)
{
if (ret == ESP_ERR_INVALID_STATE)
{
/**
* 对于普通的只打印任务处理速率还是很快的
*
* @note 1
* Issue:
* As an example, we simply print the result out, which is super slow. Therefore the conversion is too
* fast for the task to handle. In this condition, some conversion results lost.
*
* Reason:
* When this error occurs, you will usually see the task watchdog timeout issue also.
* Because the conversion is too fast, whereas the task calling `adc_digi_read_bytes` is slow.
* So `adc_digi_read_bytes` will hardly block. Therefore Idle Task hardly has chance to run. In this
* example, we add a `vTaskDelay(1)` below, to prevent the task watchdog timeout.
*
* Solution:
* Either decrease the conversion speed, or increase the frequency you call `adc_digi_read_bytes`
*/
printf("ERROR:请降低转换速度,或者增加调用adc_digi_read_bytes的频率\n" );
}
for (int i = 0; i < ret_num; i += ADC_RESULT_BYTE)
{
adc_digi_output_data_t *p = (void*)&result[i]; //把result中的数据传到adc_digi_output_data_t中,(void*)就是可以把数据转换成任意类型的数据
#if CONFIG_IDF_TARGET_ESP32
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 1, p->type1.channel, p->type1.data);//如果选择的是ESP32那么输出使用的哪个ADC的哪个通道以及那种类型的数据
#else
if (ADC_CONV_MODE == ADC_CONV_BOTH_UNIT || ADC_CONV_MODE == ADC_CONV_ALTER_UNIT) //判断ADC转换模式时ADC1、2,或者是轮流转换
{
//判断数据是否有效
if (check_valid_data(p))
{
ESP_LOGI(TAG, "Unit: %d,_Channel: %d, Value: %x", p->type2.unit+1, p->type2.channel, p->type2.data);//输出有效数据
} else
{
// abort();
ESP_LOGI(TAG, "Invalid data [%d_%d_%x]", p->type2.unit+1, p->type2.channel, p->type2.data);//输出数据无效
}
}
#if CONFIG_IDF_TARGET_ESP32S2
else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_2)
{
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %x", 2, p->type1.channel, p->type1.data);
} else if (ADC_CONV_MODE == ADC_CONV_SINGLE_UNIT_1)
{
ESP_LOGI(TAG, "Unit: %d, Channel: %d, Value: %d", 1, p->type1.channel, p->type1.data);
}
#endif //#if CONFIG_IDF_TARGET_ESP32S2
#endif
}
//See `note 1`
vTaskDelay(1);//程序运行太快,防止任务看门狗超时
} else if (ret == ESP_ERR_TIMEOUT) {
/**
* ``ESP_ERR_TIMEOUT``: If ADC conversion is not finished until Timeout, you'll get this return error.
* Here we set Timeout ``portMAX_DELAY``, so you'll never reach this branch.
*/
ESP_LOGW(TAG, "No data, increase timeout or reduce conv_num_each_intr");
vTaskDelay(1000);
}
}
adc_digi_stop();
ret = adc_digi_deinitialize();
assert(ret == ESP_OK);
}
总结
完整代码工程在这里:代码文章来源地址https://www.toymoban.com/news/detail-417841.html
到了这里,关于esp32学习笔记(4)——adc的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!