最近在搞一个电机控制的项目,STM32 的 PWM 输出是绕不开的核心环节。相信很多小伙伴都遇到过 PWM 输出频率不对、占空比不准等问题。这篇学习日志就来深入剖析 STM32 的 PWM 原理,分享一些实战经验,以及一些常见问题的避坑指南,希望能帮助大家少走弯路。
PWM 基础回顾
脉冲宽度调制 (PWM) 是一种通过改变脉冲信号的占空比来模拟不同电压值的技术。在 STM32 中,PWM 主要通过定时器 (Timer) 模块实现。定时器可以配置为 PWM 输出模式,并设置频率、占空比等参数。理解定时器的各种模式是掌握 PWM 的关键。
STM32 定时器与 PWM 实现原理
STM32 的通用定时器 (TIMx) 具有 PWM 输出功能。要配置 PWM 输出,需要进行以下步骤:
- 使能定时器时钟:通过 RCC (Reset and Clock Control) 寄存器使能定时器时钟。
- 配置定时器时基单元:设置预分频器 (Prescaler) 和自动重装载寄存器 (Auto-Reload Register, ARR) 来确定 PWM 的频率。
- 配置通道为 PWM 输出模式:选择定时器的某个通道 (Channel),将其配置为 PWM 输出模式 (PWM Mode 1 或 PWM Mode 2)。
- 设置比较寄存器 (Capture/Compare Register, CCR):CCR 的值决定了 PWM 的占空比。
- 使能通道输出:使能定时器通道的输出,将 PWM 信号输出到对应的 GPIO 引脚。
例如,如果需要生成一个 1kHz 的 PWM 信号,可以使用以下公式计算预分频器和 ARR 的值:
PWM频率 = 时钟频率 / (预分频器 + 1) / (ARR + 1)
代码示例:基于 HAL 库配置 PWM
下面是一个使用 STM32 HAL 库配置 PWM 输出的示例代码:
TIM_HandleTypeDef htim1; // 定时器句柄
void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim1.Instance = TIM1; // 选择定时器 1
htim1.Init.Prescaler = 71; // 预分频器,根据时钟频率调整
htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数模式
htim1.Init.Period = 999; // ARR 值,决定 PWM 频率
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 时钟分频
htim1.Init.RepetitionCounter = 0; // 重复计数器,高级定时器专用
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; // 自动重载预装载
if (HAL_TIM_Base_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL; // 时钟源为内部时钟
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; // 主输出触发
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; // 主从模式
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM 模式 1
sConfigOC.Pulse = 500; // CCR 值,决定占空比,这里是 50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 输出极性,高电平有效
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 互补输出极性,高级定时器专用
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 快速模式
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET; // 空闲状态
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET; // 互补空闲状态,高级定时器专用
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // 启动 PWM 输出
}
实战避坑经验
- 时钟配置:PWM 频率和精度受时钟频率的影响很大。务必确保时钟配置正确,特别是如果使用了外部晶振或倍频器。很多时候 PWM 频率不准确就是时钟配置的问题。
- GPIO 配置:将对应的 GPIO 引脚配置为复用功能 (Alternate Function),并选择正确的 AF (Alternate Function) 值,才能将定时器的 PWM 信号输出到引脚。很多新手会忘记配置 AF 值,导致没有 PWM 输出。
- 死区时间 (Dead Time):在电机控制等应用中,为了防止上下桥臂直通,需要设置死区时间。高级定时器 (TIM1, TIM8) 具有死区时间生成功能,需要正确配置。不设置死区时间可能导致功率器件损坏。
- 调试工具:使用示波器或逻辑分析仪可以方便地观察 PWM 信号的频率、占空比和波形。这对于调试 PWM 输出非常有用。
- HAL 库的坑:HAL 库虽然方便,但也存在一些坑。例如,HAL 库的 PWM 初始化函数可能会覆盖用户自定义的配置,导致 PWM 输出异常。需要仔细阅读 HAL 库的文档,了解其工作原理。
总结
STM32 的 PWM 功能强大而灵活,但也需要仔细配置和调试。希望这篇学习日志能够帮助大家更好地理解 STM32 的 PWM 原理,并避免一些常见的坑。在实际项目中,还需要根据具体需求进行调整和优化。 比如结合 DMA 来实现更复杂的 PWM 控制,或者使用多个定时器进行同步 PWM 输出。
冠军资讯
CoderPunk