4. STM32 GPIO与PWM控制:GPIO初始化、PWM生成原理、通过PWM控制直流电机转速
好,咱们进入第四章。这一章要聊的是GPIO和PWM,说白了就是怎么让STM32的引脚干活,以及怎么用它们来驱动直流电机。
我刚开始做嵌入式那会儿,觉得GPIO不就是高低电平嘛,有啥好学的?后来发现,这里面的坑还真不少。尤其是PWM,你要是没搞懂原理,电机转起来那叫一个抖。
4.1 GPIO初始化——别小看这步
GPIO,全称General Purpose Input Output,通用输入输出口。STM32的引脚功能很丰富,但最基础的就是当普通IO用。
我个人习惯,写任何外设驱动前,先把GPIO初始化搞定。这步错了,后面全白搭。
GPIO初始化需要配置的内容:
- 引脚号:你要用哪个引脚?比如PA0、PB1
- 模式:输入、输出、复用功能、模拟模式
- 输出类型:推挽输出还是开漏输出
- 上拉/下拉:要不要内部拉电阻
- 速度:低速、中速、高速、超高速
举个例子,我们要控制直流电机,通常用PWM输出。那引脚就得配成复用推挽输出模式。
// 以STM32F103为例,配置PA0为PWM输出
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA0
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
嗯,这里要注意:时钟一定要先使能。我见过太多新手,上来就配GPIO,忘了开时钟,结果引脚死活没反应。我曾经也犯过这个错,排查了半天才发现是时钟没开。
4.2 PWM生成原理——说白了就是定时器在干活
PWM,脉冲宽度调制。名字听着高大上,其实原理很简单:通过调节高低电平的占空比,来模拟一个连续变化的电压。
你想想看,电机需要的是连续电压才能平稳转动。但单片机只能输出0或3.3V,怎么办?那就快速切换,让电机“感觉”到的是一个平均电压。
PWM的两个关键参数:
- 频率:一秒内切换多少次。频率太低,电机会嗡嗡响;频率太高,驱动芯片可能跟不上。
- 占空比:高电平时间占整个周期的比例。占空比越大,平均电压越高,电机转得越快。
STM32的PWM是靠定时器生成的。定时器内部有个计数器,从0一直数到某个设定值(ARR,自动重装载值)。当计数值小于某个阈值(CCR,捕获比较值)时,输出高电平;大于时,输出低电平。
说白了,ARR决定了频率,CCR决定了占空比。
// 配置定时器2的通道1输出PWM
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 使能定时器2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时器基础配置
TIM_TimeBaseStructure.TIM_Period = 999; // ARR = 999
TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频,72MHz / (71+1) = 1MHz
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// PWM模式配置
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 500; // CCR = 500,占空比50%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 使能定时器
TIM_Cmd(TIM2, ENABLE);
你看,ARR设成999,预分频设成71,那计数频率就是1MHz。一个周期计1000个数,所以PWM频率就是1MHz / 1000 = 1kHz。CCR设成500,占空比就是50%。
避坑指南:
我曾经在项目里把预分频算错了,导致PWM频率只有几十赫兹。电机一启动,整个板子都在抖,声音还特别大。后来用示波器一看,波形跟心电图似的……所以,频率别低于100Hz,最好在1kHz以上。
4.3 通过PWM控制直流电机转速
好了,GPIO和PWM都配好了,怎么让电机转起来?
直流电机通常需要驱动芯片,比如L298N、TB6612之类的。STM32的引脚电流太小,直接推不动电机。驱动芯片的作用就是:用小电流控制大电流。
接线方式很简单:
- STM32的PWM输出引脚 → 驱动芯片的PWM输入引脚
- 驱动芯片的两个方向控制引脚 → STM32的普通GPIO
- 驱动芯片的输出 → 电机
控制逻辑是这样的:
- 方向引脚决定电机正转还是反转
- PWM占空比决定电机转速
// 控制电机正转,占空比70%
void Motor_Forward(uint16_t speed)
{
// 方向控制:假设IN1=1, IN2=0 为正转
GPIO_SetBits(GPIOB, GPIO_Pin_0); // IN1 = 1
GPIO_ResetBits(GPIOB, GPIO_Pin_1); // IN2 = 0
// 设置PWM占空比
TIM_SetCompare1(TIM2, speed); // speed范围0~999
}
// 控制电机反转,占空比50%
void Motor_Backward(uint16_t speed)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0); // IN1 = 0
GPIO_SetBits(GPIOB, GPIO_Pin_1); // IN2 = 1
TIM_SetCompare1(TIM2, speed);
}
// 停止电机
void Motor_Stop(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_0);
GPIO_ResetBits(GPIOB, GPIO_Pin_1);
TIM_SetCompare1(TIM2, 0);
}
调用起来也很直观:
int main(void)
{
// 初始化...
GPIO_Init();
TIM_PWM_Init();
while(1)
{
Motor_Forward(700); // 70%占空比,中速正转
Delay_ms(3000);
Motor_Forward(900); // 90%占空比,高速正转
Delay_ms(3000);
Motor_Backward(500); // 50%占空比,中速反转
Delay_ms(3000);
Motor_Stop();
Delay_ms(2000);
}
}
我的经验:
实际项目中,电机启动时别直接给大占空比。我习惯做一个软启动:占空比从0慢慢增加到目标值,大概花个200ms。这样能避免电流冲击,电机寿命也长一些。
4.4 常见问题与调试技巧
| 问题 | 可能原因 | 解决办法 |
|---|---|---|
| 电机不转 | GPIO没配置对,或者驱动芯片没供电 | 先量一下PWM引脚有没有波形,再查驱动芯片电源 |
| 电机抖动 | PWM频率太低 | 把频率提高到1kHz以上 |
| 电机转速不稳 | 电源供电不足,或者占空比变化太快 | 加电容滤波,或者做软启动 |
| 驱动芯片发烫 | PWM频率太高,或者电机堵转 | 降低频率,或者检查机械结构 |
嗯,这一章的内容差不多就这些。GPIO初始化是基本功,PWM原理也不复杂,关键是要动手试。我建议你拿个示波器看看波形,调调占空比,看看电机转速怎么变。实践出真知嘛。
下一章我们会聊怎么用编码器来测电机转速,实现闭环控制。到时候你会发现,PWM只是基础,真正的挑战还在后面。