一、STM32 通用定时器是什么?

STM32F103 内部的 TIM2、TIM3、TIM4、TIM5 都属于 通用定时器。它们的核心功能:

  • 定时
  • 中断
  • PWM 输出
  • 输入捕获
  • 输出比较

本篇我们使用最基础、最常用的 定时中断功能

二、通用定时器中断工作原理

  1. 定时器有一个 计数器,从 0 开始向上数
  2. 系统时钟 72MHz 太快,需要 分频(Prescaler) 降速
  3. 计数器数到 自动重装载值(Period) 时,产生 溢出
  4. 溢出后触发 更新中断(Update Interrupt)
  5. CPU 立刻响应,进入 中断服务函数 执行任务
  6. 执行完后,计数器自动清零,重新开始计数

一句话总结:分频 + 重装载值 → 决定多少秒进一次中断。

三、定时时间计算公式

定时时间(秒)= [( 分频系数 + 1 ) × ( 自动重装载值 + 1 )] / 定时器时钟

STM32F103 定时器时钟 = 72MHz

四、tim.c(定时器核心文件)

#include "tim.h"
#include "stm32f10x.h"

void Base_TIM_Init(void)
{

1. 定义结构体

TIM_TimeBaseInitTypeDef TIM_TimeInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
  • TIM_TimeBaseInitTypeDef:定时器基本配置结构体
  • NVIC_InitTypeDef:中断优先级配置结构体

2. 开定时器时钟(必须第一步)

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
  • TIM2 挂载在 APB1 总线上
  • 任何外设必须先开时钟才能工作
  • 不开时钟 → 定时器完全不工作

3. 配置定时器基本参数

TIM_TimeInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
  • 时钟分频:不分频
  • 一般默认写 TIM_CKD_DIV1 即可
TIM_TimeInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
  • 计数器模式:向上计数模式
  • 从 0 → Period 往上数,数满溢出
TIM_TimeInitStruct.TIM_Prescaler = 10000;

分频系数

  • 把 72MHz 时钟进行 10001 分频
  • 分频后计数器时钟 = 72MHz / 10001 ≈ 7199Hz
TIM_TimeInitStruct.TIM_Period = 7200;

自动重装载值

  • 计数器从 0 数到 7200 溢出
  • 溢出触发中断
TIM_TimeInitStruct.TIM_RepetitionCounter = 0;
  • 高级定时器用
  • 通用定时器写 0

⭐ 本代码的定时时间计算

(10000 + 1) × (7200 + 1) / 72000000 ≈ 1 秒

结论:每 1 秒进入一次定时器中断。

4. 初始化定时器

TIM_TimeBaseInit(TIM2, &TIM_TimeInitStruct);

把参数写入 TIM2 寄存器。

5. 开启更新中断

TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
  • 开启定时器溢出中断
  • 计数器数满 → 触发中断

6. 启动定时器

TIM_Cmd(TIM2, ENABLE);
  • 启动后,定时器才真正开始计数
  • 不写这句,定时器永远不工作

7. NVIC 中断优先级配置

NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;

指定中断通道为 TIM2 中断。

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

使能中断。

NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;

设置优先级:

  • 抢占优先级 0
  • 子优先级 0
NVIC_Init(&NVIC_InitStruct);

将优先级配置写入寄存器。

四、逐行详细讲解:main.c

int main()
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

中断分组必须放在最前面

  • 整个芯片中断规则
  • 只能设置一次
    LED_Init();
    Base_TIM_Init();

初始化 LED 和定时器。

    while(1)
    {

    }
}

主循环什么都不做,完全交给中断控制

五、中断服务函数详细讲解

void TIM2_IRQHandler(void)
{
    if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {

判断是否为定时器更新中断

  • 防止进错中断
  • 标准写法
        GPIO_WriteBit(GPIOA, GPIO_Pin_1, (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1)));

翻转 LED 电平

  • 原来 0 → 变 1
  • 原来 1 → 变 0
  • 实现闪烁效果
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

清除中断标志位(必须写)

  • 不清除 → 程序卡死在中断里

六、最终总结

  1. 你的定时器配置:每 1 秒进一次中断
  2. 中断里只做一件事:翻转 LED(快进快出)
  3. 代码现在 100% 正确,烧录直接亮灯闪烁!

七、你现在烧录进去会看到的现象

LED (PA1) :亮 1 秒 → 灭 1 秒 → 亮 1 秒 → 灭 1 秒…… 无限循环

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐