目标:掌握时基单元、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 → 输入捕获 → 从模式 → 编码器。
时基单元是定时器的"心脏",由 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),影子寄存器才从预加载寄存器加载新值。
为什么要预加载?
预加载可关闭:在 CubeMX 中取消勾选 auto-reload preload 后,写入 ARR 立即生效(不推荐,除非你清楚后果)。
更新事件 (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 影子寄存器。
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(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
| 函数 | 说明 |
|---|---|
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) { /* 主循环休眠,中断驱动 */ }
}
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 循环。
| 函数 | 说明 |
|---|---|
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;
}
}
外部信号 → 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),需要通过软件检测更新事件标志来修正捕获差值。
| 函数 | 说明 |
|---|---|
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);
// 计算频率/脉宽
}
}
主定时器 → TRGO (触发输出) → 从定时器的触发输入
常见组合:
TIM1 主 (PWM 输出) → TRGO = 更新事件
TIM2 从 (从模式复位) → 每次 TIM1 更新时 CNT 复位
用途:精确测量 PWM 参数
TIM1 CH1 输入捕获 → 捕获周期
TIM1 从模式复位 → 每次上升沿自动复位 CNT
正交编码器 → TI1 (A相) + TI2 (B相)
定时器自动判断正反转:
A 相领先 B 相 → 正转 (CNT++)
B 相领先 A 相 → 反转 (CNT--)
计数模式:
×1:仅 TI1 边沿计数
×2:TI1 + 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);
}
}
int16_t 才能正确表示正负