本文将介绍PS部分GPIO中MIO的使用。本文先通过一个控制LED闪烁的实例体会MIO的用法,学习GPIO相关结构体与API函数的使用;然后再系统讲解GPIO的相关概念。
Zynq设计与代码详解
与第1篇相似,建立一个工程,配置好Zynq的时钟和DDR后,需要在MIO Configuration->I/O Peripherals->GPIO中选中GPIO MIO。一般设计中配置的UART、以太网等外设会占用一部分MIO,这里列表中会显示剩余可用的MIO。
配置完成后按流程导入到SDK中。SDK中新建一个空白工程,src目录上右键->New->File,弹出窗口中填写带后缀的完整文件名,如“main.c”。
开发板的LED与MIO7相连,控制其闪烁的代码清单如下:
#include "xgpiops.h"
#include "sleep.h"
XGpioPs GpioPs_Init()
{
XGpioPs_Config* GpioConfigPtr;
XGpioPs psGpioInstancePtr;
GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
XGpioPs_CfgInitialize(&psGpioInstancePtr, GpioConfigPtr, GpioConfigPtr->BaseAddr);
return psGpioInstancePtr;
}
int main()
{
static XGpioPs psGpioInstancePtr;
int iPinNumber = 7; //MIO7,与LED相连
u32 uPinDirection = 0x1; //1表示输出,0表示输入
psGpioInstancePtr = GpioPs_Init(psGpioInstancePtr); //GPIO初始化
XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber, uPinDirection); //MIO7配置为输出
XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber, 1); //使能MIO7
while(1)
{
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1); //点亮
sleep(1); //延时
XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0); //熄灭
sleep(1); //延时
}
return 0;
}
程序和STM32很相似,由于这是第一次接触Zynq的应用程序代码,我们深入了解一下其中的细节。程序中有两个绿色的结构体,XGpioPs_Config存储了器件的配置信息;用户需要为系统中的GPIO设备分配一个XGpioPs类型的变量,GPIO相关的API函数都需要一个指向该变量的指针。SDK中鼠标移动到结构体上即可查看其原型:
typedef struct {
u16 DeviceId; /* 每个设备都有一个单独的ID */
u32 BaseAddr; /* GPIO的寄存器基地址 */
} XGpioPs_Config;
typedef struct {
XGpioPs_Config GpioConfig; /* 设备配置 */
u32 IsReady; /* 设备是否实例化与准备 */
XGpioPs_Handler Handler; /* 所有bank的状态处理 */
void *CallBackRef; /* bank处理的回调参考 */
u32 Platform; /* 平台数据 */
u32 MaxPinNum; /* GPIO的最大管教号 */
u8 MaxBanks; /* GPIO中的最大bank号 */
} XGpioPs;
GPIO初始化函数中首先用到了XGpioPs_LookupConfig函数,右键->Open Declaration即可查看函数原型。查看源文件我们可以知道,这个函数是根据唯一的设备ID来查找设备配置,其输入参数为要查找的设备ID号,返回值为一个XGpioPs_Config类型的指针,如果没有找到则返回NULL。上面程序中设备ID使用了宏定义XPAR_PS7_GPIO_0_DEVICE_ID,同样可以通过Open Declaration查看源定义。
接下来使用XGpioPs_CfgInitialize函数完成对XGpioPs设备的初始化,第一个参数为待实例化的XGpioPs设备的指针(所以上面程序中加了取地址符&);第二个参数为指向XGpioPs设备的配置结构体;第三个地址为设备在虚拟内存空间中的基地址,上面程序中通过访问XGpioPs_Config结构体的成员来获取基地址。
//两个函数的接口
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId){}
s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, XGpioPs_Config *ConfigPtr, u32 EffectiveAddr){}
主程序中先定义了int型的要操作的MIO管脚号,7即表示MIO7。主程序中初始化GPIO后,先用XGpioPs_SetDirectionPin函数设置指定管脚的方向,第一个参数为指向XGpioPs设备的指针;第二个参数为操作的管脚号;第三个参数位要设置的方向,0表示输入,1表示输出。
接下来使用XGpioPs_SetOutputEnablePin函数设置特定管脚的输出使能,前两个参数的含义与XGpioPs_SetDirectionPin函数相同;第三个参数为0表示禁止输出使能,为1表示启用输出使能。
//两个函数的接口
void XGpioPs_SetDirectionPin(XGpioPs *InstancePtr, u32 Pin, u32 Direction){}
void XGpioPs_SetOutputEnablePin(XGpioPs *InstancePtr, u32 Pin, u32 OpEnable){}
while循环中使用XGpioPs_WritePin函数向管脚写数据,前两个参数的含义与上面两个函数相同;第三个参数为要写入的数据,0或1。while中还用到sleep延迟函数,该函数的延时单位为秒,且传入参数为int型,因此不要希望能实现延时0.5秒、1.5秒这样的功能。
//两个函数的接口
void XGpioPs_WritePin(XGpioPs *InstancePtr, u32 Pin, u32 Data){}
unsigned sleep(unsigned int seconds) {}
这样我们就对GPIO相关操作和相关API函数有了清楚的认识。SDK在保存文件时会自动完成构建操作(编译与链接)。进行Debug调试(或直接运行),可以观察到与PS的MIO7相连的LED灯以1s频率闪烁,功能正确。
GPIO介绍
首先要清楚:GPIO信号≠MIO或EMIO,这在Vivado中是两个概念,MIO和EMIO只是GPIO信号的两种接口,很多初学者确把这些概念混淆。GPIO即General Purpose I/O,Zynq-7000中处理器的GPIO具有如下特性:
GPIO按相相关联的接口信号分为4个bank。GPIO控制和状态寄存器在内存中映射的基地址为)0xE000_A000。GPIO的模块框图如下所示,其中bank0和bank1是通过MIO直接与Zynq管脚相连的部分。另外注意7z007单核与7z010双核CLG225芯片的MIO只有32个,而非其它型号的54个。
通过软件对GPIO或bank的控制,实质上就是对一系列内存映射寄存器的控制,主要是slcr.MIO_PIN_xx寄存器,只不过MIO和EMIO之间会有一些差别。
器件管脚的GPIO控制
软件可以将GPIO配置为输出或输入模式。很多时候,应用程序会需要同时切换多个GPIO。同时切换的GPIO必须来自同一个bank的高16bits或低16bits,使用一个存储指令完成对MASK_DATA寄存器的写入。从上图可以注意到,由于MIO只有54个,因此bank1只有22位。
GPIO通道的结构框图如下:
与MIO相关的(bank0和bank1)bank控制寄存器如下,EMIO在下一篇讲述:
54个与MIO相连的GPIO信号中有两个特例,bank0的bits[7]和bits[8]。在Vivado中配置GPIO时我们会发现,与这两个信号相连的MIO7和MIO8只能作为输出out,而其它MIO都可以作为双向inout。
其实与这两个信号相关的管脚在复位期间要用来控制I/O缓冲器的电压模式,称作VMODE管脚。这两个管脚必须根据合适的电压模式,由外部系统驱动,而不能有其它系统逻辑驱动,因此不能作为输出。但是系统启动之后,已经读取了电压模式,系统便可以将MIO7和MIO8作为输出使用。
总结
最后还是要强调,GPIO才是与处理器直接相关的,分为bank0~bank3四个组。MIO和EMIO只是GPIO信号与外界连接的接口,bank0、bank1通过MIO相连,bank2、bank3通过EMIO相连。哪怕我们在实际使用过程中就是把MIO当作GPIO来看待,没有出现任何问题,但我认为学习应该保持严谨的态度,搞清这些基础的概念,也是专业素养的一种体现。