STM32 ADC+DMA+TIM实现高精度交流信号采集全攻略

发布时间:2026/9/3 21:11:00
STM32 ADC+DMA+TIM实现高精度交流信号采集全攻略 简介面向STM32嵌入式开发者的交流信号采集完整工程基于CubeMX图形化配置重点演示模拟数字转换器、直接内存访问与定时器协同工作适合需要掌握多外设联动采样、降低处理器负担的初中级开发者参考。工程中由定时器产生触发信号控制模拟数字转换器按设定频率周期性转换转换完成后数据由直接内存访问自动搬运到内存处理器仅在必要时介入代码结构清晰便于理解从模拟输入到数字缓冲区的完整采集链路也可迁移至电力监测、信号分析等实际项目。包内共一百七十六个文件大小约七点九二兆字节涵盖源码、工程配置、初始化图形配置、可执行文件、调试输出文件等类型能够还原从创建工程、初始化外设、编译烧录到在线调试的流程。目前已有四千五百三十人学习浏览资源内含初始化示例、定时器与直接内存访问设置要点、中断处理逻辑和常见调试思路是快速上手该采集方案的实用参考。1. 为什么交流信号采集非要ADCDMATIM三位一体先说个场景。我之前用STM32F103做个工频交流电压监测的小板子一开始图省事直接在主循环里轮询ADC采样率用延时大概凑合了一下。结果波形一上示波器就傻眼了明明输入的50Hz正弦波采出来像被狗啃过一样毛刺一堆相位还是歪的。后来换成ADCDMATIM的组合才算真正把这个问题解决干净。这背后的原因其实不复杂。交流信号不像直流那样读一次就完事它要求在一个周期内均匀地采足够多的点才能还原出波形、算有效值、算频率、算谐波。如果单纯靠CPU在主循环里读ADC采样间隔会受程序分支、中断响应、甚至编译器优化影响抖动非常大。而STM32的ADCDMATIM这套组合设计的初衷恰恰就是解决“精确、连续、不占CPU”的采样需求。三个人各司其职**TIM定时器**负责“节拍器”。它产生触发信号告诉ADC“现在该采一次了”。采样间隔由定时器的重载值和分频系数严格决定不随主程序跑得快慢而改变。ADC负责“拍照”。收到触发信号后在精确的时间点把模拟电压转换成数字量。DMA负责“搬运工”。ADC转换完成后结果自动被搬到内存缓冲区整个过程不需要CPU干预。这套组合对交流信号采集的意义在于采样时刻是确定性的。你只要算好定时器的触发频率就能保证波形上每个采样点之间的时间间隔完全一致后续做FFT也好、算RMS也好数据才靠谱。另一个容易被忽视的好处是CPU被彻底解放了。比如采样率20kHz每秒产生2万个中断是巨大的开销但用DMA传输采集完一整批数据后CPU才介入处理中间可以安心跑显示、跑通信、跑控制算法。提示如果你只需要采几个点、间隔不敏感单独用ADC轮询就够。但只要涉及交流信号、PWM波形还原、FFT频谱分析或者需要对采样时间节奏做精确控制ADCDMATIM基本就是绕不开的标准姿势了。2. 先算清楚采样率、缓冲区、触发方式怎么定2.1 采样率计算从信号频率反推交流信号采集的第一步是定采样率这一步很多人拿不准。我自己的经验是先定信号最高频率再定每周期采样点数两者相乘就是最低采样率。以工频50Hz交流电为例我一般一个周期采64个点计算出来就是采样率 50Hz × 64点 3200Hz但实际做项目时我不会只采这么点。64个点做有效值计算够用但要做谐波分析就捉襟见肘了。采样定理的要求是最低两倍信号频率但工程上通常取5~10倍以上交流信号采集我习惯一个周期采128~256个点。如果目标是采集50Hz信号并分析到19次谐波大约950Hz那采样率至少要2000Hz以上建议4kHz~10kHz余量给足。这里有个重要概念叫混叠。如果采样率不够信号中的高频分量会“伪装”成低频分量出现在采样结果里而且是不可恢复的错误。所以定采样率时不光要看基波频率要看信号里可能存在的最高频率成分。2.2 DMA缓冲区与传输模式的选择缓冲区大小直接决定DMA中断的频率。举个例子采样率设了8000Hz即每125微秒采样一次。如果缓冲区设64个点那么DMA传输完成中断大约每8毫秒触发一次8000Hz ÷ 64这个频率对CPU来说非常友好。缓冲区也不是越大越好太小中断频繁CPU频繁被唤醒处理不过来。太大数据实时性下降内存占用多处理延迟高。我常用的经验值是缓冲区大小 采样率 ÷ 期望中断频率。比如期望1kHz中断频率采样率8kHz那缓冲区8个点就够了想做更复杂的处理就取64或128。DMA工作模式有Normal和Circular两种。交流信号采集中我几乎总是用Circular循环模式。原因在于交流信号是周期性连续信号需要持续不断地采集循环模式让DMA自动回卷缓冲区永远在刷新配合双缓冲或半满中断可以实现“边采边处理”的效果。Normal模式适合“触发一次采一批”的场景比如按键按下才采集一段波形。2.3 触发方式内部定时器触发还是外部PWM触发STM32的ADC触发源在CubeMX里可以看到定时器触发、外部IO触发、软件触发、定时器TROGO事件等。交流信号采集一般用定时器触发就够了具体配置如下。但热词里反复出现“PWM触发ADC采样”这其实是另一种典型用法。用PWM触发采样的场景通常是你需要采样点与某个PWM周期严格对齐比如电机控制中在PWM开通的特定时刻采集母线电流。这种场景下定时器的输出比较通道产生PWM的同时TRGO事件触发ADCADC的采样时刻与PWM边沿严格同步。两种方式如何选我总结如下应用场景推荐触发方式理由工频交流电采集、音频采样定时器TRGO触发均匀间隔采样配置简单电机控制、开关电源电流采样PWM边沿触发采样点与开关动作严格对齐一次性波形捕获外部IO触发或软件触发事件驱动灵活控制注意触发源的选择在CubeMX里如果选错最常见的问题是ADC完全不动或者转换频率完全不对。我排查过很多次类似的“ADC不工作”问题最后发现就是触发源没配对。3. CubeMX配置实操一步步搭好工程3.1 定时器配置从时钟树推到触发频率以STM32F103C8T6为例APB1定时器时钟默认是72MHz。假设我想要采样率8000Hz也就是触发周期125微秒那么定时器配置是预分频器PSR 72 - 1 71即72分频计数器时钟变为1MHz自动重载ARR 125 - 1 124计数器从0计数到124正好125个1MHz时钟周期即125微秒这样配置后定时器的更新事件频率就是1MHz / 125 8000Hz。CubeMX里的操作路径选择TIM2或任意可用定时器时钟源选Internal Clock。设置Prescaler为71Counter Period为124。打开Trigger Output (TRGO)事件选择Update Event。确认定时器的时钟频率确实是你预期的值HAL里用HAL_RCC_GetPCLK1Freq()可以验证注意APB1预分频不为1时定时器时钟会翻倍这个细节非常容易踩坑。3.2 ADC配置扫描、连续转换、DMA请求一个都不能少ADC部分的关键设置每个都值得展开讲Continuous Conversion Mode连续转换模式用定时器触发时这个要选Disabled。因为每次转换都是由定时器事件触发启动的如果开启了连续转换ADC会在一次转换结束后立刻启动下一次完全打乱采样节奏。Discontinuous Conversion Mode不连续模式只采集一个通道时不用开。Number Of Conversion采集交流信号通常单通道就够选1。DMA Continuous Requests这个必须选Enabled。它的作用是在ADC产生新数据后自动向DMA发送请求。如果忘了开DMA只会在第一次转换时传输后面就不再搬运数据这是我最常遇到的“采集一次就停”的原因之一。ADC采样时间交流信号采样我一般选55.5 Cycles或更大。这里需要权衡采样时间越长内阻带来的采样误差越小但ADC的等效输入阻抗要求就越低采样时间太短对信号源阻抗很敏感。市电经过电阻分压后信号源阻抗一般比较高所以宁可多预留采样时间。还有Scan Conversion Mode只采集一个通道时选Disabled就行。但如果你要同时采集多相电压或者电压电流就需要开启扫描模式并把各个通道按顺序添加到转换序列里。多通道时DMA缓冲区的数据是“通道1结果、通道2结果、通道1结果、通道2结果”交替排列的解析时要做好区分。3.3 DMA配置循环模式是交流采样的默认答案DMA设置里Direction选择PeripheralToMemoryMode选择CircularData Width两边都选WordADC的12位结果存放于32位寄存器中选Word对齐处理起来最方便。这里有个容易忽略的点DMA句柄和ADC句柄要关联起来。在CubeMX中只要在ADC配置里开启了DMA请求并选择了对应的DMA通道代码生成时会自动把hadc1.DMA_Handle指向hdma_adc1。如果你用手写代码的方式千万别漏掉这一行hadc1.DMA_Handle hdma_adc1;这在热词里也体现了“dma句柄与dac句柄关联”可见是很多人栽过跟头的地方。漏掉这行DMA根本不会启动ADC转换完的数据就停留在数据寄存器里读出来永远是最近的一次结果。3.4 生成代码后的“补丁”CubeMX生成代码之后还需要在用户代码区填几个东西启动ADC的DMA采集HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buf, ADC_BUF_SIZE);使能定时器触发HAL_TIM_Base_Start(htim2);这里有个顺序问题我建议先启动ADCDMA再启动定时器。如果顺序反了定时器先跑起来触发信号到来时ADC和DMA还没Ready转换可能丢失。开启DMA的半传输和传输完成中断回调HAL_ADC_ConvCpltCallback(hadc1); // 半缓冲/全缓冲处理4. 数据处理的三个细节零点漂移、有效值计算、滤波4.1 中点电压与零点漂移交流信号有正半周和负半周但STM32的ADC只接受0~3.3V或参考电压的输入。所以交流信号进入ADC前必须通过偏置电路把负半周抬高到正值常见做法是用电阻分压把信号叠加到VCC/2附近。那么问题来了DMA采回来的原始值转换后的中间值对应的其实是“零电位”。处理时首先要减去这个直流偏置再除以增益才得到真实的电压值。我自己的做法是用软件校准来代替硬件微调接输入短路或者接已知直流电平采一批数据取平均值作为零点存到Flash里每次上电校准一次。这比硬件上调电位器方便得多也稳定得多。4.2 有效值计算时缓冲区对齐计算交流电压有效值的公式是RMS sqrt( (1/N) * sum(x[i]^2) )其中x[i]是去偏置后的采样值N是采样点数。但有个细节N必须是信号周期内采样点数的整数倍。比如每周期采128点采样1280点就是10个整周期计算误差很小。如果缓冲区长度不是整周期首尾相位不连续RMS计算结果会有明显波动。解决办法有两个根据频率动态调整缓冲区大小锁定整数周期。先用过零检测或FFT估算频率再计算。实战中我常用第二种先用定时器捕获热词里的“STM32定时器捕获测频率”测出频率然后计算需要采集多少个点覆盖整周期再启动一次DMA采集。这样算出来的有效值非常稳定。4.3 滑动平均与去毛刺交流信号采集中存在开关噪声、继电器抖动、电磁干扰等采集结果偶尔会出现离群值。我的做法是在应用层加一个简单的滑动平均滤波uint32_t sliding_filter(uint32_t *buf, uint8_t len) { uint32_t sum 0; for (uint8_t i 0; i len; i) { sum buf[i]; } return sum / len; }不过要注意滑动平均对50Hz交流信号做平滑会带来幅值衰减如果对精度要求高可以改成去掉最大值最小值再平均去极值平均滤波或者用中值滤波。热词里有人搜“c语言adc值滤波函数”建议先弄清楚有哪些滤波类型再结合信号特征选不要盲套。5. 装机实测采集到的波形“翻车”了怎么办5.1 现象一只采到一次数据后面全是0这是没开DMA Continuous Requests的典型症状。ADC第一次转换后DMA传输正常但因为没有持续请求DMA认为自己完成了任务后面不再搬数据。检查方法在DMA传输完成中断里看一眼搬进来的缓冲区内容。检查CubeMX配置里DMA Continuous Requests是否为Enabled。确认是不是用了Normal模式而缓冲区太小一次填满后DMA停止。5.2 现象二波形有“锯齿”或毛刺密集如果采样间隔不均匀波形会呈锯齿状。可能的原因定时器里开了中断但中断里干了太多事导致触发事件被延迟。ADC连续转换模式没有关闭定时器触发与连续转换互相打架。系统里还有更高优先级的中断频繁抢占影响DMA传输。排查思路先把所有其他中断关掉只保留定时器和DMA看波形是否好转。如果好转说明优先级或中断负载有问题如果依旧毛刺看触发频率是否实际和预期一致。我印象很深的一次排查定时器配置的时钟是72MHz但APB1预分频设置为4定时器时钟其实变成了144MHzSTM32F4系列的“两倍”机制。我按72MHz算出的数值实际触发频率高出整整一倍波形却还是“对”的只是点数密了一倍。直到用定时器捕获测频率才发现采样率和期望值对不上。5.3 现象三波形是反的或者幅度不对波形反相输入偏置电路里运放反相放大或者变压器同名端接反。检查硬件。幅度偏小ADC采样时间不够信号源内阻大导致采样电容没充满增加采样时间试试。幅度偏大甚至削顶输入信号超过了ADC量程检查偏置电压是否在1.65V附近以及分压电阻的阻值是否合适。5.4 现象四数据跳动非常大如果数字量在某个值附近不停跳变但波形宏观形状是对的多半是参考电压噪声。STM32的内部参考电压VREF如果直接从3.3V供电取而3.3V上有纹波ADC结果就会跟着抖。解决办法ADC参考电压从单独的VREF引脚输入用专门的参考电压芯片供电。如果条件有限至少在VREF引脚上加一个低ESR的钽电容或高质量陶瓷电容并联一个0.1uF和10uF。采集时可以多采几次求平均牺牲一点速度换稳定性。6. 双缓冲和半满中断把处理延迟压到最低上面提到DMA循环模式可以连续采集但如果你希望在采集的同时处理上一批数据光用普通传输完成中断会浪费一半时间。更高效的做法是利用DMA的半传输中断Half Transfer Interrupt和传输完成中断Transfer Complete Interrupt。原理是这样的假设缓冲区长度是256字。DMA搬到128个字时触发半传输中断CPU开始处理前半段数据。DMA继续搬剩下的128个字搬完触发传输完成中断CPU处理后半段数据。缓冲区自动回卷到起点开始新一轮。这样一来CPU的处理时间分散到了整个采样周期不需要等到缓冲区完全填满才开始处理数据的实时性提升一倍。在交流信号采集里如果你后续接了PID闭环或者实时通信这个特性非常有用。CubeMX里开启方式不复杂在DMA配置里Mode选Circular。在代码中使能中断__HAL_DMA_ENABLE_IT(hdma_adc1, DMA_IT_HT); // 半传输中断 __HAL_DMA_ENABLE_IT(hdma_adc1, DMA_IT_TC); // 传输完成中断在中断回调里用DMA_GetDataCounter判断当前写到哪了最稳妥的做法是分别实现HAL_ADC_ConvHalfCpltCallback和HAL_ADC_ConvCpltCallback。这就是热词里“dma双缓冲”的思路基础。严格来讲STM32系列中有一部分型号支持真正的DMA双缓冲模式可以在两个内存缓冲区之间自动切换但基本原理和应用场景是相通的采集与处理流水线化。7. 再往后可以怎么扩展聊回交流信号采集本身。完成ADCDMATIM这套基础方案后有几个方向可以继续深入。一是多通道同步采样。如果项目需要同时采集三相电压建议选择支持ADC注入组或规则组多通道排序的型号。STM32F3系列有真正的同步采样ADC多相电力监测这种场景优先选支持ADC互连的型号不要试图用单ADC扫描模拟相位差很难修。二是带通滤波和相位补偿。真实互感器、分压网络都会引入相位偏移处理交流信号时如果需要功率因数或者相位差分析就需要在软件里做相位补偿。这属于“信号链设计”和DMATIM本身无关但和采集质量直接挂钩。三是进入频域分析。交流信号采集经常伴随FFT需求比如畸变率、谐波含量。在STM32上做FFT采样率和缓冲区长度需要满足FFT的分辨率要求。比如FFT分辨率10Hz采样率8000HzFFT点数就是800点向上取到1024点这正好和前面说的缓冲区大小选择呼应在一起。我自己的感受是ADCDMATIM这套组合前前后后帮我解决了不少“采集不准”“实时性差”“CPU被打满”的麻烦几乎每个需要波形的项目都会回来用它。它的门槛不在于“会不会配置”而在于你是否真正理解每个参数在时间轴上意味着什么。算清楚采样时刻理解DMA的搬运节奏配置上的坑基本就避开了大半。最后分享一个小技巧调试采样周期是否准确不一定要接信号发生器打波形。直接在定时器输出引脚引出同频率的方波用示波器看方波频率就能验证定时器触发频率是否正确。这个办法很笨但每次都能让我快速锁定是“配置问题”还是“信号问题”。本文还有配套的精品资源点击获取