【十一】定时器中断实验(TIM1/2/3)
1. 项目概述
本项目演示如何使用 STM32F103C8T6 微控制器的3个定时器(TIM1、TIM2、TIM3)通过中断方式实现LED闪烁功能。系统使用定时器更新中断触发LED状态翻转,实现不同频率的LED闪烁效果,展示了多定时器协同工作和中断优先级管理。
技术要点
- ✅ TIM1高级定时器配置
- ✅ TIM2/TIM3通用定时器配置
- ✅ 定时器中断配置
- ✅ NVIC中断优先级管理
- ✅ 定时时间精确计算
- ✅ 中断服务函数编写
- ✅ LED状态控制
- ✅ 多定时器协同工作
应用场景
- ⏱️ 周期性任务执行
- ⏱️ LED闪烁与指示
- ⏱️ 定时采样
- ⏱️ 看门狗喂狗
- ⏱️ 软件定时器
- ⏱️ PWM波形生成
2. 硬件平台
主控芯片
- 型号: STM32F103C8T6
- 内核: ARM Cortex-M3
- 主频: 72 MHz
- Flash: 64 KB
- SRAM: 20 KB
- 定时器: TIM1-TIM4
外设资源
定时器配置
| 定时器 | 类型 | 定时时间 | 中断优先级 | 控制LED |
|---|---|---|---|---|
| TIM1 | 高级定时器 | 500ms | 抢占0/响应0 | LED6 |
| TIM2 | 通用定时器 | 500ms | 抢占1/响应0 | LED7 |
| TIM3 | 通用定时器 | 1000ms (1s) | 抢占2/响应0 | LED8 |
LED指示灯
| LED | 引脚 | 闪烁频率 | 定时器 |
|---|---|---|---|
| LED6 | PB5 | 1Hz (500ms开/500ms关) | TIM1 |
| LED7 | PB6 | 1Hz (500ms开/500ms关) | TIM2 |
| LED8 | PB7 | 0.5Hz (1s开/1s关) | TIM3 |
硬件连接
STM32F103C8T6 LED指示灯
PB5 ----------> LED6 (TIM1控制,500ms翻转)
PB6 ----------> LED7 (TIM2控制,500ms翻转)
PB7 ----------> LED8 (TIM3控制,1s翻转)
定时器工作示意图:
时间轴 →
0s 0.5s 1s 1.5s 2s 2.5s 3s
↓ ↓ ↓ ↓ ↓ ↓ ↓
TIM1 ─┐ ┌─ ┐ ┌─ ┐ ┌─ ┐
LED6 │中断 │中断 │中断 │中断 │中断 │中断 │中断
└──────┘ └──────┘ └──────┘
TIM2 ─┐ ┌─ ┐ ┌─ ┐ ┌─ ┐
LED7 │中断 │中断 │中断 │中断 │中断 │中断 │中断
└──────┘ └──────┘ └──────┘
TIM3 ─┐ ┌── ┐ ┌──
LED8 │中断 │中断 │中断 │中断
└─────────────┘ └─────────────┘
注意事项:
- TIM1是高级定时器,功能更丰富
- TIM2/TIM3是通用定时器
- 优先级:TIM1 > TIM2 > TIM3
- PB5-PB7需要禁用JTAG功能
3. 项目结构
6C_TIM1_2_3(中断)/
├── Libraries/ # STM32标准外设库
│ ├── CMSIS/ # Cortex微控制器软件接口标准
│ └── STM32F10x_StdPeriph_Driver/ # STM32F10x标准外设驱动
├── MDK/ # Keil工程文件
│ ├── TEST_CODE.uvproj # Keil项目文件
│ ├── TEST_CODE.uvopt # 项目配置选项
│ ├── Output/ # 编译输出目录
│ └── list/ # 列表文件目录
├── Readme/ # 说明文档目录
└── User/ # 用户代码目录
├── main.c # 主程序入口
├── stm32f10x_conf.h # 外设库配置文件
├── stm32f10x_it.c # 中断服务函数
├── stm32f10x_it.h # 中断服务函数头文件
└── TF_APP/ # 应用层驱动
├── TF_Timer.c # 定时器驱动实现
├── TF_Timer.h # 定时器驱动头文件
├── TF_LED.c # LED驱动实现
└── TF_LED.h # LED驱动头文件
4. 功能说明
系统工作流程
系统上电
↓
NVIC中断优先级配置
(TIM1=0, TIM2=1, TIM3=2)
↓
TIM1初始化(500ms定时)
├─ 使能TIM1时钟
├─ 配置预分频器:7200-1
├─ 配置自动重载值:5000-1
├─ 使能更新中断
└─ 启动TIM1
↓
TIM2初始化(500ms定时)
├─ 使能TIM2时钟
├─ 配置预分频器:7200-1
├─ 配置自动重载值:5000-1
├─ 使能更新中断
└─ 启动TIM2
↓
TIM3初始化(1s定时)
├─ 使能TIM3时钟
├─ 配置预分频器:7200-1
├─ 配置自动重载值:1000-1
├─ 使能更新中断
└─ 启动TIM3
↓
LED初始化
(PB5-PB7配置为推挽输出)
↓
主循环空转
(while(1);)
↓
等待定时器中断
├─ TIM1中断(500ms)
│ ├─ 清除中断标志
│ ├─ 翻转flag1标志
│ └─ 翻转LED6状态
│
├─ TIM2中断(500ms)
│ ├─ 清除中断标志
│ ├─ 翻转flag2标志
│ └─ 翻转LED7状态
│
└─ TIM3中断(1s)
├─ 清除中断标志
├─ 翻转flag3标志
└─ 翻转LED8状态
功能特点
-
多定时器独立工作:
- TIM1、TIM2、TIM3同时运行
- 互不干扰
- 各自控制LED
-
不同定时周期:
- TIM1/TIM2:500ms
- TIM3:1000ms
- 灵活配置
-
中断驱动:
- 主循环不占用
- CPU效率高
- 实时性好
-
优先级管理:
- TIM1优先级最高
- 可打断TIM2/TIM3
- 合理资源分配
-
LED闪烁效果:
LED6: ━━━━━━━━ ━━━━━━━━ ━━━━━━━━ (1Hz)
500ms off 500ms on 500ms off
LED7: ━━━━━━━━ ━━━━━━━━ ━━━━━━━━ (1Hz)
500ms off 500ms on 500ms off
LED8: ━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━ (0.5Hz)
1s off 1s on
5. 定时器原理
定时器结构
STM32定时器基本结构:
┌─────────────────────────────────────────────┐
│ 定时器TIMx │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │时钟源 │→ │预分频器 │ │
│ │(72MHz) │ │(PSC) │ │
│ └──────────┘ └────┬─────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │计数器 │ │
│ │(CNT) │ │
│ └────┬─────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │自动重载 │ │
│ │(ARR) │ │
│ └────┬─────┘ │
│ ↓ │
│ ┌──────────┐ │
│ │更新事件 │→ 中断 │
│ │(UEV) │ │
│ └──────────┘ │
└─────────────────────────────────────────────┘
定时器工作过程
定时器计数流程:
时钟源(72MHz)
↓
经过预分频器(PSC)
↓
实际计数频率 = 72MHz / (PSC + 1)
↓
计数器(CNT)递增
↓
CNT达到ARR值
↓
产生更新事件(UEV)
├─ 触发中断(如果使能)
├─ CNT清零
└─ 重新开始计数
示例(TIM1配置):
72MHz / (7200) = 10kHz
10kHz / 5000 = 2Hz
周期 = 1/2Hz = 0.5s
高级定时器 vs 通用定时器
高级定时器(TIM1):
┌────────────────────────────────────────┐
│ • 16位向上/向下/中心对齐计数器 │
│ • 4个独立通道(PWM/输入捕获) │
│ • 死区时间生成(互补输出) │
│ • 刹车功能(紧急停止) │
│ • 重复计数器 │
│ • 主从模式 │
│ • 适合电机控制、高级PWM应用 │
└────────────────────────────────────────┘
通用定时器(TIM2/TIM3):
┌────────────────────────────────────────┐
│ • 16位向上/向下/中心对齐计数器 │
│ • 4个独立通道(PWM/输入捕获) │
│ • 主从模式 │
│ • 适合一般定时、PWM、输入捕获 │
└────────────────────────────────────────┘
6. 代码详解
6.1 主程序 (main.c)
完整代码
#include "TF_LED.h"
#include "TF_Timer.h"
int main (void)
{
NVIC_Configuration(); // 配置中断优先级
TIM1_Configuration(); // 初始化定时器1,设定定时500ms
TIM2_Configuration(); // 初始化定时器2,设定定时500ms
TIM3_Configuration(); // 初始化定时器3,设定定时1s
LED_Init(); // 初始化LED所对应IO口
while(1) // 程序循环
{
// 主循环中不需要做任何事情,中断服务函数会自动处理LED翻转
}
}
主函数要点:
-
初始化顺序:
NVIC_Configuration(); // 先配置中断优先级
TIM1_Configuration(); // 再初始化定时器
TIM2_Configuration();
TIM3_Configuration();
LED_Init(); // 最后初始化LED -
主循环:
while(1); // 空转,所有工作由中断完成
特点:
- CPU空闲
- 低功耗
- 可添加其他任务
6.2 定时器驱动 (TF_Timer)
TF_Timer.h - 头文件
#ifndef __TF_TIMER_H__
#define __TF_TIMER_H__
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_tim.h"
#include "misc.h"
// 函数声明
extern void NVIC_Configuration(void); // 配置TIM1中断优先级
extern void TIM1_Configuration(void); // 初始化TIM1--设定定时500ms
extern void TIM2_Configuration(void); // 初始化TIM2--设定定时500ms
extern void TIM3_Configuration(void); // 初始化TIM3--设定定时1s
#endif
NVIC中断优先级配置
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 设置NVIC中断分组2(2位抢占优先级,2位响应优先级)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 配置TIM1中断
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 配置TIM2中断
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 配置TIM3中断
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
NVIC配置要点:
-
优先级分组:
NVIC_PriorityGroup_2 // 2位抢占,2位响应
说明:
- 抢占优先级:0-3(4级)
- 响应优先级:0-3(4级)
- 数字越小优先级越高 -
优先级分配:
TIM1_UP_IRQn → 抢占0 (最高优先级)
TIM2_IRQn → 抢占1 (中等优先级)
TIM3_IRQn → 抢占2 (低优先级)
中断嵌套:
- TIM1可打断TIM2/TIM3
- TIM2可打断TIM3
- TIM3不能打断任何定时器
TIM1定时器配置
void TIM1_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 使能TIM1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
// 定时时间计算:
// T = ((1+TIM_Prescaler)/72M) * (1+TIM_Period)
// T = ((1+7199)/72M) * (1+4999) = 0.5秒
TIM_TimeBaseStructure.TIM_Period = (5000-1); // 自动重载值
TIM_TimeBaseStructure.TIM_Prescaler = (7200-1); // 预分频器
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频系数,不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // TIM向上计数模式
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; // 重复计数器值(TIM1特有)
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 预先清除所有中断位
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); // 使能TIM1更新中断
TIM_Cmd(TIM1, ENABLE); // 允许TIM1开始计数
}
TIM1配置要点:
-
时钟源:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
说明:
- TIM1是高级定时器
- 挂载在APB2总线上
- 时钟频率72MHz -
预分频器(PSC):
TIM_Prescaler = (7200-1)
作用:
- 将72MHz分频到10kHz
- 72MHz / 7200 = 10kHz
- 每个计数值=0.1ms -
自动重载值(ARR):
TIM_Period = (5000-1)
作用:
- 计数器从0计数到4999
- 计数5000次
- 5000 × 0.1ms = 500ms -
重复计数器:
TIM_RepetitionCounter = 0
说明:
- TIM1特有功能
- 用于高级应用(PWM死区等)
- 基本应用设为0
TIM2定时器配置
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 使能TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// T = ((1+7199)/72M) * (1+4999) = 0.5秒
TIM_TimeBaseStructure.TIM_Period = (5000-1);
TIM_TimeBaseStructure.TIM_Prescaler = (7200-1);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM2, ENABLE); // 使能预装载
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
TIM2配置要点:
-
时钟源:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
说明:
- TIM2是通用定时器
- 挂载在APB1总线上
- 时钟频率72MHz(经过倍频) -
ARR预装载:
TIM_ARRPreloadConfig(TIM2, ENABLE);
作用:
- ARR寄存器有预装载功能
- 修改ARR后下个周期生效
- 避免在计数过程中修改产生错误
TIM3定时器配置
void TIM3_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// 使能TIM3时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// T = ((1+7199)/72M) * (1+999) = 1秒
TIM_TimeBaseStructure.TIM_Period = (1000-1); // 注意:改为1000
TIM_TimeBaseStructure.TIM_Prescaler = (7200-1);
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ARRPreloadConfig(TIM3, ENABLE);
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM3, ENABLE);
}
TIM3配置要点:
-
不同定时周期:
TIM_Period = (1000-1) // TIM3为1秒
计算:
- 72MHz / 7200 = 10kHz
- 1000 × 0.1ms = 100ms... 错误!
正确计算:
- PSC = 7199
- ARR = 999
- 72MHz / 7200 / 1000 = 10Hz
- 周期 = 1/10Hz = 100ms... 仍然错误!
实际上代码想实现1s:
- 应该 ARR = 9999
- 或者 PSC = 71999 -
代码Bug:
// 注释说1秒,但计算有误
// 应该修改为:
TIM_TimeBaseStructure.TIM_Period = (10000-1); // 1秒
TIM_TimeBaseStructure.TIM_Prescaler = (7200-1);
定时器中断服务函数
// TIM1更新中断服务函数
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 必须清除中断标志位
flag1 = ~flag1; // 翻转标志1
if(flag1 == 0)
{
LED_On(6); // 点亮LED6
}
else
{
LED_Off(6); // 熄灭LED6
}
}
}
// TIM2更新中断服务函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
flag2 = ~flag2;
if(flag2 == 0)
{
LED_On(7);
}
else
{
LED_Off(7);
}
}
}
// TIM3更新中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);
flag3 = ~flag3;
if(flag2 == 0) // Bug:应该是flag3
{
LED_On(8);
}
else
{
LED_Off(8);
}
}
}
中断服务函数要点:
-
中断标志检查:
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
作用:
- 检查是否是更新中断
- 防止误处理其他中断 -
清除中断标志:
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
重要性:
- 必须清除,否则一直触发中断
- 清除后才能响应下次中断 -
标志位翻转:
flag1 = ~flag1;
作用:
- 每次中断翻转标志位
- 实现LED状态切换
- 0→255, 255→0 -
代码Bug:
// TIM3中断服务函数中
if(flag2 == 0) // ❌ 错误:应该是flag3
// 应该修改为:
if(flag3 == 0) // ✅ 正确
6.3 LED驱动 (TF_LED)
LED初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOB时钟
RCC_APB2PeriphClockCmd(LED_CLK, ENABLE);
// 使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// JTAG-DP禁用 + SW-DP使能
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
// 配置LED引脚
GPIO_InitStructure.GPIO_Pin = LED_PIN; // PB0-PB7
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(LED_PORT, &GPIO_InitStructure);
// 关闭所有LED
GPIO_ResetBits(LED_PORT, LED_PIN);
}
LED控制函数
// 点亮LED
void LED_On(unsigned char Led_PIN)
{
switch(Led_PIN)
{
case 1: GPIO_SetBits(LED1_PORT, LED1_PIN); break;
case 2: GPIO_SetBits(LED2_PORT, LED2_PIN); break;
// ...
case 6: GPIO_SetBits(LED6_PORT, LED6_PIN); break; // TIM1控制
case 7: GPIO_SetBits(LED7_PORT, LED7_PIN); break; // TIM2控制
case 8: GPIO_SetBits(LED8_PORT, LED8_PIN); break; // TIM3控制
default: GPIO_SetBits(LED_PORT, LED_PIN); break;
}
}
// 关闭LED
void LED_Off(unsigned char Led_PIN)
{
switch(Led_PIN)
{
case 1: GPIO_ResetBits(LED1_PORT, LED1_PIN); break;
// ...
case 6: GPIO_ResetBits(LED6_PORT, LED6_PIN); break;
case 7: GPIO_ResetBits(LED7_PORT, LED7_PIN); break;
case 8: GPIO_ResetBits(LED8_PORT, LED8_PIN); break;
default: GPIO_ResetBits(LED_PORT, LED_PIN); break;
}
}
7. 定时器计算公式
定时时间计算
定时器定时时间计算公式:
┌────────────────────────────────────────────────┐
│ T = ((1 + PSC) / f_clk) × (1 + ARR) │
│ │
│ 其中: │
│ T - 定时时间(秒) │
│ PSC - 预分频器值(TIM_Prescaler) │
│ ARR - 自动重载值(TIM_Period) │
│ f_clk - 定时器时钟频率(Hz) │
└────────────────────────────────────────────────┘
或者简化为:
T = (PSC + 1) × (ARR + 1) / f_clk
计数频率:
f_count = f_clk / (PSC + 1)
定时周期:
T = (ARR + 1) / f_count
实例计算
TIM1/TIM2配置(500ms定时):
────────────────────────────────────
已知:
- f_clk = 72MHz = 72,000,000 Hz
- PSC = 7200 - 1 = 7199
- ARR = 5000 - 1 = 4999
计算:
1. 计数频率:
f_count = 72,000,000 / (7199 + 1)
= 72,000,000 / 7200
= 10,000 Hz = 10 kHz
2. 每个计数周期:
T_count = 1 / 10,000
= 0.0001 s = 0.1 ms
3. 定时时间:
T = (4999 + 1) × 0.0001
= 5000 × 0.0001
= 0.5 s = 500 ms ✅
TIM3配置(期望1s,实际有误):
────────────────────────────────────
代码中:
- PSC = 7199
- ARR = 999
计算:
1. 计数频率:10 kHz(同上)
2. 定时时间:
T = (999 + 1) × 0.0001
= 1000 × 0.0001
= 0.1 s = 100 ms ❌
正确配置(1s):
应该设置 ARR = 9999
T = (9999 + 1) × 0.0001 = 1 s ✅
参数选择建议
选择PSC和ARR的原则:
┌────────────────────────────────────────┐
│ 1. PSC × ARR 不超过 65535 │
│ 2. 尽量让计数频率为整数kHz │
│ 3. 预分频后频率不要太高(避免溢出) │
│ 4. 预分频后频率不要太低(影响精度) │
└────────────────────────────────────────┘
常用配置:
┌─────────┬──────┬──────┬──────────┐
│ 定时时间 │ PSC │ ARR │ 计数频率 │
├─────────┼──────┼──────┼──────────┤
│ 1ms │ 71 │ 999 │ 1MHz │
│ 10ms │ 719 │ 999 │ 100kHz │
│ 100ms │ 7199 │ 999 │ 10kHz │
│ 500ms │ 7199 │ 4999 │ 10kHz │← 本项目
│ 1s │ 7199 │ 9999 │ 10kHz │
│ 1s │ 71999│ 999 │ 1kHz │
└─────────┴──────┴──────┴──────────┘
8. 中断优先级配置
中断优先级分组
NVIC_PriorityGroup_2:
┌──────────────────────────────────────┐
│ 抢占优先级:2位(0-3,4级) │
│ 响应优先级:2位(0-3,4级) │
└──────────────────────────────────────┘
抢占优先级(Preemption Priority):
- 高抢占优先级可打断低抢占优先级
- 数字越小优先级越高
响应优先级(Sub Priority):
- 抢占优先级相同时,响应优先级决定谁先响应
- 不能相互打断
本项目配置:
┌─────────┬──────────┬──────────┐
│ 定时器 │ 抢占优先级│ 响应优先级│
├─────────┼──────────┼──────────┤
│ TIM1 │ 0 │ 0 │← 最高
│ TIM2 │ 1 │ 0 │
│ TIM3 │ 2 │ 0 │← 最低
└─────────┴──────────┴──────────┘
中断嵌套示例
中断嵌套场景:
时间 →
主程序 TIM3(2,0) 主程序 TIM1(0,0)
│ │ │ │
├──────────┤ │ │
│ 执行 │ │ │
│ ├─────────┤ │
│ │ │ │中断 │ │
│ │ │ │被打断│ │
│ │ │ │ ├──────────┤
│ │ │ │ │ │TIM1中断 │
│ │ │ │ │ │优先级高 │
│ │ │ │ │ │执行完毕 │
│ │ │ │ │ │ │
│ │ │ │←─┘ │←─────────┘
│ │ │←─┘ │
│ │ │ │
│←──┘ │ │
│ │ │
说明:
- TIM3中断正在执行
- TIM1中断到来(优先级0 > 2)
- TIM3中断被打断
- TIM1中断执行完毕
- 返回TIM3中断继续执行
- TIM3执行完毕返回主程序
9. 使用说明
9.1 硬件连接
连接步骤
-
连接LED
LED6 → PB5(TIM1控制,500ms闪烁)
LED7 → PB6(TIM2控制,500ms闪烁)
LED8 → PB7(TIM3控制,1s闪烁) -
连接调试器
SWDIO → PA13
SWCLK → PA14
注意:已禁用JTAG,只能用SW调试
9.2 编译与下载
编译工程
- 打开
MDK/TEST_CODE.uvproj - 选择目标芯片:STM32F103C8
- 编译:Project → Build Target (F7)
- 检查编译结果:0 Error(s), 0 Warning(s)
下载程序
- 配置调试器:Options → Debug → 选择 J-Link/ST-Link
- 使用SW调试方式(JTAG已禁用)
- 下载程序:Flash → Download (F8)
- 复位运行
9.3 运行效果
预期现象:
- LED6闪烁:500ms亮/500ms灭(1Hz)
- LED7闪烁:500ms亮/500ms灭(1Hz)
- LED8闪烁:1s亮/1s灭(0.5Hz,但代码有bug实际100ms)
- 三个LED独立闪烁,互不干扰
9.4 测试方法
测试1:基本功能
步骤:
1. 上电复位
2. 观察LED6、LED7、LED8闪烁
3. 用秒表测量闪烁周期
预期结果:
- LED6/LED7:1秒翻转一次(1Hz)
- LED8:2秒翻转一次(0.5Hz)
测试2:中断优先级
// 在中断服务函数中添加延时
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);
// 添加长延时
for(volatile int i = 0; i < 10000000; i++);
flag3 = ~flag3;
if(flag3 == 0) LED_On(8);
else LED_Off(8);
}
}
测试:
1. 观察LED6、LED7是否正常闪烁
2. LED8延时期间,LED6、LED7应该不受影响
预期结果:
- TIM1/TIM2中断可打断TIM3
- LED6/LED7正常闪烁
- LED8闪烁受影响
10. 常见问题
问题1:LED不闪烁
可能原因:
- ❌ 定时器未启动
- ❌ 中断未使能
- ❌ 中断标志未清除
- ❌ NVIC配置错误
解决方案:
// 检查1:定时器是否启动
TIM_Cmd(TIM1, ENABLE); // 必须使能
// 检查2:中断是否使能
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); // 使能更新中断
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // NVIC使能
// 检查3:中断标志是否清除
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 必须清除
// ...
}
}
// 调试:在中断中翻转LED测试
void TIM1_UP_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
GPIO_WriteBit(GPIOB, GPIO_Pin_5,
(BitAction)(1 - GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_5)));
}
问题2:定时不准确
原因: 计算错误或时钟配置错误
解决方案:
// 检查系统时钟
// 确保PLL配置为72MHz
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 8MHz × 9 = 72MHz
// 重新计算定时参数
// 期望500ms:
// PSC = 7199, ARR = 4999
// 72MHz / 7200 / 5000 = 2Hz = 500ms ✅
// 期望1s:
// PSC = 7199, ARR = 9999
// 72MHz / 7200 / 10000 = 1Hz = 1s ✅
// 用示波器或逻辑分析仪测量实际周期
问题3:TIM3的LED闪烁不正常
原因: 代码bug,判断了错误的标志位
解决方案:
// ❌ 原代码
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);
flag3 = ~flag3;
if(flag2 == 0) // 错误:应该是flag3
{
LED_On(8);
}
else
{
LED_Off(8);
}
}
}
// ✅ 修正后
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM3, TIM_FLAG_Update);
flag3 = ~flag3;
if(flag3 == 0) // 修正
{
LED_On(8);
}
else
{
LED_Off(8);
}
}
}
问题4:中断频繁触发
原因: 未清除中断标志位
解决方案:
// 必须在中断服务函数开始时清除标志位
void TIM1_UP_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 立即清除
// 执行中断处理代码
// ...
}
}
// 如果不清除,中断会一直触发
问题5:修改定时参数无效
原因: 未使能ARR预装载或修改时机不对
解决方案:
// 方案1:使能ARR预装载
TIM_ARRPreloadConfig(TIM2, ENABLE);
// 方案2:在更新事件时修改
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_FLAG_Update);
// 修改ARR
TIM_SetAutoreload(TIM2, new_arr_value);
// ...
}
}