# 09 定时器(HAL) > 目标:掌握时基单元、PWM、输入捕获、编码器模式 --- ## 定时器整体框图 ![定时器框图](../assets/timer-block.png) STM32 定时器是一个复杂的模块,从输入到输出包含:**时钟源 → 时基单元 → 输出/输入通道 → 从模式控制**。高级定时器(TIM1、TIM8)还包含死区生成、刹车输入等电机控制功能。 ### ⚠️ 定时器时钟源 定时器实际时钟 = `2 × PCLKx`(当 APBx 预分频 > 1 时): ```text 72MHz 配置下(APB1=/2, APB2=/1): TIM2/3/4/5 时钟 = 2 × PCLK1 = 2 × 36MHz = 72MHz TIM1/TIM8 时钟 = PCLK2 = 72MHz(APB2=/1,不分频,时钟不变) ``` 配置时基时用 **定时器时钟** 计算分频,而非 PCLK 总线频率。 本笔记依次讲解:时基单元 → 输出比较/PWM → 输入捕获 → 从模式 → 编码器。 --- ## 9.1 时基单元 ### 时基单元结构 ![时基单元](../assets/timer-timebase.png) 时基单元是定时器的"心脏",由 4 个核心寄存器组成: ```text 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)` | 更新中断回调 | ### 完整代码 ```c 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 ### 原理 ```text PWM Mode 1(向上计数): CNT < CCR → 输出有效电平(高) CNT ≥ CCR → 输出无效电平(低) CNT = ARR → 清零重来 占空比 = CCR / (ARR + 1) 频率 = TIM_CLK / (PSC+1) / (ARR+1) ``` ```text 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)` | 修改占空比 | ### 呼吸灯代码 ```c 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 输入捕获 ### 原理 ```text 外部信号 → 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)` | 捕获完成回调 | ```c void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if (htim == &htim2) { uint32_t val = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1); // 计算频率/脉宽 } } ``` --- ## 9.4 从模式控制器 ### 原理 ```text 主定时器 → 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 编码器模式 ### 原理 ```text 正交编码器 → 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) ### 代码示例 ```c 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 可能溢出回零