news 2026/9/9 17:19:59

STM32定时器TRGO触发ADC+DMA精准采样实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32定时器TRGO触发ADC+DMA精准采样实战指南

简介:这套STM32F1平台的定时器触发ADC采集并自动DMA搬运方案,面向有一定嵌入式基础、希望提升数据采集实时性的开发者,解决定时采样、外设联动与CPU负载之间的矛盾。压缩包共6个文件,以3个C源文件和3个头文件组成,分别完成ADC、TIMER3与DMA的初始化配置、中断服务函数及寄存器定义,整体仅3KB,结构精炼,便于直接移植到实际工程。资源已有4087人学习下载,覆盖从TRGO触发机制、ADC通道与采样时间配置,到DMA循环传输、半传输/全传输中断缓存管理,以及系统集成调试的完整思路。通过学习可掌握外设时钟匹配、缓存一致性处理和高效中断调度等关键技能,适合需要实现高采样率、低CPU占用的采集系统开发场景。 做电机控制、数字电源这类项目,一个经常让我头疼的问题就是:ADC采样要非常准时,但CPU又不能一直在中断里耗着。用TIMER3的TRGO作为触发信号去启动ADC转换,再用DMA把结果自动装进缓存,这套组合我最近在STM32项目里落地后,采样稳定性、CPU占用都比我之前用软件触发好很多。这篇文章就把原理、配置步骤、调试时踩的坑一次性讲清楚,适合正在用STM32做数据采集、电流环、电源控制的开发者。内容不啰嗦,直接上干货。

1. 为什么用TIMER3的TRGO触发ADC + DMA

1.1 从一次真实需求说起:PWM控制场景下的同步采样

之前做一款低压电机驱动器,电流环的采样点必须和PWM载波对齐。一开始用的老办法:PWM周期中断里读ADC,再把值算成电流反馈。表面看没什么问题,但实际测试时发现,一旦中断里处理逻辑变多,采样点就会往后漂。电机高速运转时,电流波形变化很快,采样点偏几个微秒,算出来的电流环增益就不对,甚至会诱发振荡。

后来改成TIMER3产生触发信号,通过TRGO引脚映射到ADC外部触发输入。这样ADC启动转换的时刻完全由硬件定时器决定,不需要CPU参与。转换完成后,DMA自动把结果搬进内存缓存。整个采样链路里,CPU只需要在数据攒够后做一次批量处理。采样点精确、转换频率高、CPU占用低,这几个需求一次全满足了。

1.2 TRGO触发采样的原理:事件流如何打通外设

TRGO是定时器的“主模式输出”信号,作用是把定时器内部产生的事件输出给其他外设。比如更新事件、比较事件、OCxREF信号等,通过配置MMS位段,选择其中一种事件从TRGO送出去。TIM3的TRGO在芯片内部直接连到ADC的外部触发输入,不需要占用引脚,也不用飞线,这是一条芯片内部已经接好的“专线”。

配置TRGO,本质上就是在选择“用定时器的什么事件去触发ADC”。最常用的选择是更新事件,也就是计数器溢出瞬间产生一个触发脉冲。如果你用PWM中心对齐模式,还可以选择OCxREF信号,在三角波计数达到顶点时触发采样,这种方式在电机控制里叫“定时器对称采样”,能有效减小电流纹波对采样的影响。对普通数据采集来说,更新事件已经够用,一个定时器周期对应一次ADC转换,两者完全同步。

1.3 相比定时器中断加软件采样的优势

定时器中断+软件采样的问题在于,从事件发生到中断函数执行,中间隔着中断响应、压栈、现场保存等一堆开销。如果中断里再做一些判断,采样点会进一步抖动。虽然单次抖动只有几个时钟周期,但高频采样场景下,这几个时钟周期足以把信号相位信息污染掉。

TRGO触发ADC是纯硬件路径。定时器计数到目标值,TRGO信号直接送给ADC,ADC收到触发立即开始采样,转换结束又自动请求DMA传输。整条链路没有CPU参与,延迟是固定的,不会因为软件分支、中断优先级、编译器优化而改变。这才是硬件定时采样最核心的价值:可重复、可预测、不占CPU。

2. 一套可以直接抄的配置步骤

2.1 硬件连接与选型建议

先说硬件连接。这块方案我没有额外用外部触发器,就靠芯片内部的TIM3 TRGO→ADC外部触发输入路径。ADC选的是ADC1,DMA用的是DMA1,这是STM32F103这类芯片比较经典的组合。芯片型号不同,DMA映射可能不一样,配置前先查一遍参考手册里的“ADC和DMA请求映射表”,别想当然。

采样引脚方面,我用的是PA0,配置成模拟输入。如果需要多通道采集,可以在ADC扫描模式下按顺序配置多个通道,DMA缓冲区里的数据顺序会和通道扫描顺序一一对应。采样信号的内阻不要太大,否则会影响采样电容充电,导致采集值偏低。一般传感器输出通过运放缓冲后再进ADC引脚,会比较稳。

2.2 TIMER3的TRGO配置:从定时器到ADC的关键桥梁

我用STM32标准库和HAL库都调过这套方案,核心配置点是完全一样的。以HAL库为例,TIM3初始化为72MHz时钟,预分频器72,自动重装值1000,这样更新事件频率就是1kHz,也就是采样率1kHz。如果你的系统时钟不是72MHz,先算一下预分频值,保证得到想要的更新频率。

初始化定时器后,最关键的一步是配置主模式输出,也就是让TIM3把更新事件从TRGO引出去:

TIM_MasterConfigTypeDef sMasterConfig = {0}; sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); HAL_TIM_Base_Start(&htim3);

这里务必确认TIM_TRGO_UPDATE是你要的触发源。有些应用需要在PWM中间点采样,那就要换成TIM_TRGO_OC1REF,对应的定时器比较输出也要提前配好。刚开始用这套方案时,可以直接用更新事件跑通,后续再根据控制需求调整触发点。

2.3 ADC的触发源设置与扫描模式配置

ADC部分,第一步要把触发方式从软件触发改成外部触发,并选中TIM3的TRGO。HAL库里,这段配置长这样:

ADC_ChannelConfTypeDef sConfig = {0}; hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = DISABLE; // 单通道先跑通 hadc1.Init.ContinuousConvMode = DISABLE; // 不要连续转换,让定时器来拨动 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO; // 选择TIM3 TRGO hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; // 上升沿触发 hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐 hadc1.Init.NbrOfConversion = 1; HAL_ADC_Init(&hadc1); sConfig.Channel = ADC_CHANNEL_0; // PA0 sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig);

注意ContinuousConvMode一定要设为DISABLE。如果这里开着连续转换,ADC会不停自转,定时器触发信号反而变成多余的,DMA缓存里的数据会乱成一片。外部触发模式下,ADC转换的节奏应该完全由TRGO控制。采样时间的选择要看信号源阻抗,信号源内阻大就延长采样时间,否则ADC内部采样电容没有充足时间充电,精度会变差。

2.4 DMA通道配置与缓存设计

DMA的作用是把ADC转换结果从数据寄存器搬到内存数组,整个过程不占用CPU。我推荐使用循环模式,这样DMA搬运到数组末尾后会自动回到开头,实现连续采集,配合中断可以做双缓冲效果。

hdma.Instance = DMA1_Channel1; hdma.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma.Init.PeriphInc = DMA_PINC_DISABLE; // 外设地址固定是ADC_DR hdma.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增 hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma.Init.Mode = DMA_CIRCULAR; // 循环模式 hdma.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma);

内存数组要用uint16_t类型,因为STM32 ADC是12位,数据存在低12位。半字对齐既节省内存,又方便后续平均、滤波等处理。如果缓存大小设置为偶数,比如256或512,就可以利用DMA的半传输中断,把缓存切成前后两半来乒乓处理,一边写一边读,数据吞吐效率非常高。

启动顺序有个小讲究:先调用HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, ADC_BUFFER_SIZE);,再启动TIM3。我第一次调试时就是先启动了定时器,后启动DMA,结果前几个触发脉冲没采到,缓存开头有一段无效数据。虽然是偶发现象,但等排查到的时候已经浪费了不少时间。

3. 实操中的核心细节:不要只看配置正确

3.1 初始化顺序和时钟树检查

这方案里涉及TIM3、ADC1、DMA1三个外设,任何一个的时钟没开,整条链路都会静默失败。我在检查时习惯先看RCC寄存器,特别是ADC1的时钟是否使能。有些同学配置ADC时只打开了GPIO时钟,忘了开ADC时钟,导致寄存器读出来全是0,又找不到原因。调试串口打印、LED点亮这些现象可以辅助判断外设是否正常工作。

另一个细节是ADC的时钟不要超限。STM32F103的ADC最高工作在14MHz,APB2时钟一般是72MHz,所以ADC预分频器至少要2分频。如果ADC时钟太高,采样结果会不稳定;太低又会拖慢转换速度。我用的是ADC_CLOCK_SYNC_PCLK_DIV6,即72MHz/6=12MHz,留出余量,稳定性和速度都兼顾到了。

3.2 缓存数组与半传输中断的配合方式

如果每1ms触发一次ADC转换,DMA缓存设成512个样本,大约0.5秒填满。等DMA传输完成中断触发后再处理,延迟有点大。更好的方式是开启DMA半传输完成中断,每填满半个缓存就去处理一次数据。这时另外半块缓存还在正常工作,互不干扰,本质上就是一个硬件实现的乒乓缓冲。

我在中断回调里只做一件事:把缓存统计标志位置1,具体计算放到主循环里去做。回调里千万不要做浮点运算、大量数据拷贝,哪怕只是求平均值,都可能让中断处理时间翻倍。采样率一旦提上来,DMA中断频繁触发,回调里代码稍微多点,整个系统实时性就崩了。

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { dma_half_done = 1; } } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { if (hadc->Instance == ADC1) { dma_full_done = 1; } }

主循环里看到dma_half_donedma_full_done被置位,就处理对应那一半缓存。处理完清标志,等下一次中断。这样即使采样率到几十k,主循环也能从容处理。

3.3 一个容易踩的坑:DMA连续请求与单次转换的关系

我刚开始用寄存器配置时,遇到过一种现象:采样数组里每隔一段会出现一个巨大的跳变值,像脉冲噪声一样。排查了很久,发现是ADC的DMA请求配置不对。F1系列芯片有一个DMA位,控制转换结束后是否持续发出DMA请求。配置在非连续转换模式时,每次外部触发完成一次转换,ADC会自动产生一次DMA请求,这个功能必须打开,否则DMA只在第一次转换时工作,后面数据全部静止不动。

在不同型号的STM32上,这个位的具体名称可能不同,比如有的叫DDS,有的就在ADC_CR2里。标准库有封装函数,HAL库一般在HAL_ADC_Start_DMA内部帮你处理了。如果你是从寄存器写起,一定要对照参考手册确认这几位配置,否则就是“配置看起来全对,数据就是不动”的经典问题。

4. 常见问题与排查技巧实录

4.1 ADC值全是0或固定值

这是最常见的问题。先确认PA0确实配成了模拟输入,再看ADC是否被外部触发启动。很多人忘了HAL_ADC_Start_DMA,只是初始化完就等数据,那当然永远是0。其次确认TIM3确实在跑,用调试器读一下TIM3计数器有没有在跳,或者在更新中断里翻转一个IO。如果定时器没启动,TRGO永远不会产生脉冲。

如果这些都没问题,再看ADC时钟和采样时间。ADC输入引脚悬空时,读到的值本来就是随机抖动的;如果外部接了一个高阻抗传感器,采样时间太短,采集结果会稳定在某个固定值附近,这是采样电容充电不足的表现,加大采样时间周期数就行。

4.2 采集到的数据是“错位”的

多通道扫描模式下,DMA缓存里的数据顺序必须和ADC配置通道的顺序一致。比如配置通道顺序是CH0、CH1、CH2,那么缓存前三个数据依次对应PA0、PA1、PA2,而不是你自以为的顺序。调试时不要直接用printf打印所有通道数据,先只接PA0一个信号,其余通道接地,看缓存里哪几个位置有变化,就能定位通道顺序。

另外,ADC转换结果寄存器是32位的,但有效数据只有低16位。如果你把DMA外设数据宽度配成字(32位),而内存数组是uint16_t,会导致数据错位和缓存越界。保持外设和内存宽度一致,都配成半字,问题会自动消失。

4.3 DMA中断进不去或程序卡死

先检查NVIC是否使能了DMA通道中断。HAL库中,HAL_ADC_Start_DMA实际上会自己处理中断使能,但前提是你在HAL_DMA_Init后调用了HAL_NVIC_SetPriorityHAL_NVIC_EnableIRQ,否则DMA传输完成后中断标志置起来了,但CPU根本不知道。另一种情况是中断回调函数执行时间太长,导致下一次DMA中断无法及时响应,表现为系统卡顿或采样丢数。

解决方法是设置合理的抢占优先级。电机控制类项目里,DMA中断优先级不要高于故障保护中断,否则万一电机过流,DMA正忙,故障中断被卡住,那是很危险的事情。我一般在CubeMX里把DMA中断抢占比PWM中断低一级,比普通外设中断高一级。

4.4 标准库和HAL库配置跑出来的效果不一样?

如果你会看寄存器,你会发现两边最终写入ADC_CR2、TIM_CR2、DMA_CCR的值是一样的。差异多半出在启动顺序上。HAL库的HAL_ADC_Start_DMA内部有一段时间用于清状态寄存器,如果定时器触发信号来得太快,有可能出现“下一次触发已经来了,但HAL库还在做启动准备”的情况,导致漏采。时序要求高的场景,建议直接操作寄存器启动ADC,或者调用HAL库后立刻启动定时器,中间不要插入其他初始化代码。

现象可能原因排查顺序
数组全为0未启动ADC_DMA、TIM3未运行、ADC触发源配置不对查TIM3计数器,查ADC_Start_DMA调用
数据错位多通道扫描顺序与DMA缓冲区顺序不一致单通道接地,逐通道定位数据位置
数据随机跳变ADC采样时间过短、输入信号阻抗过高增大采样时间,加一级运放缓冲
DMA中断不触发未配置NVIC、优先级过低检查NVIC使能位和抢占优先级
偶发漏采启动顺序问题、触发频率过高调整初始化顺序,必要时寄存器操作启动

5. 个人实操心得与后续扩展建议

5.1 关于触发源选择的心得

TIM3 TRGO并不是唯一选择,很多项目用TIM1、TIM2、TIM8也能做类似的事。选定时器时要看具体芯片的触发映射表,有些ADC通道只能由特定定时器触发。比如在F103上,ADC1可以由TIM2、TIM3、TIM4等触发,但如果你想同时触发ADC1和ADC2,对定时器的通道占用要求不一样。我的经验是:先把单通道、单定时器跑通,再增加多通道、多ADC同步,避免一开始就被复杂的交叉映射绕晕。

5.2 这套方案还能怎么扩展

如果你需要更高采样率,可以缩短定时器周期,但要注意ADC转换本身需要时间。12位采样、13.5周期采样时间,配合12MHz的ADC时钟,单次转换大约14个周期,约1.17微秒。采样率不是无限提的,超过ADC硬件极限后,触发信号来了但转换还没完成,数据就会丢。更快的ADC采样时间可以调到1.5周期,但采样精度会下降,信号源阻抗高时尤其明显。

如果想把这个架构用于电流环,建议把TIM3配置成PWM模式,并利用OCxREF信号触发ADC。PWM周期不是简单更新事件,而是三角波计数到峰值或谷值时刻触发采样,刚好避开开关管开关动作引起的噪声,电流采样质量会好很多。DMA半传输中断那块也可以继续扩展,比如在回调里直接查表做线性滤波,甚至把数据快速转成浮点电压值,供闭环控制调用。总体思路是:尽量把重复性工作从CPU中剥离,让定时器、ADC、DMA这三者组成一条自动流水线。

我实际用下来,最值得记住的一点是:这套方案所有环节都必须在时序上“咬合”在一起。第一次调试失败,多半不是代码语法问题,而是某个触发边沿、某个使能位、某次初始化顺序没对齐。把寄存器手册里关于触发源和DMA的说明反复读两遍,比看100篇教程都管用。希望这篇文章能帮你少走点弯路,一次跑通采样链路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 17:19:27

修复 ESP-IDF I2C 读取崩溃:FIFO 溢出与信号量竞态的三步补丁

修复 ESP-IDF I2C 读取崩溃:FIFO 溢出与信号量竞态的三步补丁 【免费下载链接】esp-idf Espressif IoT Development Framework. Official development framework for Espressif SoCs. 项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf E (4812) i2…

作者头像 李华
网站建设 2026/9/9 17:19:21

【JAVA课程设计/毕业设计】基于SpringBoot的农户种植作物信息服务管理系统的设计与实现 基于SpringBoot的作物品种与种植信息管理系统的设计与实现【附源码、数据库、万字文档】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/9/9 17:18:32

健康网站AI问答适合做什么

健康网站 AI 问答适合做什么? 有带 AI 问答的健康资讯网站可以用,但要把 AI 放在“整理线索”这一层。明白健康(https://healviews.com/)提供“问问 AI”,基于站内资料给出阅读线索,并提示信息仅供参考、不…

作者头像 李华
网站建设 2026/9/9 17:17:38

SEO入门全链路教程:从搜索引擎原理到实战优化方法

搜索引擎的排名规则其实非常"朴实":它只想把对用户最有用的结果放在最前面。所以,如果你天天琢磨怎么"骗"过搜索引擎,方向就错了,真正应该花精力的是理解它的工作逻辑,然后把你的网站打造成一个真…

作者头像 李华
网站建设 2026/9/9 17:17:01

8款AI论文辅助工具实测:从选题到查重的完整工作流

1. 先泼一盆冷水:AI论文软件到底能帮你什么 论文季又到了,我后台收到的私信里,十个有八个在问:“有没有好用的AI论文软件?”“导师说我的综述像拼贴画怎么办?”“降重降到我怀疑人生”……说真的&#xff0…

作者头像 李华
网站建设 2026/9/9 17:16:54

网络流量分析器源码实战:从libpcap抓包到协议解析与统计实现

简介:这是一套基于VC开发的网络流量分析器源码工程,面向网络运维、安全分析与C开发者,可用于业务流量监控、ARP病毒监测及网络异常排查等场景。程序实现了单点流量、点到点流量、协议流量排名统计,支持按流量值或占比查看&#xf…

作者头像 李华