通用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为例,演示完整的初始化流程。

先看整体结构,我用一张图帮你理清脉络:

CMS32 PWM模块初始化流程 1. 时钟使能 2. GPIO复用配置 3. 定时器参数设置 4. PWM模式配置 5. 启动PWM输出 注意:步骤3和4的顺序不能颠倒,必须先设周期再选模式,否则定时器可能异常

好,下面看代码。我习惯把初始化封装成一个函数,方便复用:

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这块我栽过不少跟头。挑三个典型的说说:

  1. 刹车寄存器没使能:CMS32的定时器有个BDTR寄存器,bit7是MOE位。这个位不置1,PWM死活出不来。我第一次用CMS32时在这卡了整整半天。
  2. 预分频和周期算错:想要20kHz,结果配出来2kHz,电机嗡嗡响。后来我写了个宏定义,每次改频率直接套公式算,再也没错过。
  3. 占空比突变导致电流冲击:启动时占空比从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,否则没输出
  • 改占空比前先停定时器,改完再启动