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