ZYNQ7000-GPIO详解
ZYNQ7000-GPIO詳解
參考:UG585 - Zynq-7000 SoC Technical Reference Manual (v1.12.2) 385~394頁–Ch14: General Purpose I/O(GPIO)
一. GPIO的基本概念
GPIO,General Purpose I/O,通用輸入/輸出,是ZYNQ的外設之一。ZYNQ的架構圖如下圖所示。
與非SOC不同的是,ZYNQ的GPIO引腳由PS側的MIO引腳和PL側的EMIO引腳構成(見上圖)。
關于MIO和EMIO的詳細介紹參見我的另一篇博客:傳送門:ZYNQ7000-MIO與EMIO詳解
二. GPIO框圖與分組
ZYNQ的GPIO框圖如下圖所示。ZYNQ的GPIO引腳分為4個Bank即4組,其中,
118個GPIO = 32個MIO(Bank0) + 22個MIO(Bank1)+ 32個EMIO(Bank2)+ 32個EMIO(Bank3)
可見基本都是32個IO引腳一組,這是因為GPIO的控制寄存器是32位的,每一位對應一個IO引腳的話,一組寄存器就對應32個引腳,所以,4組基本相同的寄存器分別控制4組Bank,Bank1對應的也是一組32位寄存器,只是因為MIO引腳總共就54個,54-32=22,所以這組寄存器只控制22個引腳。
三. GPIO的功能與控制寄存器
GPIO的功能有三種:輸入,輸出,中斷。使用MIO和EMIO在功能上幾乎相同,唯一的區別是EMIO因為是PL側引腳,可以與PL部分進行通訊,而MIO對PL側是透明的。
特別的,MIO7,MIO8只能做輸出,這在ZYNQ7000-MIO與EMIO詳解中有說明。
GPIO的功能在芯片內部通過一組寄存器來控制,如下圖所示。注意,一組寄存器同時控制一個GPIO Bank的所有引腳。
了解GPIO的控制器寄存器能幫助我們更深入的理解軟件中的相關庫函數,對編程有些幫助,當然,不了解也行,只要熟悉庫函數即可。
輸入/輸出控制寄存器:
| DATA_RO | data read only(RO大概是這兩個單詞的縮寫吧), GPIO引腳的值存儲在此寄存器中,無論GPIO被配置為輸入或輸出,都可以通過讀此寄存器得到GPIO引腳的值。 因為是只讀寄存器(對軟件來說),軟件向此寄存器的寫入操作將被忽略。 |
| DATA | 輸出數據寄存器,當GPIO被配置為輸出才起作用,此寄存器中的值就是輸出到引腳的值。 向此寄存器寫入就是在設置GPIO的輸出值, 讀此寄存器將返回GPIO前一時刻的輸出值,而不是現在的值。 |
| MASK_DATA_LSW | Mask Data Least Significant Words,輸出數據低16位掩碼寄存器,此寄存器只有低16位有效, 對應位為1表示DATA寄存器低16位中對應位的值可以更改, 若不為1,則表示DATA寄存器低16位中對應位保持原值 |
| MASK_DATA_MSW | Mask Data Most Significant Words,輸出數據高16位掩碼寄存器, 功能同MASK_DATA_LSW,只是它對應DATA寄存器高16位 |
| DIRM | Direction Memory,方向寄存器,默認為0表示輸入,設為1表示輸出 注意,即使DIRM為1,軟件也可以像輸入一樣去讀此引腳的電平。 |
| OEN | Output Enable,輸出使能寄存器, 僅當DIRM為0時有效,為1表示輸出使能, 為0表示輸出不使能,此時對應引腳上的值為三態值 |
中斷控制寄存器:
| INT_TYPE | Interrupt Type 中斷類型寄存器, 控制GPIO中斷是電平觸發還是邊緣觸發 |
| INT_POLARITY | Interrupt Polarity 中斷極性寄存器 控制GPIO中斷是低電平/下降沿有效,還是高電平/上升沿有效 |
| INT_ANY | Interrupt Any,雙邊沿寄存器, 僅當INT_TYPE為邊沿觸發時,此寄存器才有效,控制是否雙沿均可觸發中斷 |
| INT_STAT | Interrupt State,中斷狀態寄存器, 此寄存器的值會被與之相連的INT State D觸發器讀取 D觸發器存儲中斷狀態,軟件通過讀此D觸發器輸出來判斷中斷是否發生, 清除此D觸發器來清除中斷狀態 |
| INT_MASK | Interrupt Mask,中斷掩碼寄存器, 顯示當前哪些位被屏蔽,哪些位啟用 |
| INT_DIS | Interrupt Disable,中斷失效寄存器, 向該寄存器的任何位寫入 1 都會屏蔽該中斷信號。 從該寄存器讀取會返回不可預測的值 |
| INT_EN | Interrupt Enable,中斷使能寄存器 向該寄存器的任何位寫入 1,可以啟用/解除中斷信號的掩碼。 從該寄存器讀取將返回一個不不可預測的值 |
四. GPIO中斷設置與說明
GPIO中斷號為52。此中斷的優先級芯片內部已經固定好了,所以在軟件中配置GPIO中斷時,不需要指定GPIO中斷的優先級。
GPIO所有引腳都是共享一個中斷的,這意味著如果兩個引腳的中斷都使能了,如果不去讀取具體引腳的電平,軟件無法判斷中斷具體來自哪個引腳。
中斷觸發類型設置:
五. 在Vitis中配置GPIO
我自建了GPIO相關庫函數,將相關GPIO功能寫在一起。Xxk_PsGpio.c 與 Xxk_PsGpio.h。這里并沒有使用處理整個Bank的函數,因為實際應用時很好需要處理整個Bank,都是單獨處理某個Pin。
Xxk_PsGpio.h如下:
#ifndef XXK_PSGPIO_H #define XXK_PSGPIO_H// 包含xilinx庫中頭文件 #include "xil_printf.h" #include "xgpiops.h" #include "xscugic.h" #include "xil_exception.h"// 宏定義 #define __weak __attribute__((weak))// 與PS GPIO相關的宏定義 // 引腳宏定義 #define MIO12 12U#define EMIO0 54U #define EMIO1 55U #define EMIO2 56U #define EMIO3 57U #define EMIO4 58U// GPIO指的就是PS側的GPIO硬核,對于ZYNQ7,只有一個GPIO硬核 #define PSGPIO_INPUT 0U #define PSGPIO_OUTPUT 1U #define PSGPIO_OUTPUT_ENABLE 1U #define PSGPIO_OUTPUT_DISABLE 0U/* 中斷類型,已在xgpiops.h中定義,放在這里方便找到 #define XGPIOPS_IRQ_TYPE_EDGE_RISING 0x00U #define XGPIOPS_IRQ_TYPE_EDGE_FALLING 0x01U #define XGPIOPS_IRQ_TYPE_EDGE_BOTH 0x02U #define XGPIOPS_IRQ_TYPE_LEVEL_HIGH 0x03U #define XGPIOPS_IRQ_TYPE_LEVEL_LOW 0x04U */// PS GPIO相關函數 // 初始化psGpio int psGpioInti(XGpioPs *psGpioPtr, u16 psGpio_deviceId); //psGpioInti(&psGpio, XPAR_XGPIOPS_0_DEVICE_ID);// 設置psGpio某引腳為輸出并使能 void psGpio_SetPinOutputAndEnbale(const XGpioPs *psGpioPtr, u32 Pin); //psGpio_SetPinOutputAndEnbale(&psGpio, EMIO0);// 設置psGpio某引腳為輸入,輸入無需使能 void psGpio_SetPinInput(const XGpioPs *psGpioPtr, u32 Pin); //psGpio_SetPinInput(&psGpio, EMIO0);// 向psGpio某引腳寫入0或1 extern void XGpioPs_WritePin(const XGpioPs *psGpioPtr, u32 Pin, u32 Data); //XGpioPs_WritePin(&psGpio, EMIO0, 1);// 讀取psGpio某引腳的電平,得到0或1 extern u32 XGpioPs_ReadPin(const XGpioPs *psGpioPtr, u32 Pin); // EMIO0_pinData = XGpioPs_ReadPin(&psGpio, EMIO0);// 中斷相關函數 int scuGic_Inti(XScuGic *scuGicPtr, u16 scuGicID); //初始化中斷控制器 //scuGic_Inti(&scuGic, XPAR_XSCUTIMER_0_DEVICE_ID);void psGpio_PinIntr_SetAndEnable(XScuGic *scuGicPtr, XGpioPs *psGpioPtr, u32 psGpio_intrId,Xil_ExceptionHandler psGpio_Handler, u32 Pin, u8 IrqType); //psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR, // psGpio_Handler, EMIO0, XGPIOPS_IRQ_TYPE_EDGE_RISING);void psGpio_Handler(void *CallBackRef); // 需在main中重寫此弱函數#endifXxk_PsGpio.c 如下:
#include "Xxk_PsGpio.h"// 初始化PS側GPIO,包括MIO和EMIO int psGpioInti(XGpioPs *psGpioPtr, u16 psGpio_deviceId) {XGpioPs_Config *psGpio_configPtr;psGpio_configPtr = XGpioPs_LookupConfig(psGpio_deviceId); // 根據器件ID查找配置// 配置初始化配置int status;status = XGpioPs_CfgInitialize(psGpioPtr, psGpio_configPtr, psGpio_configPtr->BaseAddr);if (status != XST_SUCCESS){xil_printf("PsGpio %d Initialization Failed\r\n", psGpio_deviceId);return status;}status = XGpioPs_SelfTest(psGpioPtr);if (status != XST_SUCCESS){xil_printf("PsGpio %d SelfTest Failed\r\n", psGpio_deviceId);return status;}xil_printf("PsGpio %d Initialization Succeed\r\n", psGpio_deviceId);return status; }// 設置psGpio某引腳為輸入,輸入無需使能 void psGpio_SetPinInput(const XGpioPs *psGpioPtr, u32 Pin) {XGpioPs_SetDirectionPin(psGpioPtr, Pin, PSGPIO_INPUT); }// 設置psGpio某引腳為輸出并使能 void psGpio_SetPinOutputAndEnbale(const XGpioPs *psGpioPtr, u32 Pin) {XGpioPs_SetDirectionPin(psGpioPtr, Pin, PSGPIO_OUTPUT);XGpioPs_SetOutputEnablePin(psGpioPtr, Pin, PSGPIO_OUTPUT_ENABLE); }// 設置psGpio某引腳輸出不使能 void psGpio_SetPinOutputDisbale(const XGpioPs *psGpioPtr, u32 Pin) {XGpioPs_SetOutputEnablePin(psGpioPtr, Pin, PSGPIO_OUTPUT_DISABLE); }// PS GPIO PIN中斷使能 void psGpio_PinIntr_SetAndEnable(XScuGic *scuGicPtr, XGpioPs *psGpioPtr, u32 psGpio_intrId,Xil_ExceptionHandler psGpio_Handler, u32 Pin, u8 IrqType) {// 連接中斷ID與固定服務函數XScuGic_Connect(scuGicPtr, psGpio_intrId, (Xil_ExceptionHandler)psGpio_Handler, (void *)psGpioPtr);// 啟用對應中斷ID的中斷源XScuGic_Enable(scuGicPtr, psGpio_intrId);// 注意psGPIO中斷不需要設置中斷優先級// 設置psGPIO中斷類型XGpioPs_SetIntrTypePin(psGpioPtr, Pin, IrqType);// 在使能前先清除一次中斷,否則之前的中斷狀態可能殘留,導致燒寫后馬上進一次中斷XGpioPs_IntrClearPin(psGpioPtr, Pin);// 使能psGPIO中斷XGpioPs_IntrEnablePin(psGpioPtr, Pin); }// psGpio中斷處理弱函數 __weak void psGpio_Handler(void *CallBackRef) {XGpioPs *psGpioPtr = (XGpioPs *)CallBackRef;// psGpio_intrFlag = 1;if (XGpioPs_IntrGetStatusPin(psGpioPtr, MIO12) == TRUE){XGpioPs_IntrDisablePin(psGpioPtr, MIO12);XGpioPs_IntrClearPin(psGpioPtr, MIO12);xil_printf("This is psGpio_Handler - MIO12\r\n");}else if (XGpioPs_IntrGetStatusPin(psGpioPtr, EMIO4) == TRUE){XGpioPs_IntrDisablePin(psGpioPtr, EMIO4);XGpioPs_IntrClearPin(psGpioPtr, EMIO4);xil_printf("This is psGpio_Handler - EMIO4\r\n");} }// 初始化中斷控制器ScuGic int scuGic_Inti(XScuGic *scuGicPtr, u16 scuGicID) {// 打開系統的中斷處理功能Xil_ExceptionInit(); // 初始化異常句柄,只在很早版本的bsp中需要,此處為了兼容性保留Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler,scuGicPtr); // 為IRQ注冊中斷處理程序Xil_ExceptionEnable(); // 使能系統中斷功能XScuGic_Config *scuGicConfig;scuGicConfig = XScuGic_LookupConfig(scuGicID); // 根據器件ID查找配置int status;status = XScuGic_CfgInitialize(scuGicPtr, scuGicConfig, scuGicConfig->CpuBaseAddress);if (status != XST_SUCCESS){xil_printf("ScuGic Initialization Failed\r\n");return XST_FAILURE;}xil_printf("ScuGic Initialization Succeed\r\n");return status; }使用自建庫,GPIO輸入,輸出及中斷均有使用的main.c如下:
/*功能:控制EMIO0~2為輸出,EMIO4為輸入,EMIO3為中斷 */#include "Xxk_PsGpio.h" #include "sleep.h"// 全局變量 XGpioPs psGpio; XScuGic scuGic;int psGpio_EMIO_intrFlag = 0; int psGpio_MIO_intrFlag = 0;int main(int argc, char const *argv[]) {xil_printf("begin\r\n");// 初始化psGpiopsGpioInti(&psGpio, XPAR_XGPIOPS_0_DEVICE_ID);// 設置psGpio引腳方向psGpio_SetPinOutputAndEnbale(&psGpio, EMIO0);psGpio_SetPinOutputAndEnbale(&psGpio, EMIO1);psGpio_SetPinOutputAndEnbale(&psGpio, EMIO2);psGpio_SetPinInput(&psGpio, EMIO3);// 初始化中斷控制器scuGic_Inti(&scuGic, XPAR_XSCUTIMER_0_DEVICE_ID);// 設置并使能psGpio某引腳中斷psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR,psGpio_Handler, EMIO4, XGPIOPS_IRQ_TYPE_EDGE_FALLING);psGpio_PinIntr_SetAndEnable(&scuGic, &psGpio, XPAR_XGPIOPS_0_INTR,psGpio_Handler, MIO12, XGPIOPS_IRQ_TYPE_EDGE_FALLING);while (1){sleep(1);xil_printf("EMIO3 value: %d\r\n", XGpioPs_ReadPin(&psGpio, EMIO3));XGpioPs_WritePin(&psGpio, EMIO0, 1);XGpioPs_WritePin(&psGpio, EMIO1, 1);XGpioPs_WritePin(&psGpio, EMIO2, 0);if (psGpio_EMIO_intrFlag){xil_printf("This is psGpio_Handler - EMIO4\r\n");psGpio_EMIO_intrFlag = 0;XGpioPs_IntrEnablePin(&psGpio, EMIO4);}if (psGpio_MIO_intrFlag){xil_printf("This is psGpio_Handler - MIO12\r\n");psGpio_MIO_intrFlag = 0;XGpioPs_IntrEnablePin(&psGpio, MIO12);}sleep(1);XGpioPs_WritePin(&psGpio, EMIO0, 0);XGpioPs_WritePin(&psGpio, EMIO2, 1);}return 0; }void psGpio_Handler(void *CallBackRef) // 重寫弱函數 {XGpioPs *psGpioPtr = (XGpioPs *)CallBackRef;if (XGpioPs_IntrGetStatusPin(psGpioPtr, MIO12) == TRUE){psGpio_MIO_intrFlag = 1;XGpioPs_IntrDisablePin(psGpioPtr, MIO12);XGpioPs_IntrClearPin(psGpioPtr, MIO12);}if (XGpioPs_IntrGetStatusPin(psGpioPtr, EMIO4) == TRUE){psGpio_EMIO_intrFlag = 1;XGpioPs_IntrDisablePin(psGpioPtr, EMIO4);XGpioPs_IntrClearPin(psGpioPtr, EMIO4);} }六. GPIO的另一種實現方式 - AXI GPIO
本文介紹了如何使用MIO和EMIO實現GPIO,而對于ZYNQ來說,在PL中使用AXI GPIO IP核也可以實現GPIO功能,具體介紹參見我的另一篇博文。
傳送門:ZYNQ7000-AXI GPIO詳解
七. 總結
本文介紹了ZYNQ7000中GPIO的基本概念,GPIO是ZYNQ PS側最簡單的一種外設,它可以由MIO或EMIO實現。以32為界,118個GPIO被分為了4個Bank,每個Bank對應一組控制寄存器,然后簡單介紹了每個寄存器的名稱含義和功能,最后附上了通過自建GPIO函數庫,便捷操作GPIO的軟件代碼。
對GPIO理解還不深,如有疏漏,歡迎評論指出!
總結
以上是生活随笔為你收集整理的ZYNQ7000-GPIO详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ZYNQ7000-GPIO EMIO中断
- 下一篇: Python下载与安装教程