STM32 定时器学习笔记:TIM 架构、输入捕获与 PWM 一篇讲透

本文是一篇系统的 STM32 定时器(TIM)学习笔记:前半部分讲透定时器的整体架构(时基单元/PSC/CNT/ARR/影子寄存器)、输入捕获与输出比较的原理,后半部分用 CubeMX + HAL 库完成四个实战(定时中断点灯 → PWM 呼吸灯 → 舵机控制 → 输入捕获测频率占空比),最后附 HAL 函数速查表、FAQ 和面试考点。

适合:单片机/嵌入式入门同学,以及需要快速复习定时器知识点应付笔试面试的同学。

阅读路线

  • 赶时间直接上手 → 看 第七章 四个实战

  • 面试/考试抱佛脚 → 看 第九章速查表 + FAQ

  • 想真正理解 → 从头读,重点吃透第二、五章。


一、先认识一下:STM32 的定时器家族

STM32F103 里有 8 个定时器(TIM1~TIM8),按能力分三类。很多人学定时器一上来就扎进寄存器细节,结果越学越晕------先分清家族成员,再决定用谁

基本定时器 TIM6/7 通用定时器 TIM2~5 高级定时器 TIM1/8
计数模式 只有向上 向上/向下/中央对齐 同通用
通道 4 个(CH1~CH4) 4 个 + 互补输出
特色功能 触发 DAC 输入捕获、PWM、编码器接口 重复计数器、死区、刹车
挂载总线 APB1 APB1 APB2
一句话定位 硬件节拍器 多面手,本文主角 电机/电源玩家

📌 本文的三条主线:① 定时(时基单元)② PWM(输出比较通道)③ 输入捕获(输入通道)。它们共用同一个时基单元,学会时基就等于学会了 70%。


二、庖丁解牛:定时器架构与时基单元

2.1 整体结构图

看图记住两件事:

  1. 上半部分是"时基单元"(所有定时器都有):时钟源 → PSC 预分频 → CNT 计数器 → ARR 自动重装载 → 溢出产生更新事件(UEV);

  2. 下半部分是"四个通道"(通用/高级定时器才有):都是围绕着 CCR1~CCR4(捕获/比较寄存器)在工作------

    • 输入方向:GPIO 边沿时刻 → 把 CNT 值锁存 进 CCR(输入捕获);

    • 输出方向:CNT 与 CCR 比较 → 相等时驱动 GPIO(输出比较 / PWM)。

⚠️ 每个通道的 GPIO 和 CCR 是复用的,所以同一时刻一个通道只能在"输入捕获"和"输出比较/PWM"中二选一

2.2 时钟从哪来:一个经典坑

时钟源有四路:内部时钟 CK_INT(最常用)、ETR 外部引脚、其他定时器 ITRx(级联)、TI1 捕获通道。

用内部时钟时有个经典大坑(CSDN 上被问烂的):

当 APB 预分频系数 = 1 时,定时器时钟 = APB 时钟;当 APB 预分频系数 ≠ 1 时,定时器时钟 = APB 时钟 × 2

STM32F103 标准配置下 APB1 = 36MHz(分频系数 2),所以挂在 APB1 上的 TIM2~7,时钟实际上是 36 × 2 = 72MHz。这就是为什么几乎所有教程开头就说"定时器时钟是 72MHz"。

2.3 PSC / CNT / ARR 三大件

三个寄存器决定了定时器的一切节奏:

  • PSC(预分频器) :把时钟频率除以 (PSC+1),得到 CNT 的计数频率;

  • CNT(计数器):真正数数的家伙,16 位(TIM2/5 是 32 位);

  • ARR(自动重装载值):CNT 数到 ARR 就溢出清零(向上计数时),并产生更新事件。

📌 注意 &+1 的两处:分频是 ÷(PSC+1),计数是 0~ARR 共 (ARR+1) 个数。所以:

复制代码
                定时器时钟 f_TIM
更新频率 f = ─────────────────────────
              (PSC + 1) × (ARR + 1)

万能公式,背下来! 算两个例子:

  • 想每 1ms 进一次中断:72MHz ÷ (71+1) = 1MHz,再 ÷ (999+1) = 1kHz → PSC=71,ARR=999;

  • 想每 1s:PSC=7199、ARR=9999(72M ÷7200 =10kHz,÷10000 =1Hz)。

2.4 三种计数模式

  • 向上计数(最常用):0→ARR 溢出;

  • 向下计数:ARR→0 下溢;

  • 中央对齐:上去再下来,周期实际是 2×(ARR+1) 个计数------算频率时一定要注意!常用于电机控制,得到的 PWM 脉冲关于周期中点对称,谐波小。

2.5 影子寄存器(预装载):为什么改 ARR 不会"立刻"生效

ARR、PSC、CCR 这些寄存器,默认都有影子寄存器(Shadow Register)机制:你写入的值先进"预装载寄存器",等下一个更新事件(UEV)才统一"搬家"到真正干活的寄存器里。

好处是切换参数不会把当前周期搅乱(比如呼吸灯改占空比、改周期,波形不会产生毛刺)。代价是:你在代码里改了 ARR,发现没"马上"生效------这是特性,不是 bug。(也可以通过 ARPE 位关掉预装载立即生效,一般不建议。)


三、输出比较(Output Compare):PWM 的老爸

输出比较的逻辑极其简单:CNT 和 CCR 持续比较,相等的那一拍,执行预设动作

可选动作有:置位(Active)、复位(Inactive)、翻转(Toggle)、PWM模式1/2。其中翻转模式配好频率就能输出稳定方波;而 PWM,本质上就是输出比较的一种特殊规则(见下一章)。

输出比较的独特用法:

  • 生成精确的单个脉冲(到时刻触发一次,而不是周期输出);

  • 作为定时点事件:比如某个时刻触发 ADC 采样、触发其他定时器;

  • 精确控制脉冲数(如步进电机的脉冲输出)。


四、PWM 详解:频率、占空比与运行实战

4.1 原理一句话

PWM(脉冲宽度调制)= 周期固定(由 ARR 决定)+ 高电平宽度可调(由 CCR 决定)。

边沿对齐的 PWM 模式1 下,规则是:CNT < CCR → 输出有效电平;CNT ≥ CCR → 输出无效电平

ARR(自动重装载值):CNT 数到 ARR 就溢出清零(向上计数时),并产生更新事件。

4.2 三个核心公式

复制代码
PWM 频率     = f_TIM ÷ ((PSC+1) × (ARR+1))
PWM 占空比   = CCR ÷ (ARR+1)
分辨率(占空比步数) = ARR + 1 级   (ARR=999 → 千分之一步进)

💡 改频率动 ARR,改占空比动 CCR,两者完全解耦------这是理解 PWM 最重要的一句话。调 LED 亮度、电机速度时只动 CCR,频率别去碰(电机堵转啸叫往往就是 PWM 频率在人耳范围内,把它提到 20kHz 以上就安静了)。

  • PWM 模式1 vs 模式2:模式2 就是模式1 输出取反;

  • 边沿对齐 vs 中央对齐:中央对齐的脉冲居中对称,电机控制噪声更小,但频率公式要按三角波周期重新算;

  • 有效电平默认是高,也可以通过极性配置反转。


五、输入捕获(Input Capture):给信号"拍照"

5.1 原理:边沿一到,快门一按

输入捕获是 PWM 输出的"逆运算":当通道引脚检测到指定边沿(上升/下降/双边)时,硬件立刻把此刻 CNT 的值锁存进 CCR,同时可申请中断。

信号进入后依次经过:GPIO → 数字滤波器 (滤掉毛刺抖动)→ 边沿检测器(选极性)→ 通道选择(直连/交叉)→ 捕获预分频(每 N 次边沿抓一次)→ 锁存进 CCR。

5.2 测频率、测脉宽:思路一目了然

测频率:对同一个信号连续捕获两个相邻上升沿,两个捕获值之差 N 就是一个周期的计数:

复制代码
信号频率 f = f_CNT ÷ (C2 − C1)

测脉宽同理:上升沿捕获一次、下降沿捕获一次,差值就是高电平时间。

⚠️ 细节:信号周期很长时 CNT 中途会溢出,需要把更新中断里的溢出次数 × (ARR+1) 加进去(见实战四的讨论)。

5.3 进阶玩法:PWM 输入模式(一次同时测出频率和占空比)

这是输入捕获最精妙的设计:同一个引脚的信号,一路(TI1FP1)给 IC1,另一路(TI1FP2)交叉给 IC2 ,再配上从模式"复位"(TI1FP1 每来一个上升沿自动把 CNT 清零):

  • IC1(上升沿捕获)→ CCR1 = 一整个周期的计数 N1

  • IC2(下降沿捕获)→ CCR2 = 高电平期间的计数 N2

  • 频率 = f_CNT ÷ N1,占空比 = N2 ÷ N1。

全程硬件自动完成,软件连中断都可以不进,直接读两个寄存器就行。注意:PWM 输入模式只能用 CH1 或 CH2(只有 TI1FP1/TI2FP2 接到了从模式控制器)。

5.4 了解一下:编码器接口模式

通用定时器的 CH1/CH2 还能组成正交编码器接口:CH1 接 A 相、CH2 接 B 相,硬件根据两路相位差自动让 CNT 加/减计数------读 CNT 就知道电机转了多少、往哪转,常用于测速定位。配置在 CubeMX 的 "Encoder Mode" 里,本文不展开,知道有这个功能即可。


六、高级定时器的三件独家兵器

TIM1/TIM8 在通用定时器基础上多了三样(电机控制必备):

  1. 重复计数器 RCR:溢出 RCR+1 次才产生一次更新事件(更低频的更新中断);

  2. 互补输出 OCxN + 死区插入 :驱动 H 桥/三相桥时,上下两个 MOS 管绝不能同时导通(会直通短路炸管),死区就是切换瞬间强制双方都关断的一小段时间;

  3. 刹车输入 BKIN :外部紧急信号(过流/过压比较器输出)一到,硬件立刻把所有 PWM 输出拉到安全电平------这是纯硬件保护,比任何软件反应都快。

⚠️ 高级定时器还有个高频坑:用 TIM1/8 输出 PWM 时,除了 HAL_TIM_PWM_Start,还必须调用 __HAL_TIM_MOE_ENABLE(&htimx) 打开主输出使能,否则引脚毫无动静!(CubeMX 高级定时器部分工程会自动生成,但自己移植代码时经常忘。)


七、上手实战(STM32CubeMX + HAL)

四个实战从易到难,共用一个通用配置流程:

7.1 实战一:定时器中断,精准 1 秒闪一次 LED

目标:不用 HAL_Delay,让 LED 以严格的 1 秒周期翻转(同时主循环可以干别的事)。

CubeMX 配置:TIM3 → Clock Source = Internal Clock;PSC=7199,ARR=9999(72MHz ÷ 7200 = 10kHz,÷ 10000 = 1Hz);NVIC 里勾选 TIM3 全局中断。

用户代码:

复制代码
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim3);     // 启动时基 + 更新中断
/* USER CODE END 2 */
​
/* 更新中断回调(溢出一次进一次) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3)
    {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);   // 板载LED翻转
    }
}

要点解析

  • HAL_TIM_Base_Start() 是阻塞版启动,Start_IT 才开中断------忘了这是"不进中断"的头号原因;

  • 中断回调是所有定时器共用 的,必须判断 htim->Instance 是谁;

  • 中断里别做事 :快进快出,设个标志位让主循环处理,更不要在回调里 HAL_Delay(会卡死 SysTick 或死锁,看优先级)。

7.2 实战二:PWM 呼吸灯

目标:LED 亮度像呼吸一样平滑渐变。

CubeMX 配置:TIM3 → Channel1 选 PWM Generation CH1 (PA6 自动变复用推挽);PSC=71 (1MHz)、ARR=999(PWM 频率 1kHz);Pulse 先填 0。

用户代码:

复制代码
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);     // 启动PWM输出
​
while (1)
{
    for (int d = 0; d <= 999; d += 5)         // 渐亮
    {
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, d);  // 改CCR=改占空比
        HAL_Delay(5);
    }
    for (int d = 999; d >= 0; d -= 5)         // 渐暗
    {
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, d);
        HAL_Delay(5);
    }
}

要点解析

  • __HAL_TIM_SET_COMPARE 就是写 CCRx,改的是占空比,频率纹丝不动;

  • 人眼对亮度的感知是非线性的,想要"看起来更顺滑"的呼吸感,可以把步进做成指数曲线;

  • 若改颜色(RGB 三通道 PWM),一个定时器的 3 个通道即可同时驱动一颗 RGB 灯。

7.3 实战三:PWM 驱动舵机 SG90

舵机是最经典的 PWM 应用:固定 50Hz,脉宽 0.5~2.5ms 对应 0°~180°

CubeMX 配置:TIM3 CH1 PWM 输出;PSC=71 (1MHz,1 个计数 = 1µs)、ARR=19999(周期 = 20000µs = 20ms);Pulse 初始 1500(中位)。

用户代码:

复制代码
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
​
/* angle: 0~180 度 */
void Servo_SetAngle(float angle)
{
    /* 500~2500us 脉宽线性映射 */
    uint32_t ccr = (uint32_t)(500 + angle * (2000.0f / 180.0f));
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, ccr);
}

要点解析

  • 舵机电源要单独供电 并和单片机共地,直接从板子 5V 引大电流会复位或抖动;

  • 不同型号舵机脉宽范围略有差异(有的 0.5~2.4ms),以实测为准;

  • 数字舵机刷新率可以提高(如 300Hz),普通 SG90 保持 50Hz 就好。

7.4 实战四:PWM 输入模式测量信号的频率和占空比

目标:用 PA6(TIM3_CH1)测量外部一路 PWM(比如另一个板子发的 1kHz/50% 信号),算出频率和占空比。用第五章讲的 PWM 输入模式 + 从模式复位,硬件全自动。

CubeMX 配置(关键三步):

  1. TIM3 → Channel1 选 Input Capture direct mode ;Channel2 选 Input Capture indirect mode(交叉到同一引脚);

  2. CH1 极性 Rising(测周期)、CH2 极性 Falling(测高电平);PSC=71(1MHz)、ARR=65535;

  3. Slave Mode = Reset Mode,Trigger Source = TI1FP1(每次上升沿自动清零 CNT),NVIC 勾 TIM3 中断。

用户代码:

复制代码
uint32_t pwm_period = 0, pwm_pulse = 0;
float    freq = 0, duty = 0;
​
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);   // 两路都启动
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
​
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
        pwm_period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // N1:整个周期
        pwm_pulse  = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);  // N2:高电平
        if (pwm_period > 0)
        {
            freq = 1000000.0f / pwm_period;          // f_CNT = 1MHz
            duty = 100.0f * pwm_pulse / pwm_period;  // 占空比 %
        }
    }
}

要点解析

  • 理解的关键就是第五章那张图:CCR1 存周期、CCR2 存高电平,复位模式让每周期重新数;

  • 量程边界:ARR=65535、1MHz 计数 → 最低可测约 15Hz(再低 CNT 溢出怎么办?可以在更新中断里累计溢出次数再补偿,或者干脆把 PSC 调大让计数更慢);

  • 频率很高的信号(如 >100kHz)可以把 PSC 调小以提高分辨率,或换"测周法→测频法"的思路:固定时间窗口数上升沿个数。


八、HAL 常用函数/宏速查表

功能 函数 / 宏 说明
时基启动 HAL_TIM_Base_Start(&htimx) 阻塞/轮询方式
时基 + 中断 HAL_TIM_Base_Start_IT(&htimx) 更新中断走 PeriodElapsedCallback
时基 + DMA HAL_TIM_Base_Start_DMA(...) 少见,用于定时搬运
PWM 输出 HAL_TIM_PWM_Start(&htimx, TIM_CHANNEL_x) 记得高级定时器加 __HAL_TIM_MOE_ENABLE
改占空比 __HAL_TIM_SET_COMPARE(&htimx, ch, val) 写 CCRx
读占空比 __HAL_TIM_GET_COMPARE(&htimx, ch) 读 CCRx
动态改周期 __HAL_TIM_SET_AUTORELOAD(&htimx, val) 写 ARR(注意影子寄存器)
动态改分频 __HAL_TIM_SET_PRESCALER(&htimx, val) 写 PSC
输入捕获 + 中断 HAL_TIM_IC_Start_IT(&htimx, ch) 回调 HAL_TIM_IC_CaptureCallback
读捕获值 HAL_TIM_ReadCapturedValue(&htimx, ch) 读 CCRx
输出比较 HAL_TIM_OC_Start_IT(&htimx, ch) 回调 HAL_TIM_OC_DelayElapsedCallback
编码器模式 HAL_TIM_Encoder_Start(&htimx, TIM_CHANNEL_ALL) __HAL_TIM_GET_COUNTER 得位置

九、常见问题 FAQ

Q1:为什么手册/教程都说定时器时钟 72MHz,APB1 明明是 36MHz? A:见 2.2 节------APB 预分频系数 ≠1 时,定时器时钟自动 ×2。这是 STM32 定时器最经典的坑,没有之一。

Q2:我配了 PSC/ARR,中断频率却差一倍/两倍? A:按出现概率排查:① 忘记 +1(PSC、ARR 实际作用都要 +1);② 中央对齐模式周期要 ×2;③ 用错了 APB 时钟(TIM1/8 在 APB2);④ 高级定时器忘设 RCR。

Q3:更新中断死活不进? A:三步检查:NVIC 里没勾定时器全局中断 / 用了 HAL_TIM_Base_Start 而不是 Start_IT / MSP 里时钟没使能(CubeMX 生成的一般没问题)。

Q4:高级定时器(TIM1)配了 PWM,引脚没有输出? A:99% 是忘了 __HAL_TIM_MOE_ENABLE(&htim1)。剩 1% 查 AFIO 复用和极性配置。

Q5:输入捕获读到的值一直是 0? A:检查极性选错没有(上升沿 vs 下降沿)、从模式复位把 CNT 清了但捕获顺序没配对、以及 HAL_TIM_IC_Start_IT 启动了没。另外被测信号的幅度得达到 CMOS 电平标准(3.3V 系统别用 1.8V 信号直接怼)。

Q6:舵机/电机一动,单片机就复位或串口乱码? A:典型的电源/地问题:电机这些"电老虎"和 MCU 共用一路脆弱供电,电流突变拉垮电压。路数:分开供电、共地、加电容、必要时光耦隔离、加续流二极管。

Q7:PWM 驱动电机有"滋滋"啸叫? A:PWM 频率落在人耳可闻范围(20Hz~20kHz),线圈/电容在振动。把 ARR 调小把频率提到 20kHz 以上即可(注意频率越高占空比分辨率越低,自己权衡)。

Q8:定时器能不能级联(一个定时器触发另一个)? A:可以!主定时器的更新事件/OC 信号通过 ITRx 送给从定时器作时钟源或触发,CubeMX 里 Master/Slave 配置,做高精度长时间计时或同步采集常用。


十、知识点速查表(背完敢去面试)

  • 定时器家族:基本(TIM6/7 仅时基)/ 通用(TIM2~5 带 4 通道)/ 高级(TIM1/8 加 RCR、互补死区、刹车)

  • 时基三件套:PSC 分频(÷(PSC+1))、CNT 计数、ARR 重装载(0~ARR 共 ARR+1 个数)

  • 万能公式:更新频率 = f_TIM ÷ ((PSC+1)×(ARR+1))

  • APB 预分频≠1 → 定时器时钟 ×2

  • 计数模式:向上 / 向下 / 中央对齐(周期×2,PWM 对称)

  • 影子寄存器:改 ARR/CCR 写入预装载,UEV 才生效,波形无毛刺;

  • 输出比较:CNT==CCR 触发动作(置位/复位/翻转/PWM);

  • PWM:频率靠 ARR、占空比靠 CCR,两者解耦;模式1 vs 模式2 = 正反相;

  • 输入捕获:边沿锁存 CNT 到 CCR;测频 f = f_CNT ÷ (C2−C1);溢出要补 N×(ARR+1);

  • PWM 输入模式:IC1 直连测周期(N1)、IC2 交叉测高电平(N2)+ 从模式复位 → 硬件同时出频率和占空比;只能用 CH1/CH2;

  • 高级定时器三支箭:RCR 重复计数、互补+死区(防桥臂直通)、BKIN 刹车;

  • 高级定时器 PWM 必开 __HAL_TIM_MOE_ENABLE

  • 实战三板斧:Base_Start_IT(定时)、PWM_Start(输出)、IC_Start_IT(捕获)。


十一、进阶阅读方向

  • 定时器级联做 32 位/64 位超长定时(主从触发 ITRx);

  • 编码器接口 + M 法/T 法测速(电机控制入门);

  • 输出比较 + DMA 输出任意波形(简易信号发生器);

  • 高级定时器六路互补 PWM 驱动三相 BLDC(FOC 入门);

  • 定时器触发 ADC(与 ADC 那篇笔记联动,做精确采样率采集)。

参考资料

  1. ST《STM32F10x 参考手册 RM0008》第 13/14/15 章:General-purpose / Advanced-control / Basic timers

  2. 【野火】《STM32库开发实战指南》TIM 高级定时器章节:32. TIM---高级定时器 --- 野火STM32库开发实战指南------基于野火MINI开发板 文档

  3. 【CSDN】STM32 定时器"输入捕获"和"输出比较"(HAL 库 + 实例讲解):STM32 定时器"输入捕获"和"输出比较"(HAL 库 + 实例讲解)_stm32输入捕获和输出比较-CSDN博客

  4. 【CSDN】STM32 学习笔记------TIM 输入捕获:STM32学习笔记------TIM输入捕获_stm32 tim pwm捕获-CSDN博客

  5. 【博客园】STM32外设学习------TIM定时器------输入捕获测频:STM32外设学习--TIM定时器--输入捕获---测频办法。 - tlnshuju - 博客园

  6. 【知乎】"1,2,3,定时器"------跟我一起写STM32:https://zhuanlan.zhihu.com/p/599311172


📝 这篇笔记把定时器拆成"时基 → 输出 → 输入"三条线,配合四张原理时序图和四个可跑的实战,力求一遍读通。文中如有疏漏,欢迎评论区指正,一起进步!

相关推荐
PC2005-cloud1 小时前
KubeSphere学习笔记:部署nginx
笔记·学习·nginx
Bernice橘子1 小时前
职场英语学习计划Day033
学习
砚凝霜2 小时前
软考网络工程师|第 5 章 路由基础、静态路由、RIP、OSPF、BGP、IS-IS 完整备考笔记
网络·笔记
依然鸣2 小时前
PTA团体程序设计天梯赛L2真题讲解L2-045-048
数据结构·c++·经验分享·学习·算法·pat考试·pat
星火跨境XINGHUOS2 小时前
独立站搭建后去哪里学运营?全链路学习路径拆解
学习
统计学小王子3 小时前
《数学少年-从正负号到几何原本》(第三章:“进退加减,循规而行“)--3.3 奶茶店流水账
学习·初中数学科普·数学科普
2601_965799683 小时前
在线培训考试系统全功能解析:助力企业打造高效人才培养体系
大数据·人工智能·笔记·功能测试
0566464 小时前
RAG 向量检索:从“查字“到“查意“
数据库·人工智能·学习·oracle
却道天凉_好个秋4 小时前
音视频学习(一百零二):全彩夜视
学习·音视频·全彩夜视