MicroZed Chronicles: Processor System to Programmable Logic Interrupts

July 1, 2022


Editor’s Note: This content is republished from the MicroZed Chronicles, with permission from the author.

 

One of the critical techniques in an embedded system that enables responsive software is using interrupts. In this series, we have used interrupts several times in the processor system to create example and demo applications. In fact, we can’t avoid using them when we work with real-time operating systems such as FreeRTOS or PetaLinux.  

MZ_450_MicroZed_7020

Of course, heterogeneous SoCs like the Zynq and MPSoCs are slightly different interrupts that need the processor’s attention and can occur in the processor system or the programmable logic. This is the most general use case for interrupts, however, it’s also possible to pass interrupts generated in the PS to the PL. 

Since we can connect interrupts from enabled peripherals in the PS to the PL, this means we can create a more responsive system if PL action is required for a specific PS subsystem. It is not necessary for the processor to first receive the interrupt, process it, and then trigger action in the appropriate PL module. 

For this project, we are going to target the MicroZed 7020 and configure the PS button to generate an interrupt when pressed. This interrupt will be routed to the programmable logic and captured by an ILA. 

The first stage is to create a new project targeting the MicroZed 7020. 

MZ_450_Vivado_Project

Once that is created, the next step is to instantiate a Zynq processing system and configure it for the MicroZed configuration.

MZ_450_Adding_ZynqSoC

With the PS implemented and configured for the MicroZed, we need to reconfigure the PS to enable the PS to PL interrupts. 

On the interrupts tab, enable the fabric interrupts and scroll down the list to GPIO processor to fabric interrupt. 

MZ_450_Zynq_Processing_System

Add in an ILA and connect it to the IRQ processor to fabric interrupt in the block diagram. 

MZ_450_ILA

This is a simple project so it should not take long to build. Once the project is built, we can export the XSA and create the Vitis embedded application. 

The embedded software application is simple. The generic interrupt controller is configured to enable interrupts for the GPIO connected to the push button. We also need to write the interrupt service routine for the GPIO button press. 

#include <stdio.h>

#include "platform.h"

#include "xgpiops.h"

#include "xil_types.h"

#include "Xscugic.h"

#include "Xil_exception.h"

 

 

#define GPIO_DEVICE_ID XPAR_XGPIOPS_0_DEVICE_ID

#define INTC_DEVICE_ID XPAR_SCUGIC_SINGLE_DEVICE_ID

#define GPIO_INTERRUPT_ID               XPS_GPIO_INT_ID

 

#define ledpin 47

#define pbsw 51

#define LED_DELAY     100000000

 

static XScuGic Intc; /* The Instance of the Interrupt Controller Driver */

static XGpioPs Gpio; /* The driver instance for GPIO Device. */

int toggle;//used to toggle the LED

static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId);

static void IntrHandler(void *CallBackRef, int Bank, u32 Status);

 

int main()

{

    int Status;

 

    XGpioPs_Config *GPIOConfigPtr;

 

    init_platform();

 

    printf("Adam Edition MicroZed Using Vivado How To Printf \n\r");

 

    //GPIO Initilization

    GPIOConfigPtr = XGpioPs_LookupConfig(XPAR_XGPIOPS_0_DEVICE_ID);

    Status = XGpioPs_CfgInitialize(&Gpio, GPIOConfigPtr,GPIOConfigPtr->BaseAddr);

    if (Status != XST_SUCCESS) {

         print("GPIO INIT FAILED\n\r");

        return XST_FAILURE;

    }

 

    //set direction and enable output

    XGpioPs_SetDirectionPin(&Gpio, ledpin, 1);

    XGpioPs_SetOutputEnablePin(&Gpio, ledpin, 1);

 

    //set direction input pin

    XGpioPs_SetDirectionPin(&Gpio, pbsw, 0x0);

    SetupInterruptSystem(&Intc, &Gpio, GPIO_INTERRUPT_ID);

 

     while(1){

     }

    return 0;

}

 

static void SetupInterruptSystem(XScuGic *GicInstancePtr, XGpioPs *Gpio, u16 GpioIntrId)

{

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

 

    Xil_ExceptionInit();

 

    /*

    * Initialize the interrupt controller driver so that it is ready to

    * use.

    */

    IntcConfig = XScuGic_LookupConfig(INTC_DEVICE_ID);

 

    XScuGic_CfgInitialize(GicInstancePtr, IntcConfig,

                                    IntcConfig->CpuBaseAddress);

 

    /*

    * 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.

    */

    XScuGic_Connect(GicInstancePtr, GpioIntrId,

                                (Xil_ExceptionHandler)XGpioPs_IntrHandler,

                                (void *)Gpio);

 

    //Enable interrupts for all the pins in bank 0.

    XGpioPs_SetIntrTypePin(Gpio, pbsw,                                 XGPIOPS_IRQ_TYPE_EDGE_RISING);

 

    //Set the handler for gpio interrupts.

    XGpioPs_SetCallbackHandler(Gpio, (void *)Gpio, IntrHandler);

 

    //Enable the GPIO interrupts of Bank 0.

    XGpioPs_IntrEnablePin(Gpio, pbsw);

 

    //Enable the interrupt for the GPIO device.

    XScuGic_Enable(GicInstancePtr, GpioIntrId);

 

    // Enable interrupts in the Processor.

    Xil_ExceptionEnableMask(XIL_EXCEPTION_IRQ);

    }

 

static void IntrHandler(void *CallBackRef, int Bank, u32 Status)

{

    int delay;

    printf("****button pressed****\n\r");

    toggle = !toggle;

    XGpioPs_WritePin(&Gpio, ledpin, toggle);

    for( delay = 0; delay < LED_DELAY; delay++)//wait

    {}

}

When we run this on the development board, we can open hardware manager in the ILA and configure it to trigger on the rising edge of the interrupt. 

MZ_450_Trigger_Interrupt

When the button is pushed, not only does the software interrupt service routine execute, but the interrupt it triggered from the processor system GPIO to the PL fabric executes also. 

MZ_450_Trigger_Interrupt_to_Programmable_Logic

This enables the design in the programmable logic to respond to an event occurring on the GPIO. In this case, it was a simple example to demonstrate how the PS to PL interrupts can be configured and used. 

This provides a much more responsive solution and I am going to look at the time saved by using the PS to PL interrupt compared to using the ISR within the PS in a future blog.

We will also explore a few more of the less commonly used interfaces between the PS and PL.