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只是基础,真正的挑战还在后面。