<p>在 <a href="http://xilinx.eetrend.com/blog/2019/100017453.html">UltraZed-EG PCIe Carrier Card 开发纪录: Hello Cortex-A53</a> 中我们题到了如何透过 Vivado 去建立我们的项目,让 <a href="http://ultrazed.org/product/ultrazed-eg-pcie-carrier-card" target="_blank">UltraZed-EG PCIe Carrier Card</a> 上的处理器系统 (Processing Syste, PS) 里面的 Cortex-A53 可以透过 AXI_GPIO 去对可程序逻辑区 (Programmable Logic, PL) 端的 LEDs D12 ~ D19 进行输出的控制。 </p>
在这篇文章,我们要讲的则是如何透过 AXI_GPIO 来处理 输入 的控制,并让 Cortex-R5 根据不同的输入,在 ps_uart1 输出不同的讯息,以及控制不同的 LED 亮暗。
(本文以 Vivado 2018.2 进行开发)
<strong>开发目标</strong>
和 UltraZed-EG PCIe Carrier Card 开发纪录: Hello Cortex-A53 一文很像,只是这次我们把目标转向 Cortex-R5 ,以及将 GPIO 输出的功能,改成 GPIO 输入。
这次我们将透过 ps_uart1 输出 Cortex-R5 上的讯息,并透过 AXI_GPIO 搭配 interrupt 的使用,去侦测使用者按下可程序逻辑(Programmable Logic, PL) 端的 SW2 ~ SW4 这三个无段按钮。
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
<strong>建立项目</strong>
<p>首先让我们打开 Vivado 吧~ 不过在进行这一步之前,请先确定你有依照 <a href="https://coldnew.github.io/2c97574c/">让 Vivado 有 UltraZed-EG PCIe Carrier Card 的配置文件</a> 一文的说明,让我们在建立项目的时候可以找到 <a href="http://ultrazed.org/product/ultrazed-eg-pcie-carrier-card" target="_blank">UltraZed-EG PCIe Carrier Card</a> 这块板子。 </p>
启动了 Vivado 后,点选 Create New Project
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接下来指定好项目路径和名称
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
选择 RTL Project ,并将 Do not specify sources at this time 打勾,我们暂时不会汇入已经有的 verilog 程序代码
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
点选 Boards ,选择 UltraZed-EG PCIe Carrier Card
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
完成项目的建立
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
<strong>建立 Block Design</strong>
和之前的文章一样,我们的项目需要用到 Xilinx 一些预先定义好的 IP, 因此使用 Block Design 来建立我们的设计。
首先点选 IP Integrator -> Create Block Design
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接着点选 OK 建立我们的 Block Design
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
点选 Add IP 按钮去增加我们需要的 IP 核
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
我们首先寻找 Zynq UltraScale+ MPSoC 并将它加入到我们的 Block Design,并点选 Run BLock Automation 对该 IP 做一些设定
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
由于预设的 Zynq UltraScale+ MPSoC 并不会打开可程序逻辑 (Programmable Logic, PL) 对应到处理器系统 (Processing System, PS) 的中断控制 (PL-PS interrupt),因此我们要自己打开。
点击 Zynq UltraScale+ MPSoC 两下来对其进行设定,你会看到这样的页面,选择 PS-PL Configuration
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接下来,点选 General -> Interrupts -> PL to PS -> IRQ0[0-7] 将其变成 1 ,完成后点选 OK
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
你会看到我们的 Zynq UltraScale+ MPSoC 增加了 pl_ps_irq0[0:0] 这个输入界面,如果有需要的话则再 Run Block Automation 一次。
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接下来,将 Board 里面的 Push buttons 拉到我们的 Diagram 去
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
目前电路变成这样
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接下来,再把 Board 上的 LED 拉到 axi_gpio_0 上面,让整个电路变成这样
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
对 axi_gpio_0 点击两下,进入到以下设定页面
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
在这边,我们将 Enable Interrupt 打开,点选 OK 完成设定
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
我们拉条线将 ip2intc_irpt 接到 pl_ps_irq0[0:0] 上,让 interrupt 可以运作
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
完成后,点选 Run Connection Automation 进行线路连接,现在电路会变成这样
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
(注意到 ip2intc_irpt 一定要连接到 pl_ps_irq0[0:0] 上呦,也就是橘色线的这一条)
完成后可以点选 Validate Design 按钮来确认设计没问题
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
好了,让我们来产生 HDL Wrapper 吧 ~
<strong>产生 HDL Wrapper</strong>
接下来我们要将刚刚用 Block Design 建立的电路变成 verilog 程序代码,因此会需要进行产生 HDL Wrapper 这个步骤。
对你的 Block Design 档案点选右键,选择 Create HDL Wrapper ,它会根据你项目设定的语言 (VHDL 或是 Verilog) 来产生相对的 HDL 程序代码。
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
由于这次我们不需要对产出来的东西进行修改,因此选 Let Vivado manage wrapper and auto-update 即可
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
好了后,假设你的 Block Design 档案叫做 design_1.bd ,那就会产生 design_1_wrapper.v 或是 design_1_wrapper.vhdl 这样的档案。
<strong>产生比特流 (bitstream)</strong>
前面的处理都好了后,接下来点选 Program and Debug -> Generate Bitstream 去让 Viavado 将这个项目产生出 比特流 (bitstream) ,Zynq UltraScale+ 会在开机的时候根据 bitstream 的信息对 FPGA 进行设定。
这个产生的过程视你的计算机强度如何而决定花多少时间,总之先来泡杯茶吧~
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
当 bitstream 完成后,我们准备执行 Xilinx SDK 来透过写 C 语言项目来让 Cortex-R 可以透过 AXI_GPIO 侦测 SW2 ~ SW4 的中断(interrupt) ,并根据不同按钮的触发来对 LED 进行控制。
点选 File -> Export -> Export Hardware 将刚刚产生的硬件信息输出给 Xilinx SDK 去。
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
确定你有勾选 Include bitstream 后,点选 OK
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
完成后,执行 Xilinx SDK
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
<strong>建立 Xilinx SDK 项目</strong>
启动 Xilinx SDK 后,点选 File -> New -> Application Project 去建立新的项目
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
这边我命名此一项目为 helloR5 ,并指定为 standalone 的程序,注意到 Processor 要选择 psu_cortex45_0
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
接下来,我们一样选择 Hello World 来作为我们的项目样板,点选 Finish 完成项目建立。
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
<strong>打开 helloworld.c</strong>
点选左边字段的 helloR5 -> src -> helloworld.c 来编辑我们的主程序,你会看到以下的内容
* helloworld.c: simple test application
*
* This application configures UART 16550 to baud rate 9600.
* PS7 UART (Zynq) is not initialized by this application, since
* bootrom/bsp configures it to baud rate 115200
*
* ------------------------------------------------
* | UART TYPE BAUD RATE |
* ------------------------------------------------
* uartns550 9600
* uartlite Configurable only in HW design
* ps7_uart 115200 (configured by bootrom/bsp)
*/
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
int main()
{
init_platform();
print("Hello World\n\r");
cleanup_platform();
return 0;
}
这个程序默认会直接透过 Xilinx 定义好的 print() 函式透过当前开发板的 ps7_uart 进行输出,以这块板子而言,就是透过 ps_uart0 也就是 Linux 端的 /dev/ttyUSB1 会得到讯息,让我们修改一下预设的输出吧。
<strong>设定输出的 UART</strong>
在本文一开始,我们题到了我们这次希望透过 ps_uart1 输出,也就是希望 Linux 端的 /dev/ttyUSB0 可以收到讯息,那这样要怎样做呢?
首先点选 Xilinx -> Board Support Packages Settings
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
选择 helloR5_bsp
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
点选 Overview -> standalone 设定 stdin 和 stdout 成 ps_uart1 ,变成如下图这样
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
点选 OK ,完成设定,这样这个项目透过 print() 或是 xil_printf() 输出的讯息就都是从 ps_uart1 也就是 Linux 端的 /dev/ttyUSB0 进行输出啰~
<strong>透过 SW 控制 LED (轮询)</strong>
由于如果连如何抓 SW2 ~ SW4 的输入都不会的话,中断控制大概也不用提了 (笑)。 因此让我们先用最传统的方式,透过轮询 (polling) 的方式取得当前 SW2 ~ SW4 的状态,并分别控制 LED D12 ~ D14
<strong>简单的 SW2 ~ SW4 信息取得</strong>
让我们编辑 helloworld.c 将其变成以下
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "sleep.h"
XGpio sw;
int main()
{
init_platform();
// Initialize SW2 ~ SW4
int ret = XGpio_Initialize(&sw, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Setup gpio direction to IN, SW is at axi_gpio0 channel 1
XGpio_SetDataDirection(&sw, 1, 0xFF);
print("Hello Cortex-R5\n\r");
// Polling the SW2 ~ SW4 input result
while (1) {
int val = XGpio_DiscreteRead(&sw, 1);
switch (val) {
case 0x4:
xil_printf("SW 4 pressed!\n\r");
break;
case 0x1:
xil_printf("SW 3 pressed!\n\r");
break;
case 0x2:
xil_printf("SW 2 pressed!\n\r");
break;
}
// delay a bit here for 10ms
usleep(100 * 1000);
}
cleanup_platform();
return 0;
}
这个程序,基本上和 UltraZed-EG PCIe Carrier Card 开发纪录: Hello Cortex-A53 时对 LED 进行输出控制差不多,只是将原本的输出变成了输入。
特别要注意的事情是,由于轮询 (polling) 的速度很快,因此我们在循环里面加入了 usleep() 来做点延迟。
你可以依照 下载到开发板 (一次性) 上面的下载方式,并得到以下结果 (单击 SW2 ~ SW4 看看)
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
确认至少 SW2 ~ SW4 的设定没错后,再来看看如何透过中断 (Interrupt) 来取得这些按钮的状态并控制 LED 的亮暗吧 ~
<strong>加入 LED 的控制</strong>
在上面,我们做到了侦测 SW2 ~ SW4 不同按键按下的状态,这次就根据我们的结果来控制对应的 LED D12 ~ D14 吧,我们在 print("Hello Cortex-R5\n\r"); 后面加入我们对 LED 的初始化~
#define LED_CHANNEL 2
XGpio led;
int main()
{
// skip ....
print("Hello Cortex-R5\n\r");
// Initialize LEDs
ret = XGpio_Initialize(&led, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Set direction on GPIO_0 channel 2 to output (LED)
XGpio_SetDataDirection(&led, LED_CHANNEL, 0xff);
// polling the SW2 ~ SW4 input result
while (1) {
int val = XGpio_DiscreteRead(&sw, 0x1);
switch(val) {
case 0x4:
xil_printf("SW 4 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x4); // D14: ON
break;
case 0x1:
xil_printf("SW 3 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x2); // D13: ON
break;
case 0x2:
xil_printf("SW 2 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x1); // D12: ON
break;
}
// delay a bit here for 10ms
usleep(100 * 1000);
}
// skip ...
}
在这边,要特别提到我们定义的 LED_CHANNEL 这个宏,它到底是干啥么用的呢? 如果将 LED 相关控制的程序,和我们的电路对照在一起就明显啦 ~
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
是的,由于我们在建立 axi_gpio_0 的时候,将 LED 用的输出脚放入到了 GPIO_0 的 Channel 2 上,因此就是需要这样设定才能点亮它~
<strong>完整程序代码</strong>
到目前为止,透过轮询(polling)来取得 SW2 ~ SW4 并分别控制 D12 ~ D14 的 LED 完整程序代码如下:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "sleep.h"
#define LED_CHANNEL 2
XGpio sw;
XGpio led;
int main()
{
init_platform();
// Initialize SW2 ~ SW4
int ret = XGpio_Initialize(&sw, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Setup gpio direction to IN, SW is at axi_gpio0 channel 1
XGpio_SetDataDirection(&sw, 1, 0xff);
print("Hello Cortex-R5\n\r");
// Initialize LEDs
ret = XGpio_Initialize(&led, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Set direction on GPIO_0 channel 2 to output (LED)
XGpio_SetDataDirection(&led, LED_CHANNEL, 0xff);
// Polling the SW2 ~ SW4 input result
while (1) {
int val = XGpio_DiscreteRead(&sw, 0x1);
switch(val) {
case 0x4:
xil_printf("SW 4 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x4); // D14: ON
break;
case 0x1:
xil_printf("SW 3 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x2); // D13: ON
break;
case 0x2:
xil_printf("SW 2 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x1); // D12: ON
break;
}
// Delay a bit here for 10ms
usleep(100 * 1000);
}
cleanup_platform();
return 0;
}
我们将来看如何透过中断来达到一样的事情~
<strong>透过 SW 控制 LED (中断)</strong>
了解了如何透过轮询(polling) 的方式来使用 GPIO 相关的函式库后,这次来将刚刚的程序改写成中断 (interrupt) 的版本吧 !
<strong>清理目前的程序</strong>
我们先将刚刚的轮询的程序清理成这样,好方便后面程序的撰写
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "sleep.h"
#define LED_CHANNEL 2
XGpio sw;
XGpio led;
int main()
{
init_platform();
// Initialize SW2 ~ SW4
int ret = XGpio_Initialize(&sw, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Setup gpio direction to IN, SW is at axi_gpio0 channel 1
XGpio_SetDataDirection(&sw, 1, 0xff);
print("Hello Cortex-R5\n\r");
// Initialize LEDs
ret = XGpio_Initialize(&led, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Set direction on GPIO_0 channel 2 to output (LED)
XGpio_SetDataDirection(&led, LED_CHANNEL, 0xff);
/// <--- NOTE: Other Codes will be inserted here !!
// wait here
while (1) ;
cleanup_platform();
return 0;
}
<strong>加入中断控制</strong>
我们先在 while (1); 前面加入我们对中断控制器初始化用的函式 My_InterruptInitializer()
skip ...
int main()
{
/// <--- NOTE: Other Codes will be inserted here !!
ret = My_InterruptInitialize(XPAR_SCUGIC_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// wait here
while (1) ;
cleanup_platform();
return 0;
}
并在程序最前面,加入中断相关的头文件 xscugic.h ,还有一些方便我们撰写程序用的宏
#include "xscugic.h"
#define SW_INT XGPIO_IR_CH1_MASK
#define GPIO_0_INTERRUPT_ID XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR
XScuGic gic;
在这边, SW_INT 主要是帮助我们知道 axi_gpio_0 的通道 (channel) 屏蔽 (mask) ,好让我们知道当前是 axi_gpio_0 的哪个通道 (channel) 发出了中断 (interrupt)
而 GPIO_0_INTERRUPT_ID 则是对应到 Xilinx SDK 自动帮我们定义好的中断编号,你可以到 helloR5_bsp 里面去找对应的数值。
我们先定义一旦进入到中断时,相对应处理的函式 SW_Irq_Handler() ,在这边,我们做的事情和轮询(polling) 的版本很像,都是抓到 SW 的输入后,让相对应的 LED 进行输出。
不同的地方是,这个函式会在中断被触发的时候执行。
当进入到中断的时候,我们要先关掉该设备的中断,避免受到干扰,而当中断结束后,则是要回复这些设定。
有一点要注意的事情是,在中断处理的函式中,要 尽可能的快速处理 这样才可以避免影响到整体系统的其他程序。
void SW_Irq_Handler(void *gpio)
{
// Disable GPIO Interrupts
XGpio_InterruptDisable(gpio, SW_INT);
// Ignore addition button press
if ((XGpio_InterruptGetStatus(gpio) & SW_INT) != SW_INT)
return;
int val = XGpio_DiscreteRead(gpio, 1);
switch(val) {
case 0x4:
xil_printf("SW 4 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x4); // D14: ON
break;
case 0x1:
xil_printf("SW 3 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x2); // D13: ON
break;
case 0x2:
xil_printf("SW 2 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x1); // D12: ON
break;
}
// Clear the interrupt bit
XGpio_InterruptClear(gpio, SW_INT);
// Enabl GPIO interrupts
XGpio_InterruptEnable(gpio, SW_INT);
}
完成中断的处理函式后,我们需要将它喂给中断控制器,让它知道哪些设备要处理中断,因此让我们来弄我们的 My_InterruptInitialize() 吧
这边的初始化很单存,首先先初始化中断控制器,并将刚刚的 SW_Irq_Handler() 注册给这个控制器后,启用 GPIO 的中断后,就完成了。
int My_InterruptInitialize(u16 DeviceID)
{
// Interrpt controller initizlization
XScuGic_Config *IntcConfig = XScuGic_LookupConfig(DeviceID);
int ret = XScuGic_CfgInitialize(&gic, IntcConfig, IntcConfig->CpuBaseAddress);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Register Interrupt handler
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler) XScuGic_InterruptHandler,
&gic);
Xil_ExceptionEnable();
// Connect GPIO interrupt to handler
ret = XScuGic_Connect(&gic, GPIO_0_INTERRUPT_ID,
(Xil_ExceptionHandler) SW_Irq_Handler,
(void *) &sw);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Enable GPIO Interrupts
XGpio_InterruptEnable(&sw, SW_INT);
XGpio_InterruptGlobalEnable(&sw);
// Enable GPIO interrupts in the controller
XScuGic_Enable(&gic, GPIO_0_INTERRUPT_ID);
return XST_SUCCESS;
}
好啦~ 程序完成啦,可以准备下载了。 如果觉的哪些地方很模糊的话,完整的程序代码如下
<strong>完整程序代码</strong>
完整的程序代码如下:
#include <stdio.h>
#include "platform.h"
#include "xil_printf.h"
#include "xgpio.h"
#include "sleep.h"
#include "xscugic.h"
XGpio sw;
XGpio led;
XScuGic gic;
#define LED_CHANNEL 2
#define SW_INT XGPIO_IR_CH1_MASK
#define GPIO_0_INTERRUPT_ID XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR
void SW_Irq_Handler(void *gpio)
{
// Disable GPIO Interrupts
XGpio_InterruptDisable(gpio, SW_INT);
// Ignore addition button press
if ((XGpio_InterruptGetStatus(gpio) & SW_INT) != SW_INT)
return;
int val = XGpio_DiscreteRead(gpio, 1);
switch(val) {
case 0x4:
xil_printf("SW 4 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x4); // D14: ON
break;
case 0x1:
xil_printf("SW 3 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x2); // D13: ON
break;
case 0x2:
xil_printf("SW 2 pressed!\n\r");
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x1); // D12: ON
break;
}
// Clear the interrupt bit
XGpio_InterruptClear(gpio, SW_INT);
// Enabl GPIO interrupts
XGpio_InterruptEnable(gpio, SW_INT);
}
int My_InterruptInitialize(u16 DeviceID)
{
// interrpt controller initizlization
XScuGic_Config *IntcConfig = XScuGic_LookupConfig(DeviceID);
int ret = XScuGic_CfgInitialize(&gic, IntcConfig, IntcConfig->CpuBaseAddress);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Register Interrupt handler
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
(Xil_ExceptionHandler) XScuGic_InterruptHandler,
&gic);
Xil_ExceptionEnable();
// Connect GPIO interrupt to handler
ret = XScuGic_Connect(&gic, GPIO_0_INTERRUPT_ID,
(Xil_ExceptionHandler) SW_Irq_Handler,
(void *) &sw);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// Enable GPIO Interrupts
XGpio_InterruptEnable(&sw, SW_INT);
XGpio_InterruptGlobalEnable(&sw);
// Enable GPIO interrupts in the controller
XScuGic_Enable(&gic, GPIO_0_INTERRUPT_ID);
return XST_SUCCESS;
}
int main()
{
init_platform();
// Initialize SW2 ~ SW4
int ret = XGpio_Initialize(&sw, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// setup gpio direction to IN
XGpio_SetDataDirection(&sw, 1, 0xff);
print("Hello Cortex-R5\n\r");
// Initialize LEDs
ret = XGpio_Initialize(&led, XPAR_GPIO_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// setup gpio direction to OUT
XGpio_SetDataDirection(&led, 2, 0xff);
// Initialize Interrupt
ret = My_InterruptInitialize(XPAR_SCUGIC_0_DEVICE_ID);
if (ret != XST_SUCCESS)
return XST_FAILURE;
// wait for interrupt triggered
while (1) ;
cleanup_platform();
return 0;
}
<strong>设定 JTAG 下载</strong>
<p>为了透过 Micro USB 连接到 <a href="http://ultrazed.org/product/ultrazed-eg-pcie-carrier-card" target="_blank">UltraZed-EG PCIe Carrier Card</a> 上的 JTAG 来进行下载,我们需要对 <a href="http://zedboard.org/product/ultrazed-EG" target="_blank">UltraZed-EG</a>上的 SW2 要进行一些调整,变成下图这样。 </p>
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
这样子就可以透过 Micro USB 走 JTAG 下载的路线,将程序下载下去
<strong>下载到开发板 (一次性)</strong>
和 UltraZed-EG PCIe Carrier Card 开发纪录: Hello Cortex-A53 一文不同的是,这次我们不再分别下载 FPGA 和我们的程序,这次采用一次性下载的方案
点选 Run -> Run Configurations
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
在 Xilinx C/C++ Application (GDB) 建立新的设定,并设定如下:
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
点入 Application 确定我们的程序会下载到 psu_cortexr5_0 去
<center><img src="http://xilinx.eetrend.com/files/2019-02/%E5%8D%9A%E5%AE%A2/100017570-59…; alt=""></center>
这样就完成啰,点选 Run 就会看到 Xilinx SDK 先刻录 FPGA 再下载这次的程序了~
<strong>结果</strong>
按照本篇文章的设定,你的 UltraZed-EG PCIe Carrier Card 显示应该如以下影片:
<iframe src='https://coldnew.github.io/7006a3f7/video.mp4' allowfullscreen frameborder=0 width="600" height="400"></iframe>
另外,我们也可以透过 minicom, emacs, tio, gtkterm 等终端机软件,连接上 /dev/ttyUSB0 来查看透过 printf() 输出的讯息。
<strong>取得程序代码</strong>
<p>本文的范例已经上传到 <a href="https://github.com/coldnew-examples/ultrazed_pciecc_helloR5" target="_blank">coldnew/ultrazed_pciecc_helloR5</a> ,你可以透过以下命令获得 </p>
git clone https://github.com/coldnew-examples/ultrazed_pciecc_helloR5.git
<body>
<p><strong>延伸阅读 </strong></p>
<ul type="circle">
<li><a href="https://ece.gmu.edu/coursewebpages/ECE/ECE699_SW_HW/S15/viewgraphs/ECE6…; target="_blank">ECE699: Lecture 4 - Intrrrupts AXI GPIO and AXI Timer.pdf</a></li>
<li><a href="https://www.xilinx.com/support/documentation/ip_documentation/axi_gpio/…; target="_blank">AXI GPIO v2.0 LogiCORE IP Product Guide.pdf</a></li>
<li><a href="https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_2/os…; target="_blank">UG643 (v2018.2): Xilinx Standalone Library Documentation - OS and Libraries Document Collection.pdf</a></li>
<li><a href="https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18841921/AXI+gpio…; target="_blank">Xilinx Wiki: AXI gpio standalone driver</a></li>
<li><a href="https://coldnew.github.io/501bf2ae/">UltraZed-EG PCIe Carrier Card 开发纪录: Hello Cortex-A53</a></li>
<li><a href="https://www.xilinx.com/support/documentation/user_guides/ug1137-zynq-ul…; target="_blank">UG1137 (v8.0): Zynq UltraScale+ MPSoC Software Developer Guide.pdf</a></li>
<li><a href="https://www.xilinx.com/support/answers/51763.html" target="_blank">AR# 51763: Zynq-7000 - How do I know the IRQ ID# of F2P_IRQ when I connect interrupt signals from PL to PS?</a></li></ul>
</body>
本文转载自:<a href="https://coldnew.github.io/" rel="start">coldnew's blog</a>