Zynq 7010入门笔记
一、Zynq芯片简介
Zynq SoC的核心是处理器系统,相当于Zynq芯片以处理器系统为中心,FPGA(PL)是它的一个外设。 Zynq-7000 SoC由下列几个主要功能模块组成:
- 处理器系统——Processing System (PS)
- 应用处理单元——Application processor unit (APU)
- 存储接口——Memory interfaces
- IO外设——I/O peripherals (IOP)
- 内部互联——Interconnect
- 可编程逻辑——Programmable Logic (PL)

可以看到,GPIO属于一种IO外设。与之类似的还有USB、UART等等。
二、GPIO
1.GPIO简介
GPIO全称为general purpose I/O(通用目的IO),用来对器件的引脚作观测以及控制。将GPIO通过MIO模块引出后即可进行操作。与其他外设不同,GPIO可以用来连接通用的设备,比如按键、蜂鸣器、LED等,亦可用其来模拟一些其他的协议,如I2C。
(MIO( multiuse I/O):将来自PS外设和静态存储器接口的访问多路复用到PS的引脚上)
每个GPIO都可以被独立且动态地编辑为输入(input)、输出(output) 或者是中断模式(interruput sensing)。
GPIO被分成四个bank。通过一个load指令,软件可以读取一个bank中所有GPIO的值;通过一个store指令,软件可以将数据写入一个或多个GPIO中。
GPIO的控制和状态寄存器存储映射到基地址0xE000_A000。

如上图所示,GPIO分为4个bank。bank0和bank1通过MIO模块连接到外部引脚(用户可控制的54个PS端引脚);bank2和bank3通过EMIO连接到PL部分。
注意:bank0的bits[8:7]在系统复位过程中作为VMODE引脚(作为输入),用于配置MIO bank的电压,复位结束后只能作为输出信号,不能用作输入!
2.GPIO控制相关寄存器
软件通过一组存储映射的寄存器来控制GPIO。
如图,寄存器分为3组:
①DATA_RO
②DATA、MASK_DATA_LSW、MASK_DATA_MSW
③DIRM、OEN
DATA_RO寄存器可以实现观测功能。不管GPIO设置为输出还是输入,DATA_RO寄存器始终会返回GPIO引脚的状态,只需要读DATA_RO寄存器即可观测到外部器件的状态。此寄存器为只读。
第二组寄存器用来控制输出数值。
在GPIO被配置成输出时,通过往DATA寄存器中写数据可以控制器件引脚上的数值,每次操作会改写该寄存器的32位数值。DATA寄存器不会返回当前器件引脚的数值。
MASK_DATA_LSW寄存器和MASK_DATA_MSW寄存器使引脚操作的模式更加灵活。可以让我们灵活地改变DATA寄存器中特定的位。MASK_DATA_MSW寄存器控制高16位,MASK_DATA_LSW寄存器控制低16位。将MASK寄存器中的特定位写1即可屏蔽掉DATA中的对应位,这样在写操作时就只用关心需要修改的位,而不用关心其他位。
第三组寄存器控制输出使能信号(Output Enable)。
DIRM寄存器全称Direction Mode,用于控制GPIO引脚作为输入input或者输出output。实际上输入逻辑是一直有效的,所以DIRM寄存器控制的其实是输出的驱动。0:关闭输出驱动;1:打开输出驱动。
只有当GPIO被配置为输出时,OEN寄存器才起作用。它控制输出是否使能。当输出使能被关闭时,引脚将被配置为三态。0:关闭输出使能;1:打开输出使能。
3.软件操作GPIO
在Vivado中配置好硬件后点击SDK进入软件开发套件。操作GPIO需要用到如下几个函数。
//根据器件的ID查找器件的配置信息
/*
DeviceId是器件待查找的独有的ID号
*/
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)
//初始化GPIO驱动
/*
InstancePtr:指向XGpioPs实例的指针
ConfigPtr:指向XGpioPs器件信息的结构体指针
EffectiveAddr:器件的基地址
*/
s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, const XGpioPs_Config *ConfigPtr,u32 EffectiveAddr)
//把GPIO的方向设置为输出(0,输入;1,输出)
/*
InstancePtr:指向XGpioPs实例的指针
Pin:待操作的引脚编号
Direction:设置方向
*/
void XGpioPs_SetDirectionPin(const XGpioPs *InstancePtr, u32 Pin, u32 Direction)
//设置输出使能(0,关闭使能;1,打开使能)
/*
InstancePtr:指向XGpioPs实例的指针
Pin:待操作的引脚编号
OpEnable:决定指定的引脚是否被使能
*/
void XGpioPs_SetOutputEnablePin(const XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)
//向引脚中写数据
/*
InstancePtr:指向XGpioPs实例的指针
Pin:待操作的引脚编号
Data:待写入的数据(0或1)
*/
void XGpioPs_WritePin(const XGpioPs *InstancePtr, u32 Pin, u32 Data)
4.示例
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
//板上PS端LED
#define MIO0_LED 0
XGpioPs_Config *ConfigPtr;
XGpioPs Gpio; /* The driver instance for GPIO Device. */
int main(){
printf("GPIO MIO TEST!\n\r");
//根据器件的ID查找器件的配置信息
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
//初始化GPIO驱动
XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);
//把GPIO的方向设置为输出(0,输入;1,输出)
XGpioPs_SetDirectionPin(&Gpio, MIO0_LED, 1);
//设置输出使能(0,关闭使能;1,打开使能)
XGpioPs_SetOutputEnablePin(&Gpio, MIO0_LED, 1);
while(1){
//点亮
XGpioPs_WritePin(&Gpio, MIO0_LED, 1);
//延时0.5s
usleep(500000);
//熄灭
XGpioPs_WritePin(&Gpio, MIO0_LED, 0);
//延时0.5s
usleep(500000);
}
return 0;
}
三、EMIO
1.EMIO简介
EMIO(extendable multiplexed I/O)是扩展的IO,当54个PS引脚不够用时,可以通过EMIO来进行扩展,从而使用PL端的引脚。如图所示

如上图,实际上EMIO是PS与PL部分的一个接口,其作用是让PS中的外设来使用PL端的引脚。
外设能否连接到EMIO的关系图如下:
2.EMIO与MIO的差别
EMIO输入信号直接连接到PL,与输出值和OEN寄存器的值无关。
EMIO输出非三态使能的信号,所以输出值与OEN无关。输出的值仅取决于DATA、MASK_DATA_LSW、MASK_DATA_MSW三个寄存器。
3.EMIO相关操作及示例
在Vivado软件Block Design中设置好硬件部分,如图:
打开SDK进行软件的编写。
与MIO操作大同小异,直接给出一个例子:
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
//板上PS端LED
#define MIO0_LED 0
//板上PL端按键
#define EMIO_KEY0 54
XGpioPs_Config *ConfigPtr;
XGpioPs Gpio; /* The driver instance for GPIO Device. */
int main(){
u32 key_value;
printf("GPIO EMIO TEST!\n\r");
//根据器件的ID查找器件的配置信息
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
//初始化GPIO驱动
XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);
//把GPIO的方向设置为输出(0,输入;1,输出)
XGpioPs_SetDirectionPin(&Gpio, MIO0_LED, 1);
//把GPIO的方向设置为输入(0,输入;1,输出)
XGpioPs_SetDirectionPin(&Gpio, EMIO_KEY0, 0);
//设置输出使能(0,关闭使能;1,打开使能)
XGpioPs_SetOutputEnablePin(&Gpio, MIO0_LED, 1);
while(1){
//读取按键状态
key_value = XGpioPs_ReadPin(&Gpio,EMIO_KEY0);
//将按键的状态写入LED
XGpioPs_WritePin(&Gpio, MIO0_LED, ~key_value);
}
return 0;
}
四、MIO中断
1.GPIO中断控制相关寄存器

从GPIO图中可以看到,Input信号除了连接到DATA_RO模块之外,还连接到了中断检测逻辑模块(Interrupt Detection Logic)上。除了Input之外,图中左侧还有三个端口与之相连接:
- INT_TYPE:指定中断类型(边沿或电平)
- INT_POLARITY:指定中断极性。对于电平类型中断来说意味着高电平触发或者低电平触发;对于边沿触发类型来说意味着上升沿触发或者下降沿触发。
- INT_ANY:针对边沿触发类型来说,当INT_ANY寄存器写1时,则同时支持上升沿和下降沿两种中断触发类型;当INT_ANY寄存器写0时,则不同时支持上升沿和下降沿两种中断触发类型。
在检测到中断后,中断检测模块会发出一个信号给INT_State,将状态寄存在INT_State寄存器中。这个寄存器有一个清零端Clr,当我们想清除中断状态时,只需往图中INT_STAT寄存器中写1即可。
同时,INT_State寄存器的输出端连接到了INT_STAT端,意味着我们只需读取INT_STAT寄存器,我们就可以获取当前中断的状态。
INT_State的输出段连接到一个与门,同时输入的还有三组寄存器INT_MASK、INT_DIS、INT_EN。
- INT_MASK:只读寄存器,代表MIO中对应的引脚中断被屏蔽。
- INT_DIS:设置被屏蔽中断的引脚。
- INT_EN:设置使能中断的引脚。
最后中断请求输出到GIC(中断控制器),GIC是PS中的一个模块。每个中断源都会有一个ID,GIC接收到中断时,会根据ID去识别中断请求信号来自哪一个外设。参阅官方数据手册可以查到不同中断源的中断ID,比如GPIO为52。
在知道了中断请求来自于哪一个器件之后,我们还需要知道中断来源于哪一个引脚。比如两个按键都能产生中断请求,GIC会受到来自GPIO外设的中断请求,但中断究竟是由哪一个按键产生的呢?此时就要用到INT_MASK中的值。若某一个按键被屏蔽掉了,那么中断自然会来自另外一个按键。如果两个中断的屏蔽位都没有被拉高,那么我们就要查看INT_STAT寄存器中的值,这个寄存器寄存了每个引脚的中断状态,如果按键0的INT_STAT为0,则说明中断不是由它产生;若按键1的INT_STAT为1,则说明中断由它产生。
2.MIO中断相关操作
//打开中断使能
/*
InstancePtr:指向XGpioPs实例的指针
Pin:使能中断的引脚号
*/
void XGpioPs_IntrEnablePin(const XGpioPs *InstancePtr, u32 Pin)
//清除中断状态
/*
InstancePtr:指向XGpioPs实例的指针
Pin:清除中断的引脚号
*/
void XGpioPs_IntrClearPin(const XGpioPs *InstancePtr, u32 Pin)
中断系统设置函数
//设置中断系统
/*
GicInstancePtr:指向XScuGic驱动的实例
Gpio:GPIO设备驱动实例
GpioIntrId:中断号ID,可以从头文件xparameters.h中获得
*/
void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio,u16 GpioIntrId)
{
//查找GIC器件配置信息,并进行初始化
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress);
//初始化ARM处理器异常句柄
Xil_ExceptionInit();
//给IRQ注册异常处理程序
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
GicInstancePtr);
//使能处理器的中断
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
//关联中断处理函数
XScuGic_Connect(GicInstancePtr, GpioIntrId,
(Xil_ExceptionHandler)IntrHandler,
(void *)Gpio);
//为GPIO器件使能中断
XScuGic_Enable(GicInstancePtr, GpioIntrId);
//设置MIO引脚中断触发类型:下降沿
XGpioPs_SetIntrTypePin(Gpio, MIO12_KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
//打开MIO引脚中断使能信号
XGpioPs_IntrEnablePin(Gpio, MIO12_KEY);
}
中断处理函数(例)
void IntrHandler(){
printf("interrupt detected!\n\r");
key_press = 1;
XGpioPs_IntrDisablePin(&Gpio, MIO12_KEY);
}
当检测到中断时,会进入如上所示的中断处理函数。
3.示例
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"
#include "xscugic.h"
#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID
//GPIO的中断号 52
#define GPIO_INTERRUPT_ID XPAR_XGPIOPS_0_INTR
//核心板上PS端LED
#define MIO0_LED 0
//PS端按键KEY
#define MIO12_KEY 12
XGpioPs_Config *ConfigPtr;
XScuGic_Config *IntcConfig; /* Instance of the interrupt controller */
XGpioPs Gpio; /* The driver instance for GPIO Device. */
XScuGic Intc; /* The Instance of the Interrupt Controller Driver */
void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio,u16 GpioIntrId);
void IntrHandler();
u32 key_press = 0;
int main(){
u32 led_value = 0;
printf("GPIO INTERRUTP TEST!\n\r");
//根据器件ID,查找器件的配置信息
ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
//初始化GPIO驱动
XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);
//把GPIO的方向设置为输出(0:输入/1:输出)
XGpioPs_SetDirectionPin(&Gpio, MIO0_LED, 1);
//把GPIO的方向设置为输入(0:输入/1:输出)
XGpioPs_SetDirectionPin(&Gpio, MIO12_KEY, 0);
//设置输出使能(0:关闭/1:打开)
XGpioPs_SetOutputEnablePin(&Gpio, MIO12_KEY, 0);
//设置中断系统
SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID);
while(1){
if(key_press){
led_value = ~led_value;
key_press = 0;
//清除之前的中断状态
XGpioPs_IntrClearPin(&Gpio, MIO12_KEY);
//将led_value的值写入LED
XGpioPs_WritePin(&Gpio, MIO0_LED, led_value);
//延时消抖
usleep(200000);
//打开中断使能
XGpioPs_IntrEnablePin(&Gpio, MIO12_KEY);
}
}
return 0;
}
void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio,u16 GpioIntrId)
{
//查找GIC器件配置信息,并进行初始化
IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,IntcConfig->CpuBaseAddress);
//初始化ARM处理器异常句柄
Xil_ExceptionInit();
//给IRQ注册异常处理程序
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
GicInstancePtr);
//使能处理器的中断
Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
//关联中断处理函数
XScuGic_Connect(GicInstancePtr, GpioIntrId,
(Xil_ExceptionHandler)IntrHandler,
(void *)Gpio);
//为GPIO器件使能中断
XScuGic_Enable(GicInstancePtr, GpioIntrId);
//设置MIO引脚中断触发类型:下降沿
XGpioPs_SetIntrTypePin(Gpio, MIO12_KEY, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
//打开MIO引脚中断使能信号
XGpioPs_IntrEnablePin(Gpio, MIO12_KEY);
}
void IntrHandler(){
printf("interrupt detected!\n\r");
key_press = 1;
XGpioPs_IntrDisablePin(&Gpio, MIO12_KEY);
}