本文章 ,解释如何使用它 ESP-IDF 构建 ESP32 程序,发表文章
CSDN 请求进入目录 _ O x是否进入ESP32教学(基于ESP-IDF)? 确定
文章目录
-
- 一、简介
-
- 1、两个 ADC 通道简介:
- 2、ADC 衰减 —— 改变 ADC 量程和精度
- 3.减少测量误差 & ADC校准:
-
- ① 降低噪声干扰
- ② ★ADC 校准 —— ADC-Voltage 特征曲线
- 二、使用 ADC1
- 三、使用 ADC2
- 四、使用 ADC 测量电压的校准转换
-
- 1、获取 ADC-Voltage 特征曲线
- 2、将ADC值转换位电压
- 3、使用示例
- 五、ESP32 霍尔传感器内部的使用
- 六、ADC 示例
一、简介
ESP32集成了两个 12
位SAR(一次接近寄存器)
adc ,支持18个测量通道。
1、两个 ADC 通道简介:
- 支持 8 包括:GPIO32 - GPIO39(不按顺序)
- 支持 10 包括:GPIO0, GPIO2, GPIO4, GPIO12 - GPIO15, GOIO25 - GPIO27(不按顺序)
ESP32 内置霍尔传感器 ADC1 的通道0和3(GPIO36 和 GPIO39)。
两个 ADC 的 API 包含在driver/adc.h
#include "driver/adc.h"
部分开发板上的一些引脚会有特殊作用,可能有一些ADC2.占用引脚。 例如,在官方开发板中:
- ESP32 DevKitC 开发板: 由于板上的自动下载电路被占用。
- ESP-WROVER-KIT 开发板: 由于各种不同的作用,被占用。
另外,ADC2
模块也被Wi-Fi
当它们一起使用时,只有一个会被抢占,这意味着adc2_get_raw()
直到Wi-Fi
停下来,反之亦然。
从未连接到任何信号的引脚读取 ADC 值是 的。
ADC1_Channel | GPIO(ESP32 与 ESP32-S2一致) |
---|---|
ADC1_CHANNEL_0 | GPIO 36 |
ADC1_CHANNEL_1 | GPIO 37 |
ADC1_CHANNEL_2 | GPIO 38 |
ADC1_CHANNEL_3 | GPIO 39 |
ADC1_CHANNEL_4 | GPIO 32 |
ADC1_CHANNEL_5 | GPIO 33 |
ADC1_CHANNEL_6 | GPIO 34 |
ADC1_CHANNEL_7 | GPIO 35 |
ADC2_Channel | GPIO(ESP32) | GPIO(ESP32-S2) |
---|---|---|
ADC2_CHANNEL_0 | ESP32:GPIO 4 | ESP32-S2:GPIO 11 |
ADC2_CHANNEL_1 | ESP32:GPIO 0 | ESP32-S2:GPIO 12 |
ADC2_CHANNEL_2 | ESP32:GPIO 2 | ESP32-S2:GPIO 13 |
ADC2_CHANNEL_3 | ESP32:GPIO 15 | ESP32-S2:GPIO 14 |
ADC2_CHANNEL_4 | ESP32:GPIO 13 | ESP32-S2:GPIO 15 |
ADC2_CHANNEL_5 | ESP32:GPIO 12 | ESP32-S2:GPIO 16 |
ADC2_CHANNEL_6 | ESP32:GPIO 14 | ESP32-S2:GPIO 17 |
ADC2_CHANNEL_7 | ESP32:GPIO 27 | ESP32-S2:GPIO 18 |
ADC2_CHANNEL_8 | ESP32:GPIO 25 | ESP32-S2:GPIO 19 |
ADC2_CHANNEL_9 | ESP32:GPIO 26 | ESP32-S2:GPIO 20 |
2、ADC 衰减 —— 改变 ADC 的量程和精度
ADC模块 能读取电压的范围(量程)有限,因此我们一般给某个 ADC 通道配置一定的衰减,使其读取更大的电压。但是,更大的量程会导致更小的精度。因此根据 ADC 的应用场景,选择适当的衰减级别十分必要。
ESP32 的每一个通道都有提供了4个级别的衰减等级,不同的衰减等级对于的量程在下表列出:注意, ,而是在某衰减等级下测量最精确的推荐测量范围
SoC | 衰减级别(db) | 推荐范围(mV) |
---|---|---|
ESP32 | 0 0 0 | 100 ∼ 950 100 \sim 950 100∼950 |
ESP32 | 2.5 2.5 2.5 | 100 ∼ 1250 100 \sim 1250 100∼1250 |
ESP32 | 6 6 6 | 150 ∼ 1750 150 \sim 1750 150∼1750 |
ESP32 | 11 11 11 | 150 ∼ 2450 150 \sim 2450 150∼2450 |
3、减小测量误差 & ADC校准:
① 最小化噪声干扰
ESP32 ADC 对噪声很敏感,导致 ADC 读数有很大差异。为了减少噪声,用户可以参考以下两种方法:
- 物理层面 —— 在使用时可以在 ADC 输入口上连接一个0.1µF的电容。
- 在单独测量模式下,多次测量取平均值可以减少部分干扰。
- 多重采样模式也可以用来进一步减轻噪声的影响。
下图是 ADC 直接读取 和 采用以上减小误差方法进行读取的ADC值分布散点图

其中纵轴(ADC Reading)代表不同方式读取到的ADC值,横轴(Sample Number)是采样次数。
图例:
- —— 直接测量
- —— 连接电容进行测量
- —— 在有电容的情况下,连续采样64个值求平均值
可见,使用方法减少噪声影响后,ESP32 ADC模块的测量值更精准,趋近于一条直线(残差更小)
② ★ADC 校准 —— ADC-Voltage 特征曲线
直接使用ADC读取到的值是不能直接计算出电压的,要经过重要一步 ——
#include "esp_adc_cal.h"
关于ADC校准的库为esp_adc_cal.h
这个库提供 API 函数用于校正基于 ADC 参考电压 ( V r e f V_{ref} Vref) 。每个设计的ADC参考电压为 1100 m V 1100 \rm mV 1100mV。
—————————— 也叫做 ,作用是就确定被测信号的准确幅值。例如:倘若这个幅值来自供入芯片电源的电压,但是电源电压可能是不稳定的,这就会造成测得得值换算成电压时出现不准确的情况。因此,我们需要一个基准电压通过校正来获取准确的测量电压。
对于不同的参考电压,ADC 值与输入电压值(待测电压)的关系不同。关系如下图:
列出了参考电压分别在: V r e f = 1070 m V V_{ref} = 1070 \rm mV Vref=1070mV (蓝色) 和 V r e f = 1160 m V V_{ref} = 1160 \rm mV Vref=1160mV(橙色) 下的 ADC
值和待测电压 Voltage
的关系。不难看出,这两个值的 很强。我们把这条拟合曲线称为 ADC 模块(在某参考电压下)的 。
在实际应用中,我们调用esp_adc_cal.h
库提供的 API 函数去求得指定参考电压下的 ADC-Voltage 特征曲线,并利用这一条曲线去将 ADC 测量值转换为欲测量的电压Voltage
。开发者可以选择自定义参考电压值,也可以利用ESP32 内部 eFuse
()中储存的出厂参考电压校准值去获取这个曲线。
所以使用ESP32读取到模拟量电压值的步骤是:
- 取样
- 获取特征曲线(只需一次获取,多次使用)
- 调用 API 根据特征曲线转换测量的 Voltage 值
如果想要获取ESP32内部eFuse
中的参考电压,对于模组来说,可以连接电脑,使用以下命令(以 Windows 命令提示符 cmd 举例) 请将–port参数下的COMx替换成你的串口号,如COM6
> %IDF_PATH%/components/esptool_py/esptool/espefuse.py --port COMx adc_info
回显如: 有eFuse
参考电压
ADC VRef calibration: 1093 mV
无eFuse
参考电压,以1100mV作为默认值
ADC VRef calibration: None (1100 mV nominal)
对于一个采用两点校准的芯片,消息将类似于:
ADC VRef calibration: 1149 mV
ADC readings stored in efuse BLK3:
ADC1 Low reading (150 mV): 306
ADC1 High reading (850 mV): 3153
ADC2 Low reading (150 mV): 389
ADC2 High reading (850 mV): 3206
除此之外,ESP32 内部的参考电压也可以手动测量,方法是将此电压输出到(路由到)某个GPIO口上,然后手动测量此GPIO
口和GND
接口之间的电压,就是eFuse
内部的参考电压。将参考电压路由到GPIO的方法是调用API函数adc_vref_to_gpio()
,参数是想要输出电压的GPIO口编号。
提示:此函数里边的 “
_vref_
”意思是参考电压,符号为 V r e f V_{ref} Vref
/* 将参考电压路由到GPIO25上 */
adc_vref_to_gpio(ADC_UNIT_2, GPIO_NUM_25);
二、使用 ADC1
对于 ADC1 我们主要使用3个 API 函数:
adc1_config_width()
—— 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。adc1_config_channel_atten()
—— 用于配置ADC1某个通道的衰减(衰减概念见简介2)adc1_get_raw()
—— 用于读取 1 次 ADC 值,类型为int
,获取值通常为正,除非错误
函数1 | adc1_config_width() |
---|---|
简介 | 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。 |
原型 | esp_err_t adc1_config_width(adc_bits_width_t width_bit) |
返回值 | ESP_OK successESP_ERR_INVALID_ARG Parameter error |
参数 | width_bit ADC1 测量的位宽度 |
typedef enum adc_bit_width{
ADC_WIDTH_BIT_9 = 0,
//ADC capture width is 9Bit.
ADC_WIDTH_BIT_10 = 1,
//ADC capture width is 10Bit.
ADC_WIDTH_BIT_11 = 2,
//ADC capture width is 11Bit.
ADC_WIDTH_BIT_12 = 3,
//ADC capture width is 12Bit.
ADC_WIDTH_MAX,
}adc_bit_width_t;
函数2 | adc1_config_channel_atten() |
---|---|
简介 | 用于配置ADC1某个通道的衰减(衰减概念见简介2) |
原型 | esp_err_t adc1_config_channel_atten(adc1_channel_t channel, adc_atten_t atten) |
返回值 | ESP_OK successESP_ERR_INVALID_ARG Parameter error |
参数 | channel欲配置的 ADC1 通道atten衰减等级,从枚举adc_atten_t 里选 |
typedef enum adc_atten{
ADC_ATTEN_DB_0 = 0,
//No input attenumation, ADC can measure up to approx. 800 mV.
ADC_ATTEN_DB_2_5 = 1,
//The input voltage of ADC will be attenuated extending the range of measurement by about 2.5 dB (1.33 x)
ADC_ATTEN_DB_6 = 2,
//The input voltage of ADC will be attenuated extending the range of measurement by about 6 dB (2 x)
ADC_ATTEN_DB_11 = 3,
//The input voltage of ADC will be attenuated extending the range of measurement by about 11 dB (3.55 x)
ADC_ATTEN_MAX,
}adc_atten_t;
函数3 | adc1_get_raw() |
---|---|
简介 | 用于配置ADC1所有通道的捕获宽度,同时使能ADC1的输出反转。 |
原型 | int adc1_get_raw(adc1_channel_t channel) |
返回值 | -1错误其他值 ADC1获取到的测量值 |
参数 | channel ADC1 通道 |
示例:使用 ADC1 单次读取 channel 0 (GPIO36),位宽 12 BIT,衰减 0 db
#include <driver/adc.h>
#include ...
...
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
int val = adc1_get_raw(ADC1_CHANNEL_0);
...
三、使用 ADC2
配置 ADC2 的衰减使用函数adc2_config_channel_atten()
,与 ADC1 类似。
不同的是 ADC2 不像 ADC1 那样需要一次性配置通道的测量宽度,而是在测量时配置,因此 ADC2 没有单独的用于配置测量宽度的函数。同时,adc2_get_raw()
函数有两个参数,返回值不是直接返回测量值,测量值需要用指针从参数中获取。
函数 | adc2_get_raw() |
---|---|
函数原型 | esp_err_tadc2_get_raw(adc2_channel_t channel, adc_bits_width_t width_bit, int *raw_out) |
返回值 | ESP_OK获取成功ESP_ERR_TIMEOUT ADC2正在其他外设如Wifi使用,获取超时。ESP_ERR_INVALID_STATE控制器状态无效。请再试一次。 |
参数 | channel读取通道width_bit读取位宽*raw_out接受获取值的变量指针 |
使用示例:
#include <driver/adc.h>
...
int read_raw;
adc2_config_channel_atten( ADC2_CHANNEL_7, ADC_ATTEN_0db );
esp_err_t err = adc2_get_raw( ADC2_CHANNEL_7, ADC_WIDTH_12Bit, &read_raw);
// 判断读取是否成功
if (err == ESP_OK) {
printf("读取到的值为:%d\n", read_raw );
} else if ( r == ESP_ERR_TIMEOUT ) {
printf("ADC2 被占用\n");
}
四、使用 ADC 校准转换测量电压
使用以下头文件:
#include "esp_adc_cal.h"
使用 ADC 校准转换测量电压需要两步:
- 获取 ADC-Voltage 特征曲线
- 调用 API 函数将 ADC 值转换位电压
1、获取 ADC-Voltage 特征曲线
函数 | esp_adc_cal_characterize() |
---|---|
功能 | 获取ADC在指定参考电压下和衰减下的特性,并生成 ADC-Voltage 特征曲线 |
函数原型 | esp_adc_cal_value_tesp_adc_cal_characterize(adc_unit_tadc_num, adc_atten_tatten, adc_bits_width_tbit_width, uint32_t default_vref, esp_adc_cal_characteristics_t *chars) |
返回值 | ESP_ADC_CAL_VAL_EFUSE_VREF使用eFuse 中写入的参考电压ESP_ADC_CAL_VAL_EFUSE_TP使用两点表征值确定特征曲线ESP_ADC_CAL_VAL_DEFAULT_VREF使用默认参考电压( 1100 m V 1100 \rm mV 1100mV) |
参数 | adc_num欲获取的ADC编号,ADC1还是ADC2atten衰减值bit_width测量位宽default_vref参考电压(如果使用eFuse值,则此参数无效。所以一般填1100即可)*chars(输出)指向用于存储ADC特征的空结构体的指针 |
2、将ADC值转换位电压
函数 | esp_adc_cal_raw_to_voltage() |
---|---|
功能 | 基于获取到的ADC-Voltage曲线,将ADC读数转换为电压mV |
函数原型 | uint32_t esp_adc_cal_raw_to_voltage(uint32_t adc_reading, constesp_adc_cal_characteristics_t *chars) |
返回值 | 转换到的电压值(mV) |
参数 | adc_reading读取到的 ADC 值*chars指向包含ADC特征的初始化结构的指针 |
3、使用示例
#include "esp_log.h"
...
#include "esp_adc_cal.h"
#include "driver/adc.h"
...
static esp_adc_cal_characteristics_t *adcChar;
...
...
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);
adcChar = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
esp_adc_cal_value_t cal_mode = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adcChar);
if (cal_mode == ESP_ADC_CAL_VAL_DEFAULT_VREF){
ESP_LOGI("USER", "使用默认参考电压");
}else if (cal_val == ESP_ADC_CAL_VAL_EFUSE_VREF){
ESP_LOGI("USER", "使用 eFuse 中的电压");
}
uint32_t readRaw;
uint32_t voltage;
readRaw = adc1_get_raw(ADC1_CHANNEL_5);
voltage = esp_adc_cal_raw_to_voltage(readRaw, adcChar);
...
...
五、ESP32 内部霍尔传感器的使用
使用函数hall_sensor_read()
读取霍尔传感器。读取到的值是 12 位宽 (范围0-4095)。因此 霍尔传感器使用前(即调用hall_sensor_read()
之前)必须通过调用adc1_config_width()
来将ADC1配置位12位宽。
注意:霍尔传感器采用 ADC1 的通道 0 和 3。不要将这些通道配置为ADC通道。同样,也不要将其他任何东西连接到这两个通道对应的 GPIO 上(GPIO36 和 GPIO39),也不要对这两个IO口做任何配置。否则可能会影响传感器对低值信号的测量。
#include <driver/adc.h>
...
adc1_config_width(ADC_WIDTH_BIT_12);
int val = hall_sensor_read();
六、ADC 示例
将 ESP32 eFuse
中的参考电压输出到 ADC2 的 GPIO25上(对应 channel 8),然后用 ADC1 的Channel 5(对应GPIO33)去测量。每 1 秒测量一次,每次连续取样 12 个点求平均值,然后转换成电压。并输出读取到的电压和转换前的ADC值。
因此,将开发板上的 GPIO25 和 GPIO33连接起来即可
#include <iostream>
#include "driver/adc.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_adc_cal.h"
static esp_adc_cal_value_t cal_val;
static esp_adc_cal_characteristics_t *adcChar;
namespace AdcTask {
static void adc_task(void *args);
}
static void AdcTask::adc_task(void *args) {
/*ADC1_channel_5 is GPIO33*/
TickType_t tick = xTaskGetTickCount();
uint32_t readRaws;
uint32_t voltage;
while (true){
readRaws = 0;
for (int i = 0; i < 12; i++){
readRaws += adc1_get_raw(ADC1_CHANNEL_5);
}
readRaws /= 12;
voltage = esp_adc_cal_raw_to_voltage(readRaws, adcChar);
std::cout << "Read OK, voltage is: " << voltage << " mV" << std::endl;
std::cout << "readRaws is: " << readRaws << std::endl;
vTaskDelayUntil(&tick, pdMS_TO_TICKS(1000));
}
}
extern "C" void app_main(void) {
esp_err_t status = adc_vref_to_gpio(ADC_UNIT_2, GPIO_NUM_25);
if (status == ESP_OK) {
std::cout <<"v_ref routed to GPIO" << std::endl;
} else {
std::cout <<"failed to route v_ref" << std::endl;
}
adc1_config_width(adc_bits_width_t::ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_5, ADC_ATTEN_DB_11);
adcChar = (esp_adc_cal_characteristics_t*)calloc(1, sizeof(esp_adc_cal_characteristics_t));
cal_val = esp_adc_cal_characterize(ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adcChar);
if (cal_val == ESP_ADC_CAL_VAL_DEFAULT_VREF){
std::cout << "使用默认参考电压" << std::endl;
}else if (cal_val == ESP_ADC_CAL_VAL_EFUSE_VREF){
std::cout << "使用 eFuse 中的电压" << std::endl;
}
xTaskCreate(AdcTask::adc_task, "ADC", 1024, nullptr, 5, nullptr);
}