SOC-ESP32S3部分:19-ADC模数转换
飞书文档https://x509p6c8to.feishu.cn/wiki/XycAwmO6Niitdtka1RAcclYfnvf
ESP32-S3 集成了两个 12 位 SAR ADC,共支持 20 个模拟通道输入。
SAR ADC 管脚通过 IO MUX 与 GPIO1 ~ GPIO20、RTC_GPIO1 ~ RTC_GPIO20、触摸传感器接口、UART 接口、SPI 接口、以及 USB_D- 和 USB_D+ 管脚复用。
| |
所以这里有一个点需要注意,并不是所有IO都支持ADC功能,只有特定IO支持。
在ESP32S3中如何使用ADC功能呢?分为以下几步
- 创建ADC单元实例
- 配置ADC通道
- 读取转换结果
- 创建校准方案
- 转换为实际电压值
创建 ADC 单元实例
adc_oneshot_new_unit 函数用于创建一个新的 ADC 单次采样单元,并根据提供的配置进行初始化。
esp_err_t adc_oneshot_new_unit(const adc_oneshot_unit_init_cfg_t *init_config, adc_oneshot_unit_handle_t *out_handle);
参数说明
const adc_oneshot_unit_init_cfg_t *init_config
指向 adc_oneshot_unit_init_cfg_t 结构体的指针,该结构体包含 ADC 单次采样模式的初始化配置信息。adc_oneshot_unit_handle_t *out_handle
输出参数,用于返回新创建的 ADC 单次采样单元的句柄。通过这个句柄可以对 ADC 进行后续操作,如配置通道、启动转换等。
adc_oneshot_unit_init_cfg_t用于配置 ADC(模数转换器)的单次采样模式。
typedef struct {adc_unit_t unit_id; adc_oneshot_clk_src_t clk_src; adc_ulp_mode_t ulp_mode;
} adc_oneshot_unit_init_cfg_t;adc_unit_t unit_id
表示使用的 ADC 单元。
ADC选择,用于表示不同的 ADC 单元,可选ADC_UNIT_1 和 ADC_UNIT_2 等,分别代表不同的 ADC 硬件单元。adc_oneshot_clk_src_t clk_src
选择 ADC 的时钟源。设置为 0 时,驱动程序将使用默认时钟源adc_ulp_mode_t ulp_mode
设置是否支持 ADC 在超低功耗模式下工作。
使用参考:
adc_oneshot_unit_init_cfg_t init_config1 = {.unit_id = ADC_UNIT_1,.ulp_mode = ADC_ULP_MODE_DISABLE,
};adc_oneshot_unit_handle_t adc1_handle;
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
配置ADC通道参数
创建 ADC 单元实例后,通过adc_oneshot_config_channel 函数配置 ADC 单次采样模式下的特定通道。
esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle, const adc_channel_t *channel, const adc_oneshot_chan_cfg_t *chan_cfg);
参数说明
adc_oneshot_unit_handle_t handle:
ADC 单次采样单元的句柄,由 adc_oneshot_new_unit 函数创建并返回。const adc_channel_t *channel:
指向 adc_channel_t 类型的指针,表示要配置的 ADC 通道(例如 ADC1_CHANNEL_0、ADC2_CHANNEL_1 等)。const adc_oneshot_chan_cfg_t *chan_cfg:
指向 adc_oneshot_chan_cfg_t 结构体的指针
设置 adc_oneshot_chan_cfg_t 配置 ADC IO 以测量模拟信号,具体如下:
typedef struct {adc_atten_t atten; ///< 衰减等级adc_bitwidth_t bitwidth; ///< 位宽
} adc_oneshot_chan_cfg_t;字段说明
atten
含义:表示 ADC 输入信号的衰减等级。
ADC 模块在测量高电压时需要适当的衰减以防止输入过载。常见的衰减等级包括:
ADC_ATTEN_DB_0: 无衰减(适用于低电压信号)
ADC_ATTEN_DB_2_5: 2.5 dB 衰减
ADC_ATTEN_DB_6: 6 dB 衰减
ADC_ATTEN_DB_12: 12 dB 衰减bitwidth
含义:表示 ADC 转换结果的位宽。位宽决定了 ADC 转换结果的精度和范围。常见的位宽包括:
ADC_BITWIDTH_DEFAULT: 默认位宽(通常为 12 位)
ADC_BITWIDTH_9: 9 位
ADC_BITWIDTH_10: 10 位
ADC_BITWIDTH_11: 11 位
ADC_BITWIDTH_12: 12 位
使用参考:
adc_oneshot_chan_cfg_t config = {.atten = ADC_ATTEN_DB_12,.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_0, &config));
读取转换结果
完成上述配置后,ADC 即可测量来自配置好的 ADC 通道的模拟信号。调用 adc_oneshot_read 可以获取 ADC 通道的原始转换结果。
esp_err_t adc_oneshot_read(adc_oneshot_unit_handle_t handle, const adc_channel_t *channel, int32_t *out_raw);
参数说明
adc_oneshot_unit_handle_t handle:
ADC 单次采样单元的句柄,由 adc_oneshot_new_unit 函数创建并返回。const adc_channel_t *channel:
指向 adc_channel_t 类型的指针,表示要读取的 ADC 通道。int32_t *out_raw:
输出参数,用于返回 ADC 转换后的原始数据值。
使用参考
static int adc_ch2_raw;
static int adc_ch3_raw;
adc_oneshot_read(adc1_handle, ADC_CHANNEL_0, &adc_ch2_raw);
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, ADC_CHANNEL_0, adc_ch2_raw);
创建校准方案
为了让采集的数据更准确,我们可以使用官方提供的校准算法,ESP-IDF 提供的 ADC 校准方案基于芯片上某些与 ADC 校准相关的 eFuse 位的值。乐鑫模组已在出厂时完成烧录,无需用户额外烧录。
创建校准方案使用adc_cali_create_scheme_curve_fitting函数
创建校准方案
esp_err_t adc_cali_create_scheme_curve_fitting(const adc_cali_curve_fitting_config_t *config, adc_cali_handle_t *ret_handle);
参数说明
const adc_cali_curve_fitting_config_t *config:
指向 adc_cali_curve_fitting_config_t 结构体的指针,包含曲线拟合所需的配置信息。adc_cali_handle_t *ret_handle:
指向 adc_cali_handle_t 类型的指针,用于存储创建的 ADC 校准方案的句柄。// 定义并初始化曲线拟合校准配置结构体
adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit, // 设置 ADC 单元(例如 ADC_UNIT_1 或 ADC_UNIT_2).atten = atten, // 设置 ADC 输入信号的衰减等级(例如 ADC_ATTEN_DB_11).bitwidth = ADC_BITWIDTH_DEFAULT, // 设置 ADC 转换结果的位宽,默认为 12 位
};
// 创建曲线拟合校准方案,并将结果存储在 handle 中
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &handle));
使用参考:
// 定义并初始化曲线拟合校准配置结构体,结构体的数据和上述ADC采集保持一致
adc_cali_curve_fitting_config_t cali_config = {.unit_id = ADC_UNIT_1, .atten = ADC_ATTEN_DB_12,.chan = ADC_CHANNEL_2,.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &handle));
转换为实际电压值
adc_cali_raw_to_voltage 函数用于将 ADC 转换后的原始数据值转换为实际的电压值。
esp_err_t adc_cali_raw_to_voltage(adc_cali_handle_t handle, int raw, int *out_voltage);
参数说明
adc_cali_handle_t handleADC :
校准句柄,由 adc_cali_create_scheme_curve_fitting创建函数返回。int rawADC:
转换后的原始数据值。int *out_voltage :
输出参数,用于返回校准后的电压值(单位:毫伏,mV)。
使用参考:
int voltage;
// 调用 adc_cali_raw_to_voltage 进行转换
esp_err_t ret = adc_cali_raw_to_voltage(cali_handle, adc_ch2_raw, &voltage);
最终参考
我们需要采集底板的滑动电阻器的值,从原理图可以看到,滑动电阻器是接到GPIO1上的
GPIO1支持的ADC控制器和通道是固定的,可从课程开始的介绍图中看到,对应ADC1 通道0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "soc/soc_caps.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"const static char *TAG = "EXAMPLE";// 定义ADC1通道0对应的GPIO引脚
#define EXAMPLE_ADC1_UNIT ADC_UNIT_1
#define EXAMPLE_ADC1_CHAN0 ADC_CHANNEL_0 // ADC1 CH0 对应板卡的滑动电阻器GPIO0
#define EXAMPLE_ADC_ATTEN ADC_ATTEN_DB_12 // 设置衰减为12dB// 定义全局变量存储ADC原始数据和校准后的电压值
static int adc_raw_ch0;
static int voltage_ch0;// 初始化ADC校准
static bool example_adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
{esp_err_t ret = ESP_FAIL;bool calibrated = false;adc_cali_curve_fitting_config_t cali_config = {.unit_id = unit, // ADC单元编号.chan = channel, // ADC通道编号.atten = atten, // 衰减值.bitwidth = ADC_BITWIDTH_DEFAULT, // 位宽};// 创建曲线拟合校准方案ret = adc_cali_create_scheme_curve_fitting(&cali_config, out_handle);if (ret == ESP_OK){calibrated = true;}return calibrated;
}void app_main(void)
{//-------------ADC1 Init---------------//adc_oneshot_unit_handle_t adc1_handle;adc_oneshot_unit_init_cfg_t init_config1 = {.unit_id = EXAMPLE_ADC1_UNIT, // 设置ADC单元为ADC1};// 初始化ADC1单元ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));//-------------ADC1 Config---------------//adc_oneshot_chan_cfg_t config = {.atten = EXAMPLE_ADC_ATTEN, // 设置衰减值为12dB.bitwidth = ADC_BITWIDTH_DEFAULT, // 设置位宽为默认值};// 配置ADC1通道0ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));//-------------ADC1 Calibration Init---------------//adc_cali_handle_t adc1_cali_chan0_handle = NULL;// 初始化ADC1通道0的校准bool do_calibration1_chan0 = example_adc_calibration_init(EXAMPLE_ADC1_UNIT, EXAMPLE_ADC1_CHAN0, EXAMPLE_ADC_ATTEN, &adc1_cali_chan0_handle);while (1){// 读取ADC1通道0的原始数据ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw_ch0));ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", EXAMPLE_ADC1_UNIT + 1, EXAMPLE_ADC1_CHAN0, adc_raw_ch0);// 如果通道0进行了校准,将原始数据转换为电压值if (do_calibration1_chan0){ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, adc_raw_ch0, &voltage_ch0));ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", EXAMPLE_ADC1_UNIT + 1, EXAMPLE_ADC1_CHAN0, voltage_ch0);}// 延迟1秒vTaskDelay(pdMS_TO_TICKS(1000));}
}
烧录后,使用螺丝刀滑动电阻器,就可以看到读取的ADC值变化啦