ESP32-S3 ADC 连续转换(Continuous)——DMA 高速采样

4905 字
25 分钟
ESP32-S3 ADC 连续转换(Continuous)——DMA 高速采样

ESP32-S3 ADC 连续转换(Continuous)——DMA 高速采样#

本章重要 API 函数(仅 ADC Continuous 相关)#

API功能参数说明头文件
adc_continuous_new_handle()创建 ADC 连续转换句柄const adc_continuous_handle_cfg_t *hdl_config:句柄配置(帧大小、缓冲区大小)
adc_continuous_handle_t *ret_handle:返回句柄
"esp_adc/adc_continuous.h"
adc_continuous_config()配置 ADC 连续转换参数adc_continuous_handle_t handle:句柄
const adc_continuous_config_t *config:转换配置(通道数组、采样频率、格式)
"esp_adc/adc_continuous.h"
adc_continuous_register_event_callbacks()注册转换完成事件回调adc_continuous_handle_t handle:句柄
const adc_continuous_evt_cbs_t *cbs:回调结构体(on_conv_done
void *user_data:用户数据
"esp_adc/adc_continuous.h"
adc_continuous_start()启动 ADC 连续转换adc_continuous_handle_t handle:句柄"esp_adc/adc_continuous.h"
adc_continuous_stop()停止 ADC 连续转换adc_continuous_handle_t handle:句柄"esp_adc/adc_continuous.h"
adc_init()初始化 ADC 连续转换模式(自定义封装)void:无参数"myadc.h"

ADC 连续转换配置结构体关键字段:

字段说明
.adc_pattern通道配置数组指针(adc_digi_pattern_config_t[]),每个元素含衰减、分辨率、通道号、单元号
.conv_mode转换模式:ADC_CONV_SINGLE_UNIT_1(仅ADC1)/ ADC_CONV_BOTH_UNIT
.format数据输出格式:ADC_DIGI_OUTPUT_FORMAT_TYPE2(每采样4字节,自描述)
.pattern_num每次扫描的通道数量(本项目=2)
.sample_freq_hz总采样频率(本项目=20000Hz),多通道共享

TYPE2 数据格式(每采样 4 字节):

字节内容说明
Byte 0ADC_Result[7:0]12bit 结果的低 8 位
Byte 1ADC_Result[11:8](低4bit有效)12bit 结果的高 4 位
Byte 2Channel_Number通道编号
Byte 3UnitADC 单元编号

12bit 合成公式: adc_value = ((data[n+1] & 0x0F) << 8) | data[n]

注意:SPI、LCD 相关 API 已在前面章节讲解。


本章目录#

点击条目可跳转到对应小节。

章节导航#


学习前后依赖#

方向内容
前置基础ADC 基本概念、模拟电压范围、LCD 显示和中断/DMA 回调安全意识。
本章核心掌握 ADC Continuous 连续采样、DMA 搬运、TYPE2 数据帧解析和多通道轮转。
后续承接承接 13_ADC 的 Oneshot 单次采样对比,并为高速传感器数据采集打基础。

一、从 Oneshot 到 Continuous——为什么需要 DMA#

1.1 速率瓶颈在哪#

01_低速ADC 中,我们用 Oneshot 模式每 500ms 读一次 ADC,轻松愉快。但如果需要 20 kHz 采样率——也就是每秒 20000 次转换——Oneshot 就崩溃了:

Oneshot @ 20 kHz:
CPU 每秒要调用 adc_oneshot_read() 20000 次
每次阻塞等待 ~10μs
总 CPU 占用 ≈ 200ms/s = 20% → 太高了
Continuous @ 20 kHz:
DMA 每 50μs 搬一次数据(无需 CPU)
CPU 只在缓冲区满时处理一次回调
总 CPU 占用 ≈ 可以忽略不计

这就是两种模式的本质区别:Oneshot 的 CPU 开销与采样率成正比,Continuous 的 CPU 开销几乎恒定。

Pasted image 20260704144823
Pasted image 20260704144823

ESP32-S3 片上集成的 SAR ADC 主要参数:

参数说明
分辨率12 bit(可选 9~12 bit)
量程0 ~ 3.3 V(配合衰减配置)
通道数ADC1/ADC2 多通道
输出格式ADC_DIGI_OUTPUT_FORMAT_TYPE1 / TYPE2
最高采样率约 100 kHz(DMA Continuous 模式下)

12 bit 分辨率意味着数字量范围是 0 ~ 4095,对应电压:

电压 = (ADC值 / 4095) × 3.3V

1.2 DMA 的角色:CPU 的”搬运工”#

DMA(Direct Memory Access,直接存储器访问)是理解 Continuous 模式的关键。它允许外设不经过 CPU 直接把数据写入内存:

没有 DMA: 有 DMA:
ADC 完成转换 ADC 完成转换
│ │
▼ ▼
中断 CPU DMA 直接写内存
│ │
▼ ▼
CPU 暂停当前任务 去下一轮采样
CPU 把数据搬到内存 │
│ ▼
▼ 缓冲区满 → 回调
CPU 恢复之前的任务 CPU 批量处理
"每转换一次,CPU 停一次" "搬完了再叫 CPU"

本项目使用 Continuous 连续转换模式,ADC1 的通道 3 和通道 4 以 20 kHz 频率循环采样,结果通过 DMA 存入缓冲区,转换完成触发回调。


二、运行链路树#

app_main()
├─ lcd_init() // 初始化 LCD 显示
└─ adc_init() // 初始化 ADC 连续转换
├─ adc_continuous_new_handle() // 创建 ADC 连续转换句柄
│ 参数:conv_frame_size = 8 // 每帧 8 字节
│ max_store_buf_size = 1024 // 环形缓冲区 1024 字节
├─ adc_continuous_config() // 配置 ADC 模式与通道
│ 参数:pattern_num = 2 // 2 个通道
│ conv_mode = ADC_CONV_SINGLE_UNIT_1 // 仅使用 ADC1
│ format = ADC_DIGI_OUTPUT_FORMAT_TYPE2
│ sample_freq_hz = 20000 // 采样频率 20 kHz
│ adc_pattern[0] = {UNIT_1, CHANNEL_3, 12bit, 11dB}
│ adc_pattern[1] = {UNIT_1, CHANNEL_4, 12bit, 11dB}
├─ adc_continuous_register_event_callbacks() // 注册事件回调
│ 参数:on_conv_done = continuous_callback
└─ adc_continuous_start() // 启动连续转换
▼ 硬件自动循环采样
DMA 搬运 conv_frame_size(8字节) 到缓冲区
▼ 一帧数据准备好
continuous_callback() 被调用
├─ data_value = edata->conv_frame_buffer // 取帧首地址
├─ if (edata->size == 8)
│ │
│ ├─ adc_value_rl = ((data_value[1] & 0x0F) << 8) | data_value[0]
│ │ // 解析通道 3(RL)的 12 bit ADC 值
│ │
│ └─ adc_value_rp = ((data_value[5] & 0x0F) << 8) | data_value[4]
│ // 解析通道 4(RP)的 12 bit ADC 值
└─ return true 表示已处理,缓冲区可释放
app_main() 主循环(500 ms 周期)
├─ lcd_show_num(1, 8, adc_value_rl, ...) // 显示 RL 原始 ADC 值
├─ lcd_show_float(2, 8, adc_value_rl/4095*3.3) // 显示 RL 电压
├─ lcd_show_num(3, 8, adc_value_rp, ...) // 显示 RP 原始 ADC 值
├─ lcd_show_float(4, 8, adc_value_rp/4095*3.3) // 显示 RP 电压
└─ vTaskDelay(500) // 阻塞 500 ms

三、ADC 数据帧解析(TYPE2 格式)#

本项目配置为 ADC_DIGI_OUTPUT_FORMAT_TYPE2,其数据排布如下(每个采样结果占 4 字节):

局部图:TYPE2 双通道帧格式#

一帧 8 字节 = CH3 采样 + CH4 采样
CH3 / RL:
byte0 -> DATA[7:0]
byte1 -> DATA[11:8]
byte2 -> channel = CH3
byte3 -> unit = ADC1
CH4 / RP:
byte4 -> DATA[7:0]
byte5 -> DATA[11:8]
byte6 -> channel = CH4
byte7 -> unit = ADC1
单个 TYPE2 采样 = 4 字节
[data low] -> [data high] -> [channel] -> [unit]

帧大小 conv_frame_size = 8 正好容纳 2 个通道 × 4 字节 = 8 字节。

12 bit ADC 值的合成公式:

adc_value = ((data_value[n+1] & 0x0F) << 8) | data_value[n]
  • data_value[n]:低 8 位
  • data_value[n+1] & 0x0F:高 4 位(TYPE2 中 bit11~bit8 放在该字节低 4 位)

因此:

变量对应通道字节位置
adc_value_rlADC1_CHANNEL_3data_value[0..1]
adc_value_rpADC1_CHANNEL_4data_value[4..5]

TYPE1 vs TYPE2 格式对比#

ESP-IDF 提供两种输出格式,本项目选择 TYPE2:

特性TYPE1TYPE2
每采样字节数2 字节4 字节
通道/单元信息无(需要查表确定)每帧自带,自描述
数据量紧凑冗余但灵活
适用场景单通道固定采样多通道混合采样

TYPE1 格式(未使用)#

每个采样只占 2 字节

字节索引: 0 1
├────────┼────────┤
│DATA[7:0]│DATA[11:8]│
│ 低8位 │ 高4位 │
└────────┴────────┘

需要根据采样顺序查表确定数据来自哪个通道,代码较复杂。

TYPE2 格式(本项目使用)#

每个采样占 4 字节,自带通道号和 ADC 单元信息:

字节索引: 0 1 2 3
├────────┼────────┼────────┼────────┤
│DATA[7:0]│DATA[11:8]│ channel│ unit │
│ 低8位 │ 高4位 │ (通道号)│(ADC单元)│
└────────┴────────┴────────┴────────┘

为什么选择 TYPE2#

  1. 自描述性:每帧数据自带通道号和 ADC 单元信息,即使采样顺序变化也能正确解析
  2. 灵活性:支持混采 ADC1 和 ADC2 的通道(本项目只用了 ADC1)
  3. 调试友好:可以直接从原始数据判断来自哪个通道,便于调试

Pasted image 20260714144321
Pasted image 20260714144321

如果使用 TYPE1 格式,每个采样只占 2 字节,数据更紧凑,但需要根据采样顺序查表确定通道,代码会更复杂。

双通道不是真正”同时”采样#

1. SAR ADC 硬件限制#

ESP32-S3 的 ADC 是 SAR(逐次逼近寄存器)型 ADC,每个 ADC 单元(如 ADC1)内部只有一个转换核心

总览图:ADC 单元内部路径#

GPIO4(CH3) ─┐
├─> MUX ─> SAR 核心 ─> 数字输出
GPIO5(CH4) ─┘
MUX 负责快速切换通道,SAR 核心一次只转换一个通道。

2. 时间复用机制#

通过 MUX(多路选择器)快速切换,在很短的时间内轮流采样多个通道:

时序图:连续采样轮转#

时间轴 ->
|-- CH3 --|-- CH4 --|-- CH3 --|-- CH4 --|
│ │ │ │
▼ ▼ ▼ ▼
SAR转换 SAR转换 SAR转换 SAR转换

状态图:缓冲区满/读取/继续采样#

连续采样
DMA 填充 Frame Buffer
一帧满 8 字节
on_conv_done 回调
任务解析 TYPE2 数据
缓冲区释放/复用 -> 继续采样

在本项目配置中:

.pattern_num = 2, // 每次循环扫描 2 个通道
.sample_freq_hz = 20000, // 总采样率 20 kHz
  • 总采样率 20 kHz:ADC1 在 1 秒内完成 20000 次转换
  • 两通道共享:每个通道实际采样率约 10 kHz
  • 切换时间极短:MUX 切换和 SAR 转换在硬件层面完成,微秒级

3. 帧的概念#

“一帧”就是所有通道各采样一次的完整周期:

一帧时间 = 通道1转换时间 + 通道2转换时间 + MUX切换时间
本项目一帧 = CH3采样 + CH4采样 = 2次转换

当一帧数据(8 字节)收集完成后,DMA 触发中断,调用 continuous_callback,此时回调中拿到的是”一组”数据,看起来像是同时采集的。

4. 为什么看起来像”同时”#

虽然物理上是时序轮询,但在应用层看来像是同时的,原因:

  1. 速度快:20 kHz 采样率下,两次采样间隔只有约 50 μs(微秒),远小于肉眼可见的时间(毫秒级)
  2. 打包交付:一帧数据同时包含两个通道的结果,应用层拿到的就是”一组”数据
  3. DMA 自动搬运:不需要 CPU 干预通道切换,CPU 只在帧完成后处理一次

5. 真正的”同时采样”方案#

如果需要真正意义上的同时采样(两个通道在同一时刻采样),需要:

方案说明
使用 ADC1 + ADC2两个独立的 ADC 单元可以并行采样
外接多通道 ADC通过 I2C/SPI 外接专业的多通道同步采样 ADC

本项目只用了 ADC_CONV_SINGLE_UNIT_1,所以是时间复用方式。


四、重点行注释#

myadc.c L12#

data_value = edata->conv_frame_buffer;

作用:在 on_conv_done 回调中,拿到当前已转换完成的一帧数据的缓冲区首地址。

说明edata->conv_frame_buffer 是 DMA 填充好的原始字节数组,edata->size 表示本帧有效字节数。这里用指针直接引用,避免拷贝。


myadc.c L15-17#

adc_value_rl = ((data_value[1] & 0x0F) << 8) | data_value[0];
adc_value_rp = ((data_value[5] & 0x0F) << 8) | data_value[4];
return true;

作用:把 TYPE2 原始字节解析成两个 12 bit ADC 值。

  • adc_value_rl:ADC1 通道 3 的结果。
  • adc_value_rp:ADC1 通道 4 的结果。
  • return true:通知驱动层,应用层已消费该帧,缓冲区可以释放/复用。

myadc.c L25-26#

.conv_frame_size = 8,
.max_store_buf_size = 1024,
字段含义
conv_frame_size8每次连续转换产生 8 字节数据(2 通道 × 4 字节/通道)
max_store_buf_size1024DMA/驱动内部环形缓冲区最大 1024 字节,可缓存约 128 帧

myadc.c L32-35#

.atten = ADC_ATTEN_DB_11,
.bit_width = ADC_BITWIDTH_12,
.channel = ADC_CHANNEL_3,
.unit = ADC_UNIT_1,

第一个采样通道配置:

字段含义
attenADC_ATTEN_DB_1111 dB 衰减,输入量程约 0~3.3 V
bit_widthADC_BITWIDTH_1212 bit 分辨率(0-4095)
channelADC_CHANNEL_3使用 ADC1 通道 3(对应 GPIO4)
unitADC_UNIT_1属于 ADC1(esp32s3只有ADC1可用)

myadc.c L38-42#

.atten = ADC_ATTEN_DB_11,
.bit_width = ADC_BITWIDTH_12,
.channel = ADC_CHANNEL_4,
.unit = ADC_UNIT_1,

第二个采样通道配置:

字段含义
channelADC_CHANNEL_4使用 ADC1 通道 4(对应 GPIO5)
其他同上衰减 11 dB、12 bit、ADC1

myadc.c L46-50#

.adc_pattern = adc_digi_arr,
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
.pattern_num = 2,
.sample_freq_hz = 20000,
字段含义
adc_patternadc_digi_arr指向通道配置数组
conv_modeADC_CONV_SINGLE_UNIT_1只使用 ADC1 进行转换
formatADC_DIGI_OUTPUT_FORMAT_TYPE2使用 TYPE2 数据格式(带通道号/单元信息)
pattern_num2每次循环扫描 2 个通道
sample_freq_hz20000ADC 总采样率 20 kHz,两通道共享,即每通道约 10 kHz

myadc.c L55#

.on_conv_done = continuous_callback,

作用:注册”转换完成”事件回调函数。

说明:当驱动内部累积到一帧(8 字节)数据时,会在中断/任务上下文调用 continuous_callback,把最新数据交给应用层解析。


五、整体架构#

总览图:ADC 到 LCD 显示链路#

ADC1 CH3/CH4
DMA
Frame Buffer
continuous_callback()
adc_value_rl / adc_value_rp
app_main() -> LCD 显示

局部图:软件层次#

main/main.c
│ 初始化 LCD、ADC,循环显示数值
components/MYADC/myadc.c
│ 封装 ADC 连续转换和回调解析
ESP-IDF adc_continuous
│ 配置 SAR ADC、DMA、事件回调
ESP32-S3 ADC1 + DMA

六、模块实现说明#

6.1 MYADC 组件#

文件:

  • myadc.h
  • myadc.c

头文件职责

  • 导出两个 16 bit 全局变量:adc_value_rladc_value_rp,供 main.c 直接读取。
  • 导出 adc_init() 初始化函数。

源文件职责

  • 创建并配置 adc_continuous_handle
  • 配置双通道采样模式、TYPE2 输出格式、20 kHz 采样率。
  • 注册 on_conv_done 回调,在回调中解析 TYPE2 帧并更新全局变量。

6.2 main 模块#

文件:

  • main.c

职责

  • 初始化 LCD 和 ADC。
  • 在 LCD 上固定显示两行标签:RL、RP 的 ADC 值和电压值。
  • 主循环每 500 ms 读取一次 adc_value_rl / adc_value_rp,更新显示。
  • 电压换算公式:
(float)adc_value / 4095 * 3.3

七、文件简单注释#

myadc.c#

#include "myadc.h"
#include "hal/adc_types.h"
#include "esp_adc/adc_continuous.h"
adc_continuous_handle_t adc_continuous_handle; // ADC 连续转换句柄
uint8_t *data_value; // 当前帧缓冲区指针
uint16_t adc_value_rl; // 通道 3 的 ADC 值(RL)
uint16_t adc_value_rp; // 通道 4 的 ADC 值(RP)
// 转换完成回调:解析一帧 TYPE2 数据,更新两个全局 ADC 值
bool continuous_callback(adc_continuous_handle_t handle,
const adc_continuous_evt_data_t *edata,
void *user_data)
{
data_value = edata->conv_frame_buffer;
if (edata->size == 8)
{
// 组合 12 bit 数据:高 4 位在 data_value[n+1] 低 4 位,低 8 位在 data_value[n]
adc_value_rl = ((data_value[1] & 0x0F) << 8) | data_value[0];
adc_value_rp = ((data_value[5] & 0x0F) << 8) | data_value[4];
return true; // 已处理该帧
}
return false;
}
// 初始化 ADC1 两通道连续转换
void adc_init(void)
{
// 句柄配置:每帧 8 字节,环形缓冲区 1024 字节
adc_continuous_handle_cfg_t adc_contunuous_structure = {
.conv_frame_size = 8,
.max_store_buf_size = 1024,
};
adc_continuous_new_handle(&adc_contunuous_structure, &adc_continuous_handle);
// 采样通道配置:ADC1 CH3、CH4,12 bit,11 dB 衰减
adc_digi_pattern_config_t adc_digi_arr[] = {
{ .atten = ADC_ATTEN_DB_11, .bit_width = ADC_BITWIDTH_12,
.channel = ADC_CHANNEL_3, .unit = ADC_UNIT_1 },
{ .atten = ADC_ATTEN_DB_11, .bit_width = ADC_BITWIDTH_12,
.channel = ADC_CHANNEL_4, .unit = ADC_UNIT_1 }
};
// 连续转换配置:单 ADC1、TYPE2 格式、2 通道、20 kHz
adc_continuous_config_t continuous_config_structure = {
.adc_pattern = adc_digi_arr,
.conv_mode = ADC_CONV_SINGLE_UNIT_1,
.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2,
.pattern_num = 2,
.sample_freq_hz = 20000,
};
adc_continuous_config(adc_continuous_handle, &continuous_config_structure);
// 注册转换完成回调
adc_continuous_evt_cbs_t evt_structure = {
.on_conv_done = continuous_callback,
};
adc_continuous_register_event_callbacks(adc_continuous_handle,
&evt_structure, NULL);
// 启动连续转换
adc_continuous_start(adc_continuous_handle);
}

main.c#

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "lcd.h"
#include "myadc.h"
void app_main(void)
{
lcd_init(); // 初始化 LCD 显示
adc_init(); // 初始化 ADC 连续转换
// 在 LCD 上显示固定标签
lcd_show_string(1, 1, "adc_rl:", GREEN, BLACK);
lcd_show_string(2, 1, "vol_rl:", GREEN, BLACK);
lcd_show_string(3, 1, "adc_rp:", GREEN, BLACK);
lcd_show_string(4, 1, "vol_rp:", GREEN, BLACK);
while (1)
{
// 显示 RL 的 ADC 值和换算电压
lcd_show_num(1, 8, adc_value_rl, 4, YELLOW, BLACK);
lcd_show_float(2, 8, (float)adc_value_rl / 4095 * 3.3, 3, YELLOW, BLACK);
// 显示 RP 的 ADC 值和换算电压
lcd_show_num(3, 8, adc_value_rp, 4, YELLOW, BLACK);
lcd_show_float(4, 8, (float)adc_value_rp / 4095 * 3.3, 3, YELLOW, BLACK);
vTaskDelay(500); // 每 500 ms 刷新一次
}
}

容易踩坑#

现象常见原因排查方向
采样数据错位未按 TYPE2 帧格式解析或通道号判断错误逐字段解析 unit/channel/data,确认 conv_frame_size
回调中数据失效DMA 帧缓冲区生命周期理解错误在有效上下文内复制或立即解析数据
采样丢帧处理速度慢于 DMA 填充速度增大缓冲区、降低采样率或减少回调耗时
电压换算不准衰减、位宽、校准方式与输入范围不匹配确认 attenuation、bitwidth 和 ADC 校准曲线

八、关键要点总结#

  1. 连续 ADC 通过 DMA 自动搬运数据,CPU 只在回调里解析,适合高速采样。
  2. conv_frame_size 必须匹配 pattern_num × 每通道字节数。本例 2 通道 × 4 字节 = 8 字节。
  3. ADC_DIGI_OUTPUT_FORMAT_TYPE2 每个采样占 4 字节,12 bit 有效值跨前两个字节,高 4 位需要 & 0x0F 提取。
  4. 全局变量 adc_value_rl/rp 在回调中更新,主循环读取,是典型的”生产者-消费者”模型;若对实时性要求高,建议加临界区保护。
  5. 电压换算默认按 3.3 V 满量程、12 bit 计算;实际精度受参考电压、衰减、PCB 噪声影响。

Tips#

采样率、帧大小、缓冲区的关系#

这三者是一套精密的时间-空间联动,设计不当就会丢数据:

采样率 20 kHz → 每秒 20000 次转换
帧大小 8 字节 → 每帧包含 2 通道 × 4 字节
每秒帧数 = 20000 / 2 = 10000 帧/秒
每帧触发一次 on_conv_done 回调 → 每秒 10000 次回调!
缓冲区 1024 字节 → 可缓存 1024/8 = 128 帧
缓冲区填满只需 128/10000 ≈ 12.8 ms
→ 如果回调在 12.8ms 内没有处理完,数据就开始覆盖了

设计准则

  • max_store_buf_sizeconv_frame_size × 预期回调处理的最大帧数
  • 回调中只做最低限度的工作(解析数据、更新全局变量),耗时操作放到任务中
  • sample_freq_hz 不是越高越好——选择刚好满足需求的频率,给 DMA 和 CPU 留余量

回调中的线程安全问题#

continuous_callback 可能在中断上下文中执行(取决于 intr_alloc_flags 配置),这意味着:

  1. 不能使用 printf——printf 内部可能触发任务调度,在 ISR 中是不允许的
  2. 不能使用阻塞 API——vTaskDelayxQueueSend(阻塞版本)全都在 ISR 中无效
  3. 全局变量的原子性——adc_value_rl 是 16 bit 变量,ESP32-S3 是 32 位 CPU,单次写入是原子的;但如果扩展为结构体,需要加锁

本项目正确做法:回调中只做简单的位操作 + 赋值,return true 释放缓冲区。所有显示操作在 app_main() 的主循环中完成。

TYPE1 or TYPE2:选择建议#

虽然本项目用 TYPE2(更灵活),但在实际项目中:

场景推荐
单通道大量采集(如音频)TYPE1——每采样 2 字节,缓冲区利用率翻倍
多通道混合采样TYPE2——自描述,不会错位
需要确定数据来源进行调试TYPE2——直接看 channel/unit 字段
对缓冲区内存敏感TYPE1——节省 50% 内存

踩坑实录#

  1. conv_frame_size 不对导致数据错位:这是最常见的问题。TYPE2 下每采样 4 字节,2 通道 = 8 字节/帧。如果配成 conv_frame_size = 4(以为 2 个 TYPE1 采样),驱动只会搬 4 字节——结果是每帧只拿到 CH3 的数据,CH4 永远读不到。

  2. 采样频率设置过高导致驱动 assert:ESP-IDF 内部有采样频率上限检查。如果设 sample_freq_hz = 200000(200 kHz),可能触发 assert(sample_freq_hz <= max)。查看芯片数据手册确定上限。

  3. return false 的后果:回调返回 false 表示”我没处理这帧数据”,驱动会保留该帧不释放。连续返回 false 会导致缓冲区耗尽,后续 DMA 传输全部丢弃。确保正常路径 return true

知识衔接#

  • 上一章01_低速ADC——Oneshot 模式下 CPU 主动触发每次转换。建议先理解 SAR ADC 的基本原理和衰减器概念。
  • 下一站:I2C 通信(BH1750 光传感器)——一个”自带 ADC 的数字传感器”如何通过总线输出光照值。从”单片机 ADC 读模拟电压”过渡到”传感器内部集成 ADC,我只管读数字总线”。
  • 高级扩展:用 Continuous ADC 采集音频数据 → 存入环形缓冲区 → 配合 I2S 或 DAC 输出,构成一个简单的音频处理链路。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

ESP32-S3 ADC 连续转换(Continuous)——DMA 高速采样
https://mjzy.tech/posts/embedded/esp32-s3/02-adc-continuous/
作者
ENKIDU
发布于
2026-08-19
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
ENKIDU
深度学习 & 计算机底层原理 | 用代码理解世界
低语
天之锁永系天与地,而这里系着文字与记忆。欢迎来到乌鲁克的数字荒原。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
49
分类
6
标签
45
总字数
180,771
运行时长
0
最后活动
0 天前
站点信息
构建平台
Vercel
文章许可
CC BY-NC-SA 4.0

文章目录