Kampis Elektroecke

Interrupts

Durch die Verwendung von PS-PL (Processing System to Programmable Logic) oder PL-PS (Programmable Logic to Processing System) Interrupts kann die angeschlossene Logik, bzw. der Prozessor auf asynchrone Events reagieren. Schauen wir uns mal anhand des folgenden Beispiels an wie das funktioniert.

Über den Reiter Interrupts können die entsprechenden Interrupts im ZYNQ7 Processing System aktiviert werden. Für dieses Beispiel werden die PL-PS Interrupts benötigt, da der Interrupt von der programmierbaren Logik (AXI GPIO) zum Processing System geleitet werden müssen.

Insgesamt können drei verschiedene Kategorien von Interrupts verwendet werden, wobei ich mich in diesem Artikel auf die Hardwareinterrupts beschränken werde.

  • Gemeinsame Interrupts (Shared Peripheral Interrupts – SPI)
  • Private Interrupts (Private Peripheral Interrupts – PPI)
    • CPU 0 PPI
    • CPU 1 PPI
  • Software Interrupts (Software Generated Interrupts – SGI)

In diesem Beispiel wird der Interrupt des AXI GPIO-Blocks an den gemeinsamen Interrupt für beide CPU-Kerne angeschlossen.

Insgesamt stehen bis zu 16 Interruptvektoren für die programmierbare Logik zur Verfügung. Falls mehrere Interrupts genutzt werden möchten, so muss ein Concat benutzt werden, der die einzelnen Signale zu einem Vektor zusammenführt.

Damit die Peripherieinterrupts in der Anwendungssoftware genutzt werden können, müssen verschiedene Schritte ausgeführt werden:

  • Aktivieren der Interrupts in der Peripherie
  • Den Global Interrupt Controller (GIC) konfigurieren
  • Callback für den Interrupt konfigurieren
  • ARM Exceptions aktivieren

Zu Anfang werden die beiden AXI GPIO-Blöcke konfiguriert und die Interrupts für den GPIO-Block an dem die Taster angeschlossen sind aktiviert.

GPIO_ConfigPtr = XGpio_LookupConfig(XPAR_BUTTON_DEVICE_ID);
if(GPIO_ConfigPtr == NULL)
{
	xil_printf("[ERROR] Invalid button configuration!\n\r");
	return XST_FAILURE;
}
Status = XGpio_CfgInitialize(&Button, GPIO_ConfigPtr, GPIO_ConfigPtr->BaseAddress);
if(Status != XST_SUCCESS)
{
	xil_printf("[ERROR] Can not initialize buttons!\n\r");
	return XST_FAILURE;
}
XGpio_InterruptEnable(&Button, XGPIO_IR_CH1_MASK);
XGpio_InterruptGlobalEnable(&Button);

GPIO_ConfigPtr = XGpio_LookupConfig(XPAR_LED_DEVICE_ID);
if(GPIO_ConfigPtr == NULL)
{
	xil_printf("[ERROR] Invalid LED configuration!\n\r");
	return XST_FAILURE;
}
Status = XGpio_CfgInitialize(&LED, GPIO_ConfigPtr, GPIO_ConfigPtr->BaseAddress);
if(Status != XST_SUCCESS)
{
	xil_printf("[ERROR] Can not initialize LED!\n\r");
	return XST_FAILURE;
}

Der AXI GPIO-IP Block besitzt zwei Register die beschrieben werden müssen um die Interrupts zu aktivieren. 

  • IP Interrupt Enable-Register (IPIER) – Aktiviert die Interrupts für einen oder mehrere Kanäle des GPIO-Blocks
  • Global Interrupt Enable-Register (GIER) – Interrupthauptschalter für den GPIO-Block

Über die Funktion XGpio_InterruptEnable werden die Interrupts für Kanal 1 des GPIO-Blocks aktiviert. Als Parameter erwartet die Funktion eine Bitmaske mit den Kanälen bei denen die Interrupts aktiviert werden sollen. Anschließend aktiviert die Funktion XGpio_InterruptGlobalEnable sämtliche GPIO-Interrupts.

In den nächsten beiden Schritten müssen der GIC und der gewünschte konfiguriert werden. Dazu wird zuerst eine Referenz auf den GIC des Processing Systems erstellt.

GIC_ConfigPtr = XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
if(NULL == GIC_ConfigPtr)
{
	xil_printf("[ERROR] Invalid GIC configuration!\n\r");
	return XST_FAILURE;
}
Status = XScuGic_CfgInitialize(&GIC, GIC_ConfigPtr, GIC_ConfigPtr->CpuBaseAddress);
if(Status != XST_SUCCESS)
{
	xil_printf("[ERROR] Can not initialize GIC!\n\r");
	return XST_FAILURE;
}

Mit dieser Referenz wird jetzt der Interrupt konfiguriert.

XScuGic_SetPriorityTriggerType(&GIC, XPAR_FABRIC_GPIO_0_VEC_ID, 0xA0, 0x03);
if(XScuGic_Connect(&GIC, XPAR_FABRIC_GPIO_0_VEC_ID, (Xil_ExceptionHandler)GpioHandler, &Button) != XST_SUCCESS)
{
	xil_printf("[ERROR] Can not connect GpioHandler!\n\r");
	return XST_FAILURE;
}
XScuGic_Enable(&GIC, XPAR_FABRIC_GPIO_0_VEC_ID);

Durch den Aufruf der Funktion XScuGic_SetPriorityTriggerType werden die Auslösebedingung und die Priorität des Interrupts mit dem Interrupt-Vektor XPAR_FABRIC_GPIO_0_VEC_ID definiert. Sämtliche Interrupt-Vektor werden von Vivado automatisch vergeben, sobald Peripheriekomponenten an den IRQ-Port des Processing Systems angeschlossen werden und können in der Headerdatei xparameters.h eingesehen werden. Über den dritten Funktionsparameter wird die Priorität des Interrupts festgelegt, wobei Werte mit einer Schrittweite von 8, wobei der Wert 0 für die höchste Priorität und der Wert 248 für die niedrigste Priorität steht, vergeben werden. Der letzte Funktionsparameter definiert die Triggerbedingung für den Interrupt und kann entweder den Wert 1 (High Level) oder 3 (Rising Edge) besitzen.

Danach wird über die Funktion XScuGic_Connect der allgemeine Interrupthandler des GIC mit einem entsprechenden Callback für den gewünschten Interrupt verbunden. Dazu wird der entsprechende Interrupt-Vektor und ein Zeiger auf eine Callback-Funktion übergeben. Der letzte Parameter ist eine Referenz auf die Hardware, die den Interrupt nutzen soll. Diese Referenz wird beim Aufruf des Callbacks übergeben und kann dann genutzt werden um die Hardware anzusprechen (dazu später mehr). Abschließend wird der angeforderte Interrupt über die Funktion XScuGic_Enable, wodurch die Konfiguration des Interrupts abgeschlossen ist.

Im letzten Schritt müssen die ARM-Exceptions initialisiert und aktiviert werden.

Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, &GIC);
Xil_ExceptionEnable();

Die Funktion Xil_ExceptionInit wird genutzt um Abwärtskompatibilität zu gewährleisten. Bei ARM Cortex-A53, Cortex-R5 und Cortex-A9 Prozessoren werden die Exceptions automatisch aktiviert, weshalb die Funktion keinen Code enthält. Bei anderen Prozessortypen werden die Exceptions innerhalb dieses Funktionsaufrufes initialisiert. Die Codezeile Xil_ExceptionRegisterHandlerverbindet den IRQ-Vektor des ARM-Prozessors mit dem Interrupthandler des GIC, in dem dann der entsprechende Callback aufgerufen wird.

Abschließend werden die ARM-Exceptions mit dem Aufruf von Xil_ExceptionEnable aktiviert.

Die Funktion GpioHandler(void* CallbackRef) stellt den Callback für den GPIO-Interrupt dar. Beim Aufruf wird ein void-Zeiger mit der Referenz auf die aufrufende Hardware, hier vom Typ XGpio übergeben.

static void GpioHandler(void* CallbackRef)
{
	XGpio* GpioPtr = (XGpio*)CallbackRef;
	if(LED.IsReady)
	{
		XGpio_DiscreteWrite(&LED, 1, 0x0A);
		xil_printf("[INFO] Callback\n\r");
	}
	XGpio_InterruptClear(GpioPtr, XGPIO_IR_CH1_MASK);
}

In dem Callbackhandler wird die übergebene Referenz in das entsprechende Objekt gecastet und diese dann genutzt um ein Bitmuster auf den LEDs auszugeben. Danach wird noch das Interruptflag des aufrufenden GPIO-Blocks gelöscht und der Callback verlassen.

Das komplette Projekt steht in meinem GitHub-Repository zum Download bereit.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert