目录
一、简介
1、官方文档
2、功能概况
ESP32 内置了 2 个 12 位的 SAR ADC,由 5 个专用转换器控制器管理,可测量来自 18 个管脚的模拟信号,还可测量 vdd33 等内部信号。
5 个专用 ADC 控制器:
PWDET
专门用于 PWDET / PKDET (功率检测和峰值监测)。
3、主要特性
- 采用 2 个 SAR ADC,可支持同时采样与转换
- 采用 5 个专用 ADC 控制器,可支持不同应用场景(比如,高性能、低功耗,或功率检测和峰值检测)
- 支持 18 个模拟输入管脚
- 1 个内部电压 vdd33 通道、2 个 pa_pkdet 通道(部分控制器支持)
- 可配置 12 位、11 位、10 位、9 位多种分辨率
- 支持 DMA(1 个控制器支持)
- 支持多通道扫描模式(2 个控制器支持)
- 支持 Deep-sleep 模式运行(1 个控制器支持)
- 支持 ULP 协处理器控制(2 个控制器支持)
4、ADC 控制器类型
(1)RTC SAR ADC 控制器
RTC 电源域中的 SAR ADC 控制器(RTC ADC1 CTRL 和 RTC ADC2 CTRL)可在低频状态下提供最小功耗ADC测量。
- 可在 Deep-sleep 模式下对通道进行周期性检测。Deep-sleep 模式下,ULP 协处理器是唯一的触发器。
- 可按一定顺序对通道进行连续性扫描。尽管控制器无法支持连续性扫描或 DMA,但 ULP 协处理器可协助实现这部分功能。
(2)DIG SAR ADC 控制器
与 RTC SAR ADC 控制器相比,DIG SAR ADC 控制器的性能和吞吐均实现了一定优化,具备以下特点:
- 高性能。时钟更快,因此采样速率实现了大幅提升。
- 支持多通道扫描模式。每个 SAR ADC的测量规则可见样式表。扫描模式可配置为单通道模式、双通道模式或交替模式。
- 扫描可由软件或 I2S 总线发起。
- 支持DMA。扫描完成即发生中断。
5、ADC管脚信息
ADC1
:8通道:GPIO32 - GPIO39ADC2
:10个通道:GPIO0、GPIO2、GPIO4、GPIO12-GPIO15、GOIO25-GPIO27
+----------+--------------------+-------------+-------------+
| ADC | channel | ESP32 | ESP32-S2 |
| | | | |
+==========+====================+=============+=============+
| | ADC1_CHANNEL_0 | GPIO36 | GPIO36 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_1 | GPIO37 | GPIO37 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_2 | GPIO38 | GPIO38 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_3 | GPIO39 | GPIO39 |
| ADC1 +--------------------+-------------+-------------+
| | ADC1_CHANNEL_4 | GPIO32 | GPIO32 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_5 | GPIO33 | GPIO33 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_6 | GPIO34 | GPIO34 |
| +--------------------+-------------+-------------+
| | ADC1_CHANNEL_7 | GPIO35 | GPIO35 |
+----------+--------------------+-------------+-------------+
| | ADC2_CHANNEL_0 | GPIO4 | GPIO11 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_1 | GPIO0 | GPIO12 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_2 | GPIO2 | GPIO13 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_3 | GPIO15 | GPIO14 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_4 | GPIO13 | GPIO15 |
| ADC2 +--------------------+-------------+-------------+
| | ADC2_CHANNEL_5 | GPIO12 | GPIO16 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_6 | GPIO14 | GPIO17 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_7 | GPIO27 | GPIO18 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_8 | GPIO25 | GPIO19 |
| +--------------------+-------------+-------------+
| | ADC2_CHANNEL_9 | GPIO26 | GPIO20 |
+----------+--------------------+-------------+-------------+
表格中的 管脚 #
即代表ADC通道值。例如管脚 # 0
代表 ADC_CHANNEL_0
。
6、测量范围与精度
- 使用 eFuse Vref 参考值校准后的结果如上表所示。
- 芯片之间的测量差异会有 ±6%。
- Atten=3 时,测量值大于 3000(电压值约为 2450 mV)之后,精度会降低。
- 使用过滤器多次采样或计算平均值可以获得更好的 DNL 结果。
+----------+-------------+-----------------+
| | attenuation | suggested range |
| SoC | (dB) | (mV) |
+==========+=============+=================+
| | 0 | 100 ~ 950 |
| +-------------+-----------------+
| | 2.5 | 100 ~ 1250 |
| ESP32 +-------------+-----------------+
| | 6 | 150 ~ 1750 |
| +-------------+-----------------+
| | 11 | 150 ~ 2450 |
+----------+-------------+-----------------+
| | 0 | 0 ~ 750 |
| +-------------+-----------------+
| | 2.5 | 0 ~ 1050 |
| ESP32-S2 +-------------+-----------------+
| | 6 | 0 ~ 1300 |
| +-------------+-----------------+
| | 11 | 0 ~ 2500 |
+----------+-------------+-----------------+
7、硬件设计注意事项
使用 ADC 功能时,建议靠近管脚添加 0.1 µF 的对地滤波电容。
- 在初始化
SARADC1 或 SARADC2
或霍尔传感器的时候,会在SENSOR_VP/SENSOR_VN
PAD 的内部产
生一个持续约 80 ns 的输入毛刺。 - 优先推荐使用
SENSOR_VP
及SENSOR_VN
作为 ADC 使用。 - 如果设计中使用了
SENSOR_VP/SENSOR_VN
作为 GPIO,且同时使用了其他 ADC,则需要额外的软件处
理,避开该毛刺。 - 目前 ADC2 不支持与 Wi-Fi 同时使用,优先推荐使用 ADC1。
- 目前不支持高精度 ADC。SENSOR_VP 和 SENSOR_CAPP 及 SENSOR_VN 和 SENSOR_CAPN 之间的两
个采样电容 270 pF 可删除。之后这 4 个管脚可当做普通的 ADC 或是 GPIO 使用。 - ADC 的输入电压建议不超过 2450 mV,推荐在 100 ~ 950 mV 间,以获得更高的校准精度。具体请参考
《ESP32 技术规格书》中 ADC 章节。
参考 :ESP32硬件设计指南
扫描二维码关注公众号,回复:
13557850 查看本文章
二、配置参数及API
1、API函数
API | 描述 |
---|---|
adc.h | |
adc_set_i2s_data_source |
设置 I2S 数据源 |
adc_i2s_mode_init |
初始化 I2S ADC 模式 |
hall_sensor_read |
读取霍尔传感器 |
adc_common.h | |
adc_power_on |
启用 ADC |
adc_power_off |
关闭ADC |
adc_power_acquire |
增加 ADC 模块的使用计数器 |
adc_power_release |
减少 ADC 模块的使用计数器 |
adc_gpio_init |
初始化 ADC 引脚 |
adc1_pad_get_io_num |
获取特定 ADC1 通道的 GPIO |
adc1_config_channel_atten |
在 ADC1 上设置特定通道的衰减,并配置其关联的 GPIO 引脚复用器 |
adc1_config_width |
配置 ADC1 捕捉宽度,同时使能 ADC1 的输出反相 |
adc1_get_raw |
从单个通道获取 ADC1 读数 |
adc_set_data_inv |
设置 ADC 数据反转 |
adc_set_clk_div |
设置 ADC 源时钟 |
adc_set_data_width |
配置 ADC 捕获宽度 |
adc1_ulp_enable |
将 ADC1 配置为可供 ULP 使用 |
adc2_pad_get_io_num |
获取特定 ADC2 通道的 GPIO 编号 |
adc2_config_channel_atten |
配置ADC2特定通道的衰减 |
adc2_get_raw |
在单个通道上读取 ADC2 |
adc_vref_to_gpio |
将 ADC1 或 ADC2 的参考电压输出到 adc2_channe_t 的 IO |
adc2_vref_to_gpio |
将 ADC2 参考电压输出到 adc2_channe_t 的 IO |
adc_digi_init |
ADC 数字控制器初始化 |
adc_digi_deinit |
ADC 数字控制器去初始化 |
adc_digi_controller_config |
设置数字控制器 |
2、宏及结构体、枚举参数
(1)ADC 衰减参数
不同的参数决定了 ADC 的范围。
枚举 adc_atten_t
示例配置:
adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
项 | 描述 |
---|---|
ADC_ATTEN_DB_0 = 0 |
无输入衰减,ADC可测量高达约800毫伏。 |
ADC_ATTEN_DB_2_5 = 1 |
ADC的输入电压将衰减,将测量范围扩大到大约1100毫伏。 |
ADC_ATTEN_DB_6 = 2 |
ADC的输入电压将衰减,将测量范围扩大到大约1350毫伏。 |
ADC_ATTEN_DB_11 = 3 |
ADC的输入电压将被衰减,将测量范围扩大到大约2600毫伏。 |
(2)ADC分辨率
不同的参数决定了 ADC 的精度。
枚举 adc_bits_width_t
示例配置:
adc1_config_width(ADC_WIDTH_BIT_12);
项 | 描述 |
---|---|
ADC_WIDTH_BIT_9 = 0 |
ADC捕获宽度为9位。 |
ADC_WIDTH_BIT_10 = 1 |
ADC捕获宽度为10位。 |
ADC_WIDTH_BIT_11 = 2 |
ADC捕获宽度为11位。 |
ADC_WIDTH_BIT_12 = 3 |
ADC捕获宽度为12位。 |
三、官方示例参考:
ADC1/ADC2
单次读取示例:
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_adc_cal.h"
#define DEFAULT_VREF 1100 //Use adc2_vref_to_gpio() to obtain a better estimate
#define NO_OF_SAMPLES 64 //Multisampling
static esp_adc_cal_characteristics_t *adc_chars;
#if CONFIG_IDF_TARGET_ESP32
static const adc_channel_t channel = ADC_CHANNEL_6; //GPIO34 if ADC1, GPIO14 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_12; // ADC分辨率
#elif CONFIG_IDF_TARGET_ESP32S2
static const adc_channel_t channel = ADC_CHANNEL_6; // GPIO7 if ADC1, GPIO17 if ADC2
static const adc_bits_width_t width = ADC_WIDTH_BIT_13;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_11; // ADC 衰减参数。不同的参数决定了 ADC 的范围。
static const adc_unit_t unit = ADC_UNIT_1;
static void check_efuse(void)
{
#if CONFIG_IDF_TARGET_ESP32
// 检查ADC校准值是否烧录到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");
}
#elif CONFIG_IDF_TARGET_ESP32S2
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
printf("eFuse Two Point: Supported\n");
} else {
printf("Cannot retrieve eFuse Two Point calibration values. Default calibration values will be used.\n");
}
#else
#error "This example is configured for ESP32/ESP32S2."
#endif
}
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");
}
}
void app_main(void)
{
//Check if Two Point or Vref are burned into eFuse
check_efuse();
//Configure ADC
if (unit == ADC_UNIT_1) {
adc1_config_width(width);
adc1_config_channel_atten(channel, atten);
} else {
adc2_config_channel_atten((adc2_channel_t)channel, atten);
}
//Characterize ADC
adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars); // 在特定衰减下表征ADC
print_char_val_type(val_type);
//Continuously sample ADC1
while (1) {
uint32_t adc_reading = 0;
//Multisampling
for (int i = 0; i < NO_OF_SAMPLES; i++) {
if (unit == ADC_UNIT_1) {
adc_reading += adc1_get_raw((adc1_channel_t)channel);
} else {
int raw;
adc2_get_raw((adc2_channel_t)channel, width, &raw);
adc_reading += raw;
}
}
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_chars); // 将读数转换为电压
printf("Raw: %d\tVoltage: %dmV\n", adc_reading, voltage);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
ADC2
单次读取示例:
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "driver/dac.h"
#include "esp_system.h"
#define DAC_EXAMPLE_CHANNEL CONFIG_EXAMPLE_DAC_CHANNEL
#define ADC2_EXAMPLE_CHANNEL CONFIG_EXAMPLE_ADC2_CHANNEL
#if CONFIG_IDF_TARGET_ESP32
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#elif CONFIG_IDF_TARGET_ESP32S2
static const adc_bits_width_t width = ADC_WIDTH_BIT_13;
#endif
void app_main(void)
{
uint8_t output_data=0;
int read_raw;
esp_err_t r;
gpio_num_t adc_gpio_num, dac_gpio_num;
r = adc2_pad_get_io_num( ADC2_EXAMPLE_CHANNEL, &adc_gpio_num );
assert( r == ESP_OK );
r = dac_pad_get_io_num( DAC_EXAMPLE_CHANNEL, &dac_gpio_num );
assert( r == ESP_OK );
printf("ADC2 channel %d @ GPIO %d, DAC channel %d @ GPIO %d.\n", ADC2_EXAMPLE_CHANNEL, adc_gpio_num,
DAC_EXAMPLE_CHANNEL + 1, dac_gpio_num );
dac_output_enable( DAC_EXAMPLE_CHANNEL );
//be sure to do the init before using adc2.
printf("adc2_init...\n");
adc2_config_channel_atten( ADC2_EXAMPLE_CHANNEL, ADC_ATTEN_11db );
vTaskDelay(2 * portTICK_PERIOD_MS);
printf("start conversion.\n");
while(1) {
dac_output_voltage( DAC_EXAMPLE_CHANNEL, output_data++ );
r = adc2_get_raw( ADC2_EXAMPLE_CHANNEL, width, &read_raw);
if ( r == ESP_OK ) {
printf("%d: %d\n", output_data, read_raw );
} else if ( r == ESP_ERR_INVALID_STATE ) {
printf("%s: ADC2 not initialized yet.\n", esp_err_to_name(r));
} else if ( r == ESP_ERR_TIMEOUT ) {
//This can not happen in this example. But if WiFi is in use, such error code could be returned.
printf("%s: ADC2 is in use by Wi-Fi.\n", esp_err_to_name(r));
} else {
printf("%s\n", esp_err_to_name(r));
}
vTaskDelay( 2 * portTICK_PERIOD_MS );
}
}