S02_CH02_MIO实验Enter a post title
阅读原文时间:2023年07月09日阅读:1

S02_CH02_MIO实验

2.1 GPIO简介

Zynq7000系列芯片有54个MIO(multiuse I/O),它们分配在 GPIO 的Bank0 和Bank1隶属于PS部分,这些IO与PS直接相连。不需要添加引脚约束,MIO信号对PL部分是透明的,不可见。所以对MIO的操作可以看作是纯PS的操作。

GPIO的控制和状态寄存器基地址为:0xE000_A000,我们SDK下软件操作底层都是对于内存地址空间的操作。

Bank0:MI0[31:0]

Bank1:MI0[52:53]

Bank2:EMI0[31:0]

Bank3:EMI0[63:32]

2.1.1 GPIO的控制寄存器地址空间

我们在SDK下操作的时候底层都是对这些寄存器的操作,具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf

2.1.2MIO内部构造分析

DATA_RO: 此寄存器使能软件观察PIN脚,当GPIO被配置成输出的时候,这个寄存器的值会反应输出的PIN脚情况。

DATA:此寄存器控制输出到GPIO的值,读这个寄存器的值可以读到最后一次写入该寄存器的值。  

MASK_DATA_LSW:位操作寄存器,写入GPIO 低16bit 其他没有改变的位置保存原先的状态

MASK_DATA_MSW:位操作寄存器,写入GPIO 高16bit 其他没有改变的位置保存原先的状态

DIRM:此寄存器控制输出的开关,当DIRM[x]==0时候,禁止输出

OEN: 输出使能,当OEN[x]==0 的时候输出关闭,PIN脚处于三态

因此,如果要读IO状态就得读DATA_RO的值,如果是对某一位进行操作就是写MASK_DATA_LSW/MASK_DATA_MSW

具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf

2.1.3 EMIO的特性

与MIO大部分类似但是一下几点需要注意下

• EMIO在PL部分,输入与OEN寄存器无关,当DIRM设置为0的时候设置为输入可以读DATA_RO寄存器获取数据。

• 输出不能设置成三态,当DIRM设置为1的时候为输出,写入DATA寄存器或者MASK_DATA_LSW/MASK_DATA_MSW寄存器

• EMIOGPIOTN[x]=DIRM[x] & OEN[x],实现输出的控制。

具体的相关参数请参考技术手册ug585-Zynq-7000-TRM.pdf

2.2 电路分析及实验预期

在米联系列的开发板上有一个MIO是与开发板上的一个LD9相连的,这个MIO就是MIO7。实验通过操作该MIO来实现LD9的闪烁。

2.3 ZYNQ核的添加及配置

Step1:新建一个名为为Miz_sys的工程,正确配置芯片型号,还未掌握的请参照上一章进行设置。

Step2:单击Create Block Design,创建一个BD文件,并命名为System。

Step3:加入一个ZYNQ CPU IP,根据自己的产品型号,正确配置时钟频率与内存类型,尚未掌握的请重新温习上一章内容。

Step4:由于本章需要用到MIO接口,因此需要确保MIO选项被勾选(默认已勾选)。

Step5:单击OK后退出,系统整体电路如下。

Step6:右击 system.bd, 单击Generate Output Products。

Step7:右击system.bd 选择 Create HDL Wrapper产生顶层的HDL文件。

Step8:File->Export->Export Hardware。

Step9:勾选Include bitstream 直接单击OK。

Step10:File->Launch SDK加载到SDK,单击OK。

2.4新建LED_Flash SDK工程

Step1:在SDK界面中,新建一个名为MIO_Test的工程

Step2:建立一个空的工程

Step3:新建一个C的源文件

Step4:取名为main.c

接下来就向main.c中添加内容了,之前讲过,其中MIO7接到了LD9这个灯上,接下来我们利用程序让他闪起来。

#include "xgpiops.h"

#include "sleep.h"

int main()

{

static XGpioPs psGpioInstancePtr;

XGpioPs_Config* GpioConfigPtr;

int iPinNumber= 7; //LD9连接的是MIO7

u32 uPinDirection = 0x1; //1表示输出,0表示输入

int xStatus;

//--MIO的初始化

GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);

if(GpioConfigPtr == NULL)

return XST_FAILURE;

xStatus = XGpioPs_CfgInitialize(&psGpioInstancePtr,GpioConfigPtr, GpioConfigPtr->BaseAddr);

if(XST_SUCCESS != xStatus)

print(" PS GPIO INIT FAILED \n\r");

//--MIO的输入输出操作

XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber,uPinDirection);//配置MIO输出方向

XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber,1);//配置MIO的第7位输出

while(1)

{

XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1);//点亮MIO的第7位输出1

usleep(500000); //延时

XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0);//熄灭MIO的第7位输出0

usleep(500000); //延时

}

/****************************************************************

while(1)

{

XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0080);

usleep(500000); //延时

XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0000);

usleep(500000); //延时

}

******************************************************************/

return 0;

}

2.5 程序分析

接下来我们对整个程序做一个分析。

这是一个指针实例,指向的我们添加进来的GPIO端口。

绿色标识的一个结构体(SDK中结构体都用绿色标识)XGpiops,我们将鼠标停留在这个结构体上面,就可以看到它里面所包含的内容。

从这个图上可以看到,这个结构体上包含了GPIO的一些参数,分别是:设备的配置、设备是否初始化并准备好、所有状态的处理程序、块处理程序的回调、设备数据、GPIO的最大pin数量和GPIO的最大的bank数量。

也是一个指针实例,按照刚才介绍的方法我们来查看下它的释义。

从中可以看出,此结构体存放的是GPIO的设备地址和基地址。

iPinNumber这个参数,是告知程序,操作的MIO是哪一个,因为我们要操作的是MIO7,所以这里所以这里的iPinNumber等于7,在后一章的EMIO中也有这个参数,具体怎么算请参看下一节内容,这里就做个铺垫吧。

这段程序是一段查找GPIO配置程序。XGpiops_Lookupconfig()这个函数是一个xilinx官方提供的GPIO的查找配置的函数,程序的参数为要查找的GPIO的基地址。基地址可从xparameters.h中查看,单击BSP支持包(此处为MIO_Test_bsp)的小三角形,选择Ps7_Cortexa9_0文件夹下的include文件夹,在其中找到xparameters.h,双击打开它。若未找到,则在主界面下的System.mss界面点击,重新生成BSP支持包,此时只要耐心等待即可。如下图所示。

此处我们用到的就是XPAR_PS7_GPIO_0_DEVICE_ID。这段话的整体意思就是查找GPIO的配置,然后判断其是否为空,若为空则返回查找失败。

上图这段程序也是跟刚才大同小异,完成的是gpio配置的初始化工作,如果初始化不成功的话,将通过串口打印出一串初始化失败的通知信息,在此就不再去对其详细的分析。

本章中具体来看看

这个函数,因为此函数中涉及到了一些ZYNQ中GPIO的硬件结构,将鼠标停留在这个函数上,按F3查看其函数定义。

从上图方框圈出的地方我们可以看到此程序给出的功能说明,它完成的是指定pin脚的方向设置。这个程序中,首先它有一个读取bank号的子程序:我们将鼠标停留在这个函数之上,按F3查看下它是具体怎样来查找bank号的。

上图中方框圈出的地方地方就是程序查找bank号的。一开始程序先判断了ZYNQ的类型,在本章第一节GPIO简介中我们知道,7010和7020其实是有四个bank的,因此当程序执行后,其实程序是执行else部分的程序的。此时再来看看else部分的程序。程序首先给出了四个bank的bank号的最大值,然后初始化了bank号为0,接下来的while语句限制了bank的最大数量为4。接下来用pin的序号从bank0到bank4逐个比对,若是此时pin的序号小于或等于当前bank的最大值,则可以判断出pin是属于这个bank的,跳出while语句,否则bank号进行自加操作直到得出bank号。接下来又是一个if语句,判断bank号是否为bank0,若是则将pinnumber直接赋值,否则经过计算一段公式得出pinnumber。

接下来回到XGpioPs_SetDirectionPin函数分析其他的子程序,在获取了bank号之后,是一个读取寄存器的程序这里重点观察第二个参数,这是一个任务寄存器偏移+DIRM_OFFSET的参数,此时我们可打开xilinx的编程手册ug585-zynq-7000-TRM(接下来的内容中我们将将其简称为ug585),来具体看看这个是个什么东西。

复制DIRM,在ug585中查找到这么一段话:

此时得知这其实就是一个方向寄存器,当它等于0的时候输出被禁止,只有输入被运行,也就是此时是作为输入用的,等于1时做输出用。这在GPIO的通道示意图中也能发现有这个部分构成。

回到XGpioPs_SetDirectionPin的分析,再得到了bank号与要写哪个寄存器的地址后,接下来的if else语句就是对这对pinbumer这一位单独做一些操作,最后把方向寄存器的值写入到读出的那个寄存器当中。

回到main.c的分析当中,接下来的XGpioPs_SetOutputEnablePin函数,其原理与设置方向函数的原理是一样的,我们就不再深层次对其进行分析,它完成的功能在程序中也有注释。

最后,我们看到对单个位操作的函数XGpioPs_WritePin,它与之前的程序结构也是大体一致的,它的三个参数分别为gpio的基地址、要操作的MIO号和写入的数据。按下F3查看一下它的定义。

上图中,我们直接看到方框圈起来的部分,此处我们观测到有两个陌生的偏移,此时我们可在ug585中查看一下它们具体是什么意思。

此时可以得知,这两个分别是要写入数据的高16位偏移量和低16位偏移量。此时即可得知这段程序是通过判断pinNumber的值来决定寄存器偏移量是用高16位偏移量还是低16位偏移量。

此时再看XGpioPs_SetOutputEnablePin函数的接下来的这段程序:

这段程序完成的就是向指定MIO写入某个值的操作。我们分析一下这段程序,比如我们要向MIO7写入1,程序一开始已经把要写入的值赋值给了DataVar,在此段程序程序一开始又将DataVar与0x01与操作,此操作后DataVar的值还是为1。

接下来的value就是要写入寄存器的值,我们来看看它是怎么操作的。这里的意思为把PinNumber加上16(也就是把pinNumber移到高16位)赋值为1,然后再取反,执行完后这一段的值为~(80000)h,也就是(FFF7FFFF)h。

再看后半段,之前已经得到DataVar的值为1,因此这里的意思为把pinNumber位赋值为1,再与FFFF0000或操作,执行完这一段的值为(80)h | (FFFF0000)h,也就是(FFFF0080)h,整句执行完之后就是(FFF7FFF)h & (FFFF0080)h=(FFF70080)h。也就是此时Value的值为FFF70080。

XGpioPs_WriteReg这个函数就是往寄存器中写入数据,按下F3查看函数定义。如下图所示。

从图上可知,第一个参数为设备的基地址, 第二个参数为偏移量,此处为0,第三个参数为要写入寄存器的数据。

另外程序还可直接使用寄存器函数对MIO进行操作,其用法参照我们之前的分析,寄存器函数操作如下所示:

XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0080);

usleep(500000); //延时

XGpioPs_WriteReg(0xE000A000,0x00000000, 0xFF7FFFFF&0xFFFF0000);

usleep(500000); //延时

按照之前我们讲过的方法,大家可自行对库函数进行分析。

2.6 本章小结

本章讲解了ZYNQ芯片的GPIO的一些知识,然后通过使用SDK进行编程点亮一个LED。同时分析了程序的代码。测试结果说明了,库函数使用方便,但是效率地下,寄存器效率高,但是使用不方便。因此在设计系统的时候如何优化是需要综合考虑的。

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章