6. 定时器
📝本节您将了解定时器的工作原理,学习 MSPM0G3519 的定时器资源分类,掌握通用定时器(TIMG)的周期定时中断配置方法。
🏆本章目标
1️⃣ 理解定时器的计数原理和基本参数
2️⃣ 了解 MSPM0G3519 的 TIMG 和 TIMA 定时器资源
3️⃣ 通过 SysConfig 配置定时器周期中断
4️⃣ 实现定时器中断驱动 LED 闪烁
6.1 定时器基本概念
定时器是 MCU 内部的硬件计数器模块。它在时钟信号的驱动下自动递增(或递减)计数,当计数值达到预设目标时产生事件(如中断)。
与 delay_cycles 软件延时相比,定时器的优势在于:
定时器核心参数
| 参数 | 说明 |
|---|---|
| 时钟源 | 驱动计数器的时钟信号(通常为 MCLK 或其分频) |
| 预分频器(Prescaler) | 对时钟源分频后再驱动计数器,降低计数频率 |
| 计数位宽 | 计数器的最大值(16位 = 0~65535,32位 = 0~4294967295) |
| 重载值(Period) | 计数器的目标值,到达后自动重载并触发事件 |
| 计数模式 | 向上计数、向下计数、上下计数 |
三种计数模式
定时器有三种计数模式,决定了计数器数值的变化方向:
| 模式 | 计数轨迹 | 适用场景 |
|---|---|---|
| 向上计数 | 0 → 1 → 2 → … → Period → 0(循环) | 周期定时(最常用,事件在计数值达到 Period 时触发) |
| 向下计数 | Period → … → 2 → 1 → 0 → Period(循环) | 周期定时(本实验使用,事件在计数值归零时触发) |
| 上下计数 | 0 → 1 → … → Period → … → 2 → 1 → 0(循环) | 需对称波形的场景(后续 PWM 章节讲解) |

定时器工作流程
以通用 16-bit TIMG 的向下计数模式为例,五个环节串联成一个完整的定时流水线:
时钟源 ──→ 预分频器 ──→ 计数器 ──→ 零检测 ──→ 事件输出
│ │ │ │ │
32MHz ÷32=1MHz Count→0 CNT==0? 中断/PWM/触发2
3
- 时钟源:来自系统时钟 MCLK(如 32MHz),提供基础计数节拍
- 预分频器:把高频时钟降速。比如 32MHz ÷ 32 = 1MHz,换算为时间就是每 1μs 计数器减 1
- 计数器(CNT):从重载值(Period)开始递减,每个时钟节拍减 1
- 零检测:当计数器递减到 0 时,产生零检测信号
- 事件输出:零检测信号可触发中断、翻转引脚、启动 ADC 等操作
TIMG12 说明:本章实验使用的 TIMG12 是 32-bit 定时器,没有预分频器。流水线中的预分频器环节在 TIMG12 上被跳过,时钟源直接 32MHz 驱动计数器。本章在 6.1 中用含预分频器的流水线帮助理解通用概念,实际到 6.3 配置时会看到 TIMG12 的参数表中 Prescaler 不可用。

定时时间计算
定时时间 = (重载值 + 1) × 预分频值 / 时钟源频率
定时时间 = (重载值 + 1) / (时钟源频率 / 分频比)2
(即:预分频值 = SysConfig 中的 Prescaler 参数 = 分频比)
对于 TIMG12(无预分频器),预分频值视为 1,公式简化为
定时时间 = (重载值 + 1) / 时钟源频率。具体计算示例见 6.3.2 节。
例如:时钟源 32MHz,预分频 32,重载值 999:
定时时间 = (999 + 1) × 32 / 32,000,000 = 1ms
+1是因为计数器从 Period 值递减到 0 共经历 Period+1 个时钟节拍(计数值 0 也占用一个节拍)。
计数方向与中断事件
MSPM0 的 TIMG 通用定时器支持多种计数模式,其中 SysConfig "Periodic" 模式默认采用向下计数:计数器从 Period 值递减到 0,触发 DL_TIMER_IIDX_ZERO 中断事件,然后自动重载。因此,一个周期经历的节拍数为 Period + 1(计数值 0 也占用一个时钟节拍)。
本实验使用的就是向下计数模式,后面的代码中中断标志为 DL_TIMER_IIDX_ZERO(计数归零事件)。
6.2 MSPM0G3519 定时器资源
MSPM0G3519 拥有多个定时器,分为两大类:
通用定时器(TIMG)
| 定时器 | 位宽 | 捕获/比较通道 | 典型用途 |
|---|---|---|---|
| TIMG0 | 16-bit | 2 通道 | 周期定时、基础 PWM |
| TIMG6 | 16-bit | 2 通道 | 周期定时、PWM(蜂鸣器) |
| TIMG7 | 16-bit | 2 通道 | 周期定时、PWM |
| TIMG8 | 16-bit | 2 通道 | 周期定时、输入捕获、QEI(正交编码器接口,用于读取旋转编码器) |
| TIMG9 | 16-bit | 2 通道 | 周期定时、输入捕获、QEI(正交编码器接口,用于读取旋转编码器) |
| TIMG12 | 32-bit | 2 通道 | 周期定时 |
| TIMG14 | 16-bit | 4 通道 | 长周期定时、输入捕获 |
高级控制定时器(TIMA)
| 定时器 | 位宽 | 捕获/比较通道 | 典型用途 |
|---|---|---|---|
| TIMA0 | 16-bit | 4 通道 | PWM(带死区)、电机控制 |
| TIMA1 | 16-bit | 2 通道 | PWM(WS2812,一种可编程 RGB LED 协议)、编码器 |
主要区别:
- TIMG:通用定时器,支持基本定时、PWM 输出、输入捕获
- TIMA:高级定时器,在 TIMG 基础上增加死区插入、故障检测、互补输出、正交编码器接口等功能
天巧星板载占用
- TIMA1 CCP0(PB26)— WS2812 RGB LED
- TIMG6 CCP1(PB27)— 蜂鸣器 PWM
- TIMG8 CCP0/CCP1(PA29/PA30)— 旋转编码器 QEI
选择定时器时注意避开已被板载外设占用的通道。
6.3 SysConfig 配置定时器周期中断
本实验使用 TIMG12(32-bit)配置 500ms 周期中断,在中断中翻转 LED。
6.3.1 添加定时器
- 打开
.syscfg文件 - 左侧面板找到 TIMER 选项卡
- 点击 ADD 添加一个 Timer 实例
点击查看截图 — 添加 Timer 实例

6.3.2 配置参数
| 参数 | 设置值 | 说明 |
|---|---|---|
| Name | TIMER_0 | 实例名称 |
| Selected Peripheral | TIMG12 | 选择 TIMG12 |
| Timer Mode | Periodic Down Counting | 周期向下计数模式(自动重载) |
| Timer Clock Source | BUSCLK | 使用总线时钟(32MHz) |
| Timer Clock Prescaler | 不设置(TIMG12 无预分频器) | 直接使用 32MHz 时钟 |
| Desired Timer Period | 500 ms | 定时周期 500ms(SysConfig 自动计算重载值) |
| Enable Interrupts | 勾选 Zero event | 使能零检测中断(定时周期到达时触发) |

定时时间计算验证
- 时钟源:BUSCLK = 32MHz
- TIMG12 无预分频器,计数频率 = 32MHz(每计一次 = 31.25ns)
- SysConfig 支持直接填写时间字符串(如
500 ms),会自动根据时钟频率计算重载值 - 重载值 = (32,000,000 × 0.5) - 1 = 15,999,999,与实际生成的
TIMER_0_INST_LOAD_VALUE(15999999U)一致 ✓
如果需要 1 秒定时:将 Desired Timer Period 改为 1 s。 如果需要 100ms 定时:将 Desired Timer Period 改为 100 ms。
6.3.3 同时配置 LED GPIO
确保 .syscfg 中已配置 PB22 GPIO 输出(参照第 2 章 LED 配置)。
保存并编译。
6.4 定时器中断实验
6.4.1 LED 定时翻转
#include "ti_msp_dl_config.h"
int main(void)
{
SYSCFG_DL_init();
// 使能 TIMG12 的 NVIC 中断(NVIC 概念参见第 4 章)
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
// 启动定时器
DL_TimerG_startCounter(TIMER_0_INST);
while (1) {
// 主循环空闲,LED 翻转由定时器中断完成
__WFI();
}
}
// TIMG12 中断服务函数(函数名由启动文件中的中断向量表定义,必须完全匹配,不可自行修改)
void TIMG12_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMER_IIDX_ZERO:
// 定时器计数到零(周期到达),翻转 LED
DL_GPIO_togglePins(GPIO_LED_PORT, GPIO_LED_PIN_PIN);
break;
default:
break;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
代码说明
DL_TimerG_startCounter(TIMER_0_INST)
启动定时器计数。SysConfig 的 SYSCFG_DL_init() 只完成定时器的参数配置,并不会自动启动计数,需要手动调用 start 函数。
DL_TIMER_IIDX_ZERO
定时器向下计数到零时的中断标志。在周期模式下,计数器到零后自动重载继续计数,每个周期触发一次此中断。
注意:
DL_TimerG_getPendingInterrupt()在返回中断类型的同时会自动清除该中断标志,因此不需要手动清零。
TIMG12_IRQHandler
TIMG12 的中断服务函数名(固定,不可修改)。SysConfig 生成的宏 TIMER_0_INST_INT_IRQN 对应此中断号。
__WFI()(Wait For Interrupt)
让 CPU 进入低功耗休眠状态,等待任意中断唤醒后继续执行。中断触发后 CPU 自动唤醒、执行 ISR,然后回到 __WFI() 之后继续主循环(即再次进入休眠)。与空 while(1){} 不同,__WFI() 可以降低功耗。
6.4.2 编译下载验证
编译下载后,LED 以精确的 500ms 间隔闪烁——不受主循环中其他代码的影响。即使在 while(1) 中加入其他耗时操作,LED 闪烁频率不变。

6.5 定时器驱动多任务
定时器中断的真正价值在于:CPU 不被延时阻塞,可以同时处理多个任务。
前置条件
本示例需要先完成第 5 章串口配置,确保 UART_0_INST 等宏已由 SysConfig 生成。
以下示例展示:定时器中断翻转 LED,主循环同时通过串口发送数据。
#include "ti_msp_dl_config.h"
#include <stdio.h>
volatile uint32_t tick_count = 0; // 中断计数器
// volatile 告诉编译器:此变量可能被中断改变,不优化读写
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
int main(void)
{
SYSCFG_DL_init();
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
NVIC_EnableIRQ(UART_0_INT_IRQN);
DL_TimerG_startCounter(TIMER_0_INST);
char buf[32];
while (1) {
// 主循环每 2 秒通过串口报告定时器中断次数
sprintf(buf, "Tick: %lu\r\n", tick_count);
uart_send_string(buf);
delay_cycles(64000000); // 主循环每隔约 2 秒通过串口报告一次
// 计算:32MHz 系统时钟,每周期 31.25ns
// 64000000 × 31.25ns = 2s
// 注意:此处 delay_cycles 仅用于主循环的简单延时演示,
// LED 闪烁由硬件定时器独立控制,不受此延时影响
// 更规范的做法是用另一个定时器管理发送周期,完全消除阻塞延时。
}
}
void TIMG12_IRQHandler(void)
{
switch (DL_TimerG_getPendingInterrupt(TIMER_0_INST)) {
case DL_TIMER_IIDX_ZERO:
DL_GPIO_togglePins(GPIO_LED_PORT, GPIO_LED_PIN_PIN);
tick_count++;
break;
default:
break;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
运行结果:LED 精确 500ms 闪烁不受影响,同时串口每 2 秒输出一次当前 tick 计数值。


6.6 定时器常用函数
| 函数 | 功能 |
|---|---|
DL_TimerG_startCounter(inst) | 启动定时器计数 |
DL_TimerG_stopCounter(inst) | 停止定时器计数 |
DL_TimerG_getTimerCount(inst) | 读取当前计数值 |
DL_TimerG_getPendingInterrupt(inst) | 获取挂起中断类型并清除标志 |
DL_TimerG_setTimerCount(inst, val) | 设置计数器初始值 |
注意事项
- 定时器启动前确保
SYSCFG_DL_init()已执行完成 - 修改定时器参数(周期、预分频)应在停止状态下进行
- 16-bit 定时器最大计数值为 65535,如果预分频后仍不够长,使用 32-bit 的 TIMG12(最大计数 4294967295)
volatile修饰 ISR 中修改的全局变量
6.7 定时周期快速参考
以下为 32MHz 时钟源、不同预分频下可实现的定时范围(16-bit 定时器,TIMG12 除外):
理论最小定时值仅供参考,实际可用定时受中断响应时间限制,建议不低于数微秒。
| 预分频 | 计数频率 | 最小定时 | 最大定时 |
|---|---|---|---|
| 1 | 32MHz | 31.25ns | 2.048ms |
| 8 | 4MHz | 250ns | 16.384ms |
| 32 | 1MHz | 1μs | 65.536ms |
| 128 | 250kHz | 4μs | 262.144ms |
| 256 | 125kHz | 8μs | 524.288ms |
| 无(TIMG12) | 32MHz | 31.25ns | 约 134 秒 |
TIMG12 为 32-bit 定时器,无预分频器,直接使用 32MHz 时钟源计数,重载值为 4294967295 时最大定时约 134 秒。
如果需要更长的定时(如 1 秒),有两种方式:
- 使用 32-bit 通用定时器 TIMG12(最大计数 4294967295,最大定时约 134 秒)
- 在中断中用软件计数器累加(如 500ms 中断 × 2 次 = 1 秒)
6.8 常见踩坑记录
| 现象 | 原因 | 解决 |
|---|---|---|
| LED 不闪,程序像卡死 | 忘了调用 DL_TimerG_startCounter() | 在 NVIC_EnableIRQ 后加启动 |
| LED 不闪,但程序在跑 | 忘了 NVIC_EnableIRQ() | 检查 main 中是否使能了中断 |
| 编译报错 "IRQHandler 未定义" | 中断函数名拼写错误 | 严格核对函数名大小写和数字 |
tick_count 在主循环中始终为 0 | 没加 volatile | 加 volatile 修饰 |
| 编译通过但定时不对 | Desired Timer Period 超过定时器位宽(16-bit 最大 65535) | 换 32-bit 定时器或增大预分频 |
掌握了定时器后,你已经拥有了精确的时间控制能力。下一章将学习 PWM 输出,利用定时器的捕获/比较功能生成可控的方波信号。