ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

STM32F407双ADC交替采样+DMA,轻松实现等效采样率翻倍

STM32F407双ADC交替采样+DMA,轻松实现等效采样率翻倍 1. 别再只用单ADC了双ADC交替采样到底解决了什么问题我之前做一块板子要采200kHz左右的正弦波做FFT手里的STM32F407单ADC无论怎么调参数波形总是差一口气。F407的单路ADC理论峰值是2.4Msps听着够用但采样保持时间稍微加一点或者想留点余量做滤波很快就不行了。后来我索性把片内两个ADC拿出来做交替采样配合DMA搬运等效采样率直接翻倍波形质量明显改善。如果你也遇到类似的问题——测中高频信号时单ADC速度捉襟见肘、想用F407做音频采集或振动分析、或者想彻底搞懂CubeMX里那堆ADC和DMA选项这篇内容就是按我实际调通过的流程给你写的。整个过程不涉及复杂的外设库都是HAL库加CubeMX生成工程新手照做也能跑起来。1.1 单ADC的采样率天花板到底卡在哪里STM32F407内部是逐次逼近型ADC转换12位结果有固定的转换周期不是想调多快就调多快。手册上写得很清楚ADC时钟最高36MHz而我们常用配置下APB2是84MHz所以要给ADC单独分频。如果用12位分辨率单次转换需要12 SMP个ADC时钟周期SMP是采样保持时间可以配置成3、15、28、56、84、112、144、480等档位。最常用的高速配置是SMP3ADC时钟21MHz那么单次转换时间是单ADC采样率 fADC / (12 SMP) 21MHz / 15 1.4Msps注意这里1.4Msps是实际速度不是手册标题里那个2.4Msps。2.4Msps得在ADC时钟36MHz、SMP3的情况下才是36MHz/152.4Msps而36MHz要求APB2至少72MHz再用2分频配置上更紧张实际工程很少这么跑极限。单ADC只有1.4Msps想采样一个100kHz的波形理论上每个周期还能采14个点勉强够看一旦信号到200kHz每个周期只剩7个点FFT出来的频谱已经惨不忍睹了。这就是我决定上双ADC交替采样的直接原因。1.2 交替采样、同步采样、独立采样要分清很多人一听“双ADC”就以为把两个ADC都打开、两个DMA都启动就算完事其实完全不是一回事。F407里两个ADC的组合方式有几种方向完全不同独立模式ADC1采A通道ADC2采B通道互不干扰速度没有任何提升。同步采样模式两个ADC在同一个触发边沿同时开始转换分别采A和B通道适合需要严格同步的两路信号采集但单路采样率依旧不变。交替采样模式两个ADC轮流采同一个通道ADC1采第N个点ADC2采第N1个点最后通过DMA按顺序搬进内存等效采样率翻倍。打个比方一个人记账速度跟不上就安排两个人轮流记一个记单数、一个记双数最后的账本还是一个连续序列。但两个人的“笔迹”不可能完全一样反映到ADC上就是两个通道之间存在微小的增益和偏移差异采出来的相邻点会有规律性毛刺。这个坑我在后面实测部分会专门讲。2. CubeMX里把双ADC、DMA和时钟一次配对2.1 先定ADC时钟所有速度的地基打开STM32CubeMX芯片选STM32F407VET6或者你手里的具体型号进入RCC配置外部晶振HSE然后到Clock Configuration里把SYSCLK配到168MHzAPB2总线频率84MHz。这一步不处理好后面ADC分频出来全是奇怪的数字。接下来打开ADC1和ADC2两个外设在Parameter Settings里找到ADC Clock Prescaler选4分频这样ADC时钟就是84MHz/421MHz。很多教程为了追求速度直接选2分频得到42MHz已经超过36MHz上限了。短时间可能看不出问题但长时间运行转换结果会漂尤其在高温环境下我强烈不建议这么做。分辨率选12位Scan Conversion Mode保持Disable因为我们只采集一个通道Continuous Conversion Mode选Enable这样ADC会一直转不用手动触发Discontinuous Conversion Mode保持Disable。采样时间这里可以选3 Cycles先跑通流程后面再看信号源内阻决定要不要加大。需要注意双ADC交替采样时两个ADC的采样时间必须一致否则交替的半个周期会错位。CubeMX在多ADC模式下一般会把从设备的参数跟主设备同步但生成代码后最好再检查一次ADC2的SMP值。2.2 双ADC交替模式与DMA的配置细节在CubeMX里配置好ADC1后进入Mode配置不要选默认的Independent Mode要往下拉找到Dual ADC interleaved mode。不同版本的CubeMX叫法可能略有差异但关键字就是interleaved对应中文意思是交替。有的版本里还会有Dual regular simultaneous mode那个是同步采样别选错。选完交替模式后ADC2会自动变成从模式大部分参数由主ADC决定不需要重复配置。主ADC是ADC1也就是说后面启动DMA也是从ADC1这边启动这是很多新手迷糊的地方为什么我配了ADC2却找不到ADC2的DMA请求因为在双ADC交替模式下两个ADC共享一个DMA请求由master统一发出。接着到DMA Settings标签页给ADC1添加一条DMA请求。关键参数就三个Mode选Circular循环模式缓冲区满了自动回到开头继续搬运适合连续采集。Data Width选Half Word因为ADC结果是16位如果选成Word四个字节里只有两个字节有效数据会乱成一团。Memory Increment选EnableDirection从Peripheral To Memory。DMA传输中断建议打开在NVIC Settings里勾选DMA Interrupt。实际使用中我通常会同时开半传输中断和全传输中断这样上半段数据在处理的时候DMA还在往后半段写这就是所谓的双缓冲思想不用额外申请复杂的数据结构。2.3 生成代码前多检查一遍这三个地方CubeMX生成代码很快但配置错了后面调试更痛苦。我每次生成前都会花一分钟检查三处你可以直接当成检查清单用。第一确认ADC1的Mode确实是Dual ADC interleaved mode不是simultaneous。这两个选项在菜单里挨得很近一个提升采样率一个只是同步采样选错的后果是波形看不出明显提升但你又说不清哪里不对。第二确认DMA的Data Width是Half Word并且关联的是ADC1而不是其他外设。如果之前项目里用过串口DMA很容易在添加时选错请求源生成后一运行ADC缓冲区一动不动。第三检查ADC2没有单独配置一套DMA或中断。多模式下从设备的DMA请求是多余的如果两个ADC都配了DMA实际搬运时会出现重复触发缓冲区被莫名覆盖。确认没问题后点击Generate Code生成工程。如果你在Project Manager里把每个外设单独生成一个.c/.h文件后面想改ADC参数时会清爽很多不然所有初始化都堆在main.c里找起来很费劲。3. 代码里真正干活的部分DMA搬运、数据拼接与处理3.1 声明缓冲区和双ADC启动方式生成完工程后打开main.c你会在外设初始化部分看到MX_ADC1_Init和MX_ADC2_Init两个函数。在main函数外面声明一个全局缓冲区用来接收DMA搬运过来的ADC数据。#define ADC_BUFFER_SIZE 4096 __IO uint16_t adc_buffer[ADC_BUFFER_SIZE];缓冲区大小选4096是因为双ADC交替模式下每个采样点会对应两个16位结果。DMA搬运的4096个半字实际对应2048次双ADC采样。如果你打算做1024点FFT缓冲区设2048就够了不会浪费内存。主循环开始前调用双ADC多模式的启动函数HAL_ADCEx_MultiModeStart_DMA(hadc1, (uint32_t *)adc_buffer, ADC_BUFFER_SIZE);注意几个容易错的地方第一个参数传主ADC句柄也就是hadc1不是hadc2第二个参数是缓冲区地址前面一定要加类型转换第三个参数是半字个数不是采样点数别被函数名里的Start_DMA误导。启动后DMA就开始自动搬运我们只需要在中断回调里做标记volatile uint8_t adc_dma_done 0; void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_dma_done 1; } }如果你的HAL库版本比较新回调函数名可能是HAL_ADCEx_MultiModeConvCpltCallback原理一样。主循环里查到adc_dma_done置1就说明一批新数据已经准备好了。3.2 把DMA里的交错数据还原成波形双ADC交替采样时缓冲区里的数据顺序是adc_buffer[0]来自ADC1adc_buffer[1]来自ADC2adc_buffer[2]又来自ADC1adc_buffer[3]来自ADC2以此类推。也就是说偶数下标是ADC1的结果奇数下标是ADC2的结果。从使用的角度看如果你想直接对整段数据做FFT或者波形显示其实不需要把奇偶拆开因为这串数据本来就是等间隔的连续序列时间间隔是单个ADC间隔的一半。你直接遍历整个缓冲区按电压公式换算就行float voltage (float)adc_buffer[i] * 3.3f / 4095.0f;这里分母是4095不是409612位ADC满量程编码是4095很多人第一次写会写成4096结果整体偏高一丁点虽然不大但强迫症受不了。如果为了调试想分别观察ADC1和ADC2的波形可以写一个简单循环把奇偶分开。我建议第一次跑通后一定要做这一步因为接下来要判断两个ADC的一致性只有确认了奇偶通道都正常后面才敢放心用整段数据去算FFT。实际采样间隔可以这样算ADC时钟21MHz、SMP3时单ADC采样间隔0.714us双ADC交替后的相邻点间隔是0.357us等效采样率约2.8Msps。假设你采了一个100kHz正弦波一个周期能采28个点已经比单ADC的14点好太多。3.3 顺手做的去直流、归一化和滤波采集完原始数据后很多场景都需要做后处理常见的三个操作是去直流、归一化和滤波我在这里给出最朴素的实现方式够用且好理解。去直流最简单的做法是先求整段数据的平均值再把每个点减去平均值uint32_t sum 0; for (int i 0; i ADC_BUFFER_SIZE; i) { sum adc_buffer[i]; } int32_t avg sum / ADC_BUFFER_SIZE; for (int i 0; i ADC_BUFFER_SIZE; i) { float normalized (float)(adc_buffer[i] - avg) * 3.3f / 4095.0f; // 这里继续你的后续处理 }归一化一般在去直流之后做可以除以峰值也可以除以有效值看你要分析什么。如果只是看波形轮廓除以峰值比较直观如果要算信噪比或者做频谱分析建议除以有效值。滤波这块最常用的是滑动平均滤波#define WINDOW 5 for (int i WINDOW / 2; i ADC_BUFFER_SIZE - WINDOW / 2; i) { uint32_t acc 0; for (int j -WINDOW / 2; j WINDOW / 2; j) { acc adc_buffer[i j]; } filtered_out[i] acc / WINDOW; }但我要提醒你滑动平均会降低有效带宽对于本来就在追求高速采样的场景别盲目滤波。如果信号本身干净滤波反而把细节磨掉了。真遇到偶发尖峰用中位值滤波往往比均值滤波更合适。如果你想把数据发到电脑上看最简单的办法是通过串口把原始ADC值按CSV格式打出去再用串口示波器或者波形工具导入。这里有个经验不要在主循环里用阻塞式HAL_UART_Transmit发送大批量数据会拖垮采集节奏要发就用串口DMA或者先把要发的数据放进缓冲等ADC一轮采集结束后再统一发送。4. 实测记录采样率没变快、波形乱跳怎么办4.1 先验证通道顺序用低频正弦波找规律我第一次跑双ADC交替采样时波形看起来总是不对劲FFT出来一堆杂散后来发现不是配置问题而是我没搞清楚缓冲区里谁先谁后。验证方法其实很简单信号发生器输出一个1kHz正弦波接到ADC通道引脚然后分别把缓冲区里的偶数下标和奇数下标单独还原成波形在调试器里看数组。正常情况下两个波形应该都是平滑的正弦波只是相位上略有差异。如果偶数位波形正常、奇数位波形有固定偏移或者幅度偏小说明ADC1和ADC2之间的增益/失调不一致。如果奇偶波形都正常但拼接起来后锯齿感很强大概率是两个ADC的转换结果在缓冲区里的顺序跟预期相反可以把DMA目标地址往后移一个半字或者软件里做奇偶交换。这个验证步骤值十分钟别省。高速采集的数据一旦错位后面所有分析都白做排查起来比重新配置还要费时间。4.2 计算真实等效采样率而不是看手册标题很多人以为开了双ADC交替采样就直接到4.8Msps这是最大误区。4.8Msps是ADC时钟36MHz、SMP3、双ADC交替的理论极限实际工程中受分频限制、信号源内阻、PCB布局等因素影响很难跑满。我用常见的21MHz ADC时钟做了几组计算整理成表格给你参考ADC时钟采样保持SMP单ADC实际采样率双ADC交替等效采样率21MHz3 Cycles1.40Msps2.80Msps21MHz15 Cycles0.78Msps1.56Msps14MHz3 Cycles0.93Msps1.87Msps14MHz15 Cycles0.52Msps1.04Msps如果你追求高采样率就把SMP调到3如果信号源输出阻抗比较高或者前端电路驱动能力弱采样时间太短会导致采样电容充不满转换结果偏低这种情况宁可把SMP调大一点让波形更可靠。片内ADC再快前端信号跟不上也是白搭。4.3 常见问题速查表这段时间我周围也有好几个朋友在调双ADC加上自己踩过的坑我把典型问题整理成了一张速查表排查时直接对着看现象可能原因解决方法adc_buffer全为0DMA没启动或通道未接到引脚检查HAL_ADCEx_MultiModeStart_DMA是否调用用万用表确认引脚有信号波形呈规律性锯齿ADC1和ADC2增益/失调不一致分别提取奇偶数据做两点校正或确认SMP设置相同数据每隔一个点跳变DMA数据宽度选成了Word改回Half Word缓冲区数据被莫名覆盖ADC2也重复配置了DMA删除ADC2的DMA配置只保留主ADC1的DMA中断里卡死或丢数据在DMA中断回调里做了浮点运算中断里只置标志位浮点运算放到主循环调试器暂停后波形乱调试模式暂停导致ADC时钟停止正常现象不要用它验证动态波形想用Proteus仿真但找不到F407模型双ADC和DMA时序在仿真里支持不完善建议直接用开发板实测别在仿真上耗时间关于ADC1和ADC2不一致的校正我用的方法是输入一个已知直流电压分别统计两个ADC各2000个点的均值算出各自的offset再测另一个电压点算gain比例最后对其中一个通道做线性修正。公式就是最简单的两点校准adc2_corrected (adc2_raw - offset2) * (gain1 / gain2) offset1如果只是做FFT不看绝对幅度这个校正可以简化成只修正offset因为相邻两个通道的gain差异在多数情况下很小。4.4 给同样在折腾双ADC的朋友几句实在话这套方案我前前后后调了两个星期最大的体会是不要一上来就追求极限速度。先用1kHz正弦波跑通整个链路确认DMA搬运正常、奇偶通道顺序正确再一点点往上加频率遇到波形异常就回头查配置这样省时间得多。还有一点很关键F407的双ADC交替采样能把等效采样率提到Msps级别但ADC前端电路才是真正决定系统性能的地方。我用过一次很长的杜邦线直接接信号源结果高频波形严重畸变还以为是双ADC配置出了问题后来换成短引线加运放缓冲波形立刻干净了。芯片的ADC速度再快输入信号没准备好后面都是白算。如果你后续要用更高频率采集可以考虑F407的8位或6位分辨率模式转换周期更短采样率还能再往上拉。但代价是分辨率下降值不值得就看你的应用场景了。至少就我目前的振动分析和音频采集需求来说双ADC交替采样加DMA搬运这套组合已经是F407上性价比很高的方案了。
返回列表