本文转载自:coldnew's blog
在 zybo board 开发记录: 透过可程序逻辑控制 LED 闪烁 一文中我们说到了怎样纯粹使用 可程序逻辑 (Programmable Logic, PL) 去控制 Zybo board 上面的四个 LED 灯 (LD0 ~ LD3),接下来就让我们透过 Zynq 上的 ARM 处理器来作到同样的一件事情吧。
本文主要参考自 Digilentinc 的 Getting Start Guide 并加入我自己试玩的一些心得。
开发目标
我们要透过 Zynq 上的 ARM 处理器,也就是 处理系统 (Processing System, PS)去控制 LED,具体目标与电路信息如下:
根据 ZYBO FPGA Board Reference Manual 上面的数据,我们想要控制的这四个在板子上的 LED 都是位于可程序逻辑区(Programmable Logic, PL)可以碰触到的地方,如果你想要透过 Zynq 去对这些 LED 进行控制,你就会需要透过 AXI GPIO 的帮助,就像这样:
认识 AXI 总线
AXI 总线是作什么用的?我们就从 Zynq 的架构来看 (参照 The Zynq Book p.28)
由上图可以看到 AXI 总线横跨了处理器系统 (Processing System, PS) 与可程序逻辑 (Programmable Logc, PL) 两区,并连接到外围。
实际上,AXI 协议为 ARM 的协议规范,来自于 AMBA 总线架构,若你对整个协议的内容有兴趣,可以到 ARM 的 网站 去下载规格书。
建立我们的项目
我们首先当然是建立我们的项目了,在进行这一步前,请先确定你有按照 让 Vivado 有 Zybo Board 的配置文件 一文所说,将 Zybo board 的设计导入。
启动了 Vivado 后,点选 Create New Project 。
接下来指定好你的项目名称与路径
选择 RTL Project
在开发板选项中,选择 Zybo Board
完成项目建立
建立 Block Design
当我们的设计需要用到 Zynq 的处理器系统(Processing System, PS)时候,就需要透过 Block Design 来建立我们的电路设计,首先点选 IP Integrator -> Create Block Design 。
接着点选 OK 建立我们的 block design
点选 Add IP 按钮去增加我们需要的 IP 核
我们首先寻找 Zynq 并将 ZYNQ7 Processing System 加入到我们的 Block Design,并点选 Run Block Automation 对 Zynq 处理器进行一些设定
进入到 Run Block Automation 的设定页面后,确认 processing_system7_0 有被勾选到,并且 Cross Trigger In 以及 Cross Trigger Out 都是 Disable 的状态,点选 Ok 结束设定。
上面的设定好了后,就会看到 ZYNQ7 Processing System 的 DDR 以及 FIXED_IO 都有接线出来
点选 Add IP 按钮去增加我们需要的 IP 核,这次我们要增加 AXI_GPIO ,用来对可程序逻辑(Programmable Logic, PL)区域的 LED 进行控制,完成后点选上方的 Run Connection Automation 按钮
在 Run Conenction Automation 窗口内,我们选择 Custom (其实也可以在这边直接选择 leds 4bits)
接下来勾选 S_AXI ,并点选 Ok 进行确认。
好了后会像这样,我们接下来对 axi_gpio_0 这个区块点两下,进行手动设定
在 IP Configuration 页面,设定 GPIO 为输出脚,并设宽度为 4 ,这边我将输出默认值设定为 0xF, 也就是预设这四个 LED 用的输出脚都是 High 的电压。完成后点选 OK, 结束 AXI_GPIO 的设定。
接下来点选 Validate Design 按钮,我们要确认我们的 Block Design 没问题才能够继续往下走。
正常来讲不会有啥问题才对,我们结束 Block Design 的工作
加入 Constraints
在 zybo board 开发记录: 透过可程序逻辑控制 LED 闪烁 一文有提到如何取得 Constraints 档案,不过为了让这篇文章完整,我们再讲一次。
我们先连结到 Zybo Resource Center 去下载 Master XDC 档案。
你也可以直接透过 wget 命令下载并解压出 ZYBO_Master.xdc 这个档案,它就是本节要加入的 Constraints 档
coldnew@gentoo /tmp $ wget https://reference.digilentinc.com/_media/zybo/zybo_master_xdc.zip
coldnew@gentoo /tmp $ unzip zybo_master_xdc.zip
Archive: zybo_master_xdc.zip
inflating: ZYBO_Master.xdc
接下来一样选择 Project Manager -> Add sources 来增加档案
这次我们要增加的是 Constraints 档,因此选择 Add or create constraints
透过 Add Files 添加刚刚下载的 ZYBO_Master.xdc 档案
ZYBO_Master.xdc
在 ZYBO_Master.xdc 里面,预设所有对应接脚都是被批注掉的,这边我们反批注我们需要的 led 接脚,要记得一下这边的 I/O 名称,我们等等要和产生出来的 HDL Wrapper 进行对应的工作。
##LEDs
##IO_L23P_T3_35
set_property PACKAGE_PIN M14 [get_ports {led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[0]}]
##IO_L23N_T3_35
set_property PACKAGE_PIN M15 [get_ports {led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[1]}]
##IO_0_35
set_property PACKAGE_PIN G14 [get_ports {led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[2]}]
##IO_L3N_T0_DQS_AD1N_35
set_property PACKAGE_PIN D18 [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[3]}]
这样我们就可以准备将 Block Design 和硬件接脚对应在一起了。
产生 HDL Wrapper
接下来我们要透过 Block Design 产生我们的 HDL wrapper,对你的 Block Design 档案点选右键,选择 Create HDL Wrapper 。它会根据你项目设定的语言 (VHDL 或是 Verilog) 来产生相对的 HDL 程序代码。
产生出来的东西我们可能需要改些东西,为了避免麻烦这边我选第一个选项。
好了后,假设你的 Block Design 档案叫做 design_1.bd,那就会产生 design_1_wrapper.v 或是 design_1_wrapper.vhdl 这样的档案
我们接着要修改这个 HDL Wrapper,这是为什么呢? 回去看一下前面做好的 Block Design 以及 Constraints 的信息,我们可以看到 Block Design 设定好的 AXI_GPIO 其输出脚叫做 gpio_rtl ,而在 COnstraints 中,我们目标的 LED 输出脚名称是 led ,因此我们要调整一下这个 HDL Wrapper 让 gpio_rtl 和 led 可以对应在一起。
由于在本范例中,design_1_wrapper.v 也就是 toplevel 的模块,因此在这边将对外的 gpio_rtl_tri_o 接脚改为 led 让它接出即可。
diff --git a/led_flash_zynq.srcs/sources_1/imports/hdl/design_1_wrapper.v b/led_flash_zynq.srcs/sources_1/imports/hdl/design_1_wrapper.v
index 7b1b0bd..c57caa0 100644
--- a/led_flash_zynq.srcs/sources_1/imports/hdl/design_1_wrapper.v
+++ b/led_flash_zynq.srcs/sources_1/imports/hdl/design_1_wrapper.v
@@ -31,7 +31,7 @@ module design_1_wrapper
FIXED_IO_ps_clk,
FIXED_IO_ps_porb,
FIXED_IO_ps_srstb,
- gpio_rtl_tri_o);
+ led);
inout [14:0]DDR_addr;
inout [2:0]DDR_ba;
inout DDR_cas_n;
@@ -53,7 +53,7 @@ module design_1_wrapper
inout FIXED_IO_ps_clk;
inout FIXED_IO_ps_porb;
inout FIXED_IO_ps_srstb;
- output [3:0]gpio_rtl_tri_o;
+ output [3:0]led;
wire [14:0]DDR_addr;
wire [2:0]DDR_ba;
@@ -76,7 +76,7 @@ module design_1_wrapper
wire FIXED_IO_ps_clk;
wire FIXED_IO_ps_porb;
wire FIXED_IO_ps_srstb;
- wire [3:0]gpio_rtl_tri_o;
+ wire [3:0]led;
design_1 design_1_i
(.DDR_addr(DDR_addr),
@@ -100,5 +100,5 @@ module design_1_wrapper
.FIXED_IO_ps_clk(FIXED_IO_ps_clk),
.FIXED_IO_ps_porb(FIXED_IO_ps_porb),
.FIXED_IO_ps_srstb(FIXED_IO_ps_srstb),
-
.gpio_rtl_tri_o(gpio_rtl_tri_o));
+
.gpio_rtl_tri_o(led));
endmodule
改好后,点选上方的 Run Implementation 来确认我们这样的修改是否能编译/验证成功。
产生比特流 (bitstream)
前面的处理都好了后,接下来点选 Program and Debug -> Generate Bitstream 去让 Vivado 将这个项目产生出比特流 (bitstream),ZYNQ 会根据 bitstream 的信息对 FPGA 进行设定。
当 bitstream 产生完成后,由于我们这次的实作,是要透过写 C 语言程序来控制 Zynq 进行 LED 的亮暗,因此要先将刚刚产生的硬件信息输出给 Xilinx SDK 去。
点选 File -> Export -> Export Hardware
确定你有勾选 Include bitstream ,点选 Ok
完成后,启动 Xilinx SDK
Xilinx SDK
我们启动 Xilinx SDK 后,可以先看到一些像是地址映像 (Address Map) 的信息
选择 File -> New -> Application Project 去建立新的项目
这边我命名这个项目叫做 LED,并且为独立的程序
选择 Empty Application ,我们要自己来写我们的程序。
当项目建立完成后,会自动打开 LED_bsp 里面的 system.mss ,里面会显示我们所用的外围范例程序代码以及使用手册的连结,我们可以点选这些链接来了解这些外围要怎样使用。
如果你连结点选不开的话,可以到你安装 SDK 的路径下去寻找,比如说我装的是 Vivado 2016.2,则手册的路径在
/opt/Xilinx/SDK/2016.2/data/embeddedsw/XilinxProcessorIPLib/drivers
这里给个结果的范例,比如我想要查询 xgpio 的资料,则可以看到如下的 HTML 档案
建立 main.c
由于我们建立的是空白项目,必须自己添加自己的主程序,因此我们对 LED 项目的 src 按下右键,选择建立新的档案
这边将它命名为 main.c ,也就是我们唯一的主程序,点选 Finish 完成档案建立。
在 main.c 加入以下程序代码,具体功能待会在说明。
#include "xparameters.h"
#include "xgpio.h"
#include
#include
void simple_delay (int simple_delay)
{
volatile int i = 0;
for (i = 0; i < simple_delay; i++);
}
int main(int argc, char *argv[])
{
XGpio led_gpio;
/* LED Instance */
/* Initialize LED GPIO settings */
XGpio_Initialize(&led_gpio, XPAR_AXI_GPIO_0_DEVICE_ID);
XGpio_SetDataDirection(&led_gpio, 1, 0);
/* Output something via UART1, 115200 baudrate */
printf("Start to blink led_gpio !!!\n\r");
int led_value = 0x03; /* default led_gpio value */
while(1) {
printf("led_gpio value set to 0x%X\n\r", led_value);
/* Set GPIO Channel 1 value. */
XGpio_DiscreteWrite(&led_gpio, 1 , led_value);
/* sleep and change led_gpio value */
simple_delay(10000000);
led_value = ~led_value;
}
return 0;
}
main.c
在 main.c 的开头,我们加载了需要使用的几个头文件,在 Xilinx SDK 中,已经包含了一些预设好的函式库等功能,具体信息请查阅 Xilinx OS and Libraries Document Collection (UG643) 手册。
xparameters.h 这个头文件则是 Xilinx SDK 自己产生的,里面会包含一些关于你使用的 IP Core 的信息,比如标准输出的基地址 (base address) 或是其他和你这份硬件相关的设定。而 xgpio.h 则提供了一些高阶的抽象函式,让你开发 GPIO 相关的功能可以更加轻松。
#include "xparameters.h"
#include "xgpio.h"
#include
#include
我们用一个非常简单的延迟 (delay) 函式让 CPU 很忙碌的计算,来达到延迟程序的效果。
void simple_delay (int simple_delay)
{
volatile int i = 0;
for (i = 0; i < simple_delay; i++);
}
接下来是我们的主程序,我们将它拆开来看,后面见到的程序代码都会塞到主程序中。
int main(int argc, char *argv[])
{
// code
return 0;
}
是时候进入到 GPIO 的功能设定,注意到 XPAR_AXI_GPIO_0_DEVICE_ID 这个,你可以把它对应回我们的 Block Design 的 axi_gpio_0 ,这个宏(Macro)即是 Xilinx SDK 产生,定义在 xparameter.h 里面。
XGpio led_gpio; /* LED Instance */
/* Initialize LED GPIO settings */
XGpio_Initialize(&led_gpio, XPAR_AXI_GPIO_0_DEVICE_ID);
XGpio_SetDataDirection(&led_gpio, 1, 0);
我们透过 printf 去显示一些简单得除错讯息,这边的讯息会透过 UART 输出,你可以透过计算机端的软件来收到(ex: gtkterm、teraterm),我自己是透过 emacs 的 serial-term 命令来收讯息,baud rate 则是设定为 115200 、连接目标则是 /dev/ttyUSB1 。
/* Output something via UART1, 115200 baudrate */
printf("Start to blink led !!!\n\r");
最后,使用一个无穷循环去控制 LED 数值的变化,并透过 XGpio_DiscreteWrite 去对 GPIO 的通道 1 (参考前面 Block Design) 进行数据写入的工程,再调整下一次到循环时要的 LED 数值,一直重复这些动作。
就这样,我们的程序完成了。
int led_value = 0x03; /* default led value */
while(1) {
printf("led value set to 0x%X\n\n", led_value);
/* Set GPIO Channel 1 value. */
XGpio_DiscreteWrite(&led_gpio, 1, led_value);
/* sleep and change led value */
simple_delay(10000000);
led_value = ~led_value;
}
下载到 Zybo board
确定此时你有将 Zybo board 接到计算机,并且你 JP5 设定在 QSPI 模式下,就像这样
选择 Xilinx Tools -> Program FPGA 进行 FPGA 的刻录。
确认要刻录的数据无误后,点选 Program 将比特流 (bitstream) 刻录到 FPGA 去,烧完后你会发现 LD0 ~ LD3 都是亮灯的状态,因为我们在 Block Design 预设 AXI_GPIO 输出为 0xF 。
选择 Run -> Run Configuration 进行执行前的一些设定。
我们在 Xilinx C/C++ Application(GDB) 建立一个新的设定,由于我们已经刻录好 FPGA 因此只需要重起处理器系统 (Processing System, PS)即可。
确认你有启用 ps7_init 这些设定,ps7_init 定义了一些初始化的程序,我们之所以能够使用 printf 将信息透过 UART 输出,也是透过 ps7_init 的协助,具体请参考 Zynq-7000 All Programmable SoC: Embedded Design Tutorial A Hands-On Guide to Effective Embedded System Design (UG1165), p.24 页。
除了这边设定外,别忘记指定要跑的项目,Xilinx SDK 是允许同份硬件设计文件跑很多种项目的,以本文范例而言,我们要跑得项目叫做 LED 。
都设定好后,点选 Run 然后祈祷一切正常!!
结果
在你执行 Program device 以及 Run 后,Xilinx SDK 会将比特流(bitstream) 下载到我们的 Zybo Board,接着重设 CPU 后执行我们下载的程序,最后完成的成果如下
如果你有启用可以接收 UART 相关的程序,如 gtkterm、teraterm、screen、emacs 等的话,启动它并开启/dev/ttyUSB1 后,设定 baudrate 为 115200 ,就会看到我们程序透过 printf 输出的讯息
取得程序代码
本文的范例已经放置于 GitHub 上,你可以到以下的 repo 去寻找,具体项目对应的教学名称,则请参考README.md 档案
延伸阅读
[1] The Zynq Book
[2] ZYBO Quick-Start Tutorial
[3] XILINX - Embedded System Tools Reference Manual
[4] AXI GPIO v2.0 LogiCORE IP Product Guide
[5] Xilinx OS and Libraries Document Collection (UG643)
[6] ZYBO Zync-7000 Development Board Work - Getting the LEDs to flash
[7] Zynq-7000 All Programmable SoC: Embedded Design Tutorial A Hands-On Guide to Effective Embedded System Design (UG1165)
评论