1. STM32 PWM波控制实战解析
在嵌入式系统开发中,PWM(脉冲宽度调制)技术是实现模拟量控制的核心手段。作为一名有着多年STM32开发经验的工程师,我经常使用PWM来实现LED调光、电机调速等功能。今天我将分享STM32定时器PWM输出的完整实现过程,包括库函数和寄存器两种操作方式。
PWM本质上是通过调节方波信号的高电平持续时间(占空比)来等效模拟电压输出。在STM32中,这项功能主要由通用定时器(TIM2-TIM5)和高级定时器(TIM1/TIM8)实现。理解PWM的工作原理和实现方法,是掌握STM32高级控制功能的重要一步。
2. PWM基础原理与核心参数
2.1 PWM波形特性解析
PWM波有三个关键参数需要特别关注:
-
频率:决定PWM信号的周期长短,计算公式为f=1/T。例如1kHz的PWM波,其周期为1ms。
-
占空比:一个周期内高电平持续时间与总周期的比值,范围0%-100%。占空比直接决定了等效输出电压的大小。
-
分辨率:占空比可调节的最小步长,由ARR值决定。分辨率=1/(ARR+1),ARR值越大,分辨率越高。
实际应用中,LED调光通常需要≥100Hz的频率以避免肉眼可见的闪烁,而电机控制则常用1kHz-20kHz的频率范围。
2.2 STM32定时器PWM生成原理
STM32的定时器通过以下机制生成PWM波:
- 定时器时钟经过预分频器(PSC)分频后驱动计数器(CNT)计数
- 计数器从0开始向上计数至ARR值后自动重置
- 计数过程中,CNT值与比较寄存器CCR值实时比较
- 根据PWM模式设置,决定输出电平状态
STM32支持两种PWM模式:
- 模式1:CNT < CCR时输出高电平,CNT ≥ CCR时输出低电平
- 模式2:CNT < CCR时输出低电平,CNT ≥ CCR时输出高电平
3. PWM参数计算与配置
3.1 频率与占空比计算
PWM频率计算公式:
code复制f_pwm = f_clk / [(PSC + 1) × (ARR + 1)]
其中f_clk为定时器时钟频率,STM32F103的通用定时器时钟通常为72MHz。
占空比计算公式:
code复制D = (CCR + 1) / (ARR + 1) × 100%
3.2 硬件连接要点
实现PWM输出时,硬件连接需要注意:
- GPIO必须配置为复用推挽输出模式(GPIO_Mode_AF_PP)
- LED调光需串联限流电阻(通常1kΩ)
- 电机控制必须通过驱动模块(如L298N)实现,STM32引脚无法直接驱动电机
4. PWM输出代码实现
4.1 基础PWM输出(库函数版)
c复制#include "stm32f10x.h"
void TIM3_PWM_Init(uint16_t arr, uint16_t psc) {
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
// 使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6为复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置定时器基础参数
TIM_TimeBaseStruct.TIM_Prescaler = psc;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStruct.TIM_Period = arr;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
// 配置PWM模式
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = arr / 2; // 初始50%占空比
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
// 使能预装载
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
// 启动定时器
TIM_Cmd(TIM3, ENABLE);
}
4.2 动态调光实现(呼吸灯效果)
c复制void TIM3_PWM_Init(uint16_t arr, uint16_t psc); // 初始化函数同上
int main(void) {
uint16_t duty = 0;
uint8_t step = 5;
TIM3_PWM_Init(999, 71); // 1kHz PWM
TIM_SetCompare1(TIM3, 0); // 初始熄灭
while(1) {
// 渐亮
while(duty < 999) {
duty += step;
TIM_SetCompare1(TIM3, duty);
Delay_ms(10);
}
// 渐暗
while(duty > 0) {
duty -= step;
TIM_SetCompare1(TIM3, duty);
Delay_ms(10);
}
}
}
5. 电机调速实战
5.1 硬件连接
- TIM3_CH1(PA6) → L298N ENA
- PB0 → L298N IN1
- PB1 → L298N IN2
- L298N OUT → 电机
- 12V电源 → L298N VCC
5.2 代码实现
c复制void Motor_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_ResetBits(GPIOB, GPIO_Pin_0 | GPIO_Pin_1);
}
void Motor_Forward(void) {
GPIO_SetBits(GPIOB, GPIO_Pin_0);
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
}
void Motor_SetSpeed(uint8_t speed) {
if(speed > 100) speed = 100;
uint16_t ccr = (999 * speed) / 100;
TIM_SetCompare1(TIM3, ccr);
}
int main(void) {
TIM3_PWM_Init(999, 71);
Motor_GPIO_Init();
Motor_Forward();
for(uint8_t speed = 0; speed <= 100; speed += 10) {
Motor_SetSpeed(speed);
Delay_ms(500);
}
while(1);
}
6. 常见问题与解决方案
6.1 PWM无输出排查步骤
- 检查GPIO模式是否正确配置为AF_PP
- 确认定时器时钟已使能(TIM3属于APB1)
- 验证PWM模式设置(模式1或模式2)
- 确保CCR值不超过ARR范围
6.2 电机控制异常处理
- 抖动问题:提高PWM频率至1kHz以上
- 电源干扰:在电机电源端并联大容量电容
- 调速不平滑:减小占空比调节步长
7. 进阶应用方向
掌握基础PWM输出后,可以进一步探索:
- 高级定时器的互补PWM输出(带死区控制)
- 多通道PWM同步控制
- 定时器级联实现复杂时序
- PWM音频输出(需配合低通滤波)
- 舵机精确控制(20ms周期,1-2ms脉宽)
在实际项目中,我经常将PWM控制与传感器数据结合使用,比如根据环境光强度自动调节LED亮度,或者根据编码器反馈实现电机闭环控制。这些应用都建立在扎实的PWM基础之上。
