第二十四章 ESP32S3 ADC 实验
本章介绍使用 ESP32-S3模数转换器(ADC),通过三种方式实现进行通道的电压采集。通过本章将学习到单通道 ADC 的使用。
本章分为如下几个小节:
24.1 ADC 简介
24.2 硬件设计
24.3 程序设计
24.4 下载验证
24.1.1 ADC 简介
生活中接触到的大多数信息是随着时间连续变化的物理量,如声音、温度、压力等。表达这些信息的电信号,称为模拟信号(Analog Signal)。为了方便存储、处理,在计算机系统中,都是数字 0 和 1 信号,将模拟信号(连续信号)转换为数字信号(离散信号)的器件就叫模数转换器(Analog-to-Digital Convert, ADC)。
ADC 转换器可分为: 并行比较型 A/D 转换器(FLASH ADC)、逐次比较型 A/D 转换器(SAR ADC)和双积分式 A/D 转换器(Double Integral ADC)。
A/D 转换过程通常为 4 步:采样、保持、量化和编码,如下图所示。

图 24.1.1.1 A/D 转换过程图
采样:把时间连续变化的信号变换为时间离散的信号。
保持:保持采样信号,使有充分时间转换为数字信号。
量化:把采样保持电路的输出信号用单位量化电压的整数倍表示。
编码:把量化的结果用二进制代码表示。
采样和保持通常是在采样-保持电路中完成,量化和编码通常在 ADC 数字编码电路中完成。
24.1.2 ESP32-S3 ADC 介绍
ESP32-S3 集成了两个 12 位 SAR ADC, ADC1 和 ADC2,支持 20 个模拟通道输入。这 20 个模拟通道输入对应着具体的 IO,并不是随意的 IO 都有模拟输入功能,具备模拟输入功能的引脚如下表所示。

表 24.1.2.1 具备模拟输入功能的引脚
ESP32-S3 的 ADC 模块的分辨率为 12 位,所以 AD转换后的值范围为 0~4095。由于 ESP32-S3 的工作电压为 3.3V,所以当 AD 值为 4095 时,对应的电压为 3.3V;当 AD 值为 0 时,对应的电压为 0V。对于 AD 值和电压值,这里就会有一个简单的关系,如下所示。
![]()
重要注意事项:
(1)ADC2 与 Wi-Fi 的冲突:
严禁在开启 Wi-Fi 或蓝牙时使用 ADC2 的任何函数(如 adc2_get_raw),因为 Wi-Fi 射频模块与 ADC2 共享硬件资源,同时使用会导致系统崩溃或读数失败。如需使用无线功能,请务必只使用 ADC1。
(2)精度、噪声与滤波:
- ESP32 片内 ADC 易受电源噪声影响,读数可能存在波动;
- 软件滤波:最简单的提高稳定性方法是多次采样取平均值;
#define SAMPLE_TIMES 64 // 采样次数
uint32_t adc_reading = 0;
for (int i = 0; i < SAMPLE_TIMES; i++)
{
adc_reading += adc1_get_raw(ADC1_CHANNEL_6);
}
adc_reading /= SAMPLE_TIMES; // 计算平均值
- 硬件滤波:在模拟输入引脚和 GND 之间并联一个 100nF 的电容有助于滤除高频噪声;
- 非线性误差:ESP32 的 ADC 在电压范围的两端可能存在非线性。对精度要求高的应用,建议进行校准或使用外部 ADC 芯片。
(3)输入电压范围:
绝对禁止输入超过 GPIO 引脚绝对最大额定值(通常为 3.6V)的电压,即使使用了衰减器,持续输入超过 3.3V 的电压也可能损坏芯片。
24.2 硬件设计
24.2.1 例程功能
(1)本章实验功能简介:使用 ADC 采集通道 7(IO8)上面的电压,在 SPILCD 模块上面显示ADC 转换值以及换算成电压后的电压值。在 P3 端口中,使用 ,使得 IO8 连接到电位器上,然后将 ADC 采集到的数据和转换后的电压值在 SPILCD 屏中显示。用户可以通过调节电位器的旋钮改变电压值。 LED 闪烁,提示程序运行。
(2)LED 闪烁,指示程序正在运行。
24.2.2 硬件资源
1. LED
LED - IO1
2. XL9555
IIC_SDA-IO41
IIC_SCL-IO42
3. SPILCD
CS-IO21
SCK-IO12
SDA-IO11
DC-IO40(在 P5 端口,使用跳线帽将 IO_SET 和 LCD_DC 相连)
PWR- IO1_3(XL9555)
RST- IO1_2(XL9555)
4. ADC
ADC1_CHANNEL_7
24.2.3 原理图
ADC 属于 ESP32-S3 内部资源,实际上我们只需要软件设置就可以正常工作,另外还需要将待测量的电压源连接到 ADC 通道上,以便 ADC 测量。本实验,我们通过 ADC1 的通道 7(IO8)来采集外部电压值,开发板有一个电位器,可调节的电压范围是: 0~3.3V。我们可以通过跳线帽将 IO8 与电位器连接,如下图所示:

图 24.2.3.1 IO8(对应 AIN 排针)与电位器引脚示意图
使用杜邦线将 ADC 和 RV1 排针连接好后,并下载程序后,就可以用螺丝刀调节电位器变换多种电压值进行测量。
如果想测量其它地方的电压值,只需要 1 根杜邦线,一端接到 AIN 排针上,另外一端就接要测试的电压点。一定要保证测试点的电压在 0~3.3V 的电压范围,否则可能烧坏我们的 ADC,甚至是整个主控芯片。
24.3 程序设计
24.3.1 程序流程图
本实验的程序流程图:

图 24.3.1.1 ADC 实验程序流程图
24.3.2 ADC 函数解析
ESP-IDF 提供了一套 API 来配置 ADC。要使用此功能,需要导入必要的头文件:
#include " esp_adc_cal.h"
#include " driver/adc"
接下来,作者将介绍一些常用的 ESP32-S3 中的 ADC 函数,这些函数的描述及其作用如下:
(1)配置 ADC
该函数用于配置 ADC 各项参数,其函数原型如下所示:
esp_err_t adc_digi_controller_configure(const adc_digi_configuration_t *config);
该函数的形参描述,如下表所示:
| 形参 | 描述 |
|---|---|
| config | 指向 ADC 配置结构体的指针需自行定义,并根据 ADC 的配置参数填充结构体中的成员变量 |
表 24.3.2.1 函数 adc_digi_controller_configure()形参描述
返回值: ESP_OK 表示配置成功。其他表示配置失败。
该函数使用 adc_digi_pattern_config_t 以及 adc_digi_configuration_t 类型的结构体变量传入,该结构体的定义如下所示:
| 结构体 | 成员变量 | 可选参数 |
|---|---|---|
| adc_digi_pattern_config_t | atten: 配置 ADC 衰减程度, adc_atten_t 结构体为索引 |
ADC_ATTEN_DB_0,输入电压 0-1.1V ADC_ATTEN_DB_2_5,输入电压 0-1.5V ADC_ATTEN_DB_6,输入电压 0-2.2V ADC_ATTEN_DB_11,输入电压 0-3.3V |
| channel: 配置 ADC 通道, adc1_channel_t 结构 体为索引 |
ADC1_CHANNEL_0 , ADC1_CHANNEL_5, |
|
| unit: 配置 ADC 单元,adc_unit_t 结构体为索引 |
ADC_UNIT_1 ADC_UNIT_2 |
|
| bit_width: 配置 ADC 位宽,adc_bitwidth_t 结构体为索引 |
ADC_BITWIDTH_DEFAULT ADC_BITWIDTH_9 ADC_BITWIDTH_10 ADC_BITWIDTH_11 ADC_BITWIDTH_12 ADC_BITWIDTH_13 |
表 24.3.2.2 adc_digi_pattern_config_t 结构体参数值描述
| 结构体 | 成员变量 | 可选参数 |
|---|---|---|
| adc_digi_configuration_t | adc_pattern: 配置将要使用的每个 ADC 参数 |
无,该参数为结构体指针,当 adc_digi_pattern_config_t 结构体成员配 置完成后,以指针的方式将参数传递 至 adc_pattern 这一成员中 |
表 24.3.2.3 adc_digi_configuration_t 结构体参数值描述
完成上述结构体参数配置之后,可以将结构传递给 adc_digi_controller_configure () 函数,用以实例化 ADC。
(2)读取 ADC 原始数据
该函数用于读取 ADC 原始数据,其函数原型如下所示:
int adc1_get_raw(adc1_channel_t channel);
该函数的形参描述,如下表所示:
| 形参 | 描述 |
|---|---|
| channel | ADC 通道 |
表 24.3.2.4 函数 adc1_get_raw()形参描述
返回值:其他值表示 ADC 原始数值, -1 表示读取失败。
需要注意:
该函数对指定的 ADC1 通道执行一次阻塞式采样,并返回其原始数字转换值。意味着函数执行期间,CPU 会等待 ADC 转换完成后才返回结果。对于高速采样场景,建议使用 ADC 连续模式 (DMA)。
(3)校准 ADC(模数转换器)
该函数用于校准 ADC(模数转换器)特性的核心函数,它能显著提高 ESP32 系列芯片 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)
该函数会根据您提供的参数以及芯片 eFuse 中存储的出厂校准值(如果有),来表征或“校准”指定 ADC 单元和衰减配置的特性。它会填充一个 esp_adc_cal_characteristics_t结构体,该结构体包含了将原始 ADC 读数转换为精确电压值所需的所有参数和公式(通常是线性关系:y = coeff_a * x + coeff_b)。
ESP32 的片内 ADC 存在一定的非线性误差和基准电压(Vref)偏差(不同芯片个体的 Vref 可能在 1000mV ~ 1200mV 之间波动,而非常理想的 1100mV)。直接使用原始读数计算电压会导致显著误差。此函数通过校准过程,能有效补偿这些误差,获得更精确的电压值。
该函数的形参描述,如下表所示:
|
参数 |
类型 |
说明 |
|---|---|---|
|
|
|
ADC 单元编号。可选 |
|
|
|
衰减配置。决定 ADC 的可测量电压范围。可选: |
|
|
|
ADC 位宽(分辨率)。可选: |
|
|
|
默认参考电压值(单位: mV)。当芯片 eFuse 中没有存储出厂校准值时,将使用此值作为回退。通常填入 |
|
|
|
输出参数。指向一个 |
表 24.3.2.5 函数 esp_adc_cal_characterize()形参描述
返回值:esp_adc_cal_value_t枚举值,指示本次校准所使用的校准值类型,对于调试和了解校准精度来源非常有用:
- ESP_ADC_CAL_VAL_EFUSE_VREF:最佳情况。成功使用了芯片 eFuse 中存储的出厂校准 Vref 值。这是精度最高的方式;
- ESP_ADC_CAL_VAL_EFUSE_TP:使用了 eFuse 中存储的两点校准值(仅用于线性模式),精度也较高;
- ESP_ADC_CAL_VAL_DEFAULT_VREF:eFuse 中未找到校准值,使用了您提供的default_vref参数。精度取决于您提供的默认值是否接近芯片的实际 Vref。
典型使用流程
分配特性结构体:为 esp_adc_cal_characteristics_t分配内存(通常动态分配);
配置 ADC:使用 adc1_config_width()和 adc1_config_channel_atten()等函数配置 ADC 的位宽和衰减;
调用校准函数:调用 esp_adc_cal_characterize()来填充特性结构体;
读取和转换:在循环中读取原始值 (adc1_get_raw()),并使用esp_adc_cal_raw_to_voltage()和之前得到的特性结构体将原始值转换为精确的电压值(单位: mV)。
(4)检查 eFuse 中 ADC 校准数据是否可用
该函数用于检查 eFuse 中是否烧录了特定类型的 ADC 校准数据。需要包含头文件:#include "esp_adc_cal.h",函数原型如下:
esp_err_t esp_adc_cal_check_efuse(esp_adc_cal_value_t value_type)
该函数的形参描述,如下表所示:
|
参数 |
类型 |
说明 |
|---|---|---|
|
|
|
要检查的 eFuse 校准值类型。 |
表 24.3.2.6 函数 esp_adc_cal_check_efuse()形参描述
返回值:
- ESP_OK: 指定的校准类型在 eFuse 中可用;
- ESP_ERR_NOT_SUPPORTED: 请求的校准类型不支持;
- ESP_ERR_INVALID_VERSION: eFuse 中的编码版本不支持;
- 其他错误码表示检查过程中遇到问题。
使用流程:
(1)检查 eFuse 支持情况:在调用 esp_adc_cal_characterize()之前,先使用此函数确认芯片是否支持 eFuse 校准。
(2)根据检查结果选择策略:
如果返回 ESP_OK,则后续可以使用 eFuse 中的校准值进行高精度表征;
如果返回错误(如 ESP_ERR_INVALID_VERSION),则表明 eFuse 中没有可用的校准数据,后续需要依赖用户提供的默认 Vref (default_vref) 进行表征,精度会有所降低。
代码示例:
#include "esp_adc_cal.h"
// 检查 eFuse 中的 Two Point 校准值是否可用
esp_err_t ret = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP);
if (ret == ESP_OK) {
printf("eFuse Two Point 校准值可用,精度较高\n");
} else if (ret == ESP_ERR_INVALID_VERSION) {
printf("eFuse 未烧录校准数据,将使用默认 Vref\n");
} else if (ret == ESP_ERR_NOT_SUPPORTED) {
printf("请求的校准类型不支持\n");
} else {
printf("检查 eFuse 时发生错误: 0x%x\n", ret);
}
24.3.3 ADC 驱动解析
在 IDF版14_adc 例程中,14_adc \components\BSP 路径下新增了一个 ADC 文件夹,分别用于存放 adc.c、adc.h 这两个文件。其中, adc.h 文件负责声明 ADC 相关的函数和变量,而adc.c 文件则实现了 ADC 的驱动代码。下面,我们将详细解析这两个文件的实现内容。
(1) adc.h 文件
#define ADC_ADCX_CHY ADC1_CHANNEL_7
#define DEFAULT_VREF 1100 // 默认参考电压 1100mV
#define ADC_WIDTH ADC_WIDTH_BIT_12
#define ADC_ATTEN ADC_ATTEN_DB_11
/* 函数声明 */
void adc_init(void); /* 初始化ADC */
uint32_t adc_get_voltage_value(uint32_t ch, uint16_t times); /* 获取ADC转换且进行校准后的结果 */
(2)adc.c 文件
/**
* @brief 初始化ADC
* @param 无
* @retval 无
*/
void adc_init(void)
{
adc_digi_pattern_config_t adc1_digi_pattern_config; /* ADC1配置句柄 */
adc_digi_configuration_t adc1_init_config; /* ADC1初始化句柄 */
/* 配置ADC1 */
adc1_digi_pattern_config.atten = ADC_ATTEN_DB_11; /* 配置ADC衰减程度 */
adc1_digi_pattern_config.channel = ADC_ADCX_CHY; /* 配置ADC通道 */
adc1_digi_pattern_config.unit = ADC_UNIT_1; /* 配置ADC单元 */
adc1_digi_pattern_config.bit_width = ADC_BITWIDTH_12; /* 配置ADC位宽 */
adc1_init_config.adc_pattern = &adc1_digi_pattern_config; /* 配置将要使用的每个ADC参数 */
adc_digi_controller_configure(&adc1_init_config); /* 配置ADC1 */
}
从上面的代码中可以看出, ADC 的初始化函数中,配置了 ADC1 通道 7。
使用三种方法实现adc_get_result_average函数:
- 获取原始值方法:
ADC 驱动中,获取 ADC 转换结果的函数,如下所示:
/**
* @brief 获取ADC转换且进行均值滤波后的结果
* @param ch : 通道号, 0~9
* @param times : 获取次数
* @retval 通道ch的times次转换结果平均值
*/
uint32_t adc_get_result_average(uint32_t ch, uint32_t times)
{
uint32_t temp_val = 0;
uint8_t t;
for (t = 0; t < times; t++) /* 获取times次数据 */
{
temp_val += adc1_get_raw(ch);
vTaskDelay(5);
}
return temp_val / times; /* 返回平均值 */
}
以上函数用于获取 ADC 转换结果的函数,函数 adc_get_result_averagr()则是多次调用adc1_get_raw()获取多次 ADC 的转换结果,然后进行均值滤波。
- 使用校准代码实现
esp_adc_cal_characteristics_t *adc_chars = NULL; // 特性结构体指针
/**
* @brief 获取ADC转换且进行均值滤波后的结果
* @param ch : 通道号, 0~9
* @param times : 获取次数
* @retval 通道ch的times次转换结果平均值
*/
uint32_t adc_get_result_average(uint32_t ch, uint16_t times)
{
uint32_t temp_val = 0;
if (0 == times) return 0;
// 检查 eFuse 中的 Two Point 校准值是否可用
esp_err_t ret = esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP_FIT);
if (ret == ESP_OK)
{
printf("eFuse Two Point 校准值可用,精度较高\n");
} else if (ret == ESP_ERR_INVALID_VERSION)
{
printf("eFuse 未烧录校准数据,将使用默认 Vref\n");
} else if (ret == ESP_ERR_NOT_SUPPORTED)
{
printf("请求的校准类型不支持\n");
} else {
printf("检查 eFuse 时发生错误: 0x%x\n", ret);
}
// 分配内存
adc_chars = calloc(1, sizeof(esp_adc_cal_characteristics_t));
// 配置ADC硬件
adc1_config_width(ADC_WIDTH);
adc1_config_channel_atten(ADC_ADCX_CHY, ADC_ATTEN);
// 表征/校准ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_UNIT_1,
ADC_ATTEN,
ADC_WIDTH,
DEFAULT_VREF,
adc_chars);
// (可选)检查校准类型
if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF)
{
printf("使用 eFuse Vref 校准值,精度最高\n");
}
else if (val_type == ESP_ADC_CAL_VAL_DEFAULT_VREF)
{
printf("使用默认 Vref 值\n");
}
for (uint16_t t = 0; t < times; t++)
{
// 读取和转换(在循环中)
uint32_t raw_value = adc1_get_raw(ch);
uint32_t voltage_mv = esp_adc_cal_raw_to_voltage(raw_value, adc_chars); // 使用校准!
printf("Raw: %d\tVoltage: %dmV\n", raw_value, voltage_mv);
temp_val += voltage_mv;
vTaskDelay(5);
}
free(adc_chars); // 释放内存
return temp_val/times;
}
对应main,c修改:
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
uint16_t adcdata;
esp_err_t ret;
float voltage;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
spi2_init(); /* 初始化SPI2 */
xl9555_init(i2c0_master); /* 初始化XL9555 */
lcd_init(); /* 初始化LCD */
adc_init(); /* 初始化ADC */
lcd_show_string(10, 50, 200, 16, 16, "ESP32", RED);
lcd_show_string(10, 70, 200, 16, 16, "ADC TEST", RED);
lcd_show_string(10, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
// lcd_show_string(10, 110, 200, 16, 16, "ADC1_CH7_VAL:0", BLUE);
lcd_show_string(10, 130, 200, 16, 16, "ADC1_CH7_VOL:0.000V", BLUE);
while(1)
{
adcdata = adc_get_result_average(ADC_ADCX_CHY, 10);
voltage = ((float)adcdata/1000);
adcdata = voltage;
lcd_show_xnum(114, 130, adcdata, 1, 16, 0, BLUE); /* 显示电压值的整数部分 */
voltage -= adcdata;/* 把已经显示的整数部分去掉,留下小数部分 */
voltage *= 1000; /* 小数部分乘以 1000 */
lcd_show_xnum(130, 130, voltage, 3, 16, 0x80, BLUE); /* 显示小数部分 */
LED_TOGGLE(); /* LED翻转函数 */
vTaskDelay(100);
}
}
- DMA (直接内存访问) 模式读取 ADC
配置DMA模式读取ADC通常遵循以下步骤:
(1)初始化配置:创建并配置连续模式句柄 (adc_continuous_handle_cfg_t),指定DMA缓冲区大小和转换帧大小;
(2)设置转换参数:配置采样率、转换模式、数据格式等 (adc_continuous_config_t);
(3)设置通道规则:定义要扫描的每个通道的参数(衰减、位宽、所属单元等)(adc_digi_pattern_config_t);
(4)启动转换:调用 adc_continuous_start()开始连续采样;
(5)读取数据:在循环中调用 adc_digi_read_bytes()从DMA缓冲区获取数据;
(6)解析数据:根据设定的数据格式解析读取到的原始数据;
(7)停止与清理:完成采样后,停止转换并释放资源。
具体代码实现如下,对应main,c未修改:
/**
* @brief 初始化ADC
* @param 无
* @retval 无
*/
void adc_init(void)
{
adc_digi_pattern_config_t adc1_digi_pattern_config; /* ADC1配置句柄 */
adc_digi_configuration_t adc1_init_config; /* ADC1初始化句柄 */
adc_digi_init_config_t adc_digi_config = {
.max_store_buf_size = 102400,
.conv_num_each_intr = 256,
.adc1_chan_mask = BIT(ADC_ADCX_CHY), // 只启用目标通道
};
ESP_ERROR_CHECK(adc_digi_initialize(&adc_digi_config));
/* 配置ADC1 */
adc1_digi_pattern_config.atten = ADC_ATTEN_DB_11; /* 配置ADC衰减程度 */
adc1_digi_pattern_config.channel = ADC_ADCX_CHY; /* 配置ADC通道 */
adc1_digi_pattern_config.unit = ADC_UNIT_1; /* 配置ADC单元 */
adc1_digi_pattern_config.bit_width = ADC_BITWIDTH_12; /* 配置ADC位宽 */
adc1_init_config.pattern_num = 1;
adc1_init_config.adc_pattern = &adc1_digi_pattern_config; /* 配置将要使用的每个ADC参数 */
adc1_init_config.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2;
adc1_init_config.sample_freq_hz = 20000;
adc1_init_config.conv_limit_en = 0;
adc1_init_config.conv_mode = ADC_CONV_SINGLE_UNIT_1;
ESP_ERROR_CHECK(adc_digi_controller_configure(&adc1_init_config)); /* 配置ADC1 */
// 启动连续转换
ESP_ERROR_CHECK(adc_digi_start());
}
/**
* @brief 获取ADC转换且进行均值滤波后的结果
* @param ch : 通道号, 0~9
* @param times : 获取次数
* @retval 通道ch的times次转换结果平均值
*/
#define READ_LENGTH 256
uint32_t adc_get_result_average(uint32_t ch, uint32_t times)
{
// 循环读取并解析数据
uint32_t raw_value = 0;
uint32_t valid_samples = 0;
uint8_t buffer[READ_LENGTH] = {0};
uint32_t read_byte_num = 0;
// 循环直到收集足够的样本
while (valid_samples < times)
{
esp_err_t ret = adc_digi_read_bytes(buffer, READ_LENGTH, &read_byte_num, 100);
printf("ret: 0x%x, read_byte_num: %d\n", ret, read_byte_num);
if (ret == ESP_OK && read_byte_num > 0)
{
// 解析数据,只处理目标通道
for (uint32_t i = 0; i < read_byte_num; i += SOC_ADC_DIGI_RESULT_BYTES)
{
adc_digi_output_data_t *p = (adc_digi_output_data_t *)&buffer[i];
// 检查是否为目标通道
if (p->type2.unit == 0 && p->type2.channel == ch)
{
uint16_t sample = p->type2.data & 0xFFF; //数据没有校准
raw_value += sample;
valid_samples++;
if (valid_samples >= times)
break;
}
}
}
else if (ret == ESP_ERR_TIMEOUT)
{
// 短暂延迟后重试
vTaskDelay(pdMS_TO_TICKS(1));
}
else
{
printf("ADC读取错误: 0x%x\n", ret);
break;
}
}
printf("valid_samples: %d\n", valid_samples);
return (valid_samples > 0) ? (raw_value / valid_samples) : 0;
}
24.3.4 CMakeLists.txt 文件
打开本实验 BSP 下的 CMakeLists.txt 文件,其内容如下所示:
set(src_dirs
ADC
IIC
LCD
LED
SPI
XL9555)
set(include_dirs
ADC
IIC
LCD
LED
SPI
XL9555)
set(requires
driver
esp_adc)
idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)
24.3.5 实验应用代码
打开 main/main.c 文件,该文件定义了工程入口函数,名为 app_main。该函数代码如下。
i2c_obj_t i2c0_master;
/**
* @brief 程序入口
* @param 无
* @retval 无
*/
void app_main(void)
{
uint16_t adcdata;
float voltage;
esp_err_t ret;
ret = nvs_flash_init(); /* 初始化NVS */
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
led_init(); /* 初始化LED */
i2c0_master = iic_init(I2C_NUM_0); /* 初始化IIC0 */
spi2_init(); /* 初始化SPI2 */
xl9555_init(i2c0_master); /* 初始化XL9555 */
lcd_init(); /* 初始化LCD */
adc_init(); /* 初始化ADC */
lcd_show_string(10, 50, 200, 16, 16, "ESP32", RED);
lcd_show_string(10, 70, 200, 16, 16, "ADC TEST", RED);
lcd_show_string(10, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(10, 110, 200, 16, 16, "ADC1_CH7_VAL:0", BLUE);
lcd_show_string(10, 130, 200, 16, 16, "ADC1_CH7_VOL:0.000V", BLUE);
while(1)
{
adcdata = adc_get_result_average(ADC_ADCX_CHY, 20);
lcd_show_xnum(114, 110, adcdata, 5, 16, 0, BLUE); /* 显示ADC采样后的原始值 */
voltage = (float)adcdata * (3.3 / 4095); /* 获取计算后的带小数的实际电压值 */
adcdata = voltage; /* 赋值整数部分给adcx变量 */
lcd_show_xnum(114, 130, adcdata, 1, 16, 0, BLUE); /* 显示电压值的整数部分 */
voltage -= adcdata; /* 把已经显示的整数部分去掉,留下小数部分 */
voltage *= 1000; /* 小数部分乘以1000 */
lcd_show_xnum(130, 130, voltage, 3, 16, 0x80, BLUE); /* 显示小数部分 */
LED_TOGGLE(); /* LED翻转函数 */
vTaskDelay(100);
}
}
从上面的代码中可以看出,在进行完包括 ADC 的所有初始化工作后,便不断地获取 ADC1通道 7 进行 10 次转换后经过均值滤波后的结果,并将该原始值显示在 LCD 上,同时还通过该电压的原始值计算出了电压的模拟量,并在 LCD 上进行显示。
24.4 下载验证
在完成编译和烧录操作后,可以看到 LCD 上实时刷新显示着 ADC1 通道 7(IO8 引脚)采集到电压的数字量和模拟量,此时可以通过杜邦线给 IO8 引脚接入不同的电压值(注意共地,且输入电压不能超过 3.3V,否则可能损坏开发板),可以看到 LCD 上显示的电压数字量和模拟量也随之改变。


图 24.4.1 SPI LCD 显示效果图
更多推荐


所有评论(0)