09-定时器详解.md 42 KB

STM32 定时器详解

基于江科大(13, 14, 15, 16, 17, 19)、铁头山羊(40, 42, 44)三教程综合整理


1. 定时器概述

STM32F10x 系列有 最多 8 个定时器,分为三种类型:

类型 定时器 位数 特点
高级定时器 TIM1, TIM8 16 位 带死区互补输出、重复计数器、刹车功能,适合电机控制
通用定时器 TIM2~TIM5 16 位 时基单元、输入捕获、输出比较、编码器接口、PWM 生成
基本定时器 TIM6, TIM7 16 位 仅时基单元(定时中断 + DAC 触发),无输入输出通道

这三个笔记综合覆盖的是 通用定时器 TIM2~TIM5,这也是最常用、功能最丰富的定时器。

定时器时基单元详解(尚硅谷风格)

STM32的定时器三个核心寄存器构成时基单元:

           ┌─────────────────────────────────────┐
PSC(预分频)│  72MHz ÷ 7200 = 10kHz (每计数1次=0.1ms)│
           └──────────┬──────────────────────────┘
                      ↓
           ┌─────────────────────────────────────┐
CNT(计数器)│  0 → 1 → 2 → ... → 9999 → 溢出 → 0  │
           └──────────┬──────────────────────────┘
                      ↓
           ┌─────────────────────────────────────┐
ARR(自动重装)│  CNT到达ARR值时,产生更新事件(UEV)    │
           └─────────────────────────────────────┘

PSC(预分频器):

  • 将定时器的输入时钟(通常72MHz)分频为更低的频率
  • 实际分频系数 = PSC寄存器的值 + 1
  • 设置范围:0~65535。设为7199时,72MHz÷7200=10kHz(0.1ms)
  • 关键点:PSC是在更新事件时生效的,有影子寄存器

CNT(计数器):

  • 向上计数模式:从0开始递增到ARR,然后归0产生溢出
  • 向下计数模式:从ARR开始递减到0,然后重置产生溢出
  • 中央对齐模式:先递增到ARR,再递减到0,来回摆动

ARR(自动重装寄存器):

  • 决定定时器的计数周期
  • ARR=9999,PSC分频后10kHz,定时周期=10000×0.1ms=1s
  • 有影子寄存器:修改ARR会在下一次更新事件时生效

定时周期计算公式:

定时时间 = (PSC + 1) × (ARR + 1) / 定时器时钟频率
示例:PSC=7199, ARR=9999, f=72MHz
时间 = 7200 × 10000 / 72000000 = 1秒

2. 时基单元

定时器中断基本结构 上图:STM32 定时器中断基本结构(含时基单元、预分频器、计数器、自动重装寄存器)(来源:STM32入门教程 PPT 第57页)

预分频器时序图 上图:预分频器工作时序(来源:STM32入门教程 PPT 第58页)

2.1 组成

每个通用定时器都有一个 16 位的时基单元,由三个寄存器组成:

CK_PSC(定时器时钟,如 72 MHz)
    │
    ▼
┌──────────┐     ┌────────────┐     ┌──────────┐
│  PSC     │ ──→ │  CNT 计数器 │ ──→ │  ARR    │
│预分频器  │     │  (16-bit)  │     │自动重装  │
│16-bit    │     │  0~65535   │     │寄存器    │
└──────────┘     └────────────┘     └──────────┘
寄存器 全称 作用
PSC Prescaler(预分频器) 对 CK_PSC 进行分频,得到 CK_CNT = CK_PSC / (PSC + 1)
CNT Counter(计数器) 在 CK_CNT 驱动下递增/递减,每来一个脉冲 ±1
ARR Auto-Reload Register(自动重装载寄存器) 当 CNT 达到 ARR 时溢出归零(向上计数),产生更新事件

2.2 工作模式

模式 说明 应用场景
向上计数 CNT 从 0 递增到 ARR,溢出后归 0 最常用,定时中断、PWM
向下计数 CNT 从 ARR 递减到 0,溢出后回到 ARR 偶尔使用
中央对齐 CNT 从 0 到 ARR 来回摆动 需要对称 PWM 时

2.3 定时时间的计算

CK_CNT = CK_PSC / (PSC + 1)
T_溢出 = (ARR + 1) / CK_CNT
T_溢出 = (ARR + 1) × (PSC + 1) / CK_PSC

示例:CK_PSC = 72 MHz,PSC = 7199,ARR = 9999

CK_CNT = 72M / 7200 = 10000 Hz = 10 kHz
T_溢出 = (9999 + 1) / 10000 = 1 秒

2.4 代码:定时中断初始化

void Timer_Init(void)
{
    // 1. 开启 TIM2 时钟(TIM2 在 APB1 上)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 2. 选择内部时钟(默认就是内部时钟,此函数可选)
    TIM_InternalClockConfig(TIM2);
    
    // 3. 时基单元配置
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;      // 滤波器分频
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;                // ARR = 9999
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;              // PSC = 7199
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;             // 高级定时器用
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    // 4. 清除更新标志位(TIM_TimeBaseInit 末尾会手动产生更新事件)
    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    
    // 5. 开启更新中断
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    
    // 6. NVIC 配置
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
    
    // 7. 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

2.5 中断服务函数

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        // 用户代码:如变量自增、设置标志位等
        Num++;
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);  // 必须清除标志位
    }
}

2.6 HAL 库版本:定时中断

// HAL库 — 定时中断
TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 7200 - 1;            // 72MHz/7200 = 10kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 10000 - 1;              // 10kHz/10000 = 1Hz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim2);

    HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);

    HAL_TIM_Base_Start_IT(&htim2);              // 启动中断模式
}

// MSP 初始化:HAL 自动调用,负责底层 RCC/GPIO/NVIC
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2) {
        __HAL_RCC_TIM2_CLK_ENABLE();
    }
}

// HAL 弱回调,需在用户文件中实现
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM2) {
        // 定时中断处理,如 Num++;
    }
}

注意:

  • HAL_TIM_Base_Init() 内部会调用 HAL_TIM_Base_MspInit() 开启时钟等底层配置。
  • AutoReloadPreload 使能后,ARR 在更新事件时更新,防止写入途中产生异常波形。
  • 中断回调使用弱函数 HAL_TIM_PeriodElapsedCallback,在 HAL 库里有一个默认 __weak 实现,用户需在同文件或主文件中覆盖。

寄存器:SysTick定时器(尚硅谷风格)

void Systick_Init(void) {
    // LOAD寄存器(24位): 写入重装值=72000-1=71999
    // SysTick时钟=72MHz, 每计数1次=1/72μs, 计72000次=1ms
    SysTick->LOAD = 71999;
    // CTRL寄存器bit2: CLKSOURCE, |=按位或: 保持其他位不变, bit2置1选择AHB时钟(72MHz)
    // bit2=0时选择HCLK/8(9MHz), bit2=1时选择HCLK(72MHz)
    SysTick->CTRL |= SysTick_CTRL_CLKSOURCE;
    // CTRL寄存器bit1: TICKINT, |=按位或: bit1置1使能SysTick中断
    // 当计数器从1递减到0时, 触发SysTick异常(SysTick_Handler)
    SysTick->CTRL |= SysTick_CTRL_TICKINT;
    // CTRL寄存器bit0: ENABLE, |=按位或: bit0置1启动SysTick定时器
    // 使能后计数器从LOAD值开始递减, 计到0时重装LOAD并继续递减
    SysTick->CTRL |= SysTick_CTRL_ENABLE;
}

uint16_t count = 0;
void SysTick_Handler(void) {
    count++;
    if (count == 1000) { LED_Toggle(LED1); count = 0; }
}

尚硅谷:SysTick延时函数

void Delay_us(uint32_t us) {
    // LOAD寄存器(24位): 写入重装值=us×72-1, 72MHz时每微秒72个计数
    // 直接赋值: 覆盖LOAD全部24位, 设定定时重装值
    SysTick->LOAD = us * 72 - 1;
    // VAL寄存器: 直接写0清楚当前计数值和COUNTFLAG标志
    // 写入任何值都清除COUNTFLAG并将计数器重装为LOAD值
    SysTick->VAL = 0;
    // CTRL寄存器: 直接赋值(非|=即全部覆盖), 同时设置CLKSOURCE和ENABLE
    // CLKSOURCE=1(bit2): 选择AHB时钟72MHz不分频
    // ENABLE=1(bit0): 启动递减计数, 从LOAD值开始递减到0
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE | SysTick_CTRL_ENABLE;
    // &按位与: 读取CTRL寄存器bit16(COUNTFLAG), 只保留该位
    // COUNTFLAG在计数器从1递减到0时硬件置1
    // !取反: COUNTFLAG=0时!0=true继续循环, =1时!1=false退出
    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG));
    // &= ~ 按位与+取反: 将CTRL寄存器bit0(ENABLE)清零, 停止SysTick
    // 保持其他CTRL位不变, 仅关闭定时器
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE;
}

void Delay_ms(uint32_t ms) {
    while (ms--) Delay_us(1000);
}

void Delay_s(uint32_t s) {
    while (s--) Delay_ms(1000);
}

寄存器:基本定时器TIM6(尚硅谷风格)

void TIM6_Init(void) {
    // APB1ENR寄存器bit4: TIM6EN, |=按位或: 保持其他位不变,TIM6外设时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;
    // PSC寄存器(16位)直接赋值: 分频系数=7199+1=7200, 72MHz/7200=10kHz(0.1ms/计数)
    // 预分频器有影子寄存器, 当前CNT值不受影响, 下次更新事件时生效
    TIM6->PSC = 7199;
    // ARR寄存器(16位)直接赋值: 自动重装值=9999, CNT从0计到9999后溢出产生更新事件
    // 定时周期=(7199+1)×(9999+1)/72MHz=1s
    TIM6->ARR = 9999;
    // DIER寄存器bit0: UIE(更新中断使能), |=按位或: bit0置1
    // 允许CNT溢出产生更新事件时触发中断请求
    TIM6->DIER |= TIM_DIER_UIE;
    // 内部操作SCB->AIRCR: 写0x5FA解锁键+PRIGROUP=3 → 3位抢占+1位响应
    NVIC_SetPriorityGrouping(3);
    NVIC_SetPriority(TIM6_IRQn, 2);  // NVIC->IPR寄存器: 设置中断抢占优先级=2
    NVIC_EnableIRQ(TIM6_IRQn);       // NVIC->ISER寄存器对应位置1: 使能TIM6中断通道
    // CR1寄存器bit0: CEN(计数器使能), |=按位或: bit0置1启动定时器
    // CEN=1后CNT从当前值开始在CK_CNT驱动下递增
    TIM6->CR1 |= TIM_CR1_CEN;
}

void TIM6_IRQHandler(void) {
    // SR寄存器bit0: UIF(更新中断标志), CNT溢出时硬件自动置1
    // &= ~ 按位与+取反: 软件清零UIF位, 保持SR其他位不变
    // 不清除则中断持续触发, 主程序无法正常执行
    TIM6->SR &= ~TIM_SR_UIF;
    LED_Toggle(LED2);
}

3. 输出比较与 PWM

3.1 输出比较原理

输出比较(Output Compare)是定时器的输出功能,通过比较 CNTCCR(捕获/比较寄存器)的值,在引脚上输出特定的电平变化。

STM32 通用定时器有 4 个独立通道(CH1~CH4),每个通道有一个 CCR 寄存器。

3.2 PWM 模式

PWM(Pulse Width Modulation,脉冲宽度调制)是最常用的输出比较模式。

PWM 模式 1(向上计数):

  • CNT < CCR:输出有效电平(如高电平)
  • CNT ≥ CCR:输出无效电平(如低电平)
  • CNT 溢出 → CNT 归零,重新从 0 开始

           CCR  ARR
    CNT:  0───████████████████████████████████████████████████████████───0
    输出:████████████████ 低电平  ████████████████████████████████████
       ↑ 占空比 = CCR/ARR     ↑
    

3.3 PWM 参数计算

参数 公式 说明
频率 Freq = CK_PSC / (PSC+1) / (ARR+1) PWM 波的周期
占空比 Duty = CCR / (ARR+1) 高电平占整个周期的比例
分辨率 Res = 1 / (ARR+1) 占空比的调节步长

示例:72 MHz,PSC = 719,ARR = 99

Freq = 72M / 720 / 100 = 1000 Hz = 1 kHz
Duty(CCR=50) = 50 / 100 = 50%
Res = 1/100 = 1%

PWM生成原理详解

PWM(脉宽调制)通过定时器的输出比较功能实现。

输出比较的工作过程:

  1. CNT从0开始递增
  2. 每当CNT的值等于CCR(捕获/比较寄存器)时,输出引脚发生翻转
  3. 在PWM1模式下:
    • CNT < CCR时:输出有效电平(默认高电平)
    • CNT ≥ CCR时:输出无效电平(默认低电平)
  4. ARR决定PWM周期,CCR决定占空比

占空比计算公式:

占空比 = CCR / (ARR + 1) × 100%
PWM频率 = 定时器时钟 / (PSC + 1) / (ARR + 1)

示例:PSC=7199, ARR=99, CCR=50

  • 定时器时钟 = 72MHz / (7199+1) = 10kHz
  • PWM频率 = 10kHz / (99+1) = 100Hz
  • 占空比 = 50 / (99+1) = 50%

3.4 GPIO 配置注意

使用定时器 PWM 输出时,GPIO 必须设置为 复用推挽输出GPIO_Mode_AF_PP),因为引脚由外设控制而非 GPIO 模块直接控制。

3.5 代码:PWM 驱动 LED 呼吸灯

void PWM_Init(void)
{
    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // GPIO 复用推挽输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;       // TIM2_CH1 默认在 PA0
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 内部时钟
    TIM_InternalClockConfig(TIM2);
    
    // 时基单元:PSC=719, ARR=99 → Freq=1kHz
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    // 输出比较配置:PWM 模式 1
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);               // 初始化结构体默认值
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;                     // 初始 CCR=0
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    
    // 启动定时器
    TIM_Cmd(TIM2, ENABLE);
}

// 设置占空比
void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2, Compare);  // Compare: 0~100 对应占空比 0%~100%
}

主函数:呼吸灯效果

int main(void)
{
    PWM_Init();
    
    while (1)
    {
        // 渐亮
        for (uint8_t i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(i);
            Delay_ms(10);
        }
        // 渐暗
        for (uint8_t i = 0; i <= 100; i++)
        {
            PWM_SetCompare1(100 - i);
            Delay_ms(10);
        }
    }
}

3.6 HAL 库版本:PWM 输出

// HAL库 — PWM 驱动 LED 呼吸灯
TIM_HandleTypeDef htim2;

void MX_TIM2_Init(void)
{
    TIM_OC_InitTypeDef sConfigOC = {0};

    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 720 - 1;             // 72MHz/720 = 100kHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 100 - 1;                 // 100kHz/100 = 1kHz
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_PWM_Init(&htim2);

    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;                          // 初始 CCR=0
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);

    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (htim->Instance == TIM2) {
        __HAL_RCC_TIM2_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        GPIO_InitStruct.Pin = GPIO_PIN_0;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;   // 复用推挽输出
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

// 设置占空比(宏写法,与 GPIO 无关)
void PWM_SetCompare1(uint16_t Compare)
{
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, Compare);
}

与标准库对照: | 标准库 | HAL 库 | |--------|--------| | TIM_OC1Init(TIM2, &...) | HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) | | TIM_SetCompare1(TIM2, val) | __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, val) | | GPIO_Mode_AF_PP | GPIO_MODE_AF_PP | | TIM_Cmd(TIM2, ENABLE) | HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) |

引脚重映射:如果 PA0 被占用,可以将 TIM2_CH1 重映射到其他引脚:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);  // 部分重映射
// 或完全重映射参考手册确定映射关系

寄存器:PWM输出(尚硅谷风格)

void TIM5_Init(void) {
    // APB2ENR[2]: IOPAEN位, |=按位或: GPIOA时钟使能
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    // APB1ENR[4]: TIM5EN位, |=按位或: TIM5时钟使能(TIM5挂在APB1总线)
    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
    // PA1: GPIOA->CRL控制PA0~PA7, 每4位一组, PA1占[7:4]
    // |=位或: MODE1[5:4]=11(50MHz输出), CNF1_1[7]=1(复用推挽)
    // &=~取反清零: CNF1_0[6]=0, 组合CNF=10(复用推挽输出)
    GPIOA->CRL |= GPIO_CRL_MODE1 | GPIO_CRL_CNF1_1;  GPIOA->CRL &= ~GPIO_CRL_CNF1_0;

    // PSC直接赋值: 分频系数=7200, 计数时钟=72MHz/7200=10kHz, 每计数=0.1ms
    TIM5->PSC = 7199;
    // ARR直接赋值: 重装值=99, PWM频率=10kHz/(99+1)=100Hz, 周期=10ms
    TIM5->ARR = 99;
    // CR1寄存器bit4: DIR(计数方向), &= ~按位与+取反: 只清零DIR位,保持其他CR1位不变
    // DIR=0(默认)向上计数: CNT从0递增到ARR, PWM常用模式
    // DIR=1向下计数: CNT从ARR递减到0
    TIM5->CR1 &= ~TIM_CR1_DIR;
    // CCR2直接赋值: 捕获/比较寄存器CH2, 设定比较值=50
    // PWM占空比=CCR2/(ARR+1)=50/100=50%, 可在运行时动态修改
    TIM5->CCR2 = 50;
    // CCMR1[15:8]控制CH2, CC2S[15:14]选择通道方向
    // &= ~按位与+取反: CC2S位域清零, CC2S=00(CH2为输出模式)
    TIM5->CCMR1 &= ~TIM_CCMR1_CC2S;
    // OC2M[13:12]比较模式位域, 110(PWM模式1):
    // CNT<CCR时输出有效电平, CNT≥CCR时输出无效电平
    // |=按位或: OC2M_2[13]=1, OC2M_1[12]=1
    TIM5->CCMR1 |= TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1;
    // OC2M_0[14]=0清零, 确保OC2M=110即PWM模式1(非模式2或3)
    TIM5->CCMR1 &= ~TIM_CCMR1_OC2M_0;
    // CCER[5]: CC2E(CH2输出使能), |=按位或: bit5置1
    // 使能后CC2引脚(PA1)开始输出PWM波形
    TIM5->CCER |= TIM_CCER_CC2E;
}

// 直接写CCR2寄存器修改比较值, 实时改变PWM占空比
// 新值将在下一个计数周期生效(有影子寄存器)
void TIM5_SetDutyCycle(uint8_t dutyCycle) { TIM5->CCR2 = dutyCycle; }
// CR1[0]: CEN=1, |=按位或: 启动TIM5计数器, CNT开始递增产生PWM
void TIM5_Start(void) { TIM5->CR1 |= TIM_CR1_CEN; }

4. 输入捕获

4.1 输入捕获原理

输入捕获(Input Capture)是定时器的输入功能,用于 测量外部信号的频率、周期、占空比

外部信号 → TIMx_CH1(PA6) → 输入滤波器 → 边沿检测器 → ICx → 捕获 CCR
                                           ↑
                                      触发信号(上升/下降沿)

当检测到指定边沿时,当前 CNT 的值被立即锁存到 CCR 寄存器,同时可以触发中断。

4.2 测频法 vs 测周法

方法 原理 适用频率
测频法 固定时间 Ts 内统计脉冲个数 N,f = N/Ts 高频信号
测周法 测量两个上升沿之间的周期 T,f = 1/T 低频信号

4.3 测周法的实现(从模式复位)

最常用的输入捕获方式:从模式复位,即 TI1 的上升沿触发 CNT 复位。

             第一个上升沿        第二个上升沿
                 │                  │
外部信号  ───────┴────────  ────────┴────────
CNT       0────████████████████████████
                 ↑                   ↑
        捕获到 CCR1=0      捕获到 CCR1=N
        随即 CNT 归零       f = CK_CNT / N

4.4 代码:输入捕获测频率

void IC_Init(void)
{
    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // GPIO 上拉输入(TIM3_CH1 默认在 PA6)
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 内部时钟
    TIM_InternalClockConfig(TIM3);
    
    // 时基单元:PSC=71 → CK_CNT = 1MHz(测周法使用)
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;    // ARR 最大
    TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;     // CK_CNT = 1MHz
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
    
    // 输入捕获初始化:通道 1,上升沿,直通
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                // 最大滤波
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 捕获不分频
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    // 从模式复位:TI1FP1 上升沿触发 CNT 归零
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
    
    // 启动定时器
    TIM_Cmd(TIM3, ENABLE);
}

// 获取频率:f = CK_CNT / (CCR1 + 1)
uint32_t IC_GetFreq(void)
{
    return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

4.5 测频率 + 占空比(PWMI 模式)

双通道同时捕获:通道 1 测频率(上升沿触发复位),通道 2 测占空比(下降沿捕获)。

上升沿    下降沿  上升沿
  │         │      │
──┘─────────┴──────┘──
  ↑                 ↑
  CH1 捕获 CCR1     CH1 捕获 CCR1(从模式复位,CNT 归零)
           ↑
           CH2 捕获占用比(下降沿触发)
           
频率 f = CK_CNT / CCR1
占空比 Duty = CCR2 / CCR1

配置差别:将通道 2 设置为相反的极性(下降沿),选择交叉输入(TIM_ICSelection_IndirectTI)。

4.6 HAL 库版本:输入捕获测频率

// HAL库 — 输入捕获测频率
TIM_HandleTypeDef htim3;

void MX_TIM3_IC_Init(void)
{
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 72 - 1;               // 72MHz/72 = 1MHz
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 65536 - 1;               // ARR 最大
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_IC_Init(&htim3);

    sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
    sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
    sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
    sConfigIC.ICFilter = 0xF;
    HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);

    // 从模式复位:TI1FP1 上升沿触发 CNT 归零(HAL 无封装,直接用寄存器)
    TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
    TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

    HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(TIM3_IRQn);

    HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
}

void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (htim->Instance == TIM3) {
        __HAL_RCC_TIM3_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        GPIO_InitStruct.Pin = GPIO_PIN_6;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

// 捕获回调:发生捕获时自动调用
uint32_t CapturedValue = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM3) {
        CapturedValue = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
        // 频率 f = 1000000 / (CapturedValue + 1)
    }
}

PWMI 模式(测频率+占空比) HAL 库配置:

// 通道 1:上升沿,直通
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);

// 通道 2:下降沿,交叉输入
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);

// 从模式复位
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);

// 回调中读取:
//   uint32_t Period   = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
//   uint32_t DutyPulse = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_2);
//   频率 f = CK_CNT / Period
//   占空比 = DutyPulse / Period

寄存器:输入捕获测周期(尚硅谷风格)

void TIM4_Init(void) {
    // APB2ENR[3] IOPBEN, |= 按位或: 保持其他位不变,GPIOB时钟使能(PB6作输入用)
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    // APB1ENR[2] TIM4EN, |= 按位或: 保持其他位不变,TIM4时钟使能(APB1外设)
    RCC->APB1ENR |= RCC_APB1ENR_TIM4EN;
    // PB6: 浮空输入 CNF=01(浮空输入), MODE=00(输入)
    // CRL[MODE6]位域[11:10], &= ~ 按位与+取反: MODE位域清零=00(输入模式)
    GPIOB->CRL &= ~GPIO_CRL_MODE6;
    // &= ~ 按位与+取反: CNF6_1清零; |= 按位或: CNF6_0置1; 组合CNF=01=浮空输入
    GPIOB->CRL &= ~GPIO_CRL_CNF6_1;  GPIOB->CRL |= GPIO_CRL_CNF6_0;

    // PSC直接赋值: 分频系数=71+1=72, 72MHz/72=1MHz, 每计数=1μs
    TIM4->PSC = 71;
    // ARR直接赋值: 重装值=65535, 最大计数值65536, 可测周期≈65.5ms
    TIM4->ARR = 65535;
    // CR1[4] DIR, &= ~ 按位与+取反: DIR清零=向上计数(CNT从0递增到ARR)
    TIM4->CR1 &= ~TIM_CR1_DIR;
    // CCMR1[3:0] IC1F, &= ~ 按位与+取反: 数字滤波位域清零=无滤波,实时响应
    TIM4->CCMR1 &= ~TIM_CCMR1_IC1F;
    // CCER[1] CC1P, &= ~ 按位与+取反: CC1P=0=上升沿触发捕获
    TIM4->CCER &= ~TIM_CCER_CC1P;
    // CCMR1[1:0] CC1S, &= ~按位与+取反:CC1S_1清零; |=按位或:CC1S_0置1
    // CC1S=01=TI1(CH1引脚)直通输入到CH1捕获单元
    TIM4->CCMR1 &= ~TIM_CCMR1_CC1S_1; TIM4->CCMR1 |= TIM_CCMR1_CC1S_0;
    // CCER[0] CC1E, |= 按位或: 保持其他CCER位不变,使能CH1捕获
    // 检测到触发边沿时硬件自动锁存CNT→CCR1
    TIM4->CCER |= TIM_CCER_CC1E;
    // DIER[1] CC1IE, |= 按位或: 保持其他DIE位不变,使能CH1捕获中断
    TIM4->DIER |= TIM_DIER_CC1IE;
}

void TIM4_IRQHandler(void) {
    // SR[1] CC1IF, & 按位与: 测试CC1IF位是否=1,判断是否为CH1捕获触发的中断
    if (TIM4->SR & TIM_SR_CC1IF) {
        // &= ~ 按位与+取反: 软件清零CC1IF标志位,保持SR其他位不变
        // 硬件不会自动清除,不清除则无法触发下一次捕获中断
        TIM4->SR &= ~TIM_SR_CC1IF;
        // CNT直接赋值0: 将计数器复位,下次捕获时CCR1直接等于周期值
        TIM4->CNT = 0;
    }
}
// CCR1只读: 读捕获值, &或÷操作在表达式中; CCR1=捕获时的CNT快照
double TIM4_GetPWMFreq(void) { return 1000000.0 / TIM4->CCR1; }

PWMI 模式(周期+占空比同时测量):

// 通道1: 上升沿捕获 (TI1) -> CCR1 = 周期
// 通道2: 下降沿捕获 (TI1间接) -> CCR2 = 脉宽
// CCER[5] CC2P, |= 按位或: 保持其他CCER位不变,CC2P=1=通道2下降沿触发捕获
TIM4->CCER |= TIM_CCER_CC2P;
// CCMR1[9:8] CC2S, |=按位或:CC2S_1=1; &=~按位与+取反:CC2S_0=0
// CC2S=10=通道2映射到TI1间接输入(与CH1共用引脚,但检测相反边沿)
TIM4->CCMR1 |= TIM_CCMR1_CC2S_1;
TIM4->CCMR1 &= ~TIM_CCMR1_CC2S_0;
// SMCR[6:4] TS(触发选择), |= 按位或: TS=101=选择TI1FP1作为触发源
// TI1FP1是CH1经过滤波和边沿选择后的内部信号
TIM4->SMCR |= TIM_SMCR_TS_2 | TIM_SMCR_TS_0;
// SMCR[2:0] SMS, |= 按位或: SMS=100=从模式复位
// TI1FP1上升沿触发时CNT自动归零,实现周期同步测量
TIM4->SMCR |= TIM_SMCR_SMS_2;
double TIM4_GetPWMDuty(void) { return TIM4->CCR2 * 1.0 / TIM4->CCR1; }

5. 编码器接口

5.1 编码器模式原理

STM32 定时器的 编码器接口模式 可以直接连接 增量式旋转编码器(如 EC11) 的两路正交信号,自动完成正反转判断和计数

与 EXTI 方式(前面 Note 1 中的 5-2 示例)相比:

  • EXTI 方式:软件判断方向,仅能处理低速信号,占用两个中断
  • 定时器编码器模式:硬件自动处理,支持高速,不占用中断,是推荐方式

5.2 工作原理

编码器接法:TI1(PA6)接 A 相,TI2(PA7)接 B 相。

计数方向 TI1 电平(相对 TI2) TI2 电平(相对 TI1)
正转(递增) 超前 90° 滞后 90°
反转(递减) 滞后 90° 超前 90°

定时器根据两路信号的边沿顺序自动判断方向,对 CNT 进行 递增或递减

5.3 三种编码器模式

模式 TI1 边沿 TI2 边沿 单圈脉冲数
TI1 计数 忽略 1 倍
TI2 忽略 计数 1 倍
TI1+TI2(推荐) 计数 计数 2 倍 / 4 倍

4 倍频:TI1 和 TI2 的上升沿+下降沿都计数的模式。效果:编码器一圈 20 个脉冲 → 计数 80。

5.4 代码:编码器接口测速

void Encoder_Init(void)
{
    // 开启时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // GPIO 上拉输入:PA6(TIM3_CH1), PA7(TIM3_CH2)
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 时基单元:PSC=0, ARR=最大(65535),自由运行
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;         // 不分频
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
    
    // 输入捕获初始化(为编码器模式做准备)
    TIM_ICInitTypeDef TIM_ICInitStructure;
    TIM_ICStructInit(&TIM_ICInitStructure);
    
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;                  // 滤波去抖动
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
    TIM_ICInitStructure.TIM_ICFilter = 0xF;
    TIM_ICInit(TIM3, &TIM_ICInitStructure);
    
    // 编码器模式配置(必须在输入捕获之后!)
    TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
        TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
    // 注意:这里的 Rising/Falling 表示是否反相,而非真正的边沿
    
    // 启动定时器
    TIM_Cmd(TIM3, ENABLE);
}

// 获取增量值并清零
int16_t Encoder_Get(void)
{
    int16_t Temp;
    Temp = TIM_GetCounter(TIM3);
    TIM_SetCounter(TIM3, 0);
    return Temp;
}

测速主函数(结合定时中断,每隔固定时间读取一次):

int16_t Speed;

void TIM2_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {
        Speed = Encoder_Get();        // 每 1 秒(或定时间隔)读一次增量值
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}

int main(void)
{
    OLED_Init();
    Timer_Init();      // TIM2 每隔 1 秒中断一次
    Encoder_Init();    // TIM3 编码器模式
    
    while (1)
    {
        OLED_ShowSignedNum(1, 7, Speed, 5);
    }
}

5.5 HAL 库版本:编码器接口

// HAL库 — 编码器接口测速
TIM_HandleTypeDef htim3;
TIM_Encoder_InitTypeDef sEncoder = {0};

void MX_TIM3_Encoder_Init(void)
{
    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 1 - 1;                // 不分频
    htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim3.Init.Period = 65536 - 1;               // ARR 最大,自由运行
    htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

    sEncoder.EncoderMode = TIM_ENCODERMODE_TI12;  // TI1+TI2 双沿计数
    sEncoder.IC1Polarity = TIM_ICPOLARITY_RISING;
    sEncoder.IC1Selection = TIM_ICSELECTION_DIRECTTI;
    sEncoder.IC1Prescaler = TIM_ICPSC_DIV1;
    sEncoder.IC1Filter = 0xF;
    sEncoder.IC2Polarity = TIM_ICPOLARITY_RISING;
    sEncoder.IC2Selection = TIM_ICSELECTION_DIRECTTI;
    sEncoder.IC2Prescaler = TIM_ICPSC_DIV1;
    sEncoder.IC2Filter = 0xF;

    HAL_TIM_Encoder_Init(&htim3, &sEncoder);

    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (htim->Instance == TIM3) {
        __HAL_RCC_TIM3_CLK_ENABLE();
        __HAL_RCC_GPIOA_CLK_ENABLE();
        GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}

// 获取增量值并清零
int16_t Encoder_Get(void)
{
    int16_t Temp = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
    __HAL_TIM_SET_COUNTER(&htim3, 0);
    return Temp;
}

注意:

  • HAL_TIM_Encoder_Init() 内部同时完成了时基单元 + 编码器配置 + 通道滤波,无需额外调用 TIM_ICInit
  • 编码器模式下 AutoReloadPreload 通常设为 DISABLE,因为需要 CNT 对 ARR 立即响应。
  • __HAL_TIM_GET_COUNTER / __HAL_TIM_SET_COUNTER 是宏,代替标准库的 TIM_GetCounter / TIM_SetCounter

寄存器:高级定时器PWM(尚硅谷风格)

void TIM1_Init(void) {
    // PSC直接赋值: 分频=7199+1=7200, 72MHz/7200=10kHz(0.1ms/计数)
    // ARR直接赋值: 重装值=4999, 周期=(7199+1)×(4999+1)/72MHz=5s
    TIM1->PSC = 7199;  TIM1->ARR = 4999;
    // RCR(重复计数器寄存器)直接赋值: 4 → 每4+1=5次溢出才产生一次UEV
    // 高级定时器特性, 减少中断频率, RCR计满才触发更新事件
    TIM1->RCR = 4;
    // CCMR1[13:12] OC1M(输出比较模式), |= 按位或: OC1M=110=PWM模式1
    // CNT<CCR1时输出有效电平, CNT≥CCR1时输出无效电平
    TIM1->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;
    // CCER[0] CC1E, |= 按位或: 保持其他CCER位不变,CH1输出使能
    TIM1->CCER |= TIM_CCER_CC1E;
    // BDTR[15] MOE(主输出使能), |= 按位或: 保持其他BDTR位不变
    // 高级定时器必须置1,否则PWM/比较输出全部强制为高阻态
    TIM1->BDTR |= TIM_BDTR_MOE;
    // DIER[0] UIE, |= 按位或: 保持其他DIER位不变,使能更新中断
    TIM1->DIER |= TIM_DIER_UIE;
}

6. 定时器关键函数速查

基础操作

标准库 HAL 库(宏/函数) 用途
TIM_Cmd(TIMx, ENABLE/DISABLE) HAL_TIM_Base_Start/Stop(&htim) 启动/停止定时器
TIM_InternalClockConfig(TIMx) HAL 初始化默认内部时钟 内部时钟
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE) HAL_TIM_Base_Start_IT(&htim) 使能更新中断
TIM_SetCounter(TIMx, Val) __HAL_TIM_SET_COUNTER(&htim, Val) 写 CNT
TIM_GetCounter(TIMx) __HAL_TIM_GET_COUNTER(&htim) 读 CNT
TIM_ClearFlag(TIMx, flag) __HAL_TIM_CLEAR_FLAG(&htim, flag) 清除标志

输出比较 / PWM

标准库 HAL 库(宏/函数) 用途
TIM_SetCompare1(TIMx, Val) __HAL_TIM_SET_COMPARE(&htim, CH, Val) 设置 CCR
TIM_GetCompare1(TIMx) __HAL_TIM_GET_COMPARE(&htim, CH) 读取 CCR
TIM_OC1PreloadConfig(TIMx, ENABLE) sConfigOC.OCFastMode + AutoReloadPreload 预装载
TIM_OCStructInit(&s) 直接给 TIM_OC_InitTypeDef 成员赋值 初始化结构体

输入捕获

标准库 HAL 库(宏/函数) 用途
TIM_GetCapture1(TIMx) HAL_TIM_ReadCapturedValue(&htim, CH) 读取捕获值
TIM_SelectInputTrigger(TIMx, src) 寄存器直接操作(HAL 无封装) 选择触发源
TIM_SelectSlaveMode(TIMx, mode) 寄存器直接操作(HAL 无封装) 从模式配置

编码器

标准库 HAL 库 用途
TIM_EncoderInterfaceConfig(...) HAL_TIM_Encoder_Init(&htim, &sEncoder) 配置编码器模式
TIM_ICInit(TIMx, &s)TIM_EncoderInterfaceConfig(...) HAL_TIM_Encoder_Init() 一次完成 一步到位

时钟源切换

函数 用途
TIM_InternalClockConfig(TIMx) 内部时钟(CK_INT)
TIM_ITRxExternalClockConfig(TIMx, TIM_TS_ITR0~3) 其他定时器输出作为时钟
TIM_TIxExternalClockConfig(TIMx, ...) 外部引脚的时钟
TIM_ETRClockMode2Config(TIMx, ...) ETR 引脚外部时钟

7. 常见问题

7.1 定时时间不准

  • 检查 APB1 分频:TIM2~TIM7 在 APB1≠1 时时钟 = APB1×2
  • PSC 和 ARR 的范围是 0~65535(16 位),写入值需要 -1
  • 计算:T = (PSC+1)×(ARR+1) / CK_PSC

7.2 进入中断后停不下来

症状:中断持续触发,主循环无法正常运行。

原因:中断标志位没有清除。

解决

TIM_ClearITPendingBit(TIMx, TIM_IT_Update);
// 或 EXTI 中断:
EXTI_ClearITPendingBit(EXTI_LineX);

7.3 TIM_TimeBaseInit 后立即触发中断

TIM_TimeBaseInit() 函数末尾会手动产生一次更新事件(UG 位)。如果在初始化之前开启了中断,CNT=0 时的 ARR=0 状态也会导致立即触发。

解决

TIM_ClearFlag(TIMx, TIM_FLAG_Update);  // 在 TIM_ITConfig 之前清除

7.4 编码器模式与输入捕获的顺序

必须先调用 TIM_ICInit() 初始化通道,再调用 TIM_EncoderInterfaceConfig() 配置编码器模式。反过来会使得输入捕获的配置覆盖编码器模式。

7.5 TIM_OCStructInit 的必要性

如果 TIM_OCInitStructure 中的某些成员(如 TIM_OCIdleState, TIM_OCNIdleState)没有显式赋值,在高级定时器上可能会得到意外结果。使用 TIM_OCStructInit() 可以给所有成员赋默认值,避免不确定行为。


定时器核心函数速查(标注版)

基本定时中断:

  • TIMx->PSC = 7199; // 72MHz÷7200=10kHz(每计数1次=0.1ms)
  • TIMx->ARR = 9999; // 计数10000次产生溢出→1s中断一次
  • TIMx->DIER |= TIM_DIER_UIE; // DIER[0] UIE, |= 按位或: 保持其他位不变,使能更新中断
  • TIMx->CR1 |= TIM_CR1_CEN; // CR1[0] CEN, |= 按位或: 保持其他位不变,启动定时器计数

PWM输出:

  • TIMx->PSC = 7199; // 72MHz÷7200=10kHz
  • TIMx->ARR = 99; // 100Hz PWM周期
  • TIMx->CCR2 = 50; // 占空比50%
  • TIMx->CCMR1 |= OC2M_2|OC2M_1; // CCMR1[13:12], |= 按位或: OC2M=110=PWM模式1
  • TIMx->CCER |= CC2E; // CCER[5] CC2E, |= 按位或: 保持其他位不变,CH2输出使能

输入捕获:

  • TIMx->PSC = 71; // 1MHz(1μs精度)
  • CCMR1 |= CC1S_0; // CCMR1[0], |= 按位或: CC1S_0=1, CC1S=01=TI1直通CH1
  • CCER &= ~CC1P; // CCER[1], &= ~ 按位与+取反: CC1P清零=上升沿捕获
  • 读取CCR1得到周期值(单位μs)