What do I mean by 'interrupt driven' ?
By design, the LCD board I have provides the state of the buttons to the application only when the application asks for it. This means that the application has to run a loop which continually asks the LCD board for which buttons are pressed. In an interrupt-driven world, the application will sit idle until a button is pressed, and then the LCD board will tell the application what has happened.
An analogy might be wanting to know when a particular time of day occurs. You could ring up the talking clock service every minute to check yourself. Or, you could set an alarm clock and let it tell you when it's the time you require, making you free to do other things in the mean time.
Why would I want 'interrupt driven' ?
It should be pretty clear already that having the application continuously polling the button state is wasteful of resources. Not only does it take up quite a bit of CPU time for the software, but it also occupies a significant portion of the available bandwidth on the i2c bus between the RPi and the LCD board. This is a problem for my project, since I also want to share that bus with one or more other devices which are more important than the LCD board and which also need to operate in (near to) real-time.
How did I make it work ?
The LCD board uses an i2c GPIO expander chip (an MCP23017) to interface between the RPi and the LCD itself. The buttons are set up as GPIO inputs on the expander chip. The board has been designed this way to minimise the number of connections required to the RPi. The MCP23017 actually has an interrupt facility for the GPIO inputs but due to the connections constraint they are not used. However, there's nothing stopping us hooking it up. The MCP23017 can be configured to raise an interrupt pin high when any of its GPIO inputs change. By connecting this signal to an RPi GPIO, we can detect when a button has been pressed.
In the above picture, I have connected the INTA pin from the MCP23017 to one of the plate's unused pins; note that this pin I'm using was chosen simply as being the closest to the INTA pin, no other reason. It also makes this plate pin-incompatible with the Arduino it was designed for, but that's fine by me since I plug it into a breadboard anyway:
Here's the complete hookup on the breadboard with the Pi cobbler cable going to to the RPi. I've connected the INTA pin through a small circuit described here which limits the 5V MCP23017 output to something safe for the RPi's 3V3 GPIO inputs. Here, I am using GPIO23. You can also see the i2c lines in orange at the top, as well as the red/black 5V supply. The blue and yellow wires are hooked up to my oscilloscope so that I can check the GPIO voltages on the interrupt line.
Now, even though I am claiming the buttons are now interrupt driven, this still isn't strictly true unfortunately. I still have to keep asking the GPIO interrupt signal if it is high, and then ask the MCP23017 why the interrupt was raised. So why did I bother? The RPi can read the interrupt signal from the GPIO pin much faster than it can read the button states from the MCP23017 over i2c. This means that I save i2c bandwidth for my DAC device(s).
Below is the code which I use to poll the GPIO interrupt signal and turn it into qt signals:
[pastacode provider="bitbucket" lang="c++" lines="293-331" user="doughammond" repos="modulosc" path_id="app/src/LCDDriver.h" revision="f8da8d9"/]
I also had to change the initialisation code to set up the MCP23017. The data sheet explained how to enable the INTA pin for the button inputs. Here is the new code:
[pastacode provider="bitbucket" lang="c++" lines="30-66" user="doughammond" repos="modulosc" path_id="app/src/LCDDriver.cpp" revision="f8da8d9"/]