学习内容
使用SDK和提供的API进行初始化GPIO,并驱动led和btn进行操作,实现led呼吸灯效果,串口读取btn的值。
开发环境
vivado 18.3 && SDK
开发板 pynq-z2
原理讲解
- gpio可以看做一个外设,用于对器件的引脚作观测(input)以及控制(output)
- MIO 是将PS的外设和静态存储器接口的访问多路复用到PS的引脚上。
- 当我们的PS端的MIO不够用时候,我们可以在搭建硬件平台时候进行设计,将我们的PL端口的引脚定义拓展为EMIO然后被PS正常驱动。
- gpio 被分为了4个bank bank0和bank1通过MIO连接到PS的引脚,bank2和bank3通过EMIO连接到PL
这里给出GPIO的一个寄存器的结构图,方便我们从底层更加深入了解gpio的具体配置的一个状态
图片的上半部分表示的是和IO的中断功能有关的寄存器,本节主要关注下面的部分。
通过阅读UG585,我们可以知道寄存器组的不同功能: - DATA_RO:用来反映引脚的功能状态
- DATA:当GPIO信号被配置为输出时,这个寄存器控制要输出的值。该寄存器的所有32位同时写入。
- MASK_DATA_LSW: 用于屏蔽DATA的低16位
- MASK_DATA_MSW: 用于屏蔽DATA的高16位
- DIRM:用于控制IO引脚作为输入还是输出。0关闭输出驱动,1打开输出驱动
- OEN:当IO配置为输出时,用于控制打开或者关闭输出使能。0关闭使能,1进行使能
硬件平台搭建
同样和前面相似,我们先添加IP ZYNQ7,然后进行IP配置,选中uart sd gpio
EMIO的 gpio
APB AP transaction error, DAP status f0000021 问题解决方法
我们可以简单的理解为zynq这个芯片是由这样几部分组成,ddr区,PS区,PL区。DDR区是存储区,包括我们的芯片运行SDK的程序时候也是下载到这里。所以在我们没有对应我们的板子配置好DDR时,会碰到这个错误信息 “APB AP transaction error, DAP status f0000021” 这也是可以定位到我们在配置ddr信息时候没有和开发板匹配,这里我进行操作的时候,我是添加了对应的板卡信息,ddr自动连线的时候会有一个加载预配置。
pynq板卡信息会自动帮你配置ddr的信息,这里我贴出pynqz2的ddr的具体信息,需要更改的地方我会标记指出:
EMIO和MIO的区别
前面提到了zynq7000的大致划区,在ps区,也就是我们的arm核这里,一般都是引出了54个管脚进行开发,也就是我们在ZYNQ7的ip里在MIO config的可以配置的那些,这就是MIO我们可以针对这54个管脚进行配置自己想要的功能,一般不同的板卡会对应设计SD UART USB GPIO接口什么的,方便用户进行PS端的单独调试学习,那么,对于FPGA的PL端的引脚我们可以不可以进行PS操作调用呢?答案是肯定的!这就提到了EMIO的概念 EMIO的E就是 external的意思,也就是我们可以管脚定义了FPGA端的管脚进行扩展给PS端进行操作。
配置完成后如图:
然后我们可以输出生成顶层文件了
因为这里是用的EMIO,我们不能忘记新建约束文件进行管脚分配:
set_property -dict { PACKAGE_PIN R14 IOSTANDARD LVCMOS33 } [get_ports { GPIO0_tri_io[0] }]; #IO_L6N_T0_VREF_34 Sch=led[0]
set_property -dict { PACKAGE_PIN P14 IOSTANDARD LVCMOS33 } [get_ports { GPIO0_tri_io[1] }]; #IO_L4P_T0_35 Sch=btn[0]
set_property -dict { PACKAGE_PIN D20 IOSTANDARD LVCMOS33 } [get_ports { GPIO0_tri_io[2] }]; #IO_L4N_T0_35 Sch=btn[1]
最后综合生成bit流,导出硬件,launch SDK
SDK软件部分
运行SDK新建helloworld工程(不再多说,看前文),这里贴出SDK的main代码:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpiops.h"
#include "xparameters.h"
#define GPIO_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define LED0 54
#define BTN0 55
#define BTN1 56
static XGpioPs GpioPs;
static XGpioPs_Config *XGpioPs_Cfg;
int initGpio();
int main()
{
u32 led = 0;
int i,j;
init_platform();
initGpio();
while(1){
for(i=0;i<1000;i++){
for(j=0;j<1000;j++){
usleep(1);
if(i<j){
XGpioPs_WritePin(&GpioPs,LED0,led);
}
else{
XGpioPs_WritePin(&GpioPs,LED0,~led);
}
}
}
led=~led;
printf("btn0 :%d btn1 :%d \n",XGpioPs_ReadPin(&GpioPs,BTN0),XGpioPs_ReadPin(&GpioPs,BTN1));
}
print("Hello World\n\r");
cleanup_platform();
return 0;
}
int initGpio(){
int status;
XGpioPs_Cfg = XGpioPs_LookupConfig(GPIO_ID);
status = XGpioPs_CfgInitialize(&GpioPs,XGpioPs_Cfg,XGpioPs_Cfg->BaseAddr);
if( status != XST_SUCCESS){
return XST_FAILURE;
}
XGpioPs_SetDirectionPin(&GpioPs,LED0,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED0,0x01);
XGpioPs_SetDirectionPin(&GpioPs,BTN0,0x00);
XGpioPs_SetOutputEnablePin(&GpioPs,BTN0,0x00);
XGpioPs_SetDirectionPin(&GpioPs,BTN1,0x00);
XGpioPs_SetOutputEnablePin(&GpioPs,BTN1,0x00);
}
部分代码解释
#include "xgpiops.h"
#include "xparameters.h"
#define GPIO_ID XPAR_PS7_GPIO_0_DEVICE_ID
#define LED0 54
#define BTN0 55
#define BTN1 56
这里的XPAR_PS7_GPIO_0_DEVICE_ID可以在 “xparameters.h” 寻到,对于LED0 BTN0 BTN1的定义,正是因为前文提到的EMIO的概念,直接PS课操作的管脚54个,是从MIO 0 – MIO 53
所以这里的EMIO的扩展引脚编号就是54往后,从GPIO0_tri_io 0 依次往后进行编号,也可以理解为管脚号。
int initGpio(){
int status;
XGpioPs_Cfg = XGpioPs_LookupConfig(GPIO_ID);
status = XGpioPs_CfgInitialize(&GpioPs,XGpioPs_Cfg,XGpioPs_Cfg->BaseAddr);
if( status != XST_SUCCESS){
return XST_FAILURE;
}
XGpioPs_SetDirectionPin(&GpioPs,LED0,0x01);
XGpioPs_SetOutputEnablePin(&GpioPs,LED0,0x01);
XGpioPs_SetDirectionPin(&GpioPs,BTN0,0x00);
XGpioPs_SetOutputEnablePin(&GpioPs,BTN0,0x00);
XGpioPs_SetDirectionPin(&GpioPs,BTN1,0x00);
XGpioPs_SetOutputEnablePin(&GpioPs,BTN1,0x00);
}
初始化函数同前文iic的配置,和keil开发32的流程类似,声明结构体进行初始化,然后设置GPIO的方向,输入还是输出模式。