學會Zynq(4)GPIO中MIO的使用方法

FPGADesigner發表於2019-03-10

本文將介紹PS部分GPIO中MIO的使用。本文先通過一個控制LED閃爍的例項體會MIO的用法,學習GPIO相關結構體與API函式的使用;然後再系統講解GPIO的相關概念。


Zynq設計與程式碼詳解

與第1篇相似,建立一個工程,配置好Zynq的時鐘和DDR後,需要在MIO Configuration->I/O Peripherals->GPIO中選中GPIO MIO。一般設計中配置的UART、乙太網等外設會佔用一部分MIO,這裡列表中會顯示剩餘可用的MIO。
在這裡插入圖片描述
配置完成後按流程匯入到SDK中。SDK中新建一個空白工程,src目錄上右鍵->New->File,彈出視窗中填寫帶字尾的完整檔名,如“main.c”。
在這裡插入圖片描述
開發板的LED與MIO7相連,控制其閃爍的程式碼清單如下:

#include "xgpiops.h"
#include "sleep.h"

XGpioPs GpioPs_Init()
{
	XGpioPs_Config* GpioConfigPtr;
	XGpioPs psGpioInstancePtr;

	GpioConfigPtr = XGpioPs_LookupConfig(XPAR_PS7_GPIO_0_DEVICE_ID);
	XGpioPs_CfgInitialize(&psGpioInstancePtr, GpioConfigPtr, GpioConfigPtr->BaseAddr);

	return psGpioInstancePtr;
}

int main()
{
	static XGpioPs psGpioInstancePtr;
	int iPinNumber = 7;       //MIO7,與LED相連
	u32 uPinDirection = 0x1; //1表示輸出,0表示輸入

	psGpioInstancePtr = GpioPs_Init(psGpioInstancePtr);   //GPIO初始化
	XGpioPs_SetDirectionPin(&psGpioInstancePtr, iPinNumber, uPinDirection); //MIO7配置為輸出
	XGpioPs_SetOutputEnablePin(&psGpioInstancePtr, iPinNumber, 1);   //使能MIO7

	while(1)
	{
		XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 1); //點亮
		sleep(1); //延時
		XGpioPs_WritePin(&psGpioInstancePtr, iPinNumber, 0); //熄滅
		sleep(1); //延時
	}

	return 0;
}

程式和STM32很相似,由於這是第一次接觸Zynq的應用程式程式碼,我們深入瞭解一下其中的細節。程式中有兩個綠色的結構體,XGpioPs_Config儲存了器件的配置資訊;使用者需要為系統中的GPIO裝置分配一個XGpioPs型別的變數,GPIO相關的API函式都需要一個指向該變數的指標。SDK中滑鼠移動到結構體上即可檢視其原型:

typedef struct {
	u16 DeviceId;		/* 每個裝置都有一個單獨的ID */
	u32 BaseAddr;		/* GPIO的暫存器基地址 */
} XGpioPs_Config;

typedef struct {
	XGpioPs_Config GpioConfig; /* 裝置配置 */
	u32 IsReady;			      /* 裝置是否例項化與準備 */
	XGpioPs_Handler Handler;	  /* 所有bank的狀態處理 */
	void *CallBackRef; 		  /* bank處理的回撥參考 */
	u32 Platform;			      /* 平臺資料 */
	u32 MaxPinNum;			      /* GPIO的最大管教號 */
	u8 MaxBanks;			      /* GPIO中的最大bank號 */
} XGpioPs;

GPIO初始化函式中首先用到了XGpioPs_LookupConfig函式,右鍵->Open Declaration即可檢視函式原型。檢視原始檔我們可以知道,這個函式是根據唯一的裝置ID來查詢裝置配置,其輸入引數為要查詢的裝置ID號,返回值為一個XGpioPs_Config型別的指標,如果沒有找到則返回NULL。上面程式中裝置ID使用了巨集定義XPAR_PS7_GPIO_0_DEVICE_ID,同樣可以通過Open Declaration檢視源定義。

接下來使用XGpioPs_CfgInitialize函式完成對XGpioPs裝置的初始化,第一個引數為待例項化的XGpioPs裝置的指標(所以上面程式中加了取地址符&);第二個引數為指向XGpioPs裝置的配置結構體;第三個地址為裝置在虛擬記憶體空間中的基地址,上面程式中通過訪問XGpioPs_Config結構體的成員來獲取基地址。

//兩個函式的介面
XGpioPs_Config *XGpioPs_LookupConfig(u16 DeviceId){}

s32 XGpioPs_CfgInitialize(XGpioPs *InstancePtr, XGpioPs_Config *ConfigPtr, u32 EffectiveAddr){}

主程式中先定義了int型的要操作的MIO管腳號,7即表示MIO7。主程式中初始化GPIO後,先用XGpioPs_SetDirectionPin函式設定指定管腳的方向,第一個引數為指向XGpioPs裝置的指標;第二個引數為操作的管腳號;第三個引數位要設定的方向,0表示輸入,1表示輸出。

接下來使用XGpioPs_SetOutputEnablePin函式設定特定管腳的輸出使能,前兩個引數的含義與XGpioPs_SetDirectionPin函式相同;第三個引數為0表示禁止輸出使能,為1表示啟用輸出使能。

//兩個函式的介面
void XGpioPs_SetDirectionPin(XGpioPs *InstancePtr, u32 Pin, u32 Direction){}

void XGpioPs_SetOutputEnablePin(XGpioPs *InstancePtr, u32 Pin, u32 OpEnable){}

while迴圈中使用XGpioPs_WritePin函式向管腳寫資料,前兩個引數的含義與上面兩個函式相同;第三個引數為要寫入的資料,0或1。while中還用到sleep延遲函式,該函式的延時單位為秒,且傳入引數為int型,因此不要希望能實現延時0.5秒、1.5秒這樣的功能。

//兩個函式的介面
void XGpioPs_WritePin(XGpioPs *InstancePtr, u32 Pin, u32 Data){}

unsigned sleep(unsigned int seconds) {}

這樣我們就對GPIO相關操作和相關API函式有了清楚的認識。SDK在儲存檔案時會自動完成構建操作(編譯與連結)。進行Debug除錯(或直接執行),可以觀察到與PS的MIO7相連的LED燈以1s頻率閃爍,功能正確。


GPIO介紹

首先要清楚:GPIO訊號≠MIO或EMIO,這在Vivado中是兩個概念,MIO和EMIO只是GPIO訊號的兩種介面,很多初學者確把這些概念混淆。GPIO即General Purpose I/O,Zynq-7000中處理器的GPIO具有如下特性:

  • 54個GPIO訊號通過MIO與裝置管腳直接相連,且支援三態輸出;
  • 192個GPIO訊號通過EMIO介面連線PS與PL部分,64個為PL到PS的輸入,128個為PS到PL的輸出;
  • 每個GPIO可以單個或以組為單位進行動態程式設計;
  • 支援使能、按bit寫資料、按bank寫資料、輸出使能、方向控制功能;
  • 每個GPIO都可配置為中斷敏感,支援原始與遮蔽中斷的狀態讀取,敏感源支援電平敏感(高電平或低電平)、邊沿敏感(上升沿、下降沿或兩者同時)。

GPIO按相相關聯的介面訊號分為4個bank。GPIO控制和狀態暫存器在記憶體中對映的基地址為)0xE000_A000。GPIO的模組框圖如下所示,其中bank0和bank1是通過MIO直接與Zynq管腳相連的部分。另外注意7z007單核與7z010雙核CLG225晶片的MIO只有32個,而非其它型號的54個。
在這裡插入圖片描述
通過軟體對GPIO或bank的控制,實質上就是對一系列記憶體對映暫存器的控制,主要是slcr.MIO_PIN_xx暫存器,只不過MIO和EMIO之間會有一些差別。


器件管腳的GPIO控制

軟體可以將GPIO配置為輸出或輸入模式。很多時候,應用程式會需要同時切換多個GPIO。同時切換的GPIO必須來自同一個bank的高16bits或低16bits,使用一個儲存指令完成對MASK_DATA暫存器的寫入。從上圖可以注意到,由於MIO只有54個,因此bank1只有22位。

GPIO通道的結構框圖如下:
在這裡插入圖片描述
與MIO相關的(bank0和bank1)bank控制暫存器如下,EMIO在下一篇講述:

  • DATA_RO:無論輸入、輸出模式,軟體都可通過這個暫存器來獲取器件管腳上的值。不能向該暫存器寫值。但如果MIO沒有配置為使能該GPIO管腳,從DATA_RO中讀到的值將是無法預料的。
  • DATA:當GPIO訊號配置為輸出時,該暫存器控制輸出的值,一次寫入一個bank所有的32bits。讀取該暫存器可以得到前一個寫入該暫存器的值。
  • MASK_DATA_LSW:控制bank的低16bits。
  • MASK_DATA_MSW:控制bank的高16bits。
  • DIRM:選擇管腳方向為輸入或輸出。實際上GPIO的輸入邏輯總是使能狀態,DIRM控制的其實是輸出驅動器的使能狀態。當DIRM[x]==0時,禁用輸出驅動器,則GPIO處於輸入模式。
  • OEN:當GPIO配置為輸出時,OEN控制輸出是否使能。當OEN[x]==0禁用輸出時,管腳處於三態狀態。

54個與MIO相連的GPIO訊號中有兩個特例,bank0的bits[7]和bits[8]。在Vivado中配置GPIO時我們會發現,與這兩個訊號相連的MIO7和MIO8只能作為輸出out,而其它MIO都可以作為雙向inout。
在這裡插入圖片描述
其實與這兩個訊號相關的管腳在復位期間要用來控制I/O緩衝器的電壓模式,稱作VMODE管腳。這兩個管腳必須根據合適的電壓模式,由外部系統驅動,而不能有其它系統邏輯驅動,因此不能作為輸出。但是系統啟動之後,已經讀取了電壓模式,系統便可以將MIO7和MIO8作為輸出使用。


總結

最後還是要強調,GPIO才是與處理器直接相關的,分為bank0~bank3四個組。MIO和EMIO只是GPIO訊號與外界連線的介面,bank0、bank1通過MIO相連,bank2、bank3通過EMIO相連。哪怕我們在實際使用過程中就是把MIO當作GPIO來看待,沒有出現任何問題,但我認為學習應該保持嚴謹的態度,搞清這些基礎的概念,也是專業素養的一種體現。

相關文章