1. PWM定时器功能概述
PWM(脉冲宽度调制)是嵌入式开发中最常用的外设功能之一。我第一次接触PWM是在大学电子设计竞赛时,需要控制一个小舵机转动特定角度。当时完全不明白为什么调节方波的占空比就能精确控制机械位置,直到后来拆解了几个工业控制器才恍然大悟。
现代MCU的定时器模块通常都集成PWM生成功能,比如STM32的TIM1/TIM8高级定时器支持互补输出带死区控制,非常适合电机驱动场景。而像ESP32这样的物联网芯片,甚至会提供专门的LED PWM控制器,用于实现呼吸灯效果。理解PWM的工作原理,相当于拿到了控制物理世界的钥匙。
2. 硬件电路设计要点
2.1 典型负载接口电路
驱动不同负载需要匹配对应的接口电路。去年帮朋友维修3D打印机时,就遇到因MOSFET选型不当导致加热管控制失灵的案例。这里分享几个典型配置:
- LED调光:直接串联限流电阻即可,注意GPIO驱动能力。比如驱动1W白光LED(3V/300mA),使用5V电源时需要配(5V-3V)/0.3A≈6.8Ω电阻
- 直流电机:必须加装续流二极管,推荐使用SS34肖特基二极管。我曾用IRF540N MOSFET驱动12V减速电机,栅极需加10k下拉电阻
- 伺服舵机:注意信号电平匹配,部分5V舵机需要电平转换。实测SG90舵机在0.5ms-2.5ms脉冲范围内对应0-180度转角
2.2 噪声抑制实践
PWM线路容易引入高频干扰,特别是在长距离传输时。有个智能家居项目就因此导致温控失灵,后来通过以下措施解决:
- 在负载端并联0.1μF陶瓷电容
- 双绞信号线传输
- 增加磁珠滤波(如0805封装的600Ω@100MHz型号)
3. STM32 HAL库配置详解
3.1 初始化代码剖析
以STM32F103的TIM3_CH2通道为例,关键配置步骤如下:
c复制TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 72-1; // 72MHz/72=1MHz计数器时钟
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1000-1; // PWM周期=1MHz/1000=1kHz
HAL_TIM_PWM_Init(&htim3);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_2);
注意:Prescaler和Period的值要减1,因为寄存器从0开始计数。这个细节曾让我调试了半天。
3.2 动态调频技巧
常规用法是固定频率调占空比,但有些场景需要动态调整频率。比如驱动超声波换能器时,我这样实现频率切换:
c复制void PWM_Set_Freq(uint32_t freq) {
uint32_t auto_reload = SystemCoreClock / freq - 1;
__HAL_TIM_SET_AUTORELOAD(&htim3, auto_reload);
// 保持原占空比比例
uint32_t pulse = __HAL_TIM_GET_COMPARE(&htim3, TIM_CHANNEL_2);
pulse = pulse * (auto_reload + 1) / (htim3.Init.Period + 1);
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, pulse);
htim3.Init.Period = auto_reload;
}
4. 进阶应用案例
4.1 多通道同步控制
在机械臂控制项目中,需要6路PWM严格同步。使用STM32的TIM1主从定时器方案:
- 配置TIM1为主模式,触发输出选择更新事件
- 其他定时器设为从模式,触发源选ITR0(TIM1)
- 关键代码:
c复制TIM_SelectOutputTrigger(TIM1, TIM_TRGOSource_Update);
TIM_SelectSlaveMode(TIM2, TIM_SLAVEMODE_TRIGGER);
4.2 死区时间插入
驱动H桥电路时必须配置死区时间,防止上下管直通。通过TIMx_BDTR寄存器的DTG位设置:
- 上升沿延迟 = DTG[7:0] * Tdts
- Tdts由时钟分频决定,当CK_INT=72MHz时:
- 不分频:Tdts=13.89ns
- 2分频:Tdts=27.78ns
计算示例:需要500ns死区时间,选择不分频时:
DTG = 500ns / 13.89ns ≈ 36 → 0x24
5. 常见问题排查指南
5.1 无输出信号检查清单
- 时钟使能:确认RCC_APB1ENR/APB2ENR对应TIMxEN位已置1
- GPIO配置:检查AF模式设置是否正确,特别是重映射情况
- 输出使能:调用HAL_TIM_PWM_Start()后,还需确认CCER寄存器的CCxE位
- 调试器干扰:遇到过J-Link连接时PWM异常,断开后正常
5.2 波形畸变处理
- 上升沿振铃:在GPIO串接22-100Ω电阻,缩短走线长度
- 占空比跳动:检查中断优先级,避免PWM计算被高优先级中断打断
- 频率偏移:当使用内部RC振荡器时,温度变化会导致±1%偏差
6. 性能优化技巧
6.1 DMA驱动方案
需要频繁更新PWM参数时(如LED矩阵扫描),采用DMA可大幅降低CPU负载。配置步骤:
- 创建缓冲数组存放占空比值
- 配置DMA循环模式,目标地址设为TIMx_CCRx寄存器
- 触发DMA后自动更新PWM脉冲宽度
c复制HAL_DMA_Start_IT(&hdma_tim3_up, (uint32_t)pwm_buffer,
(uint32_t)&TIM3->CCR2, BUFFER_SIZE);
TIM_DMACmd(TIM3, TIM_DMA_CC2, ENABLE);
6.2 中断优化策略
在步进电机控制中,使用定时器更新中断实现微步细分:
c复制void TIM3_IRQHandler(void) {
if(__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
static uint8_t step = 0;
uint16_t pulse = microstep_table[step++]; // 查表法
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, pulse);
if(step >= MICROSTEPS) step = 0;
}
}
通过预计算正弦波表实现256细分,实测可使步进电机运行噪音降低12dB。