跳到主要内容

【十一】定时器中断实验(TIM1/2/3)

1. 项目概述

本项目演示如何使用 STM32F103C8T6 微控制器的3个定时器(TIM1、TIM2、TIM3)通过中断方式实现LED闪烁功能。系统使用定时器更新中断触发LED状态翻转,实现不同频率的LED闪烁效果,展示了多定时器协同工作和中断优先级管理。

技术要点

  • ✅ TIM1高级定时器配置
  • ✅ TIM2/TIM3通用定时器配置
  • ✅ 定时器中断配置
  • ✅ NVIC中断优先级管理
  • ✅ 定时时间精确计算
  • ✅ 中断服务函数编写
  • ✅ LED状态控制
  • ✅ 多定时器协同工作

应用场景

  1. ⏱️ 周期性任务执行
  2. ⏱️ LED闪烁与指示
  3. ⏱️ 定时采样
  4. ⏱️ 看门狗喂狗
  5. ⏱️ 软件定时器
  6. ⏱️ PWM波形生成

2. 硬件平台

主控芯片

  • 型号: STM32F103C8T6
  • 内核: ARM Cortex-M3
  • 主频: 72 MHz
  • Flash: 64 KB
  • SRAM: 20 KB
  • 定时器: TIM1-TIM4

外设资源

定时器配置

定时器类型定时时间中断优先级控制LED
TIM1高级定时器500ms抢占0/响应0LED6
TIM2通用定时器500ms抢占1/响应0LED7
TIM3通用定时器1000ms (1s)抢占2/响应0LED8

LED指示灯

LED引脚闪烁频率定时器
LED6PB51Hz (500ms开/500ms关)TIM1
LED7PB61Hz (500ms开/500ms关)TIM2
LED8PB70.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 │中断 │中断 │中断 │中断
└─────────────┘ └─────────────┘

注意事项:

  1. TIM1是高级定时器,功能更丰富
  2. TIM2/TIM3是通用定时器
  3. 优先级:TIM1 > TIM2 > TIM3
  4. 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状态

功能特点

  1. 多定时器独立工作:

    • TIM1、TIM2、TIM3同时运行
    • 互不干扰
    • 各自控制LED
  2. 不同定时周期:

    • TIM1/TIM2:500ms
    • TIM3:1000ms
    • 灵活配置
  3. 中断驱动:

    • 主循环不占用
    • CPU效率高
    • 实时性好
  4. 优先级管理:

    • TIM1优先级最高
    • 可打断TIM2/TIM3
    • 合理资源分配
  5. 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翻转
}
}

主函数要点:

  1. 初始化顺序:

    NVIC_Configuration();   // 先配置中断优先级
    TIM1_Configuration(); // 再初始化定时器
    TIM2_Configuration();
    TIM3_Configuration();
    LED_Init(); // 最后初始化LED
  2. 主循环:

    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配置要点:

  1. 优先级分组:

    NVIC_PriorityGroup_2  // 2位抢占,2位响应

    说明:
    - 抢占优先级:0-34级)
    - 响应优先级:0-34级)
    - 数字越小优先级越高
  2. 优先级分配:

    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配置要点:

  1. 时钟源:

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

    说明:
    - TIM1是高级定时器
    - 挂载在APB2总线上
    - 时钟频率72MHz
  2. 预分频器(PSC):

    TIM_Prescaler = (7200-1)

    作用:
    -72MHz分频到10kHz
    - 72MHz / 7200 = 10kHz
    - 每个计数值=0.1ms
  3. 自动重载值(ARR):

    TIM_Period = (5000-1)

    作用:
    - 计数器从0计数到4999
    - 计数5000
    - 5000 × 0.1ms = 500ms
  4. 重复计数器:

    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配置要点:

  1. 时钟源:

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

    说明:
    - TIM2是通用定时器
    - 挂载在APB1总线上
    - 时钟频率72MHz(经过倍频)
  2. 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配置要点:

  1. 不同定时周期:

    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
  2. 代码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);
}
}
}

中断服务函数要点:

  1. 中断标志检查:

    if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET)

    作用:
    - 检查是否是更新中断
    - 防止误处理其他中断
  2. 清除中断标志:

    TIM_ClearITPendingBit(TIM1, TIM_IT_Update);

    重要性:
    - 必须清除,否则一直触发中断
    - 清除后才能响应下次中断
  3. 标志位翻转:

    flag1 = ~flag1;

    作用:
    - 每次中断翻转标志位
    - 实现LED状态切换
    - 0255, 2550
  4. 代码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 硬件连接

连接步骤

  1. 连接LED

    LED6 → PB5(TIM1控制,500ms闪烁)
    LED7 → PB6(TIM2控制,500ms闪烁)
    LED8 → PB7(TIM3控制,1s闪烁)
  2. 连接调试器

    SWDIO → PA13
    SWCLK → PA14
    注意:已禁用JTAG,只能用SW调试

9.2 编译与下载

编译工程

  1. 打开 MDK/TEST_CODE.uvproj
  2. 选择目标芯片:STM32F103C8
  3. 编译:Project → Build Target (F7)
  4. 检查编译结果:0 Error(s), 0 Warning(s)

下载程序

  1. 配置调试器:Options → Debug → 选择 J-Link/ST-Link
  2. 使用SW调试方式(JTAG已禁用)
  3. 下载程序:Flash → Download (F8)
  4. 复位运行

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不闪烁

可能原因:

  1. ❌ 定时器未启动
  2. ❌ 中断未使能
  3. ❌ 中断标志未清除
  4. ❌ 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);

// ...
}
}