ARTICLE DETAIL

资讯详情

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

STM32F407定时器触发ADC与DMA传输的完整实现指南

STM32F407定时器触发ADC与DMA传输的完整实现指南 搞嵌入式的老哥应该都有体会凡是涉及电机电流采样、电网谐波分析、音频录放或者各种传感器数据采集的活ADC这一关就绕不过去。特别是当你要采的信号本身是周期性变化的或者系统要求采样点严格等间隔、不丢点同时CPU还得腾出精力去跑控制算法、刷显示、跑通信——这时候STM32F407上最经典的一套组合拳就出来了定时器TIM更新事件触发ADC启动转换转换完成后DMA自动把结果搬进内存全程不让CPU插手。这个方案配合HAL库CubeMX里拖一拖配置生成代码实际上手也不难但里面的门道真不少时钟怎么分配、采样周期怎么算、DMA为什么要用循环模式、初始化启动的先后顺序是什么新手不踩几次坑还真摸不透。这篇文章就按我实际调通并跑在项目里的方案把定时器触发ADC采集与DMA传输的整套细节拆开讲清楚给想要“抄作业”的朋友一份可以直接落地的参考。1. 方案设计思路为什么非要用“定时器触发DMA”这套组合拳先说一个最朴素的问题一套ADC采集方案你到底想让它解决什么很多人一开始都是GPIO按键、温湿度采集这类低速场景直接HAL_ADC_Start轮询就够了。但一旦进入电机控制、逆变器、电能质量监测这类领域要求瞬间就不一样了——你需要的是稳定的采样频率、确定性的采样时机、极低的CPU开销缺一个都容易出大问题。1.1 三种采样方式对比轮询、中断、定时器触发我拿最常见的三种做法做个对比看完你就能明白为什么最后会走到定时器DMA这条路。第一种是轮询采样也是最原始的。主循环里调用HAL_ADC_Start然后死等转换完成再读数据。这个方案的问题很明显采样的时间间隔完全取决于主循环跑到哪里了循环里有点延时函数、通信处理、按键扫描采样间隔就跟着漂。做低速采集无所谓做高速采集或者需要精确时间戳的信号这就等于架了个会抖的相机拍照拍出来的波形根本没法看。第二种是ADC转换完成中断。每次ADC转换完成硬件自动触发中断在中断回调里把数据读出来。这个方案比轮询强很多采样频率能由软件计算好启动时机也算可控但有个致命伤——中断频率受限于CPU的处理能力。比如ADC跑到1MHz转换速度那就是每秒100万次中断你中断里哪怕只干几百个周期的活CPU也直接被打满更别提腾出手来跑控制算法。而且高频率中断本身就会引入抖动中断优先级处理不好还会互相干扰。第三种就是今天要讲的定时器触发ADCDMA。定时器自己在那计数溢出事件输出TRGO信号硬件直接拉动ADC开始转换ADC转换完成写寄存器DMA又自动把寄存器里的数据搬进内存缓冲区。整条链路完全是硬件在跑CPU一个时钟周期都不用花。你只需要在DMA传输到一半或者传完一轮的时候进一次中断把数据搬走或者处理一下就行。1.2 这套组合能解决什么实际问题以我前段时间调的一个三相电机电流采样项目为例系统要采集UVW三相电流加上母线电压总共4通道模拟量采样频率要求8kHz每个采样点之间的时间误差不允许超过几个微秒同时主控还得跑FOC算法、来回通信、状态机管理。这种负载如果用中断采样8kHz的中断频率倒是不算高但每次中断里要依次启动4个通道的扫描通道切换时机稍有偏差三相电流采出来就有相位差直接影响控制效果。后来改成TIM2三分频输出TRGO直接触发ADC规则组扫描4通道DMA一次搬4个半字进缓冲区CPU完全不用管采样时机由硬件保证问题一下就解决了。这套架构尤其适合这几类场景电机控制里的电流/电压采样、电力电子里的谐波分析、音频信号采集、任何要求“高采样率低CPU占用严格定时”的场合。如果你只是偶尔读个电位器、温度传感器没必要上这套杀鸡用牛刀反而增加调试成本。但一旦项目对采样有硬实时要求定时器触发DMA就是STM32F407上最可靠、最省心的方案没有之一。2. 核心原理与参数计算先把采样频率和转换耗时搞明白再动手很多人用CubeMX配置的时候经常是照着别人的截图一通乱填PSC填个83ARR填个999代码一跑发现数据确实在更新就以为万事大吉了。但真要换一个采样频率需求或者换一块主频配置不同的板子马上就懵。所以这一节我把参数推导过程完整走一遍你以后不管在什么芯片什么时钟配置下都能自己算。2.1 定时器内部时钟树与分频逻辑STM32F407的系统时钟最高是168MHz经过AHB预分频、APB预分频之后分给不同外设。一个特别容易踩坑的点在这里APB1和APB2定时器时钟并不等于APB总线时钟。我当时调这个项目也被坑过网上很多教程也只说一半导致参数怎么填都对不上示波器测到的频率。具体来说F407里AHB默认168MHzAPB1预分频为4得到42MHzAPB2预分频为2得到84MHz。但是当APB预分频系数不等于1的时候定时器时钟会自动翻倍。也就是说挂在APB1上的TIM2、TIM3、TIM4、TIM5等定时器时钟 42MHz × 2 84MHz挂在APB2上的TIM1、TIM8等定时器时钟 84MHz × 2 168MHz这个翻倍机制很多人不知道结果算出来的溢出频率恰好是实际值的一半配出来的采样率完全不对。2.2 ADC采样时间、转换周期与总耗时再来看ADC侧。F407的ADC时钟来自APB2而ADC模块最高只能跑到36MHz所以必须分频。CubeMX里通常把ADC时钟预分频设为4即84MHz / 4 21MHz。这个值怎么选其实跟你要用的采样时间有关后面会细说。ADC一次规则组转换总耗时 采样阶段周期数 转换阶段固定周期数。F407是12位ADC转换阶段固定为12个ADC时钟周期采样阶段可选3、15、28、56、84、112、144、480个周期。很多人只看采样频率忽略了这个耗时结果设定了1MHz的定时器触发实际ADC转换还没结束下一次触发就来了数据全是乱的。打个比方ADC采样保持电路像拿着一个杯子去接水采样时间就是接水的时长。如果你接水的管子很细信号源内阻大时间太短就接不满采出来的电压就偏低、波动大反之时间太长又会拖慢整体的转换频率。工程上当ADC时钟21MHz采样时间配置为15周期时一次转换总耗时 (15 12) / 21MHz ≈ 1.286微秒这意味着你无脑触发ADC的极限频率大概在777kHz左右。实际使用中我会留出至少20%的余量或者直接把ADC时钟降到14MHz左右用更长的采样时间去换稳定度尤其当信号源阻抗比较高的时候。我自己总结的一个经验公式你配置之前先在草稿纸上过一遍心里就有数了定时器触发频率 定时器时钟 / (PSC 1) / (ARR 1) ADC单次转换时间 (采样周期 12) / ADC时钟频率只要保证“定时器触发频率 × 通道数 ≤ (1 / ADC单次转换时间)”的80%左右基本就是安全的。2.3 DMA搬运机制与循环缓冲区的关键意义DMA在这条链路里担任的角色就是一个完全可靠、不知疲倦的搬运工。它不关心数据内容只管在ADC转换完成寄存器可读时把数据从外设数据寄存器搬到内存缓冲区搬完一次就更新一次内存地址同时计数器减一。这里有几个关键配置你需要在心里很清楚第一方向必须是从外设到内存。F407的DMA方向配置是DMA_PERIPH_TO_MEMORY外设地址固定为ADC的数据寄存器内存地址则指向你的缓冲区数组。第二数据宽度一定要匹配。F407的ADC是12位分辨率结果保存在16位寄存器里所以外设和内存的数据宽度都选Half Word半字也就是16位。如果你配成Byte宽度DMA会拆成两个8位传输数据组合起来就完全错了配成Word宽度内存地址按4字节跳缓冲区里全是空洞调试时一脸懵。第三直接用循环模式Circular不要用普通模式。普通模式搬完N个数据就停了你得在传输完成中断里重启DMA不仅多一条语句还可能出现重启间隙丢数据的问题。循环模式则像流水线一样搬完一轮自动回到缓冲区开头继续搬ADC和DMA之间永远不断流。只要你不想停这套机制就可以一直跑下去CPU完全不用管。第四ADC配置里的DMAContinuousRequests必须使能。这跟你DMA那边设不设循环是两码事。ADC本身的DMA请求要设置为连续请求模式意思是只要ADC转换完成就持续触发DMA请求配合DMA的循环模式才能真正做到无缝。如果你忘了开这个选项ADC只会在第一组转换时发起一次DMA请求之后数据就再也不动了现象特别像“DMA没工作”其实是ADC侧没放行。3.2 定时器配置确定采样周期的入口我这里用TIM4做触发源。选择TIM4而不是TIM1、TIM8纯粹是因为F407的ADC1触发源列表里TIM4_TRGO是设置起来最直接的一个CubeMX下拉框也直观。你在自己的项目里可以用别的定时器但原理完全一样。CubeMX中进入Timer → TIM4Clock Source选Internal ClockPrescaler填83对应84MHz分频到1MHzCounter Period填999重载值计数到1000个脉冲溢出一次auto-reload preload建议Enable重点在Trigger Output (TRGO)下拉里选择Update Event这样定时器溢出频率 84MHz / 84 / 1000 1kHz也就是每1毫秒产生一次更新事件并输出一个TRGO脉冲给ADC。如果你要改采样频率直接按公式逆推不用重新乱填。这里有个细节我一开始没注意TIM4的TRGO由主模式配置决定CubeMX生成代码时会在MX_TIM4_Init里调用HAL_TIMEx_MasterConfigSynchronization把MasterOutputTrigger设为TIM_TRGO_UPDATE。如果这段代码被删了或者没生成定时器跑得再欢ADC也不会有任何反应。检查代码时优先看有没有这个函数调用。3.3 ADC配置外部触发、扫描模式与DMA请求进入Analog → ADC1Channel列表里勾上需要采样的通道比如IN0对应PA0。如果同时勾了IN0、IN1多个通道就要设置Rank顺序也就是扫描顺序。ADC参数设置里Resolution选12位ScanConvMode看情况单通道选Disable多通道选Enable并设置NbrOfConversion。ContinuousConvMode必须选Disable。这一步是整条链路的核心逻辑如果是连续转换模式ADC启动后就自己一轮一轮转下去定时器触发就失去意义了。我们要的是“收到一个外部触发转一次或扫描一组”所以连续转换必须关掉。ExternalTrigConvEdge选Rising Edge。ExternalTrigConv选Timer 4 Trigger Out event这就是把ADC1的触发源绑定到TIM4的TRGO上。DMAContinuousRequests选Enable这是前面讲过的关键必须开。SamplingTime根据你的信号源内阻和频率需求来选保证单次转换总时间远小于定时器触发周期即可。这里提一个容易忽略的点如果你的多通道扫描模式下在DMA缓冲区里看到的通道顺序跟你配置的Rank顺序完全一致。比如Rank1是通道0Rank2是通道1那么DMA缓冲区就是0、1、0、1、0、1依次排列。如果你在代码里又手动给sConfig.Channel赋值却没有按顺序配置Rank数据就会乱。3.4 DMA配置循环模式与数据宽度在DMA Settings选项卡里点击Add添加ADC1的DMA请求。弹出的DMA Request页面里方向自动识别为Peripheral To Memory模式选Circular外设地址增量禁止内存地址增量使能外设和内存数据宽度都选Half Word。优先级看情况我这组数据比较重要选High。CubeMX生成DMA代码时会自动选择一个可用的DMA Stream和Channel一般你不需要自己指定。但要注意F407的ADC1可以挂到DMA2的Stream0或者Stream4上某些情况下如果你两个DMA同时用Stream资源冲突需要人工调整。我自己有一次就撞上DMA2_Stream0被其他外设占用的尴尬CubeMX不会提醒你只在运行时报错排查了老半天才反应过来。配置完成后生成的代码结构大概是MX_GPIO_Init → MX_DMA_Init → MX_ADC1_Init → MX_TIM4_Init。你会发现CubeMX自动把DMA初始化放在ADC之前这个顺序是有讲究的ADC初始化时要通过__HAL_LINKDMA把DMA句柄挂在ADC句柄上所以DMA必须先就绪。4. 代码实现与启动流程从初始化到数据落盘的完整闭环CubeMX把毛坯房建好之后剩下就是水电装修的活了。这一节我直接贴出一份我验证过的关键代码加上必要的注释你按顺序看就能明白这套链路是怎么闭环的。4.1 初始化代码和MSP底层关联主函数main()里的初始化顺序CubeMX已经排好了你不需要动。重点是你自己要补充的全局变量/* 采样缓冲区我这里是单通道采512个点一轮 */ #define ADC_BUF_SIZE 512 uint16_t adc_raw[ADC_BUF_SIZE]; /* DMA传输完成标志 */ volatile uint8_t adc_dma_done 0;在用户代码区USER CODE BEGIN 1里加上这两个变量。注意adc_raw数组要定义成uint16_t类型对齐DMA半字传输。如果你声明成uint8_t数组再强转很容易触发字节序问题我以前试过数据高低字节是反的。4.2 启动ADC DMA和定时器的正确顺序初始化完成后主循环开始之前需要自己写一个启动序列。这个顺序我调了很久才确定最优解/* 1. 校准ADCF407用HAL库必须做这一步否则转换结果前几次会偏 */ HAL_ADCEx_Calibration_Start(hadc1); /* 2. 启动ADCDMA让DMA先做好准备等待触发 */ HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw, ADC_BUF_SIZE); /* 3. 最后启动定时器开始周期性输出TRGO触发ADC */ HAL_TIM_Base_Start(htim4);为什么校准要先做F407的ADC内部有偏移误差不校准的话前几次采样值可能整体偏差几十个LSB。为什么DMA要先启动定时器要后启动因为定时器一旦跑起来TRGO信号立刻就有了如果DMA还没就绪第一批触发的转换结果就丢了。启动顺序的本质是让下游先准备好再让上游开工。我这套配置下采样频率是1kHz缓冲区512点DMA搬完一轮大约耗时0.512秒。每搬完一轮DMA传输完成中断触发在中断回调里置一个标志位主循环检测到标志就处理数据。这样既不用每1毫秒进一次中断又不会错过任何数据。4.3 数据处理把DMA缓冲区变成可用的采样数据DMA缓冲区里的原始值是0到4095的ADC量化结果要转换成实际电压公式是电压 adc_raw[i] * (VREF / 4095)F407的参考电压一般是3.3V如果是高精度测量你需要用万用表实测VREF引脚的电压不要想当然地认为一定是3.3V。我之前有个项目板子上VREF接的3.0V基准代码里写死3.3V算出来的电压整体偏了10%查了老半天才发现是参考电压的问题。采样完成后在回调或者主循环里做一次简单的波形处理。比如我这边要算电流有效值公式就是float sum_sq 0.0f; for (int i 0; i ADC_BUF_SIZE; i) { float voltage (float)adc_raw[i] * 3.3f / 4095.0f; sum_sq voltage * voltage; } float rms sqrtf(sum_sq / ADC_BUF_SIZE);如果你做的是FFT频谱分析缓冲区长度建议选2的幂次比如512、1024直接用官方DSP库的arm_rfft_fast_f32函数性能很好。要注意FFT计算前需要对原始数据加窗汉宁窗等否则频谱泄漏会让你怀疑人生这个属于信号处理的范畴了有机会再单独写一篇。4.4 中断回调与主循环的配合DMA传输完成回调长这样void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { adc_dma_done 1; } }主循环里while (1) { if (adc_dma_done) { adc_dma_done 0; /* 处理adc_raw数组中的512个点 */ process_samples(); HAL_UART_Transmit(huart1, (uint8_t *)adc_raw, sizeof(adc_raw), 100); } }有个细节要注意DMA传输完成中断里尽量不要做耗时操作比如UART发送、字符串格式化、浮点计算这些能放到主循环就放到主循环。中断里只置标志位主循环轮询标志位处理这是嵌入式开发的黄金法则。如果ADC_BUF_SIZE比较大比如采样时间很短、缓冲点很多你等“整轮完成”再处理可能延迟太大。此时可以用DMA的半传输中断即搬完前一半数据先进一次中断处理前一半同时DMA继续往后搬搬完后一半再进一次中断处理后半段。这样数据实时性翻倍实现方式是在回调里同时做HAL_ADC_ConvHalfCpltCallback处理。F407的DMA硬核支持半传输中断HAL库也封装好了直接用。5. 踩坑实录这些坑我替你们趟过了这部分最值钱。我自己在这套方案上踩过的坑以及在论坛上帮网友解决问题的过程中遇到的典型问题统一记在这里按排查难度排序。5.1 定时器触发了但ADC纹丝不动这个现象出现在我自己第一次调的时候程序跑起来定时器确实在输出更新事件用示波器量TIM4的通道引脚能看到波形但DMA缓冲区里全是0或者初始值好像ADC完全不工作。排查步骤依次是检查HAL_TIMEx_MasterConfigSynchronization里MasterOutputTrigger是不是TIM_TRGO_UPDATE。这段代码在MX_TIM4_Init函数末尾如果你之前改过定时器配置可能被CubeMX重新生成了但一般不会丢。检查ADC1的ExternalTrigConv是不是ADC_EXTERNALTRIGCONV_T4_TRGO。我那次就是CubeMX里配好了但后来手误改成了ADC_SOFTWARE_START整个触发链路就断了。检查DMAContinuousRequests如果没开DMA只搬第一次数据就再也不动了现象会让你误以为是定时器没触发。这里我最想强调的就是初始化顺序和trigger链路的完整性。整套链路是 TIM4_TRGO → ADC1_EXT_TRIG → ADC1_DR → DMA2 → RAM任何一个环节断了现象都可能一样缓冲区不更新。所以排查时从链路头部到尾部逐个检查比漫无目的地试配置快得多。5.2 多通道数据错位波形像是被“切歪”了这个现象容易出现在你从单通道改成多通道的时候。比如我调三相电流采样配置了三个通道IN0、IN1、IN2DMA缓冲区里应该依次是0通道、1通道、2通道、0通道、1通道、2通道……但实际上我看到的数据是1通道、2通道、0通道这样错位的排列。原因出在CubeMX里Rank顺序跟你实际期望的顺序不一致。ADC规则组的扫描顺序是由Rank决定的不是由通道编号决定的。你必须在CubeMX的ADC1配置里依次添加Channel 0Rank1、Channel 1Rank2、Channel 2Rank3。如果顺序不对DMA缓冲区里的数据当然也不对。还有一个底层问题扫描模式下ADC在一条通道转换完成后中断标志置位然后自动启动下一条通道的转换转换结束时DMA请求也跟着来了。如果你在某些外设同时使用DMA的其他Stream上配置了优先级混淆也可能出现偶尔丢一组数据的情况。解决办法是DMA优先级设高或者在回调里检查逻辑错误。5.3 采样数据整体漂移、噪声大这个问题不太算“通不通”层面的问题而是“稳不稳”层面的问题。我一开始用28周期的采样时间输入信号是从一个高阻传感器出来的数据在示波器上看还行但算出来的电流有效值跟万用表实测值差了几个百分点而且毛刺明显。排查后发现是采样时间太短源阻抗没匹配。传感器输出阻抗有几十千欧采样保持电容在28个ADC时钟周期里根本充不满导致每次采到的电压都比实际值偏低、且带误差。解决办法是把采样时间从28周期改成144周期甚至480周期数据立刻就稳了。代价是转换速度降低但对我这个8kHz采样需求来说绰绰有余。如果改成480周期还是飘那就要看电源了。ADC的VDDA和VREF引脚如果有纹波转换结果必然波动。硬件上在VDDA和VREF附近做好滤波比如磁珠加电容软件上多做几次平均或者滑动滤波。这里提一个PCB布局经验ADC模拟输入走线要远离开关电源、电机驱动这些噪声源尽量短而粗必要时用地线包围输入走线减少耦合。5.4 启动顺序不对第一批数据总是坏的你可能也有这种感觉系统刚上电前几秒数据不正常后面就正常了。这里面两个原因第一ADC没校准就启动转换前几次数据偏移第二定时器先启动、ADC DMA后启动导致最初的几次触发没有对应的DMA搬运数据线路还没建立稳定。解决办法就是第4.2节里讲的启动顺序HAL_ADCEx_Calibration_Start(hadc1); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw, ADC_BUF_SIZE); HAL_TIM_Base_Start(htim4);前两条的顺序不能换后两条的顺序建议按上面的来。如果你需要更稳定的启动过程还可以在启动后先丢弃前几十个采样点再开始累计数据等缓冲区的数据流稳定了再计算。这有点像汽车起步之前要空转两下虽然没啥技术含量但很管用。5.5 高速采样时DMA中断卡死主循环有人说“DMA不占CPU”这话在数据搬运过程中是对的但别忘了我前面配了DMA中断。如果你在DMA中断回调里做了很大的工作量比如发送很长的串口数据、操作液晶屏中断时间过长整个系统的实时性就崩了。尤其是用HAL_UART_Transmit这种阻塞式发送在回调里调用发送一串几百字节的数据可能耗时几千个时钟周期更高优先级的定时器中断如果等太久系统就乱了。稳妥的做法是中断回调里只置标志位具体的处理全部丢给主循环或者RTOS任务。哪怕只是把数据memcpy到另一个缓冲区也要判断一下DMA是否还在工作。我见过有人直接在中断里memcpy 16KB的数据导致系统卡顿到按键都失灵这就是没有规划好中断优先级和中断耗时。5.6 一个容易被忽略的问题缓冲区大小与半字对齐DMA缓冲区数组定义成uint16_t这本身不会有问题。但如果你定义一个uint8_t数组然后在HAL_ADC_Start_DMA里强制转换成(uint32_t *)指针同时又配置了半字数据宽度DMA会按半字写入内存但编译器认为数组元素是uint8_t你在主循环里按什么类型去解析都会乱套。这种隐形错误查半天都查不到。最直接的办法就是缓冲区定义成uint16_t adc_raw[ADC_BUF_SIZE];然后HAL_ADC_Start_DMA的第二个参数强转成(uint32_t *)就行函数内部是按你配置的数据宽度访问缓冲区的。你也可以干脆定义成uint32_t数组然后按半字读取低16位但我觉得没必要多费内存还容易把自己搞晕。6. 延伸思考这套架构还能怎么扩展如果你已经把手上的定时器ADCDMA彻底玩明白了这几个方向可以再往下挖一挖都有很实际的应用。6.1 多通道同步采样的数据组织我现在这个项目里实际用的是三通道电流采样加一路母线电压一共4通道DMA缓冲区分组为4个采样点一组每组代表一个扫描周期。处理数据时按组遍历每组前3个点对应三相电流第4个点对应母线电压。这里的关键是你必须知道每一组在缓冲区里的偏移规律代码里用一个循环步进4就可以。如果你发现某一路数据在相邻两组之间共用了同一个ADC采样结果那说明扫描模式下DMA没有正确获取到“每一次新转换”的数据检查一下DMAContinuousRequests是不是确实开着以及EOCSelection是否设成了EOC_SEQUENCE_CONV而不是EOC_SINGLE_CONV。6.2 PWM边沿触发采样在电机控制中的应用有些FOC应用里PWM的中心对齐模式会产生一个很固定的采样窗口在PWM计数器的特定值上触发采样比如在PWM下溢时采样电流。这时候定时器触发就不一定非要“更新事件”而是用定时器的比较通道输出TRGO也就是PWM模式下的OCxREF。这样配置出来的采样时机可以与PWM周期严格同步比单纯用溢出触发更适合电机控制。STM32F407的ADC外部触发源列表里有TIM1的CC1事件、CC2事件、CC3事件等你可以在电机控制的博客里看到很多这种用法。思路跟本文完全一样只是把TRGO来源从Update Event改成某个CC事件CubeMX里下拉选项一换就行。6.3 低功耗场景下这套架构的取舍这套架构里定时器一直在计数DMA一直处于待命状态ADC随时被触发所以功耗不低。如果做电池供电的采集设备需要进入睡眠模式那就要停止定时器和DMA然后睡眠前关掉ADC唤醒后再重走一遍启动流程。HAL库也提供了HAL_TIM_Base_Stop、HAL_ADC_Stop_DMA这些函数配合WFI指令可以实现“采完一轮就睡睡醒了再采一轮”的间歇工作模式。这个方向如果能跑通你的采集系统在功耗上又能上一个台阶。最后的实操心得调这套定时器触发ADCDMA方案我最大的体会是这类硬件链路型的设计最大的敌人不是代码复杂而是“中间环节太多出了问题不知道看哪里”。所以我会建议你第一次上手时先在缓冲区里抓一份原始数据用printf或者串口打印出前面几十个点肉眼看一下是否在正常范围内再逐段确认定时器、ADC、DMA各自是否在干活。确认完链路顺畅后再去谈滤波、校准、算法这些上层的事情。另外调试时示波器一定要用上量一下定时器的TRGO引脚、ADC的采样保持时间点很多时候硬件层面的问题比软件层更快定位。如果你也准备在自己的项目里用这套组合就把上面这些参数计算和启动顺序抄下来配合CubeMX里的配置一步步核对大概率一遍就能跑通。
返回列表