🎓 总站 🏠 本课目录 01 概述 02 ARM与STM32 03 开发环境 04 最小系统与C 05 GPIO 06 中断 07 串口通信 08 DMA 09 定时器 10 ADC 11 从模块到项目 12 嵌入式操作系统
智能嵌入式系统设计 · 第9章

定时器 Timer

基于 STM32F103 · 零基础讲解定时器原理、计数模式、输入捕获、输出比较、PWM 生成。

📚 学习进度
0%

🎯学习目标

  • 了解定时器的基本概念、用途和工作原理;
  • 理解 STM32F103 定时器的分类(高级控制、通用、基本)及各自特点;
  • 掌握定时器的时基单元组成(PSC、ARR、CNT)和三种计数模式;
  • 理解定时器定时时间的计算方法,能根据需求配置 PSC 和 ARR;
  • 掌握输入捕获的原理,能测量外部信号的频率和脉宽;
  • 掌握输出比较和 PWM 的原理,能生成指定占空比的 PWM 波形;
  • 熟悉定时器相关中断的配置和处理;
  • 掌握定时器的标准库和 HAL 库编程方法。

1定时器概述

定时器(Timer)是微控制器中用于定时、计数、PWM 生成、输入捕获等功能的外设。它的本质是一个可编程的计数器,按照设定的频率自动计数,计数到特定值时触发事件。

💡 一句话理解定时器就像一个可编程的闹钟——你可以设定"每隔多久响一次",也可以用它来"数外部脉冲的个数",还能让它"输出可调宽窄的方波"。
💡 通俗理解:定时器能做什么?

定时:每隔一段时间做一件事。比如每 1ms 刷新一次数码管、每 500ms 读一次传感器、每 1s 串口发送一次数据。定时器就像一个精准的秒表,到时间就自动通知 CPU。

计数:数外部脉冲的个数。比如编码器转了多少圈、流水线上过了多少个产品、按键按了多少次。定时器就像一个计数器,每来一个脉冲就加 1。

PWM 输出:输出可调占空比的方波。比如控制 LED 亮度(占空比大=亮,占空比小=暗)、控制电机转速(占空比大=快,占空比小=慢)、控制舵机角度。定时器就像一个可调宽度的脉冲发生器

输入捕获:测量外部信号的频率和脉宽。比如红外遥控解码、超声波测距、测量 PWM 信号的频率。定时器就像一个精准的测量仪,记录信号变化的时刻。

💡 通俗理解:定时器的工作原理

想象一个水桶接水的模型:

时钟源(水龙头):提供稳定的脉冲信号,就像水龙头以固定流量出水。

预分频器 PSC(水管粗细):对时钟进行分频,降低计数频率。比如 72MHz 时钟,PSC=71,则计数频率 = 72MHz/(71+1) = 1MHz(每 1μs 计数一次)。

计数器 CNT(水桶):每个脉冲加 1,就像水桶里的水位不断上升。

自动重装载值 ARR(水桶容量):计数器计到 ARR 就"溢出",清零重新开始,同时产生一个"更新事件"(水桶满了倒掉重来)。

定时时间 = 水桶装满的时间 = (ARR+1) × (PSC+1) / 时钟频率。

时钟源 预分频器PSC 计数器CNT 自动重装ARR 溢出清零,重新计数 更新事件UIF
图1 · 定时器时基单元工作流程:时钟源 → 预分频 → 计数 → 溢出 → 更新事件

2定时器分类

STM32F103 拥有 8 个定时器,按功能强弱分为三类:

📋 三类定时器对比

特性高级控制定时器通用定时器基本定时器
型号TIM1、TIM8TIM2~TIM5TIM6、TIM7
位数16 位16 位16 位
计数模式向上/向下/中央对齐向上/向下/中央对齐仅向上计数
输入捕获✅ 4 路✅ 4 路
输出比较✅ 4 路✅ 4 路
PWM 输出✅ 4 路✅ 4 路
互补输出✅(带死区控制)
DMA 触发
时钟来源APB2APB1APB1
典型用途电机控制、逆变器通用定时、PWM、捕获DAC 触发、基本定时
⭐ 高级定时器的独特能力

互补输出:TIM1/TIM8 可以输出一对互补的 PWM 信号(CHx 和 CHxN),用于驱动 H 桥电路控制电机正反转。

死区控制:在互补输出之间插入一段"死区时间",防止上下桥臂同时导通导致短路。这是电机控制的关键安全机制。

刹车功能:当检测到异常(如过流)时,自动将输出置为安全状态,保护电路。

💡 通俗理解

基本定时器(TIM6/7)就像一个简单的闹钟——只能定时,到时间就响。功能最少,但够用。

通用定时器(TIM2~5)就像一个瑞士军刀——既能定时,又能计数,还能输出 PWM、测量信号。功能全面,最常用。

高级定时器(TIM1/8)就像一个专业电机控制器——在通用定时器的基础上,增加了互补输出、死区控制、刹车功能,专门为电机控制设计。

⭐ 时钟源定时器时钟来自 APB1 或 APB2 总线。TIM1/TIM8 挂在 APB2(72MHz),TIM2~TIM7 挂在 APB1(36MHz,但倍频后也是 72MHz)。

3计数模式

定时器有三种计数模式,决定了计数器 CNT 的计数方向和行为:

⬆️

向上计数

Up
计数器从 0 递增到 ARR,然后清零并产生更新事件。最常用,适合大多数定时场景。
⬇️

向下计数

Down
计数器从 ARR 递减到 0,然后重新装入 ARR 并产生更新事件。适合需要倒计时的场景。
↕️

中央对齐

Center-Aligned
计数器从 0 递增到 ARR,再递减到 0,产生更新事件。波形对称,常用于电机控制的 PWM。
向上计数 0 ARR 溢出 向下计数 ARR 0 重装 中央对齐 0 ARR 溢出
图2 · 三种计数模式的计数器变化波形

📐 定时时间计算

💡 核心公式

定时时间 = (ARR + 1) × (PSC + 1) / 定时器时钟频率

计数频率 = 定时器时钟频率 / (PSC + 1)

计数次数 = ARR + 1

计算示例

例1:定时器时钟 72MHz,需要 1ms 定时。

设 PSC=71,则计数频率 = 72MHz/(71+1) = 1MHz(每 1μs 计数一次)。

需要 1ms = 1000μs,所以 ARR = 1000-1 = 999。

验证:定时时间 = (999+1)×(71+1)/72MHz = 1000×72/72000000 = 0.001s = 1ms ✅


例2:定时器时钟 72MHz,需要 500ms 定时。

设 PSC=7199,则计数频率 = 72MHz/(7199+1) = 10kHz(每 0.1ms 计数一次)。

需要 500ms = 5000×0.1ms,所以 ARR = 5000-1 = 4999。

验证:定时时间 = (4999+1)×(7199+1)/72MHz = 5000×7200/72000000 = 0.5s ✅

4定时器配置流程

使用定时器的基本步骤(以定时器中断为例):

  1. 使能定时器时钟:TIM2~TIM7 在 APB1,TIM1/TIM8 在 APB2;
  2. 配置时基单元:设置预分频器(PSC)、自动重装载值(ARR)、计数模式;
  3. 使能更新中断:如果需要定时中断,使能 TIM_IT_Update;
  4. 配置 NVIC:设置中断优先级并使能中断通道;
  5. 使能定时器:调用 TIM_Cmd() 启动计数;
  6. 编写中断服务函数:检测更新标志位,清除标志位,执行用户代码。
标准库配置代码(TIM2 产生 1ms 中断)
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

// 2. 配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 999;                // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler = 71;              // PSC = 71
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

// 3. 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);

// 4. 配置 NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// 5. 启动定时器
TIM_Cmd(TIM2, ENABLE);
中断服务函数
void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  // 清除标志位
        // 用户代码:如 LED 翻转、计数器累加等
        GPIO_TogglePin(GPIOA, GPIO_Pin_0);
    }
}
HAL 库配置代码(CubeMX 生成 + 用户代码)
// CubeMX 自动生成的初始化代码
TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 999;
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim2);
}

// main() 中启动定时器中断
HAL_TIM_Base_Start_IT(&htim2);

// 回调函数(HAL 库自动调用)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2)
    {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);
    }
}
⭐ 配置要点

必须先使能时钟,否则配置无效;

② PSC 和 ARR 的值都是 0~65535(16 位),实际值 = 设定值 + 1;

③ 中断服务函数中必须清除标志位,否则会反复进入中断;

④ HAL 库使用回调函数机制,不需要手动判断和清除标志位。

5输入捕获

输入捕获(Input Capture)用于测量外部信号的频率脉宽。当外部信号的边沿(上升沿或下降沿)到来时,定时器自动将当前计数器的值锁存到捕获寄存器(CCR)中。

💡 通俗理解

输入捕获就像一个秒表:当信号从低变高(上升沿)时,你按下"开始";当信号从高变低(下降沿)时,你按下"停止"。两次按键的时间差就是脉宽。

如果连续两个上升沿之间的时间差就是信号的周期,取倒数就是频率

📏 测量频率

测量频率的原理

① 配置输入捕获为上升沿触发

② 第一个上升沿到来时,记录 CNT 值为 T1

③ 第二个上升沿到来时,记录 CNT 值为 T2

④ 周期 T = (T2 - T1) × (PSC+1) / 时钟频率;

⑤ 频率 f = 1 / T。

📏 测量脉宽(PWM 输入模式)

测量脉宽的原理

① 配置两个通道:通道 1 上升沿触发,通道 2 下降沿触发;

② 上升沿到来时,记录 CNT 值为 T1(周期起点);

③ 下降沿到来时,记录 CNT 值为 T2(高电平结束);

④ 高电平时间 = (T2 - T1) × (PSC+1) / 时钟频率;

⑤ 占空比 = 高电平时间 / 周期。

标准库输入捕获配置
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2. 配置 GPIO(PA6 作为 TIM3_CH1 输入)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;  // 上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. 配置输入捕获
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;   // 上升沿
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM3, &TIM_ICInitStructure);

// 4. 使能捕获中断
TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM3, ENABLE);

6输出比较与 PWM

输出比较(Output Compare)

输出比较将计数器 CNT 的值与比较寄存器 CCR 的值进行比较,当两者相等时,输出引脚电平发生翻转、置位或复位。

💡 通俗理解输出比较就像一个定时闹钟:你设定一个时间(CCR),当计数器走到这个时间时,就自动触发一个动作(翻转引脚电平)。可以用来生成精确的方波。

PWM(脉冲宽度调制)⭐

PWM(Pulse Width Modulation,脉冲宽度调制)是一种通过改变方波的高电平持续时间(脉宽)来等效控制模拟信号的技术。

💡 通俗理解:什么是 PWM?

想象一个快速开关的灯:如果灯每秒开关 1000 次(频率 1kHz),每次亮 0.5ms 灭 0.5ms,人眼看到的就是"半亮";如果亮 0.8ms 灭 0.2ms,人眼看到的就是"很亮"。

PWM 就是这样一种技术——用数字信号(高低电平)模拟出模拟信号的效果。高电平时间占整个周期的比例叫占空比

⭐ PWM 核心参数

频率:PWM 信号每秒重复的次数。频率太低会闪烁(LED),太高会增加开关损耗(电机)。

占空比:高电平时间占整个周期的百分比。

占空比 = CCR / (ARR + 1) × 100%

其中 CCR 是比较寄存器值,ARR 是自动重装载值。

占空比 25% CCR ARR 占空比 50% 占空比 75%
图3 · 不同占空比的 PWM 波形对比

🔧 PWM 的常见应用场景

💡

LED 调光

占空比大=亮,占空比小=暗。频率 1kHz 以上人眼无闪烁。

⚙️

电机调速

占空比大=快,占空比小=慢。频率 10~20kHz。

🎮

舵机控制

脉宽 0.5~2.5ms 对应 0°~180°。频率 50Hz(周期 20ms)。

🔊

蜂鸣器

不同频率产生不同音调,占空比控制音量。

PWM 配置代码(标准库)
// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2. 配置 GPIO(PA0 复用推挽输出)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. 配置定时器时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 999;         // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler = 71;       // PSC = 71
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

// 4. 配置 PWM 模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500;            // CCR = 500,占空比 50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

// 5. 启动 PWM
TIM_Cmd(TIM2, ENABLE);

// 动态修改占空比
TIM_SetCompare1(TIM2, 750);  // 占空比变为 75%
⭐ PWM 模式 1 vs 模式 2

模式 1:CNT < CCR 时输出有效电平(高电平),CNT ≥ CCR 时输出无效电平。最常用。

模式 2:CNT < CCR 时输出无效电平,CNT ≥ CCR 时输出有效电平。与模式 1 相反。

7标准库函数

常用定时器标准库函数(源码在 stm32f10x_tim.c):

📋 时基配置函数

函数功能参数说明
TIM_TimeBaseInit()初始化时基单元参数:定时器指针 + TIM_TimeBaseInitTypeDef 结构体
TIM_Cmd()使能/禁用定时器参数:定时器 + NewState(ENABLE/DISABLE)
TIM_ITConfig()配置定时器中断参数:定时器 + 中断源 + NewState
TIM_GetITStatus()获取中断状态返回 SET 或 RESET
TIM_ClearITPendingBit()清除中断挂起位必须在中断服务函数中调用

📋 PWM / 输出比较函数

函数功能
TIM_OC1Init()~TIM_OC4Init()配置通道 1~4 的输出比较模式
TIM_SetCompare1()~TIM_SetCompare4()设置比较寄存器值(动态修改 PWM 占空比)
TIM_CtrlPWMOutputs()使能 PWM 输出(高级定时器必须调用)
TIM_OC1PreloadConfig()使能/禁用预装载(影子寄存器)

📋 输入捕获函数

函数功能
TIM_ICInit()配置输入捕获模式
TIM_GetCapture1()~TIM_GetCapture4()读取捕获寄存器值
TIM_PWMIConfig()配置 PWM 输入模式(双通道)

📋 其他常用函数

函数功能
TIM_SetCounter()直接设置计数器值
TIM_GetCounter()读取当前计数器值
TIM_SetPrescaler()动态修改预分频值
TIM_SetAutoreload()动态修改自动重装载值
TIM_GenerateEvent()软件触发更新事件

8HAL 库函数

HAL 库中定时器相关函数(更抽象,跨系列可移植):

📋 基本定时器函数

函数功能
HAL_TIM_Base_Init()初始化定时器基本功能
HAL_TIM_Base_Start()启动定时器(轮询模式)
HAL_TIM_Base_Start_IT()启动定时器(中断模式)
HAL_TIM_Base_Start_DMA()启动定时器(DMA 模式)
HAL_TIM_Base_Stop()停止定时器

📋 PWM 函数

函数功能
HAL_TIM_PWM_Init()初始化 PWM 模式
HAL_TIM_PWM_ConfigChannel()配置 PWM 通道
HAL_TIM_PWM_Start()启动 PWM 输出
HAL_TIM_PWM_Stop()停止 PWM 输出
__HAL_TIM_SET_COMPARE()动态修改 PWM 占空比(宏)

📋 输入捕获函数

函数功能
HAL_TIM_IC_Init()初始化输入捕获
HAL_TIM_IC_ConfigChannel()配置捕获通道
HAL_TIM_IC_Start_IT()启动输入捕获(中断模式)
HAL_TIM_IC_Stop_IT()停止输入捕获

📋 回调函数(中断处理)

回调函数触发时机
HAL_TIM_PeriodElapsedCallback()定时器溢出(更新事件)
HAL_TIM_IC_CaptureCallback()输入捕获完成
HAL_TIM_PWM_PulseFinishedCallback()PWM 脉冲完成
💡 HAL 库 vs 标准库对比
对比项标准库HAL 库
初始化手写结构体配置CubeMX 图形化生成 + HAL_TIM_Base_Init()
启动 PWMTIM_Cmd() + TIM_CtrlPWMOutputs()HAL_TIM_PWM_Start() 一行搞定
修改占空比TIM_SetCompare1()__HAL_TIM_SET_COMPARE()
中断处理手动判断标志位 + 清除重写回调函数,自动清除
可移植性仅限 STM32F1跨 STM32 系列可移植

重点例题

例题 1:定时器中断实现 LED 每 500ms 翻转 要求:使用 TIM3,定时器时钟 72MHz,PA0 接 LED,每 500ms 翻转一次。 思路:① 计算 PSC 和 ARR;② 配置定时器中断;③ 在中断中翻转 LED。
// PSC=7199, ARR=4999
// 定时时间 = (7199+1)×(4999+1)/72MHz = 7200×5000/72000000 = 0.5s

// 1. 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 2. 配置 GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 3. 配置定时器
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

// 4. 使能中断 + NVIC
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

// 5. 启动定时器
TIM_Cmd(TIM3, ENABLE);

// 中断服务函数
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
        GPIO_WriteBit(GPIOA, GPIO_Pin_0,
            (BitAction)(1 - GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_0)));
    }
}
关键点:① 定时时间 = (PSC+1)×(ARR+1)/时钟频率;② 中断中必须清除标志位;③ 翻转引脚用读-取反-写操作。
例题 2:PWM 控制 LED 亮度(呼吸灯效果) 要求:用 TIM2 通道 1 输出 PWM,PA0 接 LED,实现呼吸灯效果(亮度渐变)。 思路:① 配置 PWM;② 在主循环中逐渐改变占空比。
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 配置 GPIO(复用推挽)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

// 配置定时器时基(PWM 频率 = 72MHz/(71+1)/(999+1) = 1kHz)
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 999;
TIM_TimeBaseStructure.TIM_Prescaler = 71;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

// 配置 PWM 模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);

TIM_Cmd(TIM2, ENABLE);

// 主循环:呼吸灯
uint16_t pwm = 0;
int8_t dir = 1;
while(1)
{
    pwm += dir * 5;
    if (pwm >= 995) dir = -1;
    if (pwm <= 5) dir = 1;
    TIM_SetCompare1(TIM2, pwm);
    delay_ms(10);
}
关键点:① GPIO 必须配置为复用推挽输出;② PWM 频率 = 时钟/((PSC+1)×(ARR+1));③ 动态修改 TIM_SetCompare1() 改变占空比。
例题 3:输入捕获测量 PWM 频率和占空比 要求:用 TIM3 通道 1(PA6)捕获外部 PWM 信号,测量其频率和占空比。 思路:① 配置输入捕获(上升沿触发);② 在中断中记录两次捕获值;③ 计算周期和脉宽。
volatile uint16_t IC_Value1 = 0, IC_Value2 = 0;
volatile uint16_t IC_Duty = 0;
volatile uint8_t IC_Flag = 0;

// 配置输入捕获(省略时钟和 GPIO 配置)
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM3, &TIM_ICInitStructure);

TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE);
TIM_Cmd(TIM3, ENABLE);

// 中断服务函数
void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET)
    {
        TIM_ClearITPendingBit(TIM3, TIM_IT_CC1);
        if (IC_Flag == 0)
        {
            IC_Value1 = TIM_GetCapture1(TIM3);  // 第一次捕获
            IC_Flag = 1;
        }
        else
        {
            IC_Value2 = TIM_GetCapture1(TIM3);  // 第二次捕获
            IC_Duty = IC_Value2 - IC_Value1;    // 周期(计数值)
            IC_Flag = 0;
        }
    }
}
// 频率 = 时钟频率 / ((PSC+1) × IC_Duty)
// 占空比需要配合下降沿捕获计算
关键点:① 输入捕获会自动锁存 CNT 到 CCR;② 需要至少两次捕获才能计算周期;③ 溢出处理要考虑 16 位计数器回绕。

🎯自测(点击展开)

STM32 定时器分几类?各有哪些?
三类:高级控制定时器(TIM1/TIM8)、通用定时器(TIM2~TIM5)、基本定时器(TIM6/TIM7)。
定时器的三种计数模式是什么?
向上计数(0→ARR)、向下计数(ARR→0)、中央对齐(0→ARR→0)。
定时时间如何计算?写出公式。
定时时间 = (ARR+1) × (PSC+1) / 定时器时钟频率。例如 72MHz、PSC=71、ARR=999,则定时 1ms。
PWM 占空比如何计算?
占空比 = CCR / (ARR+1) × 100%。CCR 是比较寄存器值,ARR 是自动重装载值。
PWM 输出时 GPIO 应配置为什么模式?为什么?
复用推挽输出(AF_PP)。因为 PWM 信号由定时器外设产生,需要通过复用功能输出到引脚。
高级定时器输出 PWM 时为什么必须调用 TIM_CtrlPWMOutputs()?
高级定时器有互补输出和死区控制功能,默认 PWM 输出是关闭的,必须显式使能主输出(MOE 位)。
输入捕获的原理是什么?
当外部信号边沿到来时,定时器自动将当前计数器 CNT 的值锁存到捕获寄存器 CCR 中,软件读取 CCR 即可得到捕获时刻。
定时器时钟来自哪个总线?
TIM1/TIM8 挂在 APB2(72MHz),TIM2~TIM7 挂在 APB1(36MHz,但倍频后也是 72MHz)。
基本定时器 TIM6/TIM7 有什么功能限制?
只能向上计数、只能定时,没有输入捕获、输出比较、PWM 功能。常用于 DAC 触发。
HAL 库中如何动态修改 PWM 占空比?
使用宏 __HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_x, newValue) 或标准库函数 TIM_SetCompare1()。
PWM 模式 1 和模式 2 有什么区别?
模式 1:CNT < CCR 时输出有效电平(高电平),CNT ≥ CCR 时输出无效电平。模式 2 相反。
中断服务函数中为什么必须清除标志位?
如果不清除标志位,中断条件一直满足,CPU 会反复进入中断服务函数,导致程序卡死在中断中。

📝强化题库

选择题点选即时判分;填空题输入后"检查"或"显示答案"。

已答 0/0答对 0正确率
已答 0/0答对 0