ZYNQ7000-GPIO详解
本文介绍了ZYNQ7000芯片中GPIO的基本概念,分组、功能、控制寄存器、中断设置以及如何在Vitis中配置GPIO。
摘要
本文介绍了ZYNQ7000芯片中GPIO的基本概念,分组、功能、控制寄存器、中断设置以及如何在Vitis中配置GPIO。
本文参考:UG585 - Zynq-7000 SoC Technical Reference Manual (v1.12.2) 385~394页–Ch14: General Purpose I/O(GPIO)
关键词:ZYNQ;GPIO;MIO;EMIO;Vitis
一. GPIO的基本概念
GPIO,General Purpose I/O,通用输入/输出,是ZYNQ的外设之一。ZYNQ的架构图如下图所示。
与非SOC不同的是,ZYNQ的GPIO引脚由PS侧的MIO引脚和PL侧的EMIO引脚构成(见上图)。
关于MIO和EMIO的详细介绍参见我的另一篇博客:传送门:ZYNQ7000-MIO与EMIO详解
二. GPIO框图与分组
ZYNQ的GPIO框图如下图所示。ZYNQ的GPIO引脚分为4个Bank即4组,其中,
118个GPIO = 32个MIO(Bank0) + 22个MIO(Bank1)+ 32个EMIO(Bank2)+ 32个EMIO(Bank3)
可见基本都是32个IO引脚一组,这是因为GPIO的控制寄存器是32位的,每一位对应一个IO引脚的话,一组寄存器就对应32个引脚,所以,4组基本相同的寄存器分别控制4组Bank,Bank1对应的也是一组32位寄存器,只是因为MIO引脚总共就54个,54-32=22,所以这组寄存器只控制22个引脚。
三. GPIO的功能与控制寄存器
GPIO的功能有三种:输入,输出,中断。使用MIO和EMIO在功能上几乎相同,唯一的区别是EMIO因为是PL侧引脚,可以与PL部分进行通讯,而MIO对PL侧是透明的。
特别的,MIO7,MIO8只能做输出,这在ZYNQ7000-MIO与EMIO详解中有说明。
GPIO的功能在芯片内部通过一组寄存器来控制,如下图所示。注意,一组寄存器同时控制一个GPIO Bank的所有引脚。
了解GPIO的控制器寄存器能帮助我们更深入的理解软件中的相关库函数,对编程有些帮助,当然,不了解也行,只要熟悉库函数即可。
输入/输出控制寄存器:
寄存器名称 | 说明 |
---|---|
DATA_RO | data read only(RO大概是这两个单词的缩写吧), GPIO引脚的值存储在此寄存器中,无论GPIO被配置为输入或输出,都可以通过读此寄存器得到GPIO引脚的值。 因为是只读寄存器(对软件来说),软件向此寄存器的写入操作将被忽略。 |
DATA | 输出数据寄存器,当GPIO被配置为输出才起作用,此寄存器中的值就是输出到引脚的值。 向此寄存器写入就是在设置GPIO的输出值, 读此寄存器将返回GPIO前一时刻的输出值,而不是现在的值。 |
MASK_DATA_LSW | Mask Data Least Significant Words,输出数据低16位掩码寄存器,此寄存器只有低16位有效, 对应位为1表示DATA寄存器低16位中对应位的值可以更改, 若不为1,则表示DATA寄存器低16位中对应位保持原值 |
MASK_DATA_MSW | Mask Data Most Significant Words,输出数据高16位掩码寄存器, 功能同MASK_DATA_LSW,只是它对应DATA寄存器高16位 |
DIRM | Direction Memory,方向寄存器,默认为0表示输入,设为1表示输出 注意,即使DIRM为1,软件也可以像输入一样去读此引脚的电平。 |
OEN | Output Enable,输出使能寄存器, 仅当DIRM为0时有效,为1表示输出使能, 为0表示输出不使能,此时对应引脚上的值为三态值 |
中断控制寄存器:
寄存器名称 | 说明 |
---|---|
INT_TYPE | Interrupt Type 中断类型寄存器, 控制GPIO中断是电平触发还是边缘触发 |
INT_POLARITY | Interrupt Polarity 中断极性寄存器 控制GPIO中断是低电平/下降沿有效,还是高电平/上升沿有效 |
INT_ANY | Interrupt Any,双边沿寄存器, 仅当INT_TYPE为边沿触发时,此寄存器才有效,控制是否双沿均可触发中断 |
INT_STAT | Interrupt State,中断状态寄存器, 此寄存器的值会被与之相连的INT State D触发器读取 D触发器存储中断状态,软件通过读此D触发器输出来判断中断是否发生, 清除此D触发器来清除中断状态 |
INT_MASK | Interrupt Mask,中断掩码寄存器, 显示当前哪些位被屏蔽,哪些位启用 |
INT_DIS | Interrupt Disable,中断失效寄存器, 向该寄存器的任何位写入 1 都会屏蔽该中断信号。 从该寄存器读取会返回不可预测的值 |
INT_EN | Interrupt Enable,中断使能寄存器 向该寄存器的任何位写入 1,可以启用/解除中断信号的掩码。 从该寄存器读取将返回一个不不可预测的值 |
四. GPIO中断设置与说明
GPIO中断号为52。此中断的优先级芯片内部已经固定好了,所以在软件中配置GPIO中断时,不需要指定GPIO中断的优先级。
GPIO所有引脚都是共享一个中断的,这意味着如果两个引脚的中断都使能了,如果不去读取具体引脚的电平,软件无法判断中断具体来自哪个引脚。
中断触发类型设置:
五. 在Vitis中配置GPIO
我自建了GPIO相关库函数,将相关GPIO功能写在一起。Xxk_PsGpio.c 与 Xxk_PsGpio.h。这里并没有使用处理整个Bank的函数,因为实际应用时很少需要处理整个Bank,都是单独处理某个Pin。
Xxk_PsGpio.h如下:
#ifndef XXK_PSGPIO_H
#define XXK_PSGPIO_H
// 包含xilinx库中头文件
#include "xil_printf.h"
#include "xgpiops.h"
#include "xscugic.h"
#include "xil_exception.h"
// 宏定义
#define __weak __attribute__((weak))
// 与PS GPIO相关的宏定义
// 引脚宏定义
#define MIO12 12U
#define EMIO0 54U
#define EMIO1 55U
#define EMIO2 56U
#define EMIO3 57U
#define EMIO4 58U
// GPIO指的就是PS侧的GPIO硬核,对于ZYNQ7,只有一个GPIO硬核
#define PSGPIO_INPUT 0U
#define PSGPIO_OUTPUT 1U
#define PSGPIO_OUTPUT_ENABLE 1U
#define PSGPIO_OUTPUT_DISABLE 0U
/* 中断类型,已在xgpiops.h中定义,放在这里方便找到
#define XGPIOPS_IRQ_TYPE_EDGE_RISING 0x00U
#define XGPIOPS_IRQ_TYPE_EDGE_FALLING 0x01U
#define XGPIOPS_IRQ_TYPE_EDGE_BOTH 0x02U
#define XGPIOPS_IRQ_TYPE_LEVEL_HIGH 0x03U
#define XGPIOPS_IRQ_TYPE_LEVEL_LOW 0x04U
*/
// PS GPIO相关函数
// 初始化psGpio
int psGpioInti(XGpioPs *psGpioPtr, u16 psGpio_deviceId);
//psGpioInti(&psGpio, XPAR_XGPIOPS_0_DEVICE_ID);
// 设置psGpio某引脚为输出并使能
void psGpio_SetPinOutputAndEnbale(const XGpioPs *psGpioPtr, u32 Pin);
//psGpio_SetPinOutputAndEnbale(&psGpio, EMIO0);
// 设置psGpio某引脚为输入,输入无需使能
void psGpio_SetPinInput(const XGpioPs *psGpioPtr, u32 Pin);
//psGpio_SetPinInput(&psGpio, EMIO0);
// 向psGpio某引脚写入0或1
extern void XGpioPs_WritePin(const XGpioPs *psGpioPtr, u32 Pin, u32 Data);
//XGpioPs_WritePin(&psGpio, EMIO0, 1);
// 读取psGpio某引脚的电平,得到0或1
extern u32 XGpioPs_ReadPin(const XGpioPs *psGpioPtr, u32 Pin);
// EMIO0_pinData = XGpioPs_ReadPin(&psGpio, EMIO0);
// 中断相关函数
int scuGic_Inti(XScuGic *scuGicPtr, u16 scuGicID); //初始化中断控制器
//scuGic_Inti(&scuGic, XPAR_XSCUTIMER_0_DEVICE_ID);
void psGpio_PinIntr_SetAndEnable(XScuGic *scuGicPtr, XGpioPs *psGpioPtr, u32 psGpio_intrId,
Xil_ExceptionHandler psGpio_Handler, u32 Pin, u8 IrqType);
//psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR,
// psGpio_Handler, EMIO0, XGPIOPS_IRQ_TYPE_EDGE_RISING);
void psGpio_Handler(void *CallBackRef); // 需在main中重写此弱函数
#endif
Xxk_PsGpio.c 如下:
#include "Xxk_PsGpio.h"
// 初始化PS侧GPIO,包括MIO和EMIO
int psGpioInti(XGpioPs *psGpioPtr, u16 psGpio_deviceId)
{
XGpioPs_Config *psGpio_configPtr;
psGpio_configPtr = XGpioPs_LookupConfig(psGpio_deviceId); // 根据器件ID查找配置
// 配置初始化配置
int status;
status = XGpioPs_CfgInitialize(psGpioPtr, psGpio_configPtr, psGpio_configPtr->BaseAddr);
if (status != XST_SUCCESS)
{
xil_printf("PsGpio %d Initialization Failed\r\n", psGpio_deviceId);
return status;
}
status = XGpioPs_SelfTest(psGpioPtr);
if (status != XST_SUCCESS)
{
xil_printf("PsGpio %d SelfTest Failed\r\n", psGpio_deviceId);
return status;
}
xil_printf("PsGpio %d Initialization Succeed\r\n", psGpio_deviceId);
return status;
}
// 设置psGpio某引脚为输入,输入无需使能
void psGpio_SetPinInput(const XGpioPs *psGpioPtr, u32 Pin)
{
XGpioPs_SetDirectionPin(psGpioPtr, Pin, PSGPIO_INPUT);
}
// 设置psGpio某引脚为输出并使能
void psGpio_SetPinOutputAndEnbale(const XGpioPs *psGpioPtr, u32 Pin)
{
XGpioPs_SetDirectionPin(psGpioPtr, Pin, PSGPIO_OUTPUT);
XGpioPs_SetOutputEnablePin(psGpioPtr, Pin, PSGPIO_OUTPUT_ENABLE);
}
// 设置psGpio某引脚输出不使能
void psGpio_SetPinOutputDisbale(const XGpioPs *psGpioPtr, u32 Pin)
{
XGpioPs_SetOutputEnablePin(psGpioPtr, Pin, PSGPIO_OUTPUT_DISABLE);
}
// PS GPIO PIN中断使能
void psGpio_PinIntr_SetAndEnable(XScuGic *scuGicPtr, XGpioPs *psGpioPtr, u32 psGpio_intrId,
Xil_ExceptionHandler psGpio_Handler, u32 Pin, u8 IrqType)
{
// 连接中断ID与固定服务函数
XScuGic_Connect(scuGicPtr, psGpio_intrId,
(Xil_ExceptionHandler)psGpio_Handler,
(void *)psGpioPtr);
// 启用对应中断ID的中断源
XScuGic_Enable(scuGicPtr, psGpio_intrId);
// 注意psGPIO中断不需要设置中断优先级
// 设置psGPIO中断类型
XGpioPs_SetIntrTypePin(psGpioPtr, Pin, IrqType);
// 在使能前先清除一次中断,否则之前的中断状态可能残留,导致烧写后马上进一次中断
XGpioPs_IntrClearPin(psGpioPtr, Pin);
// 使能psGPIO中断
XGpioPs_IntrEnablePin(psGpioPtr, Pin);
}
// psGpio中断处理弱函数
__weak void psGpio_Handler(void *CallBackRef)
{
XGpioPs *psGpioPtr = (XGpioPs *)CallBackRef;
// psGpio_intrFlag = 1;
if (XGpioPs_IntrGetStatusPin(psGpioPtr, MIO12) == TRUE)
{
XGpioPs_IntrDisablePin(psGpioPtr, MIO12);
XGpioPs_IntrClearPin(psGpioPtr, MIO12);
xil_printf("This is psGpio_Handler - MIO12\r\n");
}
else if (XGpioPs_IntrGetStatusPin(psGpioPtr, EMIO4) == TRUE)
{
XGpioPs_IntrDisablePin(psGpioPtr, EMIO4);
XGpioPs_IntrClearPin(psGpioPtr, EMIO4);
xil_printf("This is psGpio_Handler - EMIO4\r\n");
}
}
// 初始化中断控制器ScuGic
int scuGic_Inti(XScuGic *scuGicPtr, u16 scuGicID)
{
// 打开系统的中断处理功能
Xil_ExceptionInit(); // 初始化异常句柄,只在很早版本的bsp中需要,此处为了兼容性保留
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler)XScuGic_InterruptHandler,
scuGicPtr); // 为IRQ注册中断处理程序
Xil_ExceptionEnable(); // 使能系统中断功能
XScuGic_Config *scuGicConfig;
scuGicConfig = XScuGic_LookupConfig(scuGicID); // 根据器件ID查找配置
int status;
status = XScuGic_CfgInitialize(scuGicPtr, scuGicConfig, scuGicConfig->CpuBaseAddress);
if (status != XST_SUCCESS)
{
xil_printf("ScuGic Initialization Failed\r\n");
return XST_FAILURE;
}
xil_printf("ScuGic Initialization Succeed\r\n");
return status;
}
使用自建库,GPIO输入,输出及中断均有使用的main.c如下:
/*
功能:控制EMIO0~2为输出,EMIO4为输入,EMIO3为中断
*/
#include "Xxk_PsGpio.h"
#include "sleep.h"
// 全局变量
XGpioPs psGpio;
XScuGic scuGic;
int psGpio_EMIO_intrFlag = 0;
int psGpio_MIO_intrFlag = 0;
int main(int argc, char const *argv[])
{
xil_printf("begin\r\n");
// 初始化psGpio
psGpioInti(&psGpio, XPAR_XGPIOPS_0_DEVICE_ID);
// 设置psGpio引脚方向
psGpio_SetPinOutputAndEnbale(&psGpio, EMIO0);
psGpio_SetPinOutputAndEnbale(&psGpio, EMIO1);
psGpio_SetPinOutputAndEnbale(&psGpio, EMIO2);
psGpio_SetPinInput(&psGpio, EMIO3);
// 初始化中断控制器
scuGic_Inti(&scuGic, XPAR_XSCUTIMER_0_DEVICE_ID);
// 设置并使能psGpio某引脚中断
psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR,
psGpio_Handler, EMIO4, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR,
psGpio_Handler, MIO12, XGPIOPS_IRQ_TYPE_EDGE_FALLING);
while (1)
{
sleep(1);
xil_printf("EMIO3 value: %d\r\n", XGpioPs_ReadPin(&psGpio, EMIO3));
XGpioPs_WritePin(&psGpio, EMIO0, 1);
XGpioPs_WritePin(&psGpio, EMIO1, 1);
XGpioPs_WritePin(&psGpio, EMIO2, 0);
if (psGpio_EMIO_intrFlag)
{
xil_printf("This is psGpio_Handler - EMIO4\r\n");
psGpio_EMIO_intrFlag = 0;
XGpioPs_IntrEnablePin(&psGpio, EMIO4);
}
if (psGpio_MIO_intrFlag)
{
xil_printf("This is psGpio_Handler - MIO12\r\n");
psGpio_MIO_intrFlag = 0;
XGpioPs_IntrEnablePin(&psGpio, MIO12);
}
sleep(1);
XGpioPs_WritePin(&psGpio, EMIO0, 0);
XGpioPs_WritePin(&psGpio, EMIO2, 1);
}
return 0;
}
void psGpio_Handler(void *CallBackRef) // 重写弱函数
{
XGpioPs *psGpioPtr = (XGpioPs *)CallBackRef;
if (XGpioPs_IntrGetStatusPin(psGpioPtr, MIO12) == TRUE)
{
psGpio_MIO_intrFlag = 1;
XGpioPs_IntrDisablePin(psGpioPtr, MIO12);
XGpioPs_IntrClearPin(psGpioPtr, MIO12);
}
if (XGpioPs_IntrGetStatusPin(psGpioPtr, EMIO4) == TRUE)
{
psGpio_EMIO_intrFlag = 1;
XGpioPs_IntrDisablePin(psGpioPtr, EMIO4);
XGpioPs_IntrClearPin(psGpioPtr, EMIO4);
}
}
六. GPIO的另一种实现方式 - AXI GPIO
本文介绍了如何使用MIO和EMIO实现GPIO,而对于ZYNQ来说,在PL中使用AXI GPIO IP核也可以实现GPIO功能,具体介绍参见我的另一篇博文。
七. 总结
本文介绍了ZYNQ7000中GPIO的基本概念,GPIO是ZYNQ PS侧最简单的一种外设,它可以由MIO或EMIO实现。以32为界,118个GPIO被分为了4个Bank,每个Bank对应一组控制寄存器,然后简单介绍了每个寄存器的名称含义和功能,最后附上了通过自建GPIO函数库,便捷操作GPIO的软件代码。
对GPIO理解还不深,如有疏漏,欢迎评论指出!
徐晓康的博客持续分享高质量硬件、FPGA与嵌入式知识,软件,工具等内容,欢迎大家关注。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)