news 2026/9/28 5:45:40

STM32CubeMX+DMA实现ADC多通道采集与滤波实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32CubeMX+DMA实现ADC多通道采集与滤波实战

做嵌入式这些年,ADC采集算是碰得最多的外设之一。早期我习惯用中断方式:每次转换结束进中断、读数据、出中断,单通道这么搞倒也没啥大问题。但一旦要同时采集电压、电流、温度好几个通道,中断频率一上来,主循环里的任务调度就会被频繁打断——LCD刷新开始卡顿、按键响应变迟钝、就连PID控制周期都被拖累。后来我把这套逻辑换成了DMA模式,ADC转换完的数据自己就跑到内存里去了,CPU全程不用操心,整个系统一下子清爽了很多。

今天的实战主题就是 STM32CubeMX + DMA 搞定 ADC 多通道采集,顺便把我趟过坑之后沉淀下来的几种滤波方案一并分享出来。无论你是刚入手 STM32 的新手,还是被 DMA 配置坑过、想搞懂背后原理的工程师,这篇应该都能给你带来点实在的帮助。

1. 为什么我推荐用DMA做ADC多通道采集

1.1 中断方式其实也能用,但成本太高

很多朋友一开始接触多通道 ADC 采集,习惯性用的是“转换完成中断”方案。ADC 每完成一次转换就拉一次中断,CPU 进中断把数据从ADC_DR寄存器读出来,然后再启动下一次转换。听起来没什么问题,但仔细算一笔账就明白了。

假设我用 3 个通道、单次采样时间 55.5 周期,再加上 12.5 个周期的转换时间,一个通道从启动到转换完成大约需要 68 个 ADC 时钟周期。ADC 时钟配置成 12MHz 的话,一次转换耗时大概 5.7 微秒。3 个通道一轮下来大约 17 微秒。如果还要跑连续转换,中断发生频率接近 60kHz。这个频率下,哪怕中断服务函数里只做“读寄存器 + 存数组”这两步操作,CPU 的有效处理时间也被吃掉了一大部分。关键是,中断服务函数里不适合做复杂数据处理,滤波、换算、协议打包这些活儿最终还是得回到主循环里干,数据积压的问题会越来越明显。

还有一个更隐蔽的问题:中断响应存在延迟。如果主程序正在处理某个临界区,或者一个更高优先级中断正在执行,ADC 转换完成中断会排队等待。这一等,下一次采样启动的时间就不固定了,采样间隔抖动直接影响到信号分析的准确性。对采样时序有要求的场景,这就是硬伤。

1.2 DMA的角色:一个不需要CPU盯着的搬运工

DMA 的全称是 Direct Memory Access,直译过来就是“直接存储器访问”。你可以把它想象成一个专职搬运工:ADC 转换完成一个数据,DMA 自动把ADC_DR寄存器里的值搬到内存指定地址;搬完一个继续搬下一个,全程不需要 CPU 插手。

用 DMA 做 ADC 采集时,整个工作流程变成了这样:CPU 启动一次 DMA 传输,然后该干嘛干嘛;ADC 按配置好的顺序扫描各个通道,转换结果被 DMA 源源不断地送到内存缓冲区;等一组数据搬完了,DMA 可以产生一个中断告诉 CPU“数据齐了,你来处理吧”。CPU 要做的只是初始化时动动嘴,数据处理时才真正干活,中间这段采集过程完全不占用任何 CPU 时间。

从系统架构的角度看,这不是简单的“中断换成 DMA”,而是把采集这个重复劳动从 CPU 手里完全剥离了。CPU 的主循环可以稳定地跑显示刷新、通信协议、控制算法,采样频率再高也不会阻塞业务逻辑。

1.3 三种典型场景,建议直接上DMA

结合我自己的项目经验,下面这三种场景基本是“非 DMA 不可”:

  • 多通道连续采样,需要周期性刷新数据的场景。比如一个电源管理模块里要同时监控输入电压、输出电压、两路负载电流,DMA 模式一轮一轮刷新数据,CPU 定期取平均值就行。
  • 主循环任务繁重,不能让中断反复打断的场景。比如带 GUI 刷屏、跑 Modbus 通信、做 PID 闭环控制,中断频率过高会直接拖垮实时性。
  • 对采样时序一致性有要求的场景。像交流信号的过零检测、波形采样这种,每次采样间隔必须均匀,中断模式很难保证这一点,DMA 模式配合固定的 ADC 触发源可以做到严格稳定。

当然,中断模式也不是一无是处。如果你只是偶尔采一次数据、通道少、转换频率低,用中断反而更简单,没必要为了用 DMA 而用 DMA。选型的标准就一条:ADC 在不在你的关键路径上。

2. STM32CubeMX一步步配置:ADC多通道+DMA

2.1 时钟分频先算清楚:别让ADC超频工作

我用 STM32F103C8T6 当示例芯片来走一遍完整配置流程。新建工程选择芯片之后,首先要做的是在 Clock Configuration 页面把时钟树配好。F103 的最高主频是 72MHz,系统时钟走 PLL 倍频上去,这个大家应该比较熟,不多说。关键是看 ADC 的时钟分频。

F103 的 ADC 时钟挂在 APB2 总线上,APB2 的最高频率是 72MHz,但 ADC 模块自己的最高工作时钟是 14MHz,所以必须分频。在 CubeMX 的 Clock Configuration 界面里找到 ADC1 的时钟源,如果把 PCLK2 设为 72MHz,ADC 时钟分频就选 6 分频,得到 12MHz,安全合规。也有人选 4 分频得到 18MHz,超频状态下的转换结果位数会受影响,不建议这么干。

时钟这里我多提醒一句:ADC 时钟频率直接决定了转换时间和最高采样率。12MHz 下最短采样时间 1.5 周期配置时,单个通道一次转换最快需要 1.5 + 12.5 = 14 个 ADC 时钟周期,约 1.17 微秒。但注意,通道的输入源阻抗越高,需要的采样时间越长,否则采样电容充不满,转换结果就会偏小,这个后面滤波那节还会展开讲。

2.2 ADC参数逐项设置:扫描、连续、采样时间

进入 Pinout & Configuration,在 Analog 分类下找到 ADC1,先把需要采集的通道引脚勾选上。比如我接的是 IN0、IN1、IN2 三个通道,就把这三个勾上。

接着看 Parameter Settings 里的关键选项,我挨个说:

  • Scan Conversion Mode:置为 Enabled。多通道采集必须开启扫描模式,否则 ADC 只会转换一个通道。
  • Continuous Conversion Mode:置为 Enabled。开启连续转换,ADC 转换完一轮自动开始下一轮,配合 DMA 的循环模式,数据会一直刷新。
  • Discontinuous Conversion Mode:保持 Disabled。不连续模式一般用于“每次触发只转 N 个通道”的省电场景,我们这里用不上。
  • DMA Continuous Requests:置为 Enabled。这个选项翻译过来是“DMA 连续请求”,它决定了 ADC 转换完成后要不要持续向 DMA 发出数据传输请求。对于多通道连续采集,这里必须打开,否则可能只传一轮就停了。
  • Number Of Conversion:填 3,因为我们用了三个通道。
  • 每个 Rank 的采样时间:我建议至少选 55.5 周期,输入源阻抗比较高的时候直接上 239.5 周期。采样时间越长,采样电容充得越满,结果越接近真实值;代价是转换速度变慢。低速信号根本不在乎多出来的十几微秒,优先保证精度就好。
  • ADC 数据对齐方式:右对齐。12 位分辨率下,右对齐后高 4 位是 0,读出来的数值直接用,换算电压也方便。

还有一个容易忽略的地方:End of Conversion Selection 选项。默认的 EOC flag at end of sequence 表示一组序列转换完之后才置位转换结束标志,这适合多通道场景;如果选成 EOC flag at end of conversion,每个通道转换完都会置一次标志,逻辑上有点绕。我们保持默认即可。

2.3 DMA参数设置:模式、数据宽度、优先级

ADC 参数配完,接下来是最关键的 DMA 配置。在 ADC1 的 DMA Settings 选项卡下点 Add,F103 的 ADC1 对应 DMA1 的 Channel1,这个是硬件固定的,CubeMX 会自动带出来。

参数设置我逐项说明一下:

  • Mode 选 Circular,循环模式。循环模式的特点是 DMA 搬运完指定数量的数据后,地址自动回到起始位置,继续搬运下一轮。这样 ADC 连续转换,DMA 连续搬运,数据实时更新,CPU 只需要在回调里取数就行。
  • Direction 选 Peripheral To Memory,方向是外设到内存。ADC 是外设,内存缓冲区是目的地,这个方向别选反。
  • Peripheral Address 和 Memory Address 一般不用手填,CubeMX 根据外设和用户代码里的缓冲区自动处理。Data Width 这里,外设端和内存端都选 Half Word,也就是 16 位。因为 F103 的 ADC 是 12 位分辨率,一个转换结果正好占 16 位,用 Half Word 最省内存,数据也最规整。
  • Priority 建议选 High。DMA 有多个通道同时工作时,优先级高的通道优先被服务。ADC 数据有实时性要求,给高优先级合适。

配置完保存工程,点击 Generate Code 生成代码,工具链选 MDK-ARM 或者 STM32CubeIDE 都行。

2.4 生成代码后的目录检查

生成完工程后,我习惯打开看一下目录结构。核心文件是adc.c和dma.c,初始化函数MX_ADC1_Init()和MX_DMA_Init()会自动生成。务必留意一行代码:HAL_ADC_Start_DMA()的真实调用时机。CubeMX 生成的代码里,DMA 初始化在main()函数中MX_DMA_Init()调用,ADC 初始化是MX_ADC1_Init(),整个顺序都不要动,如果先初始化 ADC 再初始化 DMA,某些系列芯片会出问题。

还有个细节:DMA 中断的优先级在stm32f1xx_it.c里默认没有配置,需要你在 NVIC 设置里手动打开 DMA1 Channel1 中断,否则采集完成回调函数根本不会触发。CubeMX 的 NVIC 页面里勾上 DMA1 channel1 global interrupt,重新生成代码即可。这一步经常被漏掉。

3. 代码实现:启动一次采集,剩下的交给DMA

3.1 定义缓冲区并启动DMA采集

打开main.c,在用户代码区定义一个数组用作 DMA 缓冲区:

/* USER CODE BEGIN PV */ #define ADC_CH_NUM 3 uint16_t adc_buf[ADC_CH_NUM]; volatile uint8_t adc_data_ready = 0; /* USER CODE END PV */

注意,我定义的是uint16_t类型数组,因为 ADC 结果是 12 位,一个元素占 16 位。DMA 缓冲区的大小等于通道数,这个数组会被 DMA 按顺序填充:adc_buf[0]对应第一个使能的通道,adc_buf[1]对应第二个,以此类推。如果你在 CubeMX 里把通道顺序调整了,数组下标和通道的对应关系也要跟着变。

然后在main()函数的用户代码区域启动采集:

/* USER CODE BEGIN 2 */ HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CH_NUM); /* USER CODE END 2 */

这里有个细节容易踩坑:F1 系列 HAL 库中HAL_ADC_Start_DMA的第二个参数类型是uint32_t*,而我们传入的是uint16_t*数组,所以要强转。很多朋友编译报警,其实就是这里类型不匹配,强转一下就好。第三个参数是 DMA 传输的数据个数,这里传ADC_CH_NUM,也就是 3,DMA 每搬运 3 个数据就完成一轮传输。

启动之后,整个采集链路就自动跑起来了。ADC 连续转换,DMA 自动搬运,CPU 在主循环里该干啥干啥。

3.2 回调函数里的数据搬运和标志

DMA 每搬完一轮数据,会触发一次传输完成中断。HAL 库封装好的回调函数是HAL_ADC_ConvCpltCallback,我们需要重写它。直接在main.c的用户代码区添加:

/* USER CODE BEGIN 4 */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADC1) { adc_data_ready = 1; } } /* USER CODE END 4 */

主循环里判断标志再处理数据:

while (1) { if (adc_data_ready) { adc_data_ready = 0; // 到这里,adc_buf[0]~adc_buf[2] 就是一组完整的采集数据 process_adc_data(); } }

这里我特别提醒一个时机问题:HAL_ADC_ConvCpltCallback是在中断上下文执行的,函数体里面不要做耗时操作。像电压换算、滑动窗口滤波这种计算,放在主循环里去处理;回调里只置个标志位,最多做一次数组拷贝,把数据快照下来,避免主循环处理期间 DMA 又刷新了缓冲区,导致数据一致性出问题。

想更进一步降低中断频率的话,可以把 DMA 缓冲区放大,比如定义成uint16_t adc_buf[ADC_CH_NUM * 16],DMA 一次搬完 48 个数据才触发一次回调,这样回调频率变成原来的 1/16,对 CPU 的打扰更小。代价是数据实时性变差,适合对实时性要求不高的监控类场景。

3.3 从原始值到真实电压:换算与多通道对齐

ADC 原始值是一个 0~4095 的整数(12 位分辨率下),要把它换算成真实的物理电压,公式很简单:

电压 = 原始值 × 参考电压 ÷ 4096

参考电压在 F103 上默认就是 VDDA,通常是 3.3V。写成代码:

float voltage = (float)adc_buf[0] * 3.3f / 4096.0f;

有一个非常常见的认知误区:12 位 ADC 的最大数字是 4095,为什么除数是 4096?因为 ADC 的满量程对应参考电压本身,数值范围是 0~4095,一共 4096 个刻度。如果除以 4095,算出来会偏大一丢丢。这种偏差单独看无所谓,但做校准表时能明显感觉到不整齐。统一用 4096 做除数。

多通道对齐的问题,说白了就一句话:数组下标和 CubeMX 里配置的通道扫描顺序一一对应。你在 CubeMX 的 Rank 列表里看到的顺序,就是 DMA 往缓冲区写数据的顺序。如果先配了 Rank 1 是 IN2,那么adc_buf[0]就是 IN2 的数据。很多人数据“串了”,基本都是配置顺序和代码用到的地方没对上。

4. 附赠干货:我在项目里实际用过的几种ADC滤波手段

4.1 滑动窗口平均滤波:实时性与平滑度的平衡

ADC 采集的原始数据通常带一定噪声,尤其是用开关电源供电时,纹波会直接耦合到采样值上。最简单的处理办法是算术平均,攒够 N 个点求平均。但算术平均有个问题:必须等攒齐 N 个点才能输出一个结果,实时性不好。

我在实际项目里用得最多的是滑动窗口平均滤波,也叫环形缓冲平均滤波。它的思路是维护一个固定长度的窗口,每次进来一个新数据,就把窗口里最老的数据踢掉,再对当前窗口里的所有数据求平均。这样每来一个有效采样点,就能立刻得到一个平滑后的输出,实时性和平滑效果兼顾。

#define WIN_SIZE 16 static uint16_t s_window[WIN_SIZE]; static uint8_t s_index = 0; static uint32_t s_sum = 0; uint16_t sliding_window_filter(uint16_t sample) { s_sum -= s_window[s_index]; // 减掉最老的数据 s_sum += sample; // 加上新数据 s_window[s_index] = sample; // 覆盖最老的位置 s_index = (s_index + 1) % WIN_SIZE; return (uint16_t)(s_sum / WIN_SIZE); }

这段代码有几个点值得说一下。s_sum用的是 32 位无符号整数,16 个 12 位 ADC 的最大值是 16 × 4095 = 65520,用 16 位整形会溢出,这个坑我踩过一次,窗口一长结果就完全不对了。循环取模操作(s_index + 1) % WIN_SIZE在编译器优化后效率很高,也可以换成位运算(s_index + 1) & (WIN_SIZE - 1),前提是窗口大小是 2 的幂。

滑动窗口的窗口大小怎么定?我的经验是:信号变化慢、噪声频率高的场景,窗口选大一些,比如 32;信号变化快、需要及时响应的场景,窗口选小一些,比如 8 或 16。窗口越大,延迟越大,响应越迟钝,这是物理规律,没法绕过。

4.2 限幅滤波:先剔除“一眼假”的突变值

滑动窗口平均能防随机噪声,但对付不了突然冒出来的超大尖峰。比如电机启动瞬间产生的电磁干扰,可能让 ADC 值瞬间跳变几百甚至上千的数值。这种突变数据如果混进滑动窗口里,即使只有一个点,也会把平均值带偏一大截。

限幅滤波的思路很简单:如果当前采样值和上一次有效值之间的差值超过了一个阈值,就认为这次采样是异常数据,直接用上一次的值替代,或者干脆丢弃。

#define LIMIT_DELTA 50 uint16_t last_valid_value = 0; uint16_t limit_filter(uint16_t sample) { uint16_t delta; if (sample > last_valid_value) delta = sample - last_valid_value; else delta = last_valid_value - sample; if (delta > LIMIT_DELTA) return last_valid_value; // 判定为尖峰,用上次的值顶替 last_valid_value = sample; return sample; }

阈值LIMIT_DELTA怎么定?要看信号的物理意义。比如温度传感器,正常变化速率每分钟最多几度,ADC 值每毫秒跳变超过 50 就非常可疑;但如果采的是电流信号,动态响应很快,阈值定 50 可能把正常的变化也当成尖峰给滤掉了。这个阈值没有标准答案,必须实测数据的波动范围再定。

我习惯把限幅滤波放在最前面,先做粗筛,剔除明显异常值,然后再进滑动窗口做平滑。两级串联的效果比任何单一滤波都稳。

4.3 中值+去极值平均:解决随机尖峰

如果数据里的随机尖峰比较多,而且你期望的输出值要更“稳”,可以考虑中值滤波和去极值平均的组合方案,也就是经典的去极值平均滤波。

思路是:采集 N 个点,把这 N 个点排序,去掉一个最大值和一个最小值,剩下的 N-2 个点求平均。这样既不放过真实的信号变化,又能把偶然出现的脉冲干扰剔除掉。

#define MED_SIZE 9 uint16_t median_avg_filter(uint16_t* raw_buf) { uint16_t sort_buf[MED_SIZE]; memcpy(sort_buf, raw_buf, MED_SIZE * sizeof(uint16_t)); // 选择排序,数据量小,效率无所谓 for (uint8_t i = 0; i < MED_SIZE - 1; i++) { uint8_t min_idx = i; for (uint8_t j = i + 1; j < MED_SIZE; j++) { if (sort_buf[j] < sort_buf[min_idx]) min_idx = j; } if (min_idx != i) { uint16_t tmp = sort_buf[i]; sort_buf[i] = sort_buf[min_idx]; sort_buf[min_idx] = tmp; } } uint32_t sum = 0; for (uint8_t i = 1; i < MED_SIZE - 1; i++) sum += sort_buf[i]; return (uint16_t)(sum / (MED_SIZE - 2)); }

注意几个点:sort_buf是局部数组,大小为 9 时栈占用大约 18 字节,在 STM32 上毫无压力,但如果你把MED_SIZE改成 100,就得考虑栈空间够不够了。还有,这个函数需要攒够 9 个采样点才输出一个结果,输出频率是采样频率的 1/9,实际使用时要评估实时性能不能接受。

三种滤波方法做个简单对比:

滤波方法实时性抗尖峰能力平滑效果计算开销
滑动窗口平均高弱中等很低
限幅滤波极高强无极低
中值+去极值平均低强好中等

实际项目里我常用组合:先限幅,再滑动窗口,偶尔数据质量特别差时上中值+去极值。

4.4 别小看硬件滤波:软件解决不了的问题要靠它

软件滤波能处理很多噪声,但它治标不治本。有些问题,比如采样值整体系统性偏低、波动范围很大,软件再怎么算都救不回来。这时候要先回头检查硬件,常见问题有这么几类:

  • 采样时间太短:输入源阻抗高时,采样电容没充满就开始转换,结果必然偏小。F103 的采样时间配置到 239.5 周期,能明显改善这个问题。
  • 参考电压不稳:VDDA 上如果直接接了一堆负载,参考电压本身就在波动,ADC 结果当然跟着跳。解决办法是把模拟电源和数字电源分开,用磁珠或电感隔离,靠近 VDDA 引脚放一个 100nF 和 4.7uF 的去耦电容。
  • 输入信号带宽太宽:如果输入信号本身带高频噪声,ADC 的采样保持电路会把混叠信号采进来。可以在 ADC 引脚前端加一个 RC 低通滤波,比如 100Ω 串 100nF 电容到地,截止频率大约 16kHz,够滤掉大部分开关噪声。
  • 地回路问题:模拟地和数字地没有单点连接时,数字电路的开关噪声会通过地平面窜进模拟回路。PCB 布局上要保证模拟信号的回流路径尽量短,ADC 引脚远离高频时钟线和 PWM 输出线。

顺便提一嘴,现在不少新型号的 MCU 已经内置了硬件滤波机制,比如部分 GD32H7 系列自带 ADC 硬件滤波单元,配置好寄存器后 CPU 拿到手的直接就是滤波后的结果,软件压力小很多。选型的时候如果明确知道要用在噪声环境里,优先考虑这类带硬滤支持的型号,能省不少事。

5. 常见问题与排查技巧实录(建议收藏)

5.1 多通道数据错位、对不上号

现象:adc_buf[0]读到的不像是第一个通道的数据,整体好像错了一位。

最常见的原因是 CubeMX 里通道扫描顺序和代码里读取的顺序不一致。比如你在 CubeMX 里按 Rank 1 = IN2、Rank 2 = IN0、Rank 3 = IN1 配置,那么 DMA 填充顺序就是 IN2、IN0、IN1,adc_buf[0]对应 IN2 而不是你脑补的 IN0。排查方法很简单:回到 CubeMX 的 ADC 配置页,对照 Rank 顺序改代码注释,或者在初始化结构体里看Channel字段的排列顺序。

另一个容易被忽略的原因是 DMA 传输还没开始,第一次中断的数据是历史残留。一般启动 DMA 后,第一次回调里的数据可能不完全对位,稳妥的做法是初始化完成后先丢弃前几组数据:

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CH_NUM); // 等待几组数据刷新后再开始使用 for (int i = 0; i < 5; i++) { while (!adc_data_ready); adc_data_ready = 0; }

5.2 第一次采样值明显偏大或偏小

初次上电后,ADC 内部采样电容还没有完成充电,第一次转换结果往往会偏离真实值。如果连续转换已经开启了,一般只有第一轮数据受影响。

最简单的方案是启动后延时几毫秒再取值,或者干脆丢弃第一次转换结果。代码层面,我在启动 DMA 后加一段空等待:

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CH_NUM); HAL_Delay(5); // 等采样电容充电 adc_data_ready = 0; // 清掉可能已经置位的标志

这行HAL_Delay(5)不会影响采集链路,但能把第一轮“不干净”的数据挡在门外。

5.3 采样值一直跳,软件滤波救不回来

如果滤波参数已经调得够重了,数据还是在跳,基本就是硬件问题。排查优先级我自己是这样的,先给一个固定电位给 ADC 输入,比如直接用跳线把输入引脚接到 GND 或 VCC,看读值是否稳定。如果固定输入还跳,就是 ADC 配置或电源问题;如果固定输入稳定,那问题出在输入信号链路本身。

接着检查配置:ADC 时钟分频是否正确,是否超频;采样时间是否太短。再查电源:万用表量一下 VDDA 是不是稳定 3.3V,有没有明显纹波。最后查布线:输入信号线有没有和 PWM 线、时钟线平行走线过长。

按这个顺序查下来,绝大多数“软件救不回来”的跳动问题都能定位到根因。

5.4 DMA只进一次中断,之后再也不触发

这个现象非常经典:程序跑起来第一次回调进了,后面就再也没反应。

先说原因,DMA 工作在 Normal 模式而不是 Circular 模式时,搬完指定数量数据就停了,不会再自动搬运。如果 CubeMX 里 DMA Mode 忘记选 Circular,那就会“只跑一轮就罢工”。检查 DMA 配置,改成 Circular,重新生成代码就能解决。

还有一种情况是Continuous Conversion Mode没有打开。ADC 转完最后一通道后不启动新一轮转换,DMA 自然没有新数据可搬。把 Continuous Conversion Mode 置为 Enabled 即可。

还有一种更隐蔽的原因:CubeMX 生成的 DMA 中断没有正确使能。如果你在主函数里手动重开 DMA:

HAL_ADC_Stop_DMA(&hadc1); HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, ADC_CH_NUM);

注意 Stop 和 Start 之间要确保 DMA 中断标志已经清掉,最好在回调里不调用 Stop,保持循环模式一直跑,从根上避掉这个问题。

5.5 快速排查速查表

现象优先检查项处理办法
无任何数据DMA 中断未使能NVIC 中打开 DMA1 Channel1 全局中断
只进一次中断DMA Mode 非 Circular修改为 Circular
数据全部为 0缓冲区地址不对/通道未使能检查 DMA 缓冲区是否在 SRAM,引脚是否配置为模拟输入
数据错位通道顺序和代码读取顺序不符对照 CubeMX Rank 顺序调整代码
首个值异常采样电容首次充电启动后延时 5ms 或丢弃第一轮数据
数值整体偏小采样时间过短采样时间提高到 55.5 或 239.5 周期
数值跳动剧烈硬件电源/地噪声加强滤波电容,分离模拟地和数字地
F1 平台 DMA 不工作缓冲区放在 CCM RAM将缓冲区放在普通 SRAM,禁止使用__attribute__((at(0x10000000)))

最后再分享一个我自己的使用习惯:软件滤波只是兜底手段,不要一上来就把滤波参数调得很重,那样只会掩盖硬件问题。先让硬件指标尽量做好——供电干净、地回路短、采样时间给够,这时候哪怕不滤波,数据质量也基本过得去。滤波只是在这个基础上再压掉最后一点波动而已。

另外,等这套 DMA 采集链路跑顺了,有一个特别值得继续做的扩展:把 DMA 缓冲区开大,配合HAL_ADC_ConvHalfCpltCallback实现“乒乓缓冲”,前半段在填的时候处理后半段,后半段在填的时候处理前半段,这样采样和处理可以真正做到完全并行。当然,这就不是今天这篇能说完的了,等你把 DMA 的基本功练扎实,自然就会发现它还有更多可玩的余地。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:45:37

YOLO烟盒检测实战:1934张图像数据集训练与调优避坑指南

简介&#xff1a;本资源为面向YOLO系列目标检测算法的烟盒识别数据集&#xff0c;适合从事目标检测学习、模型训练与验证的开发者及研究者使用&#xff0c;可解决烟盒类目标识别任务中数据准备繁琐的问题。压缩包共2000个文件&#xff0c;约192.79MB&#xff0c;包含1545个xml标…

作者头像 李华
网站建设 2026/9/28 5:45:24

3天搞定思维图网站,有做思维图的网站吗哪家好

3天搞定思维图网站,有做思维图的网站吗哪家好 备案流程一头雾水,是很多初创团队上线前的最大噩梦。看着别人网站秒开,自己却在管局系统里盯着“初审”状态发呆,那种焦虑感懂的人都懂。其实, 有做思维图的网站吗 这个搜索词背后,藏着大量想快速落地项目却卡在基建上的需求。大家搜 哪家好…

作者头像 李华
网站建设 2026/9/28 5:45:01

别再用丑模板了,这份microsoft免费网站保姆级建站教程救急

别再用丑模板了,这份microsoft免费网站保姆级建站教程救急 别再盯着那些千篇一律、配色辣眼的模板网站发呆了。对于项目经理来说,拿着一个连基本响应式都没做好的模板去见客户,简直是职业自杀。你心里清楚, 模板网站太丑不够用 ,客户要的是专业、是速度、是信任感,而不是一堆廉价的占位符。 这篇…

作者头像 李华
网站建设 2026/9/28 5:44:54

黄岛网站建设哪家权威?3招避开高价坑,性能优化才是硬道理

黄岛网站建设哪家权威?3招避开高价坑,性能优化才是硬道理 在黄岛找建站公司,最让人头疼的不是功能多不多,而是怕被坑高价。很多老板拿着几份报价单,看着几千到几万不等的价格,心里直打鼓:这钱到底花哪儿了?是不是虚高?其实,判断一家建站公司是否“权威”,不看它吹嘘了多少案例,而要看它在 性能优化…

作者头像 李华
网站建设 2026/9/28 5:44:47

3步搞定wordpress时间归档,一文搞懂防黑优化全攻略

3步搞定wordpress时间归档,一文搞懂防黑优化全攻略 网站被黑挂马不知道怎么办?别慌,这种“中招”后的慌乱感,我见过太多新手站长。往往页面突然多出一堆乱七八糟的广告链接,或者被360、百度直接标记为“含有非法信息”,流量瞬间归零。这时候你才意识到, wordpress时间归档…

作者头像 李华
网站建设 2026/9/28 5:44:17

做app网站需要什么条件:5万块避坑,拒绝被黑挂马

做app网站需要什么条件:5万块避坑,拒绝被黑挂马 上周一个做跨境电商的朋友半夜给我打电话,声音都劈了。他那个刚上线不到一周的官网,首页突然变成了一片黑色,中间跳出一行血红色的字:“你的服务器已中毒,点击购买解药”。后台更是乱成一锅粥,数据库里全是乱码,SSL证书也莫名失效了。他问我最多的一件事不是…

作者头像 李华