4. 定时器与PWM驱动:基本定时器、通用定时器、高级定时器配置、PWM生成与占空比调节、实战:舵机控制

定时器这玩意儿,可以说是STM32里最常用、也最容易出幺蛾子的外设之一。我刚开始做项目那会儿,总觉得定时器不就是计个数嘛,有啥难的?结果有一次做电机控制,PWM波形死活不对,查了两天发现是定时器同步配置错了。从那以后,我对定时器就多了几分敬畏。

说白了,STM32的定时器分三类:基本定时器、通用定时器、高级定时器。它们的关系有点像汽车——基本型能跑,通用型能拉货,高级型能越野。咱们一个一个来看。

4.1 三类定时器的核心差异

先上一张对比表,帮你快速建立认知:

特性 基本定时器 (TIM6/TIM7) 通用定时器 (TIM2~TIM5) 高级定时器 (TIM1/TIM8)
计数位数 16位 16/32位 16位
PWM输出 ❌ 不支持 ✅ 支持 (4通道) ✅ 支持 (6通道+互补)
输入捕获 ❌ 不支持 ✅ 支持 ✅ 支持
死区插入 ❌ 不支持 ❌ 不支持 ✅ 支持
刹车功能 ❌ 不支持 ❌ 不支持 ✅ 支持
典型用途 定时中断、DMA触发 PWM、输入捕获、编码器 电机控制、逆变器

看到没?基本定时器连PWM都出不来,只能做做定时中断。通用定时器才是咱们做PWM的主力。高级定时器嘛,主要是给电机控制、数字电源这些需要死区互补的场合准备的。

4.2 定时器配置的核心三板斧

不管用哪类定时器,配置流程都绕不开这三个参数:

  • 预分频器 (PSC):决定计数时钟频率。公式很简单:定时器时钟 = 系统时钟 / (PSC + 1)
  • 自动重装载值 (ARR):决定计数周期。溢出频率 = 定时器时钟 / (ARR + 1)
  • 计数模式:向上、向下、中央对齐。PWM最常用向上计数。

举个例子,我想让定时器产生1kHz的PWM,系统时钟72MHz:

// 目标:PWM频率 = 1kHz
// 72MHz / (PSC+1) / (ARR+1) = 1000
// 取 PSC = 71, ARR = 999
TIM_HandleTypeDef htim2;
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;        // 72MHz / 72 = 1MHz
htim2.Init.Period = 999;          // 1MHz / 1000 = 1kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);

我的小习惯:配置PSC和ARR时,尽量让PSC先分频到1MHz左右,再用ARR调频率。这样ARR的分辨率更高,占空比调节更细腻。我在做LED呼吸灯时吃过亏,PSC设太大,ARR只有几十,调占空比时跳变感特别明显。

4.3 PWM生成与占空比调节

PWM的核心就一个概念:比较值 (CCR)。当计数器值小于CCR时,输出高电平;大于CCR时,输出低电平。占空比 = CCR / (ARR + 1)。

配置PWM通道的步骤:

  1. 初始化定时器(PSC + ARR)
  2. 配置PWM通道(极性、比较值)
  3. 使能PWM输出
  4. 启动定时器
// 配置TIM2通道1为PWM输出
TIM_OC_InitTypeDef sConfigOC;
sConfigOC.OCMode = TIM_OCMODE_PWM1;      // PWM模式1
sConfigOC.Pulse = 500;                    // CCR = 500, 占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);

// 启动PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

运行时调节占空比,直接改CCR就行:

// 动态调节占空比到75%
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 750);

我曾经踩过的坑:在中断里直接调用HAL_TIM_PWM_Start(),结果死机了。后来发现,PWM启动只需要一次,不要在循环里反复启停。改占空比用SET_COMPARE宏就行,别动Start/Stop。

4.4 实战:舵机控制

舵机控制是PWM最经典的应用之一。我做过一个六足机器人,用了12个舵机,全是靠定时器PWM驱动的。舵机的控制原理其实很简单:

  • 周期:20ms(50Hz)
  • 0°:脉宽0.5ms(占空比2.5%)
  • 90°:脉宽1.5ms(占空比7.5%)
  • 180°:脉宽2.5ms(占空比12.5%)

配置代码:

// 舵机控制:50Hz PWM
// 72MHz / 71+1 = 1MHz
// 1MHz / 19999+1 = 50Hz
htim2.Init.Prescaler = 71;
htim2.Init.Period = 19999;    // ARR = 19999
HAL_TIM_PWM_Init(&htim2);

// 角度转脉宽函数
void Servo_SetAngle(uint8_t channel, uint16_t angle) {
    // angle: 0~180
    // 脉宽范围: 500~2500us
    // 1MHz下,1us = 1个计数
    uint16_t pulse = 500 + (angle * 2000 / 180);
    __HAL_TIM_SET_COMPARE(&htim2, channel, pulse);
}

// 使用示例
Servo_SetAngle(TIM_CHANNEL_1, 90);  // 转到90°

关键点:舵机对PWM频率很敏感,必须是50Hz左右。频率偏差大了,舵机会抖动或者啸叫。我建议用示波器实测一下脉宽,别光靠算。另外,多个舵机共用一个定时器时,ARR是共享的,但每个通道的CCR独立,所以可以分别控制角度。

4.5 知识结构总览

下面这张图帮你理清本章的知识脉络:

定时器与PWM知识体系 基本定时器 通用定时器 高级定时器 核心配置参数 PSC(预分频器) | ARR(自动重装载) | 计数模式 PWM生成:CCR比较值决定占空比 实战:舵机控制(50Hz,脉宽500~2500μs) ⚠ 避坑提醒 PWM频率必须精确50Hz | 动态调占空比用SET_COMPARE | 多通道共享ARR

4.6 常见问题与调试技巧

做PWM调试时,我总结了几条经验:

  • 波形出不来?先检查GPIO复用功能配置,很多人忘了把引脚设为AF模式。
  • 频率不对?用逻辑分析仪实测,别信计算器。时钟树配置错了,算得再准也没用。
  • 舵机抖动?大概率是PWM频率不准,或者电源供电不足。舵机启动电流很大,别跟其他大负载共用一路电源。
  • 占空比跳变?检查ARR和CCR的位数匹配。16位定时器,ARR最大65535,别超了。

调试小技巧:我习惯在代码里加一个调试模式,用串口打印PSC、ARR、CCR的实际值。这样不用示波器也能快速定位问题。另外,HAL库的HAL_TIM_PWM_Start()其实会重新加载影子寄存器,所以启动顺序很重要——先配置,再启动,别搞反了。

嗯,定时器和PWM这部分就聊到这儿。记住一句话:PSC管频率范围,ARR管频率精度,CCR管占空比。把这仨搞明白了,舵机控制、LED调光、电机调速都不在话下。