本文轉(zhuǎn)載自:coldnew's blog
在 zybo board 開發(fā)記錄: 透過(guò)可程序邏輯控制 LED 閃爍 一文中我們說(shuō)到了怎樣純粹使用 可程序邏輯 (Programmable Logic, PL) 去控制 Zybo board 上面的四個(gè) LED 燈 (LD0 ~ LD3),接下來(lái)就讓我們透過(guò) Zynq 上的 ARM 處理器來(lái)作到同樣的一件事情吧。
本文主要參考自 Digilentinc 的 Getting Start Guide 并加入我自己試玩的一些心得。
開發(fā)目標(biāo)
我們要透過(guò) Zynq 上的 ARM 處理器,也就是 處理系統(tǒng) (Processing System, PS)去控制 LED,具體目標(biāo)與電路信息如下:
根據(jù) ZYBO FPGA Board Reference Manual 上面的數(shù)據(jù),我們想要控制的這四個(gè)在板子上的 LED 都是位于可程序邏輯區(qū)(Programmable Logic, PL)可以碰觸到的地方,如果你想要透過(guò) Zynq 去對(duì)這些 LED 進(jìn)行控制,你就會(huì)需要透過(guò) AXI GPIO 的幫助,就像這樣:
認(rèn)識(shí) AXI 總線
AXI 總線是作什么用的?我們就從 Zynq 的架構(gòu)來(lái)看 (參照 The Zynq Book p.28)
由上圖可以看到 AXI 總線橫跨了處理器系統(tǒng) (Processing System, PS) 與可程序邏輯 (Programmable Logc, PL) 兩區(qū),并連接到外圍。
實(shí)際上,AXI 協(xié)議為 ARM 的協(xié)議規(guī)范,來(lái)自于 AMBA 總線架構(gòu),若你對(duì)整個(gè)協(xié)議的內(nèi)容有興趣,可以到 ARM 的 網(wǎng)站 去下載規(guī)格書。
建立我們的項(xiàng)目
我們首先當(dāng)然是建立我們的項(xiàng)目了,在進(jìn)行這一步前,請(qǐng)先確定你有按照 讓 Vivado 有 Zybo Board 的配置文件 一文所說(shuō),將 Zybo board 的設(shè)計(jì)導(dǎo)入。
啟動(dòng)了 Vivado 后,點(diǎn)選 Create New Project 。
接下來(lái)指定好你的項(xiàng)目名稱與路徑
選擇 RTL Project
在開發(fā)板選項(xiàng)中,選擇 Zybo Board
完成項(xiàng)目建立
建立 Block Design
當(dāng)我們的設(shè)計(jì)需要用到 Zynq 的處理器系統(tǒng)(Processing System, PS)時(shí)候,就需要透過(guò) Block Design 來(lái)建立我們的電路設(shè)計(jì),首先點(diǎn)選 IP Integrator -> Create Block Design 。
接著點(diǎn)選 OK 建立我們的 block design
點(diǎn)選 Add IP 按鈕去增加我們需要的 IP 核
我們首先尋找 Zynq 并將 ZYNQ7 Processing System 加入到我們的 Block Design,并點(diǎn)選 Run Block Automation 對(duì) Zynq 處理器進(jìn)行一些設(shè)定
進(jìn)入到 Run Block Automation 的設(shè)定頁(yè)面后,確認(rèn) processing_system7_0 有被勾選到,并且 Cross Trigger In 以及 Cross Trigger Out 都是 Disable 的狀態(tài),點(diǎn)選 Ok 結(jié)束設(shè)定。
上面的設(shè)定好了后,就會(huì)看到 ZYNQ7 Processing System 的 DDR 以及 FIXED_IO 都有接線出來(lái)
點(diǎn)選 Add IP 按鈕去增加我們需要的 IP 核,這次我們要增加 AXI_GPIO ,用來(lái)對(duì)可程序邏輯(Programmable Logic, PL)區(qū)域的 LED 進(jìn)行控制,完成后點(diǎn)選上方的 Run Connection Automation 按鈕
在 Run Conenction Automation 窗口內(nèi),我們選擇 Custom (其實(shí)也可以在這邊直接選擇 leds 4bits)
接下來(lái)勾選 S_AXI ,并點(diǎn)選 Ok 進(jìn)行確認(rèn)。
好了后會(huì)像這樣,我們接下來(lái)對(duì) axi_gpio_0 這個(gè)區(qū)塊點(diǎn)兩下,進(jìn)行手動(dòng)設(shè)定
在 IP Configuration 頁(yè)面,設(shè)定 GPIO 為輸出腳,并設(shè)寬度為 4 ,這邊我將輸出默認(rèn)值設(shè)定為 0xF, 也就是預(yù)設(shè)這四個(gè) LED 用的輸出腳都是 High 的電壓。完成后點(diǎn)選 OK, 結(jié)束 AXI_GPIO 的設(shè)定。
接下來(lái)點(diǎn)選 Validate Design 按鈕,我們要確認(rèn)我們的 Block Design 沒問(wèn)題才能夠繼續(xù)往下走。
正常來(lái)講不會(huì)有啥問(wèn)題才對(duì),我們結(jié)束 Block Design 的工作
加入 Constraints
在 zybo board 開發(fā)記錄: 透過(guò)可程序邏輯控制 LED 閃爍 一文有提到如何取得 Constraints 檔案,不過(guò)為了讓這篇文章完整,我們?cè)僦v一次。
我們先連結(jié)到 Zybo Resource Center 去下載 Master XDC 檔案。
你也可以直接透過(guò) wget 命令下載并解壓出 ZYBO_Master.xdc 這個(gè)檔案,它就是本節(jié)要加入的 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
接下來(lái)一樣選擇 Project Manager -> Add sources 來(lái)增加檔案
這次我們要增加的是 Constraints 檔,因此選擇 Add or create constraints
透過(guò) Add Files 添加剛剛下載的 ZYBO_Master.xdc 檔案
ZYBO_Master.xdc
在 ZYBO_Master.xdc 里面,預(yù)設(shè)所有對(duì)應(yīng)接腳都是被批注掉的,這邊我們反批注我們需要的 led 接腳,要記得一下這邊的 I/O 名稱,我們等等要和產(chǎn)生出來(lái)的 HDL Wrapper 進(jìn)行對(duì)應(yīng)的工作。
##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]}]
這樣我們就可以準(zhǔn)備將 Block Design 和硬件接腳對(duì)應(yīng)在一起了。
產(chǎn)生 HDL Wrapper
接下來(lái)我們要透過(guò) Block Design 產(chǎn)生我們的 HDL wrapper,對(duì)你的 Block Design 檔案點(diǎn)選右鍵,選擇 Create HDL Wrapper 。它會(huì)根據(jù)你項(xiàng)目設(shè)定的語(yǔ)言 (VHDL 或是 Verilog) 來(lái)產(chǎn)生相對(duì)的 HDL 程序代碼。
產(chǎn)生出來(lái)的東西我們可能需要改些東西,為了避免麻煩這邊我選第一個(gè)選項(xiàng)。
好了后,假設(shè)你的 Block Design 檔案叫做 design_1.bd,那就會(huì)產(chǎn)生 design_1_wrapper.v 或是 design_1_wrapper.vhdl 這樣的檔案
我們接著要修改這個(gè) HDL Wrapper,這是為什么呢? 回去看一下前面做好的 Block Design 以及 Constraints 的信息,我們可以看到 Block Design 設(shè)定好的 AXI_GPIO 其輸出腳叫做 gpio_rtl ,而在 COnstraints 中,我們目標(biāo)的 LED 輸出腳名稱是 led ,因此我們要調(diào)整一下這個(gè) HDL Wrapper 讓 gpio_rtl 和 led 可以對(duì)應(yīng)在一起。
由于在本范例中,design_1_wrapper.v 也就是 toplevel 的模塊,因此在這邊將對(duì)外的 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
改好后,點(diǎn)選上方的 Run Implementation 來(lái)確認(rèn)我們這樣的修改是否能編譯/驗(yàn)證成功。
產(chǎn)生比特流 (bitstream)
前面的處理都好了后,接下來(lái)點(diǎn)選 Program and Debug -> Generate Bitstream 去讓 Vivado 將這個(gè)項(xiàng)目產(chǎn)生出比特流 (bitstream),ZYNQ 會(huì)根據(jù) bitstream 的信息對(duì) FPGA 進(jìn)行設(shè)定。
當(dāng) bitstream 產(chǎn)生完成后,由于我們這次的實(shí)作,是要透過(guò)寫 C 語(yǔ)言程序來(lái)控制 Zynq 進(jìn)行 LED 的亮暗,因此要先將剛剛產(chǎn)生的硬件信息輸出給 Xilinx SDK 去。
點(diǎn)選 File -> Export -> Export Hardware
確定你有勾選 Include bitstream ,點(diǎn)選 Ok
完成后,啟動(dòng) Xilinx SDK
Xilinx SDK
我們啟動(dòng) Xilinx SDK 后,可以先看到一些像是地址映像 (Address Map) 的信息
選擇 File -> New -> Application Project 去建立新的項(xiàng)目
這邊我命名這個(gè)項(xiàng)目叫做 LED,并且為獨(dú)立的程序
選擇 Empty Application ,我們要自己來(lái)寫我們的程序。
當(dāng)項(xiàng)目建立完成后,會(huì)自動(dòng)打開 LED_bsp 里面的 system.mss ,里面會(huì)顯示我們所用的外圍范例程序代碼以及使用手冊(cè)的連結(jié),我們可以點(diǎn)選這些鏈接來(lái)了解這些外圍要怎樣使用。
如果你連結(jié)點(diǎn)選不開的話,可以到你安裝 SDK 的路徑下去尋找,比如說(shuō)我裝的是 Vivado 2016.2,則手冊(cè)的路徑在
/opt/Xilinx/SDK/2016.2/data/embeddedsw/XilinxProcessorIPLib/drivers
這里給個(gè)結(jié)果的范例,比如我想要查詢 xgpio 的資料,則可以看到如下的 HTML 檔案
建立 main.c
由于我們建立的是空白項(xiàng)目,必須自己添加自己的主程序,因此我們對(duì) LED 項(xiàng)目的 src 按下右鍵,選擇建立新的檔案
這邊將它命名為 main.c ,也就是我們唯一的主程序,點(diǎn)選 Finish 完成檔案建立。
在 main.c 加入以下程序代碼,具體功能待會(huì)在說(shuō)明。
#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 的開頭,我們加載了需要使用的幾個(gè)頭文件,在 Xilinx SDK 中,已經(jīng)包含了一些預(yù)設(shè)好的函式庫(kù)等功能,具體信息請(qǐng)查閱 Xilinx OS and Libraries Document Collection (UG643) 手冊(cè)。
xparameters.h 這個(gè)頭文件則是 Xilinx SDK 自己產(chǎn)生的,里面會(huì)包含一些關(guān)于你使用的 IP Core 的信息,比如標(biāo)準(zhǔn)輸出的基地址 (base address) 或是其他和你這份硬件相關(guān)的設(shè)定。而 xgpio.h 則提供了一些高階的抽象函式,讓你開發(fā) GPIO 相關(guān)的功能可以更加輕松。
#include "xparameters.h"
#include "xgpio.h"
#include
#include
我們用一個(gè)非常簡(jiǎn)單的延遲 (delay) 函式讓 CPU 很忙碌的計(jì)算,來(lái)達(dá)到延遲程序的效果。
void simple_delay (int simple_delay)
{
volatile int i = 0;
for (i = 0; i < simple_delay; i++);
}
接下來(lái)是我們的主程序,我們將它拆開來(lái)看,后面見到的程序代碼都會(huì)塞到主程序中。
int main(int argc, char *argv[])
{
// code
return 0;
}
是時(shí)候進(jìn)入到 GPIO 的功能設(shè)定,注意到 XPAR_AXI_GPIO_0_DEVICE_ID 這個(gè),你可以把它對(duì)應(yīng)回我們的 Block Design 的 axi_gpio_0 ,這個(gè)宏(Macro)即是 Xilinx SDK 產(chǎn)生,定義在 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);
我們透過(guò) printf 去顯示一些簡(jiǎn)單得除錯(cuò)訊息,這邊的訊息會(huì)透過(guò) UART 輸出,你可以透過(guò)計(jì)算機(jī)端的軟件來(lái)收到(ex: gtkterm、teraterm),我自己是透過(guò) emacs 的 serial-term 命令來(lái)收訊息,baud rate 則是設(shè)定為 115200 、連接目標(biāo)則是 /dev/ttyUSB1 。
/* Output something via UART1, 115200 baudrate */
printf("Start to blink led !!!\n\r");
最后,使用一個(gè)無(wú)窮循環(huán)去控制 LED 數(shù)值的變化,并透過(guò) XGpio_DiscreteWrite 去對(duì) GPIO 的通道 1 (參考前面 Block Design) 進(jìn)行數(shù)據(jù)寫入的工程,再調(diào)整下一次到循環(huán)時(shí)要的 LED 數(shù)值,一直重復(fù)這些動(dòng)作。
就這樣,我們的程序完成了。
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
確定此時(shí)你有將 Zybo board 接到計(jì)算機(jī),并且你 JP5 設(shè)定在 QSPI 模式下,就像這樣
選擇 Xilinx Tools -> Program FPGA 進(jìn)行 FPGA 的刻錄。
確認(rèn)要刻錄的數(shù)據(jù)無(wú)誤后,點(diǎn)選 Program 將比特流 (bitstream) 刻錄到 FPGA 去,燒完后你會(huì)發(fā)現(xiàn) LD0 ~ LD3 都是亮燈的狀態(tài),因?yàn)槲覀冊(cè)?Block Design 預(yù)設(shè) AXI_GPIO 輸出為 0xF 。
選擇 Run -> Run Configuration 進(jìn)行執(zhí)行前的一些設(shè)定。
我們?cè)?Xilinx C/C++ Application(GDB) 建立一個(gè)新的設(shè)定,由于我們已經(jīng)刻錄好 FPGA 因此只需要重起處理器系統(tǒng) (Processing System, PS)即可。
確認(rèn)你有啟用 ps7_init 這些設(shè)定,ps7_init 定義了一些初始化的程序,我們之所以能夠使用 printf 將信息透過(guò) UART 輸出,也是透過(guò) ps7_init 的協(xié)助,具體請(qǐng)參考 Zynq-7000 All Programmable SoC: Embedded Design Tutorial A Hands-On Guide to Effective Embedded System Design (UG1165), p.24 頁(yè)。
除了這邊設(shè)定外,別忘記指定要跑的項(xiàng)目,Xilinx SDK 是允許同份硬件設(shè)計(jì)文件跑很多種項(xiàng)目的,以本文范例而言,我們要跑得項(xiàng)目叫做 LED 。
都設(shè)定好后,點(diǎn)選 Run 然后祈禱一切正常!!
結(jié)果
在你執(zhí)行 Program device 以及 Run 后,Xilinx SDK 會(huì)將比特流(bitstream) 下載到我們的 Zybo Board,接著重設(shè) CPU 后執(zhí)行我們下載的程序,最后完成的成果如下
如果你有啟用可以接收 UART 相關(guān)的程序,如 gtkterm、teraterm、screen、emacs 等的話,啟動(dòng)它并開啟/dev/ttyUSB1 后,設(shè)定 baudrate 為 115200 ,就會(huì)看到我們程序透過(guò) printf 輸出的訊息
取得程序代碼
本文的范例已經(jīng)放置于 GitHub 上,你可以到以下的 repo 去尋找,具體項(xiàng)目對(duì)應(yīng)的教學(xué)名稱,則請(qǐng)參考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)
評(píng)論