本篇总结
使用 TIM3 时基模式,定时触发更新中断。
🔑 KEIL MDK、UART调试、TIM
本示例演示 W55MH32L 的定时器(TIM)外设的基本用法:将 TIM3 配置为基本时基模式,设置预分频器与自动重装载值使更新事件每 10ms 产生一次,使能更新中断,在中断服务函数中打印函数名以验证定时周期。
TIM 简介
W55MH32 的定时器分为三类:高级控制定时器(TIM1/TIM8,支持互补 PWM、死区插入)、通用定时器(TIM2-5/TIM9-14,支持输入捕获、输出比较、PWM)、基本定时器(TIM6/TIM7,仅时基功能)。三者核心都是 16 位计数器 + 预分频器,基本定时器最简单,通用与高级定时器在其基础上扩展了通道逻辑。
| 定时器 | 分辨率 | 计数模式 | 预分频 | DMA 请求 | 通道数 | 互补输出 |
| TIM1、TIM8 | 16位 | 上/下/上下 | 1~65536 | 可以 | 4 | 有 |
| TIM2、3、4、5 | 16位 | 上/下/上下 | 1~65536 | 可以 | 4 | 没有 |
| TIM9、TIM12 | 16位 | 向上 | 1~65536 | 不可以 | 2 | 没有 |
| TIM10、11、13、14 | 16位 | 向上 | 1~65536 | 不可以 | 1 | 没有 |
| TIM6、TIM7 | 16位 | 向上 | 1~65536 | 可以 | 0 | 没有 |
本例程使用 TIM3(通用定时器)配置为基本时基模式:仅使用计数器 + 预分频器 + 自动重装载值,不涉及捕获/比较通道,等同于基本定时器的用法。时基核心公式:更新周期 = (PSC+1) × (ARR+1) / TIMxCLK。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口日志观察工具:点击下载
硬件连接
本例程为纯内部定时器演示,无需外部连线。定时器更新中断在芯片内部触发,中断服务函数通过 USART1(PA9/PA10)打印日志。仅需一根 Micro USB 线连接开发板 DEBUG USB 口到 PC 即可观察串口输出。
| 引脚 | 功能 | 方向 | 模式 |
| PA9 | USART1_TX | 开发板 → PC | 复用推挽输出 (AF_PP) |
| PA10 | USART1_RX | PC → 开发板 | 浮空输入 (IN_FLOATING) |
工程结构
工程位于 SDK 内 1.SDK\ModuleDemo\TIM\TIM_Basic\,核心文件如下:
TIM_Basic/
├── USER/
│ ├── main.c ← TIM初始化 + 中断服务函数 + printf 重定向 + 主流程
│ ├── w55mh32_conf.h ← 外设头文件集中包含
│ ├── w55mh32_it.c/h ← 中断服务函数(系统异常部分)
│ ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│ └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
└── delay/
└── delay.c/h ← SysTick 延时函数
步骤1:TIM 定时器初始化(含 NVIC 中断配置)
文件:USER/main.c | 函数:TIM_Configuration() — 配置 TIM3 时基、使能更新中断、配置 NVIC 优先级并启动定时器。
void TIM_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 开启 TIM3 时钟(TIM3 挂在 APB1 上) */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
/* 时基配置:预分频使 CK_CNT = 1MHz,周期 10000 计数 = 10ms */
TIM_TimeBaseStructure.TIM_Period = 9999; // 自动重装载值 ARR
TIM_TimeBaseStructure.TIM_Prescaler = clocks.SYSCLK_Frequency / 1000000 - 1; // 预分频 PSC,SYSCLK/1M-1
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频(基本定时器不用)
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
/* 使能 TIM3 更新中断 */
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
/* NVIC 中断优先级配置 */
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; // TIM3 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; // 子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStructure);
/* 启动定时器 */
TIM_Cmd(TIM3, ENABLE);
}
定时周期计算:CK_CNT = SYSCLK / (PSC+1) = 72MHz / 72 = 1MHz,更新事件周期 = (ARR+1) / CK_CNT = 10000 / 1MHz = 10ms。五步完成:开时钟 → 时基参数配置 → 使能更新中断 → NVIC 优先级配置 → 启动定时器。
如果你想自定义
- 改定时周期 → 修改
TIM_Period(ARR)和 TIM_Prescaler(PSC)。如要 1 秒中断:PSC = 7199(10kHz)、ARR = 9999
- 换用 TIM6/TIM7(基本定时器) → 修改
RCC_APB1Periph_TIM6、TIM6、TIM6_IRQn,并去掉 CounterMode(TIM6 仅向上计数)
- 改中断优先级 → 修改
NVIC_IRQChannelPreemptionPriority(0~15,数值越小优先级越高)
步骤2:中断服务函数
函数:TIM3_IRQHandler() — TIM3 更新中断触发时进入,先检查中断标志,再清除标志,最后打印函数名。
/* TIM3 中断服务函数 */
void TIM3_IRQHandler(void)
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) /* 检查是否发生更新中断 */
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); /* 清除更新中断标志 */
printf("%s\n", __FUNCTION__); /* 打印中断函数名 */
}
}
中断服务函数固定命名 TIM3_IRQHandler,与启动文件中的中断向量表一致。必须先清除中断标志再执行业务逻辑,否则会反复进入中断导致主循环无法执行。
注意
在中断中调用 printf 仅供演示。实际项目中 printf 耗时较长(尤其高波特率下),高频中断会严重影响实时性。建议中断中只置标志位,在主循环中处理打印。
步骤3:主函数逻辑
函数:main() — 初始化延时和 UART(115200) → 打印时钟频率 → 配置 TIM3 → 进入空循环等待中断。
RCC_ClocksTypeDef clocks;
int main(void)
{
delay_init();
UART_Configuration(115200);
RCC_GetClocksFreq(&clocks);
printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
"PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
(float)clocks.SYSCLK_Frequency / 1000000,
(float)clocks.HCLK_Frequency / 1000000,
(float)clocks.PCLK1_Frequency / 1000000,
(float)clocks.PCLK2_Frequency / 1000000,
(float)clocks.ADCCLK_Frequency / 1000000);
printf("TIM Basic Test.\n");
TIM_Configuration();
while (1);
}
主循环为空 while(1),所有工作由 TIM3 更新中断驱动。clocks 为全局变量,供 TIM_Configuration() 计算 PSC 时引用 SYSCLK 频率。
步骤4:printf 重定向
重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。
int SER_PutChar(int ch)
{
while (!USART_GetFlagStatus(USART_TEST, USART_FLAG_TC));
USART_SendData(USART_TEST, (uint8_t)ch);
return ch;
}
int fputc(int c, FILE *f)
{
if (c == '\n')
SER_PutChar('\r'); // 补回车,确保串口工具正确换行
return (SER_PutChar(c));
}
编译与下载
Keil MDK 中打开 TIM_Basic.uvprojx,F7 编译,确认 0 Error(s)。连接 DEBUG USB 口到 PC,F8 下载。下载成功后按 RESET 键启动新固件。
运行验证
打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。按 RESET 启动后,预期串口先打印时钟信息与 "TIM Basic Test.",随后每 10ms 打印一行 "TIM3_IRQHandler"。
SYSCLK: 72.0Mhz, HCLK: 72.0Mhz, PCLK1: 36.0Mhz, PCLK2: 72.0Mhz, ADCCLK: 36.0Mhz
TIM Basic Test.
TIM3_IRQHandler
TIM3_IRQHandler
TIM3_IRQHandler
TIM3_IRQHandler
...
看到 "TIM3_IRQHandler" 持续打印即说明定时器更新中断按预期每 10ms 触发一次。
常见问题
Q: 编译报错 "cannot open source file w55mh32.h"
检查 Project → Options → C/C++ → Include Paths,确保 SDK 的 Libraries 目录已添加。
Q: 串口只打印了时钟信息和 "TIM Basic Test.",但没有 "TIM3_IRQHandler"
中断未触发。按顺序检查:TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE) 是否调用 → NVIC_Init 是否调用且 NVIC_IRQChannelCmd=ENABLE → TIM_Cmd(TIM3, ENABLE) 是否调用。三者缺一不可。
Q: 中断打印频率不对(不是 10ms 一次)
先看串口打印的 SYSCLK 频率是否正确(应为 72.0Mhz)。PSC = SYSCLK/1000000 - 1 是为了让 CK_CNT = 1MHz,若 SYSCLK 异常则 PSC 计算错误。也可直接写死 TIM_Prescaler = 71(72MHz 下)。
Q: 程序反复复位或串口输出卡死
中断服务函数中未清除中断标志(TIM_ClearITPendingBit),导致退出中断后立即再次进入,主循环无法执行。确认清除标志的语句在中断函数最前面。另外中断中 printf 耗时较长,若中断频率过高也会导致系统卡死。
Q: 换用 TIM6/TIM7 后编译报错或中断不触发
TIM6/TIM7 是基本定时器,无 TIM_CounterMode 成员(仅向上计数),需删除该行赋值。中断号需改为 TIM6_IRQn / TIM7_IRQn,中断函数名改为 TIM6_IRQHandler / TIM7_IRQHandler。
总结
本文基于 W55MH32L 的 TIM3 实现了基本定时器时基与更新中断:
- TIM3 配置为向上计数模式,PSC = SYSCLK/1M-1(CK_CNT=1MHz)、ARR = 9999,更新中断周期 10ms
- NVIC 配置 TIM3 中断通道(抢占优先级 0、子优先级 3),
TIM_ITConfig 使能更新中断
- 中断服务函数 TIM3_IRQHandler 中检查标志 → 清除标志 → 打印函数名
- 关键公式:更新周期 = (PSC+1) × (ARR+1) / TIMxCLK,掌握此公式即可配置任意定时周期