3. GPIO与PWM基础:GPIO配置与输出、PWM原理、定时器生成PWM、占空比与频率控制

好,咱们今天聊点实在的。GPIO和PWM,这俩东西在电机控制里,就像人的手和脚。GPIO负责发号施令,PWM负责精确出力。很多新手一上来就急着调PID,结果连灯都点不亮,电机嗡嗡响就是不转。我当年也干过这事儿,折腾了一下午,最后发现是GPIO模式配错了。

所以,咱们先把地基打牢。这一节,我会带着你从GPIO的配置讲起,再到PWM的原理,最后用定时器亲手生成一个可调频率、可调占空比的PWM波。嗯,都是实战干货。

3.1 GPIO配置与输出:不只是点灯那么简单

GPIO,全称是General Purpose Input Output,通用输入输出口。在电机控制里,它通常用来做三件事:控制使能信号、读取霍尔传感器电平、输出PWM波(部分MCU的PWM也是从GPIO复用出来的)。

我个人习惯,在项目开始前,先把所有用到的GPIO列个表。别嫌麻烦,这能省下后面排查问题的80%时间。

3.1.1 配置步骤(以STM32为例)

配置一个GPIO,说白了就是告诉MCU三件事:

  • 模式:是输入还是输出?输出的话是推挽还是开漏?
  • 速度:跑多快?电机控制一般用高速(50MHz以上)
  • 上下拉:内部要不要接个电阻?

举个例子,控制电机驱动芯片的EN(使能)引脚:

// 使能引脚配置
GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_GPIOA_CLK_ENABLE();   // 先开时钟,这是很多新手容易忘的

GPIO_InitStruct.Pin = GPIO_PIN_8;       // PA8
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;     // 不需要上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 输出高电平,使能电机驱动
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
⚠️ 我曾经踩过的坑: 有一次我忘了使能GPIO的时钟,结果配置了半天,引脚死活没反应。查了半小时原理图,最后发现是RCC时钟没开。记住:任何外设,先开时钟,再配寄存器

3.1.2 输出电平的几种方式

控制GPIO输出,其实就三种操作:置高、置低、翻转。你想想看,电机控制里的使能、刹车、方向切换,本质上都是这些操作的组合。

操作 HAL库函数 寄存器操作(更快)
置高 HAL_GPIO_WritePin(port, pin, SET) GPIOA->BSRR = pin
置低 HAL_GPIO_WritePin(port, pin, RESET) GPIOA->BRR = pin
翻转 HAL_GPIO_TogglePin(port, pin) GPIOA->ODR ^= pin

我个人建议,在电机控制这种对时序要求高的场合,尽量用寄存器操作。HAL库虽然方便,但函数调用开销大,尤其是在中断里频繁翻转引脚时,差别很明显。

3.2 PWM原理:说白了就是“开关”的艺术

PWM,Pulse Width Modulation,脉冲宽度调制。名字听着高大上,其实原理特别简单:用数字信号模拟模拟电压

你想想看,要让电机转得快一点,是不是得给它加更高的电压?但MCU只能输出0V或3.3V,怎么办?那就快速开关,让平均电压变高。开关得越快,电机越平稳。

3.2.1 两个核心参数

  • 频率(Frequency):一秒内开关多少次。单位Hz。电机控制常用10kHz~20kHz,低于人耳听觉范围会有噪音。
  • 占空比(Duty Cycle):一个周期内,高电平时间占多少百分比。50%占空比,平均电压就是1.65V。

举个例子:

  • 频率 = 10kHz,周期 = 100μs
  • 占空比 = 50%,高电平时间 = 50μs
  • 电机得到的平均电压 ≈ 3.3V × 50% = 1.65V
💡 关键点: 占空比决定电机转速,频率决定电机运行的平滑度和噪音。我一般习惯把频率定在16kHz,既能避开人耳敏感区,又能保证MOS管开关损耗不太大。

3.3 定时器生成PWM:这才是核心

MCU怎么产生PWM?靠定时器。说白了,定时器就是一个计数器,从0数到某个值(ARR,自动重装载值),然后归零重新数。在数的过程中,和另一个值(CCR,捕获比较值)做比较,一旦相等,就翻转电平。

你想想看:

  • ARR决定周期 → 决定频率
  • CCR决定翻转点 → 决定占空比

3.3.1 频率与占空比的计算公式

这个公式我建议你记在笔记本上,每次配定时器都会用到:

PWM频率 = 定时器时钟频率 / (PSC + 1) / (ARR + 1)
占空比 = CCR / (ARR + 1) × 100%

其中:

  • PSC:预分频器,把高频时钟降下来
  • ARR:自动重装载值,决定计数周期
  • CCR:捕获比较值,决定占空比

举个例子,我想生成一个16kHz、50%占空比的PWM:

// 假设定时器时钟 = 72MHz
// 目标频率 = 16kHz
// 目标占空比 = 50%

// 第一步:确定ARR和PSC
// 16kHz = 72MHz / (PSC+1) / (ARR+1)
// 我习惯先定ARR,再算PSC
// 取ARR = 4499,则 (PSC+1) = 72MHz / 16kHz / 4500 = 1
// 所以 PSC = 0

// 第二步:计算CCR
// 占空比50%,CCR = (ARR+1) × 50% = 2250

TIM_HandleTypeDef htim2;
TIM_OC_InitTypeDef sConfigOC = {0};

htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;          // PSC = 0
htim2.Init.Period = 4499;          // ARR = 4499
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_PWM_Init(&htim2);

sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 2250;            // CCR = 2250
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
🔧 我的小技巧: 调试时,我习惯先用示波器看PWM波形。如果没有示波器,可以用一个LED串联1k电阻接到PWM输出脚,改变占空比看亮度变化。虽然不精确,但能快速验证配置是否正确。

3.4 占空比与频率的动态控制

电机控制中,频率和占空比不是一成不变的。启动时需要低频大占空比,运行时需要高频小占空比。怎么动态调整?

3.4.1 运行时修改占空比

这个简单,直接修改CCR寄存器就行:

// 运行时修改占空比为75%
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 3375);  // 3375 = 4500 × 75%

注意:修改CCR时,最好在定时器更新事件发生后再改,否则可能产生不完整的脉冲。HAL库的底层已经处理了这个问题,但如果你直接操作寄存器,记得在中断里同步。

3.4.2 运行时修改频率

修改频率需要改ARR,但ARR变了,占空比也会跟着变。所以需要同时调整CCR:

// 假设要把频率从16kHz改为20kHz
// 新ARR = 72MHz / 1 / 20kHz - 1 = 3599
// 要保持占空比50%,新CCR = (3599+1) × 50% = 1800

__HAL_TIM_SET_AUTORELOAD(&htim2, 3599);   // 改ARR
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, 1800);  // 改CCR
⚠️ 注意: 修改ARR时,如果新ARR小于当前计数器的值,计数器会立即溢出。这会导致一个异常长的脉冲。我建议在修改ARR前,先停止定时器,改完再启动。或者用影子寄存器(Shadow Register)机制,等当前周期结束再更新。

3.5 实战经验总结

好了,这一节的内容差不多就这些。最后给你几个我这些年总结出来的经验:

  1. GPIO配置别偷懒:每个引脚的模式、速度、上下拉都要明确配置。不要依赖默认值,不同MCU的默认值可能不一样。
  2. PWM频率选16kHz左右:太低有噪音,太高MOS管发热严重。16kHz是个折中点。
  3. 占空比不要从0直接跳到100%:电机启动时,我习惯用软件做个软启动,占空比从10%逐渐增加到目标值。否则电流冲击很大,容易烧MOS管。
  4. 调试时先看波形:别一上来就接电机。先用示波器确认PWM波形正确,频率和占空比都对了,再接负载。

嗯,这一节就到这里。下一节我们会讲定时器的更高级用法——输入捕获和编码器接口,那是做闭环控制的基础。到时候见。