目录
ZYNQ芯片的的总统框图如下:
ZYNQ 分为 PS 和 PL 两部分,那么器件的引脚资源同样也分成了两部分。 ZYNQ PS 中的外设
可以通过 MIO( Multiuse I/O,多用输入/输出)模块连接到 PS 端的引脚上。同时也可以通过 EMIO 连接到 PL端的引脚。
如图GPIO框图:
GPIO 分为 4 个 Bank,其中 Bank0和 Bank1 连接到 MIO;而 Bank2 和 Bank3 连接到 EMIO。除 Bank1 之外的 Bank 都具有 32bit, Bank1 只具有 22bit, 所以总共 54 个 MIO。Bank2 和 Bank3 用于控制扩展的 MIO 即EMIO,总共64 个 EMIO。
所以控制GPIO的方式可以通过MIO和EMIO,除此之外从框图中还可看出还可通过AXI GPIO控制GPIO。
GPIO控制:
①MIO
②EMIO
③AXI GPIO
PS 所有的外设都可以通过 MIO 访问,这些外设也是与 MIO 进行连接,每个 MIO 可以独立控制,以及独立驱动单个引脚的外设,但是MIO 一但选定,引脚位置就已经确定下来了,不需要添加引脚约束。一般厂商会给MIO与外设的连接情况。PYNQ-Z2的MIO对应表目前我没有找到,查看原理图大多数的外设都是PL的,所以PS可操作外设少。
PS 通过 APB 总线对控制、状态寄存器的读写实现对 GPIO 的驱动。MIO的作用有点像Linux驱动中的Pinctrl子系统的功能:引脚的功能复用。
通过如上红色框相关寄存器的操作实现GPIO的驱动。
DATA_RO:数据只读寄存器,通过该寄存器能够观察器件引脚上的值。如果 GPIO 信号配置为输出,则通常会反映输出上驱动的值,写入此寄存器将被忽略
MASK_DATA_LSW、MASK_DATA_MSW:数据掩码寄存器,该寄存器使软件能够有选择地一次更改所需的的输出值,该寄存器避免了对未更改位的读-修改-写序列的需要
DATA:数据寄存器,该寄存器控制 GPIO 信号配置为输出时要输出的值。该寄存器的所有 32 位都是一次写入的。读取该寄存器返回写入 DATA 或 MASK_DATA_ {LSW, MSW}的先前值,它不会返回器件引脚上的当前值。
DIRM:方向模式寄存器,用于控制 I/O 引脚是用作输入还是输出。当 DIRM [x] == 0 时,输出驱动器被禁用,该引脚作为输入引脚使用。
OEN:使能输出寄存器。当I/O 配置为输出时,该寄存器控制是否启用输出。禁用输出时,引脚为 3态。当 OEN [x] == 0 时,输出被禁用
从这些寄存器中我们可以看到,如果配置引脚为输出,不仅需要设置方向,还要使能输出。但是编程的时候不必在意这些寄存器,直接调用 Xilinx 官方提供的函数即可。
小结:
MIO(multiuse I/O):属于Zynq的PS部分,Zynq7000 系列芯片有 54 个 MIO。它们分配在 GPIO 的 Bank0 和 Bank1 上,这些引脚可以用在GPIO、SPI、UART、TIMER、Ethernet、USB等功能上,每个引脚都同时具有多种功能。
这些 IO 与 PS 直接相连,不需要添加引脚约束。对 MIO 的操作可以看作是纯 PS 的操作。
PS 和外设之间的通信主要是通过MIO实现的,除此之外,PS 还可以通过EMIO( Extended MIO, EMIO)来实现与外部设备的连接。 EMIO 使用了 PL 的I/O 资源, 当 PS 需要扩展超过 54 个引脚的时可以用 EMIO, 也可以用它来连接 PL 中实现的 IP 模块。
PS 端经由 EMIO 引出的接口会直接连接到 PL 端的器件引脚上, 通过 IO 管脚约束来指定所连接 PL 引脚的位置。 通过这种方式, EMIO 可以为 PS 端实现额外的 64 个输入引脚或 64 个带有输
出使能的输出引脚。EMIO 还有一种使用方式,就是连接 PL 内实现的功能模块( IP 核) , 此时 PL 端的 IP 作为 PS 端的一个外部设备。下面会介绍。
注意:MIO 的 SPI, I2C 等资源对应固定的管脚,不能用于所有的MIO口上,但是EMIO映射出的 SPI 和I2C可以拓展到任意PL的IO上。
小结
EMIO:属于Zynq的PS部分,只是连接到了PL上,再从PL的引脚连到芯片外面实现数据输入输出。Zynq7000 系列芯片有 64 个 EMIO,它们分配在 GPIO 的 Bank2 和 Bank3 上,当 MIO 不够用时,PS 可以通过驱动 EMIO 控制 PL 部分的引脚,EMIO 的使用相当于PS + PL 的结合使用。所以,EMIO 需要分配引脚以及编译综合生成 bit文件。
通过 EMIO 实现了 PS 端与 PL 端的交互,而 PS 与 PL 最主要的连接方式则是一组 AXI 接口。 AXI 互联接口作为 ZYNQ PS 和 PL 之间的桥梁, 能够使两者协同工作,进而形成一个完整的、 高度集成的系统。
每个AXI接口包含多个AXI通道,九个PL接口是用了上千个信号来实现的。
AXI全称Advanced eXtensible Interface 是Xilinx从6系列的FPGA开始引入的一个接口协议,主要描述了主设备和从设备之间的数据传输方式。在zYNQ中继续使用,版本是AXI4,所以经常会看到AXI4.0,ZYNQ内部设备都有AXI接口。
在ZYNQ中,支持AXI-Lite,AXI4和AXIl-Stream三种总线
AXI4-Lite: 具有轻量级,结构简单的特点,适合小批量数据、简单控制场合。不支持批量传输,读写时一次只能读写一个字( 32bit )。主要用于访问一些低速外设和外设的控制。
AXI4: 在AXI4-Lite上增加了批量传输功能,可以连续对一片地址进行一次性读写。
上面两种均采用内存映射控制方式,即ARM将用户自定义IP编入某一地址进行访问,读写时就像在读自己的片内RAM,编程简便,但是资源占用过多,需要额外的读地址线、写地址线、读数据线、写数据线、写应答线这些信号线。
AXI4-Stream: 连续流接口,不需要地址线(像FIFO,一直读或一直写就行)。对于这类IP,ARM不能通过内存映射方式控制,必须有一个转换装置。AXI-DMA模块来实现内存映射到流式接口的转换。AXl-Stream适用的场合有很多:视频流处理;通信协议转换;数字信号处理;无线通信等。其本质是针对数值流构建的数据通路,从信源(例如ARM内存、DMA、无线接收前端)到信宿(例如HDMI显示器、高速AD音频输出)构建起连续的数据流,这种接口适合做实时信号处理。
AXI GPIO IP 核为 AXI 接口提供了一个通用的输入/输出接口。 与 PS 端的 GPIO 不同, AXI GPIO 是一个软核( Soft IP),即 ZYNQ 芯片在出厂时并不存在这样的一个硬件电路, 是由用户通过配置 PL 端的逻辑资源来实现的一个功能模块。 PS 端的 GPIO 是一个硬核( Hard IP) ,它是一个生产时在硅片中实现的功能电路。
AXI GPIO 可以配置成单通道或者双通道, 每个通道的位宽可以单独设置。 另外通过打开或者关闭三态缓冲器, AXI GPIO 的端口还可以被动态地配置成输入或者输出接口。其顶层模块的框图如上所示。
模块的左侧实现了一个 32 位的 AXI4-Lite 从接口, 用于主机访问 AXI GPIO 内部各通道的寄存器。 当右侧接口输入的信号发生变化时,模块还能向主机产生中断信号。
小结
AXIGPIO: 由FPGA的PL逻辑核功能实现,相当于GPIO的IP核,是通过AXI总线挂在PS上的GPIO上。使用FPGA的逻辑资源生成的GPIO硬件电路,占用较多的PL端的资源,所以用的是PL资源,而非PS硬件资源。所以AXI GPIO也需要分配引脚以及编译综合生成 bit文件
GPIO相关头文件介绍
xparameters:定义了各个外设的基地址、器件ID、中断
xgpiops:包含 PS GPIO 的函数声明
xil_printf:中断输出相关函数
sleep:延时相关函数xstatus:包含 XST_FAILURE 和 XST_SUCCESS 的宏定义,返回值宏定义
重要结构体:XGpioPs和XGpioPs_Config
- typedef struct {
- XGpioPs_Config GpioConfig; /**< Device configuration */
- u32 IsReady; /**< Device is initialized and ready */
- XGpioPs_Handler Handler; /**< Status handlers for all banks */
- void *CallBackRef; /**< Callback ref for bank handlers */
- u32 Platform; /**< Platform data */
- u32 MaxPinNum; /**< Max pins in the GPIO device */
- u8 MaxBanks; /**< Max banks in a GPIO device */
- } XGpioPs;
-
- typedef struct {
- u16 DeviceId; /**< Unique ID of device */
- u32 BaseAddr; /**< Register base address */
- } XGpioPs_Config;
XGpioPs 结构体里面各个成员变量的大致作用:
GpioConfig 是XGpioPs_Config结构体变量,其内部成员有DeviceId和BaseAddr,保存gpio这个设备的Id和基地址,在zynq中任何外设都有一个相应的id和基地址用来识别。
IsReady,当设备初始化完成,设备已经准备好
Handler ,中断句柄,用于中断读取和写入
CallBackRef 中断回调函数,没有用到中断时可不做关心
Platform 这个是跟你的开发板硬件平台相关,不做关心
MaxPinNum ps端能使用的最大的pin数量,在zynq中为118
MaxBanks 在zynq中的io bank数目为4
(1)查询函数XGpioPs_LookupConfig()
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId)
参数:设备id,可以在#include "xparameters_ps.h"找到id和地址
根据唯一的设备ID号DeviceID,该函数查找设备配置。根据该号,该函数返回一个配置表。 每一个设备都有一个唯一的配置表:
- XGpioPs_Config XGpioPs_ConfigTable[XPAR_XGPIOPS_NUM_INSTANCES] =
- {
- {
- XPAR_PS7_GPIO_0_DEVICE_ID,
- XPAR_PS7_GPIO_0_BASEADDR
- }
- };
XGpioPs_LookupConfig这个函数就是根据设备id,查找配置表,然后将设备的地址等信息,通过XGpioPs_Config 指针返回。
(2)初始化函数XGpioPs_CfgInitialize()
- s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, const XGpioPs_Config *ConfigPtr,
- u32 EffectiveAddr)
InstancePtr:XGpioPs 结构体指针
ConfigPtr:指向XGpioPs_Config 设备指针
EffectiveAddr:指向设备基地址参数2和参数3由XGpioPs_LookupConfig()返回值而来。
该函数用于初始化一个GPiO实例,包括初始化该实例的所有成员。
配置和初始化GPIO模板
- XGpioPs Gpio; /* The driver instance for GPIO Device. */
- XGpioPs_Config *ConfigPtr;
- ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);
- Status = XGpioPs_CfgInitialize(&Gpio, ConfigPtr,ConfigPtr->BaseAddr);
- if (Status != XST_SUCCESS)
- {
- return XST_FAILURE;
- }
(1)设置GPIO输入输出方向XGpioPs_SetDirectionPin()
void XGpioPs_SetDirectionPin(const XGpioPs *InstancePtr, u32 Pin, u32 Direction)
InstancePtr:XGpioPs结构体指针
Pin:要写入数据的Pin的编号,zynq中为0-117(包括MIO和EMIO)
Direction:指定引脚设置的方向,输入方向为:0,输出方向为:1。
(2)设置GPIO输出使能XGpioPs_SetOutputEnablePin
void XGpioPs_SetOutputEnablePin(const XGpioPs *InstancePtr, u32 Pin, u32 OpEnable)
InstancePtr:指向XGpioPs结构体的指针
Pin:Pin是要写入数据的Pin的编号,zynq中为0-117(包括MIO和EMIO)
OpEnable:设定引脚使能开关,0为关闭输出使能,1为打开输出使能。
这两个函数一般 和GPIO的初始化放在一起。
(3)读取GPIO引脚值XGpioPs_ReadPin()
-
- u32 XGpioPs_ReadPin(XGpioPs *InstancePtr, u32 Pin)
InstancePtr:是指向XGpioPs结构体的指针
Pin:要读取数据的Pin编号
功能 :此函数用于读取指定对象的状态
(4)向写入GPIO引脚值XGpioPs_WritePin()
void XGpioPs_WritePin(XGpioPs *InstancePtr, u32 Pin, u32 Data)
InstancePtr:是指向XGpioPs结构体的指针
Pin:要写入数据的Pin编号
Data:写入指定引脚的数据(0或1)
注:尽管 MIO 和 EMIO 组之间存在功能差异,但是对每组 GPIO 的控制是相同的。
(1)GPIO配置和初始化XGpio_Initialize()
int XGpio_Initialize(XGpio * InstancePtr, u16 DeviceId)
InstancePtr :XGpio实例的指针
DeviceId: 设备id
函数内部其实调用了XGpio_LookupConfig和XGpio_CfgInitialize完成了GPIO的初始化。
- /* GPIO driver initialisation */
- Status = XGpio_Initialize(&Gpio, GPIO_DEVICE_ID);
- if (Status != XST_SUCCESS)
- {
- return XST_FAILURE;
- }
(2)设置GPIO输入输出方向XGpio_SetDataDirection()
void XGpio_SetDataDirection(XGpio *InstancePtr, unsigned Channel,u32 DirectionMask)
InstancePtr :指向GPIO实例的指针
Channel: 待设置GPIO的通道(Vivado中设置gpio IP时的设置通道为1或2)
DirectionMask:方向设置。0:output;1:input
(3)读取GPIO值XGpio_DiscreteRead()
u32 XGpio_DiscreteRead(XGpio * InstancePtr, unsigned Channel)
InstancePtr :指向GPIO实例的指针
Channel: 待设置GPIO的通道(Vivado中设置gpio IP时的设置通道为1或2)
返回值: GPIO状态
(4)向GPIO写入数据XGpio_DiscreteWrite()
void XGpio_DiscreteWrite(XGpio * InstancePtr, unsigned Channel, u32 Data)
InstancePtr :指向GPIO实例的指针
Channel: 待设置GPIO的通道(Vivado中设置gpio IP时的设置通道为1或2)
Data :写入的数据(0/1)