第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功能框图。你看一眼,心里就有数了。
你看,从引脚进来,先经过输入缓冲,再到输入限定器。输出方向呢,是从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);
}
}
代码很简单,但有几个点我想强调一下:
- GPAMUX1寄存器:在设置方向之前,一定要先把引脚配置为GPIO模式(写0)。如果忘了这步,引脚可能被配置成PWM或其他外设功能,你写GPxDAT就没反应。
- delay_loop:这个延时函数是纯软件延时,不精确。实际项目中建议用定时器。但做实验嘛,够用就行。
- 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