UltraZed-EG PCIe Carrier Card 開發紀錄: Hello Cortex-R5

UltraZed-EG PCIe Carrier Card 開發紀錄: Hello Cortex-A53 中我們題到了如何透過 Vivado 去建立我們的專案,讓 UltraZed-EG PCIe Carrier Card 上的處理器系統 (Processing Syste, PS) 裡面的 Cortex-A53 可以透過 AXI_GPIO 去對可程式邏輯區 (Programmable Logic, PL) 端的 LEDs D12 ~ D19 進行輸出的控制。

在這篇文章,我們要講的則是如何透過 AXI_GPIO 來處理 輸入 的控制,並讓 Cortex-R5 根據不同的輸入,在 ps_uart1 輸出不同的訊息,以及控制不同的 LED 亮暗。

(本文以 Vivado 2018.2 進行開發)

開發目標

UltraZed-EG PCIe Carrier Card 開發紀錄: Hello Cortex-A53 一文很像,只是這次我們把目標轉向 Cortex-R5 ,以及將 GPIO 輸出的功能,改成 GPIO 輸入。

這次我們將透過 ps_uart1 輸出 Cortex-R5 上的訊息,並透過 AXI_GPIO 搭配 interrupt 的使用,去偵測使用者按下可程式邏輯(Programmable Logic, PL) 端的 SW2 ~ SW4 這三個無段按鈕。

建立專案

首先讓我們打開 Vivado 吧~ 不過在進行這一步之前,請先確定你有依照 讓 Vivado 有 UltraZed-EG PCIe Carrier Card 的設定檔 一文的說明,讓我們在建立專案的時候可以找到 UltraZed-EG PCIe Carrier Card 這塊板子。

啟動了 Vivado 後,點選 Create New Project

接下來指定好專案路徑和名稱

選擇 RTL Project ,並將 Do not specify sources at this time 打勾,我們暫時不會匯入已經有的 verilog 程式碼

點選 Boards ,選擇 UltraZed-EG PCIe Carrier Card

完成專案的建立

建立 Block Design

和之前的文章一樣,我們的專案需要用到 Xilinx 一些預先定義好的 IP, 因此使用 Block Design 來建立我們的設計。

首先點選 IP Integrator -> Create Block Design

接著點選 OK 建立我們的 Block Design

點選 Add IP 按鈕去增加我們需要的 IP 核

我們首先尋找 Zynq UltraScale+ MPSoC 並將它加入到我們的 Block Design,並點選 Run BLock Automation 對該 IP 做一些設定

由於預設的 Zynq UltraScale+ MPSoC 並不會打開可程式邏輯 (Programmable Logic, PL) 對應到處理器系統 (Processing System, PS) 的中斷控制 (PL-PS interrupt),因此我們要自己打開。

點擊 Zynq UltraScale+ MPSoC 兩下來對其進行設定,你會看到這樣的頁面,選擇 PS-PL Configuration

接下來,點選 General -> Interrupts -> PL to PS -> IRQ0[0-7] 將其變成 1 ,完成後點選 OK

你會看到我們的 Zynq UltraScale+ MPSoC 增加了 pl_ps_irq0[0:0] 這個輸入界面,如果有需要的話則再 Run Block Automation 一次。

接下來,將 Board 裡面的 Push buttons 拉到我們的 Diagram

目前電路變成這樣

接下來,再把 Board 上的 LED 拉到 axi_gpio_0 上面,讓整個電路變成這樣

axi_gpio_0 點擊兩下,進入到以下設定頁面

在這邊,我們將 Enable Interrupt 打開,點選 OK 完成設定

我們拉條線將 ip2intc_irpt 接到 pl_ps_irq0[0:0] 上,讓 interrupt 可以運作

完成後,點選 Run Connection Automation 進行線路連接,現在電路會變成這樣

(注意到 ip2intc_irpt 一定要連接到 pl_ps_irq0[0:0] 上呦,也就是橘色線的這一條)

完成後可以點選 Validate Design 按鈕來確認設計沒問題

好了,讓我們來產生 HDL Wrapper 吧 ~

產生 HDL Wrapper

接下來我們要將剛剛用 Block Design 建立的電路變成 verilog 程式碼,因此會需要進行產生 HDL Wrapper 這個步驟。

對你的 Block Design 檔案點選右鍵,選擇 Create HDL Wrapper ,它會根據你專案設定的語言 (VHDL 或是 Verilog) 來產生相對的 HDL 程式碼。

由於這次我們不需要對產出來的東西進行修改,因此選 Let Vivado manage wrapper and auto-update 即可

好了後,假設你的 Block Design 檔案叫做 design_1.bd ,那就會產生 design_1_wrapper.v 或是 design_1_wrapper.vhdl 這樣的檔案。

產生位元流 (bitstream)

前面的處理都好了後,接下來點選 Program and Debug -> Generate Bitstream 去讓 Viavado 將這個專案產生出 位元流 (bitstream) ,Zynq UltraScale+ 會在開機的時候根據 bitstream 的資訊對 FPGA 進行設定。

這個產生的過程視你的電腦強度如何而決定花多少時間,總之先來泡杯茶吧~

當 bitstream 完成後,我們準備執行 Xilinx SDK 來透過寫 C 語言專案來讓 Cortex-R 可以透過 AXI_GPIO 偵測 SW2 ~ SW4 的中斷(interrupt) ,並根據不同按鈕的觸發來對 LED 進行控制。

點選 File -> Export -> Export Hardware 將剛剛產生的硬體資訊輸出給 Xilinx SDK 去。

確定你有勾選 Include bitstream 後,點選 OK

完成後,執行 Xilinx SDK

建立 Xilinx SDK 專案

啟動 Xilinx SDK 後,點選 File -> New -> Application Project 去建立新的專案

這邊我命名此一專案為 helloR5 ,並指定為 standalone 的程式,注意到 Processor 要選擇 psu_cortex45_0

接下來,我們一樣選擇 Hello World 來作為我們的專案樣板,點選 Finish 完成專案建立。

打開 helloworld.c

點選左邊欄位的 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 會得到訊息,讓我們修改一下預設的輸出吧。

設定輸出的 UART

在本文一開始,我們題到了我們這次希望透過 ps_uart1 輸出,也就是希望 Linux 端的 /dev/ttyUSB0 可以收到訊息,那這樣要怎樣做呢?

首先點選 Xilinx -> Board Support Packages Settings

選擇 helloR5_bsp

點選 Overview -> standalone 設定 stdinstdoutps_uart1 ,變成如下圖這樣

點選 OK ,完成設定,這樣這個專案透過 print() 或是 xil_printf() 輸出的訊息就都是從 ps_uart1 也就是 Linux 端的 /dev/ttyUSB0 進行輸出囉~

透過 SW 控制 LED (輪詢)

由於如果連如何抓 SW2 ~ SW4 的輸入都不會的話,中斷控制大概也不用提了 (笑)。 因此讓我們先用最傳統的方式,透過輪詢 (polling) 的方式取得當前 SW2 ~ SW4 的狀態,並分別控制 LED D12 ~ D14

簡單的 SW2 ~ SW4 資訊取得

讓我們編輯 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 看看)

確認至少 SW2 ~ SW4 的設定沒錯後,再來看看如何透過中斷 (Interrupt) 來取得這些按鈕的狀態並控制 LED 的亮暗吧 ~

加入 LED 的控制

在上面,我們做到了偵測 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 相關控制的程式,和我們的電路對照在一起就明顯啦 ~

Code

#define LED_CHANNEL 2

XGpio led;

// Initalize GPIO_0
XGpio_Initialize(&led, XPAR_GPIO_0_DEVICE_ID);

// Set direction on GPIO_0 channel 2 to output (LED)
XGpio_SetDataDirection(&led, LED_CHANNEL, 0xff);

// Make D14 ON (GPIO_0 channel 2)
XGpio_DiscreteWrite(&led, LED_CHANNEL, 0x4); // D14: ON

AXI_GPIO_0

是的,由於我們在建立 axi_gpio_0 的時候,將 LED 用的輸出腳放入到了 GPIO_0Channel 2 上,因此就是需要這樣設定才能點亮它~

完整程式碼

到目前為止,透過輪詢(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;
}

我們將來看如何透過中斷來達到一樣的事情~

透過 SW 控制 LED (中斷)

了解了如何透過輪詢(polling) 的方式來使用 GPIO 相關的函式庫後,這次來將剛剛的程式改寫成中斷 (interrupt) 的版本吧 !

清理目前的程式

我們先將剛剛的輪詢的程式清理成這樣,好方便後面程式的撰寫

#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;
}

加入中斷控制

我們先在 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;
}

好啦~ 程式完成啦,可以準備下載了。 如果覺的哪些地方很模糊的話,完整的程式碼如下

完整程式碼

完整的程式碼如下:

#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;
}

設定 JTAG 下載

為了透過 Micro USB 連接到 UltraZed-EG PCIe Carrier Card 上的 JTAG 來進行下載,我們需要對 UltraZed-EG 上的 SW2 要進行一些調整,變成下圖這樣。

這樣子就可以透過 Micro USB 走 JTAG 下載的路線,將程式下載下去

下載到開發板 (一次性)

UltraZed-EG PCIe Carrier Card 開發紀錄: Hello Cortex-A53 一文不同的是,這次我們不再分別下載 FPGA 和我們的程式,這次採用一次性下載的方案

點選 Run -> Run Configurations

Xilinx C/C++ Application (GDB) 建立新的設定,並設定如下:

點入 Application 確定我們的程式會下載到 psu_cortexr5_0

這樣就完成囉,點選 Run 就會看到 Xilinx SDK 先燒錄 FPGA 再下載這次的程式了~

結果

按照本篇文章的設定,你的 UltraZed-EG PCIe Carrier Card 顯示應該如以下影片:

另外,我們也可以透過 minicom, emacs, tio, gtkterm 等終端機軟體,連接上 /dev/ttyUSB0 來查看透過 printf() 輸出的訊息。

取得程式碼

本文的範例已經上傳到 coldnew/ultrazed_pciecc_helloR5 ,你可以透過以下命令獲得

git clone https://github.com/coldnew-examples/ultrazed_pciecc_helloR5.git