User Tools

Site Tools


mcp23017

GPIO-Portexpander with MCP23017 and interrupts

Very often people talk about “turn something on/off” with a RaspberryPi, but you can only very rarely find something useful about reading many inputs from other systems.

I needed a solution, where I'm able to read around 16 inputs which are coming from my house (lights on/off, doorbell, motion detection sensors etc.). So I finally ended at the MCP23017, a very powerful I/O chip with I²C connection, this one is perfect for the RaspberryPi, so I designed a prototype in a first step. The wiring was pretty easy so far, but 16 “longer” wires on a small circuit board, well that takes some time as you might guess seeing the pics of the prototype.

Top of prototype:

Bottom of prototype:

A lot solutions in the web explain ways how to read a GPIO-pin from the RaspberryPi in a loop (like “while (true)”), read one or more portpins many many times and if the state of a pin suddenly changes an event is triggered. But by just using python GPIO-lib and the wiringPi lib, there are a lot more possibilities, like interrupts. I used the libs to activate and react on interrupts on one pin in a short python script. The base is explained in the artice about using Interrupts with RaspberryPi.

Project

The whole idea behind my project is to read many inputs by just using - or let's say “blocking” - only one of my RaspberryPi's GPIO pins. This is easily done by connecting a MCP23017 via I²C to the RaspberryPi GPIO header and connect one I/O-pin of the RPi to an interrupt pin of the MCP23017. Done. If one pin state changes (either of port A or port B) an interrupt is triggered, this triggers the Pi and the Pi just asks the state of the complete pin-sets of the MCP23017 which I (currently) finally store in a database.

First I have designed a protoype that fits on an RaspberryPi Model B and - as the GPIO headers start with the same functionality - it will also fit on a Model B+. It should also fit on a BananaPi, as far as I remember (but did not test yet).

I installed my prototype in the electric control cabinet of my house and it works like a charm.

So I decided to create a real board, where I do not have to solder so many wires. I designed it to be easily soldered and to be able to hook one on another. By using this technique you are able to connect up to 8 boards which gives you either 128 inputs or outputs or however you set them up (more are not possible, because there are only 8 different I²C addresses possible).


Prerequisites

For creating an own protoype, first get aligned with interrupts as described in the article about interrupts and ensure everything is up to date:

sudo apt-get update
sudo apt-get upgrade

Assuming python is also installed, the python I²C-communication skills are needed. Therefore there is a package “smbus” existing. In addition, python has to learn how set the GPIOs to interrupt enabled (if not already installed), so the following two packages have to be installed:

sudo apt-get install python-rpi.gpio
sudo apt-get install python-smbus

In this project all states are stored in a database, including a status table, where a watchdog is writing in each minute the current date and time. So mysql has to be installed too, beside python tools to use together with mysql:

sudo apt-get install mysql-server mysql-client mysql-common 
sudo apt-get install python-mysqldb

Now it has to be ensured that I²C is working on the Raspberry Pi. If “rpi-update” was run several times before, it might(!) be challengeing to get the bus up and running, as a lot changed there during the last updates. I will not discuss and explain that here in detail, but when having the I²C-tools installed, the bus could easily be examined and give results like this:

$ sudo apt-get install i2c-tools
$ i2cdetect -y 0
Error: Could not open file `/dev/i2c-0' or `/dev/i2c/0': No such file or directory
$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

Bus 1 is the only interesting one, so having the first one (bus 0) failing, it does not matter right now. Important is, that address 0x20 is free here, which is proved by the “–”.

If the MCP23017 is later installed with the default address 0x20 it will look like:

$ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- 
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                 

If the 20 will show up there, it's ensured, that the I²C connection via SDA and SCL is working so far.

By the way the /etc/modules file could look like this, to be prepared for I²C and 1wire (if interested in), there might also be dependencies with cameras and 1wire and I²C, so reading here might also be interesting, when beeing in trouble:

# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

wire
w1-gpio
w1-therm
i2c-bcm2708
i2c-dev

Now all prerequisites should be done so far.


MCP23017 with I²C

The MCP23017 is an I/O chip with 16 pins (split over two ports A and B with each having 8 pins), that can be set either as IN or OUT pins.

By Googleing the datasheet you get the most information out of the datasheet. Here you also find an overview about the register of the chip and you can easily find out how to init the MCP23017 to work as needed.

This screenshot from the datasheet shows the main interesting table:

The meaning of the register's names are explained on further pages. Here the focus is primarily on these values:

  • IODIRA and IODIRB –> 0x00 and 0x01 –> Define the I/O pins direction for port A and B
  • GPPUA and GPPUB –> 0x0C and 0x0D –> Activate internal pullup resistors for port A and B
  • GPINTENA and GPINTENB –> 0x04 and 0x05 –> Activate the interrupts for OnChange on both ports
  • IOCON –> 0x0A and 0x0B –> Connect the interrupts from both ports together on one (MIRROR-Bit)
  • GPIOA and GPIOB –> 0x12 and 0x13 –> Read port A/port B

Whenever to read the status of one port, it's just neccessary to read 0x12 for GPIOA (or 0x13 for GPIOB), which could be done like this for testing purposes:

pi@RaspberryPi ~ $ sudo i2cget -y 1 0x20 0x12
0xe6

Here 0x20 is the default chip address and 0x12 represents the complete port A.

The hex result 0xe6 equals decimal 230, which is not really giving any useful value, but as binary it makes the situation transparant: 1110 0110

That means, there are three pins pulled to ground and five pins pulled to high:

Bit 7 = 1
Bit 6 = 1
Bit 5 = 1
Bit 4 = 0
Bit 3 = 0
Bit 2 = 1
Bit 1 = 1
Bit 0 = 0

These bits represent the status of the pins of port A.

Init MCP23017

When the MCP23017 is correctly installed, it has to be initiated via I²C to be prepared to trigger interrupts and to define all pins as IN-pins and so on. Here it's very useful to have the datasheet beside. Without the datasheet the setup would have been impossible, as the right registers/addresses to setup have to be known. Googleing the datasheet should be the fasted way to find it, if not already done within the former step.

So the following steps are done to work in a setup as needed for the project to read 16 inputs and trigger an interrupt, whatever pin changes its status:

First verify the MCP23017 is there (Expecting address 0x20):

sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- UU -- -- -- -- 
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    


Define complete Port A as IN-Port:

sudo i2cset -y 1 0x20 0x00 0xFF


Define complete Port B as IN-Port:

sudo i2cset -y 1 0x20 0x01 0xFF


Activate all internal pullup resistors at Port A:

sudo i2cset -y 1 0x20 0x0C 0xFF


Activate all internal pullup resistors at Port B:

sudo i2cset -y 1 0x20 0x0D 0xFF


Activate Interrupt OnChange for Port A:

sudo i2cset -y 1 0x20 0x04 0xFF


Activate Interrupt OnChange for Port B:

sudo i2cset -y 1 0x20 0x05 0xFF


Connect Interrupt-Pin A with the one of B (MIRROR = 1):

sudo i2cset -y 1 0x20 0x0A 0x40


Connect Interrupt-Pin B with the one of A (MIRROR = 1):

sudo i2cset -y 1 0x20 0x0B 0x40


Test: Read GPIO-Byte from Port A:

sudo i2cget -y 1 0x20 0x12


Test: Read GPIO-Byte from Port B:

sudo i2cget -y 1 0x20 0x13

A small hint by the side, the commands of “i2c-tools” work like this, for more refer to help/man:

sudo i2cset -y 1 <AddrOfTheDevice> <FunctionFromDataSheet> <ValueBeeingAssignedTo>
sudo i2cget -y 1 <AddrOfTheDevice> <ValueFromDataSheet>


By the way, all this could also be done with python, which might speed up the procedure:

import smbus
bus = smbus.SMBus(1)    # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
 
DEVICE_ADDRESS = 0x20
bus.write_byte_data(DEVICE_ADDRESS, 0x00, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x01, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x0c, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x0d, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x04, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x05, 0xFF)
bus.write_byte_data(DEVICE_ADDRESS, 0x0a, 0x40)
bus.write_byte_data(DEVICE_ADDRESS, 0x0b, 0x40)
bus.read_byte_data(DEVICE_ADDRESS, 0x12)
bus.read_byte_data(DEVICE_ADDRESS, 0x13)

Remind

  • Every time the MCP23017 looses it's power, the init steps have to be done again!
  • Don't forget to read the ports at the end of the init procedure as the reading resets the interrupt signal! Doing this ensures to have a clean start afterwards.

Personally designed MCP23017 PCB

To use the MCP23017 in a useful setup on the Pi, it requires quite some time to wire all the connections from the 2×8 pins. To save the time, I desigend my own circuit board with eagle, which perfectly fits on a usual model B (and probably also on a model A or a model B+):

The circuitplan is quite easy to setup and does not require any special skills except concentration - when having read the datasheet:

With this plan, it took me some time to design the layout and wire as effective as possible. So I finally desigend this two layer layout:

The board has one MCP23017 (PDIP) installed and 16 connectors for the corresponding I/Os. For each set of connectors of one port, ground is also set on a connector pin. This makes it easier to connect one pin to ground as ground is right there available.

The connector in the middle is prepared for 1wire devices like a 1wire temperature sensor. Ground, 3.3V and the typical datapin (Pin 7) are prepared here. A DS18S20 temperature sensor could easily be connected to the connector here and will work out of the box with the standard rasbian 1wire modules.

The board has five sets of jumpers. Within the three-headed ones the I²C address of the MCP23017 has to be set by either setting the addresspins to ground or high level. All jumpers set to ground set the address to 0x20. (All the examples in this documentation are based on 0x20.)

The other two jumpers are prepared for the interrupt pins of port A or port B of the MCP23017. This opens the possibility to either use the MCP23017's pins as IN-put or OUT-put pins. If the board is used for OUT-pins only, no interrupts are neccessary and the interrupt pins could stay unconnected, so they can be used with other functionalities on the RaspberryPi. As one port or even one single pin can be used as either IN or OUT, the usage of the two possible interrupts is totally free. So feel free to jumper the interrupt, that is needed - or even jumper them both. I soldered usage of interrupt A as I don't need the pin for other things.

Pin-Setup

The following RaspberryPi pins are used:

  • I²C:
    • SDA → PIN 3 → GPIO 2 → Name: SDA
    • SCL → PIN 5 → GPIO 3 → Name: SCL
  • Interrupts:
    • Interrupt A → PIN 18 → GPIO 24 → Name: GPIO 5
    • Interrupt B → PIN 22 → GPIO 25 → Name: GPIO 6
  • 1wire bus:
    • 1wire → PIN 7 → GPIO 4 → Name: GPIO 7

“GPIO x” is used as described vor Rev2, when using on a normal model B RaspberryPi. Please always refer to this table:

+----------+-Rev2-+------+--------+
| wiringPi | GPIO | Phys | Name   |
+----------+------+------+--------+
|      0   |  17  |  11  | GPIO 0 |
|      1   |  18  |  12  | GPIO 1 |
|      2   |  27  |  13  | GPIO 2 |
|      3   |  22  |  15  | GPIO 3 |
|      4   |  23  |  16  | GPIO 4 |
|      5   |  24  |  18  | GPIO 5 |
|      6   |  25  |  22  | GPIO 6 |
|      7   |   4  |   7  | GPIO 7 |
|      8   |   2  |   3  | SDA    |
|      9   |   3  |   5  | SCL    |
|     10   |   8  |  24  | CE0    |
|     11   |   7  |  26  | CE1    |
|     12   |  10  |  19  | MOSI   |
|     13   |   9  |  21  | MISO   |
|     14   |  11  |  23  | SCLK   |
|     15   |  14  |   8  | TxD    |
|     16   |  15  |  10  | RxD    |
|     17   |  28  |   3  | GPIO 8 |
|     18   |  29  |   4  | GPIO 9 |
|     19   |  30  |   5  | GPIO10 |
|     20   |  31  |   6  | GPIO11 |
+----------+------+------+--------+


Remind

When using the MCP23017 pins as OUT-pins, don't use them directly as they are only delivering very low current. When using in OUT-mode, send the signals e.g. into optocouplers or create transistor amplifier ciruits. This prevents the MCP23017 from beeing killed.


Database Setup

This step is of course optional and is only needed when the values, that have changed, shall be inserted and stored into a database - probably to be interpreted by other software afterwards.

If a MySQL-DB is locally installed on the RaspberryPi - as described before - two tables have to be set up, to work with the script at the end of this article:

  • status → stores every e.g. 60 seconds an updated timestamp, that could be interpreted/validated by a watchdog (not implemented yet) to ensure, the script is still running and did not crash
  • ioevents → stores all event changes with date and time from every single pin of port A and B

As my setup is German, you might recognize some german in the generated scripts. Overall, these scripts should be easily imported any current MySQL-DB and the comments can either be changed or ignored:

createStatusSQL.sql
-- Host: localhost
-- Erstellungszeit: 16. Dez 2014 um 19:46
-- Server Version: 5.5.40
-- PHP-Version: 5.4.35-0+deb7u2
-- Author: Dipl.-Inform. (FH) Andreas Link
 
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
 
--
-- Datenbank: `homebus`
--
 
-- --------------------------------------------------------
 
--
-- Tabellenstruktur für Tabelle `status`
--
 
DROP TABLE IF EXISTS `status`;
CREATE TABLE IF NOT EXISTS `status` (
  `entry` tinyint(3) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'Prim-Key mit max einem Entry',
  `watchdog` TIMESTAMP NULL DEFAULT NULL COMMENT 'Timestamp als Watchdog-Erkennung',
  PRIMARY KEY (`entry`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Status des Homebus';

The IOevents table stores the history of all events having triggered an interrupt, so all changes are currently stored. This might not be relevant for every project, but I like having the history (here I can see, when the postman rang the bell :-)).

createIOeventsSQL.sql
-- Host: localhost
-- Erstellungszeit: 16. Dez 2014 um 19:41
-- Server Version: 5.5.40
-- PHP-Version: 5.4.35-0+deb7u2
-- Author: Dipl.-Inform. (FH) Andreas Link
 
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
 
--
-- Datenbank: `homebus`
--
 
-- --------------------------------------------------------
 
--
-- Tabellenstruktur für Tabelle `ioevents`
--
 
DROP TABLE IF EXISTS `ioevents`;
CREATE TABLE IF NOT EXISTS `ioevents` (
  `entry` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'PrimaryKey Autoincrement',
  `timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Zeitpunkt des Eintrages',
  `A0` tinyint(3) UNSIGNED NOT NULL,
  `A1` tinyint(3) UNSIGNED NOT NULL,
  `A2` tinyint(3) UNSIGNED NOT NULL,
  `A3` tinyint(3) UNSIGNED NOT NULL,
  `A4` tinyint(3) UNSIGNED NOT NULL,
  `A5` tinyint(3) UNSIGNED NOT NULL,
  `A6` tinyint(3) UNSIGNED NOT NULL,
  `A7` tinyint(3) UNSIGNED NOT NULL,
  `B0` tinyint(3) UNSIGNED NOT NULL,
  `B1` tinyint(3) UNSIGNED NOT NULL,
  `B2` tinyint(3) UNSIGNED NOT NULL,
  `B3` tinyint(3) UNSIGNED NOT NULL,
  `B4` tinyint(3) UNSIGNED NOT NULL,
  `B5` tinyint(3) UNSIGNED NOT NULL,
  `B6` tinyint(3) UNSIGNED NOT NULL,
  `B7` tinyint(3) UNSIGNED NOT NULL,
  PRIMARY KEY (`entry`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='Verlauf aller über Interrupts ausgeloesten Events' AUTO_INCREMENT=1 ;

These scripts might easily be executed within phpMyAdmin in a database of your choice. Please remind the DROP TABLE IF EXISTS in before of every script. Just to ensure, nothing is deleted, that might already be existing. Setting up a dedicated DB-user could be worthwhile but is not neccessary.


Python Script

This is the final python script running on my RaspberryPi in the electric control cabinet; it includes all former steps and requires all former described steps to be executed.

If the script is started, it initiates the MCP23017, inititiates the DB connection and updates every 60 seconds aka every minute the timestamp in the status table. This could be used to be interpreted by a watchdog service, if the watchdog detects an entry older than 2 minutes, the script probably might have crashed.

Whenever any portpin of the MCP23017 is pulled to ground, an interrupt is triggered. Within the execution of the trigger, first port A and then port B is read and their current states are stored into the DB. After reading the ports, the interrupt is automatically reset.

When using this script or modifying it, it would be nice to refer to this website, thank you.

MCP23017-status-script.py
#!/usr/bin/python
 
# Author: Dipl.-Inform. (FH) Andreas Link - www.AndreasLink.de
# Website: http://raspberrypi.link-tech.de/
 
import RPi.GPIO as GPIO
import sys
import smbus
import time
import MySQLdb
 
# Init GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(25, GPIO.IN, pull_up_down=GPIO.PUD_UP)
 
# Init I2C-bus
bus = smbus.SMBus(1)    # 0 = /dev/i2c-0 (port I2C0), 1 = /dev/i2c-1 (port I2C1)
 
DEVICE_ADDRESS = 0x20  # MCP23017 is without extra Jumper on 0x20 
entprellVar = 0 # Used for "entprellen" aka bouncing
db = 0   # Database connect
curs = 0 # Cursor pointing on MySQL-DB to execute commands
 
def initMySql():
	global db, curs
	print "Init MySQLDB: host 'localhost', user 'dbusername', DB 'homebus'"
	db = MySQLdb.connect("localhost", "dbusername", "dbpassword", "homebus")
	curs=db.cursor()
	print "MySQL-DB init done"
 
def logIOevent(a0, a1, a2, a3, a4, a5, a6, a7, b0, b1, b2, b3, b4, b5, b6, b7):
	global db, curs
	try:
		curs.execute("INSERT INTO `homebus`.`ioevents` (`entry`,`timestamp`,`A0`,`A1`,`A2`,`A3`,`A4`,`A5`,`A6`,`A7`,`B0`,`B1`,`B2`,`B3`,`B4`,`B5`,`B6`,`B7`) VALUES (NULL,CURRENT_TIMESTAMP,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s);", (str(a0),str(a1),str(a2),str(a3),str(a4),str(a5),str(a6),str(a7),str(b0),str(b1),str(b2),str(b3),str(b4),str(b5),str(b6),str(b7)))
		db.commit()	
	except:
		# If conction fails, try to reconnect the DB
		print "Connection to DB lost, trying to reconnect..."
		initMySql()
 
def initMCP23017():
    print "Init MCP23017 (", hex(DEVICE_ADDRESS), ")"
    bus.write_byte_data(DEVICE_ADDRESS, 0x00, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x01, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x0c, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x0d, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x04, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x05, 0xFF)
    bus.write_byte_data(DEVICE_ADDRESS, 0x0a, 0x40)
    bus.write_byte_data(DEVICE_ADDRESS, 0x0b, 0x40)
    bus.read_byte_data(DEVICE_ADDRESS, 0x12)
    bus.read_byte_data(DEVICE_ADDRESS, 0x13)
    print "MCP23017 init done!"
 
# Init MCP23017 all as IN and ALL with Interrupt
initMCP23017()
 
# MySSQL-DB init
initMySql()
 
 
# Interrupt Routine, beeing called, when interrupt happens
def interrupt_routine(callback):
    global entprellVar
    if entprellVar == 0:
		entprellVar = 1
 
		print time.strftime("%d.%m.%Y %H:%M:%S"), "Interrupt triggered... reading I2C-bus:"
		time.sleep(0.8) # Relax briefly before reading bus
		portA = bus.read_byte_data(DEVICE_ADDRESS, 0x12)
		portB = bus.read_byte_data(DEVICE_ADDRESS, 0x13)
 
		# Print raw-data
		print "PortA:", bin(portA)
		print "PortB:", bin(portB)
 
		# Try to determine which bit is set:
		print ""
		print "Port A:"
 
		###### Port A - Pin 0 #####
		hexMask = 0x01 #Pin0 0000 0001
		hexAND0A = hexMask & portA #AND
		if int(hexAND0A) == 0:		
		    print "A-Pin 0 = true"
		    dbA0 = 1
		else:
		    print "A-Pin 0 = false"
		    dbA0 = 0
		##### ----- #####
 
		###### Port A - Pin 1 #####
		hexMask = 0x02 #Pin1 0000 0010
		hexAND1A = hexMask & portA #AND
		if int(hexAND1A) == 0:
		    print "A-Pin 1 = true"
		    dbA1 = 1
		else:
		    print "A-Pin 1 = false"
		    dbA1 = 0
		##### ----- #####
 
		###### Port A - Pin 2 #####
		hexMask = 0x04 #Pin2 0000 0100
		hexAND2A = hexMask & portA #AND
		if int(hexAND2A) == 0:
		    print "A-Pin 2 = true"
		    dbA2 = 1
		else:
		    print "A-Pin 2 = false"
		    dbA2 = 0
		##### ----- #####
 
		###### Port A - Pin 3 #####
		hexMask = 0x08 #Pin3 0000 1000
		hexAND3A = hexMask & portA #AND
		if int(hexAND3A) == 0:
		    print "A-Pin 3 = true"
		    dbA3 = 1
		else:
		    print "A-Pin 3 = false"
		    dbA3 = 0
		##### ----- #####
 
		###### Port A - Pin 4 #####
		hexMask = 0x10 #Pin4 0001 0000
		hexAND4A = hexMask & portA #AND
		if int(hexAND4A) == 0:
		    print "A-Pin 4 = true"
		    dbA4 = 1
		else:
		    print "A-Pin 4 = false"
		    dbA4 = 0
		##### ----- #####
 
		###### Port A - Pin 5 #####
		hexMask = 0x20 #Pin5 0010 0000
		hexAND5A = hexMask & portA #AND
		if int(hexAND5A) == 0:
		    print "A-Pin 5 = true"
		    dbA5 = 1
		else:
		    print "A-Pin 5 = false"
		    dbA5 = 0
		##### ----- #####
 
		###### Port A - Pin 6 #####
		hexMask = 0x40 #Pin6 0100 0000
		hexAND6A = hexMask & portA #AND
		if int(hexAND6A) == 0:
		    print "A-Pin 6 = true"
		    dbA6 = 1
		else:
		    print "A-Pin 6 = false"
		    dbA6 = 0
		##### ----- #####
 
		###### Port A - Pin 7 #####
		hexMask = 0x80 #Pin7 1000 0000
		hexAND7A = hexMask & portA #AND
		if int(hexAND7A) == 0:
		    print "A-Pin 7 = true"
		    dbA7 = 1
		else:
		    print "A-Pin 7 = false"
		    dbA7 = 0
		##### ----- #####
 
		print ""
		print "Port B:"
 
		###### Port B - Pin 0 #####
		hexMask = 0x01 #Pin0 0000 0001
		hexAND0B = hexMask & portB #AND
		if int(hexAND0B) == 0:
		    print "B-Pin 0 = true"
		    dbB0 = 1
		else:
		    print "B-Pin 0 = false"
		    dbB0 = 0
		##### ----- #####
 
		###### Port B - Pin 1 #####
		hexMask = 0x02 #Pin1 0000 0010
		hexAND1B = hexMask & portB #AND
		if int(hexAND1B) == 0:
		    print "B-Pin 1 = true"
		    dbB1 = 1
		else:
		    print "B-Pin 1 = false"
		    dbB1 = 0
		##### ----- #####
 
		###### Port B - Pin 2 #####
		hexMask = 0x04 #Pin2 0000 0100
		hexAND2B = hexMask & portB #AND
		if int(hexAND2B) == 0:
		    print "B-Pin 2 = true"
		    dbB2 = 1
		else:
		    print "B-Pin 2 = false"
		    dbB2 = 0
		##### ----- #####
 
		###### Port B - Pin 3 #####
		hexMask = 0x08 #Pin3 0000 1000
		hexAND3B = hexMask & portB #AND
		if int(hexAND3B) == 0:
		    print "B-Pin 3 = true"
		    dbB3 = 1
		else:
		    print "B-Pin 3 = false"
		    dbB3 = 0
		##### ----- #####
 
		###### Port B - Pin 4 #####
		hexMask = 0x10 #Pin4 0001 0000
		hexAND4B = hexMask & portB #AND
		if int(hexAND4B) == 0:
		    print "B-Pin 4 = true"
		    dbB4 = 1
		else:
		    print "B-Pin 4 = false"
		    dbB4 = 0
		##### ----- #####
 
		###### Port B - Pin 5 #####
		hexMask = 0x20 #Pin5 0010 0000
		hexAND5B = hexMask & portB #AND
		if int(hexAND5B) == 0:
		    print "B-Pin 5 = true"
		    dbB5 = 1
		else:
		    print "B-Pin 5 = false"
		    dbB5 = 0
		##### ----- #####
 
		###### Port B - Pin 6 #####
		hexMask = 0x40 #Pin6 0100 0000
		hexAND6B = hexMask & portB #AND
		if int(hexAND6B) == 0:
		    print "B-Pin 6 = true"
		    dbB6 = 1
		else:
		    print "B-Pin 6 = false"
		    dbB6 = 0
		##### ----- #####
 
		###### Port B - Pin 7 #####
		hexMask = 0x80 #Pin7 1000 0000
		hexAND7B = hexMask & portB #AND
		if int(hexAND7B) == 0:
		    print "B-Pin 7 = true"
		    dbB7 = 1
		else:
		    print "B-Pin 7 = false"
		    dbB7 = 0
		##### ----- #####
 
		print ""
 
		# Log Event in DB
		logIOevent(dbA0, dbA1, dbA2, dbA3, dbA4, dbA5, dbA6, dbA7, dbB0, dbB1, dbB2, dbB3, dbB4, dbB5, dbB6, dbB7)
 
		time.sleep(1)
		entprellVar = 0
    else:
		print "Ignoring double interrupt"
 
# Init Interrupt-Callback-Function
GPIO.add_event_detect(25, GPIO.FALLING, callback=interrupt_routine)
 
while True:
	try:
		print time.strftime("%d.%m.%Y %H:%M:%S"), "Watchdog activ -> Waiting for a new interrupt..."
		curs.execute("UPDATE `homebus`.`status` SET `watchdog`=NOW() WHERE `status`.`entry`=0;")
		db.commit()	
	except:
		# If conction fails, try to reconnect the DB
		print "Lost DB-connection, trying to reconnect..."
		initMySql()		    
    	entprellVar = 0
 
	time.sleep(60)
 
db.close()
GPIO.cleanup()

I also found another very good german(!) source for reading about the MCP23017 and I²C: http://www.elektronx.de/tutorials/porterweiterung-mit-mcp23017-und-i2c/


Thank you for reading, implementing and testing :-). Feel free to improve and extend these scripts (I'm not a python professional, so there is a lot room to improve) with own ideas.

Remind: This page/article is still in progress, I might probably extend or optimize some sections.

Feedback could be returned via Google+.

mcp23017.txt · Last modified: 11.03.2017 12:27 by andreas