PYNQ上手筆記 | ③PS端+PL端點燈
上一節中分別獨立實驗了Zynq的PS端和PL端,並初步實驗了PS端先硬體再軟體的開發流程和IP核設計的設計方法。第一節中提及到:Zynq是以PS端的ARM處理器系統為核心的,PS端和PL端是通過AXI匯流排,並且Xilinx已經提供了各種AXI通訊的IP核,接下來的實驗中將會更加明確的體驗到利用IP核設計的設計方法。
1.實驗目標
板載的LED和RGBLED都是接在PL端的,這個實驗將利用PS端程式通過AXI匯流排控制連線在PL端的LED。
2.實驗步驟
整個實驗流程參考《The Zynq Book Tutorials》的chpt1:First Designs on Zynq(這本實驗指導手冊是英文的,但我個人認為英文原版要比中文看著清楚很多)。
需要注意的一點是,這本實驗指導手冊是針對Zybo和Zedboard的,將其中關於板子的操作換為Pynq中的即可。關於軟體驅動程式的部分,書中是採用空白工程+匯入隨書程式的方法,這裡因為板子不同,不採用這種方法,直接利用SDK匯入例程
2.1.建立基於Pynq-Z2開發板的Vivado工程
2.2.基於ip核構建BlockDesign
點選左側導航欄“IP INTEGRATOR -> Create Bolock Design”,在IP整合環境下建立一個塊上設計:
2.2.1.ZYNQ7 Processing System ip 核
注:搜尋“zynq”即可新增。
從圖中可以看出,這只是一個空的IP核,並沒有任何連線,雙擊開啟該ip核配置,也全都是預設配置,接下來需要將這個IP核與實際PS處理器系統對應,點選Run Block Automation,Vivado就會自動根據板子的PS配置資訊進行對應,這就是為什麼要使用board file的原因,如果沒有這個官方提供的board file,所有的PS端配置資訊都需要手動配置:
自動連線完成後,可以看到ip核的DDR埠和FIXED_IO埠已經匯出,與實際開發板配置對應:
這個時候雙擊ip核開啟設定可以看到都已經自動完成了,其中“Peripheral I/O Pins”是指定PS端I/O外設引腳,此處我們暫且不作修改:
2.2.2.AXI GPIO ip 核
注:搜尋“gpio”即可新增。
同樣的,AXI GPIO ip核也是一個空核,並沒有進行任何連線,要注意的是,這是AXI GPIO軟核,在PL端實現,S_AXI端連線在AXI總線上與PS端處理器進行通訊,GPIO端連線實際引腳(可任意分配),驅動板載外設。
首先對AXI GPIO ip核雙擊進行設定:
然後同樣的,點選“Run Connection Automation”,自動完成ZynqPS核和AXI GPIO核之間的連線:
連線完成後可以看到,整個硬體設計如下:
點選右側Address Editor即可看到硬體相關資訊,在PL端實現的axi_gpio_0 IP核中所包含的暫存器首地址和結束地址,這個資訊待會就要匯出到硬體設計檔案中,通過軟體操作相關暫存器。
2.2.3.驗證設計
整體設計完成後,首先按F6或者選擇Tools -> Validate Design驗證設計:
2.3.匯出硬體設計到SDK
2.3.1.由設計檔案建立HDL檔案
在原始檔頁面右擊設計檔案,選擇建立HDL檔案
2.3.2.生成Bitstream
在右側導航欄選擇"PROGRAM AND DEBUG -> Generate Bitstream",生成位元流檔案,用來配置PL端的設計(AXI GPIO 軟ip核), 此過程需要花10-20min不等。
2.3.3.匯出硬體設計檔案
接下來匯出整個設計的硬體設計檔案(hdf)供SDK進行軟體設計使用,因為設計中包含PL端設計,所以要勾選包含Bitstream檔案:
2.3.3.過渡到SDK
匯出完成之後硬體設計工作完成,開啟SDK,進入軟體設計工作:左上角進入“File -> Launch SDK”:
2.4.SDK中軟體設計
2.4.1.工程目錄
其中:
- 橙色部分是需要自己編寫的應用程式程式碼
- 帶有bsp的綠色部分是SDK自動生成的板級驅動支援包
- 黃色部分是關於ARM核心的一些硬體設計檔案
2.4.1.1.硬體設計檔案(hdf)
紅色檔案system.hdf就是在vivado中設計完硬體之後匯出的硬體設計檔案,其中包含了一些設計資訊和暫存器地址對映,可以看到其中axi_gpio_0 ip核所包含暫存器的地址資訊和之前在vivado中看到的完全一樣:
2.4.1.2.板級驅動支援包(BSP)
板級驅動包中只需要關注綠色的檔案system.mss,這個檔案說明了該工程中板級驅動支援包的所有資訊,包括:
-
目標硬體資訊
-
採用作業系統資訊
-
外設驅動資訊
-
庫資訊
2.4.1.3.應用程式程式碼(APP)
應用程式程式碼可以自己編寫或者從例程匯入,這裡直接在system.mss檔案中匯入:
一共包括4個例程,匯入第一個基本例程:
2.4.2.xgpio_example
最好的參考資料是官方文件,包含XGpio操作所有的API說明
2.4.2.1.Xilinx GPIO 控制器介紹
Xilinx GPIO控制器是專為Xilinx FPGA設計的軟IP核,有以下功能:
- 每個通道可設定32個I/O
- 每個I/O可設定為輸入或輸出
- 可配置支援雙通道和外部中斷
XGpio的API宣告在xgpio.h檔案中,API實現在xgpio.c檔案中
2.4.2.2. 初始化XGpio
API:int Xgpio_Initialize(InstancePtr,DeviceID)
功能:根據給定的DeviceID初始化呼叫者提供的XGpio例項
引數 | 型別 | 說明 |
---|---|---|
InstancePtr | sturct* XGpio | GPIO例項指標(xgpio.h) |
DeviceID | u16 | 匯出hdl時自動生成(xparameters.h) |
return | int | 成功-XST_SUCCESS 失敗-XST_DEVICE_NOT_FOUND |
注:
xparameters.h是在匯出硬體設計檔案時由Vivado自動生成的,包含了所有系統硬體引數的巨集定義。
開啟xparameters.h
找到AXI_GPIO_0的有關巨集定義,即可看到和之前在vivado中,在hdf檔案中看到的,都是對應的:
2.4.2.3. 設定XGpio輸入/輸出方向
API:void XGpio_SetDataDirection(XGpio *InstancePtr, unsigned Channel, u32 DirectionMask)
功能:根據給定的DeviceID初始化呼叫者提供的XGpio例項
引數 | 型別 | 說明 |
---|---|---|
InstancePtr | sturct* XGpio | GPIO例項指標(xgpio.h) |
Channel | unsigned | 包含要操作的GPIO的通道 |
DirectionMask | u32 | 指定32個I/O的方向,0-出,1-入 |
注意:如果此功能與除1之外的任何通道一起使用,則必須為雙通道構建硬體。如果不是,則此功能將斷言。
2.4.2.4. XGpio電平邏輯操作
API:XGpio_DiscreteWrite(XGpio * InstancePtr,unsigned Channel,u32 Data)
功能:寫入指定GPIO通道的暫存器
引數 | 型別 | 說明 |
---|---|---|
InstancePtr | sturct* XGpio | GPIO例項指標(xgpio.h) |
Channel | unsigned | 包含要操作的GPIO的通道 |
Data | u32 | 32個I/O的電平,0-低,1-高 |
2.4.2.4.1.置高某個I/O的電平
API:XGpio_DiscreteSet(XGpio * InstancePtr,unsigned Channel,u32 Mask)
功能:將輸出設定為指定GPIO通道的邏輯1
引數 | 型別 | 說明 |
---|---|---|
InstancePtr | sturct* XGpio | GPIO例項指標(xgpio.h) |
Channel | unsigned | 包含要操作的GPIO的通道 |
DirectionMask | u32 | 將被設定為1的位組,0-不選中,1-選中(其它位不受影響) |
2.4.2.4.1.拉低某個I/O的電平
API:XGpio_DiscreteClear(XGpio * InstancePtr,unsigned Channel,u32 Mask)
功能:將輸出設定為指定GPIO通道的邏輯0
引數 | 型別 | 說明 |
---|---|---|
InstancePtr | sturct* XGpio | GPIO例項指標(xgpio.h) |
Channel | unsigned | 包含要操作的GPIO的通道 |
DirectionMask | u32 | 將被設定為0的位組,0-不選中,1-選中(其它位不受影響) |
2.4.3.修改main程式
匯入的例程中如果未發生異常,串列埠沒有任何輸出,所以為了測試,進行以下小修改,將串列埠輸出程式碼提前:
2.4.4.下載並執行程式
因為該設計中包含PL端設計位元流,所以在run程式前需要設定,右鍵->Run As -> Run Configurations:
3.實驗結果
注:要在執行之前開啟串列埠終端。
4.實驗總結
完成了整個實驗後再來看這張圖,首先在PL端通過新增AXI GPIO IP核實現一個GPIO控制器,GPIO控制器由於在PL端,所以輸出直接與4個板載LED相連,然後vivado自動佈局佈線,將GPIO控制器的受控端與PS端處理器核進行對應連線,然後生成匯出硬體設計檔案供SDK使用。
SDK根據硬體設計檔案生成C工程,通過運行於PS端ARM處理器的程式控制PL端實現的GPIO控制器,完成PS通過AXI匯流排控制PL端LED(GPIO)的實驗。
5.實驗擴充套件
5.1.自己動手實現led驅動
5.1.1.新建空工程
在SDK中左上角“File -> New -> Application Project”,建立一個空工程:
5.1.2.標頭檔案
/**
* @filename led_bsp_mculover666.h
* @brief 實現板載4顆LED驅動
* @data 2018/10/18
* @author Mculover666
*/
#ifndef LED_BSP_MCULOVER666_H_
#define LED_BSP_MCULOVER666_H_
#include "xgpio.h"
#include "xparameters.h"
#define ON 1
#define OFF 0
#define LED_XGpio_Device_ID XPAR_GPIO_0_DEVICE_ID
#define LED_XGpio_Channel 1
#define LED 0x0000000F
#define LED1 0x00000001
#define LED2 0x00000002
#define LED3 0x00000004
#define LED4 0x00000008
/* 初始化LED所在io */
void led_init();
void LED1_Statue(int statue);
void LED2_Statue(int statue);
void LED3_Statue(int statue);
void LED4_Statue(int statue);
void LED_Statue(int statue);
#endif /* LED_BSP_MCULOVER666_H_ */
5.1.3.原始檔
/**
* @filename led_bsp_mculover666.c
* @brief 實現板載4顆LED驅動
* @data 2018/10/18
* @author Mculover666
*/
#include "led_bsp_mculover666.h"
XGpio gpio; //例項化一個Xgpio型別結構體
/**
* @brief LED IO初始化
* @param none
* @retval none
* */
void led_init()
{
/* 初始化XGpio */
XGpio_Initialize(&gpio, LED_XGpio_Device_ID);
/* 設定XGpio中LED所在引腳方向為輸出 */
/* !!!注意:0-output 1-input !!! */
XGpio_SetDataDirection(&gpio, LED_XGpio_Channel, ~LED);
/* 預設輸出低電平 */
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED);
}
/* 定義LED單個操作函式 */
void LED1_Statue(int statue)
{
if(statue)
XGpio_DiscreteSet(&gpio, LED_XGpio_Channel, LED1); //低電平點亮
else
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED1); //高電平點亮
}
void LED2_Statue(int statue)
{
if(statue)
XGpio_DiscreteSet(&gpio, LED_XGpio_Channel, LED2); //低電平點亮
else
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED2); //高電平點亮
}
void LED3_Statue(int statue)
{
if(statue)
XGpio_DiscreteSet(&gpio, LED_XGpio_Channel, LED3); //低電平點亮
else
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED3); //高電平點亮
}
void LED4_Statue(int statue)
{
if(statue)
XGpio_DiscreteSet(&gpio, LED_XGpio_Channel, LED4); //低電平點亮
else
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED4); //高電平點亮
}
/* 定義LED整體操作函式 */
void LED_Statue(int statue)
{
if(statue)
XGpio_DiscreteSet(&gpio, LED_XGpio_Channel, LED); //低電平點亮
else
XGpio_DiscreteClear(&gpio, LED_XGpio_Channel, LED); //高電平點亮
}
5.1.4.主檔案
/**
* @filename main.c
* @brief 測試LED驅動
* @data 2018/10/18
* @author Mculover666
*/
#include "led_bsp_mculover666.h"
#include "xil_printf.h"
int main()
{
/* 初始化LED所在io */
led_init();
print("LED init ok\r\n");
/* 開啟所有io */
LED_Statue(ON);
print("LED is all on\r\n");
while(1);
}
5.1.5.實驗結果
5.2.加入RGBLED驅動
加入RGBLED驅動有兩種方法:
-
掛載在已實現的axi_gpio_0的通道1上:axi_gpio_0支援32個io,led只使用了前4個,所以可以將兩顆rgbled的6個引腳掛載在axi_gpio_0上
-
掛載在已實現的axi_gpio_0的通道2上
因為硬體設計檔案更改,所以需要重新自動佈局佈線,生成位元流,匯出硬體檔案
然後將程式中通道值改為2,LED的值改為0x3f(使用了6個io)即可看到現象:
#define LED 0x3f /* Assumes bit 0 of GPIO is connected to an LED */
#define LED_CHANNEL 2