第3章 GPIO与PWM基础:从点亮LED到精准控速
各位同学,今天我们来聊聊嵌入式控制里最基础、也最核心的两个东西——GPIO和PWM。你想想看,剃须刀的刀头要动起来,靠的就是电机。而电机怎么转、转多快,说白了就是靠PWM信号来控制的。这一章,我会带着大家从最底层的寄存器配置开始,一步步把PWM波形“造”出来。
3.1 GPIO初始化:别小看这第一步
GPIO,通用输入输出口。听起来简单,但我在项目中见过太多人在这里翻车。有一次,一个同事调了半天电机就是不转,最后发现是GPIO模式配成了输入模式——嗯,信号根本出不去。
初始化GPIO,一般需要做这几件事:
- 使能时钟:别以为上电就能用。现在的MCU为了省电,外设时钟默认都是关着的。你得先打开对应GPIO组的时钟。
- 配置模式:输出还是输入?推挽还是开漏?复用功能还是普通IO?这些要明确。
- 设置速度:对于PWM输出,我建议配成高速模式。低速模式下波形边沿会变缓,影响精度。
- 上下拉电阻:一般输出模式不需要,但输入模式建议配上拉,防止浮空。
核心要点:GPIO初始化不是“点个灯”那么简单。它决定了你的PWM信号能不能干净地到达电机驱动芯片。
下面是一个典型的GPIO初始化代码,以STM32为例:
void GPIO_Init(void)
{
// 1. 使能GPIO时钟
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 2. 配置PA0为复用功能输出
GPIOA->MODER &= ~(0x3 << 0); // 先清零
GPIOA->MODER |= (0x2 << 0); // 设为复用功能
// 3. 设置输出类型为推挽
GPIOA->OTYPER &= ~(0x1 << 0);
// 4. 设置速度为高速
GPIOA->OSPEEDR |= (0x3 << 0);
// 5. 无上下拉
GPIOA->PUPDR &= ~(0x3 << 0);
// 6. 配置复用功能为TIM2_CH1
GPIOA->AFR[0] &= ~(0xF << 0);
GPIOA->AFR[0] |= (0x1 << 0); // AF1对应TIM2
}
我的习惯:每次配GPIO,我都会先读一下参考手册的GPIO章节。别嫌麻烦,不同系列MCU的寄存器布局可能不一样。我曾经因为看错数据手册,把AFR寄存器配错了,查了两天才找到原因。
3.2 PWM波形生成:让电机转起来
PWM,脉冲宽度调制。说白了就是让一个方波的高电平时间可以调节。电机看到的高电平时间越长,转速就越快。
生成PWM波形,通常用定时器的比较输出功能。定时器不断计数,当计数值和比较值相等时,输出电平翻转。这样就能产生一个频率和占空比都可控的方波。
配置定时器生成PWM,一般步骤是:
- 使能定时器时钟:和GPIO一样,先开时钟。
- 配置预分频器:决定计数频率。比如系统时钟84MHz,预分频84,计数频率就是1MHz。
- 设置自动重装载值:决定PWM周期。计数频率1MHz,重装载值1000,PWM频率就是1kHz。
- 配置比较值:决定占空比。重装载值1000,比较值500,占空比就是50%。
- 配置输出模式:PWM模式1还是模式2?一般用模式1,即计数小于比较值时输出有效电平。
- 使能输出:别忘了打开定时器的输出通道。
避坑指南:我曾经在配置PWM时,忘了使能定时器的更新中断。结果波形死活出不来。后来发现是定时器根本没开始计数。记住,使能定时器后,还要调用TIM_Cmd()或者设置CR1寄存器的CEN位。
来看一个完整的PWM配置代码:
void PWM_Init(void)
{
// 1. 使能定时器时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
// 2. 配置预分频器
TIM2->PSC = 83; // 84MHz / (83+1) = 1MHz
// 3. 设置自动重装载值
TIM2->ARR = 999; // 1MHz / (999+1) = 1kHz
// 4. 配置比较值(初始占空比50%)
TIM2->CCR1 = 500;
// 5. 配置输出模式为PWM1
TIM2->CCMR1 &= ~(0x7 << 4); // 清零OC1M位
TIM2->CCMR1 |= (0x6 << 4); // 模式1:向上计数时,CNT<CCR时输出有效
// 6. 使能输出
TIM2->CCER |= TIM_CCER_CC1E;
// 7. 使能定时器
TIM2->CR1 |= TIM_CR1_CEN;
}
小技巧:调试PWM时,我习惯先用示波器看波形。如果没有示波器,可以用逻辑分析仪。实在不行,用万用表测平均电压也能大致判断占空比对不对。
3.3 占空比与频率调节:精准控制的核心
占空比和频率,是PWM的两个核心参数。对于剃须刀电机来说:
- 频率:决定了电机转动的“细腻程度”。频率太低,电机会发出嗡嗡声,甚至抖动。频率太高,驱动芯片可能跟不上。我一般选在1kHz到20kHz之间。
- 占空比:直接决定电机转速。占空比越大,平均电压越高,转速越快。但要注意,占空比不能太低,否则电机可能启动不了。
调节占空比,其实就是修改定时器的比较值。比如:
void SetDutyCycle(uint16_t duty)
{
// 限制占空比范围
if(duty > 1000) duty = 1000;
if(duty < 0) duty = 0;
// 更新比较值
TIM2->CCR1 = duty;
}
调节频率,则需要修改预分频器或自动重装载值。但要注意,修改ARR会影响占空比,因为占空比 = CCR / ARR。所以一般先调好频率,再调占空比。
重要提醒:动态修改ARR时,一定要等定时器计数到0再改。否则可能导致波形异常。我一般会在更新中断里修改,或者用影子寄存器功能。
下面是一个频率和占空比同时调节的例子:
void SetPWMParam(uint16_t freq_hz, uint16_t duty_percent)
{
uint16_t arr, ccr;
// 计算ARR
arr = 1000000 / freq_hz - 1; // 假设计数频率1MHz
// 计算CCR
ccr = arr * duty_percent / 100;
// 等待定时器计数到0
while((TIM2->CR1 & TIM_CR1_CEN) && (TIM2->CNT != 0));
// 更新寄存器
TIM2->ARR = arr;
TIM2->CCR1 = ccr;
}
注意:频率和占空比的调节范围是有限的。频率太高,ARR值太小,占空比调节精度会下降。比如频率100kHz,ARR只有9,占空比只能调10个等级。所以要根据实际需求权衡。
3.4 实战经验:剃须刀电机的PWM调参
我在做剃须刀项目时,遇到过一个问题:电机在低速时抖动厉害。后来发现是PWM频率选得太低了,只有500Hz。电机在每个PWM周期里,转速波动很大。
解决办法:把频率提高到10kHz。同时,在低速时(占空比小于20%),我加了一个“软启动”逻辑——先给一个高占空比脉冲,让电机转起来,再降到目标占空比。这样抖动就消失了。
另外,不同电机的特性不一样。有刷电机和无刷电机的PWM控制方式差别很大。有刷电机直接用PWM控制电压就行,无刷电机还需要换相逻辑。这个我们后面章节会详细讲。
我的建议:刚开始做PWM控制时,先不要追求高性能。用最简单的配置,让电机转起来。然后慢慢优化频率、占空比、加减速曲线。一步到位往往容易出问题。
好了,这一章的内容就到这里。GPIO和PWM是嵌入式控制的基础,也是电机控制的起点。下一章,我们会讲定时器的更高级用法——编码器接口和输入捕获。这些在剃须刀的转速检测和位置控制中非常有用。
记住,纸上得来终觉浅。看完代码,一定要上板子试试。只有亲手调过PWM波形,你才能真正理解它的脾气。