第七章 通用定时器(GPTIM) ------ 成为时间的主宰
我们已经掌握了SysTick这个简单可靠的"节拍器",但它功能单一,且整个系统中只有一个。当我们需要多个独立的定时任务,或者需要实现更复杂的时序控制(如生成PWM波)时,就需要请出单片机中的"瑞士军刀"------通用定时器(General-Purpose Timer, GPTIM)。
1. 揭开定时器的神秘面纱:水龙头与水桶的故事
要理解定时器的工作原理,我们可以把它想象成一个自动化的"滴水计时"装置:
-
时钟源 (Clock Source) :这是总水源,比如市政自来水管道。在MSPM0中,它可以是BUSCLK (总线时钟, 通常为32MHz)、MFCLK (中频时钟) 或 LFCLK (低频时钟)。
-
主时钟分频器 (Clock Divider):这是管道上的第一个总阀门,可以对水流进行粗略的"减速"。例如,设置为8分频,就意味着水流速度降为原来的1/8。
-
预分频器 (Prescaler) :这是第二个更精密的阀门。它是一个8位的计数器,可以设置一个0到255的值(我们称之为
PSC)。它的工作方式是:"每流过(PSC + 1)滴水,我才向下游放出1滴水。" 这就实现了对水流的精细控制。 -
计数器时钟 (Counter Clock) :经过两级阀门"减速"后的最终水流速度。它的频率计算公式为:
计数器时钟频率 = 时钟源频率 / (主时钟分频比 * (预分频值 + 1)) -
计数器 (Counter) & 自动重装载值 (Load Value) :这就是我们的水桶。它有一个固定的容量,这个容量就是"自动重装载值"(我们称之为
ARR)。水滴(计数器时钟脉冲)一滴一滴地落入桶中,桶里的水量(计数器值)不断增加(向上计数模式)。 -
更新事件/中断 (Update Event/Interrupt) :当水桶被滴满(计数器值达到
ARR)时,它会瞬间"哗啦"一下自动倒空,然后从第一滴水开始重新装水。在倒空的那一刻,它会"Duang"地响一声------这就是更新事件 ,如果开启了中断,就会触发一次定时器中断。
(文字流程图): [时钟源] -> [主分频] -> [预分频器] -> (计数器时钟) -> [计数器水桶] -> (水满溢出) -> [中断信号]
结合您的代码进行计算验证:
- 时钟源:BUSCLK = 4MHz (注意,您注释写的是4MHz,一般为32MHz,我们以4MHz为例)
- 主分频:8
- 预分频值:99
计数器时钟频率 = 4,000,000 Hz / (8 * (99 + 1)) = 4,000,000 / 800 = 5,000 Hz
(如果BUSCLK为32MHz,则频率为 40,000 Hz,与您代码注释一致)- 重载值:39999
中断周期 = (重载值 + 1) / 计数器时钟频率 = (39999 + 1) / 40000 Hz = 40000 / 40000 = 1 秒
结论:这套配置完美地实现了一个1秒触发一次的中断!
2. 软件设计:让SysConfig为你计算一切
手动计算这些参数不仅繁琐,而且容易出错。幸运的是,SysConfig可以帮我们自动完成这一切,我们只需要告诉它我们想要的结果。
-
打开项目的
.syscfg文件,添加 TIMER 模块,选择一个可用的通用定时器实例,例如TIMG0。 -
配置时钟 (Clock Configuration):
- Clock Source : 选择
BUSCLK。SysConfig会自动显示出BUSCLK的当前频率(例如32MHz)。 - 【关键】Desired Timer Clock Freq (Hz) : 不要手动去算Prescaler! 直接在这里输入你想要的"水流速度",例如
40000Hz。SysConfig会自动为你计算出最佳的Clock Divider和Prescaler组合。这极大地简化了配置过程。
- 配置定时器 (Timer Configuration):
- Timer Mode :
PERIODIC(周期模式)。 - 【关键】Period Count : 直接在这里输入你想要的定时周期,例如
1000,然后在旁边的单位下拉框中选择ms(毫秒)。SysConfig会根据上一步的计数器时钟频率,自动为你计算出Load Value(即39999)。
- 配置中断 (Interrupt Configuration):
- 勾选 Enable Interrupt。
- 在下方的 Interrupts 列表中,勾选
Zero Event。 - 在NVIC配置中,确保TIMG0的中断已使能。
- 保存 。SysConfig 会自动生成您在草稿中展示的
gTIMER_0ClockConfig、gTIMER_0TimerConfig等所有底层配置结构体和初始化函数。
3. 代码实现:一个纯粹的while(1)
经过SysConfig的强大支持,我们的软件代码变得异常简洁和清晰。
TIM.h
c
#ifndef __TIM_H__
#define __TIM_H__
// 在这个例子中,由于所有配置和初始化都在SysConfig生成的
// SYSCFG_DL_init()中完成,我们甚至不需要一个独立的TIM_Init()函数。
// 但为了结构完整性,可以保留一个空函数或用于未来扩展。
#endif // __TIM_H__
TIM.c (中断服务函数)
c
#include "ti_msp_dl_config.h"
/**
* @brief TIMG0 中断服务函数
* @note 函数名由SysConfig在系统启动文件中指定
*/
void TIMG0_IRQHandler(void)
{
// 使用switch语句判断中断源是个好习惯,即使当前只有一个
switch (DL_TimerG_getPendingInterrupt(TIMG0)) {
case DL_TIMERG_IIDX_ZERO: // 由计数器溢出(归零)触发的中断
// 在这里执行你的周期性任务
// 例如:翻转LED
DL_GPIO_togglePins(GPIO_GRP_0_PORT, GPIO_GRP_0_LED_PIN);
break;
default:
break;
}
}
main.c (主函数)
c
#include "ti_msp_dl_config.h"
int main(void)
{
// 这一步初始化了所有东西:时钟、GPIO、定时器、中断...
SYSCFG_DL_init();
// 使能全局中断
__enable_irq();
// 主循环可以完全空闲,或者执行一些非实时的任务。
// 所有的周期性工作都交给了定时器中断在后台精确执行。
while (1)
{
// 让CPU进入休眠,直到下一次中断发生,从而降低功耗
__WFI();
}
}
编译并烧录程序。 你会看到LED以精准的1秒周期闪烁(每秒翻转一次,所以亮0.5s,灭0.5s,周期为1s)。整个main循环却"无所事事",这正是中断驱动设计的精髓------高效、节能、实时。
4. 思考与拓展:定时器的无穷潜力
我们刚刚只用了定时器最基础的功能,但它的真正实力远不止于此。
- PWM输出 (脉冲宽度调制) :定时器可以在一个周期内,控制一个引脚输出高电平的时间占比。例如,70%的时间为高电平,30%为低电平。通过改变这个占比(占空比),我们就可以平滑地控制LED的亮度 ,或者控制电机的转速。这是下一章我们将要探索的强大功能。
- 输入捕获 (Input Capture):将定时器配置为"秒表"模式。当一个外部信号(如另一个传感器)的边沿到来时,定时器可以"捕获"当前计数器的值。通过计算两次捕获之间的差值,我们就能精确测量外部信号的频率或脉冲宽度。
- 多通道与联动:一个定时器通常有多个通道,可以独立或联动地实现更复杂的波形生成和时序控制。
掌握了通用定时器,你才算真正掌握了控制"时间"的钥匙,从而能够驾驭更广阔的嵌入式应用场景。