09-定时器.md 13 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 立即生效(不推荐,除非你清楚后果)。

详细工作原理

UEV(更新事件)生成机制

更新事件 (UEV) 是定时器时基单元的核心事件,触发影子寄存器加载、中断请求和 DMA 请求:

向上计数模式:

  CNT:  0 → 1 → 2 → ... → ARR-1 → ARR
                                    │
                            CNT = ARR 时(计数溢出)
                                    │
                            ┌───────┴───────┐
                            │  UEV 生成     │
                            └───────┬───────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
              影子寄存器加载    SR.UIF=1       DMA 请求
              (PSC, ARR)       (更新中断标志)   (如果开启)

相关寄存器:

寄存器/位 功能
TIMx_SR.UIF (bit 0) 更新中断标志位,硬件置 1,软件写 0 清除
TIMx_DIER.UIE (bit 0) 更新中断使能位,置 1 允许 UEV 产生中断
TIMx_DIER.UDE (bit 8) 更新 DMA 请求使能位,置 1 允许 UEV 触发 DMA

手动产生 UEV:向 TIMx_EGR.UG (bit 0) 写 1,可软件触发 UEV,常用于初始化时立即加载 PSC/ARR 影子寄存器。

CKD 时钟分频与采样时钟

TIMx_CR1.CKD[1:0] 位控制死区采样时钟分频比,影响数字滤波器和死区发生器的时基:

CKD[1:0] 分频比 t_DTS(采样时钟周期)
00 1 t_CK_INT
01 2 2 × t_CK_INT
10 4 4 × t_CK_INT

t_DTS(Dead-Time Sampling clock)是死区发生器和输入滤波器的采样时钟。更大的分频比可以延长死区分辨率和滤波窗口,适合更低频的输入信号。

RCR 重复计数器(高级定时器专用)

RCR(Repetition Counter Register)位于高级定时器 TIM1/TIM8 中,将 UEV 的发生周期延长 N+1 次:

无 RCR (RCR=0): 每次 CNT=ARR 都产生 UEV(默认行为)
RCR=1:         每 2 次 CNT=ARR 才产生 1 次 UEV
RCR=N:         每 N+1 次 CNT=ARR 才产生 1 次 UEV

RCR 适用于PWM 模式下控制精确的脉冲个数。例如输出 5 个完全相同周期的 PWM 脉冲:设置 RCR=4,定时器在产生第 5 个更新事件后触发中断 / DMA。

时基计算公式

计数器时钟:      CK_CNT = TIM_CLK / (PSC + 1)

向上计数溢出周期:  T_ov = (PSC + 1) × (ARR + 1) / TIM_CLK

向下计数溢出周期:  T_ov = (PSC + 1) × (ARR + 1) / TIM_CLK(与向上相同)

中心对齐模式周期:  T_ov = (PSC + 1) × (ARR + 1) × 2 / TIM_CLK(一次完整三角波)

示例:TIM_CLK = 72MHz,PSC = 71,ARR = 999

CK_CNT = 72MHz / (71 + 1) = 1MHz → 1µs 分辨率
T_ov   = (71 + 1) × (999 + 1) / 72MHz = 72 × 1000 / 72MHz = 1ms

时基单元

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(注意溢出处理)

详细工作原理

输入捕获的完整信号路径:

外部引脚 TIx
     │
     ▼
  ┌─────────┐    ┌──────────┐    ┌──────────┐    ┌──────────────┐
  │ 输入滤波 │───→│ 边沿检测 │───→│ 捕获通道 │───→│ CCR 寄存器   │
  └─────────┘    └──────────┘    └──────────┘    └──────────────┘
       │                                              │
   TIMx_CR1.CKD                                   TIMx_SR.CCxIF
   控制采样时钟                                     捕获完成标志
                                                        │
                                                   ┌────┴────┐
                                                   │ 中断    │
                                                   │ DMA     │
                                                   └─────────┘

捕获时序

TIx 引脚上的边沿到达时:
  1. 输入滤波器(可选)对信号去抖
  2. 边沿检测器识别上升/下降沿
  3. 当前 CNT 值被硬件锁定到 CCRx 寄存器(发生在检测到边沿的同一时钟周期)
  4. TIMx_SR.CCxIF 置 1
  5. 如果 TIMx_DIER.CCxIE 已使能 → 触发中断
  6. 如果 TIMx_DIER.CCxDE 已使能 → 触发 DMA 请求(DMA 自动搬走 CCR 值)

捕获事件触发的 DMA 请求特别适合高频信号测量——DMA 控制器可以将连续捕获的 CCR 值搬到内存数组,无需 CPU 逐次干预。

输入滤波器:通过 TIMx_CCMR1.ICxF[3:0] 位配置,以 t_DTS 为单位设置采样窗口。所有连续 N 个采样值一致时,才认为信号有效,滤除毛刺。

注意溢出处理:两次捕获之间 CNT 可能溢出(从 ARR 回 0),需要通过软件检测更新事件标志来修正捕获差值。

定时器框图

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 可能溢出回零