CPSC 359

R-PI Interrupts

PhD Student

Fall 2018

Today

What's an interrupt?

Think, for example, of how you might receive input from a keyboard. 

You might have a loop constantly asking the hardware (i.e. polling the hardware) if it's seen a key press.

This feels wasteful. Instead, the keyboard may notify the CPU when a key was pressed, interrupting the current program. The CPU then reads the key press, and continues executing the original, interrupted program.

This is the key idea behind interrupts

IRQs

Many possible sources of interrupts, so there's dedicated hardware that maps input interrupt lines to device interrupt request IRQ lines.

IRQs

When an interrupt occurs, at a high level this is what happens:

  • An exception occurs, and the CPU saves important program state, sets some flags, then branches to the exception handler
  • The exception handler should save program state to the stack, then service the interrupt
  • Exception handler restores program state, and informs the CPU that it's finished, at which point the additional previous state that the CPU saved is restored, and execution of the original program is continued

Side note: IRQ routines should be fast so that you can return to the original program without much delay (and so that you don't risk dropping other interrupts). They should also be debugged very carefully, because they run in a more privileged mode (especially important when the original code that's being interrupted was at a lower privilege level.)

Exception Vector Table

	// Exception Vector Table:
	//
	// The start of the table must be aligned to an address
	// evenly divisible by 2048 (i.e. it must end with 11 zeroes).
	// Furthermore, each entry must also be aligned to an
	// address evenly divisible by 128 (i.e. must end with 7 zeroes),
	// and entries must follow each other consecutively in memory.
	// Each vector can be as long as 32 instructions.
	.align 11
_vectors:
	// Synchronous
	.align  7
	b	_synch_handler	// call handler stub
	
	// IRQ
	.align  7
	b	_IRQ_handler	// call handler	stub

	// FIQ
	.align  7
	b	_FIQ_handler	// call handler stub
	
	// SError
	.align  7
	b	_SError_handler	// call handler stub

@Bottom of startV2.s in example 05_GPIO_PushButtonInterrupt, this tells the CPU where the handlers are for certain requests. You don't need to change this for assignment 3

IRQ Handler Pt.1


_IRQ_handler:
	// Save state of all general purpose registers.
	// We do this so that any C code that we call
	// from here can use any of the general purpose
	// registers.
	stp	x0, x1, [sp, -16]!
	stp	x2, x3, [sp, -16]!
	stp	x4, x5, [sp, -16]!
	stp	x6, x7, [sp, -16]!
	stp	x8, x9, [sp, -16]!
	stp	x10, x11, [sp, -16]!
	stp	x12, x13, [sp, -16]!
	stp	x14, x15, [sp, -16]!
	stp	x16, x17, [sp, -16]!
	stp	x18, x19, [sp, -16]!
	stp	x20, x21, [sp, -16]!
	stp	x22, x23, [sp, -16]!
	stp	x24, x25, [sp, -16]!
	stp	x26, x27, [sp, -16]!
	stp	x28, x29, [sp, -16]!
	str	x30, [sp, -16]!
	// Call the IRQ handler written in C
	bl	IRQ_handler
	// Restore state of all general purpose registers
	ldr	x30, [sp], 16
	ldp	x28, x29, [sp], 16
	ldp	x26, x27, [sp], 16
	ldp	x24, x25, [sp], 16
	ldp	x22, x23, [sp], 16
	ldp	x20, x21, [sp], 16
	ldp	x18, x19, [sp], 16
	ldp	x16, x17, [sp], 16
	ldp	x14, x15, [sp], 16
	ldp	x12, x13, [sp], 16
	ldp	x10, x11, [sp], 16
	ldp	x8, x9, [sp], 16
	ldp	x6, x7, [sp], 16
	ldp	x4, x5, [sp], 16
	ldp	x2, x3, [sp], 16
	ldp	x0, x1, [sp], 16
	// Return from exception
	eret

@Bottom of startV2.s in example 05_GPIO_PushButtonInterrupt, this saves all the registers on the stack then calls the IRQ handler in C. You don't need to change this for assignment 3

IRQ Handler Pt.2


extern unsigned int sharedValue;
void IRQ_handler()
{
    unsigned int r;

    // Print out exception type
    uart_puts("\nInside IRQ exception handler:\n");

    // Print out further information about the exception    
    r = getCurrentEL();
    uart_puts("    CurrentEL is:  0x");
    uart_puthex(r);
    uart_puts("\n");

    r = getDAIF();
    uart_puts("    DAIF is:  0x");
    uart_puthex(r);
    uart_puts("\n");

    r = *IRQ_PENDING_2;
    uart_puts("    IRQ_PENDING_2 is:  0x");
    uart_puthex(r);
    uart_puts("\n");
    
    r = *GPEDS0;
    uart_puts("    GPEDS0 is:  0x");
    uart_puthex(r);
    uart_puts("\n");
    	

    // Handle GPIO interrupts in general
    if (*IRQ_PENDING_2 == 0x00100000) {
      // Handle the interrupt associated with GPIO pin 17
      if (*GPEDS0 == 0x00020000) {
        // Clear the interrupt by writing a 1 to the GPIO Event Detect
        // Status Register at bit 17 (p. 96 of the Broadcom Manual)
        *GPEDS0 = (0x1 << 17);
    
        // Handle the interrupt:
        // We do this by incrementing the shared global variable
        sharedValue++;
      }
    }

    // Return to the IRQ exception handler stub
    return;
}

 handlers.c in example 05_GPIO_PushButtonInterrupt. This is where you'll be making some changes in assignment 3

IRQ Handler Pt.2



    // Handle GPIO interrupts in general
    if (*IRQ_PENDING_2 == 0x00100000) {
      // Handle the interrupt associated with GPIO pin 17
      if (*GPEDS0 == 0x00020000) {
        // Clear the interrupt by writing a 1 to the GPIO Event Detect
        // Status Register at bit 17 (p. 96 of the Broadcom Manual)
        *GPEDS0 = (0x1 << 17);
    
        // Handle the interrupt:
        // We do this by incrementing the shared global variable
        sharedValue++;
      }
    }

This is the more important part. We check that we have an IRQ pending, then check to see if it was generated by pin 17. If it was, we clear the status register for pin 17.

Demo

Take some time to run example 05_GPIO_PushButtonInterrupt

Connect pin 17 on your breakout board to switch B (the bottom button) 

Let's add another pin



    // Handle GPIO interrupts in general
    if (*IRQ_PENDING_2 == 0x00100000) {
      // Handle the interrupt associated with GPIO pin 17
      if (*GPEDS0 == 0x00020000) {
        // Clear the interrupt by writing a 1 to the GPIO Event Detect
        // Status Register at bit 17 (p. 96 of the Broadcom Manual)
        *GPEDS0 = (0x1 << 17);
    
        // Handle the interrupt:
        // We do this by incrementing the shared global variable
        sharedValue++;
      }
    }

Let's also service pin 18. And let's do it on a falling edge (the current example is on a rising edge.)

Let's add another pin

    // Handle GPIO interrupts in general
    if (*IRQ_PENDING_2 == 0x00100000) {

      // Handle the interrupt associated with GPIO pin 17
      if (*GPEDS0 == 0x00020000) {
        // Clear the interrupt by writing a 1 to the GPIO Event Detect
        // Status Register at bit 17 (p. 96 of the Broadcom Manual)
        *GPEDS0 = (0x1 << 17);
    
        // Handle the interrupt:
        // We do this by incrementing the shared global variable
        sharedValue++;
      }

      // Handle the interrupt associated with GPIO pin 18
      if (*GPEDS0 == 0x00040000) {
        // Clear the interrupt by writing a 1 to the GPIO Event Detect
        // Status Register at bit 18 (p. 96 of the Broadcom Manual)
        *GPEDS0 = (0x1 << 18);
    
        // Handle the interrupt:
        // We do this by decrementing the shared global variable
        sharedValue--;
      }
    }

This won't do anything yet (even if you've connected pin 18 to the pulled up button on your breadboard). We need to set pin 18 to generate an interrupt.

Let's enable another pin


void init_GPIO17_to_risingEdgeInterrupt()
{
    register unsigned int r;
    
    
    // Get the current contents of the GPIO Function Select Register 1
    r = *GPFSEL1;

    // Clear bits 21 - 23. This is the field FSEL17, which maps to GPIO pin 17.
    // We clear the bits by ANDing with a 000 bit pattern in the field. This
    // sets the pin to be an input pin
    r &= ~(0x7 << 21);

    // Write the modified bit pattern back to the
    // GPIO Function Select Register 1
    *GPFSEL1 = r;

    // Disable the pull-up/pull-down control line for GPIO pin 17. We follow the
    // procedure outlined on page 101 of the BCM2837 ARM Peripherals manual. We
    // will pull down the pin using an external resistor connected to ground.

    // Disable internal pull-up/pull-down by setting bits 0:1
    // to 00 in the GPIO Pull-Up/Down Register 
    *GPPUD = 0x0;

    // Wait 150 cycles to provide the required set-up time 
    // for the control signal
    r = 150;
    while (r--) {
        asm volatile("nop");
    }

    // Write to the GPIO Pull-Up/Down Clock Register 0, using a 1 on bit 17 to
    // clock in the control signal for GPIO pin 17. Note that all other pins
    // will retain their previous state.
    *GPPUDCLK0 = (0x1 << 17);

    // Wait 150 cycles to provide the required hold time
    // for the control signal
    r = 150;
    while (r--) {
        asm volatile("nop");
    }

    // Clear all bits in the GPIO Pull-Up/Down Clock Register 0
    // in order to remove the clock
    *GPPUDCLK0 = 0;
    
    // Set pin 17 to so that it generates an interrupt on a rising edge.
    // We do so by setting bit 17 in the GPIO Rising Edge Detect Enable
    // Register 0 to a 1 value (p. 97 in the Broadcom manual).
    *GPREN0 = (0x1 << 17);
    
    // Enable the GPIO IRQS for ALL the GPIO pins by setting IRQ 52
    // GPIO_int[3] in the Interrupt Enable Register 2 to a 1 value.
    // See p. 117 in the Broadcom Peripherals Manual.
    *IRQ_ENABLE_IRQS_2 = (0x1 << 20);
}

This is the init for pin 17, let's add another function for 18

Let's enable another pin


void init_GPIO18_to_fallingEdgeInterrupt()
{
    register unsigned int r;    
    r = *GPFSEL1;
    r &= ~(0x7 << 24); // For pin 18
    *GPFSEL1 = r;

    *GPPUD = 0x0;

    r = 150;
    while (r--) {
        asm volatile("nop");
    }
    // Similar to previous function
    *GPPUDCLK0 = (0x1 << 18);

    r = 150;
    while (r--) {
        asm volatile("nop");
    }

    // Clear all bits in the GPIO Pull-Up/Down Clock Register 0
    // in order to remove the clock
    *GPPUDCLK0 = 0;
    
    // Set pin 18 to so that it generates an interrupt on a /falling/ edge.
    // We do so by setting bit 18 in the GPIO Falling Edge Detect Enable
    // Register 0 to a 1 value (p. 97 in the Broadcom manual).
    *GPFEN0 = (0x1 << 18);
    
    // Enable the GPIO IRQS for ALL the GPIO pins by setting IRQ 52
    // GPIO_int[3] in the Interrupt Enable Register 2 to a 1 value.
    // See p. 117 in the Broadcom Peripherals Manual.
    *IRQ_ENABLE_IRQS_2 = (0x1 << 20);
}

Add this to main.c

Let's enable another pin


    // Initialize the sharedValue global variable and
    // and set the local variable to be same value
    localValue = sharedValue = 0;
    
    // Set up GPIO pin #17 to input and so that it triggers
    // an interrupt when a rising edge is detected
    init_GPIO17_to_risingEdgeInterrupt();

Find this in main.c


    // Initialize the sharedValue global variable and
    // and set the local variable to be same value
    localValue = sharedValue = 0;
    
    // Set up GPIO pin #17 to input and so that it triggers
    // an interrupt when a rising edge is detected
    init_GPIO17_to_risingEdgeInterrupt();
    init_GPIO18_to_fallingEdgeInterrupt();

Change it to this

Demo

Connect pin 18 on your breakout board to switch A (the top button) 

CPSC 359: Pi Interrupts

By Joshua Horacsek

CPSC 359: Pi Interrupts

  • 1,338