ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F429 HAL库 ADC+DMA多通道数据采集实战详解

STM32F429 HAL库 ADC+DMA多通道数据采集实战详解 简介面向STM32F429嵌入式开发者的ADCDMA例程基于HAL库实现高效模拟量采集适合学习单片机外设配合DMA传输的工程师。工程共297个文件、约12.05MB其中包含92个.h头文件与80个.c源文件覆盖标准外设驱动和HAL库配置同时带有uvprojx工程文件、hex可执行文件及编译中间文件便于直接打开工程、对照源码和烧录验证。已有169人学习适合正在研究多通道采样、低CPU占用数据搬运或中断处理流程的读者。资源从ADC初始化、通道配置到DMA启动与数据接收均有完整代码可帮助理解HAL_ADC_Start_DMA的调用链路并延伸到错误检查、状态读取和资源释放等实际工程问题对开发工业数据采集或物联网传感设备有直接参考价值。 去年下半年我接了一个工业数据采集的小项目主控选的STM32F429需要同时采集三路模拟信号还要求不占用CPU时间去轮询。当时第一反应就是HAL库加ADC加DMA这套组合拳。从CubeMX生成工程到最终调通前后折腾了差不多一周中间踩了不少坑也积累了一些比较实用的经验。借着整理例程包的机会我把这套基于HAL的ADCDMA完整流程重新梳理了一遍从设计思路、CubeMX配置、代码实现到问题排查都写清楚希望能帮到正在用F429做采集的朋友。这套方案解决的核心问题其实很明确让ADC在后台连续不断地把采样结果搬进内存CPU该干嘛干嘛等数据到了直接拿过来用就行。对于多通道模拟量采集、音频采样、示波器这类需要高频连续采样的场景这套组合基本是标配做法。如果你刚开始接触HAL库或者想搞明白ADC和DMA到底怎么配合这篇内容应该能给你一个比较完整的参考。1. 整体设计思路与方案选型1.1 为什么选择HAL库而不是标准库很多从STM32F103时代过来的老工程师习惯用标准库寄存器操作写在明面上逻辑也直白。但F429甚至更新的系列ST官方已经把重心完全转移到HAL库和LL库上。我自己从HAL库角度做一个客观评价封装程度高、代码可读性好而且CubeMX能直接生成初始化代码省去大量手写寄存器配置的时间。当然它也有一层层的结构体嵌套刚开始看源码容易懵不过在ADCDMA这个场景下HAL库抽象出来的调用接口比标准库确实省心不少。另外HAL库的坑也不少比如某些外设的回调函数需要自己在中断里手动调用、某些版本的库在DMA模式下有缓存一致性问题等但这些都有固定的处理套路后面在实例环节一一展开。1.2 F429的ADC资源与DMA映射关系STM32F429内部有3个独立的ADC分别是ADC1、ADC2、ADC3每个都是12位精度最多支持19个通道16个外部引脚加3个内部通道温度传感器、VREFINT、VBAT。ADC1和ADC2可以组成双模式同步采样ADC3也可以加入组成三ADC同步采样这对三相电机电流采集这类应用是很有用的特性。DMA这块F429的每个DMA控制器有8个数据流每个数据流有8个通道请求映射。ADC1的DMA请求是映射到DMA2的Stream0和Stream4如果你用的是ADC3那对应的是DMA2的Stream1。这个映射关系在参考手册的DMA请求映射表里都能查到但很多人第一次配置会搞错。如果CubeMX里配错了DMA数据流ADC数据根本不会搬运而且还不报错只能干瞪眼。我选ADC1DMA2 Stream0这个组合配置简单逻辑也最顺。如果后续要做双ADC同步采样再扩展到ADC1ADC2的双DMA通道也不冲突。1.3 连续采样vs单次采样的取舍ADC采样的基本流程分两种单次转换和连续转换。单次转换就是触发一次采一个点转换完成后停止等待下一次触发适合低频、单点的电压读取。连续转换则是一旦启动ADC就不停地采样、转换、更新数据寄存器直到你主动调用停止函数。在DMA配合下连续转换才能发挥出真正价值。因为DMA能把每次转换结果自动送到内存缓冲区CPU只需要定期来处理缓冲区的数据就行。如果单次转换配合DMA每次都要重新启动一次转换启动的软件开销反而把DMA的优势抵消了。所以我这个例程选用ADC连续转换加DMA循环模式数据链路全程无CPU干预。2. 核心细节解析与实操要点2.1 时钟树配置ADC时钟不能超36MHz刚开始用CubeMX时容易忽略一个问题ADC的输入时钟不是直接拿APB2时钟而是要经过ADC预分频器。F429的APB2挂的是90MHz如果主频跑180MHz而ADC的时钟最高只能到36MHz所以预分频系数最低也要4分频。这个限制直接决定了ADC的采样速度上限。12位分辨率下ADC的转换时间 采样周期 12个时钟周期。如果采样周期设为3周期那么总转换时间是15个ADC时钟周期36MHz时钟下就是约2.4Msps。如果外部阻抗较大、信号源输出能力弱就只能把采样周期拉长比如84周期或480周期速度自然降下来。这个取舍要在配置前就想清楚尤其做高速采集时时钟树没配好后面怎么调都白搭。CubeMX里的具体做法是在Clock Configuration页面里把APB2 Prescaler调到4分频以上保证ADC输入时钟不超36MHz然后将ADC的Clock Prescaler设为4分频得到90/422.5MHz。这样既满足时钟上限也给后续采样周期配置留了余量。2.2 DMA核心参数数据宽度、循环模式与内存递增DMA配置里最容易出问题的三个参数我逐个说。第一个是外设数据宽度和内存数据宽度。ADC1的数据寄存器是32位的但实际有效的只有低16位12位精度外加对齐方式决定有效位位置。因此外设数据宽度设置为半字Half Word更合理内存端也设置半字这样每个采样点占2字节多少点点进去就是多少字节不用自己算偏移。如果两边都用字Word内存占用翻倍处理起来还容易把无效的位也带上。第二个是循环模式Circular Mode。这个必须开。开了之后DMA传输满一整轮缓冲区后会自动把传输计数器重置回初始值继续从头写数据。这个特性配合ADC连续转换就是一套无限循环的数据采集流水线。如果不开循环模式传输一轮就停了ADC还在继续转换但数据全丢。第三个是内存地址递增Memory Increment。ADC外设地址始终是同一个寄存器地址所以外设地址不递增但内存地址要从缓冲区的第一个字节一直写到最后一个必须开启递增。这两个递增设置是独立的各配各的很多人只开一个导致数据全写进同一个地址到时候看到的现象就是缓冲区每个值都一样。2.3 规则组与注入组的选择逻辑F4系列ADC有两类转换通道组规则组和注入组。规则组最多16个通道按顺序依次转换转换结果都进同一个数据寄存器所以多通道时必须在DMA中断里根据通道序号去区分数据。注入组最多4个通道优先级更高可以打断规则组转换转换结果各进各的寄存器适合需要严格同步采样的场景。对于绝大多数普通多通道采集需求规则组就够了配合DMA和扫描模式Scan ModeADC会按配置的通道顺序一个一个转下去DMA会按同样的顺序把结果摆进缓冲区一一对应。需要注意是规则组模式下数据寄存器只有DR一个所以如果不开启DMA多通道数据读取会非常别扭要不停地读DR和查状态位。DMA几乎是规则组多通道的标配。3. 实操过程与核心环节实现3.1 CubeMX配置完整步骤这个例程的配置我建议全程在CubeMX里完成手写裸寄存器太容易丢细节。第一步先选芯片型号STM32F429ZIT6然后在Pinout视图里找到ADC1把IN0、IN1、IN2三个引脚使能对应的是PA0、PA1、PA2三个外部引脚。ADC1的参数设置如下ModeIndependent Mode独立模式Scan Conversion ModeEnabled多通道必须开Continuous Conversion ModeEnabled连续转换Discontinuous Conversion ModeDisabledNumber Of Conversion3三个通道Rank配置三个Rank分别选Channel 0、1、2每个Rank的采样时间先都设为168 Cycles后续根据信号源阻抗再优化ADC DMA RequestEnabled打开DMA请求DMA Continuous RequestsEnabled这个选项必须开不开的话每次转换完一轮就停DMA设置里添加DMA2 Stream0方向是Peripheral To Memory外设数据宽度Half Word内存数据宽度Half Word循环模式Enabled内存地址递增Enabled外设地址不递增优先级High。时钟树部分把主频配到180MHzAPB2分频为4ADC预分频选4分频。如果暂时不需要这么高的主频降到168MHz也行只要保证ADC时钟不超36MHz就问题不大。3.2 代码实现与初始化流程CubeMX生成工程后主要代码其实已经齐了但有几个关键点要在用户代码区补上。先看初始化之后的核心启动代码#define ADC_BUF_SIZE 1024 __IO uint16_t adc_buf[ADC_BUF_SIZE]; __IO uint8_t adc_dma_flag 0;主函数里启动ADC采集的调用方式HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, ADC_BUF_SIZE);这一行做完ADC和DMA就自己跑起来了不需要再做其他事。adc_buf就是DMA要填充的内存缓冲区大小按需分配。如果开三个通道缓冲区里数据的排列是ch0、ch1、ch2、ch0、ch1、ch2循环排列每个通道占据总数三分之一的空间。DMA传输完成一半和传输完成一整轮时HAL库会调用对应的回调函数。在回调里做数据搬运是最省事的void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc-Instance ADC1) { // 一整轮DMA传输完成 adc_dma_flag 1; } } void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if(hadc-Instance ADC1) { // 一半传输完成可以做双缓冲处理 } }主循环里检测标志位然后计算有效值或做滤波。3.3 数据读取与滤波处理DMA把原始数据拿回来后如果直接拿来做反馈控制数值抖动通常比较大。我一般会在应用层做两级处理第一级是中值滤波每次取5个点排序取中间值把偶尔出现的尖峰干掉第二级再做一次滑动平均让波形平滑。uint16_t get_median_filtered_value(uint32_t channel_index) { uint16_t sample[5]; uint16_t temp; for(int i 0; i 5; i) { sample[i] adc_buf[channel_index i * 3]; if(i 0 sample[i] sample[i - 1]) { temp sample[i]; sample[i] sample[i - 1]; sample[i - 1] temp; } } return sample[2]; }注意这个排序只是简化版的气泡排序思路实际使用中数据量大时建议用更好的排序算法。ADC多通道下通道数据是交错存放的取通道值时索引一定要按通道数对齐这上面栽过跟头。3.4 数据校准与多通道扩展F429的ADC本身有校准功能HAL库也提供了校准函数。启动ADC之前建议先调用一次校准HAL_ADCEx_Calibration_Start(hadc1);校准能消除内部电容阵列的偏差直接关系到最后转换结果的精度。如果对精度要求高比如工业仪表这种还需要自己做两点校准用万用表实测几个电压点然后做线性拟合把增益误差和偏移误差都校正掉。HAL库不做这个事应用层需要自己处理。如果后续通道数不够用可以考虑把ADC2也拉进来两个ADC各管几路或者组成双ADC同步采样模式。后者需要把Mode从Independent改成Dual Regular SimultaneousDMA配置也要相应调整F429的参考手册在这一块讲得比较详细这里不展开。4. 常见问题与排查技巧实录4.1 问题速查表我从实际调试中整理了一些高频问题按症状、原因、解决方式列个表方便对照排查症状可能原因解决方式DMA传输完成标志一直不置位DMA数据流选错或DMA连续请求没开核对数据流映射开启DMA Continuous Requests所有通道数据都一样内存地址递增未开启在DMA配置中开启Memory Increment数据只更新一次后不再变循环模式未打开或ADC连续转换未开启开启Circular Mode和Continuous Conversion采集值整体偏高或偏低未做ADC校准或参考电压不准先调用校准函数必要时做两点校准波形出现周期性毛刺采样周期太短信号源驱动能力不足增大采样时间比如从3周期改成84周期多通道数据错位DMA缓冲区索引没和通道数对齐检查取数据的索引算法确保按通道交错读取4.2 一个容易忽略的隐藏坑规则组序列错误F429的规则组通道转换顺序不是自动编号的是通过Rank配置来定义。比如你把Rank1配成Channel 2、Rank2配成Channel 0那转换顺序就是先CH2再CH0和引脚编号完全无关。有一次我就是Rank顺序和预期不一致导致采集回来的三路信号对不上号检查半天才发现是CubeMX里Rank配置拖拽错了顺序。所以多通道配置完成后一定要回CubeMX的ADC1配置页里逐个Rank确认Channel选择是否正确顺序是否和预期一致。4.3 DMA回调里做的事尽量少HAL_ADC_ConvCpltCallback是在DMA中断上下文里被调用的这个中断的频率不低如果缓冲区是1024点、采样率2Msps那么一轮传输完成就有约2毫秒一次的中断回调。在这个回调里如果去做耗时操作比如打印日志、阻塞式发送串口、动态分配内存很容易把中断拖死甚至触发HardFault。正确的做法是回调里只设置标志位最多做一次内存拷贝把数据搬到应用缓冲区然后立刻退出。后续的数据处理全部放到主循环或低优先级任务里做。4.4 在线调试的小技巧调试ADCDMA时我强烈建议先别急着上FreeRTOS或者复杂算法把采集链路单独拉出来验证。用调试器在HAL_ADC_ConvCpltCallback里打一个断点或者看adc_dma_flag是否置位就能确认DMA链路是否通了。然后用调试器变量窗口周期性刷新观察adc_buf数组检查数据变化是否有规律。如果adc_buf所有数值都是4095或者0大概率是引脚配置和实际接线对不上如果数值在正常范围内但跳变剧烈就要考虑采样周期是不是太短或者信号源阻抗过高。另外在用ST-Link调试时如果开启了实时变量查看DMA写入内存的频率很高调试器读取内存的操作可能会影响采样时序。真遇到诡异问题可以断开调试器让程序独立跑再用串口把数据发出来对比排除干扰因素。5. 实测效果与调参经验5.1 不同采样时间下的表现差异我把三路信号分别接到PA0、PA1、PA2用信号发生器输出1kHz正弦波、直流偏置2V方波和三角波。第一次全部配置为3周期采样时间采集回来的1kHz正弦波没有明显失真但方波边沿位置有过冲抖动。把采样时间改到84周期后波形明显变平滑过冲消失但最高采样率下降不少。如果信号频率低不需要高速采样用长采样时间换稳定性是性价比很高的选择。对于1kHz级别的信号168周期采样时间下波形已经非常平滑了。实测10kHz以内信号168周期完全够用。只有上到50kHz以上时才需要考虑缩短采样时间。5.2 参考电压对ADC精度的影响F429的VREF引脚如果直接接3.3V那ADC满量程就是3.3V。但3.3V往往来自板载LDO精度和温漂都比较一般。对12位ADC来说参考电压每偏差10mV满量程读数就会偏差约12个LSB左右这个误差对高精度测量是一个不可忽略的量。讲究一点的方案是外部用专用参考电压芯片比如REF3030这种输出3.0V的器件然后软件上按实际参考电压去做归一化。如果手头没有外部基准至少要用万用表实测一下板子上的3.3V实际值在计算物理量时用实测值不要直接用3.3这个名义值。5.3 采样率与DMA中断频率的匹配假设ADC时钟36MHz采样周期84周期12位分辨率下每个点转换时间是841296个ADC时钟周期约2.67微秒理论采样率约375ksps。如果三通道扫描一轮三个点耗时约8微秒一轮采样率约125ksps。DMA缓冲区1024点一轮传输完成中断约8.2毫秒一次这个频率在中断里做标志位操作完全没压力。如果采样周期降到3周期单通道理论采样率能到2.4Msps三通道扫描约800ksps一轮1024点完成中断大概1.28毫秒一次。这种情况下中断回调只做标志位主循环里忙不过来可能会丢数据。这时候要考虑加大缓冲区或者用双缓冲DMAHalf Transfer中断Full Transfer中断配合乒乓切换CPU处理就有充足时间。6. 例程包使用说明与扩展思路6.1 例程包文件结构压缩包里我放了这几个文件ADC_DMA.iocCubeMX工程文件直接双击可以打开重新配置Core/Src/main.c、adc.c、dma.c核心代码README.md快速上手说明和接线图Doc/包含STM32F429参考手册ADC相关章节和HAL库ADC驱动源码注释拿到例程包后先打开ioc文件看一下配置确认和自己板子上的引脚是否一致。F429有很多封装引脚排列不一样如果板子上的ADC引脚不在PA0-PA2需要重新分配引脚。CubeMX里改引脚非常方便改完重新生成代码就行。6.2 扩展方向一多通道与多ADC协同如果采集通道超过3个可以在ADC1的Number Of Conversion里增加到16个通道但扫描一轮的时间会变长通道间切换也有额外开销。更合理的做法是ADC1管前8路、ADC2管后8路两个ADC并行采样DMA分别配置到两个不同缓冲区或者用双ADC模式做交叉采样提高单通道采样率。F429的双ADC模式里有一种叫交替模式Alternate Trigger可以让ADC1和ADC2交替采同一个通道等效采样率提升一倍。对高速信号采集这个模式值得深入研究。6.3 扩展方向二定时器触发ADC采样连续转换适合匀速采样但有些场景希望以精确的时间间隔采样比如工频信号每周期采128个点、电机电角度每转采固定点数。这时候用定时器触发ADC就对了。在CubeMX里把ADC触发源改成Timer Trigger Out事件同时配置一个定时器输出PWM或Update事件ADC就会在定时器触发沿到来时才启动一次转换DMA照样在后台搬数据精度由定时器时钟保证和主循环调度完全无关。我自己做电机相电流采样就是用的这种方案触发源用PWM中心对齐模式的Update事件精确到纳秒级别效果很稳。6.4 扩展方向三缓存一致性问题的注意点如果用的是F4系列不带D-Cache不存在缓存一致性问题。如果换了带D-Cache的芯片比如F7、H7系列DMA写入的内存如果被CPU读取可能会出现读到的还是旧缓存数据的情况。解决套路是要么DMA缓冲区放到非缓存的SRAM区要么在CPU读数据之前调用SCB_InvalidateDCache_by_Addr把对应内存区的缓存失效一下。这个坑在F429上不用操心但如果有人照着这个例程移植到H7上一定要提醒他们注意不然会莫名奇妙地读到旧数据。说了这么多我觉得这套ADCDMA的玩法核心就两个一个是把CubeMX里面的配置点全部理解透另一个是理解DMA中断和主循环之间的数据流关系。把这两块吃透了不管是F429还是其他F系列配置思路完全通用。例程包里也留了一些调试用的辅助函数比如把缓冲区的原始数据直接通过串口打印出来的工具实测对排查问题非常有帮助。如果你的F429板子已经到手建议直接烧进去跑一下把三路信号接上马上就能看到效果。本文还有配套的精品资源点击获取
返回列表