學會Zynq(8)PL中斷示例(SPI)

FPGADesigner發表於2019-03-18

PL中斷

雙核Zynq中共有20個PL到PS的中斷。IRQF[15:0]是16個共享外設中斷(SPI),可配選擇上升沿觸發或高電平觸發,中斷號為61-68和84-91。
在這裡插入圖片描述
另外還有4個私有外設中斷(PPI)IRQF2P[19:16],每個CPU都有一個來自PL的FIQ(快速中斷)和IRQ,其中斷敏感型別固定。
在這裡插入圖片描述
本文體會Zynq中PL到PS的SPI的使用方法。若設定為電平敏感,PL必須提供在中斷被確認後將其清除掉的機制。若設定為上升沿敏感,PL必須提供足夠寬的脈衝,讓GIC能夠捕獲。


硬體平臺搭建

本例中將連線到PL部分的兩個按鍵作為中斷請求源,從PL部分路由到PS的中斷介面。設定Zynq7處理系統,Interrupts中啟用PL到PS的中斷,選擇IRQ_F2P[15:0],啟用SPI。下面4個是PPI。
在這裡插入圖片描述
配置DDR和MIO電平,啟用UART,設計程式根據按鍵中斷串列埠列印相應資訊。配置完成後白板中的Zynq7 IP會出現IQR_F2P管腳,但位寬只有[0:0]。我們把中斷源以匯流排的形式連線到這個管腳後,執行Validate Design有效性檢查,會自動更新位寬。
在這裡插入圖片描述

將多路訊號拼接為匯流排可以用Concat IP核實現。這個IP可以設定埠數目和每個埠上的位寬,設為Auto可以完成自動檢測。In0在輸出匯流排的低位,In[Number of Ports - 1]在輸出匯流排的高位。
在這裡插入圖片描述
硬體連線如下圖所示。由於使用了PL部分的管腳,要新增XDC約束檔案,將sw0和sw1繫結到按鍵管腳。生成bit流檔案後,將硬體平臺匯出到SDK中。
在這裡插入圖片描述


SDK程式設計

新建工程,依次新增原始檔並編寫程式碼。init.h檔案程式碼如下:

#include <stdio.h>
#include "xscugic.h"
#include "xil_exception.h"

//---------------------------------------------------------
//        引數定義
//---------------------------------------------------------
#define SW1_INT_ID              61       //按鍵PL中斷1
#define SW2_INT_ID              62       //按鍵PL中斷2
#define INTC_DEVICE_ID          XPAR_PS7_SCUGIC_0_DEVICE_ID  //中斷裝置
#define INT_TYPE_RISING_EDGE    0x03     //上升沿敏感
#define INT_TYPE_HIGHLEVEL      0x01     //高電平敏感
#define INT_TYPE_MASK           0x03
#define INT_CFG0_OFFSET 0x00000C00

//---------------------------------------------------------
//        函式宣告
//---------------------------------------------------------
void IntcTypeSetup(XScuGic *InstancePtr, int intId, int intType);
int IntcInitFunction(u16 DeviceId);

init.c檔案的程式碼如下:

#include "init.h"

static XScuGic INTCInst;

//---------------------------------------------------------
//             中斷處理程式
//---------------------------------------------------------
static void SW_intr_Handler(void *param)
{
    int sw_id = (int)param;
    printf("SW%d int\n\r", sw_id);  //根據中斷請求源列印相關資訊
}

//---------------------------------------------------------
//         中斷敏感型別設定函式
//---------------------------------------------------------
void IntcTypeSetup(XScuGic *InstancePtr, int intId, int intType)
{
    int mask;

    intType &= INT_TYPE_MASK;
    mask = XScuGic_DistReadReg(InstancePtr, INT_CFG0_OFFSET+(intId/16)*4);
    mask &= ~(INT_TYPE_MASK << (intId%16)*2);
    mask |= intType << ((intId%16)*2);
    XScuGic_DistWriteReg(InstancePtr, INT_CFG0_OFFSET+(intId/16)*4, mask);
}

//---------------------------------------------------------
//             中斷初始化函式
//---------------------------------------------------------
int IntcInitFunction(u16 DeviceId)
{
    XScuGic_Config *IntcConfig;     //儲存中斷裝置的配置資訊
    int status;

    /*  初始化中斷控制器  */
    IntcConfig = XScuGic_LookupConfig(DeviceId);
    status = XScuGic_CfgInitialize(&INTCInst, IntcConfig, IntcConfig->CpuBaseAddress);
    if(status != XST_SUCCESS) return XST_FAILURE;   //檢測初始化狀態

    /*  設定並開啟中斷異常處理功能  */
    Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
           (Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst);
    Xil_ExceptionEnable();

    /*  為中斷設定中斷處理函式  */
    status = XScuGic_Connect(&INTCInst, SW1_INT_ID,
    		          (Xil_ExceptionHandler)SW_intr_Handler, (void *)1);
    if(status != XST_SUCCESS) return XST_FAILURE;

    status = XScuGic_Connect(&INTCInst, SW2_INT_ID,
                  (Xil_ExceptionHandler)SW_intr_Handler, (void *)2);
    if(status != XST_SUCCESS) return XST_FAILURE;

    /*  將中斷設定為上升沿敏感型別  */
    IntcTypeSetup(&INTCInst, SW1_INT_ID, INT_TYPE_RISING_EDGE);
    IntcTypeSetup(&INTCInst, SW2_INT_ID, INT_TYPE_RISING_EDGE);

    /*  使能中斷  */
    XScuGic_Enable(&INTCInst, SW1_INT_ID);
    XScuGic_Enable(&INTCInst, SW2_INT_ID);

    return XST_SUCCESS;
}

main.c檔案的程式碼如下:

#include "init.h"

int main(void)
{
    //初始化中斷
	IntcInitFunction(INTC_DEVICE_ID);

    while(1);
    return 0;
}

SDK Terminal中新增串列埠,下載程式。按下按鍵終端會顯示相應的資訊。由於PL中沒有對按鍵做消抖處理,直接作為中斷請求源,有時會“過於靈敏”。


相關函式使用說明

我們只使用了兩個PL中斷,在硬體平臺中可以看到,自動從低bit開始分配,即使用IRQ_F2P[1:0],對應的中斷號是62-61。為了程式設計方便在init.h中對這兩個中斷號做巨集定義。

1.中斷控制器例項

init.c定義了一個XScuGic型別的結構體變數,設計者需要為系統中的每個中斷裝置分配一個此型別的變數。其它中斷相關的API需要指向此變數的指標作為傳入引數。

中斷初始化函式中又定義了一個指向XScuGic_Config型別的結構體變數的指標,這個結構體包含了中斷裝置的配置資訊。兩個結構體的原型如下(注意XScuGic_Config也是XscuGic的一個成員):

typedef struct
{
	u16 DeviceId;		/**< Unique ID  of device */
	u32 CpuBaseAddress;	/**< CPU Interface Register base address */
	u32 DistBaseAddress;	/**< Distributor Register base address */
	XScuGic_VectorTableEntry HandlerTable[XSCUGIC_MAX_NUM_INTR_INPUTS];/**<
				 Vector table of interrupt handlers */
} XScuGic_Config;
typedef struct
{
	XScuGic_Config *Config;  /**< Configuration table entry */
	u32 IsReady;		 /**< Device is initialized and ready */
	u32 UnhandledInterrupts; /**< Intc Statistics */
} XScuGic;

2.中斷控制器初始化

中斷初始化函式中使用XScuGic_LookupConfig()函式和XScuGic_CfgInitialize()函式完成中斷控制器的初始化。其實上面這部分處理和GPIO部分的一樣,只是處理物件從“GPIO裝置”換成了“中斷裝置”。

XScuGic_LookupConfig()函式的原型介面如下。傳入引數為裝置ID,返回的是一個XScuGic_Config型別的指標。如果存在該中斷裝置,則返回該配置資訊的列表;未找到則返回NULL。

XScuGic_Config *XScuGic_LookupConfig(u16 DeviceId){}

XScuGic_CfgInitialize()函式的原型介面如下。該函式用來初始化傳入的XScuGic型別的變數(可稱作中斷控制器例項),初始化該結構體中的成員欄位。第二個引數便是用於初始化中斷控制器例項的配置資訊表。第三個引數EffectiveAddr是虛擬記憶體地址空間中的裝置基地址,如果沒有使用地址轉換功能,傳入Config->BaseAddress即可。

s32  XScuGic_CfgInitialize(XScuGic *InstancePtr, 
XScuGic_Config *ConfigPtr, u32 EffectiveAddr) {}

3.中斷異常處理

中斷控制器初始化完成後,使用Xil_ExceptionRegisterHandler()和Xil_ExceptionEnable()兩個函式設定中斷異常處理功能。
Xil_ExceptionRegisterHandler()為異常註冊處理函式。處理器執行過程中遇到該異常,便會呼叫設定的處理函式。該函式原型如下,第一個為異常的ID號,第二個為設定的處理函式,第三個是處理函式被呼叫時傳給它的引數。

void Xil_ExceptionRegisterHandler(u32 Exception_id,
				    Xil_ExceptionHandler Handler,  void *Data)

Xil_ExceptionEnable()啟用IRQ異常,沒有引數和返回值。異常處理機制並不是中斷獨有的,在xil_exception.h檔案中有所有異常情況的ID列表。下面貼出init.c中的程式碼,更進一步理解:

Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
       (Xil_ExceptionHandler)XScuGic_InterruptHandler, &INTCInst);

XIL_EXCEPTION_ID_INT表示IRQ中斷異常。XScuGic_InterruptHandler是庫中提供的函式,它可以解析那些中斷時活躍的和啟用的,並呼叫相應的中斷處理程式,先服務優先順序最高的中斷。該函式是保證中斷系統正常執行的基礎,其原型如下:

void XScuGic_InterruptHandler(XScuGic *InstancePtr)

它需要一個指向XscuGic型別的指標引數。在呼叫Xil_ExceptionRegisterHandler()時通過設定第三個引數便可完成引數傳入。

4.中斷源設定

之後使用XScuGic_Connect()函式將中斷源和中斷處理程式聯絡起來,當中斷髮生時會呼叫設定的中斷處理程式。函式原型如下,Int_ID是中斷源對應的ID號;Handler是中斷處理程式;最後一個void*型別的CallBackRef在中斷處理程式被呼叫時會作為引數傳入。

s32  XScuGic_Connect(XScuGic *InstancePtr, u32 Int_Id,
             Xil_InterruptHandler Handler, void *CallBackRef)

再之後便是用自定義的函式完成中斷敏感型別的設定,主要是對中斷相關暫存器的配置。最後使用XScuGic_Enable()函式啟用中斷源。函式原型如下,Int_Id為中斷源對應的ID號。

void XScuGic_Enable(XScuGic *InstancePtr, u32 Int_Id)

5.中斷處理程式

static void SW_intr_Handler(void *param)
{
    int sw_id = (int)param;
    printf("SW%d int\n\r", sw_id);  //根據中斷請求源列印相關資訊
}

中斷處理程式只能傳入void*型別的引數,因此在內部使用時需要做適當的型別轉換。我們通過兩個按鍵中斷傳入不同的引數,便可在一箇中斷處理程式中識別出到底是哪個中斷源發出了中斷請求。

相關文章