☰
零CPU干预:STM32多通道ADC+DMA采集实战指南
2026/10/15 18:35:07 网站建设 项目流程

简介:面向嵌入式初学者与需要快速搭建多通道实时采集方案的开发者,这份以 STM32 为主控的工程包完整演示了 ADC 与 DMA 协同工作的典型流程。工程基于 HAL 库编写,覆盖多通道规则组采样、定时器触发、DMA 循环传输及传输完成中断等关键环节,并在代码中给出可移植的配置框架。压缩包共 219 个文件,其中 61 个头文件与 34 个 C 源文件构成核心源码,配合 .uvprojx 工程文件、CubeMX 的 .ioc 配置以及编译生成的 .hex/.axf 等产物,整体约 8.49MB。资源还包含 .map、.lst、.crf 等构建辅助文件,可帮助分析内存分配、函数调用与编译细节。已有 1682 人学习下载。通过这套工程包,读者既能快速跑通上机实验,也可以对照源码理解 DMA 传输的内存管理、多通道序列设置和采样时间调试等实际调参思路,对后续开发传感器数据采集、环境监控等项目具有直接参考价值。

1. 多通道采集必须 ADC+DMA 的原因:零 CPU 干预才是真后台

做嵌入式采集的人迟早会撞上同一个问题:单通道逐次采样好写,但一到现场,传感器一多,你才发现 MCU 的 CPU 根本忙不过来。用 ADC 中断逐个读通道是最容易想到的方案,但这条路在通道数超过 3 个、采样率又要求不低的时候就走不通了。CPU 频繁进出中断,主循环被割得七零八落,稍微加点显示或通信逻辑,采样就开始抖动,数据出现周期性跳变。

STM32 的 ADC 多通道采集配合 DMA,解决的就是这个痛点:ADC 自己按顺序把多通道扫完,DMA 把结果搬到内存,全程不占 CPU。CPU 只负责在 DMA 传完一轮后拿数据做处理。对做数据采集、信号监测、电池管理系统这类项目的开发者来说,这套组合是 STM32 性价比最高的采集方案。本文从通道序列配置开始,到 DMA 循环模式、数据解析、常见踩坑,再到进阶的双缓冲与多 ADC 同步,把一条路走完。

2. 通道序列与扫描模式:先定规则再谈传输

2.1 扫描模式、连续转换和通道组的正确理解

ADC 多通道采集的第一步不是开 DMA,而是搞清楚 ADC 三种模式怎么组合:扫描模式、连续转换模式、以及间断模式。很多人第一次上手,会误以为把多个通道都使能,ADC 就会自动轮流采样。实际上不会,必须显式配置扫描模式(SCAN)并设置通道序列长度。

扫描模式(SCAN)解决的是“一次转换覆盖哪些通道”的问题。你配置的通道顺序,就是 ADC 转换的先后顺序。比如配置通道顺序为 CH0、CH3、CH7,那么 ADC 会按这个顺序依次转换,转换结果依次存入数据寄存器。连续转换模式(CONT)解决的是“一轮转完要不要继续”的问题。开启后,ADC 完成一轮转换会立即开始新一轮;关闭则转完一轮就停,需要外部触发或软件触发再启动。

实际项目中,我一般把 DMA 和连续转换配合使用:连续转换保证 ADC 始终有最新数据,DMA 循环模式把结果持续搬到内存缓冲区。如果你只需要单次采集,可以开单次模式,触发一次读一轮,但多通道实时监测场景几乎都用连续模式。

// ADC 通道序列初始化示例(以单 ADC 多通道为例) ADC_ChannelConfTypeDef sConfig = {0}; sConfig.ScanConvMode = ADC_SCAN_ENABLE; // 开启扫描模式 sConfig.ContinuousConvMode = ENABLE; // 开启连续转换 sConfig.NbrOfConversion = 4; // 本轮转换 4 个通道 sConfig.DiscontinuousConvMode = DISABLE; // 不使用间断模式 // 依次配置通道,顺序即转换顺序 // 先配 CH0,再配 CH1,CH2,CH3 sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig);

Rank 字段的优先级比 Channel 的枚举顺序高。代码里先配哪个通道,和物理引脚没有关系,完全由 Rank 决定。如果你把 CH3 配到 Rank 1,CH0 配到 Rank 2,那么第一轮转换先出 CH3 的数据,再出 CH0 的数据。这个细节会直接影响 DMA 缓冲区的数据排列,后面解析数据时要严格对照。

采样时间(SamplingTime)也要注意:不同通道的输入阻抗不一样,需要设置的采样时间也不同。ADC 转化时间 = 采样时间 + 固定转换周期,STM32 大部分型号固定转换周期是 12 个 ADC 时钟周期。如果通道接的是高阻抗信号源(比如运放输出串了大电阻),采样时间不够会导致采样值偏低——这是新手经常忽略的问题,后面避坑章节会专门展开。

2.2 DMA 通道映射:外设到内存的方向和内存地址自增

DMA 在 ADC 多通道里的角色,是把 ADC 数据寄存器(外设地址)的数据搬到内存数组(目标地址)。配置要点有三个,缺一不可:方向必须是外设到内存(PERIPH_TO_MEMORY),外设地址固定不变(外设寄存器地址只有一个),内存地址必须自增(要连续写入数组的每个元素)。

DMA 的外设地址填的是 ADC 的数据寄存器地址,不同 STM32 型号把 ADC1 的 DR 地址定义在不同位置,标准库用&hadc1->Instance->DR直接取地址即可,不要手动算地址。内存地址填你自己的缓冲区数组首地址,注意把这个数组定义成全局变量,不要定义在函数内部作为局部变量——函数退出后栈空间释放,DMA 还在往这个地址写数据,就会写坏别的内存区域。

// DMA 初始化配置段 DMA_HandleTypeDef hdma_adc; // 假定 adc_dma_buffer 是已定义的全局数组:uint32_t adc_dma_buffer[4] = {0}; hdma_adc.Instance = DMA2_Stream0; // 不同型号映射不同 hdma_adc.Init.Channel = DMA_CHANNEL_0; // ADC1 对应 DMA 通道,查手册确认 hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; // 方向:外设到内存 hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址不自增 hdma_adc.Init.MemInc = DMA_MINC_ENABLE; // 内存地址自增 hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // 外设数据宽度 32 位 hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // 内存数据宽度 32 位 hdma_adc.Init.Mode = DMA_CIRCULAR; // 循环模式:持续搬运 hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc); __HAL_LINK_DMA(&hadc1, DMA_Handle, hdma_adc); // 将 DMA 挂到 ADC1 上

数据宽度这里有个常见的错误观念:ADC1 的数据寄存器是 32 位的,但实际上只用了低 16 位(或低 12 位,取决于分辨率设置,一般一个 32 位缓冲区内会包含对齐后的数据)。如果你按 16 位搬运,虽然单次传输的数据量减半,但 ADC 的 16 位数据模式下左右对齐规则比较复杂,反而容易错位。常见做法是外设和内存统一设 32 位,一个通道占一个 uint32_t 阵列位置,后续用位操作取数据,代码逻辑清晰很多。

具体 DMA 流和通道编号,不同 STM32 系列差得很大。F1 系列和 F4 系列就不一样,L4 系列又不同。开 CubeMX 会自动分配,手写寄存器的建议先查参考手册的 DMA 请求映射表,不要照抄网上代码——抄错 Stream 和 Channel 的结果是 DMA 根本不触发,ADC 状态寄存器里 DMA 标志一直不置位。

3. HAL 库移植与启动时序:从 CubeMX 生成到手动改代码

3.1 ADC+DMA 初始化顺序为什么必须先配 DMA

初始化顺序是个踩了无数次的坑:应该先初始化 DMA,再初始化 ADC。原因在于,ADC 初始化完成后,如果使能了 DMA 请求,ADC 状态一旦开始转换就会立刻触发 DMA 传输。如果你先初始化 ADC 再初始化 DMA,中间哪怕只有一两毫秒的空档,ADC 已经开始跑,而 DMA 通道还没就绪,首批数据可能丢失或产生 DMA 错误中断。

用 CubeMX 生成的代码一般顺序是对的:先 MX_DMA_Init(),再 MX_ADC1_Init()。但如果你在现有工程上手动添加 ADC 采集功能,很容易把初始化代码追加到末尾,顺序变成先 ADC 后 DMA,这个问题非常隐蔽,程序不报错,但数据偶尔错乱,难以排查。

// 主函数中的初始化顺序示例 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); // 第一步:先初始化 DMA MX_ADC1_Init(); // 第二步:再初始化 ADC // 其他外设初始化... // 启动 ADC 采集 HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_dma_buffer, 4); }

HAL_ADC_Start_DMA的最后一个参数是传输长度,也就是你要搬多少个数据项。注意这里设的是 4,对应 4 个通道一轮的数据量。DMA 循环模式下,每完成 4 个数据的传输会产生一个传输完成中断,然后把缓冲区下标归零,继续新一轮搬运。

关于传输长度还有一个容易被忽略的点:DMA 的传输长度是数据项个数,不是字节数。在你配置数据宽度是 32 位(一个字)的前提下,传输长度 4 表示搬运 4 个字,也就是 16 字节。如果你把长度误写成字节数,DMA 搬运到一半就会停止,缓冲区后半部分永远是旧数据。

3.2 回调函数里做数据处理:从数据寄存器到物理量

DMA 搬运只是把数据从外设搬到内存,真正的处理逻辑在HAL_ADC_ConvCpltCallback回调里。这个回调在每轮转换完成时被触发(循环模式下半轮完成也会触发,取决于配置),你需要在这里解析adc_dma_buffer中的数据。

数据解析的关键是搞清楚每个数组元素对应哪个物理通道。前面提到过,通道顺序由 Rank 决定,所以 buffer[0] 对应 Rank 1 的通道,buffer[1] 对应 Rank 2 的通道,依次类推。如果你在 CubeMX 里是按 CH0、CH1、CH2、CH3 的顺序配的 Rank,那 buffer[0] 就是 CH0,buffer[1] 就是 CH1,不会乱。

// ADC DMA 转换完成回调 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { // 原始数据取低 12 位(12 位分辨率),右对齐模式 uint16_t ch0_raw = adc_dma_buffer[0] & 0x0FFF; uint16_t ch1_raw = adc_dma_buffer[1] & 0x0FFF; uint16_t ch2_raw = adc_dma_buffer[2] & 0x0FFF; uint16_t ch3_raw = adc_dma_buffer[3] & 0x0FFF; // 换算电压:参考电压 3.3V,分辨率 4096 float ch0_volt = (float)ch0_raw * 3.3f / 4095.0f; float ch1_volt = (float)ch1_raw * 3.3f / 4095.0f; // 将电压值转为实际物理量(以 NTC 热敏电阻测温度为例) // 公式:T = 1 / (a + b*ln(R) + c*ln(R)^3) - 273.15 // R 由分压电路和 ch0_volt 计算得到 float ntc_resistance = 10.0f * ch0_volt / (3.3f - ch0_volt); // 10K 上拉电阻 float temperature = calc_ntc_temp(ntc_resistance); // 查表或 Steinhart-Hart 公式计算 } }

为什么取低 12 位而不是直接读整个uint32_t?因为 ADC 如果是 12 位分辨率,转换结果是在数据寄存器的低 12 位;如果设置了左对齐,数据在高 12 位。CubeMX 默认配置通常是右对齐,也就是数据在低 12 位,高位补零。但如果你在配置里改了对齐方式,这里的取位逻辑要跟着变。

回调里做的事情要尽量精简。DMA 每轮转换完成就进一次中断,中断里做浮点运算和公式计算,会占用中断响应时间。实际项目中我一般把原始数据先拷出来,用 volatile 标志位通知主循环去处理,浮点运算放到主循环里做。如果必须中断里算,至少加个__disable_irq保护,防止主循环读一半数据被覆盖。

3.3 数据缓冲区大小:循环模式下的覆盖窗口

缓冲区大小直接影响数据完整性。在循环模式下,DMA 每搬完一轮数据就自动从头开始写。如果你在主循环里处理数据的速度比 ADC 转换速度快,缓冲区大小设置多少都没问题。但现实是主循环可能因为其他任务被卡住几十毫秒,这时候缓冲区就不够用了。

一个实用的策略是把缓冲区设置成通道数的整数倍,比如通道数 4,缓冲区长度可以设成 8、12 或 16。这样 DMA 在写前半部分时,你可以在主循环里处理后半部分,只要处理速度跟得上,数据不会互相覆盖。配合 DMA 的半传输完成中断(HAL_ADC_ConvHalfCpltCallback),可以做到双缓冲效果,这是后面进阶章节要讲的方案。

缓冲区还必须是全局的。另外注意HAL_ADC_Start_DMA里传入的数组需要是uint32_t类型,不要用uint16_t。因为前面配置的数据宽度是 32 位,两者不一致的话,内存写入会越界,把相邻位置的变量写坏。

4. 误差分析与校准:采样值不准的背后有三个来源

4.1 阻抗匹配对采样精度的影响,以及采样时间怎么算

ADC 采样不准,第一个要查的就是采样时间。ADC 内部采样保持电容的充电过程需要时间,如果信号源阻抗太大,电容没充满就开始转换,采样值就会偏小。STM32 参考手册给了一个公式:采样时间 ≥ (RADC + Rsource) × CADC × ln(2^(N+1)),其中 N 是分辨率位数,RADC 是 ADC 内部采样电阻,CADC 是采样电容。不同系列参数略有差别,F1 的 RADC 约 1kΩ,CADC 约 8pF,F4 的 RADC 约 1.5kΩ,CADC 约 4pF。

实际问题中没人会拿公式手算。工程上的做法是直接加大采样周期:默认配置通常给 1.5 或 7.5 个 ADC 时钟周期的采样时间,这对低阻抗源是够用的;如果你的信号源是几十 kΩ 的输出阻抗,把采样时间提到 84 或 239 个周期,采样值通常会恢复正常。代价是采样率下降,但对大多数传感器采集场景来说,几十 kSPS 的采样率完全够用。

// 配置高阻抗信号源的采样时间示例 sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 239.5 个 ADC 时钟周期 // 计算有效采样率 // 假设 ADC 时钟 12MHz,转换周期 12.5 个时钟,总耗时 = (239.5 + 12.5) / 12MHz ≈ 21μs // 单通道采样率约 47kSPS;4 通道循检时每通道约 11.8kSPS

采样时间和采样率的折中关系值得牢记:单通道能跑多快,多通道整体速度就按通道数均分。4 通道时你的有效采样率是单通道的四分之一,因为 ADC 只有一个转换器,无论多少通道都是串行转换。如果你的应用要求每通道 100kSPS 且通道数 4,那么 ADC 整体吞吐率需要 400kSPS,这已经接近 F1 系列的极限了,这时候要考虑更高主频的系列或使用多 ADC 并行采集。

另一个采样偏差来源是通道之间的串扰。当相邻通道切换时,采样电容里残留的电压会影响下一次采样。如果前一个通道电压很高(比如接近参考电压),后一个通道是微弱信号,后者的采样值会被抬高。消除方法是让通道序列中信号幅度相差大的通道之间保持一定间隔时间,或者把采样时间拉长,给电容充分放电的时间。经验做法是电压较高和电压较低的通道不要排在一起,中间插入电源或地通道做缓冲。

4.2 参考电压和偏移误差的软件校准思路

参考电压不干净,采样值再准也是镜花水月。如果项目的参考电压来自 VREF+ 引脚,而 VREF+ 又直接接在电源上,那么电源纹波会直接耦合到 ADC 转换结果中。对于要求 12 位精度的采集系统,建议使用独立的基准电压源芯片。如果条件受限只能接电源,至少给 VREF+ 加一颗高品质的 100nF 去耦电容,并让 VREF+ 布线尽量短粗。

芯片本身的偏移误差(Offset Error)和增益误差(Gain Error)也是不可忽视的因素。STM32 ADC 出厂前会有校准值,但实际使用中温度变化会导致漂移。老道的做法是在产品出厂前做两点校准:一端接 GND 测偏移,一端接精确参考电压测增益。校准数据存到 Flash,上电后用来修正换算公式。

// 两点校准后的软件修正示例 #define VREF_CALIB 3.300f // 标准参考电压 #define OFFSET_ERROR 0.012f // 偏移误差:实测 0V 时 ADC 读数为 15,折算 0.012V float adc_to_voltage(uint16_t raw_value) { // 校准公式:V = (raw * VREF / 4095) - OFFSET_ERROR // 再做一次增益修正:乘以 VREF_CALIB / VREF_ACTUAL float voltage = (raw_value * VREF_CALIB / 4095.0f) - OFFSET_ERROR; float gain_factor = VREF_CALIB / 3.30f; // 实际参考电压与标称之比 return voltage * gain_factor; }

偏移误差的修正值如果超过 10 个 LSB,要警惕是硬件设计问题而非软件能纠正的范围。曾经遇到一块板子采样值整体偏高 30 个 LSB,查了一整天才发现是 PCB 布局时把 VREF+ 的走线布在了开关电源下方,高频噪声耦合进去了。软件校准掩盖了症状,但噪声依然存在,换上干净的基准电压源后问题才彻底消失。软件校准是最后一道防线,不是第一道防线。

5. 多通道采集避坑指南:死机、错位、丢数这些坑一次说清

5.1 DMA 传输完成中断为什么主循环卡死

现象:打开 DMA 传输完成中断后,主循环里的任务明显变慢,甚至出现逻辑错乱。原因是每轮 DMA 转换完成都会触发中断,如果回调函数里做了太多工作——比如浮点运算、打印日志、操作 Flash——中断嵌套会让 CPU 忙于处理中断,主循环任务得不到执行。

解决:回调函数里只做数据读取和标志位设置,数据处理全部移到主循环。如果必须快速处理,用HAL_ADC_ConvCpltCallback里拷贝数据到另一个缓冲区,并置一个 volatile 标志位,主循环轮询这个标志位后处理数据。这是最简单有效的解耦方式。

5.2 DMA 开启后偶尔采到全零或旧数据

现象:下电再上电后,第一次采到的数据有时候是 0,有时候是上一次运行留下的旧值。原因是 DMA 启动时和数据缓冲区的初始状态有关——缓冲区还未被 DMA 写入时,内容是上次的值。如果 DMA 启动后立刻读缓冲区,读到的可能是旧数据。

解决:启动 DMA 前用 memset 清空缓冲区,并在启动后等待第一个传输完成标志再读数据。另外确认 ADC 校准是否在启动前完成,STM32 ADC 的校准序列(HAL_ADCEx_Calibration_Start)必须在连续转换之前执行,否则首次转换精度会差。不少开发者把这步漏了,导致每次上电第一次采样误差特别大。

5.3 多通道数据顺序错乱,或通道数和期望不一致

现象:buffer[0] 里收到的是 CH3 的数据,buffer[2] 收到的是 CH0 的数据。原因有两种:第一种是初始化时 Rank 和 Channel 的对应关系配错了;第二种是 DMA 的传输长度和 ADC 通道数不一致,DMA 提前或延后触发,导致缓冲区里各通道数据错位。

解决:在初始化代码里逐条打印 Rank 对应的 Channel 映射关系,确认转换顺序。DMA 长度改回和通道数一致的值。另外注意,如果你中途用HAL_ADC_Stop_DMA停止再重启,DMA 传输长度需要重新设置,否则下次启动会按残留长度传输。

5.4 ADC 转换完成标志一直不置位

现象:启动采集后回调一直不触发,检查 DMA 状态发现传输未开始。原因是 DMA 通道映射错误,或 ADC 的 DMA 请求没有使能。常见于手动改代码时漏掉了ADC_CR2_DMA位(标准库)或hadc1.Init.DMAContinuousRequests字段(HAL 库)。

解决:在HAL_ADC_Start_DMA之前,检查hadc1.Init.DMAContinuousRequests是否设为 ENABLE。如果这个字段是 DISABLE,ADC 只会在第一轮转换时请求 DMA,之后不再追加请求,DMA 传输一轮就停。这在单次采集模式下没问题,但循环模式下必须设为 ENABLE,否则缓冲区只刷新一次。

5.5 用 printf 输出采样值时系统进入 HardFault

现象:在 DMA 回调里加了printf("%d", adc_dma_buffer[0])后,程序直接 HardFault。原因是printf在中断上下文里使用串口发送,如果串口还没有初始化或发送保留区已满,会触发断言或死循环,最终导致 HardFault。另外部分嵌入式printf实现需要分配堆内存,中断里用 malloc 式的操作在 MCU 上是不安全的。

解决:中断回调不碰任何阻塞式 IO,把原始数据拷贝到全局变量,在主循环里打印。如果必须实时输出调试数据,用 DMA 串口发送加环形缓冲区的方式,保证发送过程不会阻塞和长耗时,同时也要保证串口发送在中断上下文里是异步且安全的。

6. 进阶:双缓冲 DMA 让主循环不再丢数据,以及多 ADC 并行扩展采样率

双缓冲模式是解决“处理不过来就丢数”的经典方案。在上面第 3 章提到过,把缓冲区长度设为通道数的 2 倍,DMA 先写前半段,写完触发半传输完成中断;写入后半段,完成后触发传输完成中断。你可以在半传输中断里处理前半段数据,在传输完成中断里处理后半段数据。这样 DMA 始终在写当前半段,你在处理另一个半段,两边不冲突。

HAL 库在半传输完成中断和传输完成中断都提供了对应的回调函数:HAL_ADC_ConvHalfCpltCallback和HAL_ADC_ConvCpltCallback。你只需要在回调里通过判断是前半段还是后半段,去访问对应的一段缓冲区即可。这样主循环即使偶尔被其他任务卡住,数据也不会被 DMA 覆盖——DMA 永远不会回头写已经处理过的前半段,直到你开始处理后半段并且 DMA 又绕回来。

// 双缓冲 ADC 采集示例 #define ADC_CH_NUM 4 #define ADC_BUFF_LEN 8 // 2 倍通道数,形成两个半缓冲区 volatile uint32_t adc_dma_buffer[ADC_BUFF_LEN]; volatile uint8_t process_half = 0; // 0=正在处理前半段,1=正在处理后半段 // 半传输完成回调 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { process_half = 0; // 前半段数据准备好,主循环可以处理 buffer[0] - buffer[3] } // 传输完成回调 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { process_half = 1; // 后半段数据准备好,主循环可以处理 buffer[4] - buffer[7] } // 主循环中的处理逻辑 while (1) { if (process_half == 0) { process_data(&adc_dma_buffer[0], ADC_CH_NUM); process_half = 0xFF; // 防止重复处理 } else if (process_half == 1) { process_data(&adc_dma_buffer[ADC_CH_NUM], ADC_CH_NUM); process_half = 0xFF; } }

双缓冲的代价是内存占用翻倍:4 通道 32 位缓冲区仅 16 字节,翻倍也就 32 字节,对绝大多数 MCU 来说根本不是负担。但它换来的好处是主循环的延时容忍度可以从一个转换周期放宽到一整轮转换的时间。如果一轮转换是 84μs,你在主循环里即使被阻塞了 80μs,数据依然安全。这是做信号处理类项目时最重要的优化手段之一。

多 ADC 并行是另一个进阶方向。STM32 部分型号有多个 ADC 外设,比如 F4 系列有 3 个 ADC。把通道分布到 ADC1 和 ADC2 上,两个 ADC 可以同时采样不同的通道,整体吞吐率翻倍。在同一块芯片上,ADC1 和 ADC2 可以配置为同步采样模式,外部触发信号同时启动两个 ADC 转换,它们的转换结果在硬件层面就可以实现精确同步,这在三相电压电流检测这种要求同一时刻采样多路信号的场景下特别有用。DMA 方面,每个 ADC 独立配置自己的 DMA 通道,或者某些型号还支持双 ADC 共用 DMA 的多缓冲模式。我一般只用独立 DMA 通道的方式,逻辑清晰,代码也不复杂。

最近做某个采集设备项目时,系统要求 8 通道模拟输入,每通道不低于 50kSPS 采样率。如果只用单 ADC,整体吞吐率要 400kSPS,F1 系列根本不够看;换成 F4 的双 ADC 并行,每片 ADC 只跑 200kSPS,余量充足,配合双缓冲 DMA,主循环里还能跑浮点滤波和实时曲线绘制。从那以后我每次设计多通道采集方案时,都会先算总吞吐率离 MCU 极限有多远,超过 80% 就换平台或上双 ADC,不再硬着头皮去压榨单 ADC 的极限。这个习惯帮我少走了很多弯路,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询