嵌入式开发原创教程嵌入式入门 03
定时器与中断:用 1ms 节拍做一个不阻塞的呼吸灯
配置 TIM2 产生 1ms 中断,建立系统时间戳,并用主循环处理 LED 状态,避免 delay 阻塞。

实验目标
- 配置一个 1ms 硬件定时器;
- 在中断中只更新计数和标志;
- 在主循环中执行 LED 动作;
- 理解“中断负责计时,主循环负责业务”。
一、配置 TIM2
以 72MHz 时钟为例:
预分频 PSC = 71
自动重装 ARR = 999
计数频率 = 72MHz / (71 + 1) / (999 + 1) = 1kHz
中断周期 = 1ms在 CubeMX 中:
- 打开 TIM2;
- Clock Source 选择 Internal Clock;
- Prescaler 填 71;
- Counter Period 填 999;
- NVIC 中勾选 TIM2 global interrupt;
- 生成代码。
二、加入定时器代码
/* USER CODE BEGIN PV */
volatile uint32_t g_ms = 0U;
volatile uint8_t g_led_toggle = 0U;
/* USER CODE END PV */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
g_ms++;
if ((g_ms % 500U) == 0U)
{
g_led_toggle = 1U;
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
HAL_TIM_Base_Start_IT(&htim2);
while (1)
{
if (g_led_toggle)
{
g_led_toggle = 0U;
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
}注意:
- 中断服务函数中不要调用 HAL_Delay;
- 不要做串口打印和 Flash 写入;
- 共享变量用 volatile 告诉编译器它可能在中断中改变;
- 如果变量超过 32 位或涉及多步读写,需要考虑临界区。
三、为什么主循环没有 delay
主循环只检查一个标志。即使以后加入按键扫描、串口解析和传感器采集,也不会被 500ms 的 delay 卡住。
四、验证
- 下载程序;
- LED 应该每 500ms 翻转一次;
- 用调试器观察 g_ms 是否持续增加;
- 在 while 循环中加入一个计数变量,确认主循环仍在运行。
五、常见错误
中断进不去
检查 NVIC 是否勾选、是否调用了 HAL_TIM_Base_Start_IT,以及中断优先级是否合理。
LED 闪烁频率不对
重新计算 PSC 和 ARR,确认系统时钟不是 8MHz 或 64MHz。
程序卡死
检查是否在中断中调用了阻塞函数,或中断频率过高导致主循环得不到执行。
参考资料:


