一、前言 Xlinx的ZYNQ系列SOC集成了APU、各種專用外設資源和傳統的FPGA邏輯,為ARM+FPGA的應用提供助力,降低功耗和硬體設計難度的同時極大提高兩者間傳輸的帶寬。之前在研究生課題中使用過ZYNQ搭建環路系統對演算法進行板級驗證,但並沒有深入使用和理解這個異構平臺,今天算是對入門的總結 ...
一、前言
Xlinx的ZYNQ系列SOC集成了APU、各種專用外設資源和傳統的FPGA邏輯,為ARM+FPGA的應用提供助力,降低功耗和硬體設計難度的同時極大提高兩者間傳輸的帶寬。之前在研究生課題中使用過ZYNQ搭建環路系統對演算法進行板級驗證,但並沒有深入使用和理解這個異構平臺,今天算是對入門的總結。一款SOC的入門必然是GPIO的使用,而中斷則是MCU能保證實時性的必殺武器。硬體調試難度高一直是FPGA的痛點,集成ARM的FPGA更是如此,cross-trigger調試有效地解決了這一問題,所以它也作為入門ZYNQ的必要技能。
二、硬體系統搭建
ZYNQ的三種GPIO分別是MIO、EMIO和AXI-GPIO。PS部分直接連接到晶元引腳的IO叫MIO,經過FPGA再連接到引腳的是EMIO。EMIO可以通過硬體約束指定不同的埠號和電壓標準,提高了ARM IO的靈活性。而AXI-GPIO相當於是對ARM IO的補充,通過調用AXI-GPIO IP核與外部通信。以下通過一個實例來說明三種IO的使用方式。
系統功能:使用一個MIO使連接其上的LED閃爍,使用8個EMIO同樣與LED連接構成流水燈效果,另外再調用一個5bit位寬的AXI-GPIO IP核以終端模式響應電路板上5個按鍵。
平臺:米聯客 MIZ702N (ZYNQ-7020)
配置ZYNQ IP,使能MIO和EMIO,配置EMIO位寬是8bit。
使能Cross Trigger和共用中斷。
之後添加AXI-GPIO IP Core,配置位寬並使能其中斷功能:
運行Run Automatic Connection最終block design系統結構:
這裡使用ILA抓取AXI-GPIO的中斷信號。
三、軟體編程與AXI-GPIO中斷模式解析
Implementation,export hardware with bitstream, launch SDK. BSP中自帶了硬體系統所使用到的IP的一些示例代碼和文檔,為入門提供了很好的幫助。
為了方便復用,對Xilinx提供的API做進一步封裝,生成gpiops.h gpiops.c gpio.h gpio.c和gic.h文件。接下來重點講述GIC相關的代碼原理。若要使用中斷系統,首先要初始化GIC,和其他IP一樣包括查找配置和初始賦值兩個步驟,分別由LookupConfig和CfgInitialize兩個函數完成。後者實際上初始化了中斷處理句柄使其指向了一個空結構。要理解內部原理,需要弄清楚XScuGic的數據結構。
其中Handler實際上是一個函數指針類型,用於定義中斷產生時的回調函數。而CallBackRef用於傳入InstancePtr,即Gic Instance Pointer。GIC初始化完,要將GIC與中斷ID和自定義中斷回調函數綁定。
內部的核心代碼依然和初始化時一致,只不過換成了輸入參數:
下一步該使能中斷了,一方面是使用GIC對GPIO中斷ID的響應,另一方面是使能AXI-GPIO的中斷信號。最後是系統對異常的處理函數,這裡將其封裝在exception_enable中:
總結來看,中斷系統建立的步驟為:
1 初始化GIC
2 連接GIC與中斷ID和回調函數
3 使能中斷
4 使能異常處理
那麼為什麼完成上述操作後,中斷事件發生會立即執行自定義中斷回調函數GpioHandler呢?CPU會將中斷向量表存儲在特定的寄存器中,讀取該寄存器可以獲取中斷向量表內容,裡邊存放著各個中斷ID對應的中斷函數入口地址。跳轉指令則有中斷控制器完成。
接下來是各個文件的軟體代碼:
1 /* 2 * main.c 3 * 4 * Created on: 2020年2月22日 5 * Author: s 6 */ 7 8 9 #include "xparameters.h" 10 11 #include "xstatus.h" 12 #include <xil_printf.h> 13 #include "sleep.h" 14 15 #include "gpiops.h" 16 #include "gpio.h" 17 #include "gic.h" 18 19 20 XGpioPs GpioPs; /* The driver instance for GPIO Device. */ 21 XGpio Gpio; 22 XScuGic Intc; /* The Instance of the Interrupt Controller Driver */ 23 24 25 #define printf xil_printf /* Smalller foot-print printf */ 26 27 #define LOOP_NUM 8 28 29 30 31 32 static u32 MIO_OUT_PIN_INDEX =7; /* LED button */ 33 static u32 EMIO_OUT_PIN_BASE_INDEX = 54; 34 volatile u32 IntrFlag; /* Interrupt Handler Flag */ 35 36 void GpioHandler(void *CallbackRef); 37 int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr 38 ,u32 IntrId); 39 40 int main() 41 { 42 int Status; 43 u8 i=0; 44 u32 sys_led_out=0x1; 45 46 Status = gpiops_initialize(&GpioPs,GPIOPS_DEVICE_ID); 47 if (Status != XST_SUCCESS) { 48 return XST_FAILURE; 49 } 50 51 Status = gpio_initialize(&Gpio,GPIO_DEVICE_ID); 52 if (Status != XST_SUCCESS) { 53 return XST_FAILURE; 54 } 55 56 57 /* 58 * Set the direction for the pin to be output and 59 * Enable the Output enable for the LED Pin. 60 */ 61 gpiops_setOutput(&GpioPs,MIO_OUT_PIN_INDEX); 62 63 for(i=0;i<LOOP_NUM;i++){ 64 gpiops_setOutput(&GpioPs,EMIO_OUT_PIN_BASE_INDEX+i); 65 } 66 67 gpio_setDirect(&Gpio, 1,GPIO_CHANNEL1); 68 69 Status = setupIntSystem(&Intc,&Gpio,INTC_GPIO_INTERRUPT_ID); 70 if (Status != XST_SUCCESS) { 71 return XST_FAILURE; 72 } 73 74 printf("Initialization finish.\n"); 75 76 while(1){ 77 78 for(i=0;i<LOOP_NUM;i++){ 79 /* Set the GPIO output to be low. */ 80 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x1); 81 usleep(200*1000); 82 gpiops_outputValue(&GpioPs, EMIO_OUT_PIN_BASE_INDEX+i, 0x0); 83 } 84 85 gpiops_outputValue(&GpioPs, MIO_OUT_PIN_INDEX, sys_led_out); 86 sys_led_out = sys_led_out == 0x0 ? 0x1 : 0x0; 87 } 88 return 0; 89 } 90 91 int setupIntSystem(XScuGic *IntcInstancePtr,XGpio *gpioInstancePtr 92 ,u32 IntrId) 93 { 94 int Result; 95 /* 96 * Initialize the interrupt controller driver so that it is ready to 97 * use. 98 */ 99 100 Result = gic_initialize(&Intc,INTC_DEVICE_ID); 101 if (Result != XST_SUCCESS) { 102 return XST_FAILURE; 103 } 104 105 XScuGic_SetPriorityTriggerType(IntcInstancePtr, IntrId, 106 0xA0, 0x3); 107 108 /* 109 * Connect the interrupt handler that will be called when an 110 * interrupt occurs for the device. 111 */ 112 Result = XScuGic_Connect(IntcInstancePtr, IntrId, 113 (Xil_ExceptionHandler)GpioHandler, gpioInstancePtr); 114 if (Result != XST_SUCCESS) { 115 return Result; 116 } 117 118 /* Enable the interrupt for the GPIO device.*/ 119 XScuGic_Enable(IntcInstancePtr, IntrId); 120 121 /* 122 * Enable the GPIO channel interrupts so that push button can be 123 * detected and enable interrupts for the GPIO device 124 */ 125 XGpio_InterruptEnable(gpioInstancePtr,GPIO_CHANNEL1); 126 XGpio_InterruptGlobalEnable(gpioInstancePtr); 127 128 /* 129 * Initialize the exception table and register the interrupt 130 * controller handler with the exception table 131 */ 132 exception_enable(&Intc); 133 134 IntrFlag = 0; 135 136 return XST_SUCCESS; 137 } 138 139 void GpioHandler(void *CallbackRef) 140 { 141 XGpio *GpioPtr = (XGpio *)CallbackRef; 142 u32 gpio_inputValue; 143 144 145 /* Clear the Interrupt */ 146 XGpio_InterruptClear(GpioPtr, GPIO_CHANNEL1); 147 printf("Input interrupt routine.\n"); 148 149 //IntrFlag = 1; 150 gpio_inputValue = gpio_readValue(GpioPtr, 1); 151 switch(gpio_inputValue) 152 { 153 case 30: 154 printf("button up\n"); 155 break; 156 case 29: 157 printf("button center\n"); 158 break; 159 case 27: 160 printf("button left\n"); 161 break; 162 case 23: 163 printf("button right\n"); 164 break; 165 case 15: 166 print("button down\n"); 167 break; 168 } 169 170 }main.c
1 /* 2 * gpio.h 3 * 4 * Created on: 2020年2月23日 5 * Author: s 6 */ 7 8 #ifndef SRC_GPIO_H_ 9 #define SRC_GPIO_H_ 10 11 #include "xgpio.h" 12 13 #define GPIO_DEVICE_ID XPAR_GPIO_0_DEVICE_ID 14 #define INTC_GPIO_INTERRUPT_ID XPAR_FABRIC_AXI_GPIO_0_IP2INTC_IRPT_INTR 15 #define GPIO_CHANNEL1 0x1F 16 17 int gpio_initialize(XGpio * InstancePtr, u16 DeviceId); 18 void gpio_setDirect(XGpio *InstancePtr, unsigned Channel, 19 u32 DirectionMask); 20 void gpio_outputValue(XGpio * InstancePtr, unsigned Channel, u32 Data); 21 u32 gpio_readValue(XGpio * InstancePtr, unsigned Channel); 22 #endif /* SRC_GPIO_H_ */gpio.h
/* * gpio.c * * Created on: 2020年2月23日 * Author: s */ #include "gpio.h" int gpio_initialize(XGpio * InstancePtr, u16 DeviceId) { return XGpio_Initialize(InstancePtr,DeviceId); } void gpio_setDirect(XGpio *InstancePtr, unsigned Channel, u32 DirectionMask) { XGpio_SetDataDirection(InstancePtr, Channel, DirectionMask); } void gpio_outputValue(XGpio * InstancePtr, unsigned Channel, u32 Data) { XGpio_DiscreteWrite(InstancePtr, Channel, Data); } u32 gpio_readValue(XGpio * InstancePtr, unsigned Channel) { return XGpio_DiscreteRead(InstancePtr, Channel); }gpio.c
1 /* 2 * gpiops.c 3 * 4 * Created on: 2020年2月23日 5 * Author: s 6 */ 7 8 #include "gpiops.h" 9 10 int gpiops_initialize(XGpioPs *InstancePtr,u16 DeviceId) 11 { 12 XGpioPs_Config *ConfigPtr; 13 14 ConfigPtr = XGpioPs_LookupConfig(DeviceId); 15 return XGpioPs_CfgInitialize(InstancePtr, ConfigPtr, 16 ConfigPtr->BaseAddr); 17 } 18 19 void gpiops_setOutput (XGpioPs *InstancePtr,u32 Pin) 20 { 21 XGpioPs_SetDirectionPin(InstancePtr, Pin, 1); 22 XGpioPs_SetOutputEnablePin(InstancePtr, Pin, 1); 23 } 24 25 void gpiops_setInput(XGpioPs *InstancePtr,u32 Pin) 26 { 27 XGpioPs_SetDirectionPin(InstancePtr, Pin, 0); 28 } 29 30 void gpiops_outputValue(XGpioPs *InstancePtr,u32 Pin,u32 Data) 31 { 32 XGpioPs_WritePin(InstancePtr, Pin, Data); 33 } 34 35 36 u32 gpiops_readValue(XGpioPs *InstancePtr,u32 Pin) 37 { 38 /* Read the state of the data so that it can be verified. */ 39 return XGpioPs_ReadPin(InstancePtr, Pin); 40 }gpiops.c
1 /* 2 * gpio.h 3 * 4 * Created on: 2020年2月23日 5 * Author: s 6 */ 7 8 #ifndef SRC_GPIOPS_H_ 9 #define SRC_GPIOPS_H_ 10 11 #include "xgpiops.h" 12 13 #define GPIOPS_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID 14 15 16 17 int gpiops_initialize(XGpioPs *InstancePtr,u16 DeviceId); 18 void gpiops_setOutput (XGpioPs *InstancePtr,u32 Pin); 19 void gpiops_setInput(XGpioPs *InstancePtr,u32 Pin); 20 void gpiops_outputValue(XGpioPs *InstancePtr,u32 Pin,u32 Data); 21 u32 gpiops_readValue(XGpioPs *InstancePtr,u32 Pin); 22 23 24 #endif /* SRC_GPIOPS_H_ */gpiops.h
1 /* 2 * gic.h 3 * 4 * Created on: 2020年2月23日 5 * Author: s 6 */ 7 8 #ifndef SRC_GIC_H_ 9 #define SRC_GIC_H_ 10 11 #include "xscugic.h" 12 13 #define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID 14 15 s32 gic_initialize(XScuGic *InstancePtr,u16 DeviceId) 16 { 17 XScuGic_Config *IntcConfig; 18 19 IntcConfig = XScuGic_LookupConfig(DeviceId); 20 if (NULL == IntcConfig) { 21 return XST_FAILURE; 22 } 23 24 return XScuGic_CfgInitialize(InstancePtr, IntcConfig, 25 IntcConfig->CpuBaseAddress); 26 } 27 28 void exception_enable(void *Data) 29 { 30 Xil_ExceptionInit(); 31 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, 32 (Xil_ExceptionHandler)XScuGic_InterruptHandler, Data); 33 34 /* Enable non-critical exceptions */ 35 Xil_ExceptionEnable(); 36 } 37 38 39 #endif /* SRC_GIC_H_ */gic.h
四、交叉觸發調試
右鍵工程文件夾->Run As/Debug As分別用於代碼下載和調試。SDK基於GDB提供了強大的調試能力,支持斷點運行,可查看內部寄存器、地址數值以及彙編代碼等。Debug As ->Debug Configuartion,雙擊System Debugger新建ELF文件。勾選Reset entire system和Program FPGA,因為ELF只是軟體,硬體信息存儲在bitstream中。最重要的是勾選enable cross-triggering。
點擊enable cross-triggering右側的按鈕,按照如下操作使能Processor to Fabric Trigger.
再次create使能Fabric to Processor Trigger:
最後點擊Debug下載軟硬體代碼併進入調試界面。
1 首先嘗試PS觸發PL調試:
指定中斷回調函數起始位置一個斷點。然後進入VIVADO,打開Hardware Manager連接硬體。註意此時觸發模式選擇IN_ONLY。此時不用設置ILA抓取信號的觸發條件,因為觸發由PS端的PC控制。點擊Run Trigger等待觸發條件。這時回到SDK點擊Resume按鈕使代碼開始運行。按下任意按鍵產生中斷,此時軟體代碼運行到斷點處停止,ILA隨即抓取中斷信號。
2 嘗試PL觸發PS調試:
這回在VIVADO中設置觸發模式為OR_TRIG_IN,並啟動觸發條件為上升沿觸發。按下按鍵,C運行到滿足ILA觸發條件時C代碼立即停止,故PL控制了PS端的程式運行。
可以看到此時程式進入IRQHandler。
串口終端也列印進入中斷函數的信息,正確響應中斷。到此示例結束。本文雖是對ZYNQ入門的整理,但涉及到的東西很多,包括GPIO應用、中斷系統建立和相應機制、調用AXI匯流排IP核、軟體設計以及軟硬體交叉觸發調試流程。