使用定时器 2 进行中断点灯,500ms LED 灯翻转一次
一、配置步骤(思路→落地)
-
系统时钟到 72 MHz(HSE+PLL x9)
-
常见设置:
AHB=72 MHz
,APB1=36 MHz (分频2)
,APB2=72 MHz (分频1)
。 -
F1 定时器"×2 规则" :当 APB prescaler ≠ 1 时,定时器时钟 = 2 × PCLK。
-
所以在
APB1=36 MHz、分频2
时,TIM2CLK = 2 × 36 MHz = 72 MHz(与你的 Ft=72M 一致)。
-
-
定时参数计算

→ 每 500 ms 产生一次更新中断。
3.HAL 初始化 TIM2
-
使能
TIM2
时钟,配置Prescaler=7199
、Period=4999
、向上计数。 -
使能更新中断、配置 NVIC(
TIM2_IRQn
)。 -
HAL_TIM_Base_Start_IT(&htim2)
启动中断。
4.编写回调
在 HAL_TIM_PeriodElapsedCallback()
里判断 htim->Instance == TIM2
,做你的事情(例如翻转 PC13 LED)。
5.中断向量
在 TIM2_IRQHandler()
中调用 HAL_TIM_IRQHandler(&htim2)
。
timer.c:
cpp
/******************** timer.c(HAL 版本,行级详细注释) ********************/
#include "timer.h" // 定时器对外接口与 TIM_HandleTypeDef 声明
#include "led.h" // LED 翻转函数(中断回调里用)
/* 全局定时器句柄
* 作用:承载 TIM2 的寄存器基址和初始化配置;HAL 的所有 TIM API 都围绕这个句柄工作
*/
TIM_HandleTypeDef timer_handle = {0};
/**
* @brief 基本定时器初始化 + 启动更新中断
* @param arr 自动重装载值(ARR),计数到 arr 后产生更新事件;范围 0~65535(16 位定时器)
* @param psc 预分频值(PSC),计数时钟 = TIMxCLK / (psc + 1);范围 0~65535
* @note 更新中断频率公式:
* f_update = TIMxCLK / ((psc + 1) * (arr + 1))
* 对于 F103,若 APB 分频!=1,则 TIMxCLK = 2 * PCLKx(F1"×2"规则)
* 例:72MHz、psc=7199、arr=4999 → f_update=2Hz(每500ms一次中断)
*/
void timer_init(uint16_t arr, uint16_t psc)
{
timer_handle.Instance = TIM2; // 绑定底层硬件实例:TIM2(F103C8 可用 TIM1~TIM4)
timer_handle.Init.Prescaler = psc; // 设定预分频,影响计数时钟(越大越慢)
timer_handle.Init.Period = arr; // 设定自动重装载值(计到此值产生更新事件)
timer_handle.Init.CounterMode = TIM_COUNTERMODE_UP; // 计数方向:向上计数
timer_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // ARR 立即生效(不缓存)
HAL_TIM_Base_Init(&timer_handle); // 调 HAL 完成底层寄存器初始化(会回调 MSP)
HAL_TIM_Base_Start_IT(&timer_handle); // 以"中断模式"启动定时器(开启更新中断)
}
/**
* @brief HAL 的 MSP 初始化回调(由 HAL_TIM_Base_Init() 触发)
* @param htim 指向当前要初始化的定时器句柄
* @note 这里完成与外设相关的"板级"初始化:开时钟、配置 NVIC 等
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2) // 仅处理 TIM2,其他定时器可在此追加分支
{
__HAL_RCC_TIM2_CLK_ENABLE(); // ① 打开 TIM2 外设时钟(APB1)
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 2); // ② 配置 NVIC 优先级(抢占2,响应2,取值范围视分组而定)
HAL_NVIC_EnableIRQ(TIM2_IRQn); // ③ 使能 TIM2 中断线
}
}
/**
* @brief TIM2 的中断服务函数(由启动文件向量表调用)
* @note 在 HAL 中断框架下,需要把中断分发给 HAL_TIM_IRQHandler() 做标志判断和回调触发
*/
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&timer_handle); // 统一的 HAL 中断分发入口(会调用 PeriodElapsedCallback)
}
/**
* @brief 更新事件回调(UPD:计数溢出/重装载触发)
* @param htim 触发回调的定时器句柄
* @note 推荐在回调里只做"快小活"(如置标志、翻转管脚),避免长阻塞影响中断响应
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2) // 确认来源是 TIM2(工程里可能有多路 TIM)
{
led1_toggle(); // 例:每次更新事件翻转 LED1(500ms 一次)
}
}
main.c:
cpp
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
int main(void)
{
HAL_Init(); /* 初始化HAL库 */
stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
led_init(); /* 初始化LED灯 */
timer_init(5000 - 1, 7200 - 1);
while(1)
{
}
}
参数选择:
1)timer_handle.Instance = TIM2;
-
可选实例(F103C8 常见) :
TIM1
(高级定时器,APB2),TIM2
、TIM3
、TIM4
(通用定时器,APB1)。 -
含义 :告诉 HAL 句柄要操作哪一个定时器外设。你也可以改为
TIM3
/TIM4
,其余代码保持一致,只需改 NVIC 的中断号。
2)timer_handle.Init.Prescaler = psc;
(uint16_t
,0~65535)
-
可选任意数值 ,影响计数时钟:
CK_CNT = TIMxCLK / (PSC + 1)
。 -
建议 :选择能让
ARR
落在合适范围(不太大也不太小),以兼顾分辨率与溢出周期。
3)timer_handle.Init.Period = arr;
(uint16_t
,0~65535)
-
可选任意数值 ,影响更新事件周期:
T_update = (ARR + 1) / CK_CNT
。 -
注意:若需要更高计数范围,可用 32 位的 TIM(F1 上 TIM2/5 为 32 位,但 F103C8 没 TIM5;TIM2 在大多数包络是 32 位,Cube/HAL 会按芯片裁剪,通常仍当 32 位用,但保险起见按 16 位配置即可)。
4)timer_handle.Init.CounterMode = TIM_COUNTERMODE_UP;
-
可选值:
-
TIM_COUNTERMODE_UP
(向上计数) -
TIM_COUNTERMODE_DOWN
(向下计数) -
TIM_COUNTERMODE_CENTERALIGNED1/2/3
(中心对齐模式 1/2/3,用于 PWM 等对称场景)
-
-
含义:决定 CNT 的计数方式与更新事件时机(中心对齐模式会影响更新与比较事件的时序)。
5)timer_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
-
可选值:
-
TIM_AUTORELOAD_PRELOAD_DISABLE
(禁用预装载,写 ARR 立即生效) -
TIM_AUTORELOAD_PRELOAD_ENABLE
(使能预装载,写 ARR 先进缓冲,待下次更新事件才生效)
-
-
含义:是否把 ARR 放到缓冲寄存器,同步在更新事件时加载,常用于 PWM 周期更新避免"中途撕裂"。
说明:
timer_handle.Init.ClockDivision
在你的代码里没设,默认DIV1
。如需要可加:
TIM_CLOCKDIVISION_DIV1 / DIV2 / DIV4
含义 :数字滤波分频(对死区/滤波有影响),基本定时用途通常保持DIV1
。
6)HAL_TIM_Base_Start_IT(&timer_handle);
-
含义:以"中断模式"启动定时器(使能更新中断并开始计数)。
-
对应停止 :
HAL_TIM_Base_Stop_IT(&timer_handle);
。 -
非中断模式 :
HAL_TIM_Base_Start()
/HAL_TIM_Base_Stop()
(不触发回调)。
7)HAL_TIM_Base_MspInit()
(HAL 的"MSP 回调")
-
__HAL_RCC_TIM2_CLK_ENABLE();
:打开 TIM2 外设时钟。 -
HAL_NVIC_SetPriority(TIM2_IRQn, 2, 2);
-
第2个参数 = 抢占优先级,越小越高;
-
第3个参数 = 子优先级,越小越高。
-
可选范围 取决于优先级分组(
HAL_NVIC_SetPriorityGrouping()
或系统默认分组)。常见Group 2
时两者范围均 0~3。
-
-
HAL_NVIC_EnableIRQ(TIM2_IRQn);
:使能该中断线。 -
可选中断号 :
TIM1_UP_IRQn
(TIM1 溢出/更新时间),TIM2_IRQn
,TIM3_IRQn
,TIM4_IRQn
等,视你使用的定时器而定。
8)TIM2_IRQHandler()
-
固定写法 :在具体的中断向量里调用
HAL_TIM_IRQHandler(&句柄)
,由 HAL 统一完成更新标志判断与回调触发。 -
如果你改为 TIM3 :向量函数要对应
void TIM3_IRQHandler(void)
,并调用HAL_TIM_IRQHandler(&timer_handle)
。
9)HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
-
回调来源判断 :
if (htim->Instance == TIM2)
;多定时器共用一个回调时务必判断来源。 -
可在此做的事 :翻转 IO、投递事件标志、给队列/信号量(RTOS)等快小活;不要长期阻塞(例如长延时、长串口打印等)。
小注:500ms 的常用参数(72MHz、APB1÷2 → TIM2CLK=72MHz)
-
PSC = 7199
,ARR = 4999
→CK_CNT = 72MHz/(7199+1)=10kHz
,T = (4999+1)/10k = 0.5s
-
若要 1ms 中断:
PSC=71
、ARR=999
(10kHz→此处应为 1kHz?更标准:PSC=71
得 1MHz,再ARR=999
得 1kHz→1ms) -
若要 100us 中断:
PSC=71
、ARR=99
(1MHz/100 → 10kHz→100µs)
温馨提示:F1 的"×2 规则"很关键------若
APB1
分频不是 1,则 TIM2/3/4 的时钟 =2 * PCLK1
。确保系统时钟树配置与计算一致,否则节拍会偏。