GPIO and GPIO interrupts

Moderators: mdrjr, odroid

GPIO and GPIO interrupts

Unread postby Hundertvolt » Fri Nov 14, 2014 12:37 am

Hi,

I am currently porting a Python GPS module driver from a Raspberry Pi to Odroid U3. Don't worry about level shifters and the usual hardware suspects, they are all cared for ;)

For timing issues, the module outputs a 1 pulse-per-second synchronization TTL signal. At the Raspberry, this was connected to one GPIO. The Python script uses the RPI.gpio library to act on a pin-change interrupt, which is a clean and easy solution with low processing overhead and sub-microsecond accuracy.

I'd like to do the same or at least something similar on the U3 - but yet, I could not find any information on GPIO interrupts anywhere. Can you give me a hint?

Thanks in advance! :P
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby odroid » Fri Nov 14, 2014 12:03 pm

As far as I know, RPI interrupt implementation in WiringPI seems to a timer driven polling... not a real hardware interrupt handler.
If it is true.. I think you can make a simple thread to check the IRQ from GPS module.
User avatar
odroid
Site Admin
 
Posts: 25317
Joined: Fri Feb 22, 2013 11:14 pm
languages_spoken: English
ODROIDs: ODROID

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Sat Nov 15, 2014 1:59 am

It looks like I could find a solution! :D It is not yet tested thoroughly, but seems to work as expected.

First, you have to export the port:
Code: Select all
cd /sys/class/gpio/
echo 204 > export


in /sys/class/gpio/gpio204, you can find several virtual files. These are given according to Linux standards, which are very well described here:
https://www.kernel.org/doc/Documentation/gpio/sysfs.txt

Essential is: If you can find "direction", the processor and the kernel do support real interrupts! And for the U3, this file exists 8-) A little drawback is that it can only be written with superuser rights, but it's OK for testing.

The documents describes a "poll(2)" method and several handlers. Luckily, these functions are well represented within Python with the "select" module and by its methods "poll" and "epoll" - and here goes a test script which I got running (note that you have to run it using sudo ./gpiotest.py):
Code: Select all
#!/usr/bin/python
import os, time, select

GPIO_MODE_PATH= os.path.normpath('/sys/class/gpio/gpio204/direction')
GPIO_PIN_PATH=os.path.normpath('/sys/class/gpio/gpio204/value')
GPIO_EDGE_PATH=os.path.normpath('/sys/class/gpio/gpio204/edge')


file = open(GPIO_MODE_PATH, 'r+')
file.write("in")          ## make the pin an input
file.close()                 

file = open(GPIO_EDGE_PATH, 'r+')
file.write("both")          ## let the interrupt be triggered on rising and falling edges
file.close()                 

file = open(GPIO_PIN_PATH, 'r') ## open the virtual file

epoll = select.epoll()
epoll.register(file, select.EPOLLIN|select.EPOLLET) #register poll method and triggered events
while True:
    events = epoll.poll() #this line reads blocking
    for fileno, event in events:
        if fileno == file.fileno(): #check if interrupt comes from our handle
            print('Interrupt')


file.close()  ## Make sure to close the file when you're done!


It kept blocking until an event (pin-change) occured, and while blocking, there was no difference in the processor load in neither of the 4 cores compared to an idle OS, so it does not seem to be a hidden polling, but a true interrupt, as promised ;)
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Sat Nov 15, 2014 2:05 am

Yet another question... I have found something which is not so nice for hardware fiddling... :?

If I export GPIO 204 after booting up the O3, its standard direction is "out"! This is the pin I would like to use as an interrupt input (as all other pins are already reserved for other functions), but this forces me to connect a protective resistor in series with it.

Is there any way to ensure that a GPIO is never anything else than an input after booting?

BTW, GPIO 200 and 199 are inputs by default...
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby mdrjr » Sat Nov 15, 2014 10:27 pm

Yes, because we use 204 to prevent the ATMel to reset (on our U3 IO Shield).
If you want to rebuild the kernel I can point it to you.
mdrjr
Site Admin
 
Posts: 11640
Joined: Fri Feb 22, 2013 11:34 pm
Location: Brazil
languages_spoken: english, portuguese
ODROIDs: -

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Mon Nov 17, 2014 10:24 pm

This would be very cool! ;) So I would be grateful for a hint where I could change the bootup pin behaviour.

Thanks in advance! :)
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby mdrjr » Tue Nov 18, 2014 4:31 am

To reset the PIN 204 state..

Edit: https://github.com/hardkernel/linux/blo ... ung.c#L412
And remove Lines from 412 to 425.
Leave that function empty.


You can change the part that the kernel controls here:
https://github.com/hardkernel/linux/blo ... -samsung.c
mdrjr
Site Admin
 
Posts: 11640
Joined: Fri Feb 22, 2013 11:34 pm
Location: Brazil
languages_spoken: english, portuguese
ODROIDs: -

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Fri Nov 21, 2014 11:35 pm

I did not yet try the kernel compilation... Thank you anyway!

What I did was to rewire the lines on the I/O shield, so that the interrupt output of the GPIO expander triggers the pin-change interrupt (falling edge) of pin 204.

By this, it is possible to enable pin-change interrupts for the whole IO expander, meaning that whenever the interrupt is triggered, the IO chip is read exactly in time without any excess bus or processor load. :)
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby mdrjr » Sat Nov 22, 2014 6:08 am

I see... you are using INT0/INT1 to trigger GPIO204 ? So you get interrupts ?
mdrjr
Site Admin
 
Posts: 11640
Joined: Fri Feb 22, 2013 11:34 pm
Location: Brazil
languages_spoken: english, portuguese
ODROIDs: -

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Sat Nov 22, 2014 9:13 pm

Yes, I can use rising-edge, falling-edge and both together as an interrupt source.

I'd love to share some code with you about that - but currently the "full editor" of the forum is not working for me - neither on Firefox, Chrome or IE on three different computers (including the O3 ;) ) ... maybe you chould check that?
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby odroid » Sat Nov 22, 2014 11:09 pm

Our forum DB is little bit unstable now.

You can do "Quick Reply" and "Edit".
User avatar
odroid
Site Admin
 
Posts: 25317
Joined: Fri Feb 22, 2013 11:14 pm
languages_spoken: English
ODROIDs: ODROID

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Mon Nov 24, 2014 2:44 am

Okay, thanks! :)

So, well, what I first did on hardware side - on my O3 IO shield rev. 0.3 - was to remove the 5V pull-up resistor R2 from the INT1 pad and to replace it with a 3K3 pull-up to the 1.8V rail. The TCA6416 pulls this low when an interrupt is triggered. This, I connected to GPIO204 with another 3K3 resistor in series. This series resistor is for pure safety reasons - as I mentioned earlier, the GPIO204 is an output by default in the current kernel.

Then, I made this shell script for setting up the I2C interface, load the TCA6416 kernel module and configure GPIO204 as an input with falling-edge triggered interrupt:
Code: Select all
#!/bin/bash
echo 204 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio204/direction
echo falling > /sys/class/gpio/gpio204/edge
modprobe gpio-pca953x
modprobe i2c-gpio-custom bus0=4,200,199
echo tca6416 0x20 > /sys/devices/platform/i2c-gpio.4/i2c-4/new_device
echo 289 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio289/direction

This script has to be executed as superuser once before making any I/O operations with this setup.

In Python, the following code does the job - I am using the TCA6416 to read in a 1PPS time pulse for synchronizing external devices. timestamp() is a function used to get the system time. The pulse is connected to GPIO289, which is pin P00 in the IO shield.

Code: Select all
    irq = open('/sys/class/gpio/gpio204/value', 'r') # open interrupt pin
    pps = open('/sys/class/gpio/gpio289/value', 'r') # open timepulse pin

    epoll = select.epoll()
    epoll.register(irq, select.EPOLLIN|select.EPOLLET) #register poll method and triggered events
    last_irq = 0
    while True:
        events = epoll.poll() #this line reads blocking
        for fileno, event in events:
            if fileno == irq.fileno(): #check if interrupt comes from our handle
                irq_timestamp = timestamp() #get precise timestamp of the IRQ
                value = pps.read() #read the pin of the TCA6416
                pps.seek(0) #reset read position in virtual file system
                if ((value == "1\n") and (last_irq == 0)): #pin of TCA6416 had a rising edge
                    last_irq = 1 #set as triggered (to avoid retriggering if other pins cause an interrupt)
                     
                    ## here: do whatever needs to be done for processing the interrupt ##

                if (value == "0\n"): #falling edge: reset rising edge trigger
                    last_irq = 0


That's basically it - at the end, don't forget to close the virtual files ;)

The TCA6416 resets the interrupt as soon as its input registers are read OR if the input pin status returns back to its previous state, so the reading of the registers should be done as soon as possible after the interrupt occurs. After reading, the TCA6416 will memorize its input state and trigger the next interrupt as soon as some pin changes.

One drawback of this is that if you are using multiple input pins, the method of reading every single pin via the Linux virtual file system will read all registers again with each pin and mask the other ones out. This causes a lot of unnecessary bus load on the I2C, and will reset the interrupt with the first read pin. This behaviour is prone to glitches, especially if changes in the pins occur while reading.

So it would be a nice thing to read all pins at once, e.g. as a single 16bit or at least as two 8bit values. Do you know if this is possible within the Linux virtual file system?
If not, there is still the possibility of not using the kernel drivers for the TCA6416 and to access it directly via the I2C functionality of Python, for sure, but this would mean writing the driver in Python from scratch, as I could not find anything like this on the web...
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby odroid » Mon Nov 24, 2014 10:39 am

Very interesting development.
So I've changed it to "Sticky". ;)
User avatar
odroid
Site Admin
 
Posts: 25317
Joined: Fri Feb 22, 2013 11:14 pm
languages_spoken: English
ODROIDs: ODROID

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Thu Nov 27, 2014 7:19 am

OK, some news :)

As the TCA6416A is not such a complicated device, I wrote a small interface driver for Python which enables a "one interrupt, one read" strategy, instead of reading the whole device repeatedly for getting each pin.

With the Adafruit I2C library https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_I2C/Adafruit_I2C.py, it becomes very easy to do so. It contains some Raspberry Pi stuff, but this may be overridden at the initialization (it affects only the I2C bus number, which is 4 in our case).

Here comes all you need to read the TCA6416A byte-wise, low byte first:
Code: Select all
#!/usr/bin/python

from Adafruit_I2C import Adafruit_I2C

class TCA6416A(Adafruit_I2C):

    # Constants / Registers
    TCA6416A_BASE_ADDRESS = 0x20
    TCA6416A_INPUT_PORT_0 = 0x00
    TCA6416A_INPUT_PORT_1 = 0x01
    TCA6416A_OUTPUT_PORT_0 = 0x02
    TCA6416A_OUTPUT_PORT_1 = 0x03
    TCA6416A_POLARITY_INV_PORT_0 = 0x04
    TCA6416A_POLARITY_INV_PORT_1 = 0x05
    TCA6416A_CONFIG_PORT_0 = 0x06
    TCA6416A_CONFIG_PORT_1 = 0x07
   
    def __init__(self, addr=0x00, busnum=-1, debug=False):
        tca_addr = (self.TCA6416A_BASE_ADDRESS | addr) #set the logic status of the address pin
        self.tca6416 = Adafruit_I2C(tca_addr, busnum, debug)
       
    def read_inport(self):
        # Read the inputs
        list = self.tca6416.readList(self.TCA6416A_INPUT_PORT_0, 2)
        return list
     
    def write_outport(self, list):
        # Write the outputs
        self.tca6416.writeList(self.TCA6416A_OUTPUT_PORT_0, list)
       
    def read_outport(self):
        # Read the logic levels
        list = self.tca6416.readList(self.TCA6416A_OUTPUT_PORT_0, 2)
        return list

    def write_polarity(self, list):
        # Write the logic levels
        self.tca6416.writeList(self.TCA6416A_POLARITY_INV_PORT_0, list)       
       
    def read_polarity(self):
        # Read the logic levels
        list = self.tca6416.readList(self.TCA6416A_POLARITY_INV_PORT_0, 2)
        return list
       
    def write_config(self, list):
        # Write the I/O configs
        self.tca6416.writeList(self.TCA6416A_CONFIG_PORT_0, list)
       
    def read_config(self):
        # Read the I/O configs
        list = self.tca6416.readList(self.TCA6416A_CONFIG_PORT_0, 2)
        return list


As suggested in this thread http://forum.odroid.com/viewtopic.php?f=80&t=4307, I added
Code: Select all
# Use i2c-gpio-custom bus[0..3]=i2cbus-nr,sda-pin,sdc-pin
i2c-gpio-custom bus0=4,200,199
i2c-dev

to /etc/modules to get the smbus module running correctly.

The shell script for enabling the interrupt now looks much simpler:
Code: Select all
#!/bin/bash
echo 204 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio204/direction
echo falling > /sys/class/gpio/gpio204/edge


Here comes how to use it:
Code: Select all
#!/usr/bin/python
import os, time, select
from TCA6416A import TCA6416A

tca = TCA6416A(0x00, 4, False) #address pin is low, bus number 4
tca.write_outport( [0x00, 0x00] ) #set all output registers zero
tca.write_config( [0x01, 0x00] ) # set all unused pins to outputs to avoid interference (open inputs!)

irq = open('/sys/class/gpio/gpio204/value', 'r') # open interrupt pin
epoll = select.epoll()
epoll.register(irq, select.EPOLLIN|select.EPOLLET) #register poll method and triggered events
last_irq = 0
while True:
    events = epoll.poll() #this line reads blocking
    for fileno, event in events:
        if fileno == irq.fileno(): #check if interrupt comes from our handle
            irq_timestamp = timestamp() #get interrupt timestamp
            tca_inport = tca.read_inport() #read GPIO expander once
            pps_value = (tca_inport[0] & 0x01) # mask for pin 0 in array [0] (LSB)
            if ((pps_value == 1) and (last_irq == 0)): #pin has trigger value 1 and interrupt is armed
                last_irq = 1 #disarm interrupt until pin was 0 again
 
                   #do whatever you want to do if a TCA6416A input pin changed from 0 to 1
                   #just invert the "pps_value" checks to trigger to a falling edge

            if (pps_value == 0): #pin got back to 0
                last_irq = 0 #re-arm interrupt

            #here, further checks for other pins can be added by different masks for pps_value
            #all 16 pins may be checked with one single read operation for each interrupt


and that's all. Significantly reduces I2C bus load and the probability of glitches related to missing a change due to too many device reads which involuntarily reset an interrupt. :geek:
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Mon Dec 01, 2014 4:18 am

OK, weekend, kernel time. :geek: so, now to the GPIO204 initial state...

I did as you told me:
To reset the PIN 204 state..

Edit: https://github.com/hardkernel/linux/blo ... ung.c#L412
And remove Lines from 412 to 425.
Leave that function empty.


...and compiled and installed a new kernel as it is described in http://odroid.us/mediawiki/index.php?title=Step-by-step_Native_Compiling_a_Kernel

of course I used the branch odroid-3.8.y, and I did not touch the initramfs. I left the menuconfig options unchanged.
I rebooted after the "sync" command, and the U3 came up as usual :) , checking the date of the files in the /media/boot directory showed that they were updated correctly, uname -a confirmed this impression.
:!: One thing which is very important using this (very good!) tutorial: At least on the U3 you must copy the new kernel to /media/boot (not just to /boot, which also exists! Otherwise, you'll keep booting the old kernel.

And look what happened... GPIO204 is an input by default. :mrgreen:

So THANK YOU very much for this!

Nevertheless...
You can change the part that the kernel controls here:
https://github.com/hardkernel/linux/blo ... -samsung.c

what exactly can I set here? And how? I did not yet get the whole idea... :roll:
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby mdrjr » Mon Dec 01, 2014 8:24 am

Won't be just a couple lines :( You'll need a few hackery to setup some gpio's to a specific default state on boot.

It will requires a few if's because you can't touch other gpio's that might be in use by the OS.
So.. I would say to not follow this approach :)
mdrjr
Site Admin
 
Posts: 11640
Joined: Fri Feb 22, 2013 11:34 pm
Location: Brazil
languages_spoken: english, portuguese
ODROIDs: -

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Mon Dec 01, 2014 5:10 pm

Okay, so I got this correctly - for just having GPIO204 as an input - like the two other GPIOs - it is sufficient to remove the lines 412 to 425 in the samsung.c, with nothing to be done in the gpio-samsung.c.

That was all I asked for, it's working perfectly, so - thanks a lot!!! :D
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3

Re: GPIO and GPIO interrupts

Unread postby chjatala » Tue Feb 03, 2015 2:09 am

Do you have idea what is the maximu frequency at which your routine could service interrupts; without missing any. To this end, using the approach you have described, I did a little test in which I used GPIO199 as interrupt source and GPIO204 as the interrupt destination. The GPIO204 is configured in the as edge trigger mode. The GPIO199 is toggled with certain frequency. I found that, the epoll or poll methods to detect interrupt work start missing interrupts if the toggle frequency is about 1 KHz. Is this normal or I am missing something.
chjatala
 
Posts: 23
Joined: Mon Jul 14, 2014 8:07 pm
languages_spoken: english
ODROIDs: Odroid-u3

Re: GPIO and GPIO interrupts

Unread postby Hundertvolt » Wed Feb 25, 2015 5:06 pm

I don't really know about the max frequency - but as far as I can judge, this probably is an issue related to Python. With the method described, I also experienced some jitter of about 1ms, although I knew for sure my input signal was constant.
So far, for my application this is sufficient, so I did not do any further investigations in this issue - nevertheless, I will keep an eye open on that.

The capabilities of the GPIOs are somewhat higher. The CAN application I described in my other posts easily receives a whole vehicle CAN bus, with several CAN messages with 1ms period - so if you use a C function for getting the interrupts, I expect the max frequency to be much higher.
Hundertvolt
 
Posts: 28
Joined: Fri Nov 14, 2014 12:27 am
languages_spoken: english, german
ODROIDs: Odroid U3


Return to Hardware and peripherals

Who is online

Users browsing this forum: No registered users and 2 guests