文章目录
- 通过GPIO控制LED状态
- 通过按键控制LED状态
- 通过EMIO复用PL端按键控制PS端LED状态
- 通过EMIO复用多个PL端的GPIO
GPIO(General Purpose Input Output)是一个外设,用来对器件的引脚做观测(输入)和控制(输出)。每个GPIO口都是独立且可以进行动态的编程的,作为输入、输出或者中断感知。软件通过一组存储映射的寄存器来对GPIO进行控制。
MIO(Multiuse Input Output)将来自PS外设和静态存储器接口的访问多路复用到PS的引脚上。
EMIO是扩展的MIO,是PS和PL之间的一个接口,当PS的引脚不够用的时候,可以通过EMIO来进行扩展,从而使用PL的引脚。
GPIO被分为4个Bank,Bank0和Bank1通过MIO将外设连到PS端,Bank2和Bank3通过EMIO将外设连到PL端。
本实验在黑金ZYNQ 7020开发板上进行,使用PS端的MIO控制LED,实现LED的闪烁效果。
通过GPIO控制LED状态
创建IP核完成后,双击配置该IP核,勾选GPIO MIO选项。
使用GPIO控制LED的步骤为:初始化GPIO的驱动;设置GPIO的方向为输出;设置输出使能;写数据到GPIO输出引脚。
GPIO使用的示例可以通过bsp文件夹下的.mss文件中的网页扩展链接跳转得到。
这里包括了两种GPIO使用的代码示例,一种是中断类型,一种是直接使用。
将代码示例导入到工程中。
在该文件中可以参考相关的写法,碰到函数可以按住Ctrl键并点击跳转到其定义的地方。
原理图中有两个PS端的LED,对应着黑金开发板上的两个LED,分别接在MIO0_LED和MIO13_LED上,想要点亮LED就需要给相应的引脚置低电平。
找到这两个LED在原理图中的连接位置,如下图所示,这样就可以将其在代码中写入了。
本实验参考示例代码xgpiops_polled_example.c编写的C代码如下。
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs_Config *ConfigPtr; //结构体类型,成员有设备ID和寄存器地址
XGpioPs Gpio; //结构体类型,GPIO操作实例
static u32 Output_Pin0 = 0; //PS端LED连接的输出引脚是0号或13号
static u32 Output_Pin13 = 13; //PS端LED连接的输出引脚是0号或13号int main()
{printf("gpio led test!\n");//查找器件的配置信息ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);//初始化GPIO的驱动XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);//设置GPIO的方向为输出XGpioPs_SetDirectionPin(&Gpio, Output_Pin0, 1); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, Output_Pin13, 1);//设置输出使能XGpioPs_SetOutputEnablePin(&Gpio, Output_Pin0, 1); //0-disable,1-enableXGpioPs_SetOutputEnablePin(&Gpio, Output_Pin13, 1);//写数据到GPIO输出引脚while(1){XGpioPs_WritePin(&Gpio, Output_Pin0, 0x0); //点亮LED1printf("ps led1 on!\n");XGpioPs_WritePin(&Gpio, Output_Pin13, 0x1); //熄灭LED2printf("ps led2 off!\n");sleep(1);XGpioPs_WritePin(&Gpio, Output_Pin0, 0x1); //熄灭LED1printf("ps led1 off!\n");XGpioPs_WritePin(&Gpio, Output_Pin13, 0x0); //点亮LED2printf("ps led2 on!\n");sleep(1);}return 0;
}
该程序实现的是点亮黑金开发板上PS端的两个LED,让其交叉亮灭,间隔时间是1秒,效果如下动图所示。
因为在程序中还设置了打印输出,因此SDK终端也会有相应的输出,动图如下。
通过按键控制LED状态
本实验中需要使用开发板上的两个按键分别控制两个LED的状态。
将上面点亮LED实验的Vivado工程另存,然后在另存工程的基础上进行修改。本实验相较于上面只点亮LED的实验来说,需要从按键对应的GPIO口读入数据,然后将其写入到LED对应的GPIO中。
黑金开发板中PS端的按键在原理图中的连接如下图所示,按键按下后,端口输出是低电平,没按下时是高电平。
找到这两个按键在原理图中的连接位置,如下图所示。
用PS端按键控制PS LED的C代码如下。
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs_Config *ConfigPtr; //结构体类型,成员有设备ID和寄存器地址
XGpioPs Gpio; //结构体类型,GPIO操作实例
static u32 Output_Pin0 = 0; //PS端LED1连接的输出引脚是0号
static u32 Output_Pin13 = 13; //PS端LED2连接的输出引脚是13号
static u32 Input_Pin50 = 50; //PS端KEY1连接的输入引脚是50号
static u32 Input_Pin51 = 51; //PS端KEY2连接的输入引脚是51号
static u32 key_value1,key_value2;int main()
{printf("gpio led test!\n");//查找器件的配置信息ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);//初始化GPIO的驱动XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);//设置LED的GPIO方向为输出XGpioPs_SetDirectionPin(&Gpio, Output_Pin0, 1); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, Output_Pin13, 1);//设置KEY的GPIO方向为输入XGpioPs_SetDirectionPin(&Gpio, Input_Pin50, 0); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, Input_Pin51, 0);//设置输出使能XGpioPs_SetOutputEnablePin(&Gpio, Output_Pin0, 1); //0-disable,1-enableXGpioPs_SetOutputEnablePin(&Gpio, Output_Pin13, 1);while(1){//从KEY读数据key_value1 = XGpioPs_ReadPin(&Gpio, Input_Pin50);key_value2 = XGpioPs_ReadPin(&Gpio, Input_Pin51);//写数据到GPIO输出引脚XGpioPs_WritePin(&Gpio, Output_Pin0, key_value1); //从KEY1读入的值写入LED1XGpioPs_WritePin(&Gpio, Output_Pin13, key_value2); //从KEY2读入的值写入LED2}return 0;
}
板上验证结果如下动图所示。
在PS KEY1按下时,PS LED1点亮,在PS KEY2按下时,PS LED2点亮,松开时熄灭。
通过EMIO复用PL端按键控制PS端LED状态
PS端还可以通过EMIO复用PL端的接口。
在前面工程的基础上,配置IP核的时候勾选GPIO EMIO选项。
需要使用几个EMIO就填几个,这里只需要复用一个PL端的按键,因此选择1个。
这里的最大宽度是64,因为PL端有两个Bank,每个Bank是32位,如下图所示。
配置成功之后,IP核图示中就多了一个GPIO_0口,在其上右键选择Make External后就会为其添加引脚,可以进行自己命名引脚名称,我这里命名为了GPIO_KEY,将GPIO_0展开后可以看到其有输入、输出和使能三项,如下图所示。
重新生成后,打开.v文件如下,里面就多了一行我们添加的GPIO_KEY,因为现在要复用PL端的按键,因此要生成Bitstream文件,点击生成比特流。
生成比特流文件的时候报错了,这是因为使用PL端接口时要先进行管脚分配。
点击Open Elaborated Design,然后点击菜单栏Layout,选择I/O Planning调出管脚分配窗口。
这是PL端的4个按键,这里选择KEY1进行复用。
在原理图中找到其连接的位置,管脚为N15。
BANK35连接的电源是VCCIO_35,如下图所示。
在原理图中找到VCCIO_35,其输出是3.3V的电压。
然后在管脚分配这里进行设置,管脚设置为N15,电压设置为3.3V,如下图所示。
点击保存,弹出下面的窗口,自己输入一个文件名称即可。
这样就在源目录下生成了对应的约束文件,里面的内容就是刚才设置的。
再次点击生成比特流,生成结束后打开设计。
也可以点击Report Utilization查看使用率,如下图所示。
这个时候再导出硬件的时候,就要将比特流文件勾选,因为使用到了PL端的引脚。
PL端的Bank编号是从54开始的,本实验中也只使用了一个,因此其编号就是54。
本实验中的代码相比于PS端按键控制PS LED只改动了一点,将其中一个PL按键的编号改为了54,具体代码如下。
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs_Config *ConfigPtr; //结构体类型,成员有设备ID和寄存器地址
XGpioPs Gpio; //结构体类型,GPIO操作实例
static u32 Output_Pin0 = 0; //PS端LED1连接的输出引脚是0号
static u32 Output_Pin13 = 13; //PS端LED2连接的输出引脚是13号
static u32 Input_Pin50 = 50; //PS端KEY1连接的输入引脚是50号
static u32 Input_Pin54 = 54; //PL端KEY1设置为54
static u32 key_value1,key_value2;int main()
{printf("gpio key led test!\n");//查找器件的配置信息ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);//初始化GPIO的驱动XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);//设置LED的GPIO方向为输出XGpioPs_SetDirectionPin(&Gpio, Output_Pin0, 1); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, Output_Pin13, 1);//设置KEY的GPIO方向为输入XGpioPs_SetDirectionPin(&Gpio, Input_Pin50, 0); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, Input_Pin54, 0);//设置输出使能XGpioPs_SetOutputEnablePin(&Gpio, Output_Pin0, 1); //0-disable,1-enableXGpioPs_SetOutputEnablePin(&Gpio, Output_Pin13, 1);while(1){//从KEY读数据key_value1 = XGpioPs_ReadPin(&Gpio, Input_Pin50);key_value2 = XGpioPs_ReadPin(&Gpio, Input_Pin54);//写数据到GPIO输出引脚XGpioPs_WritePin(&Gpio, Output_Pin0, key_value1); //从KEY1读入的值写入LED1XGpioPs_WritePin(&Gpio, Output_Pin13, key_value2); //从KEY2读入的值写入LED2}return 0;
}
代码编写成功后,还不能像之前那样直接下载到开发板,因为用到了PL端的资源,因此需要点击菜单栏Xilinx—>Program FPGA进行FPGA的编程,如下图所示。
编程完毕之后,就可以在开发板上下载程序运行了,实验结果如下图所示,成功用PL端的KEY1控制了PS端的LED2。
通过EMIO复用多个PL端的GPIO
本实验将通过EMIO复用8个PL端的GPIO,包括4个按键和4个LED,具体的实验结果是,PS端的两个按键分别控制PS端的两个LED,PL端的四个按键分别控制PL端的四个LED。
首先在IP核配置的时候,选择EMIO GPIO的宽度为8。
更新之后,IP核图示这里的GPIO显示也是8位的。
在原理图中找到PL端LED的引脚,再到原理图中找到其连接的位置,前面的BANK35中就有。
将本实验用到的8个引脚分配汇总如下表所示。
名称 | LED1 | LED2 | LED3 | LED4 | KEY1 | KEY2 | KEY3 | KEY4 |
---|---|---|---|---|---|---|---|---|
引脚 | M14 | M15 | K16 | J16 | N15 | N16 | T17 | R17 |
电压都是3.3V,在Vivado中打开引脚配置,按照上表进行管脚分配,分配完成后如下图所示。
配置引脚完成后进行保存,生成比特流文件,导出到硬件,启动SDK,然后在里面修改C代码如下。
#include "stdio.h"
#include "xparameters.h"
#include "xgpiops.h"
#include "sleep.h"#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID
XGpioPs_Config *ConfigPtr; //结构体类型,成员有设备ID和寄存器地址
XGpioPs Gpio; //结构体类型,GPIO操作实例
static u32 PS_LED1 = 0; //PS端LED1连接的输出引脚是0号
static u32 PS_LED2 = 13; //PS端LED2连接的输出引脚是13号
static u32 PS_KEY1 = 50; //PS端KEY1连接的输入引脚是50号
static u32 PS_KEY2 = 51; //PS端KEY2连接的输入引脚是51号
static u32 ps_key_value1,ps_key_value2;static u32 PL_KEY1 = 54; //PL端KEY的引脚,根据引脚[0:7]的顺序
static u32 PL_KEY2 = 55;
static u32 PL_KEY3 = 56;
static u32 PL_KEY4 = 57;
static u32 PL_LED1 = 58;
static u32 PL_LED2 = 59;
static u32 PL_LED3 = 60;
static u32 PL_LED4 = 61;
static u32 pl_key_value1,pl_key_value2,pl_key_value3,pl_key_value4;int main()
{printf("gpio emio test!\n");//查找器件的配置信息ConfigPtr = XGpioPs_LookupConfig(GPIO_DEVICE_ID);//初始化GPIO的驱动XGpioPs_CfgInitialize(&Gpio, ConfigPtr, ConfigPtr->BaseAddr);//设置PS LED的GPIO方向为输出XGpioPs_SetDirectionPin(&Gpio, PS_LED1, 1); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, PS_LED2, 1);//设置PS KEY的GPIO方向为输入XGpioPs_SetDirectionPin(&Gpio, PS_KEY1, 0); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, PS_KEY2, 0);//设置PS输出使能XGpioPs_SetOutputEnablePin(&Gpio, PS_LED1, 1); //0-disable,1-enableXGpioPs_SetOutputEnablePin(&Gpio, PS_LED2, 1);//设置PL LED的GPIO方向为输出XGpioPs_SetDirectionPin(&Gpio, PL_LED1, 1); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, PL_LED2, 1);XGpioPs_SetDirectionPin(&Gpio, PL_LED3, 1);XGpioPs_SetDirectionPin(&Gpio, PL_LED4, 1);//设置PL KEY的GPIO方向为输入XGpioPs_SetDirectionPin(&Gpio, PL_KEY1, 0); //0-input,1-outputXGpioPs_SetDirectionPin(&Gpio, PL_KEY2, 0);XGpioPs_SetDirectionPin(&Gpio, PL_KEY3, 0);XGpioPs_SetDirectionPin(&Gpio, PL_KEY4, 0);//设置PL输出使能XGpioPs_SetOutputEnablePin(&Gpio, PL_LED1, 1); //0-disable,1-enableXGpioPs_SetOutputEnablePin(&Gpio, PL_LED2, 1);XGpioPs_SetOutputEnablePin(&Gpio, PL_LED3, 1);XGpioPs_SetOutputEnablePin(&Gpio, PL_LED4, 1);while(1){//从PS KEY读数据ps_key_value1 = XGpioPs_ReadPin(&Gpio, PS_KEY1);ps_key_value2 = XGpioPs_ReadPin(&Gpio, PS_KEY2);//写数据到PS LEDXGpioPs_WritePin(&Gpio, PS_LED1, ps_key_value1); //从PS KEY1读入的值写入PS LED1XGpioPs_WritePin(&Gpio, PS_LED2, ps_key_value2); //从PS KEY2读入的值写入PS LED2//从PL KEY读数据pl_key_value1 = XGpioPs_ReadPin(&Gpio, PL_KEY1);pl_key_value2 = XGpioPs_ReadPin(&Gpio, PL_KEY2);pl_key_value3 = XGpioPs_ReadPin(&Gpio, PL_KEY3);pl_key_value4 = XGpioPs_ReadPin(&Gpio, PL_KEY4);//写数据到PL LEDXGpioPs_WritePin(&Gpio, PL_LED1, pl_key_value1); //从PL KEY1读入的值写入PL LED1XGpioPs_WritePin(&Gpio, PL_LED2, pl_key_value2); //从PL KEY2读入的值写入PL LED2XGpioPs_WritePin(&Gpio, PL_LED3, pl_key_value3); //从PL KEY3读入的值写入PL LED3XGpioPs_WritePin(&Gpio, PL_LED4, pl_key_value4); //从PL KEY4读入的值写入PL LED4}return 0;
}
点击菜单栏Xilinx—>Program FPGA进行FPGA的编程,随后在开发板上运行程序,实验结果如下动图所示。
参考视频:
正点原子手把手教你学ZYNQ之嵌入式开发