news 2026/9/8 8:31:41

瑞萨RA6M5上使用AGT定时器输出PWM的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
瑞萨RA6M5上使用AGT定时器输出PWM的完整指南

简介:面向需要掌握瑞萨RA6M5定时器应用的开发者,这是一套基于FSP库驱动AGT比较匹配功能并生成PWM输出的完整工程资源。压缩包共21个文件,体积仅33KB,包含C/H源码文件、Keil工程文件、FSP配置描述文件(gpdsc)、分散加载链接脚本(scat)以及引脚配置等,目录结构简洁,便于按模块查看与复用。目前已有194人学习/下载。工程内提供了从AGT初始化、时钟与预分频设置、比较匹配值配置、PWM占空比调整到中断回调注册的完整代码示例,可直接导入Keil编译验证;同时清晰展示FSP库的调用方式、中断处理与调试思路,兼顾代码可读性和实际可运行性,能显著减少RA6M5定时器应用的环境搭建与底层调试成本,对于工业控制、波形生成等场景具有直接参考价值,非常适合正在学习FSP库或快速落地PWM输出项目的嵌入式开发者。 各位做嵌入式的老哥,如果你手里正好有一块瑞萨RA6M5,想输出一路PWM,第一反应多半是开GPT定时器。官方教程、例程、论坛问答几乎全是GPT,反倒是AGT这个老牌通用定时器经常被忽略。

实际上拿AGT做PWM的场景比你想的多得多。低速脉冲输出、LED调光、舵机控制、周期翻转引脚,这类需求完全可以用AGT搞定,而且只会占用一个16位定时器,把资源更充裕的GPT留给闭环控制、编码器计数这种高级任务。这篇文章就完整走一遍RA6M5上用FSP库配置AGT比较匹配输出PWM的流程,从模块原理、图形化配置到代码实现、调试踩坑全讲透。

1. 为什么这次没有用GPT:AGT到底能干什么

1.1 RA6M5定时器资源盘点

RA6M5这颗芯片定时器资源其实挺丰富,放在Cortex-M33内核MCU里也算良心。常用的有两类:

  • GPT(General PWM Timer):通用PWM定时器,通道多、位数高,支持编码器接口、互补输出、死区控制、ADC联动触发,适合电机控制这类硬实时应用。
  • AGT(Asynchronous General Purpose Timer):异步通用定时器,16位向上计数,有两个比较匹配寄存器,支持单脉冲输出、PWM输出、外部计数,也可以当作唤醒定时器在低功耗模式下运行。

用一句话说:GPT负责“复杂且高速”的波形,AGT负责“简单且足够”的波形。很多人一上来习惯性选GPT,但如果是点个LED、控个舵机、给传感器发个周期脉冲,用GPT确实浪费。GPT通道少了,后面加功能还得挪资源,不如一开始就规划好。

1.2 比较匹配产生PWM的原理并不复杂

AGT输出PWM靠的是比较匹配机制,工作模式叫“PWM模式”,核心是两个比较匹配寄存器,在不同手册里命名不太一样,FSP里通常称作AGTMR0AGTMR1,分别对应周期匹配值和占空比匹配值。

我习惯拿秒表来类比。计数器像秒表一样从0开始向上走,每来一个时钟脉冲就加1。它走到第一个匹配值的时候,引脚输出电平跳变一次;走到第二个匹配值的时候,电平再跳变一次,同时计数器被清零,重新开始下一轮。两段匹配值之间的时间差就决定了占空比,整个循环的周期由其中一个匹配值决定。只要两个匹配值设定合适,波形就稳定输出。

这里需要留意一个细节:AGT的比较匹配PWM输出并不是像GPT那样有独立的死区互补逻辑,它是纯粹的“比较器+电平切换”。它能做出来的PWM频率范围偏向中低速,真正要高精度、高分辨率、带互补的PWM,还是得用GPT。这就是为什么我把“选型”放在第一步,后面很多问题都跟选型思路直接相关。

2. FSP工程配置全程:从Stacks到引脚

2.1 创建工程并添加AGT定时器驱动

开发环境我用的是e2 studio搭配FSP,其实用Keil搭配RASC是一样的,配置界面基本相同,生成的代码结构也一样。新建工程之后打开FSP配置界面,路径是:

Stacks -> New Stack -> Timer -> Timer Driver (r_agt)

添加成功后,配置页面会列出这个定时器模块,接下来要做的就是把参数一项项填对。下面是我测试时使用的配置:

  • Name:建议改成g_timer0这种有含义的名字,方便生成的API调用时一眼认出。
  • Channel:RA6M5有AGT0和AGT1,选AGT0。
  • Mode:选择Pulse Width Modulation (PWM)
  • Period:填写周期计数值,或者直接填目标时间(比如1ms)。
  • Duty Cycle:FSP允许直接填占空比百分比,比如50,这是图形化配置比纯寄存器直观的地方。
  • Callback:填一个回调函数名,比如agt0_callback
  • Interrupt Priority:建议选高一点的优先级,PWM比较匹配中断如果被阻塞太久,波形会出毛刺。

2.2 周期、占空比与时钟源的参数计算

AGT的时钟源默认接PCLKD,具体频率和芯片配置、外部晶振有关。我在测试板上PCLKD是50MHz。这个值很关键,因为AGT计数器的频率直接由它分频得到。

AGT的预分频器支持1、2、4、8、16、32、64、128分频。PWM频率计算公式如下:

PWM频率 = PCLKD / (预分频值 × (周期匹配值 + 1))

举个例子,PCLKD = 50MHz,预分频选1,想输出1kHz PWM,则周期匹配值 = 50MHz / 1kHz - 1 = 49999。这个值小于65535,AGT能正常处理,不需要额外分频。

占空比 = 匹配值1 / (周期匹配值 + 1) × 100%

比如周期匹配值是49999时,想要50%占空比,那么匹配值1就是25000。FSP计算这些是自动的,但你必须能验算,因为一旦波形不对,靠这些公式能快速定位到底是谁错了。我之前遇到过一个案例,预分频配错导致实际波形频率比目标值低了4倍,拿公式一套就发现是预分频选成了8而不是2。

2.3 引脚分配与复用功能检查

AGT0和AGT1的PWM输出引脚在不同型号封装上差异比较大。RA6M5常见的是AGTO0和AGTO1引脚,具体到你的板子要去看数据手册的引脚定义表,不要想当然。

在FSP的Pins选项卡里搜索AGT0,把输出引脚配置为AGTO0复用模式。这里最容易翻车:明明配置好了定时器,代码也写对了,引脚就是没有波形。原因往往是引脚复用没有生效,寄存器实际的PSEL值不对,或者这个引脚在别的Stacks模块里已经被占用了。在检查代码之前,先在FSP的Pin Conflicts界面看一遍有没有冲突。

硬件层面也要注意,AGT输出推挽能力有限,驱动三极管、MOS管做电平转换没问题,如果直接驱动大电流负载最好是加驱动电路,别指望MCU引脚能输出大电流,这在哪颗MCU上都是常识。

3. 代码实现与动态调节占空比

3.1 最小能跑通的PWM代码

配置完成后点击Generate Project Content,FSP会自动生成一堆驱动文件。核心API都在r_agt.h里暴露出来。实际使用中你不需要知道底层寄存器怎么操作,只要正确调用FSP库函数就行。

一个最小可运行的例子是这样:

#include "hal_data.h" /* 定义回调函数 */ volatile bool agt_cycle_end_flag = false; void agt0_callback(timer_callback_args_t *p_args) { if (TIMER_EVENT_CYCLE_END == p_args->event) { agt_cycle_end_flag = true; } } void hal_entry(void) { fsp_err_t err = FSP_SUCCESS; /* 1. 打开AGT定时器 */ err = R_AGT_Open(&g_timer0_ctrl, &g_timer0_cfg); if (FSP_SUCCESS != err) { __BKPT(0); /* 报错处理 */ } /* 2. 启动PWM输出 */ err = R_AGT_Start(&g_timer0_ctrl); if (FSP_SUCCESS != err) { __BKPT(0); } while (1) { /* 主循环做自己的事,PWM输出完全由硬件驱动 */ } }

代码看起来很简单,但它背后干了不少事:R_AGT_Open会把FSP界面里配置的周期值、占空比、引脚复用、中断优先级全部写进寄存器,R_AGT_Start让计数器跑起来,比较匹配事件一旦发生,电平自动翻转,全程不占用CPU。

3.2 为什么不建议直接改寄存器

很多人习惯了寄存器操作,觉得FSP封装太厚。我理解这种想法,但在AGT上直接改寄存器有一个比较坑的地方:AGTMR0、AGTMR1这类寄存器在运行时修改,需要遵循一定的时序,比如先停止计数器、修改、再重新启动。FSP的R_AGT_PeriodSetR_AGT_DutyCycleSet内部已经把这种时序处理好了,你直接调API反而安全。

还有个容易忽略的点:FSP里的占空比设置单位是百分比,但实际写进寄存器的是计数周期值。每次都自己算并且直接写寄存器,免不了因为计数范围、边界值不一致而出错。用官方API相当于让驱动程序帮你处理这些细节,工程上更稳妥。

3.3 动态改变占空比做一个呼吸灯

既然介绍PWM,肯定得有个能直观看到效果的实验。用AGT做呼吸灯就很合适,整个过程可以加深你对占空比修改边界值的理解。

呼吸灯的逻辑是周期性地把占空比从0慢慢加到100,再慢慢减回0。AGT的动态修改很简单:

#include "hal_data.h" #define PWM_STEP 2 /* 每次改变的百分比步长 */ #define PWM_DELAY_MS 10 /* 每步之间的延时 */ volatile uint16_t duty_percent = 50; uint8_t step_direction = 1; void agt0_callback(timer_callback_args_t *p_args) { /* 可以在这里做周期事件标记,呼吸灯主循环处理 */ (void)p_args; } void hal_entry(void) { R_AGT_Open(&g_timer0_ctrl, &g_timer0_cfg); R_AGT_Start(&g_timer0_ctrl); while (1) { /* 让占空比按预设方向渐变 */ if (step_direction) { duty_percent += PWM_STEP; if (duty_percent >= 100) { duty_percent = 100; step_direction = 0; } } else { duty_percent -= PWM_STEP; if (duty_percent <= 0) { duty_percent = 0; step_direction = 1; } } R_AGT_DutyCycleSet(&g_timer0_ctrl, duty_percent, AGT_OUTPUT_PIN_A); R_BSP_SoftwareDelay(PWM_DELAY_MS, BSP_DELAY_UNITS_MILLISECONDS); } }

这里有个实用心得:R_AGT_DutyCycleSet的第三个参数要区分输出引脚通道,AGT0有两个输出引脚(A和B),你用哪个引脚就在FSP里把哪个引脚分配出去,这里就传哪个引脚枚举。如果传错了,API返回FSP_ERR_INVALID_ARGUMENT,但波形大概率出不来。

另外建议把占空比渐变步长控制在1~5之间,延时10ms左右。步长大变化太跳,步长小又看不出呼吸效果。有人喜欢把延时做长到50ms模拟缓慢呼吸,体验更好,但实际测试时发现时间太长会显得像“呼吸暂停”,不是很好看。这也是调试过程中调出来的经验。

4. 常见问题与调试心得

4.1 PWM没有波形输出,肯定先查引脚复用

代码和配置都看起来没问题的前提下,没波形输出有80%是引脚复用配置出错。FSP里生成工程后,Pins选项卡可能没有自动把AGTO0分配到目标引脚上,或者你选的引脚并不支持AGTO0复用。查询引脚复用关系一定要去看数据手册的“Pin Function”表格,别只看命名。确认之后再看是否正确调用了R_AGT_Start,这一步漏了哪怕定时器Open成功也不会有波形。

还有一种隐蔽情况:FSP生成的引脚配置在R_AGT_OpenR_AGT_Start之间被其他代码覆盖了。比如你在这之间重新配置了GPIO,把复用功能改回了普通输出,波形自然就丢了。建议Open之后尽快Start,不要在中间插其他硬件操作。

4.2 周期和频率对不上,优先查预分频

R_AGT_PeriodSet修改周期时,参数单位是“计数个数”,不是时间。很多人在这里犯迷糊,填了一个时间值进去,结果频率完全不对。

FSP界面配置时是可以选时间单位的,但运行时API统一走计数个数。所以你在主循环里动态改频率,要自己算计数个数 = 定时器时钟频率 / 目标PWM频率 - 1再传进去。这个公式在上面已经给过。注意这里必须占用一个 int 或 uint32_t 类型的变量,如果频率太低导致计数值超过65535,AGT这16位计数器直接溢出,库函数会返回错误,但不会自动帮你换预分频。

我的建议是:如果PWM频率低于1kHz,老老实实用500Hz的晶振或者降低PCLKD,要么就换预分频。我在调试时为了验证极低频率,试过配置2Hz的脉冲,当时的处理方法是在FSP里把预分频调到128,然后周期值设置为一个较大的数,才把波形做出来。

4.3 100%占空比输出异常,这是常见坑

很多从STM32转过来的人,第一次接触这块容易踩一个坑:占空比调到100%时波形出不来,甚至引脚电平直接变低或者不再翻转。原因很简单,如果占空比匹配值和周期匹配值相等,AGT产生的事件可能从“比较匹配”变成了“溢出归零”,电平翻转逻辑不再按你预期的方式工作。

解决方法是设置一个上限,占空比不要真的写到100,而是设到99或者用周期值减一。这种方式在工程里也很常见。如果你非要做100%全开,不如直接把引脚切换成普通GPIO输出高电平,做完再切回PWM模式。别跟定时器的边界条件较劲,它没有为“占空比=周期”这种状态设计搬移逻辑。

4.4 回调事件区分:CYCLE_END和PULSE_END

配置回调函数之后,事件名是需要区分的。AGT的PWM模式下,常见的事件有TIMER_EVENT_CYCLE_END(周期结束,对应计数器归零)和TIMER_EVENT_PWM_PULSE_END(占空比匹配时刻)。如果你把逻辑写在错误的事件分支里,可能一个循环触发两次,或者一次都不触发。

用回调函数做频率计数时要注意在中断里只做标记,具体逻辑放主循环。我之前图省事直接在中断里调用太重的处理函数,结果主循环卡死,PWM波形也乱了。中断回调里放一个置位,主循环里轮询标志再处理,这是嵌入式基本功,但也是新手最容易忽略的一点。

5. 把AGT PWM用好的几个额外建议

先回头看选型。如果你的项目里PWM频率需求量级在几十kHz以下、占空比精度要求不是特别苛刻、不涉及互补死区,直接用AGT非常合适,尤其是低功耗场景下,AGT还能在睡眠模式下维持输出,这是GPT不具备的优势。

如果后续要扩展电机控制、半桥全桥驱动、多路联动PWM,再上GPT或者再加一个AGT通道也不迟。RA6M5和FSP这套组合的好处就是驱动统一,切换定时器只需要改配置、改通道名,API结构保持一致,迁移成本不高。

另外说个硬件细节:PWM驱动LED时,LED串联限流电阻的取值直接决定亮度均匀性,你要是跟我一样拿AGT做呼吸灯,电阻值选220Ω到1kΩ之间根据LED压降算,别一股脑用100Ω,电流大了AGTO引脚会过载。

有条件的话,调试时务必备一个逻辑分析仪来验证PWM界面配置的周期和占空比。没有逻辑分析仪,用示波器也凑合;如果都没有,那就只能像我早期一样,通过改变占空比观察LED亮度的变化来间接确认功能是否正常。方法是笨了点,但对于验证“有没有信号”完全够用。

AGT做PWM这条路,走通一次之后,后面各种需要定时、脉冲计数的场景都会顺手很多。尤其是在做低功耗产品或需要多路PWM而GPT通道紧张的场合,这颗“老定时器”的价值会远超你的预期。我自己最初也被“低端外设”这个标签误导过,真正用过之后才发现,AGT在RA6M5上完全不算低端,该有的功能一个不少,只是需要有人把它的能力讲明白。

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

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

用大模型给电商商品资料包做体检:Qwen实战与Prompt设计

上周帮一位做家居百货的卖家朋友处理店铺申诉&#xff0c;他的天猫店因为主图上印了“全网销量第一”被系统抽检下架。这个违规表述其实在商品资料包里躺了半个多月&#xff0c;前后经过运营、美工、店长三个人之手&#xff0c;愣是没人察觉。也就是在那一周&#xff0c;我用 Q…

作者头像 李华
网站建设 2026/9/8 8:28:36

Python切片操作完全指南:语法、实例与性能陷阱

写了几年 Python&#xff0c;切片&#xff08;slicing&#xff09;大概是我用得最多的语法之一&#xff0c;但也是我发现周围同事误解最深的一个。新同学往往觉得它不过是 a[1:3] 取一段&#xff0c;老同学则常在 a[::-1] 、 a[5:0:-1] 这种“负步长 负停靠点”的组合里…

作者头像 李华
网站建设 2026/9/8 8:28:35

服务器不稳定排查指南:从CPU、内存到网络的系统化定位方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 8:27:54

测试用例设计与编写实战:方法、模板与优先级管理

测试用例这东西&#xff0c;说实话&#xff0c;看着简单&#xff0c;写好不容易。我在测试这行摸爬滚打了这么多年&#xff0c;见过太多“看起来行云流水、一执行就翻车”的项目&#xff0c;根源往往不是执行的人不行&#xff0c;而是用例本身就没写好。所以今天这篇&#xff0…

作者头像 李华
网站建设 2026/9/8 8:27:54

Go并发编程实战:从goroutine、channel到GMP模型与性能调优

1. 为什么咱们都该好好学学Go的并发干咱们这行的&#xff0c;迟早会碰到并发这道坎。Java里有线程池&#xff0c;Python里有GIL&#xff0c;C里有各种锁和原子操作&#xff0c;轮子不少&#xff0c;但真正想把并发写得又简单又不容易出错&#xff0c;Go绝对是绕不开的那一个。我…

作者头像 李华
网站建设 2026/9/8 8:27:32

从 LangChain 到 LangGraph:Agent 开发如何走向工程化与状态可控

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华