09-定时器.md 7.5 KB

09 定时器(HAL)

目标:掌握时基单元、PWM、输入捕获、编码器模式


定时器整体框图

定时器框图

STM32 定时器是一个复杂的模块,从输入到输出包含:时钟源 → 时基单元 → 输出/输入通道 → 从模式控制。高级定时器(TIM1、TIM8)还包含死区生成、刹车输入等电机控制功能。

⚠️ 定时器时钟源

定时器实际时钟 = 2 × PCLKx(当 APBx 预分频 > 1 时):

72MHz 配置下(APB1=/2, APB2=/1):
  TIM2/3/4/5 时钟 = 2 × PCLK1 = 2 × 36MHz = 72MHz
  TIM1/TIM8 时钟 = PCLK2 = 72MHz(APB2=/1,不分频,时钟不变)

配置时基时用 定时器时钟 计算分频,而非 PCLK 总线频率。

本笔记依次讲解:时基单元 → 输出比较/PWM → 输入捕获 → 从模式 → 编码器。


9.1 时基单元

时基单元结构

时基单元

时基单元是定时器的"心脏",由 4 个核心寄存器组成:

           PSC                   CNT                   ARR
  Clock ──→ /(PSC+1) ──→ 计数器(递增/减) ──→ 比较 = ARR ──→ 更新事件(UEV)
                                ↑                           │
                                └────────── 清零重来 ────────┘

频率计算:
  CK_CNT = TIM_CLK / (PSC + 1)

定时周期(向上计数模式):
  T = (PSC + 1) × (ARR + 1) / TIM_CLK
参数 功能 示例值 (1ms, 72MHz)
PSC (预分频器) 对 TIM_CLK 分频 71 → CK_CNT = 1MHz
CNT (计数器) 每个时钟递增/减 从 0 到 ARR
ARR (自动重装) 计数目标值 999 → (999+1)×1µs = 1ms
RCR (重复计数器) 高级定时器专用,控制 N 次 UEV 触发一次中断

影子寄存器(预加载)

上图中阴影部分即为影子寄存器。

影子寄存器:PSC 和 ARR 写入后不会立即生效,而是先写入"预加载寄存器",等当前定时周期结束时(发生更新事件 UEV),影子寄存器才从预加载寄存器加载新值。

为什么要预加载?

  • 避免在计数过程中突然改变 PSC/ARR 导致异常波形
  • 确保周期变更在"安全的边界时刻"生效

预加载可关闭:在 CubeMX 中取消勾选 auto-reload preload 后,写入 ARR 立即生效(不推荐,除非你清楚后果)。

CubeMX 配置

  • TIM2 → Internal Clock
  • Prescaler (PSC):71
  • Counter Period (ARR):999
  • auto-reload preload:Enable
  • NVIC → TIM2 global interrupt → Enable

关键 API

函数 说明
HAL_TIM_Base_Start(&htim) 启动定时器(轮询)
HAL_TIM_Base_Start_IT(&htim) 启动定时器(中断)
HAL_TIM_PeriodElapsedCallback(&htim) 更新中断回调

完整代码

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == &htim2)
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();
    HAL_TIM_Base_Start_IT(&htim2);

    while (1) { /* 主循环休眠,中断驱动 */ }
}

9.2 输出比较 & PWM

原理

PWM Mode 1(向上计数):
  CNT < CCR  → 输出有效电平(高)
  CNT ≥ CCR  → 输出无效电平(低)
  CNT = ARR  → 清零重来

占空比 = CCR / (ARR + 1)
频率   = TIM_CLK / (PSC+1) / (ARR+1)
CCR=3  —— 比较值
ARR=9  —— 自动重装值

CNT:  0  1  2  3  4  5  6  7  8  9 | 0  1  2  3  4  5  6 ...
      ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑  ↑ | ↑  ↑  ↑  ↑  ↑  ↑  ↑
OUT:  █  █  █  ░  ░  ░  ░  ░  ░  ░ | █  █  █  ░  ░  ░  ░
      └──高──┘  └───────低────────┘ | └──高──┘  └──低───
      占空比 = 3/10 = 30%

呼吸灯示例:PWM 频率 1kHz(不可见闪烁),占空比从 0→100%→0 循环。

CubeMX 配置

  • TIM2 Channel 1 → PWM Generation CH1
  • PSC = 71, ARR = 999, Pulse (CCR) = 0
  • 引脚 PA0

关键 API

函数 说明
HAL_TIM_PWM_Start(&htim, Channel) 启动 PWM
__HAL_TIM_SET_COMPARE(&htim, Channel, CCR) 修改占空比

呼吸灯代码

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

    uint16_t duty = 0;
    int8_t dir = 1;

    while (1)
    {
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
        HAL_Delay(5);

        if (dir == 1) duty += 10;
        else          duty -= 10;

        if (duty >= 999) dir = -1;
        if (duty == 0)   dir = 1;
    }
}

9.3 输入捕获

原理

外部信号 → TIx 引脚 → 滤波 → 边沿检测 → 捕获 CNT → 存入 CCR

频率测量:
  第 1 次上升沿 → 捕获 CCR1
  第 2 次上升沿 → 捕获 CCR2
  周期 = CCR2 - CCR1(注意溢出处理)

CubeMX 配置

  • TIM2 Channel 1 → Input Capture direct mode
  • PSC = 71 → CK_CNT = 1MHz (1µs 分辨率)
  • ARR = 65535(最大范围)
  • 引脚 PA0

关键 API

函数 说明
HAL_TIM_IC_Start_IT(&htim, Channel) 启动输入捕获
HAL_TIM_IC_CaptureCallback(&htim) 捕获完成回调
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim == &htim2)
    {
        uint32_t val = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
        // 计算频率/脉宽
    }
}

9.4 从模式控制器

原理

主定时器 → TRGO (触发输出) → 从定时器的触发输入

常见组合:
  TIM1 主 (PWM 输出) → TRGO = 更新事件
  TIM2 从 (从模式复位) → 每次 TIM1 更新时 CNT 复位

用途:精确测量 PWM 参数
  TIM1 CH1 输入捕获 → 捕获周期
  TIM1 从模式复位 → 每次上升沿自动复位 CNT

CubeMX 配置

  • TIM1:
    • Slave Mode:Trigger Mode
    • Trigger Source:ITR0(取决于哪个定时器做主)
    • Channel 1:Input Capture direct mode

9.5 编码器模式

原理

正交编码器 → TI1 (A相) + TI2 (B相)

定时器自动判断正反转:
  A 相领先 B 相 → 正转 (CNT++)
  B 相领先 A 相 → 反转 (CNT--)

计数模式:
  ×1:仅 TI1 边沿计数
  ×2:TI1 + TI2 边沿计数
  ×4:所有边沿计数(最高分辨率)

CubeMX 配置

  • TIM3 → Combined Channels:Encoder Mode TI1 and TI2
  • PSC = 0(不分频,直接计数)
  • ARR = 65535(16 位最大范围)
  • Encoder Mode:TI1 and TI2 (×4)

代码示例

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_TIM3_Init();
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

    int16_t position;
    while (1)
    {
        position = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
        // 通过串口打印 position 值
        HAL_Delay(50);
    }
}

常见坑点

  • ⚠️ PSC 和 ARR 是 16 位寄存器(TIM2 在某些系列是 32 位),注意数值范围
  • ⚠️ 修改 PSC/ARR 后,等待更新事件 (UEV) 影子寄存器才加载
  • ⚠️ 输出比较修改 CCR 后立即生效(无预加载)
  • ⚠️ 编码器模式 CNT 读出来要转为 int16_t 才能正确表示正负
  • ⚠️ 输入捕获注意溢出处理:两次捕获之间 CNT 可能溢出回零