colinh
Posts: 95
Joined: Tue Dec 03, 2013 11:59 pm
Location: Munich

Getting interrupts from GPIO (in assembler)

Thu Jan 23, 2014 11:14 pm

Hi there.


I've read all the threads on this that I can find, and still can't see what I'm doing wrong :-(

Edit: OK, I found my mistake which, of course, had nothing to do with anything interrupty. I'm posting this anyway as a how-to for anyone else interested in this. Most code available seems to be in C.


What I think I'm doing is:

1. Setting pins GPIO30 and 31 as INPUT with PULLUP resistors.
2. Setting GPAFEN0 to detect a falling edge on GPIO30
3. Setting the GPU (in IRQ2_EN) to generate interrupts for gpio_int[1] <<<< EDIT2: curious bug?
4. I have a branch to my interrupt service routine, irq, at 0x0018 in the jump table. (I have my kernel loaded at 0x0000)
5. I enable interrupts in the CPSR
6. The ISR checks if the interrupt was due to gpio_int[0] <<<< EDIT2: curious bug? and branches to the handler gpio_0_irq if it was.
7. gpio_0_irq writes a 1<<30 to GPIO_EDS0 to clear the interrupt on GPIO30 ...
8. ... and reads the level on the GPIO31 pin


Things to note (this is intended as documentation for anyone else trying to get interrupts working):

The BCM2835 has 54 GPIO pins. These are organised in two banks, ie bank 0 [31:0] for GPIO31-0 and bank 1 [22:0] for GPIO54-32. These are the 0 and 1 used for the GPIO registers like GPSET1, GPCLR0, GPCLR1, GPLEV0, GPLEV1, GPEDS0, GPEDS1, GPREN0, GPREN1... (There's only one GPPUD, because only GPIO0-31 have pullup/down resistors).

Note that none of the GPIO pins in the second bank (GPIO32-54) is available on any headers, which is a shame, because it makes getting two different interrupts from the GPIO pins difficult. In fact, only GPIO40/45 are used at all and are connected to PWM0/1_OUT. Well, GPIO46-53 are used for HDMI and the SD card, but I don't want to mess with those...)


The GPU interrupts are "not very well documented." (*) However, there are 64 of them, also organised in two banks (which have nothing to do with the two banks of GPIO pins. TWO BANKS simply means there are more than 32 (but <= 64) of whatever it is). To show that these are different things (**), the designers have chosen to call these two banks 1 and 2 (instead of 0 and 1). These are the 1 and 2 used for the IRQ registers like IRQ1_PDG, IRQ2_PDG, IRQ1_EN, IRQ2_EN (my names for the registers).

(*) understatement
(**) sarcasm


The first bank of GPIO pins is said to generate one GPU interrupt the second another with a third being generated (as well) by both. It is guessed that the first bank generates gpio_irq[0] and the second bank gpio_irq[1]. These are the GPU IRQs 49 and 50 (ie. bits 17 and 18 in the second IRQ bank). <<<< EDIT2: Apparently this guess is incorrect.

>>>>
This is odd.

Code: Select all

    ldr     r1, =IRQ_base
    mov     r0, #0x1<<18                        // enable gpio_int[0] (17 = GPU intrpt 49 - 32)
    str     r0, [r1, #IRQ_IRQ2_EN]              // (- 32, to get 2nd bank of 32 intrpts)

    pop     {pc}

//***********************************************

.globl irq
irq:
    push    {r0,r1,lr}

    ldr     r1, =IRQ_base
    ldr     r0, [r1, #IRQ_IRQ2_PDG]
    tst     r0, #1<<17
    bleq     gpio_0_irq

As you can see, I have set bit 18 in IRQ2_EN, but am testing bit 17 in IRQ2_PDG. This works! Changing both to 17 or both to 18 or swapping them to 17 and 18 results in the handler, gpio_0_irq, NOT being called...

<<<<

Here's my feeble attempt...

Code: Select all

.section .text

//	header-pin			    RPi board signal		 BCM2835 pin
//	P1-1,17				    3.3 V              
//	P1-2,4				     5 V              
//	P1-3,5		    	    SDA1,SCL1				  GPIO2,3
//	P1-6,9,14,20,25		  GND
//	P1-8,10		    	   TXD0, RXD0				 GPIO14,15
//	P1-7				       GPIO_GCLK				  GPIO4 TDI
//	P1-11:13,15,16,18,22	GPIO_GEN0:6		      GPIO17,18,27,22:25 2TMS 3TRST 5TDO 6TCK
//	P1-19,21,23			   SPI_MOSI:MISO:SCLK	  GPIO10,9,11
//	P1-24,26			      SPI_CE0_N:CE_1_N		 GPIO8,7

//	P5-1				       5 V
//	P5-2				       3.3 V
//	P5-3:4				     GPIO_GEN7:8			   GPIO28:29
//	P5-5:6				     GPIO_GEN9:10			  GPIO30:31
//	P5-7:8				     GND


.globl init_gpio
init_gpio:
	push	{lr}

	ldr     r1, =GPIO_base

	ldr     r0, [r1, #GPIO_GPFSEL3]
	bic     r0, #0x7<<0		// CLEAR bits 0000.0111 << (n * 3)  set GPIO30 and 31 as INPUT
	bic     r0, #0x7<<3
	str     r0, [r1, #GPIO_GPFSEL3]

	mov	  r0, #0b10
	str     r0, [r1, #GPIO_GPPUD]				// enable PULL_UP resistors...
	mov	  r0, #150
	bl		delay

	mov	  r0, #1<<30                          // GPIO pins 30 and 31 = GPIO_GEN9 and 10 (on header P5)
	orr     r0, #1<<31
	str     r0, [r1, #GPIO_GPPUDCLK0]			// ... on GPIO30, 31
	mov	  r0, #150
	bl		delay

	mov		r0, #0
	str      r0, [r1, #GPIO_GPPUD]				// "remove control signal"
	str      r0, [r1, #GPIO_GPPUDCLK0]			// "remove the clock"

	mov	   r0, #1<<30
	str      r0, [r1, #GPIO_GPAFEN0]				// detect asynchronous FALLING EDGE on GPIO30

	ldr		r1, =IRQ_base
	mov		r0, #1<<18						// enable gpio_int[0] (17 = GPU intrpt 49 - 32) >>>> odd bug? <<<<
	str		r0, [r1, #IRQ_IRQ2_EN]				// (- 32, to get 2nd bank of 32 intrpts)
	pop		{pc}

//***********************************************

.globl irq
irq:
	push	{r0,r1,lr}

	ldr		r1, =IRQ_base
	ldr		r0, [r1, #IRQ_IRQ2_PDG]
	tst		r0, #1<<17
	bleq     gpio_0_irq

	pop		{r0,r1,lr}

	subs	r15, lr, #4						// exit, reinstalling CPSR

//***********************************************

.globl gpio_0_irq
gpio_0_irq:
    push    {r0,r1}

    ldr     r1, =GPIO_base
    mov     r0, #1<<30
    str     r0, [r1, #GPIO_EDS0]            // clear interrupt by writing a 1

    ldr     r0, [r1, #GPIO_LEV0]
    and     r0, #1<<31                // read level of GPIO31 pin

    pop    {r0,r1}
    mov   pc, lr

//***********************************************

main:
...
    mrs     r0,cpsr                     // enable normal interrupts
    bic     r0,#0x80                    // 1000.0000
    msr     cpsr_c,r0
...

//***********************************************

colinh
Posts: 95
Joined: Tue Dec 03, 2013 11:59 pm
Location: Munich

Re: Getting interrupts from GPIO (in assembler)

Fri Jan 24, 2014 12:47 pm

I edited the above post to reflect an oddity I've come across. To me it looks like a bug in the BCM2835 (or, at least, in the "documentation").

I'm looking to detect a falling edge on a GPIO pin in the first bank (ie on one of GPIO0 to GPIO31), in this case GPIO30.

I set the relevant bit 30 in GPAFEN0.

I set bit 18 for gpio_int[1] in IRQ2_EN (called "Interrupt enable register 2." in the docs).

On the falling edge event, bit 17 of IRQ2_PDG (called "GPU pending 2 register." in the docs -- note consistency in naming) gets set (?!) as tested for in my IRQ exception handler.



This may explain why various people have had problems, and why they resort to polling. My code worked initially because I had enabled ALL of the gpio_int[n] GPU interrupts (in a desperate attempt...). When I changed to enabling only gpio_int[0] my code stopped working.

Am I being silly, or have I made a great and useful discovery (which others have also made)? I haven't bothered checking other people's C-code, linux drivers, python libraries etc as I usually find that more painful than developing an entire OS from scratch...

EDIT: Answer: I was being very silly, of course.
Last edited by colinh on Thu Jan 30, 2014 2:20 am, edited 1 time in total.

User avatar
Gert van Loo
Posts: 2481
Joined: Tue Aug 02, 2011 7:27 am
Contact: Website

Re: Getting interrupts from GPIO (in assembler)

Wed Jan 29, 2014 10:30 am

Need input!

So I will try to find some time to look into this but I am very short of time.
To help me out I need a lot more details. What registers exactly where?
The chips has 17K of registers all over the place.
There are at least two dozen register dealing with GPIO in multiple places.
The same for interrupts.

User avatar
jojopi
Posts: 3074
Joined: Tue Oct 11, 2011 8:38 pm

Re: Getting interrupts from GPIO (in assembler)

Wed Jan 29, 2014 6:04 pm

colinh wrote:(There's only one GPPUD, because only GPIO0-31 have pullup/down resistors).
There only needs to be one GPPUD, to hold the disable/down/up setting. To send pulls to GPIO32+, toggle the bits in GPPUDCLK1 instead of GPPUDCLK0.
Note that none of the GPIO pins in the second bank (GPIO32-54) is available on any headers, which is a shame, because it makes getting two different interrupts from the GPIO pins difficult.
You can enable multiple GPIO interrupts on a single IRQ. The Linux kernel currently appears to use #52, IRQ_GPIO3 only. I suspect the reason for having four channels is to allow for different handler priorities, but I have not personally seen code that attempts that.
As you can see, I have set bit 18 in IRQ2_EN, but am testing bit 17 in IRQ2_PDG. This works! Changing both to 17 or both to 18 or swapping them to 17 and 18 results in the handler, gpio_0_irq, NOT being called...
TST+BLEQ calls your routine if all of the bits tested are clear. When you test the correct pending bit, it is always set except if the interrupt has not happened at all. You wanted TST+BLNE I think.

colinh
Posts: 95
Joined: Tue Dec 03, 2013 11:59 pm
Location: Munich

Re: Getting interrupts from GPIO (in assembler)

Thu Jan 30, 2014 2:18 am

jojopi wrote:
colinh wrote:(There's only one GPPUD, because only GPIO0-31 have pullup/down resistors).
There only needs to be one GPPUD, to hold the disable/down/up setting. To send pulls to GPIO32+, toggle the bits in GPPUDCLK1 instead of GPPUDCLK0.
Yes. I was thinking of something else, I presume.
Note that none of the GPIO pins in the second bank (GPIO32-54) is available on any headers, which is a shame, because it makes getting two different interrupts from the GPIO pins difficult.
You can enable multiple GPIO interrupts on a single IRQ. The Linux kernel currently appears to use #52, IRQ_GPIO3 only. I suspect the reason for having four channels is to allow for different handler priorities, but I have not personally seen code that attempts that.
You're right, of course. By "difficult" I just meant more complicated. I hadn't actually got as far as writing the handler for multiple concurrent interrupt events and had notions of using the second bank at higher priority. But on reflection, that's probably not the way to do it anyway.

As you can see, I have set bit 18 in IRQ2_EN, but am testing bit 17 in IRQ2_PDG. This works! Changing both to 17 or both to 18 or swapping them to 17 and 18 results in the handler, gpio_0_irq, NOT being called...
TST+BLEQ calls your routine if all of the bits tested are clear. When you test the correct pending bit, it is always set except if the interrupt has not happened at all. You wanted TST+BLNE I think.
I'd just noticed that myself.

It's amazing how wrong preconceptions / assumptions can warp one's thinking :oops:

So, an interrupt was being generated. And it did jump to the correct handler, because the wrong bit wasn't set :roll: *sigh*


Anyway, it looks like gpio[1] is for interrupt events on GPIO0-31, I'd guess that gpio[2] is for GPIO32-54. gpio[3] presumably gets set in either case, and I don't know what gpio[0] is for.

olso4539
Posts: 30
Joined: Mon Feb 03, 2014 9:02 pm

Re: Getting interrupts from GPIO (in assembler)

Fri Mar 28, 2014 8:26 pm

colinh,
you aren't the only one confused by gpi_int[0]

I just spent a half hour trying to figure out what the fourth interrupt source was and whether it was 1-3 being used (correct) or 0-2.

I'm also wondering what gpio_init[0] does.

cdbrown
Posts: 1
Joined: Tue Apr 15, 2014 9:27 pm

Re: Getting interrupts from GPIO (in assembler)

Tue Apr 15, 2014 9:49 pm

I just got GPIO interrupts working in a bare metal environment after several nights of frustration, partly due to silly mistakes in my port numbers, partly because there seem to be several seemingly sensible ways to set up interrupt vectors which in reality don't work, but also because I was assuming that gpio_irq[1] was the right interrupt to use with GPIO 4, when in fact gpio_irq[1] doesn't get fired by events on low-numbered GPIO pins.

Interestingly, it looks like all four gpio_irqs are involved with GPIO interrupts. I experimented with triggering interrupts on GPIOs 4, 17, 30, 31, and 47 (using the SD card detection switch :)). The following table shows which interrupts were being fired when each gpio_irq was enabled:

Code: Select all

GPIO pin:           4    17   30   31   47
gpio_irq[0] (49)    Y    Y    Y    Y    N
gpio_irq[1] (50)    N    N    Y    Y    N
gpio_irq[2] (51)    N    N    N    N    Y
gpio_irq[3] (52)    Y    Y    Y    Y    Y
As others have observed, it looks like gpio_irq[3] is triggered for events on all GPIO pins. The remaining gpio_irqs 0-2 seem to correspond to three overlapping ranges of GPIO pins, not aligned with GPIO bank 0 and GPIO bank 1 as previously supposed.

I haven't tested exactly where these ranges begin and end. The main point to take away is that gpio_irq[0] a safe bet for GPIO interrupts because it covers all the GPIO ports exposed on the Pi (0-31).

SKyd3R
Posts: 11
Joined: Thu Nov 14, 2013 9:30 am

Re: Getting interrupts from GPIO (in assembler)

Mon Apr 21, 2014 3:31 pm

Can you know what interrupt does the handler call without checking the pending vectors?

Looking at the pending vectors you can get the pending interrupts but no the one that makes the handler call in order to attend only that interrupt.

Scorpion81
Posts: 1
Joined: Mon May 27, 2019 10:01 am

Re: Getting interrupts from GPIO (in assembler)

Mon May 27, 2019 10:05 am

Hi Colinh

I am trying to implement an interrupt on GPIO in assembly language for the Raspberry Pi 3 B+.
I miss some information in the code you have posted.

-What is IRQ_Base?
-IRQ_IRQ2_EN

In other words: I miss the data section of the code. Can you help me out?

Regards,
Patrick

colinh
Posts: 95
Joined: Tue Dec 03, 2013 11:59 pm
Location: Munich

Re: Getting interrupts from GPIO (in assembler)

Tue Jun 25, 2019 1:41 am

Sorry about the delay in replying -- I haven't logged on in years!

In case you haven't already figured it out, you need to read the "Peripherals" document for your chip. See

https://www.raspberrypi.org/documentati ... /README.md

I used the BCM2835. The IRQ register info is on p.112. (of the BCM2835 Peripherals doc). You need to read the first pages too which explain about ARM Physical Address and VideoCore IV addresses.

Then you need to read the documents for the subsequent chips to see what, if anything, changed. And something did...

Return to “Bare metal, Assembly language”