通用GPIO与PWM基础:从点亮LED到驱动电机
做电机控制,绕不开两个基本功:GPIO和PWM。GPIO是单片机的“手脚”,PWM是电机的“油门”。这一章,我带你把这俩基础打牢。
很多初学者一上来就盯着复杂的电机算法,结果连PWM波形都出不来,查半天发现是GPIO模式没配对。嗯,这种坑我踩过不止一次。
2.1 GPIO模式配置——别小看这步
CMS32的GPIO,说白了就是一组可配置的引脚。每个引脚可以当输入、输出、复用功能,或者模拟通道。电机控制里,GPIO主要干三件事:
- 控制使能信号:比如驱动芯片的EN脚,高电平才让电机转
- 读取霍尔/编码器信号:需要配置成输入,还得带上拉
- 输出PWM波形:这时GPIO要配成复用功能,交给定时器模块接管
我个人习惯,初始化GPIO时先看数据手册的引脚功能表。CMS32的GPIO寄存器主要有这几个:
| 寄存器 | 功能 | 电机控制常用配置 |
|---|---|---|
| PxCR | 方向控制(0=输入,1=输出) | 使能脚配输出,霍尔脚配输入 |
| PxDR | 输出数据寄存器 | 使能脚初始拉低,防止上电误转 |
| PxPCR | 上拉/下拉使能 | 霍尔输入建议使能内部上拉 |
| PxFC | 功能选择(GPIO/复用) | PWM脚配复用,其他配GPIO |
我曾经犯过的错:把PWM输出脚配成了推挽输出模式,结果定时器波形出不来,查了两天才发现是PxFC没切到复用功能。记住——PWM引脚必须配成复用开漏或复用推挽,不能配成普通GPIO输出。
配置代码其实很简单,以PA0为例:
// 配置PA0为推挽输出,初始低电平
P0CR &= ~(1 << 0); // 先清方向位
P0CR |= (1 << 0); // 设为输出
P0DR &= ~(1 << 0); // 初始输出低
P0PCR |= (1 << 0); // 使能上拉(可选)
// 配置PA1为复用功能(PWM输出)
P1FC |= (1 << 1); // 切到复用模式
P1CR |= (1 << 1); // 输出方向
2.2 PWM定时器原理——方波里的学问
PWM,脉冲宽度调制。说白了就是让一个方波的高电平时间占比可调。这个占比叫占空比,从0%到100%。
电机控制里,PWM频率通常选在10kHz到20kHz之间。为什么是这个范围?低于10kHz,电机可能会发出人耳可闻的啸叫;高于20kHz,开关损耗又上去了。我一般习惯用16kHz,听不到噪音,效率也还行。
CMS32的定时器产生PWM,核心是三个寄存器:
- 周期寄存器(PR):决定PWM频率。比如系统时钟48MHz,预分频48,PR设1000,那频率就是48M/48/1000=1kHz
- 比较寄存器(CMP):决定占空比。CMP=500时占空比50%
- 控制寄存器(CR):启动/停止、极性选择、对齐方式
关键公式:
PWM频率 = 系统时钟 / (预分频值 × (周期寄存器值 + 1))
占空比 = 比较寄存器值 / (周期寄存器值 + 1) × 100%
你想想看,如果周期寄存器设999,比较寄存器设499,那占空比就是50%。这个关系搞清楚了,调转速就是改比较寄存器的事。
2.3 CMS32 PWM模块初始化——手把手配置
CMS32的定时器模块叫TMR,支持多通道PWM输出。我以TMR0通道0为例,演示完整的初始化流程。
先看整体结构,我用一张图帮你理清脉络:
好,下面看代码。我习惯把初始化封装成一个函数,方便复用:
void TMR0_PWM_Init(uint16_t period, uint16_t duty)
{
// 1. 时钟使能
CG_SetPeripheralClock(CG_PERIPHERAL_TMR0, ENABLE);
// 2. GPIO复用配置 - PA1为TMR0_CH0输出
P1FC |= (1 << 1); // 复用功能
P1CR |= (1 << 1); // 输出方向
P1PCR &= ~(1 << 1); // 关闭上拉(PWM不需要)
// 3. 定时器基本参数
TMR0->CR1 = 0x00; // 先停止定时器
TMR0->PSCR = 47; // 预分频48分频(48MHz/48=1MHz)
TMR0->ARR = period; // 自动重装载值(决定周期)
// 4. 配置PWM模式1(向上计数,有效电平高)
TMR0->CCMR1 |= (0x06 << 4); // 模式1:PWM模式1
TMR0->CCER1 |= 0x01; // 使能输出,极性不反转
// 5. 设置占空比并启动
TMR0->CCR1 = duty; // 比较值(决定占空比)
TMR0->CR1 |= 0x01; // 使能计数器
TMR0->BDTR |= 0x80; // 使能主输出(刹车寄存器必须配)
}
我的调试小技巧:初始化完成后,先用示波器看波形。如果没有示波器,可以用逻辑分析仪,或者干脆用LED串个电阻看亮度变化——占空比越大LED越亮,肉眼就能判断PWM是否工作。
2.4 避坑指南——我踩过的三个雷
做电机驱动这些年,PWM这块我栽过不少跟头。挑三个典型的说说:
- 刹车寄存器没使能:CMS32的定时器有个BDTR寄存器,bit7是MOE位。这个位不置1,PWM死活出不来。我第一次用CMS32时在这卡了整整半天。
- 预分频和周期算错:想要20kHz,结果配出来2kHz,电机嗡嗡响。后来我写了个宏定义,每次改频率直接套公式算,再也没错过。
- 占空比突变导致电流冲击:启动时占空比从0直接跳到50%,电机猛地一抖。后来我加了软启动,占空比每10ms增加1%,平滑多了。
特别注意:在电机运行中修改占空比,一定要先停止定时器,改完再启动。否则比较寄存器在计数中间被改写,可能导致一个异常周期的脉冲,轻则电机抖动,重则烧MOS管。
2.5 实战练习——用PWM控制LED呼吸灯
在正式驱动电机之前,我建议你先拿LED练手。呼吸灯的逻辑和电机调速一模一样,只是频率不同。
练习要求:
- PWM频率:1kHz(LED用这个频率不会闪烁)
- 占空比从0%渐变到100%,再回到0%,周期2秒
- 用PA1输出,接LED串联220Ω电阻到GND
核心代码就这几行:
uint16_t duty = 0;
uint8_t dir = 1; // 1=增加,0=减少
while(1)
{
TMR0->CCR1 = duty;
delay_ms(10); // 每10ms调整一次
if(dir) duty += 10;
else duty -= 10;
if(duty >= 1000) dir = 0;
if(duty == 0) dir = 1;
}
这个实验跑通了,恭喜你——PWM的基础你已经掌握了。接下来驱动电机,无非是把LED换成MOS管,把频率从1kHz改成16kHz,把占空比和转速对应起来。
本章核心要点:
- GPIO配PWM输出必须切复用模式,别忘配PxFC
- PWM频率=时钟/(预分频×(周期+1)),这个公式刻在脑子里
- 初始化顺序:时钟→GPIO→定时器参数→PWM模式→启动
- BDTR的MOE位必须置1,否则没输出
- 改占空比前先停定时器,改完再启动