12.STM32 定时器(TIM)从原理到实战:四种核心工作模式详解

STM32——TIMER(定时器)篇 使用纯软件(CPU死等)的方式实现定时(延时)功能缺点:1. 延时不准确 2. CPU死等。主要特性:16位递增计数器(计数值:0~65535)16位预分频器(分频系数:1~65536)可用于触发DAC在更新事件(计数器溢出)时,会产生中断/DMA请求主要特性:16位递增、递减、中心对齐计数器(计数值:0~65535)16位预分频器(分频系数:1~65536)可用于触发DAC、ADC在更新事件、触发事件、输入捕获、输出比较时,会产生中断/DMA请求。 阅读详情

摘要:本文系统讲解 STM32 定时器(TIM)的核心原理与四种常用工作模式。文章从定时器的基本概念和分类入手,介绍时钟来源、关键寄存器(PSC、ARR、CNT、CCR)以及定时时间计算公式;随后重点剖析基本定时中断、PWM 输出、输入捕获和编码器接口四种模式,并结合 CubeMX 配置与 HAL 库代码给出 LED 翻转、呼吸灯、PWM 频率/占空比测量等实战案例;最后总结 HAL 库使用步骤、常见注意事项以及 PSC/ARR 差 1、时钟源配置错误、中断不触发、PWM 无输出等高频问题的排查方法。

一、TIM 定时器是什么

STM32 的 TIM(Timer)是内核之外的独立硬件外设,核心是一个可编程计数器,在时钟驱动下自动累加/递减,达到设定值时产生事件或中断。它不占用 CPU 循环,是嵌入式中“精确延时、定时任务、波形生成、信号测量”的基础。

二、定时器分类(以 STM32F4 为例)

类型

代表型号

计数器位数

主要功能

基本定时器

TIM6、TIM7

16 位

仅定时 + 触发 DAC,无外部通道

通用定时器(全功能)

TIM2、TIM3、TIM4、TIM5

TIM2/TIM5 为 32 位;TIM3/TIM4 为 16 位

定时中断、输入捕获、输出比较、PWM、编码器接口;均为 4 通道

通用定时器(简化)

TIM9~TIM14

16 位

定时、PWM、输入捕获;TIM9/TIM12 为 2 通道,TIM10/11/13/14 为 1 通道

高级定时器

TIM1、TIM8

16 位

在通用基础上增加互补 PWM 输出、死区时间、刹车输入,多用于电机驱动

F4/H7 等系列部分定时器为 32 位(如 TIM2/TIM5),计数范围更大。

三、核心工作原理

TIM 定时器内部结构框图

上图是 TIM 定时器的内部结构框图,展示了预分频器(PSC)、自动重装载寄存器(ARR)、计数器(CNT)以及捕获/比较通道(CH1~CH4)之间的连接关系,是理解后续时钟配置和计数流程的基础。

1. 时钟来源

定时器挂在 APB1 或 APB2 总线上。当 APB 预分频系数 ≠ 1 时,定时器时钟 = APB 总线时钟 × 2。

以 F103(72 MHz 系统时钟)为例:

  • APB1 = 36 MHz → TIM2~7 时钟 = 72 MHz

  • APB2 = 72 MHz → TIM1 时钟 = 72 MHz

2. 关键寄存器

寄存器

作用

PSC(预分频器)

对输入时钟再分频,实际计数时钟 = TIM_CLK / (PSC+1)

ARR(自动重装载值)

计数器计数上限,计到 ARR 后 并重新开始

CNT(计数器)

当前计数值,硬件自动累加

CCR1~CCR4

捕获/比较寄存器,用于 PWM 占空比或输入捕获

DIER / SR

中断使能 / 状态标志

3. 定时时间计算公式

举例:72 MHz 时钟,想产生 1 ms 中断:

  • 取 PSC = 71 → 计数时钟 = 72 MHz / 72 = 1 MHz(1 μs 计一次)

  • 取 ARR = 999 → 1000 × 1 μs = 1 ms

四、四种核心工作模式

配置定时器:咱们用的 F4 芯片有 8 个 TIM 定时器

STM32CubeMX 中 TIM2 的时钟源选择界面

下图是 STM32CubeMX 中 TIM 的时钟源选择界面,这里需要勾选 Internal Clock(内部时钟),让定时器使用 APB1 总线提供的时钟信号。

  • 使能 TIM

STM32CubeMX 中使能 TIM2 的界面

  • 定时器配置

TIM2 参数配置界面,设置预分频和自动重装载值

上图是 TIM1 的参数配置界面,在这里设置预分频系数(Prescaler)和自动重装载值(Counter Period),这两个参数直接决定定时中断的时间间隔。

TIM2 中断使能配置界面

上图是 TIM1 的中断使能配置界面,勾选 TIM1 global interrupt 后,定时器溢出时才会触发更新中断,从而进入中断回调函数。

以下是在 Keil 工程中找到 TIM 定时器中断函数的方法:

1. 基本定时中断(最常用)

计数器 时触发更新中断(Update Interrupt),在中断服务函数里执行周期性任务。

// HAL 库示例:1 ms 定时中断
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;        // 72MHz / 72 = 1MHz
htim2.Init.Period = 999;          // 1MHz / 1000 = 1kHz → 1ms
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2);    // 启动定时中断

// 中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM2) {
        // 每 1ms 执行一次
    }
}
实战案例:TIM1 实现 LED 每秒翻转一次

下面以 STM32F4 系列为例,使用高级定时器 TIM1 实现 LED 每 1 秒翻转一次。系统时钟为 168 MHz,APB2 总线时钟为 84 MHz,由于 APB2 预分频系数为 2(不等于 1),TIM1 的时钟 = 84 MHz × 2 = 168 MHz。

第一步:CubeMX 配置

  1. 在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz。

  2. 在 Timers 中选择 TIM1,勾选 Internal Clock(内部时钟),使 TIM1 使用 APB2 总线提供的时钟信号。

  3. 在 Parameter Settings 中设置:

    • Prescaler(预分频系数)= 1679,即 PSC = 1679,实际分频 = 168 MHz / (1679+1) = 100 kHz(10 μs 计一次)

    • Counter Period(自动重装载值)= 4999,即 ARR = 4999,溢出周期 = 5000 × 10 μs = 50 ms

    • Counter Mode = Up(向上计数)

  4. 在 NVIC Settings 中勾选 TIM1 update interrupt,使能 TIM1 更新中断。

  5. 在 GPIO 中配置一个普通输出引脚(如 PA5)作为 LED 控制引脚,输出模式为 Push-Pull,速度可选 Low。

  6. 生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。

第二步:HAL 库完整代码

在 main.c 中,先定义一个全局变量用于计数:

// main.c 中定义全局变量
volatile uint16_t tim1_50ms_count = 0;   // 50ms 中断计数

在 main() 函数中初始化 LED 引脚并启动 TIM1 定时中断:

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    // 初始化 LED 引脚(PA5)
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    // 启动 TIM1 定时中断
    HAL_TIM_Base_Start_IT(&htim1);

    while (1)
    {
        // 主循环可执行其他任务
    }
}

在 stm32f4xx_it.c 中,TIM1 更新中断服务函数会调用 HAL 库的回调函数。在 main.c 末尾重写回调函数,实现 LED 每秒翻转一次:

// 中断回调:每 50ms 进入一次,累计 20 次(1 秒)翻转 LED
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1)
    {
        tim1_50ms_count++;
        if (tim1_50ms_count >= 20)   // 20 × 50ms = 1000ms = 1s
        {
            tim1_50ms_count = 0;
            HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);   // 翻转 LED
        }
    }
}

第三步:预期现象

  • 编译下载后,LED 以 1 秒间隔稳定翻转(亮 1 秒、灭 1 秒)。

  • 若使用示波器测量 PA5 引脚,可观察到周期为 2 秒、占空比 50% 的方波。

  • 若 LED 翻转速度异常,优先检查 PSC 和 ARR 是否差 1,以及 TIM1 时钟是否为 168 MHz。

2. PWM 输出(输出比较模式)

CNT 从 0 累加到 ARR,当 CNT < CCRx 时输出高电平,CNT ≥ CCRx 时输出低电平,从而产生占空比可调的方波。

  • 频率 = f_TIM / [(PSC+1) × (ARR+1)]

  • 占空比 = CCRx / (ARR+1)

// 输出 PWM,ARR=999(分辨率 1/1000)
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500); // 50% 占空比
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
实战案例:TIM1_CH1 输出 1kHz、50% 占空比 PWM 驱动 LED 呼吸灯

下面以 STM32F4 系列为例,使用高级定时器 TIM1 的通道 1(PA8)输出 1 kHz、50% 占空比的 PWM 波形,并通过动态调节占空比实现 LED 呼吸灯效果。系统时钟为 168 MHz,APB2 总线时钟为 84 MHz,由于 APB2 预分频系数为 2(不等于 1),TIM1 的时钟 = 84 MHz × 2 = 168 MHz。

第一步:CubeMX 配置

  1. 在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz,APB2 总线时钟为 84 MHz。

  2. 在 Timers 中选择 TIM1,勾选 Internal Clock(内部时钟),使 TIM1 使用 APB2 总线提供的时钟信号。

  3. 在 Parameter Settings 中设置:

    • Prescaler(预分频系数)= 167,即 PSC = 167,实际分频 = 168 MHz / (167+1) = 1 MHz(1 μs 计一次)

    • Counter Period(自动重装载值)= 999,即 ARR = 999,PWM 周期 = 1000 × 1 μs = 1 ms,对应频率 = 1 kHz

    • Counter Mode = Up(向上计数)

    • Pulse(初始 CCR1 值)= 500,即初始占空比 = 500 / 1000 = 50%

    • CH Polarity = High(高电平有效)

  4. 在 GPIO Settings 中确认 PA8 已被自动配置为 AF1(TIM1_CH1 复用功能),输出模式为 Push-Pull,速度可选 High。

  5. 在 NVIC Settings 中勾选 TIM1 update interrupt,使能 TIM1 更新中断(用于中断方式实现呼吸灯)。

  6. 生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。

第二步:HAL 库完整代码

在 main.c 中,先定义一个全局变量用于呼吸灯方向控制:

// main.c 中定义全局变量
volatile uint16_t pwm_duty = 0;      // 当前占空比(0~999)
uint8_t duty_direction = 1;          // 1=递增,0=递减

在 main() 函数中初始化 TIM1 PWM 输出并启动。注意:高级定时器 TIM1 必须先使能主输出(MOE),否则即使配置正确也没有波形输出:

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    // 高级定时器 TIM1 必须使能主输出(MOE),否则无波形输出
    __HAL_TIM_MOE_ENABLE(&htim1);

    // 启动 TIM1_CH1 PWM 输出(PA8)
    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

    while (1)
    {
        // 呼吸灯:占空比从 0 递增到 999,再从 999 递减到 0
        if (duty_direction == 1)
        {
            pwm_duty += 5;
            if (pwm_duty >= 999)
            {
                pwm_duty = 999;
                duty_direction = 0;
            }
        }
        else
        {
            pwm_duty -= 5;
            if (pwm_duty <= 0)
            {
                pwm_duty = 0;
                duty_direction = 1;
            }
        }

        // 更新 CCR1,改变占空比
        __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_duty);

        HAL_Delay(10);   // 每 10ms 调整一次,呼吸周期约 4 秒
    }
}

若希望呼吸灯在定时器中断中更新,可在 main.c 末尾重写回调函数,配合 TIM1 更新中断实现:

// 中断回调:每 1ms 进入一次,动态调整占空比实现呼吸灯
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1)
    {
        if (duty_direction == 1)
        {
            pwm_duty += 1;
            if (pwm_duty >= 999)
            {
                pwm_duty = 999;
                duty_direction = 0;
            }
        }
        else
        {
            pwm_duty -= 1;
            if (pwm_duty <= 0)
            {
                pwm_duty = 0;
                duty_direction = 1;
            }
        }
        __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_duty);
    }
}

第三步:预期现象

  • 编译下载后,LED 呈现由暗到亮、再由亮到暗的呼吸效果,周期约 2 秒。

  • 若使用示波器测量 PA8 引脚,可观察到频率为 1 kHz、占空比在 0%~100% 之间周期性变化的方波。

  • 若 LED 亮度不变或没有输出,优先检查 GPIO 复用配置是否正确(PA8 必须为 AF1),以及 MOE 主输出是否已使能。

第四步:常见问题排查

  • 无波形输出:检查 GPIO 是否配置为复用功能(AF1),确认 HAL_TIM_PWM_Start() 已调用,并确认已执行 __HAL_TIM_MOE_ENABLE(&htim1) 使能主输出。

  • 频率不对:确认 TIM1 时钟是否为 168 MHz,PSC 和 ARR 是否差 1(实际分频 = PSC+1,周期 = ARR+1)。

  • 占空比不变化:确认 __HAL_TIM_SET_COMPARE() 的通道参数为 TIM_CHANNEL_1,且 CCR1 值在 0~ARR 范围内。

  • 引脚无输出但代码正常:确认 PA8 是否被其他外设占用,检查 CubeMX 中 PA8 的复用功能是否确实为 TIM1_CH1(AF1)。

  • LED 亮度异常:检查 LED 限流电阻是否合适,PWM 频率过低(如低于 100 Hz)会导致 LED 明显闪烁。

3. 输入捕获

测量外部信号的频率、周期、脉宽。当通道引脚出现设定边沿(上升/下降)时,硬件自动锁存当前 CNT 值到 CCR 寄存器。

典型用法:两次捕获的 CNT 差值即为信号周期。

// HAL 库示例:输入捕获测量信号周期(以 TIM3_CH1 为例)
// 1. 初始化配置:TIM3 时钟 72MHz,预分频 71 → 计数时钟 1MHz(1us 计一次)
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71;                 // 72MHz / 72 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF;                // 16 位计数器最大值
HAL_TIM_IC_Init(&htim3);

// 配置输入捕获通道:上升沿捕获,直接映射到 TI1
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;  // 上升沿触发捕获
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;        // 直接映射
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;                  // 不分频
sConfigIC.ICFilter = 0;                                  // 不滤波
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);

// 2. 启动函数:使能捕获通道和捕获中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);

// 3. 中断回调处理:两次捕获差值即为信号周期
uint32_t capture_val = 0;    // 上一次捕获的 CNT 值
uint32_t period_us = 0;      // 测量到的周期(us)

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
        uint32_t now = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
        period_us = (now >= capture_val) ? (now - capture_val) : (0xFFFF - capture_val + now);
        capture_val = now;   // 更新基准值,等待下一次捕获
    }
}
实战案例:TIM3_CH1 测量外部 PWM 信号的频率和占空比

下面以 STM32F4 系列为例,使用通用定时器 TIM3 的通道 1(PA6)测量外部 PWM 信号的频率和占空比。系统时钟为 168 MHz,APB1 总线时钟为 84 MHz,由于 APB1 预分频系数为 2(不等于 1),TIM3 的时钟 = 84 MHz × 2 = 168 MHz。

第一步:CubeMX 配置

  1. 在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz,APB1 总线时钟为 84 MHz。

  2. 在 Timers 中选择 TIM3,在 Channel1 下拉框中选择 Input Capture direct mode(输入捕获直接模式),使 TIM3_CH1 使用 APB1 总线提供的时钟信号。

  3. 在 Parameter Settings 中设置:

    • Prescaler(预分频系数)= 167,即 PSC = 167,实际分频 = 168 MHz / (167+1) = 1 MHz(1 μs 计一次)

    • Counter Period(自动重装载值)= 0xFFFF = 65535,即 ARR = 65535,16 位计数器最大值,避免测量长周期信号时溢出

    • Counter Mode = Up(向上计数)

    • Polarity Selection = Rising Edge(上升沿捕获)

    • IC Selection = Direct(直接映射到 TI1)

    • Prescaler(捕获分频) = No division(不分频)

    • Filter = 0(不滤波)

  4. 在 GPIO Settings 中确认 PA6 已被自动配置为 AF2(TIM3_CH1 复用功能),输入模式。

  5. 在 NVIC Settings 中勾选 TIM3 global interrupt,使能 TIM3 捕获中断。

  6. 生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。

第二步:HAL 库完整代码

在 main.c 中,先定义全局变量用于保存捕获值和计算结果:

// main.c 中定义全局变量
volatile uint32_t ic_capture_val = 0;    // 上一次捕获的 CNT 值
volatile uint32_t ic_period_us = 0;      // 信号周期(us)
volatile uint32_t ic_freq_hz = 0;        // 信号频率(Hz)
volatile uint32_t ic_duty = 0;           // 占空比(0~1000,对应 0.0%~100.0%)
volatile uint8_t  ic_capture_count = 0;  // 捕获计数,用于区分上升沿和下降沿

在 main() 函数中初始化 TIM3 输入捕获并启动:

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    // 启动 TIM3_CH1 输入捕获(PA6)
    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);

    while (1)
    {
        // 主循环可周期打印测量结果
        // printf("Freq = %lu Hz, Duty = %lu.%lu%%\r\n",
        //        ic_freq_hz, ic_duty / 10, ic_duty % 10);
        HAL_Delay(500);
    }
}

在 stm32f4xx_it.c 中,TIM3 捕获中断服务函数会调用 HAL 库的回调函数。在 main.c 末尾重写回调函数,实现频率和占空比测量:

// 中断回调:测量 PWM 频率和占空比
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
        uint32_t now = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);

        if (ic_capture_count == 0)
        {
            // 第一次上升沿:记录基准值
            ic_capture_val = now;
            ic_capture_count = 1;

            // 切换为下降沿捕获,测量高电平脉宽
            __HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
                                          TIM_INPUTCHANNELPOLARITY_FALLING);
        }
        else if (ic_capture_count == 1)
        {
            // 下降沿:计算高电平脉宽(占空比)
            uint32_t high_us = (now >= ic_capture_val) ?
                               (now - ic_capture_val) :
                               (0xFFFF - ic_capture_val + now);
            ic_duty = high_us * 1000 / ic_period_us;   // 占空比 = 高电平 / 周期
            ic_capture_count = 2;

            // 切换为上升沿捕获,测量完整周期
            __HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
                                          TIM_INPUTCHANNELPOLARITY_RISING);
        }
        else
        {
            // 第二次上升沿:计算周期和频率
            uint32_t period = (now >= ic_capture_val) ?
                              (now - ic_capture_val) :
                              (0xFFFF - ic_capture_val + now);
            ic_period_us = period;
            ic_freq_hz = 1000000UL / period;   // 频率 = 1 / 周期
            ic_capture_val = now;
            ic_capture_count = 1;

            // 切换为下降沿捕获,准备测量下一个高电平脉宽
            __HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
                                          TIM_INPUTCHANNELPOLARITY_FALLING);
        }
    }
}

第三步:预期现象

  • 将信号发生器输出 1 kHz、50% 占空比的 PWM 接到 PA6,串口打印结果应为 Freq = 1000 Hz,Duty = 50.0%。

  • 改变信号发生器频率或占空比,串口打印值应随之实时更新,误差通常在 ±1% 以内。

  • 若使用示波器对比,测量结果应与示波器读数基本一致。

第四步:常见问题排查

  • 捕获不到中断:检查 PA6 是否配置为复用功能(AF2),确认 HAL_TIM_IC_Start_IT() 已调用,并确认 NVIC 中 TIM3 全局中断已使能。

  • 频率测量偏大或偏小:确认 TIM3 时钟是否为 168 MHz,PSC 是否差 1(实际分频 = PSC+1)。若信号频率较高,可适当增大预分频以降低计数时钟。

  • 占空比始终为 0 或 100%:检查边沿切换逻辑是否正确,确认 __HAL_TIM_SET_CAPTUREPOLARITY() 的通道参数为 TIM_CHANNEL_1。

  • 测量结果跳变不稳定:信号抖动较大时,可在 CubeMX 中适当增大 Filter 值(如 0x0F)进行硬件滤波,或检查信号源是否稳定。

  • 低频率信号测量不准:当信号频率低于约 15 Hz 时,周期可能超过 16 位计数器范围(65535 μs),此时应增大 PSC 降低计数时钟,或改用 32 位定时器(如 TIM2/TIM5)。

4. 编码器接口

通用定时器的 CH1、CH2 可配置为正交编码器接口,直接读取旋转编码器的脉冲和方向,无需外部中断,CPU 开销极低。

// HAL 库示例:正交编码器接口(以 TIM4_CH1/CH2 为例)
// 1. 初始化配置:编码器模式,计数范围 0~999
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0;                  // 编码器模式不分频
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 999;                   // 计数上限,溢出后回绕
HAL_TIM_Encoder_Init(&htim4);

// 配置编码器接口:CH1/CH2 均作为输入,上升沿+下降沿都计数(4 倍频)
sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI12;      // TI1+TI2 双边沿计数
sEncoderConfig.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
sEncoderConfig.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC1Filter = 0;
sEncoderConfig.IC2Filter = 0;
HAL_TIM_Encoder_ConfigChannel(&htim4, &sEncoderConfig, TIM_CHANNEL_ALL);

// 2. 启动函数:使能编码器接口
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);

// 3. 读取编码器计数和方向(可在主循环或定时中断中调用)
int16_t encoder_count = (int16_t)__HAL_TIM_GET_COUNTER(&htim4);  // 当前脉冲计数
// 正转时计数递增,反转时计数递减;方向由计数增减方向自动判断

五、使用步骤总结(HAL 库)

  1. 使能时钟:__HAL_RCC_TIM2_CLK_ENABLE()

  2. 配置 GPIO(PWM/捕获时需要):复用推挽/输入模式

  3. 初始化定时器:设置 PSC、ARR、计数模式

  4. 配置通道:PWM 模式 / 输入捕获模式 / 编码器模式

  5. 启动:HAL_TIM_Base_Start_IT() / HAL_TIM_PWM_Start() 等

  6. 编写中断回调:处理 $T_{溢出} = \frac{(PSC+1) \times (ARR+1)}{f_{TIM}}$ 或捕获事件

六、常见注意事项

  • PSC 和 ARR 都是从 0 开始计数,所以实际分频系数是 PSC+1,周期是 ARR+1,容易差 1。

  • 定时器中断优先级要合理设置,避免被其他高优先级中断长时间阻塞。

  • 高级定时器 TIM1/TIM8 的 PWM 输出需要额外使能主输出:__HAL_TIM_MOE_ENABLE(&htim1),否则没有波形输出。

  • 多个定时器共享中断向量时(如 TIM2 全局中断),需在回调中判断 htim->Instance 区分来源。

五、常见问题排查

定时器配置过程中,最容易踩坑的往往是分频系数、时钟源、中断使能和高级定时器主输出这几类问题。下面总结 5 个高频问题及对应的排查思路。

1. PSC / ARR 差 1 导致定时时间或频率不对

现象:定时中断周期比预期慢或快,PWM 频率与计算值不符。

原因:PSC 和 ARR 都是从 0 开始计数的,实际分频系数是 PSC+1,计数周期是 ARR+1。很多初学者直接把 PSC 和 ARR 当作实际值代入公式,导致结果差 1。

解决方案:计算时务必使用 PSC+1 和 ARR+1。例如想用 72 MHz 时钟产生 1 ms 中断,应取 PSC = 71(实际分频 72)、ARR = 999(实际计数 1000),而不是 PSC = 72、ARR = 1000。

2. 时钟源配置错误导致定时器不工作或频率异常

现象:定时器完全没有计数,或计数频率与预期相差很大。

原因:CubeMX 中未勾选 Internal Clock(内部时钟),或 APB 预分频系数不为 1 时未意识到定时器时钟会翻倍。例如 F4 系列 APB1 为 84 MHz 且预分频系数为 2 时,TIM2~TIM5 的时钟实际是 168 MHz。

解决方案:在 CubeMX 的 Timers 中确认已勾选 Internal Clock;计算前先确认定时器挂在哪条总线上,并根据 APB 预分频系数正确推导定时器时钟(预分频系数不为 1 时定时器时钟 = APB 时钟 × 2)。

3. 中断不触发或回调不执行

现象:定时器配置正确,但中断服务函数始终不进入,或回调函数没有反应。

原因:常见原因有三个:一是 NVIC 中未勾选对应的定时器全局中断;二是启动函数用错,例如用 HAL_TIM_Base_Start() 而不是 HAL_TIM_Base_Start_IT();三是回调函数名写错或未在 main.c 中重写。

解决方案:在 CubeMX 的 NVIC Settings 中勾选对应定时器的全局中断;确认启动函数带 _IT 后缀;检查回调函数名是否为 HAL_TIM_PeriodElapsedCallback(),并在回调中通过 htim->Instance 判断中断来源。

4. PWM 无输出或输出为恒定电平

现象:代码编译下载后,引脚上测不到 PWM 波形,或输出一直为高/低电平。

原因:常见原因有三个:一是 GPIO 未配置为复用功能(AF);二是高级定时器 TIM1/TIM8 未使能主输出(MOE);三是通道启动函数未调用或通道号写错。

解决方案:确认 GPIO 已配置为对应定时器通道的复用功能(如 PA8 为 AF1);高级定时器必须执行 __HAL_TIM_MOE_ENABLE(&htim1) 使能主输出;确认已调用 HAL_TIM_PWM_Start() 且通道参数正确。

5. 输入捕获测量结果不稳定或始终为 0

现象:捕获到的频率或占空比跳变剧烈,或始终为 0 / 100%。

原因:常见原因有三个:一是捕获边沿切换逻辑有误,导致高电平脉宽或周期计算错误;二是信号抖动较大,未做硬件滤波;三是计数器溢出处理不当,导致差值计算错误。

解决方案:检查边沿切换逻辑,确认上升沿和下降沿交替捕获;在 CubeMX 中适当增大 Filter 值(如 0x0F)进行硬件滤波;处理计数器回绕时使用 (now >= capture_val) ? (now - capture_val) : (0xFFFF - capture_val + now) 的方式计算差值。

七、总结

本文系统梳理了 STM32 定时器(TIM)从原理到实战的完整知识体系。核心要点可归纳为以下几点:

  • 理解时钟链路:定时器时钟由 APB 总线时钟经预分频推导而来,APB 预分频系数不为 1 时定时器时钟会翻倍,这是所有计算的前提。
  • 掌握两个关键参数:PSC 决定计数时钟频率,ARR 决定计数周期,二者均从 0 开始计数,实际分频为 PSC+1、周期为 ARR+1,务必避免差 1 错误。
  • 分清四种工作模式:基本定时中断用于周期性任务,PWM 输出用于波形生成,输入捕获用于测量外部信号,编码器接口用于读取旋转编码器,按需求选择对应模式。
  • 牢记高级定时器特例:TIM1/TIM8 输出 PWM 前必须使能主输出(MOE),否则即使配置正确也没有波形。
  • 善用中断回调:通过 htim->Instance 区分中断来源,并确认启动函数带 _IT 后缀、NVIC 中断已使能。

建议读者先按「五、使用步骤总结」的流程跑通一个基本定时中断案例,再逐步扩展到 PWM、输入捕获和编码器接口,遇到问题时对照「五、常见问题排查」逐项定位。掌握定时器后,无论是 LED 控制、电机驱动还是信号测量,都能事半功倍。

STM32基本定时器TIM6和TIM7 1. STM32上定时器的分类前面学习了STM32系统定时器SysTick,它的主要作用是为OS提供系统滴答,当然我们也可以利用它实现了精准延时。在STM32单片机中,除了属于CM3内核中的一个外设的系统定时器外,还有几个属于片上外设的定时器:基本定时器(TIM6和TIM7)、通用定时器(TIM2/3/4/5)和高级定时器(TIM1和TIM8)。强调,这里指的是除互联型的STM32F1系列单片机。 阅读详情

相关推荐

STM32f103ZET6 定时器实战:从 TIM6 定时中断到 TIM3 PWM 呼吸灯

方式一:引脚重映射:将 TIM3_CH2 从默认 PA7 映射到 PB5,控制 PB5 上的原有 LED(如 D0 灯)。方式二:外接 LED:使用默认 PA7 引脚,外接 LED(阳极接 3.3V,阴极接 PA7),直接通过 PA7 输出 PWM 控制。

m0_74186706的博客 1647

STM32-定时器详解

定时器作为微控制器不可缺少的外设,在STM32中也是如此。相信不少初学者学到定时器的时候对STM32的学习热情就大打折扣甚至想要放弃了,因为这一部分知识确实比较复杂。但是,如果你在之前对GPIO、串口通信、外部中断的学习中把这些外设掌握了的话,学习这个新知识并不难。 例本章共计1万余字,从STM32定时器的原理、寄存器介绍、定时器配置以及定时器的几个常用的功能(如定时器中断、定时器输出比较PWM波形、定时器输入捕获测电平长度、定时器编码器模式应用等)的使用方法来教大家掌握定时器这一外设。

qq_44016222的博客 18万+

STM32高级定时器TIM1中断的细节配置

STM32高级定时器TIM1中断的细节配置

qq_60845288的博客 7403

STM32定时器详解

定时器作为微控制器不可缺少的外设,在STM32中也是如此。相信不少初学者学到定时器的时候对STM32的学习热情就大打折扣甚至想要放弃了,因为这一部分知识确实比较复杂。但是,如果你在之前对GPIO、串口通信、外部中断的学习中把这些外设掌握了的话,学习这个新知识并不难。例本章共计1万余字,从STM32定时器的原理、寄存器介绍、定时器配置以及定时器的几个常用的功能(如定时器中断、定时器输出比较PWM波形、定时器输入捕获测电平长度、定时器编码器模式应用等)的使用方法来教大家掌握定时器这一外设。

zhuxinmingde的博客 7881

STM32之五:TIM定时器(2-通用定时器)

下面我们分别介绍这几个阶段。

guaizaiguaizai的博客 3173

STM32定时器中断配置实战详解(基于TIM6/TIM7)

STM32定时器是嵌入式系统中实现精确时间控制的核心外设,分为通用定时器(如TIM2-TIM5)、基本定时器(TIM6、TIM7)和高级定时器(TIM1、TIM8)。其中,TIM6/TIM7为16位向上计数的自动重载定时器,常用于产生周期性中断,不带GPIO输出功能,专精于时间基准生成。

weixin_42113456的博客 1809

STM32的定时器使用介绍

代码中想用个延时,有两种方式:一种是用软件延时,即在代码中加一个延时函数,但同时有一个缺点:不精确且占用CPU资源。还有一种就是使用定时器,它使用的是精准的时基,通过硬件的方式,实现定时功能。此次介绍的就是如何使用STM32的定时器功能实现延时。话不多说,加快上车吧。

weixin_51772979的博客 1420

STM32高级定时器TIM1的PWM输出实战详解

STM32系列微控制器基于ARM Cortex-M内核,凭借其高性能、低功耗与丰富的外设接口,广泛应用于工业控制、智能家电、物联网等领域。其定时器资源尤为强大,尤其是高级定时器TIM1,为复杂PWM波形生成提供了硬件支持。PWM(Pulse Width Modulation,脉宽调制)技术通过调节脉冲宽度控制输出能量,广泛用于电机调速、LED调光、电源管理等场景。本章将为读者建立STM32与PWM技术的基本认知框架,为深入理解后续定时器配置与工程应用打下坚实基础。

weixin_42433737的博客 1437

STM32—定时器原理及配置(入门详解)

详解定时器原理及在STM32CubeMX中定时器的配置,配有简单例题易懂

Ai1101224的博客 6949

树莓派3-改装wifi外置天线

声明:本文只是技术性探讨,改装引起的产品损坏等后果,由用户自负! 改装主要有两种方法 方法一:直接替换原来的天线 1、移除原有的小陶瓷天线,就在Made in PRC(或UK)那边的那个 清理一下阻焊剂方便天线接地(图中PRC左侧) 2、焊接天线 天线的屏蔽层焊接到清理完阻焊剂的地方,中间的信号线焊接到PRC右侧焊盘。 3、最终的样子

huayucong的博客 2万+

ESP8266连接阿里云(STM32)

一、概述 之前通过串口调试助手+ESP8266已经连接上阿里云了 ESP8266连接阿里云(串口调试助手) 这次我们使用STM32+ESP8266来接入阿里云,其实本质上就是STM32和ESP8266进行串口通信,相当于使用STM32代替之前使用的串口调试助手来发送AT指令。只是发送的字符串数据可能有些细节需要注意一下。 二、保存连接的AT指令 定义几个字符串数据保存需要发送的AT指令 //注意: //末尾记得加\r\n //字符...

xyx0610的博客 4万+

STM32 定时器TIM

在STM32中,定时器起着至关重要的作用,它设计复杂但功能强大,针对不同的情境有严谨的设计。TIM(Timer)定时器定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时定时器不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。

Echo_cy_的博客 6384

从0玩转Mamba神经网络:理论+实战的保姆级教程

Mamba神经网络是一种基于状态空间模型(SSM)的高效序列处理架构,相比Transformer具有更快的推理速度和更低的内存消耗。本文提供从理论到实践的完整指南,详细讲解Mamba的工作原理、与Transformer的差异,并给出基于PyTorch的文本分类实战示例。教程包含环境配置、数据处理、模型搭建、训练评估全流程,展示了Mamba在IMDb情感分析任务中的应用。文章还探讨了Mamba的性能优势及进阶方向,为开发者提供了探索这一新兴技术的实用指南。通过阅读本文,读者可以快速掌握Mamba的核心概念并上

FJN110的博客 317

STM32F4 32位定时器TIM2、TIM5的使用(附STM32F407----STM32F401的移植说明)

** STM32F4 32位定时器TIM2、TIM5的使用(附STM32F407----STM32F401的移植说明) ** STM32F4是一款性价比极高的MCU,撇开强大的FPU(硬件浮点运算单元),单从定时器来讲,STM32F4比STM32F1多了两个32位定时器(TIM2、TIM5),而232=4.29∗1092^{32}=4.29*10^9232=4.29∗109,可以看到32位定时器可...

qq_42348833的博客 1万+

Arcgis中的模型构建器(ModelBuilder)技术之批量图层转KML

这段时间全省各地都在开展耕地流出图斑整改恢复工作,期间或多或少都需要村委会协助摸排举证,除了举证软件外,奥维、外业精灵等软件想必大家接触得也很多。这就经常需要我们转kmz文件给业主或者摸排人员,那么有没有什么方法在arcgis下快速批量转kmz文件呢,今天就介绍一下如何在模型构建器(ModelBuilder)构建批量图层转KML的模型。

weixin_44671081的博客 1325

IPD流程中的RR\OR\IR\PB\SR\AR\CR等名词汇总(基于新的产品包需求架构)

RR:Raw Requirement,原始需求=》IR:Initial Requirement,初始需求=》SR:System Requirement,系统需求=》AR:Allocated Requirement,分配需求=》CR:Process Change Requirement,流程变更需求。

a1809032425的博客 3194

【STM32】通用定时器(TIM2到TIM5)

00. 目录 文章目录00. 目录01. 通用定时器简介02. TIM2到TIM5主要特性03. TIM2到TIM5功能说明04. 时钟选择05. 寄存器5.1 TIMx 控制寄存器 1 (TIMx_CR1)5.2 TIMx 控制寄存器 2 (TIMx_CR2)5.3 TIMx 从模式控制寄存器 (TIMx_SMCR)5.4 TIMx DMA/ 中断使能寄存器 (TIMx_DIER)5.5 TIMx 状态寄存器 (TIMx_SR)5.6 TIMx 事件生成寄存器 (TIMx_EGR)5.7 TIM

C/C++软件工程师、嵌入式软件工程师、物联网研发工程师、C/C++讲师、物联网讲师、嵌入式讲师、鸿蒙讲师---欢迎大家一起交流(私信添加博主微信) 6739

华为OD机试 - 末世分配资源包 - 二分查找 + 贪心(Python/JS/C/C++ 新系统 100分)

本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。末世时代,政府为各地方配资源,现有资源分配表 nums[n],要求按如下规则分配给 k 个营地:在最优平均分配情况下,得到最大团队所获得的资源数4,3,6,9,7 216最佳划分之一:[4,3,6] -> 13 [9,7] -> 16最大

学Java,找哪吒 224

STM32TIM定时器

摘要:STM32的TIM定时器是一个多功能外设,支持外部脉冲计数、波形生成等功能。本文详细解析了TIM2的外部时钟模式2配置流程:从PA0引脚输入脉冲信号,经ETR模块滤波后作为时钟源,通过PSC预分频和CNT计数,配合ARR自动重载值实现脉冲计数。重点阐述了硬件模块间的信号流向、寄存器配置方法(包括GPIO初始化、时钟模式选择、分频设置等)和中断处理机制,并提供了完整的代码实现。特别说明了PSC分频原理和ARR的预装机制(ARPE)对定时器行为的影响,为STM32定时器的外部脉冲计数应用提供了完整参考方案

2504_92863595的博客 2251

【智能模型系列】基于Unity和MediaPipe【手部追踪+手势识别】系统开发指南

在本文中,我将详细介绍一个基于MediaPipe的Unity手势识别系统,该系统能够识别多种常见手势(如拳头、点赞、胜利手势等),并提供完整的事件系统来处理手势状态变化。这个系统非常适合用于VR/AR交互、手势控制游戏或任何需要自然用户界面的应用场景。

纯技术流日常开发商业开发记录总结等 587

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

STM32——6 TIM定时器

超级万字详解STM32!超级详细细致,分为34各个小节。STM32,看这一篇就够了!值得收藏!!!

念头通达 1585

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

STM32定时器中断完全指南:从原理到实战应用

STM32定时器中断是嵌入式开发中实现精确时序控制的核心技术,掌握其原理与配置方法对开发实时系统至关重要。定时器中断的工作原理与配置步骤定时时间的精确计算方法中断服务函数的最佳实践常见问题的解决方案多种实际应用场景的实现在实际项目中,建议结合CubeMX工具进行可视化配置,并遵循"中断简短"原则确保系统稳定性。随着对定时器理解的深入,您可以进一步探索PWM输出输入捕获等高级功能,构建更复杂的嵌入式应用系统。

niuTyler的博客 2569

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

STM32定时器级联实战:如何用TIM3+TIM2实现64位计时器(附代码)

本文详细介绍了STM32定时器级联功能的应用,通过TIM3+TIM2硬件级联实现64位计时器,显著提升计时精度并降低CPU负载。文章包含配置代码、性能对比及常见问题解决方案,适用于无人机飞控、工业自动化等高精度计时场景。

github5actions的博客 880

STM32定时器(TIM)详解:从定时中断到PWM,一文掌握定时器全家桶

在STM32微控制器中,定时器(TIM,Timer)是功能最丰富、应用最广泛的外设之一。从简单的"延时中断"到复杂的"电机PWM控制",从"频率测量"到"编码器测速",几乎所有嵌入式项目都离不开定时器。想象一下:你需要一个精确到毫秒级的定时任务(如温湿度采集),需要用PWM调节LED亮度,需要测量超声波传感器的回声时间,还需要读取电机编码器的转速——这些功能都能通过STM32的定时器实现。

qq2745567641的博客 2747
上一篇: 11.SysTick 详解:从原理到实战,掌握 Cortex-M 系统嘀嗒定时器
下一篇: 13. STM32 定时器与 PWM 实战指南
硅基造物主
博客等级 码龄4年 17粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值