概述
F28335共有88个复用IO端口。共被分成A、B和C三个分组。如下:
- A分组:GPIO0-GPIO31
- B分组:GPIO32-GPIO63
- C分组:GPIO64-GPIO87
GPIO共有3组寄存器,分别为:GPIO控制寄存器、GPIO中断寄存器和GPIO数据寄存器。其中,前两者是受EALLOW保护的。可以总结如下表格:
寄存器名称 | EALLOW保护 | 编程结构体 |
---|---|---|
GPIO控制寄存器 | 是 | GpioCtrlRegs |
GPIO中断寄存器 | 是 | GpioIntRegs |
GPIO数据寄存器 | 否 | GpioDataRegs |
寄存器源代码
关于寄存器源代码实现方式,可以参见我之前的博客——F28335第三篇——寄存器文件结构(CODE_SECTION,DATA_SECTION)
GPIO控制寄存器
GPIO控制寄存器主要配置各个通用IO口的基本功能。例如,复用端口的功能选择,端口采样周期,端口数据方向选择等。具体作用,可以参考相关书籍或者TI文档。
//起始地址为0x6F80
struct GPIO_CTRL_REGS
{
union GPACTRL_REG GPACTRL; // GPIO A组控制寄存器(GPIO0 to 31)
union GPA1_REG GPAQSEL1; // GPIO A组输入限定选择寄存器1 (GPIO0 to 15)
union GPA2_REG GPAQSEL2; // GPIO A组输入限定选择寄存器2 (GPIO16 to 31)
union GPA1_REG GPAMUX1; // GPIO A组功能选择寄存器1 (GPIO0 to 15)
union GPA2_REG GPAMUX2; // GPIO A组功能选择寄存器2 (GPIO16 to 31)
union GPADAT_REG GPADIR; // GPIO A组方向选择寄存器 (GPIO0 to 31)
union GPADAT_REG GPAPUD; // GPIO A组上拉控制寄存器(GPIO0 to 31)
Uint32 rsvd1;
union GPBCTRL_REG GPBCTRL; // GPIO B组控制寄存器(GPIO32 to 63)
union GPB1_REG GPBQSEL1; // GPIO B组输入限定选择寄存器1 (GPIO32 to 47)
union GPB2_REG GPBQSEL2; // GPIO B组输入限定选择寄存器2(GPIO48 to 63)
union GPB1_REG GPBMUX1; // GPIO B组功能选择寄存器1 (GPIO32 to 47)
union GPB2_REG GPBMUX2; // GPIO B组功能选择寄存器2 (GPIO48 to 63)
union GPBDAT_REG GPBDIR; // GPIO B组方向选择寄存器1 (GPIO32 to 63)
union GPBDAT_REG GPBPUD; // GPIO B组上拉控制寄存器 (GPIO32 to 63)
Uint16 rsvd2[8];
union GPC1_REG GPCMUX1; // GPIO C组功能选择寄存器1 (GPIO64 to 79)
union GPC2_REG GPCMUX2; // GPIO C组功能选择寄存器2 (GPIO80 to 87)
union GPCDAT_REG GPCDIR; // GPIO C组方向选择寄存器1 (GPIO64 to 87)
union GPCDAT_REG GPCPUD; // GPIO C组上拉控制寄存器 (GPIO64 to 87)
};
注意
C组相对于A、B两组寄存器数量和端口的数量都较少!
GPIO中断寄存器
此寄存器主要有两个作用:
- 选择外部中断的触发端口对应的GPIO口
- 低功耗唤醒端口选择
//起始地址为0x6FE0
struct GPIO_INT_REGS
{
union GPIOXINT_REG GPIOXINT1SEL; //外部中断源XINT1输入端口选择寄存器(GPIO0-GPIO31)
union GPIOXINT_REG GPIOXINT2SEL; //外部中断源XINT2输入端口选择寄存器 (GPIO0-GPIO31)
union GPIOXINT_REG GPIOXNMISEL; //外部中断源XINMI输入端口选择寄存器 (GPIO0-GPIO31)
union GPIOXINT_REG GPIOXINT3SEL; //外部中断源XINT3输入端口选择寄存器 (GPIO32-GPIO63)
union GPIOXINT_REG GPIOXINT4SEL; //外部中断源XINT4输入端口选择寄存器 (GPIO32-GPIO63)
union GPIOXINT_REG GPIOXINT5SEL;//外部中断源XINT5输入端口选择寄存器 (GPIO32-GPIO63)
union GPIOXINT_REG GPIOXINT6SEL;//外部中断源XINT6输入端口选择寄存器 (GPIO32-GPIO63)
union GPIOXINT_REG GPIOXINT7SEL; //外部中断源XINT7输入端口选择寄存器(GPIO32-GPIO63)
union GPADAT_REG GPIOLPMSEL; //LPM唤醒输入端口选择寄存器(GPIO0-GPIO31)
};
注意:
每个外部端口中断源并不是任意选择的,具体选择范围见注释括号内容。
GPIO数据控制器
此寄存器主要就是实现通用端口的数据读写功能。
//起始地址为OX6FC0
struct GPIO_DATA_REGS
{
union GPADAT_REG GPADAT; // GPIO A组数据寄存器 (GPIO0 to 31)
union GPADAT_REG GPASET; // GPIO A组置位寄存器 (GPIO0 to 31)
union GPADAT_REG GPACLEAR; // GPIO A组清零寄存器(GPIO0 to 31)
union GPADAT_REG GPATOGGLE; // GPIO A组状态翻转寄存器 (GPIO0 to 31)
union GPBDAT_REG GPBDAT; // GPIO B组数据寄存器 (GPIO32 to 63)
union GPBDAT_REG GPBSET; // GPIO B组置位寄存器 (GPIO32 to 63)
union GPBDAT_REG GPBCLEAR; // GPIO B组清零寄存器 (GPIO32 to 63)
union GPBDAT_REG GPBTOGGLE; // GPIO B组状态翻转寄存器 (GPIO32 to 63)
union GPCDAT_REG GPCDAT; // GPIO C组数据寄存器(GPIO64 to 87
union GPCDAT_REG GPCSET; // GPIO C组置位寄存器 (GPIO64 to 87)
union GPCDAT_REG GPCCLEAR; // GPIO C组清零寄存器(GPIO64 to 87)
union GPCDAT_REG GPCTOGGLE; // GPIO C组状态翻转寄存器 (GPIO64 to 87)
Uint16 rsvd1[8];
};
注意:
- F28335为每个端口数据控制设置了四个寄存器。
- 数据寄存器(GPxDAT)是最直观的寄存器,读取值即为当前端口的状态值。可以直接写入1或0来置位或者清零。但是需要注意的是,当使用GPxDAT改变一个输出引脚的状态时,可能会对同一端口的其他引脚产生不确定的影响。 尤其是在对同一个端口不同位连续赋值的时候,具体问题及解决方法将在实例中详细展示!
- 置位寄存器(GPxSET)将端口状态置1。对该寄存器写1有效,写0没有任何效果。读取永远返回0。
- 清零寄存器(GPxCLEAR)将端口状态清0。对该寄存器写1有效,写0没有任何效果。读取永远返回0。
- 翻转寄存器(GPxTOGGLE)将端口状态取反。对该寄存器写1有效,写0没有任何效果。读取永远返回0。
GPIO寄存器作用原理图
选择GPIO0到GPIO27为例,其余端口以此类推。图片提取自TI官方文档。可以通过该原理图分析如何使用每一个寄存器。
另外附上中文版,选自<手把手教你学DSP>
例程
功能
每次按下按键,使得蜂鸣器发声,两个LED灯交换点亮。
硬件原理图
源代码
主程序
/*
* 主程序,功能实现:
* 1.通过开关实现打开关闭led灯。
* 2.通过开关实现打开蜂鸣器
* main.c
*/
#include"DSP28335.h"
int main(void) {
//1.系统初始化
InitSysCtrl();
//2.初始化GPIO
InitKeyGpio(); //初始化GPIO
//3.中断配置
//3.1 关闭PIE中断
DINT;
//关闭总中断
IER = 0x0000; //关闭CPU中断
IFR = 0x0000; //清除中断标志位
InitPieCtrl(); //清除所有PIE中断
InitPieVectTable(); //初始化中断向量表,防止误触发
//3.2写入中断服务程序
EALLOW;
PieVectTable.XINT1 = &IsrXint1; //写入中断服务程序
EDIS;
//3.3 打开中断
PieCtrlRegs.PIECTRL.bit.ENPIE=1;//使能PIE中断
PieCtrlRegs.PIEIER1.bit.INTx4=1;//打开第一组第4个中断
IER|=0x0001;//打开CPU第一组中断
EINT;//打开所有中断
//3.4配置外部中断
XIntruptRegs.XINT1CR.bit.ENABLE = 1; //外部中断使能
XIntruptRegs.XINT1CR.bit.POLARITY = 0; //下降沿触发
//4.功能函数
GpioDataRegs.GPBDAT.bit.GPIO60=1;//关闭蜂鸣器
GpioDataRegs.GPASET.bit.GPIO6=1;//关闭LED1
GpioDataRegs.GPACLEAR.bit.GPIO7=1;//打开LED2
while(1);
}
中断服务程序
interrupt void IsrXint1(void) {
PieCtrlRegs.PIEACK.bit.ACK1 = 1; //第一组中断已经响应
//让两个灯交换闪烁
GpioDataRegs.GPATOGGLE.bit.GPIO6 = 1;
GpioDataRegs.GPATOGGLE.bit.GPIO7 = 1;
GpioDataRegs.GPBDAT.bit.GPIO60 = 0; //打开蜂鸣器
DELAY_US(10000);//蜂鸣器延时
GpioDataRegs.GPBDAT.bit.GPIO60 = 1; //关闭蜂鸣器
}
GPIO初始化程序
void InitKeyGpio() {
EALLOW;
//led1
GpioCtrlRegs.GPAMUX1.bit.GPIO6=0;//普通IO功能
GpioCtrlRegs.GPADIR.bit.GPIO6=1;//方向为输出
//led2
GpioCtrlRegs.GPAMUX1.bit.GPIO7=0;//普通IO功能
GpioCtrlRegs.GPADIR.bit.GPIO7=1;//方向为输出
//蜂鸣器
GpioCtrlRegs.GPBMUX2.bit.GPIO60=0;//普通IO功能
GpioCtrlRegs.GPBDIR.bit.GPIO60=1;//方向为输出
//按钮
GpioCtrlRegs.GPAMUX1.bit.GPIO13=1;//普通IO功能
GpioCtrlRegs.GPADIR.bit.GPIO13=0;//方向为输入
GpioCtrlRegs.GPAQSEL1.bit.GPIO13=0;//同步输入
GpioIntRegs.GPIOXINT1SEL.bit.GPIOSEL=13;//将GPIO13设置为外部中断1
EDIS;
}
注意事项
在主程序中有以下几行代码,关于led初始化的。
//4.功能函数
GpioDataRegs.GPASET.bit.GPIO6=1;//关闭LED1
GpioDataRegs.GPACLEAR.bit.GPIO7=1;//打开LED2
若是写成如下格式:
GpioDataRegs.GPADAT.bit.GPIO6=1;//关闭LED1
GpioDataRegs.GPADAT.bit.GPIO7=0;//打开LED2
很有可能就会初始化失败。即LED1和LED2的状态和设定值不相同。这就是因为前文所说的当使用GPxDAT改变一个输出引脚的状态时,可能会对同一端口的其他引脚产生不确定的影响。 为了解决这个问题,最好的解决方式按照上文使用其他三组寄存器。如果一定要使用使用GPxDAT寄存器,本人实验发现有以下几种方式,也可以得到理想结果。但是,还是要慎用!
写两遍
GpioDataRegs.GPADAT.bit.GPIO6=1;//关闭LED1
GpioDataRegs.GPADAT.bit.GPIO6=1;//关闭LED1
GpioDataRegs.GPADAT.bit.GPIO7=0;//打开LED2
GpioDataRegs.GPADAT.bit.GPIO7=0;//打开LED2
加入延时
GpioDataRegs.GPADAT.bit.GPIO6=1;//关闭LED1
DELAY_US(10);
GpioDataRegs.GPADAT.bit.GPIO7=0;//打开LED2
DELAY_US(10);