学会Zynq(4)GPIO中MIO的使用方法

本文将介绍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具有如下特性:

  • 54个GPIO信号通过MIO与设备管脚直接相连,且支持三态输出;
  • 192个GPIO信号通过EMIO接口连接PS与PL部分,64个为PL到PS的输入,128个为PS到PL的输出;
  • 每个GPIO可以单个或以组为单位进行动态编程;
  • 支持使能、按bit写数据、按bank写数据、输出使能、方向控制功能;
  • 每个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在下一篇讲述:

  • DATA_RO:无论输入、输出模式,软件都可通过这个寄存器来获取器件管脚上的值。不能向该寄存器写值。但如果MIO没有配置为使能该GPIO管脚,从DATA_RO中读到的值将是无法预料的。
  • DATA:当GPIO信号配置为输出时,该寄存器控制输出的值,一次写入一个bank所有的32bits。读取该寄存器可以得到前一个写入该寄存器的值。
  • MASK_DATA_LSW:控制bank的低16bits。
  • MASK_DATA_MSW:控制bank的高16bits。
  • DIRM:选择管脚方向为输入或输出。实际上GPIO的输入逻辑总是使能状态,DIRM控制的其实是输出驱动器的使能状态。当DIRM[x]==0时,禁用输出驱动器,则GPIO处于输入模式。
  • OEN:当GPIO配置为输出时,OEN控制输出是否使能。当OEN[x]==0禁用输出时,管脚处于三态状态。
  • 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来看待,没有出现任何问题,但我认为学习应该保持严谨的态度,搞清这些基础的概念,也是专业素养的一种体现。


    文章来源:FPGADesigner的博客
    *本文由作者授权转发,如需转载请联系作者本人

    推荐阅读