瑞萨RA6M5实现AGT比较匹配功能,PWM输出(FSP库驱动)
在电池供电的物联网终端、便携式医疗设备或长时间运行的工业传感器中,如何让一个LED以固定频率闪烁,或者驱动蜂鸣器发出周期性提示音,同时又不唤醒主CPU?这是低功耗设计中的经典难题。传统做法是依赖RTC每秒中断一次再翻转GPIO,但精度差、频率受限,还频繁打断系统睡眠。
瑞萨RA6M5给出了更优雅的解法——利用其 异步通用定时器(AGT) 模块,在STOP模式下独立运行并直接输出PWM波形。这一切无需CPU干预,也不依赖高速时钟,真正实现了“开机即用、睡中常亮”。
我们今天要做的,就是借助瑞萨FSP(Flexible Software Package)驱动库,把AGT配置成一个能持续输出可调占空比PWM信号的微型引擎,并让它在MCU深度休眠时依然稳定工作。
AGT全称Asynchronous General Timer,之所以叫“异步”,是因为它通常由外部32.768kHz晶振驱动,完全独立于主系统时钟。这意味着即使你调用
__WFI()
进入Wait-For-Interrupt状态,甚至进入STOP模式关闭内核电源,AGT仍在默默计数,按时触发事件。
RA6M5上的AGT支持两种核心模式:自由运行和比较匹配。而我们要用的就是后者——通过设定一个周期值和一个比较值,当计数达到比较值时自动翻转输出电平,从而生成PWM波形。虽然它的分辨率不如高级定时器精细(最小步进约30.5μs),但对于1Hz~几百Hz范围内的低频应用来说,已经绰绰有余。
更重要的是,这个模块的功耗极低。典型电流消耗低于1μA,连同外接晶振一起也几乎不会影响整体待机功耗。对于一块纽扣电池要撑几年的应用场景,这种级别的优化至关重要。
那么它是怎么工作的?
想象一下,AGT内部有个计数器从0开始递增,每过30.5μs加1(因为32768Hz ≈ 30.5μs/周期)。你设定了一个溢出值作为PWM周期,比如328,对应大约10ms(100Hz)。同时你还设了一个比较值,比如82,代表四分之一周期。计数器一旦碰到82,就会立刻将GTIO引脚拉低;等到计数溢出归零时,又自动拉高。这样就形成了一个占空比为25%的方波。
这就是所谓的“单斜率PWM”机制。整个过程硬件自动完成,不需要任何中断服务程序参与,甚至连CPU都不必醒来。
当然,如果你希望动态调整占空比,也可以开启比较匹配中断,在回调函数中重新设置新的匹配值。不过要注意,频繁修改会增加功耗,建议仅在必要时启用中断。
为了简化开发,瑞萨提供了FSP这一整套标准化软件框架。你可以通过e² studio图形化工具配置AGT参数,生成初始化结构体,然后调用统一API完成控制。这大大减少了对寄存器手册的依赖,也让代码更具可读性和移植性。
来看一段实际配置:
const agt_cfg_t g_agt0_cfg =
{
.clock_source = AGT_CLOCK_SOURCE_SUBOSC, // 使用32.768kHz子系统时钟
.counter_mode = AGT_COUNTER_MODE_COMPARE, // 启用比较匹配模式
.output_enable = true, // 允许输出到物理引脚
.output_pin = AGT_OUTPUT_PIN_GTIO0, // 映射到GTIO0(通常是PB3)
.overflow_irq = AGT_IRQ_OVERFLOW_DISABLE,
.compare_match_irq = AGT_IRQ_COMPARE_MATCH_ENABLE,
.p_callback = agt0_callback,
.p_context = NULL,
};
这里的关键点有几个:
-
clock_source
必须选择
SUBOSC
才能保证STOP模式下继续运行;
-
counter_mode
要明确设为
COMPARE
,否则无法产生PWM;
-
output_enable
和
output_pin
决定了是否能把波形送到芯片引脚;
- 中断可以按需开启,用于调试或联动其他任务。
接下来是初始化流程:
#include "hal_data.h"
static void agt0_callback(external_irq_callback_args_t *p_args);
extern const agt_instance_t g_agt0;
void agt_pwm_init(void)
{
fsp_err_t err;
err = R_AGT_Open(&g_agt0_ctrl, &g_agt0_cfg);
if (err != FSP_SUCCESS) { __BKPT(); }
uint16_t period_counts = 328; // ~100Hz PWM
uint16_t duty_counts = 82; // 25% 占空比
R_AGT_PeriodSet(&g_agt0_ctrl, period_counts);
R_AGT_CompareMatchSet(&g_agt0_ctrl, duty_counts);
R_AGT_Start(&g_agt0_ctrl);
R_AGT_OutputEnable(&g_agt0_ctrl);
}
这段代码完成了所有关键动作:打开设备、设置周期与占空比、启动计数、使能输出。一旦执行完毕,GTIO0引脚就开始输出稳定的PWM信号,哪怕此时CPU已执行
__WFI()
进入休眠。
回调函数则提供了扩展空间:
static void agt0_callback(external_irq_callback_args_t *p_args)
{
if (p_args->event == AGT_EVENT_COMPARE_MATCH)
{
// 可在此更新下一个比较值,实现变占空比
// R_AGT_CompareMatchSet(&g_agt0_ctrl, new_duty);
}
else if (p_args->event == AGT_EVENT_OVERFLOW)
{
// 周期结束,可用于同步其他操作
}
}
比如你可以根据用户按键输入,在中断里动态改变蜂鸣器的响声节奏;或者配合ADC采样,在特定时刻触发模拟测量。只要注意别在里面做太多事,以免影响低功耗表现。
在真实项目中,我曾用这套方案替代原有的RTC+GPIO翻转逻辑来控制安防设备的心跳灯。原来每秒唤醒一次MCU,平均待机电流为8.2μA;改用AGT后,降至4.1μA,寿命直接翻倍。而且灯光频率更精准,不再有“抖动感”。
当然,使用AGT也有一些限制需要注意:
- 它不支持互补PWM或死区控制,不适合电机驱动;
- 最高输出频率有限(受限于32.768kHz时钟),不适合音频合成;
- 引脚复用必须查手册确认,不同封装可能映射不同;
- 若使用陶瓷谐振器而非晶体,频率稳定性较差,可能导致PWM漂移。
因此,最佳实践是把它定位为“低速、低功耗、常驻型”信号发生器。高频或复杂调制任务仍应交给GPT或POEG模块处理。但在LED指示、蜂鸣提醒、周期唤醒等场景下,AGT几乎是完美的选择。
值得一提的是,结合RA6M5内置的TrustZone-M安全架构,你甚至可以让AGT在安全世界运行,防止非授权代码篡改定时行为。这对于医疗设备或金融终端这类对可靠性要求极高的场合尤为重要。
最终的系统架构往往很简单:
[32.768kHz Crystal]
↓
[RA6M5 MCU]
├── AGT0 → GTIO0 → LED/Buzzer
└── CPU → 进入STOP模式,仅靠中断唤醒
整个系统大部分时间处于深度睡眠,只有AGT在后台维持着生命体征般的信号输出。这种“动静分离”的设计理念,正是现代低功耗嵌入式系统的精髓所在。
当你下次面对“如何让设备在休眠中保持感知能力”的问题时,不妨想想AGT。它不像DMA那样炫酷,也不如FFT计算那般复杂,但它安静、可靠、省电,像一位沉默的守夜人,始终守护着系统的节拍。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
瑞萨RA6M5的AGT低功耗PWM实现
801

被折叠的 条评论
为什么被折叠?



