这里有三个例程,相关度很高,就写在一起了。
ESP32 集成了 2 个 SAR(逐次逼近寄存器)ADC,总共支持 18 个测量通道(模拟使能引脚)。
逐次逼近ADC的原理可以看这篇博客,写的很简练明了:逐次逼近比较型adc原理_niepangu的博客-CSDN博客
基准电压的作用可以看这个:ADC参考电压有多重要? - 知乎
ADC1和ADC2的例程很相似,因此只分析前者。
ADC1
本示例说明如何配置 ADC1 并读取连接到 GPIO 引脚的电压。
代码流程
这里有一个流程图,如果没有显示,请刷新一下网页。
graph TD
检查校准值是否烧录到eFuse中-->配置ADC的位宽和衰减-->创建含有ADC各项参数的结构体并初始化-->多次采样后取平均-->将读数转换为电压值
头文件
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"//adc相关的头文件
#include "esp_adc_cal.h"//校正由芯片间ADC基准电压(Vref)变化引起的测量电压的差异
变量和宏定义
#define DEFAULT_VREF 1100 //默认基准电压,使用adc2_vref_to_gpio()获得更好的估计
#define NO_OF_SAMPLES 64 //Multisampling 多采样的次数
static esp_adc_cal_characteristics_t *adc_chars;//存储ADC相关特征的结构体
//配置ADC的通道、位宽、衰减、数量
#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;
#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_0;
static const adc_unit_t unit = ADC_UNIT_1;
函数
检查eFuse函数
eFuse翻译为电子保险丝,可以对芯片进行动态实时重新编程。这里的作用是存储。由于本身特性的限制,eFuse的可读次数是有限的。
本函数用于从eFuse中读取芯片是否已烧录了Two Point和eFuse Vref。校准值用于生成特性曲线,以解释特定ESP32芯片的 ADC 基准电压的变化。ESP32有3个校准值来源。
- Two Point表示ADC在150 mV和850 mV时的读数。为了获得更准确的校准结果,用户应测量这些值并将其刻录到 eFuse BLOCK3 中。
- eFuse Vref 表示真正的 ADC 基准电压。该值在工厂校准期间测量并烧录到 eFuse BLOCK0 中。
- 默认Vref是用户在字符化期间作为参数提供的ADC基准电压的估计值。如果两点或eFuse Vref 值不可用,将使用默认 Vref。
esp_adc_cal_check_efuse()用于检查 ADC 校准值是否已刻录到eFuse中
static void check_efuse(void)
{
#if CONFIG_IDF_TARGET_ESP32
//Check if TP is burned into 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
//检查校准值是否烧录到eFuse中
check_efuse();
//Configure ADC
if (unit == ADC_UNIT_1) {
adc1_config_width(width);//配置ADC1的捕获宽度
adc1_config_channel_atten(channel, atten);//配置通道1的衰减值
} else {
adc2_config_channel_atten((adc2_channel_t)channel, atten);
}
//Characterize ADC
adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));//为ADC特征结构体分配空间
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars);//初始化该结构体,参数DEFAULT_VREF仅在ESP32且eFuse校准值无法使用时有效
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) {
//从单通道中读取ADC1的读数
adc_reading += adc1_get_raw((adc1_channel_t)channel);
} else {
int raw;
//从单通道中读取ADC2的读数
//如通过esp_wifi_start()启动Wi-Fi,则此函数将始终失败并显示ESP_ERR_TIMEOUT
adc2_get_raw((adc2_channel_t)channel, width, &raw);
adc_reading += raw;
}
}
//计算读数的均值
adc_reading /= NO_OF_SAMPLES;
//Convert adc_reading to voltage in mV
//根据ADC的特征将ADC读数转换为以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));
}
}
Single_Read
本例程包含以下内容:
- 如何使用ADC驱动的单读取函数从GPIO引脚获取单个ADC读数
- 如何使用ADC校准函数获得校准结果(以mV为单位)
本例程与之前部分差不多,只不过是把读取次数变为1次。
变量和宏定义
头文件与之前的部分基本相同,不再赘述。
//ADC Channels ADC通道
#if CONFIG_IDF_TARGET_ESP32
#define ADC1_EXAMPLE_CHAN0 ADC1_CHANNEL_6
#define ADC2_EXAMPLE_CHAN0 ADC2_CHANNEL_0
//通道标签
static const char *TAG_CH[2][10] = {{"ADC1_CH6"}, {"ADC2_CH0"}};
#else
#define ADC1_EXAMPLE_CHAN0 ADC1_CHANNEL_2
#define ADC2_EXAMPLE_CHAN0 ADC2_CHANNEL_0
static const char *TAG_CH[2][10] = {{"ADC1_CH2"}, {"ADC2_CH0"}};
#endif
//ADC Attenuation ADC衰减
#define ADC_EXAMPLE_ATTEN ADC_ATTEN_DB_11
//ADC Calibration ADC校准
#if CONFIG_IDF_TARGET_ESP32
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_VREF
#elif CONFIG_IDF_TARGET_ESP32S2
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32C3
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP
#elif CONFIG_IDF_TARGET_ESP32S3
#define ADC_EXAMPLE_CALI_SCHEME ESP_ADC_CAL_VAL_EFUSE_TP_FIT
#endif
static int adc_raw[2][10];
static const char *TAG = "ADC SINGLE";
//ADC1、2的特征
static esp_adc_cal_characteristics_t adc1_chars;
static esp_adc_cal_characteristics_t adc2_chars;
检查校准值并初始化ADC特征
static bool adc_calibration_init(void)
{
esp_err_t ret;
bool cali_enable = false;
//检查芯片是否支持校准值eFuse Vref
ret = esp_adc_cal_check_efuse(ADC_EXAMPLE_CALI_SCHEME);
if (ret == ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG, "Calibration scheme not supported, skip software calibration");
} else if (ret == ESP_ERR_INVALID_VERSION) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else if (ret == ESP_OK) {
//初始化ADC1/2的特征
cali_enable = true;
esp_adc_cal_characterize(ADC_UNIT_1, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc1_chars);
esp_adc_cal_characterize(ADC_UNIT_2, ADC_EXAMPLE_ATTEN, ADC_WIDTH_BIT_DEFAULT, 0, &adc2_chars);
} else {
ESP_LOGE(TAG, "Invalid arg");
}
return cali_enable;
}
主函数
void app_main(void)
{
esp_err_t ret = ESP_OK;
uint32_t voltage = 0;
bool cali_enable = adc_calibration_init();
//ADC1 config
//配置ADC1的位宽及其通道的衰减值
ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_DEFAULT));
ESP_ERROR_CHECK(adc1_config_channel_atten(ADC1_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));
//ADC2 config
ESP_ERROR_CHECK(adc2_config_channel_atten(ADC2_EXAMPLE_CHAN0, ADC_EXAMPLE_ATTEN));
while (1) {
//从单个通道获取ADC1读数
adc_raw[0][0] = adc1_get_raw(ADC1_EXAMPLE_CHAN0);
ESP_LOGI(TAG_CH[0][0], "raw data: %d", adc_raw[0][0]);
if (cali_enable) {
//将读数转换为电压
voltage = esp_adc_cal_raw_to_voltage(adc_raw[0][0], &adc1_chars);
ESP_LOGI(TAG_CH[0][0], "cali data: %d mV", voltage);
}
vTaskDelay(pdMS_TO_TICKS(1000));
do {
//从单个通道获取ADC2读数
ret = adc2_get_raw(ADC2_EXAMPLE_CHAN0, ADC_WIDTH_BIT_DEFAULT, &adc_raw[1][0]);
} while (ret == ESP_ERR_INVALID_STATE);
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG_CH[1][0], "raw data: %d", adc_raw[1][0]);
if (cali_enable) {
//将读数转换为电压
voltage = esp_adc_cal_raw_to_voltage(adc_raw[1][0], &adc2_chars);
ESP_LOGI(TAG_CH[1][0], "cali data: %d mV", voltage);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}