拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32定时器触发ADC精准采样与串口上机全链路解析

简介面向STM32嵌入式开发者这份资源提供基于STM32F103定时器触发ADC采样、并将采样数据通过串口发送至上位机的完整工程示例可帮助理解定时器、ADC与UART三大外设的协同配置。压缩包共189个文件主要包含C/H源码、Keil工程文件uvprojx、编译生成的o/d/hex/axf文件、链接脚本、烧录文件及说明文档整体大小约6.62MB目录结构清晰。已有4882人学习下载。项目基于标准外设库核心代码涵盖TIM2定时中断周期触发、ADC通道2采样及串口异步发送并给出预分频值、自动重载值、采样时间、波特率等关键参数配置同时提供基础工程框架适合入门或进阶开发者直接编译学习、二次开发或作为课堂实验与毕业设计的参考模板。从定时器初始化到串口数据发送每个环节都有对应源码和配置细节便于逐步调试。 在做数据采集类的STM32项目时我见过太多人一开始就用HAL_ADC_Start(hadc1)配合HAL_Delay()在while(1)里循环采样。说实话这种做法在信号变化慢、对时间不敏感的场合能跑但一旦你开始做电赛信号题、仪器仪表、电机反馈这类对采样时序有要求的项目数据出来的波形就全部露馅了点与点之间的间隔忽长忽短画出来的正弦波和狗啃的一样。这篇文章就围绕一个非常经典的组合展开定时器触发ADC采集数据发送至上位机。我会从定时器为什么能触发ADC、怎么配置、ADC侧怎么配合一直到串口数据帧设计和上位机解析把整条链路的关键细节全部过一遍。适合正在做STM32数据采集、上微机通信、或者被采样时序问题折磨的朋友参考。1. 为什么坚持用定时器触发而不是轮询或者定时器中断里启动ADC先说结论定时器触发ADC的本质是让两个外设直接硬件协作不经过CPU也不经过任何软件判断。把几种常见的做法摆在一起对比你就明白了方案触发方式时间抖动来源CPU占用适用场景主循环轮询 延时软件查询循环体执行时间、中断抢占高低速慢变信号实验验证定时器中断里启动ADC软件启动中断响应延迟、中断嵌套中数据率不高对时序不苛刻定时器硬件触发ADC硬件直达几乎为零极低精确采样、固定频率采集、音频/振动分析很多人觉得“定时器中断里启动ADC”就是定时器触发ADC把这两个概念混为一谈。定时器中断方案的表现是定时器溢出后产生中断CPU进入中断服务函数执行HAL_ADC_Start_IT()然后ADC才开始转换。这中间至少有几微妙的中断响应时间而且这个时间不是固定的——如果此时恰好在处理别的中断比如串口中断、外部中断ADC启动就会被推迟。硬件触发就不是这个路子了定时器溢出事件直接通过内部的TRGO信号送到ADC的触发输入端ADC检测到这个边沿后自动开始转换。整个过程CPU不参与不受中断响应时间影响。芯片手册上的术语叫“由硬件控制最快响应”这也是示波器、数据记录仪这类设备坚持用硬件定时的原因。另外还有一点容易被忽略当你用定时器中断启动ADC等于每个采样周期都把CPU从正常流程里打断一次。假设你采样率是30kHz意味着每33微秒就要打断一次CPU忙于进出中断留给数据处理、界面刷新、其他控制逻辑的时间就被压缩了。硬件触发模式下CPU只需要在ADC转换完成后做一次读取和发送效率完全不一样。所以如果你将来要在STM32上做多通道数据同步采集、或者用ADC采样结果做实时FFT分析硬件定时器触发基本是绕不开的基础架构。2. 定时器侧配置ARR和PSC不是随手填的先算清楚采样率定时器触发的关键配置集中在两点一是把定时器的更新事件设置为触发输出二是把ARR和PSC算到目标采样率。以STM32F103系列为例假设主频为72MHz使用通用定时器TIM3它挂在APB1总线上。这里有个经典坑APB1的外设时钟是36MHz但定时器时钟是72MHz。原因是APB1预分频设置为2时定时器时钟会乘以2倍所以定时器实际工作时钟是72MHz而不是36MHz。如果你直接套36MHz去计算采样率直接差一倍后面所有数据都对不上。定时器溢出频率的计算公式是f_trigger f_tim / ((PSC 1) * (ARR 1))PSC和ARR都是16位寄存器值从0开始所以公式里要加1。假设我这次想采集一个10kHz左右的音频信号按奈奎斯特采样定律采样率至少要20kHz我通常留出余量目标定在30kHz。现在反推f_tim / ((PSC1) * (ARR1)) 30000 Hz 72MHz / ((PSC1) * (ARR1)) 30000 (PSC1) * (ARR1) 24002400的拆法很多我习惯让PSC尽量小、ARR尽量大这样ARR具有较高的分辨率。这里取PSC1那么ARR11200即ARR1199。如果你取PSC2则ARR799也可以。两者实际效果没有本质区别唯一的差别是定时器计数粒度不同ARR越大计数器每次递增的数值越小相位分辨率越高。在外部触发ADC这个场景下两者差别不大但养成PSC优先小的习惯未来做PWM或精确延时时会有帮助。在STM32CubeMX中配置步骤是这样在Timers里启用TIM3配置为Internal Clock设置Prescaler为1Counter Period为1199在Trigger Output (TRGO) Parameters里把Trigger Event Selection设为Update Event保存并生成代码。生成代码后在main函数里只需要启动定时器HAL_TIM_Base_Start(htim3);从此时刻起TIM3会以30kHz的频率持续产生更新事件并通过TRGO信号送给ADC。补充一句不同芯片的触发源映射不一样。F103里TIM3的TRGO可以触发ADC1和ADC2F4系列可能就需要查一下参考手册里触发源表格。CubeMX的好处是ADC触发源下拉框里只会列出当前芯片实际支持的选项你只需要知道选对应的Timer那一项就行。3. ADC侧配置触发源选对、转换模式别选错定时器配置完了接着就是ADC。在CubeMX中ADC1打开后关键是找到External Trigger Conversion Source这个下拉框把它从默认的Disabled改成对应的定时器触发选项。以F103 TIM3为例下拉框里会出现Timer 3 Trigger Out event。这里有一个很高频的操作失误有些人一边把触发源设置为定时器一边又把Continuous Conversion Mode设为Enabled。结果程序下载后ADC不是在等待定时器触发而是一轮接一轮自己疯狂转换数据处理逻辑全部乱套。原因是这样的单次转换模式Single Conversion下ADC每收到一次触发就转一次停在原地等下一次触发而连续转换模式下ADC转完一轮紧接着自动开始下一轮外部触发仅用于启动第一轮。所以在定时器触发场景下正确配置是Continuous Conversion Mode Disabled加上Trigger方式选Rising Edge。接下来是采样时间的设置。ADC转换总时间 采样阶段时间 转换阶段时间。F103的转换阶段固定为12.5个ADC时钟周期采样阶段可选1.5到239.5个周期。ADC时钟通常在CubeMX里配置为12MHzPCLK2经过分频。以我常用的配置为例采样时间设为55.5周期那么采样时间 55.5 / 12MHz ≈ 4.625us 转换时间 12.5 / 12MHz ≈ 1.042us 总转换时间 ≈ 5.67us而我刚才设定的采样间隔是33.3us转换占用的时间不到五分之一完全足够。如果你的采样率更高比如200kHz采样采样间隔只有5us那就必须把采样时间调低比如1.5周期否则ADC还没转完上一次下一次触发就来了数据会直接覆盖或丢失。DMA要不要开取决于你的通道数和数据量。单通道、30kHz采样率每秒钟产生3万个16位数据这个量级完全可以在ADC转换完成中断里读取。CubeMX里给ADC使能中断然后在中断回调里取值就行void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc-Instance ADC1) { uint16_t adc_val HAL_ADC_GetValue(hadc1); // 处理或发送数据 } }但如果你做的是多通道扫描采集比如8个通道同时采集转换完成中断读取8个数据的操作会频繁打断主流程这时候建议开启DMA让ADC转换结果自动搬运到缓冲区。定时器每触发一次ADC就完成一轮通道扫描DMA把整组结果搬进数组CPU最终只需要处理整块数据。4. 串口上行链路字节序、数据帧、速率匹配一个都不能少ADC这边采到数据了怎么送到上位机也是个容易翻车的地方。很多人第一次调试时发现上位机收到的波形数值对但是偶尔冒出一个大得离谱的尖峰或者本来是平滑的正弦波显示的曲线像被撕碎了一样。这种问题十有八九出在字节序上。STM32是32位小端处理器一个16位ADC值存成两个字节时低字节在前高字节在后。比如ADC读到0x04D2那么发送顺序是0xD2 0x04。如果你在上位机按“先高后低”拼接就会得到0xD204数值瞬间从1234变成53764正好对应一个极其异常的尖峰。一个简单可靠的数据帧格式可以参考这样帧头1帧头2通道号数据高字节数据低字节校验0xAA0x550x010x040xD2校验和上位机先找帧头0xAA 0x55确认同步后再固定取后面4个字节解析。这样即使发生字节错位重新同步的成本也很低下次找帧头就能恢复不会影响后续数据。字符串格式比如printf(adc%d\r\n, val)适合低速调试一帧要十几个ASCII字节数据率高了以后串口根本扛不住还是二进制帧实在。速率匹配同样要提前算。30kHz采样率每个点2字节每秒就是60KB。如果用的是串口通信波特率代表的是bit/s而不是byte/s一个字节在8N1格式下要占10个bit那么60KB/s的数据流需要的波特率至少是600000也就是说57600波特率差了10倍以上115200也不够。我一般会选460800甚至921600。如果你的USB转串口模块是CH340G460800通常没问题更高的话最好挑CP2102或者FT232这类模块稳定性更好。发送方式上切忌在ADC中断回调里用阻塞式HAL_UART_Transmit()。以30kHz采样率算每33us就要发2个字节阻塞发送期间CPU无暇它不是问题问题是如果串口忙发送会把整个采样节奏卡死。正确做法是做好缓冲或者在低采样率场景下用DMA发送。特别是当你把采样率提高到100kHz以上这已经不是建议而是必须了。5. 联调阶段的高效排查法用已知信号验证全链路链路全部连起来之后不要急着用真实传感器信号验证那是给自己找麻烦。我先接一个信号发生器或者单片机PWM产生一个幅度、频率都已知的信号送进ADC引脚然后用上位机观察波形。比如用TIM2产生一个1kHz的方波或正弦波幅度在0~3.3V之间送入PA0。如果这端采样率是30kHz那么一个周期应该采集到30个点。在上位机波形显示里数一下一个周期有多少个点基本就能判断整条链路对不对。如果点数明显不对优先检查定时器时钟频率是不是被当成36MHz算了这是最常出问题的地方。排查丢帧时我习惯用一个简单方法在上位机记录收到数据的速率和理论速率做对比。如果理论是30000帧/秒实际只有28500说明有5%的帧丢了。原因通常是串口缓冲区溢出、上位机处理不及时或者DMA配置不合理可以先把采样率降到1kHz确认基础链路没问题后再逐渐调高找出临界点。还有一个很容易被忽视的问题如果ADC输入阻抗比较高信号源驱动能力不足采样电容在极短的采样时间内充不满采到的数值就会整体偏小而且采样率越高越明显。处理办法是在ADC引脚前加一个运放跟随器或者至少保证信号源输出阻抗足够低。波形出现规则的周期性毛刺时不要急着怀疑ADC和串口先看看板子上有没有其他开关电源、PWM信号在靠近模拟输入引脚的地方跑噪声耦合进ADC通道才是常见元凶。用短而粗的杜邦线、在引脚附近加一个0.1uF电容到地往往就能解决。最后分享一个我自己的检查习惯每当遇到采集数据对不上号的时候我会按这个顺序排查先拿示波器看ADC外部触发引脚上有没有定频脉冲确认脉冲频率和设计值一致再看ADC数据是否单调、周期性是否匹配输入信号最后才查串口帧和数据拼接。这个顺序能帮你快速把问题定位在传感器、采样电路、传输链路中的哪一环避免在某个环节反复折腾却始终找不到根因。这个架构本身是很成熟的定时器触发与ADC的结合也几乎是所有STM32数据采集项目的标配。等你熟练了这套流程后面再加DMA多通道、SD卡存储或者无线传输都只是在现有链路上扩展的事核心的时间同步和数据处理逻辑不需要推倒重来。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门