Zynq 7010入门笔记

一、Zynq芯片简介

Zynq SoC的核心是处理器系统,相当于Zynq芯片以处理器系统为中心,FPGA(PL)是它的一个外设。 Zynq-7000 SoC由下列几个主要功能模块组成:

  1. 处理器系统——Processing System (PS)
    • 应用处理单元——Application processor unit (APU)
    • 存储接口——Memory interfaces
    • IO外设——I/O peripherals (IOP)
    • 内部互联——Interconnect
  2. 可编程逻辑——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);
}

版权声明:本文为m0_51261356原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。