news 2026/7/30 22:37:07

AT32(四):TMR实战——精准定时与PWM波形控制技巧

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AT32(四):TMR实战——精准定时与PWM波形控制技巧

1. 从零开始:理解AT32的TMR定时器

如果你刚开始接触AT32F421这类单片机,听到“定时器”这个词,可能会觉得它既基础又有点神秘。基础是因为几乎所有单片机项目都离不开它,神秘则是因为它的配置寄存器看起来有点复杂。别担心,今天我就用最直白的方式,带你彻底玩转AT32的TMR模块,特别是精准定时和PWM波形控制这两个核心技能。我自己在项目里用AT32F421做过不少东西,从简单的LED闪烁到复杂的电机控制,踩过不少坑,也总结了很多“一次配置成功”的窍门,这篇文章就是把这些实战经验毫无保留地分享给你。

AT32F421的定时器,官方叫TMR,其实和STM32的TIM是类似的东西,但它在一些细节上做了优化。我们主要会用到两种:基础定时器(比如TMR6)和通用定时器(比如TMR3)。你可以把基础定时器想象成一个只会“数数”的简单闹钟,它数到设定的时间就“响”一下(产生中断),功能纯粹,非常适合做精准的延时。而通用定时器就像是一个多才多艺的瑞士军刀,它不仅能数数,还能输出特定波形(PWM)、测量外部信号的宽度(输入捕获),甚至多个定时器还能联动工作。我们今天的实战目标很明确:第一,用TMR6实现一个2秒的精准定时,并且在这期间检测按键;第二,用TMR3生成一个频率和占空比都可调的PWM波;第三,还是用TMR3,来捕获并测量一个外部PWM信号的参数。听起来是不是很有用?我们一步步来。

在开始写代码前,有个小建议:一定要把时钟树搞清楚。AT32F421的定时器时钟来源于AHB总线,你的系统时钟配置直接决定了定时器“数数”的快慢。我见过不少朋友代码逻辑都对,但定时时间就是不准,八成是时钟没配对。比如,如果你系统时钟配成了120MHz,那么定时器计数一次的时间就是1/120微秒,这是所有计算的基础。好了,理论铺垫就这些,我们直接进入实战环节。

2. 微秒级精准定时:TMR6的实战配置

我们先来解决第一个问题:如何用TMR6实现一个2秒的定时,并且在定时期间随时响应按键?这个场景非常典型,比如你的设备上电后,需要一个短暂的“等待配置”时间,如果用户在这期间按下某个键,就进入配置模式,否则就进入正常运行模式。

2.1 定时器核心参数:PR与DIV的计算

配置定时器,本质上就是配置两个核心寄存器:自动重载寄存器(TMRx_PR)分频器(TMRx_DIV)。定时器计数时钟(CK_CNT)的频率等于输入时钟(CK_PSC)除以(DIV+1)。计数器从0开始向上数,每来一个CK_CNT时钟就加1,一直数到PR的值,然后产生一个更新事件(溢出),同时计数器清零重新开始。所以,定时周期T的计算公式是:T = (PR + 1) * (DIV + 1) / TMRx_CLK其中TMRx_CLK是定时器的输入时钟频率。

假设我们的系统AHB时钟是120MHz,并且TMR6直接挂载在AHB上(即TMR6_CLK = 120MHz)。我们需要2秒的定时,即T=2s。这里有个技巧:PR和DIV都是16位寄存器,最大值是65535。如果我们直接把2秒换算成计数次数,会是2 * 120e6 = 240,000,000次,这远远超过了65535。所以我们必须借助分频器DIV来降低计数频率。

一个常用的方法是先确定PR的值,再反算DIV。为了让定时器分辨率更高(即计数的“步进”更精细),我们通常希望PR尽可能大。我们可以先把PR设为最大值65535(即0xFFFF),然后计算需要的DIV:DIV = (T * TMRx_CLK) / (PR + 1) - 1代入数值:DIV = (2 * 120e6) / (65535 + 1) - 1 ≈ 3661.8,取整为3662。 这样配置后,实际的定时时间会有一点点误差,但对于2秒来说,这点误差通常可以接受。如果你想追求极致的精度,可以调整PR和DIV的值进行微调。在我的代码里,我用了另一种思路,我先把目标频率(0.5Hz,对应周期2秒)换算成计数器频率,再配置分频,原理是相通的。

2.2 按键检测与中断处理的巧妙配合

光有定时器还不行,我们需要在定时器运行的同时,检测PC13按键是否被按下。这里的关键是如何让主循环和定时器中断协同工作。一个常见的错误设计是:在定时器中断服务函数里做长时间的延时或复杂的打印,这会导致主程序卡死,也无法及时响应按键。

我的做法是这样的:

  1. 初始化TMR6为单脉冲模式:通过tmr_one_cycle_mode_enable(TMR6, TRUE)实现。这样定时器溢出一次后就会自动停止,而不是周而复始。这正好符合我们“只等2秒”的需求。
  2. 设置一个全局状态标志:比如char TIM6OVF = 0;。在TMR6的溢出中断服务函数里,不做复杂操作,仅仅将这个标志置1。
  3. 主循环轮询:在主函数的while循环里,同时检查两个条件:一是定时器溢出标志TIM6OVF是否为1,二是按键是否被按下(TX标志是否被置1)。只要有一个条件满足,就跳出循环,进入后续的模式判断。
  4. 按键消抖:原始代码中使用了usrkey_state()函数,它内部应该已经做了消抖处理。如果你自己写按键检测,一定要记得消抖,通常用简单的延时或状态机来实现,避免一次按下被误判多次。

这种“中断置标志,主循环查询”的模式,在单片机编程中非常经典和实用。它既保证了中断响应快,又避免了在中断里处理复杂逻辑带来的风险。当2秒超时后,TIM6OVF变为1,程序知道用户没有按键,进入PWM捕获模式。如果在2秒内按键按下,我们手动将TX置1,并立即复位和停止TMR6(tmr_reset(TMR6);),然后跳出循环,进入PWM产生模式。

注意:在初始化TMR6并开启计数之前,一定要先初始化好串口等打印调试工具。因为TMR6的tmr_base_init函数可能会软件触发一次溢出中断,如果中断服务函数里调用了printf,但串口还没初始化,程序就会卡死。我的代码里把USART初始化放在了TMR6初始化之前,就是这个原因。

3. PWM波形生成:用TMR3输出可控波形

搞定精准定时后,我们进入更精彩的部分:用通用定时器TMR3生成PWM波。PWM(脉冲宽度调制)可以说是单片机控制外部世界的“魔法棒”,控制LED亮度、电机转速、舵机角度都离不开它。AT32的通用定时器通常有多个通道,每个通道都可以独立配置成PWM输出模式。

3.1 频率与占空比:精度优先级的抉择

生成PWM,最核心的就是设定两个参数:频率(或周期)占空比。在16位定时器的限制下(PR和DIV最大65535),鱼与熊掌有时难以兼得。你想获得一个非常高的频率(比如100kHz),周期精度可以很细,但此时计数器最大值(PR)会很小,导致占空比可调节的级数变少,精度下降。反之,如果你想获得非常精细的占空比调节(比如用于高精度DAC模拟),就需要把PR设得很大,这又会限制你能生成的最低频率。

我的sigle_pwm_gen函数里引入了一个accuracy_priority_type参数,让你可以根据应用场景优先保证某一项的精度

  • DUTYCYCLE优先级:适用于对占空比精度要求极高的场景,比如LED调光要求65536级无闪烁。此时,函数会将PR固定为最大值0xFFFF,然后通过调整DIV的值来逼近你想要的频率。由于PR最大,占空比寄存器(CxDT)可以有0~65535共65536个取值,占空比精度达到1/65536。代价是频率的精度会有所牺牲,并且可生成的频率范围有下限(计算可知,在120MHz下,周期不能小于547微秒)。
  • PERIOD优先级:适用于对频率精度要求极高的场景,比如生成一个非常精确的音频频率。此时,函数会将DIV固定为0(即不分频),然后通过调整PR的值来得到精确的周期。这样,每个计数步长对应的时间是固定的(1/120MHz ≈ 8.33纳秒),周期精度最高。代价是占空比的可调级数受限于PR的大小。

你可以根据你的项目需求来选择模式。比如控制舵机,需要的是50Hz的固定频率和精确的脉冲宽度(占空比),那么用PERIOD优先级,固定DIV=0,计算PR=23999(对应120MHz/(23999+1)=50Hz),然后通过CxDT来精确控制0.5ms到2.5ms的高电平宽度。

3.2 通道配置与极性设置

选定PR和DIV后,就要配置具体的输出通道了。我们用的是TMR3的通道1,对应引脚PA6。AT32的PWM输出模式主要两种:PWM模式APWM模式B。它们的区别在于输出电平与计数值比较结果的对应关系。

  • PWM模式A:当计数器值小于通道捕获/比较寄存器值(CxDT)时,输出有效电平(可设置为高或低);大于等于时,输出无效电平。
  • PWM模式B:与模式A相反,当计数器值小于CxDT时,输出无效电平;大于等于时,输出有效电平。

通常我们使用PWM模式A。配合oc_polarity(输出极性)和oc_idle_state(空闲状态)这两个参数,可以灵活定义你想要的波形。例如,想要一个“高电平有效”的PWM(即占空比期间输出高电平),可以这样配:

tmr_oc_init_structure.oc_mode = TMR_OUTPUT_CONTROL_PWM_MODE_A; tmr_oc_init_structure.oc_polarity = TMR_OUTPUT_ACTIVE_HIGH; // 有效电平为高 tmr_oc_init_structure.oc_idle_state = FALSE; // 空闲(非有效)时输出低电平

这样,当计数器从0数到CxDT-1时,输出高电平;数到CxDT到PR时,输出低电平。占空比就是CxDT / (PR+1)

最后,记得使能通道输出(tmr_channel_enable)和使能定时器计数器(tmr_counter_enable),PWM波形就会从PA6引脚输出了。你可以用示波器或者逻辑分析仪接到PA6上,亲眼看到你生成的波形,这是最有成就感的一步!

4. PWM波形捕获:测量外部信号的秘诀

学会了输出,再来学输入。有时候我们需要测量一个外部信号的频率和占空比,比如读取旋转编码器的速度、测量遥控器的PWM信号等。这就是PWM输入捕获功能。AT32的通用定时器可以轻松实现这个功能,而且精度很高。

4.1 输入捕获与从模式联动

PWM捕获的原理,简单说就是测量一个完整周期内高电平的时间。这需要两次捕获:一次在上升沿,一次在下降沿。如果只用一个通道,就需要在中断里切换捕获边沿,计算稍显麻烦。AT32提供了一个更聪明的办法:使用两个通道,并结合定时器的“从模式”

在我的sigle_pwm_cap函数中,使用了TMR3的通道2(PA7)作为输入捕获引脚,配置为上升沿触发。但关键在于后面的配置:

  1. 触发源选择tmr_trigger_input_select(TMR3, TMR_SUB_INPUT_SEL_C2DF2);这句话的意思是,选择通道2的滤波后数据(C2DF2)作为定时器的触发输入源。也就是说,通道2捕获到的事件,可以触发整个定时器。
  2. 从模式设置为复位模式tmr_sub_mode_select(TMR3, TMR_SUB_RESET_MODE);这是点睛之笔。在此模式下,当选择的触发输入事件(即通道2的上升沿)发生时,定时器的计数器会立刻被清零复位。
  3. 使能从同步模式tmr_sub_sync_mode_set(TMR3, TRUE);启用这个联动功能。

这一套组合拳下来,实现了什么效果呢?外部PWM信号的每一个上升沿到来时,TMR3的计数器都被清零重启。同时,我们将通道1配置为下降沿捕获(注意,代码中是通过tmr_pwm_input_config函数内部配置的,它会把另一个通道自动配为相反的边沿)。这样:

  • 在上升沿(通道2事件),计数器清零,我们记下这个时刻(实际上计数器是0,不用记)。
  • 在接下来的下降沿(通道1事件),计数器已经累加了高电平的时间,我们读取通道1的捕获值(CCR1),这个值就是高电平的计数值
  • 在下一次上升沿(通道2事件),计数器再次被清零,但在此之前,计数器已经累加了一个完整周期的时间。此时,我们读取计数器的值(或者直接读取通道2的捕获值CCR2,因为在复位模式下,上升沿时也会触发捕获),这个值就是整个周期的计数值

有了高电平计数值和周期计数值,频率和占空比就很容易计算了:频率 = 定时器时钟频率 / 周期计数值占空比 = (高电平计数值 * 100) / 周期计数值

4.2 中断处理与数据计算

所有的捕获事件都会触发中断。在TMR3_GLOBAL_IRQHandler中断服务函数中,我们需要判断是哪个通道的中断,并读取对应的捕获值。

  • 对于通道1中断(下降沿),我们读取tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_1),这就是高电平时间t_high
  • 对于通道2中断(上升沿),我们读取tmr_channel_value_get(TMR3, TMR_SELECT_CHANNEL_2),这就是整个周期t_period

这里有一个细节:必须在中断里及时清除对应的中断标志位,否则会连续进入中断。使用tmr_flag_clear(TMR3, TMR_C1_FLAG)来清除。

计算得到的频率和占空比,我存放在了全局变量PWMin_frequencyPWMin_dutycycle中,并在主循环里打印出来。这里又有一个实战经验:避免在中断服务函数中进行耗时操作,如浮点运算或格式化打印。我的做法是在中断里只做简单的整数乘除,将结果存入全局变量。复杂的打印操作放在主循环中。虽然主循环的打印可能会被中断打断,但因为我们只是显示数据,即使数据在更新中途被读取,显示值跳动一下,也不会影响系统的核心功能,这是一种实用的取舍。

5. 调试技巧与常见问题避坑指南

代码写完了,能不能一次成功?大概率会遇到问题。分享几个我调试时遇到的坑和解决方法,希望能帮你节省时间。

问题一:定时器时间不准。

  • 检查时钟源:确认crm_clocks_freq_get获取到的AHB时钟频率是不是你预期的值。如果系统时钟配置错了,一切都白搭。
  • 检查PR和DIV的计算:用手算或打印出来看看,是不是超出了0~65535的范围。特别注意公式里的“+1”和“-1”,很容易漏。
  • 检查计数器方向tmr_cnt_dir_set(TMR6, TMR_COUNT_UP)是向上计数,这是最常用的。确保没有配错。

问题二:PWM输出没有波形。

  • 检查GPIO复用:这是新手最容易忽略的一点!PA6要输出TMR3的通道1波形,除了配置GPIO为复用模式,必须调用gpio_pin_mux_config(GPIOA, GPIO_PINS_SOURCE6, GPIO_MUX_1);将引脚映射到正确的复用功能上。AT32F421的PA6,其TMR3_CH1功能通常对应GPIO_MUX_1,具体要查数据手册的复用功能表。
  • 检查通道是否使能tmr_channel_enable(TMR3, TMR_SELECT_CHANNEL_1, TRUE);这行代码加了吗?
  • 检查总输出使能:有些定时器有刹车和死区控制,通用定时器TMR3没有,所以不需要tmr_output_enable。但如果有,一定要打开。

问题三:PWM捕获不到数据,或者数据乱跳。

  • 检查输入引脚配置:和输出一样,输入引脚PA7也要配置为复用模式并映射到GPIO_MUX_1
  • 检查从模式和触发源配置:确保tmr_sub_mode_selecttmr_trigger_input_selecttmr_sub_sync_mode_set这几条语句按顺序正确配置,并且参数匹配。
  • 检查中断标志清除:在中断函数里,是不是只清除了发生中断的那个标志?如果该清除的没清除,会一直进中断;如果错清了别的标志,可能导致数据丢失。
  • 信号质量问题:如果外部信号有毛刺,可能会导致错误的边沿检测。可以尝试增大输入滤波器值input_filter_value,这个值表示需要连续多少个采样点一致才认为边沿有效,可以有效滤除噪声。

关于原始文章末尾提到的“无法解释的问题”:即在中断禁止寄存器(IDEN)中未使能某个中断,但中断状态寄存器(ISTS)却显示了该中断标志。这种情况我确实也遇到过,尤其是在复杂定时器操作和从模式联动时。一种可能的解释是,某些定时器内部操作(如软件触发、从模式复位)会强制设置相应的状态标志,即使该中断在IDEN中被禁用。这些标志位可能只是状态指示,并不一定会导致CPU跳转到中断向量。关键在于,只要你在中断服务函数中,通过tmr_flag_get判断并处理了已使能的中断,然后清除它们,系统就能正常工作。对于这些“额外”的标志,如果你不使能它的中断,也不在中断服务函数里读取它,就可以忽略。当然,最严谨的做法是查阅芯片的勘误手册,里面可能会记录这类硅片行为,或者咨询原厂技术支持。在实战中,遵循“处理所需,忽略其余”的原则,通常能让项目快速推进。

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

【Linux内幕】深入解析schedule_work与共享工作队列的协作机制

1. 从“排队打饭”到内核调度:schedule_work与共享工作队列初印象 大家好,我是老K,在Linux内核和驱动开发这个行当里摸爬滚打了十几年,从早期的2.6内核一直跟到现在的5.x系列,亲手调试过的驱动和设备不计其数。今天想…

作者头像 李华
网站建设 2026/7/21 6:03:24

5种画风任选:SDXL 1.0绘图工坊新手入门指南

5种画风任选:SDXL 1.0绘图工坊新手入门指南 关键词:SDXL 1.0、AI绘图、画风预设、RTX 4090、高清图像生成、Stable Diffusion 摘要:本文为AI绘图新手提供SDXL 1.0绘图工坊的完整入门指南,从环境部署到5种画风的实际应用&#xff0…

作者头像 李华
网站建设 2026/7/21 6:03:26

SmolVLA效果展示:‘抓红方块放蓝盒’指令→6维连续动作向量真实输出

SmolVLA效果展示:‘抓红方块放蓝盒’指令→6维连续动作向量真实输出 1. 项目概述 SmolVLA是一个专门为经济实惠的机器人技术设计的紧凑型视觉-语言-动作模型。这个模型的神奇之处在于,它能看懂图像、理解语言指令,然后直接输出机器人应该执…

作者头像 李华
网站建设 2026/7/21 6:03:26

探索大数据领域批处理的最佳解决方案

探索大数据领域批处理的最佳解决方案:从原理到实践的全维度指南 1. 引入与连接:当“海量数据”遇上“必须解决的问题” 小王是某电商的数据工程师,最近愁得头发都快掉了——每天凌晨3点,他要处理10TB的用户行为日志,生成“每日用户画像”报表。之前用Hadoop MapReduce,…

作者头像 李华
网站建设 2026/7/21 6:03:43

轻量级语义重排序神器:Qwen3-Reranker-0.6B使用体验分享

轻量级语义重排序神器:Qwen3-Reranker-0.6B使用体验分享 1. 为什么你需要一个轻量级重排序器 如果你正在构建智能问答系统或者知识库检索工具,可能已经遇到过这样的问题:用向量搜索找到的文档看起来相关,但实际内容却不太匹配。…

作者头像 李华