1. STM32 PWM功能概述
PWM(脉冲宽度调制)是嵌入式开发中最常用的外设功能之一,在电机控制、LED调光、音频生成等场景中应用广泛。STM32系列MCU的定时器模块提供了强大的PWM生成能力,通过灵活配置可以实现各种复杂的波形输出。
我最初接触PWM是在一个无人机电调项目中,需要精确控制无刷电机的转速。当时发现STM32的PWM配置比想象中复杂得多,寄存器配置稍有偏差就会导致输出异常。经过多个项目的实践积累,我总结出一套稳定可靠的PWM配置方法,下面将详细介绍从基础原理到实战应用的全过程。
2. PWM硬件原理与STM32实现
2.1 PWM波形基本参数
一个标准的PWM波形有三个关键参数:
- 周期(Period):完整波形循环的时间长度
- 占空比(Duty Cycle):高电平持续时间占周期的百分比
- 极性(Polarity):有效电平是高电平还是低电平
以控制LED亮度为例,当PWM频率高于100Hz时,人眼就看不到闪烁现象,此时通过调节占空比就能实现平滑的亮度变化。占空比越大,LED看起来越亮。
2.2 STM32定时器的PWM模式
STM32的通用定时器(TIM2-TIM5)和高级定时器(TIM1,TIM8)都支持PWM输出,主要区别在于:
- 通用定时器:4个独立通道,支持互补输出
- 高级定时器:额外支持刹车功能、死区插入等电机控制特性
以TIM3为例,其内部结构包含:
- 时基单元:控制计数器频率和周期
- 捕获/比较寄存器:存储比较值
- 输出控制电路:生成实际PWM波形
关键提示:不同STM32系列的定时器资源差异较大,F1系列每个定时器只有4个通道,而H7系列可达8个通道。选型时需注意数据手册。
3. 开发环境准备
3.1 硬件需求
- STM32开发板(如STM32F103C8T6最小系统板)
- LED和220Ω限流电阻
- 示波器(可选,用于波形观测)
- ST-Link调试器
3.2 软件工具
- STM32CubeMX 6.6.1
- Keil MDK 5.36
- STM32CubeF1 HAL库
4. CubeMX工程配置
4.1 时钟树配置
- 选择外部晶振作为时钟源(通常8MHz)
- 配置PLL倍频得到72MHz系统时钟
- APB1总线时钟设为36MHz(TIM3挂载在此总线)
4.2 TIM3参数设置
- 选择Clock Source为Internal Clock
- 配置Prescaler为35(得到2MHz计数频率)
- 设置Counter Period为1999(生成1kHz PWM波)
- 启用Channel1的PWM Generation模式
- 配置Pulse初始值为500(25%占空比)
4.3 GPIO配置
- 将TIM3_CH1映射到PA6引脚
- 设置GPIO Mode为Alternate Function Push-Pull
- GPIO Pull-up/Pull-down选择No pull-up and no pull-down
5. 代码实现详解
5.1 HAL库初始化
c复制/* TIM3初始化函数 */
MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 35;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1999;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim3);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig);
HAL_TIM_PWM_Init(&htim3);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_MspPostInit(&htim3);
}
5.2 主程序逻辑
c复制int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM3_Init();
// 启动PWM输出
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
while (1)
{
// 呼吸灯效果实现
for(int i=0; i<=2000; i+=10){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(10);
}
for(int i=2000; i>=0; i-=10){
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, i);
HAL_Delay(10);
}
}
}
6. 关键参数计算原理
6.1 PWM频率计算
公式:Fpwm = Ftim_clock / ((Prescaler + 1) * (Period + 1))
以本配置为例:
- Ftim_clock = 72MHz / (APB1 prescaler) = 36MHz
- Prescaler = 35 → 实际分频系数=36
- Period = 1999
- Fpwm = 36MHz / (36 * 2000) = 500Hz
实测技巧:当需要精确频率时,建议先用公式计算理论值,再用示波器实测校准。
6.2 占空比调节
占空比 = (Pulse / (Period + 1)) * 100%
例如:
- Pulse=500时:500/2000=25%
- Pulse=1500时:1500/2000=75%
7. 进阶应用技巧
7.1 多通道同步输出
c复制// 同时配置通道1和通道2
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2);
// 设置不同占空比
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 1000); // 50%
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, 1500); // 75%
7.2 互补输出配置(带死区)
c复制// 高级定时器TIM1配置示例
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 54; // 约1us死区时间
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
8. 常见问题排查
8.1 无PWM输出
- 检查GPIO是否配置为复用功能
- 验证定时器时钟是否使能
- 确认HAL_TIM_PWM_Start()已调用
- 用示波器检查引脚是否有信号
8.2 波形抖动严重
- 降低系统中断频率
- 检查电源稳定性
- 尝试增加PWM周期值
- 避免在中断服务程序中修改PWM参数
8.3 占空比调节不线性
- 检查Period值是否过小
- 确认Pulse值不超过Period
- 浮点运算时注意数据类型转换
9. 实际项目应用案例
9.1 直流电机调速
c复制// 通过ADC读取电位器值调节PWM
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
uint16_t adc_val = HAL_ADC_GetValue(&hadc1);
uint16_t pulse = (adc_val * 2000) / 4095; // 12位ADC转Pulse值
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}
9.2 舵机控制
c复制// 标准舵机控制信号(50Hz, 0.5ms-2.5ms脉宽)
void Servo_SetAngle(TIM_HandleTypeDef *htim, uint32_t Channel, float angle)
{
// 角度转脉冲宽度(0°=0.5ms, 180°=2.5ms)
float pulse_width = 0.5 + (angle / 180.0) * 2.0;
// 20ms周期,72MHz时钟,预分频72-1
uint16_t pulse = (uint16_t)(pulse_width * 1000);
__HAL_TIM_SET_COMPARE(htim, Channel, pulse);
}
10. 性能优化建议
- 直接寄存器操作:对实时性要求高的场景,直接操作TIMx->CCR1寄存器比HAL库更快
c复制TIM3->CCR1 = 1000; // 直接设置占空比
- DMA传输:需要频繁更新PWM参数时,可使用DMA减轻CPU负担
c复制HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t *)pwm_values, buffer_size);
-
定时器级联:复杂波形生成时,可将多个定时器级联使用
-
中断优化:避免在PWM中断中执行耗时操作,保持中断服务程序精简
