ZYNQ裸板簡單實戰-中斷篇


前言

  中斷對於單片機過來的我們來說,相對也算比較熟悉了,還是嚴謹一點從頭開始說吧。中斷是什么?是一種當滿足要求的突發事件發生時通知處理器進行處理的信號。中斷可以由硬件處理單元和外部設備產生,也可以由軟件本身產生。對硬件來說,中斷信號是一個由某個處理單元產生的異步信號,用來引起處理器的注意。對軟件來說,中斷還是一種異步事件,用來通知處理器需要改變代碼的執行,當然,輪詢所產生的中斷的過程是同步的。
  當處理器收到中斷,它會停下當前正在做的任務,然后跳轉到需要處理的地方去。這和輪詢的方式是相反的,輪詢是由軟件同步獲取設備的狀態。在中斷方式中,不需要由處理器不斷地輪詢設備的 I/O 端口來查看是否需要處理,設備本身會中斷處理器。
(1)ARM體系中,在存儲地址的低位,固化了一個32字節的硬件中斷向量表。
(2)異常中斷發生時,程序計數器PC所指的位置不同,異常中斷就不同。中斷結束后,中斷不同,返回地址也不同。但是,對於系統復位中斷,不需要返回,因為整個應用系統就是從復位中斷中開始的。
在這里插入圖片描述
數據訪問終止:數據訪問的地址不存在,或者當前地址不允許訪問。
快速中斷請求:外部引腳的快速中斷請求,比外部中斷請求等級高,但是一般外設的中斷請求使用外部中斷請求。
指令預取終止:預取指令的地址不存在,或者當前地址不允許訪問。
未定義的指令:ARM或協處理器認為當前指令未定義。

ARM中斷流程:

在這里插入圖片描述

  首先在主程序中發生IRQ中斷請求,程序跳到中斷向量表找IRQ中斷對應的解析程序地址,然后再跳到中斷解析程序,進而執行中斷程序。

zynq的中斷

那具體到zynq的中斷呢,先簡單概括下吧(起碼先看見這些縮寫起碼不再頭疼)

  1. Zynq的中斷類型有:
    軟件中斷(Software Generated Interrupt, SGI,中斷號0-15)(16–26 reserved)
    私有外設中斷(Private Peripheral Interrupt, PPI,中斷號27-31),
    共享外設中斷(Shared Peripheral Interrupt, SPI,中斷號32-95).
  2. 私有外設中斷(PPI):每個CPU都有一組PPI,包括全局定時器、私有看門狗定時器、私有定時器和來自PL的FIQ/IRQ.
  3. 軟件中斷(SGI)被路由到一個或者兩個CPU上,通過寫ICDSGIR寄存器產生SGI.
  4. 共享外設中斷(SPI)由PS和PL上的各種I/O控制器和存儲器控制器產生,這些中斷信號被路由的CPU.
  5. 通用中斷控制器(GIC)是核心資源,用於集中管理從PS和PL產生的中斷信號的資源集合。控制器可以使能、關使能、屏蔽中斷源和改變中斷源的優先級,並且會將中斷送到對應的CPU中,CPU通過私有總線訪問這些寄存器。
  6. 中斷控制器(ICC,Interrupt Controller CPU)和中斷控制器分配器(ICD, Interrupt Controller Distributor)是GIC寄存器子集。
  7. (外部)中斷請求(IRQ)、快速中斷請求(FIQ)
      接下來詳細解釋一下,中斷(主要是硬件中斷)可以進一步被分類為以下幾種類型:
    • 可屏蔽中斷( Maskable Interrupts,IRQ)—可通過在中斷屏蔽寄存器中設定位掩碼來關閉。觸發可屏蔽中斷的事件源不總是重要的。程序設計人員需要決定該事件是否應該導致程序跳到所需處理的地方去。使用可屏蔽中斷的設備包括定時器、比較器和 ADC。• 不可屏蔽中斷( Non-Maskable Interrupts,NMI)—無法通過在中斷屏蔽寄存器中設定位掩碼來關閉。這些是不可忽視的中斷。 NMI 的事件包括上電、外部重啟(用實際的按鈕)和嚴重的設備失效。
    • 處理器間中斷( Inter-Processor Interrupts,IPI)—在多處理器系統中,一個處理器可能需要中斷另一個處理器的操作。在這種情況下,就會產生一個 IPI,以便於處理器間通信或同步。Zynq 芯片的 PS 部分是基於使用雙核 Cortex-A9 處理器和 GIC pl390 中斷控制器的 ARM 架構。中斷結構與 CPU 緊密鏈接,並接受來自 I/O 外設( IOP)和可編程邏輯( PL)的中斷。中斷控制器架構如下圖所示(感謝原子將官方手冊一些不太好理解的做了個相對非常清晰的框圖和漢化):
    在這里插入圖片描述
    從圖中可以看到, CPU 接收的中斷來源有三種,分別是私有外設中斷( private peripheral interrupts, PPI)、軟件生成的中斷( software generated interrupts, SGI)和共享外設中斷( shared peripheral interrupts、 SPI)。每個 CPU 都有一組私有外設中斷,使用存儲寄存器進行私有訪問。 PPI 包括全局定時器、專用看門狗定時器( AWDT)、專用定時器和來自 PL 的 FIQ/IRQ。軟件生成的中斷通過 SGI 分配(派)器分配給一個或兩個 CPU。共享外設中斷由 PS 和 PL 中的各種 I/O 和存儲控制器生成。它們被路由到任一個或兩個 CPU,來自 PS 外設的 SPI 中斷也可以路由到 PL。下面我們從下圖的系統級中斷環境來進一步了解中斷
    在這里插入圖片描述
      首先我們來看通用中斷控制器。通用中斷控制器是一個用於集中管理從 PS 和 PL 發送到 CPU 的中斷,啟用、禁用、屏蔽和優先化中斷源的處理中心,將具有最高優先級的中斷源分配給各個 CPU 之前集中所有中斷源,並在 CPU 接口接受下一個中斷時以編程方式將它們發送到選定的 CPU。此外,控制器還支持用於實現安全感知系統的安全擴展。該控制器基於非矢量化的 ARM 通用中斷控制器架構版本 1.0( GIC v1)。GIC 寄存器通過 CPU 私有總線訪問寄存器,以避免臨時阻塞或互連中的瓶頸,從而實現快速讀/寫響應。GIC 確保針對多個 CPU 的中斷一次只能由一個 CPU 占用。所有中斷源都由唯一的中斷 ID 號標識,對應有它自己的可配置優先級和目標 CPU 列表。接下來我們依次來看軟件生成中斷、 CPU 私有外設中斷和共享外設中斷。每個 CPU 都可以使用軟件生成的中斷中斷自身、另一個 CPU 或同時中斷兩個 CPU。有 16 個軟件生成中斷。向軟件產生的中斷寄存器( Software Generated Interrupts Register, ICDSGIR)寫入SGI 中斷編號並指定目標 CPU(或兩個 CPU),就產生了一個 SGI。該寫操作通過 CPU 自己的專用(私有)總線進行。每個 CPU 都有自己的一組 SGI 寄存器,用於生成 16 個軟件生成的中斷中的一個或多個。中斷的清除是通過讀取中斷確認寄存器( Interrupt Acknowledge Register, ICCIAR)或向中斷掛起清除寄存器( Interrupt Clear-Pending Register, ICDICPR)對應的位寫入1來實現的。所有的 SGI 都是邊緣觸發的,且其敏感性類型是固定的,不能修改。只讀的 ICDICFR0 寄存器指定了所有 16 個 SGI 的靈敏度類型。
    在這里插入圖片描述
      翻譯下英文部分:每個 CPU 有自己專用的一組 16 個中斷源,可以被路由到 16 個公共中斷目標,每個目標可以是一個或多個 CPU。
      每個 CPU 核連接到了一個有五個外設中斷的私有組上,這五個外設中斷見下表。 PPI 的敏感類型是固定的,不能改變。需要注意的是:來自 PL 的快速中斷( FIQ)信號和中斷( IRQ)信號在發送給中斷控制器之前,會在傳輸給 PS 的時候被反轉。因此,這些信號因此在 PL 內低電平有效,在 PS-PL 接口處高電平有效
    在這里插入圖片描述
      來自各種模塊的大約 60 個中斷的組可以被路由到 PL 或 CPU 中的一個或兩個,這 60 個中斷見下表。那些目標為 CPU 的中斷的優先級和中斷的接收情況是由中斷控制器管理的。除 IRQ# 61 至# 68 和# 84 至# 91 外,所有中斷靈敏度類型均由請求源固定,無法更改。必須對 GIC 進行編程以適應這種情況。 Boot ROM不編程這些寄存器。因此, SDK 設備驅動程序必須對 GIC 進行編程以適應這些敏感類型。對於電平敏感類型的中斷,請求源必須為中斷處理程序提供一種機制,以便在確認中斷后清除中斷。此要求適用於具有高電平敏感類型的任何 IRQF2P [n](來自 PL)。對於上升沿敏感的中斷,請求源必須提供足夠寬的脈沖以便 GIC 捕獲。這通常至少為 2 個 CPU_2x3x 周期。此要求適用於具有上升沿靈敏度類型的任何 IRQF2P [n](來自 PL)。
    在這里插入圖片描述
    在這里插入圖片描述
      了解了軟件生成中斷 SGI、 CPU 私有外設中斷 PPI 和共享外設中斷 SPI 后,我們來看下中斷優先級定序。所有的中斷請求,無論是 PPI、 SGI 還是 SPI,都分配了一個唯一的 ID 編號,以用於中斷控制器的仲裁。中斷分派(配)器保存每個 CPU 的中斷掛起列表,並從中選擇優先級最高的中斷,然后把它發送到 CPU接口。如果具有相同優先級的兩個中斷同時到達,具有最低中斷 ID 的會首先被發送。每個 CPU 都存在着優先級定序邏輯,所以對最高優先級中斷的選擇是每個 CPU 各自進行的。中斷分配器具有中斷、處理器和活躍信息的中央列表,並負責觸發 CPU 的軟件中斷。為了給每個處理器提供單獨的副本, SGI 和 PPI 分派器寄存器是分組的。硬件確保針對多個 CPU 的中斷同一時間只能被一個 CPU 獲取。在發送掛起的最高優先級的中斷給 CPU 接口后,中斷分配器會從該 CPU 收到中斷已被確認的消息,這樣它就可以改變對應的中斷的狀態。只有確認中斷的 CPU 才能結束該中斷。

一.MIO作GPIO中斷(填坑GPIO篇)

以上我們大概了解了 ZYNQ 的中斷。下面我們來看作為 GPIO 的 MIO 的中斷。
在這里插入圖片描述
左邊有 7 個寄存器,說明如下:
INT_MASK:這個寄存器是只讀的,顯示哪些位當前被屏蔽,哪些位未被屏蔽/啟用。
INT_EN:向該寄存器的任何位寫入 1,可以啟用/解除中斷信號的掩碼。從該寄存器讀取將返回一個不可預測的值。
INT_DIS:向該寄存器的任何位寫入 1 都會屏蔽該中斷信號。從該寄存器讀取會返回不可預測的值。
INT_STAT:該寄存器顯示是否發生了中斷事件。將 1 寫入該寄存器中的某個位可清除該位的中斷狀態。將 0 寫入該寄存器中的某個位將被忽略。
INT_TYPE:該寄存器控制中斷是邊沿敏感還是電平敏感。
INT_POLARITY:該寄存器控制中斷是低電平有效還是高電平有效(或下降沿敏感或上升沿敏感)。
INT _ANY:如果 INT_TYPE 設置為邊沿敏感,則該寄存器在上升沿和下降沿都會啟用中斷事件。如果INT_TYPE 設置為電平敏感,則忽略該寄存器。
從 INT_TYPE、 INT_POLARITY 和 INT _ANY 寄存器我們可以看到中斷觸發方式可以是上升沿,下降沿,邊沿,低電平或高電平。

  從圖中我們可以看到, INT_TYPE、 INT_POLARITY 和 INT _ANY 控制監視 GPIO 輸入信號的中斷檢測邏輯。如果檢測到中斷,中斷檢測邏輯將 GPIO 的 INT_STAT 狀態設置為真。如果中斷未屏蔽,則中斷傳輸到一個或電路(圖中未畫出)。該或電路將四個 BANK 中所有 GPIO 的所有中斷組合成一個輸出( IRQID# 52)到中斷控制器。如果中斷被禁止(屏蔽),則 INT_STAT 狀態將保持直到被清除,但它不會傳輸到中斷控制器,除非稍后寫入 INT_EN 以禁用屏蔽。由於所有 GPIO 共享相同的中斷,因此軟件必須同時考慮INT_MASK 和 INT_STAT 以確定哪個 GPIO 導致中斷
  通過向 INT_EN 和 INT_DIS 寄存器寫入 1 來控制中斷屏蔽狀態。向 INT_EN 寄存器寫入 1 將禁用屏蔽,允許活動中斷傳輸到中斷控制器。將 1 寫入 INT_DIS 寄存器可啟用屏蔽。可以使用 INT_MASK 寄存器讀取中斷屏蔽的狀態。如果 GPIO 中斷是邊沿觸發的,則 INT 狀態由檢測邏輯鎖存。通過向 INT_STAT 寄存器寫入 1 來清除INT 鎖存器。對於電平觸發的中斷,必須清零 GPIO 中斷輸入源,以清除中斷信號。或者,軟件可以使用INT_DIS 寄存器屏蔽該輸入。可以通過讀取 INT_STAT 和 INT_MASK 寄存器來推斷進入中斷控制器的中斷信號的狀態。如果INT_STAT = 1 且 INT_MASK = 0,則該中斷信號有效。

下面上實例,工程中我們用AXIDMA來搬移數據(詳細使用日常在AXIDMA篇),由於16個軟件中斷已經不夠用了,所以我們將中斷掛在GPIO上(EMIO)來判斷是否有輸入:

#define INTC_DEVICE_ID        XPAR_SCUGIC_SINGLE_DEVICE_ID//通用中斷控制器 ID
#define INTC		XScuGic 
#define INTC_HANDLER	XScuGic_InterruptHandler
static XGpioPs Gpio; /* The Instance of the GPIO Driver */
static INTC Intc;	/*通用中斷控制器驅動實例  */
*/
int SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio,
				u16 GpioIntrId) /*官方GPIO例程調用,嗯xilinx 牛逼!  */
{
	int Status;

	XScuGic_Config *IntcConfig; /* Instance of the interrupt controller */

	Xil_ExceptionInit();/* 該函數是一個用於初始化異常處理程序的通用API跨所有支持的arm處理器這個函數不做任何事情但是,它仍然存在,以確保向后兼容性問題 */
	/*
	 * 初始化中斷控制器
	 */
	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
	if (NULL == IntcConfig) {
		return XST_FAILURE;
	}

	Status = XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,
					IntcConfig->CpuBaseAddress);
	if (Status != XST_SUCCESS) {
		return XST_FAILURE;
	}
	/*
	 * 為特定的異常注冊一個處理程序。這個處理器正在當處理器遇到指定的異常時調用。
	 * Connect the interrupt controller interrupt handler to the hardware
	 * interrupt handling logic in the processor.
	 */
	 */
	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
				(Xil_ExceptionHandler)XScuGic_InterruptHandler,
				GicInstancePtr);
	/*
	 * Connect the device driver handler that will be called when an
	 * interrupt for the device occurs, the handler defined above performs
	 * the specific interrupt processing for the device.
	 */
	Status = XScuGic_Connect(GicInstancePtr, GpioIntrId,
				(Xil_ExceptionHandler)XGpioPs_IntrHandler,
				(void *)Gpio);
	if (Status != XST_SUCCESS) {
		return Status;
	}
	/* 下面就是需要根據這邊的實際情況來修改的部分了
Enable falling edge interrupts for all the pins in bank 0. */
	XGpioPs_SetIntrType(Gpio, 2, 0x00, 0xFFFFFFFF, 0x00);
	/* Set the handler for gpio interrupts. */
	XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler);
	/* Enable the GPIO interrupts of Bank 2. */
	XGpioPs_IntrEnable(Gpio, 2, 0xFF);
	/* Enable the interrupt for the GPIO device. */
	XScuGic_Enable(GicInstancePtr, GpioIntrId);
	/* Enable interrupts in the Processor. */
	Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);
	return XST_SUCCESS;
}

static void IntrHandler(void *CallBackRef, u32 Bank, u32 Status)
/*
	 * 好家伙也是直接從官方例程搬了來改,讀bank2部分,哪個bit來了就處理那一部分
	 */
{
	XGpioPs *Gpio = (XGpioPs *)CallBackRef;
	u32 DataRead = 0;;
	
	//DataRead = XGpioPs_ReadPin(Gpio, Input_Pin);
	DataRead = XGpioPs_Read(Gpio, 2); //直接讀bank2,判斷哪一位來了中斷
	if ((DataRead & 0x1) == 0x01)
	{
		TxIntrHandler(&AxiDma);
	}
	if((DataRead & 0x2) == 0x02)
	{
		RxIntrHandler(&AxiDma);
	}
	if ((DataRead & 0x4) == 0x04)
	{
		TxIntrHandler(&AxiDma1);
	}
	if((DataRead & 0x8) == 0x08)
	{
		RxIntrHandler(&AxiDma1);
	}
	if ((DataRead & 0x10) == 0x010)
	{
		TxIntrHandler(&AxiDma2);
	}
	if((DataRead & 0x20) == 0x20)
	{
		RxIntrHandler(&AxiDma2);
	}
	if ((DataRead & 0x40) == 0x40)
	{
		TxIntrHandler(&AxiDma3);
	}
	if((DataRead & 0x80) == 0x080)
	{
		RxIntrHandler(&AxiDma3);
	}
	else
	{

	}
}

二.AXI GPIO作GPIO中斷

  (填坑GPIO篇,由於這部分還沒實際用到,邏輯大哥沒給配的時候,只能理論上做個分析了,原子真香!比我這東拼西湊的不論整體設計還是清晰地注釋那高了不止一個層次,反復觀摩學習)
  直接嫖一波代碼,寫的簡潔易懂,是用AXIGPIO做了個按鍵控制LED的功能

1 #include "stdio.h"
2 #include "xparameters.h"
3 #include "xgpiops.h"
4 #include "xgpio.h"
5 #include "xscugic.h"
6 #include "xil_exception.h"
7 #include "xil_printf.h"
8 #include "sleep.h"
9
10 //宏定義
11 #define SCUGIC_ID XPAR_SCUGIC_0_DEVICE_ID //中斷控制器 ID
12 #define GPIOPS_ID XPAR_XGPIOPS_0_DEVICE_ID //PS 端 GPIO 器件 ID
13 #define AXI_GPIO_ID XPAR_AXI_GPIO_0_DEVICE_ID //PL 端 AXI GPIO 器件 ID
14 #define GPIO_INT_ID XPAR_FABRIC_GPIO_0_VEC_ID //PL 端 AXI GPIO 中斷 ID
15
16 #define MIO_LED 0 //PS LED 連接到 MIO0
17 #define KEY_CHANNEL 1 //PL 按鍵使用 AXI GPIO 通道 1
18 #define KEY_MASK XGPIO_IR_CH1_MASK //通道 1 的位定義
19
20 //函數聲明
21 void instance_init(); //初始化器件驅動
22 void axi_gpio_handler(void *CallbackRef); //中斷服務函數
23
24 //全局變量
25 XScuGic scugic_inst; //中斷控制器 驅動實例
26 XScuGic_Config * scugic_cfg_ptr; //中斷控制器 配置信息
27 XGpioPs gpiops_inst; //PS 端 GPIO 驅動實例
28 XGpioPs_Config * gpiops_cfg_ptr; //PS 端 GPIO 配置信息
29 XGpio axi_gpio_inst; //PL 端 AXI GPIO 驅動實例
30
31 int led_value = 1; //LED 顯示狀態
32
33 int main()
34 {
35 printf("AXI GPIO INTERRUPT TEST!\n");
36
37 //初始化各器件驅動
38 instance_init();
39
40 //配置 PS GPIO
41 XGpioPs_SetDirectionPin(&gpiops_inst, MIO_LED, 1); //設置 PS GPIO 為輸出
42 XGpioPs_SetOutputEnablePin(&gpiops_inst, MIO_LED, 1); //使能 LED 輸出
43 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //點亮 LED
44
45 //配置 PL AXI GPIO
46 XGpio_SetDataDirection(&axi_gpio_inst, KEY_CHANNEL, 1); //設置 AXI GPIO 通道 1 為輸入
47 XGpio_InterruptEnable(&axi_gpio_inst, KEY_MASK); //使能通道 1 中斷
48 XGpio_InterruptGlobalEnable(&axi_gpio_inst); //使能 AXI GPIO 全局中斷
49
50 //設置中斷優先級和觸發類型(高電平觸發)
51 XScuGic_SetPriorityTriggerType(&scugic_inst, GPIO_INT_ID, 0xA0, 0x1);
52 //關聯中斷 ID 和中斷處理函數
53 XScuGic_Connect(&scugic_inst, GPIO_INT_ID, axi_gpio_handler, &axi_gpio_inst);
54 //使能 AXI GPIO 中斷
55 XScuGic_Enable(&scugic_inst, GPIO_INT_ID);
56
57 //設置並打開中斷異常處理功能
58 Xil_ExceptionInit();
59 Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
60 (Xil_ExceptionHandler)XScuGic_InterruptHandler, &scugic_inst);
61 Xil_ExceptionEnable();
62
63 while(1);
64
65 return 0;
66 }
67

  在主函數中,首先調用自己編寫的 instance_init( )函數對各器件驅動進行初始化。 然后分別配置 PS 端GPIO、 PL 端的 AXI GPIO, 如程序第 40 至 48 行所示。 在配置 PL 端 AXI GPIO 時,我們需要使能其中斷功能, 包括 AXI GPIO 通道 1 的中斷和全局中斷。接下來在程序第 50 至 55 行配置 GIC。每一個中斷源都有自己唯一的標識——中斷號(ID),具體的數值可以在頭文件 xparameters_ps.h 中查看。 其中由 PL 產生的共享外設中斷( SPI)共 16 個, 中斷 ID 分別為 61 到 68, 以及 84 到 91。 我們在程序第 14 行定義了一個宏 GPIO_INT_ID, 用於標識 AXI GPIO 的中斷ID,它的值為 61。配置 GIC 首先需要設置中斷 ID 所代表的中斷源的優先級和觸發類型。中斷優先級共分為 32 個等級, 0 代表最高優先級, 0xF8( 10 進制數 248)代表最低優先級, 各優先級之間的步進值為8。 也就是說,支持的優先級分別為 0、 8、 16、 32……、 248。中斷觸發類型分為高電平敏感類型和上升沿敏感類型。 AXI GPIO在檢測到輸入接口的信號發生改變時, 會產生一個電平類型的中斷請求,高有效,因此將中斷源 AXI GPIO的觸發類型設置為高電平敏感類型。
  然后還需要將中斷 ID 與其中斷服務函數關聯起來。中斷服務函數 axi_gpio_handler( )是需要我們自己編寫的, 用於響應和處理 AXI GPIO 中斷的函數。除此之外, 還要調用函數 XScuGic_Enable(&scugic_inst, GPIO_INT_ID)來使能中斷 ID 所對應的中斷源。
  最后我們需要初始化並設置 ARM 處理器的異常處理功能, 如程序第 57 至 61 行所示。 ARM 處理器支持 7 種異常情況:復位、未定義指令、軟件中斷、指令預取中止、數據中止、 中斷請求( IRQ) 和快速中斷請求( FIQ) 。每種異常也都有自己的 ID 標識, 其中 XIL_EXCEPTION_ID_INT 用於標識中斷請求( IRQ)異常。 我們通過調用函數 Xil_ExceptionRegisterHandler( XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler) XScuGic_InterruptHandler, &scugic_inst )來給 IRQ 異常注冊處理程序,它會將中斷控制器 GIC 的中斷處理程序與 ARM 處理器中的硬件中斷處理邏輯連接起來。另外還要通過 Xil_ExceptionEnable( )函數使能 IRQ 異常。

68 //初始化各器件驅動
69 void instance_init()
70 {
71 //初始化中斷控制器驅動
72 scugic_cfg_ptr = XScuGic_LookupConfig(SCUGIC_ID);
73 XScuGic_CfgInitialize(&scugic_inst, scugic_cfg_ptr, scugic_cfg_ptr->CpuBaseAddress);
74
75 //初始化 PS 端 GPIO 驅動
76 gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);
77 XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);
78
79 //初始化 PL 端 AXI GPIO 驅動
80 XGpio_Initialize(&axi_gpio_inst, AXI_GPIO_ID);
81 }
82
83 //PL 端 AXI GPIO 中斷服務(處理)函數
84 void axi_gpio_handler(void *CallbackRef)
85 {
86 int key_value = 1;
87 XGpio *GpioPtr = (XGpio *)CallbackRef;
88
89 print("Interrupt Detected!\n");
90 XGpio_InterruptDisable(GpioPtr, KEY_MASK); //關閉 AXI GPIO 中斷使能
91 key_value = XGpio_DiscreteRead(GpioPtr, KEY_CHANNEL); //讀取按鍵數據
92 if(key_value == 0){ //判斷按鍵按下
93 led_value = ~led_value;
94 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value); //改變 LED 顯示狀態
95 }
96 sleep(1); //延時 1s,按鍵消抖
97 XGpio_InterruptClear(GpioPtr, KEY_MASK); //清除中斷
98 XGpio_InterruptEnable(GpioPtr, KEY_MASK); //使能 AXI GPIO 中斷
99 }

  其中 instance_init( )函數用於初始化設計中所使用的各個器件的驅動,包括 PS 端的 GIC 和 GPIO, 及PL 端的 AXI GPIO, 如程序第 68 至 81 行所示。其中通用中斷控制器( GIC)是 PS 中用於集中管理中斷信號的資源,如果我們在程序中使用到了中斷,就需要對其進行初始化及配置。而 axi_gpio_handler( void *CallbackRef )是中斷服務(處理) 函數,當 CPU 檢測到 AXI GPIO 產生的中斷后, 需要執行該函數。如程序第 83 至 99 行所示,在中斷服務函數中,我們先通過 XGpio_DiscreteRead ( GpioPtr, KEY_CHANNEL )函數讀取 AXI GPIO 通道 1 所連接的 PL 端按鍵的狀態;當判斷到按鍵按下時,通過 XGpioPs_WritePin(&gpiops_inst, MIO_LED, led_value)函數修改 PS 端 LED 的顯示狀態。對於電平敏感類型的中斷, 在中斷服務函數響應了中斷之后,需要將中斷源的中斷清除。如程序第97行所示,我們通過 XGpio_InterruptClear( GpioPtr, KEY_MASK )函數清除 AXI GPIO 通道 1 的中斷狀態寄存器。
  再次贊美原子簡潔易懂的代碼和詳細的注釋!

三.挖坑串口中斷

詳細部分就在串口篇闡開記述吧,作為補償同樣的軟件中斷來一個吧,也就是隨便一個外設掛在那16個軟件中斷上

四.相對普遍應用的軟件中斷

  此處拿1553B掛載為例(代表一般外設)(其余1553B部分在1553B篇)
原理在開篇講的差不多了,現在直接貼代碼吧

static int rt1553BSetupIntrSystem(INTC *IntcInstancePtr,u16 rt1553bIntrId)
 {
 	int Status;
 #ifdef XPAR_INTC_0_DEVICE_ID

 #else
 #ifndef TESTAPP_GEN
 	XScuGic_Config *IntcConfig;

 	/*
 	 * Initialize the interrupt controller driver so that it is ready to
 	 * use.
 	 */
 	IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);
 	if (NULL == IntcConfig) {
 		return XST_FAILURE;
 	}

 	Status = XScuGic_CfgInitialize(IntcInstancePtr, IntcConfig,
 					IntcConfig->CpuBaseAddress);
 	if (Status != XST_SUCCESS) {
 		return XST_FAILURE;
 	}
 #endif /* TESTAPP_GEN */

 	XScuGic_SetPriorityTriggerType(IntcInstancePtr, XPS_FPGA0_INT_ID,
 					0xA0, 0x3);

 	/*
 	 * Connect the interrupt handler that will be called when an
 	 * interrupt occurs for the device.
 	 */
 	Status = XScuGic_Connect(IntcInstancePtr, XPS_FPGA0_INT_ID,
 				 (Xil_ExceptionHandler)rt1553B_handle,
 				 NULL);
 	if (Status != XST_SUCCESS) {
 		return Status;
 	}

 	/*
 	 * Enable the interrupt for the Timer device.
 	 */
 	XScuGic_Enable(IntcInstancePtr, XPS_FPGA0_INT_ID);
 #endif /* XPAR_INTC_0_DEVICE_ID */

 #ifndef TESTAPP_GEN

 	/*
 	 * Initialize the exception table.
 	 */
 	Xil_ExceptionInit();

 	/*
 	 * Register the interrupt controller handler with the exception table.
 	 */
 	Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,
 			 (Xil_ExceptionHandler)INTC_HANDLER,
 			 IntcInstancePtr);

 	/*
 	 * Enable exceptions.
 	 */
 	Xil_ExceptionEnable();

 #endif /* TESTAPP_GEN */

 	return XST_SUCCESS;
 }

  相對於常用的GPIO,串口之類為什么要單獨拎出來做一個一般中斷呢?自然是沒有例程參考后令人頭疼的ID了,xilinx系列的軟件風格在初始化的時候都會對ID做好匹配(其他是不是不知道哈,認知局限),那么這個ID在常用的xparameters.h里就很難找了,在包含的頭文件xparameters_ps.h還是可以找到滴,所以也就是說把熟悉的串口中斷設置函數里關於串口的部分刪掉(因為我們沒有啊,對,是的直接刪掉

static int UartNs550SetupIntrSystem(INTC *IntcInstancePtr,
					XUartNs550 *UartInstancePtr,//刪掉
					u16 UartIntrId))

ID命名為我們現在設備的ID,對應的具體地址也就是

#define XPS_FPGA0_INT_ID		61U

(換了個馬甲你應該還是認識我的對吧)
  既然這樣簡單粗暴地話,也就是隨便來個掛在軟件中斷的設備我們都可以通過這樣的方式來解決了,官方有實例的那不是就更好了?
  暫時只遇見過這么多,之后還有中斷問題在進行補充吧,認知局限了想象力!

學習zynq時間較短,認知有限,代碼也比較簡陋。如有錯誤歡迎批評指正。影響閱讀的圖片水印也已去掉歡迎大家收藏
參考:芯片手冊 原子開發手冊 CSDN Xilinx社區等網絡資源


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM