第3章:GPIO基础

各位同学,今天我们来聊聊C2000最基础、也是最常用的外设——GPIO。

GPIO,全称General Purpose Input Output,通用输入输出口。说白了,它就是芯片和外界打交道的「手脚」。你想让LED亮?靠它。你想读按键状态?也靠它。我刚开始学DSP那会儿,第一个实验就是点亮一个LED,那种成就感,到现在还记得。

3.1 GPIO模块功能框图

先别急着写代码,咱们得先看看GPIO模块长什么样。

C2000的GPIO模块,内部结构其实挺讲究的。它不像有些单片机那样,一个引脚就是一个简单的IO。它内部有输入缓冲、输出缓冲、上拉下拉电阻、甚至还有输入限定器(Qualification)。

核心要点:每个GPIO引脚都可以独立配置为输入、输出、或者外设功能(比如PWM、CAP、QEP等)。

下面这张图,是我自己画的GPIO功能框图。你看一眼,心里就有数了。

C2000 GPIO模块功能框图 GPIO引脚 输入缓冲 (施密特触发) 输出缓冲 (推挽/开漏) 输入限定器 (防抖/采样) 上拉/下拉电阻 (可配置) 外设多路复用 (GPxMUX) GPxDAT寄存器 (读/写数据) GPxDIR寄存器 (输入/输出方向) GPxSET寄存器 (置位输出) 图例: 输入路径 输出路径 配置路径

你看,从引脚进来,先经过输入缓冲,再到输入限定器。输出方向呢,是从GPxDAT寄存器到输出缓冲,再到引脚。中间还有上拉下拉电阻可以配置。嗯,这里要注意:输入限定器是用来防抖的,尤其在读取按键时特别有用。

3.2 GPIO寄存器详解

寄存器是操作GPIO的核心。C2000的GPIO寄存器,每个端口(Port A、Port B等)都有一套独立的寄存器。常用的有这几个:

寄存器名称 功能描述 位宽 读/写
GPxDIR 方向控制寄存器(0=输入,1=输出) 32位 读/写
GPxDAT 数据寄存器(读引脚状态/写输出电平) 32位 读/写
GPxSET 输出置位寄存器(写1置位,写0无影响) 32位 只写
GPxCLEAR 输出清除寄存器(写1清零,写0无影响) 32位 只写
GPxTOGGLE 输出翻转寄存器(写1翻转,写0无影响) 32位 只写

3.2.1 GPxDIR — 方向控制

这个寄存器决定了引脚是输入还是输出。每个位对应一个引脚。

  • 0:引脚配置为输入模式
  • 1:引脚配置为输出模式

举个例子,你想让GPIO0到GPIO7都作为输出,那就写:

GpioDataRegs.GPADIR.all = 0x00FF;  // 低8位设为输出

我个人习惯用位操作,这样更清晰:

GpioDataRegs.GPADIR.bit.GPIO0 = 1;  // 只设置GPIO0为输出
GpioDataRegs.GPADIR.bit.GPIO1 = 1;  // GPIO1为输出

小技巧:如果你只想修改某一位,用位结构体(.bit.)操作,不要用.all,否则容易误改其他引脚的方向。

3.2.2 GPxDAT — 数据寄存器

这个寄存器有点特殊。它既能读,也能写。

  • 读操作:返回引脚当前的实际电平状态(注意,不是输出寄存器的值)
  • 写操作:设置输出电平(前提是该引脚配置为输出模式)

读引脚状态:

uint16_t pinState = GpioDataRegs.GPADAT.bit.GPIO0;  // 读取GPIO0的电平

写输出电平:

GpioDataRegs.GPADAT.bit.GPIO0 = 1;  // GPIO0输出高电平
GpioDataRegs.GPADAT.bit.GPIO0 = 0;  // GPIO0输出低电平

注意:用GPxDAT写输出时,是「读-改-写」操作。如果你同时操作多个位,可能会因为中断而产生竞争。我曾经在项目中遇到过这个问题——一个中断服务程序里改了GPIO,主循环里也在改,结果引脚状态乱跳。后来改用GPxSET和GPxCLEAR就解决了。

3.2.3 GPxSET — 置位寄存器

这个寄存器是「只写」的。你写1,对应的引脚就输出高电平。写0,没任何影响。

GpioDataRegs.GPASET.bit.GPIO0 = 1;  // GPIO0输出高电平
GpioDataRegs.GPASET.all = 0x0001;   // 效果一样

为什么要有这个寄存器?因为它不需要「读-改-写」,直接写1就置位,不会影响其他引脚。这在多任务环境下特别安全。

3.2.4 GPxCLEAR — 清除寄存器

和GPxSET对应,写1清零,写0无影响。

GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;  // GPIO0输出低电平

3.2.5 GPxTOGGLE — 翻转寄存器

写1翻转引脚状态,写0无影响。这个在调试时特别方便。

GpioDataRegs.GPATOGGLE.bit.GPIO0 = 1;  // GPIO0电平翻转

3.3 GPIO输出控制LED实验

好了,理论知识讲完了。咱们来动手做个实验——用GPIO控制一个LED闪烁。

硬件连接很简单:把LED的正极通过一个330Ω电阻接到GPIO0,负极接地。

完整代码:

#include "F2837xD_device.h"  // 根据你的芯片型号调整头文件

void delay_loop(uint32_t count)
{
    volatile uint32_t i;
    for(i = 0; i < count; i++);
}

void main(void)
{
    // 1. 初始化系统控制
    InitSysCtrl();

    // 2. 禁止全局中断
    DINT;

    // 3. 初始化GPIO
    // 设置GPIO0为输出
    GpioCtrlRegs.GPAMUX1.bit.GPIO0 = 0;   // GPIO模式,不是外设功能
    GpioCtrlRegs.GPADIR.bit.GPIO0 = 1;    // 输出方向

    // 4. 使能全局中断
    EINT;

    // 5. 主循环
    while(1)
    {
        // LED亮
        GpioDataRegs.GPASET.bit.GPIO0 = 1;
        delay_loop(5000000);

        // LED灭
        GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;
        delay_loop(5000000);
    }
}

代码很简单,但有几个点我想强调一下:

  1. GPAMUX1寄存器:在设置方向之前,一定要先把引脚配置为GPIO模式(写0)。如果忘了这步,引脚可能被配置成PWM或其他外设功能,你写GPxDAT就没反应。
  2. delay_loop:这个延时函数是纯软件延时,不精确。实际项目中建议用定时器。但做实验嘛,够用就行。
  3. volatile关键字:延时函数里的循环变量一定要加volatile,否则编译器可能优化掉这个循环。

避坑指南:我曾经在调试一个项目时,LED死活不亮。查了半天,发现是GPAMUX没配置,引脚默认被映射到了ePWM功能。你想想看,你往GPxDAT写数据,引脚却在输出PWM波形,那能亮才怪。所以,先配MUX,再配DIR,最后写DAT,这个顺序别搞反了。

如果你想让LED呼吸效果,可以这样:

// 简单的PWM模拟呼吸灯
while(1)
{
    for(i = 0; i < 100; i++)
    {
        GpioDataRegs.GPASET.bit.GPIO0 = 1;
        delay_loop(i * 100);
        GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;
        delay_loop((100 - i) * 100);
    }
    for(i = 100; i > 0; i--)
    {
        GpioDataRegs.GPASET.bit.GPIO0 = 1;
        delay_loop(i * 100);
        GpioDataRegs.GPACLEAR.bit.GPIO0 = 1;
        delay_loop((100 - i) * 100);
    }
}

这个呼吸灯效果,说白了就是通过改变占空比来实现的。虽然软件延时不太精确,但视觉效果还不错。

好了,GPIO的基础内容就这些。记住:GPIO是DSP和外界的桥梁,搞懂了它,后面的外设学习就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321