大花科技
嵌入式开发原创教程嵌入式入门 03

定时器与中断:用 1ms 节拍做一个不阻塞的呼吸灯

配置 TIM2 产生 1ms 中断,建立系统时间戳,并用主循环处理 LED 状态,避免 delay 阻塞。

定时器与中断:用 1ms 节拍做一个不阻塞的呼吸灯

实验目标

  • 配置一个 1ms 硬件定时器;
  • 在中断中只更新计数和标志;
  • 在主循环中执行 LED 动作;
  • 理解“中断负责计时,主循环负责业务”。

一、配置 TIM2

以 72MHz 时钟为例:

textcode
预分频 PSC = 71
自动重装 ARR = 999
计数频率 = 72MHz / (71 + 1) / (999 + 1) = 1kHz
中断周期 = 1ms

在 CubeMX 中:

  1. 打开 TIM2;
  2. Clock Source 选择 Internal Clock;
  3. Prescaler 填 71;
  4. Counter Period 填 999;
  5. NVIC 中勾选 TIM2 global interrupt;
  6. 生成代码。

二、加入定时器代码

ccode
/* 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 卡住。

四、验证

  1. 下载程序;
  2. LED 应该每 500ms 翻转一次;
  3. 用调试器观察 g_ms 是否持续增加;
  4. 在 while 循环中加入一个计数变量,确认主循环仍在运行。

五、常见错误

中断进不去

检查 NVIC 是否勾选、是否调用了 HAL_TIM_Base_Start_IT,以及中断优先级是否合理。

LED 闪烁频率不对

重新计算 PSC 和 ARR,确认系统时钟不是 8MHz 或 64MHz。

程序卡死

检查是否在中断中调用了阻塞函数,或中断频率过高导致主循环得不到执行。

参考资料: