Raspberry Pi: MCP23017 I2C IO-Expander mit Python steuern

Die Voraussetzungen für die Verwendung eines MCP23017 IO-Expanders mit einem Raspberry Pi habe ich ich bereits in meinem letzten Artikel beschrieben. Sind diese Voraussetzungen erfüllt, kann der IO-Expander neben den beschriebenen Tools auch mit diversen bestehenden Programmbibliotheken angesprochen werden. Ich habe mich für die Verwendung von Python entschieden.

Vorassetzungen

Für Python gibt es eine bestehende Bibliothek, die zu Erstellung eigener Anwendungen für den I2C-Bus herangezogen werden kann. Diese muss zunächst auf dem Raspberry Pi installiert werden:

#sudo apt-get update
#sudo apt-get install python-smbus

Um die I2C-Funktionen nutzen zu  können, muss der Programmcode so anfangen:

#!/usr/bin/python
import smbus

Die erste Zeile sorgt dafür, dass die Datei automatisch mit dem richtigen Interpreter ausgeführt wird. Dafür musss die Datei jedoch zunächst mit chmod ausführbar gemacht werden.

Die Zweite Zeile importiert die Klassenbibliothek smbus. (Andere Bezeichnung für I2C)

Funktionsbeschreibung

Für die Steuerung des MCP23017 habe ich folgende Klassen geschrieben:

  •  mcp23017: Enthält die Einstellungen für den Zugriff auf den I2C Bus sowie die Konfiguration der Register des IO-Expanders MCP23017.
  • pin: Stellt Funktionen zur Ansteuerung der einzelnen GPIO-Pins eines IO-Expanders zur Verfügung. Benötigt eine Instanz von mcp23017. Einzelne Pins können als Output, Input oder Tristate konfiguriert werden. Im Output-Modus kann der Wert gesetzt werden, im Input-Modus gelesen.

Quellcode

mcp23017.py:

class mcp23017:
def __init__(self,i2cbus=0,device=0x20):
self.device=device
self.bus = smbus.SMBus(i2cbus)
self.gpioa = {"register":0x12,"dirreg":0x00,"pureg":0x0c}
self.gpiob = {"register":0x13,"dirreg":0x01,"pureg":0x0d}
class pin:
def __init__(self,ioexp,register="gpioa",bit=0):
self.__ioexp=ioexp
self.__register=register
if self.__register=="gpioa":
self.__ioreg=self.__ioexp.gpioa
else:
self.__ioreg=self.__ioexp.gpiob
self.__register=self.__ioreg["register"]
self.__dirreg=self.__ioreg["dirreg"]
self.__pureg=self.__ioreg["pureg"]
self.__bit=bit
#default to tri state
self.setx()
def getbit():
return self.__bit
def getregister():
return self.__register
def getdirreg():
return self.__dirreg
def getpureg():
return self.__pureg
def setbit(newbit):
self.__bit=newbit
def setregister(newregister):
self.__register=newregister
def setdirreg(newdirreg):
self.__dirreg=newdirreg
def setpureg(newpureg):
self.__pureg=newpureg
def enable(self):
self.disable_bit(self.__dirreg,self.__bit)
self.enable_bit(self.__register,self.__bit)
#print self.value
def disable(self):
self.disable_bit(self.__dirreg,self.__bit)
self.disable_bit(self.__register,self.__bit)
def setinput(self):
self.enable_bit(self.__dirreg,self.__bit)
self.enable_bit(self.__pureg,self.__bit)
#Tri state mode, input and pullup resistor off
def setx(self):
self.enable_bit(self.__dirreg,self.__bit)
self.disable_bit(self.__pureg,self.__bit)
def value(self):
try:
returnvalue=self.__ioexp.bus.read_byte_data(self.__ioexp.device,self.__register) & 2**self.__bit
except:
print("Error reading bus")
return 3
if returnvalue==0:
return 0
else:
return 1
def enable_bit(self,register,bit):
try:
value_old =  self.__ioexp.bus.read_byte_data(self.__ioexp.device,register)
newvalue = value_old | 1<<bit
except:
print("Unable to read bus")
try:
self.__ioexp.bus.write_byte_data(self.__ioexp.device, register, newvalue)
except:
print("Unable to write bus")
def disable_bit(self,register,bit):
try:
value_old =  self.__ioexp.bus.read_byte_data(self.__ioexp.device, register)
newvalue = value_old & ~(1<<bit)
except:
print("Unable to read bus")
try:
self.__ioexp.bus.write_byte_data(self.__ioexp.device, register, newvalue)
except:
print("Unable to read bus")

Download mcp23017.py

Um die Funktionen set_bit und disable_bit zu verstehen, ist ein grundlegendes Verständnis von logischen Operatoren erforderlich. Einen guten Artikel dazu findet man z.B. hier.

Verwendungsbeispiel

Das folgende Programm lässt eine an GPA0 angeschlossene LED blinken:


#!/usr/bin/python
from mcp23017 import mcp23017,pin
from time import sleep

mymcp=mcp23017()
mypin=pin(mymcp,"gpioa",0)
while True:
mypin.enable()
sleep(1)
mypin.disable()
sleep(1)

 

Alternativen

Es gibt für den Raspberry Pi bereits fertige  Anwendungen für die Verwnedung von I2C. Zu nennen ist hier z.B. Quick2Wire. Um zu verstehen, wie das Ganze funktioniert, fand ich es aber hilfreich, den nötigen Programmcode selbst zu schreiben.

 Verwendungsmöglichkeiten

Es gibt eine Vielzahl von I2C Chips für die verschiedensten Anwendungsgebiete. Denkbar sind neben diversen Aktoren wie Roboterarmen und IO-Expandern  auch Sensoren für Temperatur, Helligkeit, Feuchtigkeit usw..  Im nächsten Artikel beschreibe ich, wie man die bisher beschriebene Hardware und Software für die Steuerung von Funksteckdosen verwenden kann.

4.75 avg. rating (94% score) - 4 votes

31 Gedanken zu “Raspberry Pi: MCP23017 I2C IO-Expander mit Python steuern

  1. Pingback: Vorbereiten des Raspberry Pi auf I2C | G-SURFG-SURF

  2. Pingback: Raspberry Pi: MCP23017 für die Steuerung von Funksteckdosen | G-SURFG-SURF

  3. Hallo,

    ich bin gerade dabei deine Gedanken zu adaptieren. Bei deiner mcp23017.py fehlen hier die Einrückungen, so dass es zwangsläufig zu einer Fehlermeldung des Python-Interpreters kommt. Kannst du die Originaldatei evtl. irgendwie zur Verfügung stellen. Ich habe zwar versucht die Einrückungen zu Ergänzen aber irgendwo steckt da immer noch ein Fehler drin.

    MfG!

    • Hallo,

      da du der zweite innerhalb von ein paar Tagen bist, der sich über kaputten Quellcode beschwert, muss ich mir wohl etwas einfallen lassen. Ich denke die File direkt als Download anzubieten macht Sinn. Ich passe das nachher an.

      Gruß

      Jan

    • Der Quellcode steht jetzt zum Download bereits. Den Link findest du direkt unter dem Code. Lass mich wissen, ob das Problem damit gelöst ist.

      Gruß

      Jan

    • Jepp jetzt funktioniert der Quellcode.
      Da ich totaler Neuling im Bereich Elektronik bin gab es zu Beginn ein paar Probleme.
      Mit i2cdetect verschwand der mcp unter der Adresse 0x20 und tauchte unter einer anderen auf, so dass Schreibzugriffe nur zufällig funktionierten. Bis ich dann mitbekommen habe, dass über die PIN’s 15 bis 17 die Adresse eingestellt wird.

      • Ok, auf die Adressierung bin ich echt nicht eingegangen. Die Verbindungen gehen aber zumindest aus dem Schaltplan hervor. Damit der MCP23017 sauber initialisiert wird, benötigt er -wie ich zwischenzeitlich feststellen musste- außerdem zu Beginn einen Reset. D.h. der Reset Pin muss kurz auf Masse gelegt werden. Dies kannst du erreichen, indem du den Pin über einen 100 nF Kondensator mit Masse verbindest (der 1K Pullup bleibt). Beim Einschalten leitet der Kondensator so lange, bis er geladen ist. Das reicht, um den Reset zuverlässig durchzuführen. Ich werde das bei Gelegenheit ergänzen. Wenn du nicht für einen sauberen Reset sorgst, kann es sein, dass der MCP23017 nach dem Einschalten nicht funktioniert.

  4. Hey,

    habe das mal getestet. Und es lief an sich super und funktionierte. Nur gibt es was ähnliches auch für den PCF8574? Also Weil mit diesem Teil kann man die Pins ja super einfach ansteuern.

    Nicht so umständlich wie sonst bei einigen Dingern.

    Michael

    • Ich kenne den PCF8574 nicht. Auf den ersten Blick scheint er etwas einfacher gestrickt zu sein. Du müsstest dich halt durch das Datenblatt arbeiten und dann den Code entsprechend anpassen. Fertig in der Schublade habe ich das leider nicht.

  5. Guten Abend,

    zunächst herzlichen Dank. Eines ist mir aufgefallen.Ich nutze folgendes Scenario: via Crontab startet ich alle Stunden einen Python Script welcher die einzelnen Auswertungen durchführt und dann entsprechend via MCP23017 Relais steuert und sich danach beendet.
    Jedoch jedesmal wenn der Script erneut gestartet wird, wird wie eine Art „Softwarereset“ durchgeführt und alle eventuell geschaltete Ausgänge werden auf LOW gesetzt. Aktuell behelfe ich mir mit dem Umweg vor Scriptbeendigung den aktuellen Status in eine Datei zu schreiben, und bei einem Neustart des Scriptes wieder einzulesen.

    Könnte man die mcp23017 Klasse nicht so umbauen, das die aktuellen Werte übernommen werden beim Start.

  6. Hm,

    das meine ich nicht. Ein Beispielprogramm:

    #!/usr/bin/python
    from mcp23017 import mcp23017,pin
    from time import sleep

    mymcp=mcp23017(1,0×21)
    mypin=pin(mymcp,“gpioa“,0)

    print mypin.value()
    sleep (5)
    mypin.enable()
    print mypin.value()

    – Start man diesen Script beim ersten mal ergibt die Ausgabe von Value eine 0, nach 5 Sekunden schaltet als Bsp. eine LED ein. Value ergibt 1. Der Script endet. Bei einem erneuten Start müsste eigentlich bereits das erste Value eine 1 ergeben. Aber die LED erlischt sofort bei Scriptstart und der erste Value ergibt wieder eine 0. Hier müsste aber die LED weiterleuchten und der erste Value eine 1 ergeben.
    .
    Der Befehl „mypin=pin(mymcp,“gpioa“,0)“ setzt bei der Abarbeitung den Kanal wieder auf 0, statt vorher seinen Wert auszulesen und zu belassen.

    Ich habe leider bei der Klasse keinen Hinweis gefunden, den Wert vor dem mypin=pin… auszulesen und so den korrekten vorhandenen Statzus zu übernehmen.

    Grüße Michael
    (Ich bin nicht der Michael vom April 2014)

    • Ah ok, so habe ich die Klasse selbst nie eingesetzt, weil ich ja ausschließlich die Funksteckdosen damit angesteuert habe. Deswegen ist die Init-Funktion der Klasse Pin eventuell etwas zu spezifisch geraten. Ich vermute es liegen an der Zeile

      #default to tri state
      self.setx()

      im Code. Kannst du das self.setx() mal mit einer Raute auskommentieren und das Ganze nochmal testen? Ich denke, diese Initialisierung des Pin-Values gehört eigentlich nicht in die Klasse, sondern sollte wenn nötig im Programm erfolgen. Es ist aber auch schon etwas her, dass ich den Code geschrieben habe. 😉

      Gruß

      Jan

  7. Hallo Jan,

    perfekt das war die Lösung. Jetzt merkt er sich die vorhandenen Einstellungen.

    Herzlichen Dank

    Michael

  8. Hallo Jan,
    ich fange auch gerade erst mit Python an.
    Kannst Du auch ein Beispiel geben, wie man ein Wert eines Pins ausliest?
    Wie werden die Pins für Input und Output definiert?

    Wie sieht es aus, wenn man 2 MCP23017 einsetzten möchte?

    Gruß Thomas

    • Hi,
      wenn du zwei MCP23017 einsetzen möchtest, musst du ihnen unterschiedliche Adressen geben. Das geschieht über die Adresspins (siehe Datenblatt) Im nächsten Post gibt es ein Anwendungsbeispiel: Hier klicken
      Du brauchst die Funktionen pin.setinput() und pin.value(). Lass mich wissen, falls du noch weitere Hilfe brauchst.

      • Hallo Jan,
        Danke für die Antwort.
        Ja, ich bräuchte wirklich noch Hilfe.
        Ich habe zwar früher viel Basic geschrieben und konnte das auch ohne Probleme lesen, aber mit Python tue ich mir richtig schwer (ist aber auch schon über 20 Jahre her)…..
        Ich möchte einzelne Pins als Ausgang oder Eingang setzten und dann Ein-/Ausschalten oder den Zustand auslesen…
        Das wäre ganz toll, wenn Du mir dafür kurze Beispiele schreiben könntest, welche ich dann hier ausprobieren kann…
        Ich habe da richtig ein Verständnisproblem…

        Vielen Dank für die Mühe.

        Thomas

        • Wie in dem Beispiel:

          Du fängst an und importierst die Klassen

          from mcp23017 import mcp23017,pin

          Dann definierst du einen mcp23017 als Instanz

          mcp23017_1=mcp23017(0,0×20)
          mcp23017_2=mcp23017(0,0×21) //Zweiter mcp mit anderer Adresse
          .
          .
          .

          Dann definierst du deine Pins. Die beziehen sich auf die vorher definierten mcps

          meinpin=pin(mcp23017_1,“gpiob“,1)

          gpiob ist dabei das IO-Register des mcp (siehe Datenblatt, der hat zwei, gpioa und b), 1 ist der pin bzw. das bit im register

          Jetzt hat der bin verschiedene Funktionen

          meinpin.setinput() mach den Pin zu einem Input-Pin. (Standard ist Output)

          Wenn ich jetzt seinen Wert wissen will (kann nur 0 oder 1 sein), mache ich ein

          pinwert=meinpin.value()

          Wenn ich von einem Output-Pin den Wert setzen will, brauche ich die Funktion meinpin.enable() bzw. meinpin.disable()

          Funktioniert das so bei dir?

          • Hallo,
            ich habe mich wg. eines Buches mit Python3 auseinander gesetzt. Python2.7 gerade erst deinstalliert….
            Die IDE findet das smbus Modul nicht….
            Gruß Thomas

          • Du musst das Paket python-smbus installieren. Also sudo apt-get install python-smbus

          • Hallo Jan,
            Nachdem ich die Hardware fertig habe, bin ich heute angefangen zu testen.
            Ich habe 3 MCP23017 eingebaut, die erkannt werden.
            Ich habe heute an einem MCP23017 eine Relaisstufe angeschlossen.
            Sie schaltet zwar invers, aber es funktioniert prima.
            Nun muss ich in den nächsten Tagen das auslesen auch testen…
            Gruß Thomas

          • Hallo Thomas,

            freut mich zu hören, dass es bei dir klappt. Melde dich, falls du beim Lesen der Werte Hilfe brauchst.

  9. Hallo Jan,
    leider bekomme ich folgende Meldung:
    Flie „/usr/lib/python3.2/mcp23017.py“, line 4,
    Import smbus
    ImportError: No module named smbus

    Bei der Installation der smbus.so wird ein python 2.6 minimalpack installiert. Da wird dann die smbus.so Datei installiert….
    Irgendwie geht’s nicht mit pyton 3.2

    Gruß Thomas

    • Hilft nur eine manuelle Installation, wenn es kein passendes Paket gibt. Müsste so gehen:

      wget http://ftp.de.debian.org/debian/pool/main/i/i2c-tools/i2c-tools_3.1.0.orig.tar.bz2
      tar xf i2c-tools_3.1.0.orig.tar.bz2
      cd i2c-tools-3.1.0/py-smbus
      cp smbusmodule.c smbusmodule.c.orig # make backup
      # copy the email code into smbusmodule.c
      python3 setup.py build
      sudo python3 setup.py install

      Aber warum willst du unbedingt Python 3.2 nehmen? Ist jetzt nicht SO verbreitet.

      • Hallo Jan,
        Ich hatte mir ein Buch gekauft. Das bezieht sich auf Python3.

        Das was Du oben geschrieben hast, habe ich ohne Erfolg schon durchgeführt.
        Ich werde das OS neu aufsetzten und dann mit Python 2.7 weiter machen.

        Wenn das dann läuft, gebe ich entsprechend Meldung.

        Danke für Deine Mühe.

  10. Hallo Jan,

    nachdem, dank Deines QuellCodes nun erfolgtreich 3xMCP alles steuern und regeln habe ich als letzte Ausbaustufe ein Display 2×16 an den 4.ten MCP angeschlossen. Ich möchte aber auch hier nicht auf Wiring setzen, sondern Deine Klasse benutzen. Hast Du bereits Erfahrung mit Deiner Klasse und Ansteuerung eines LCD’s.

    siehe „https://github.com/WiringPi/WiringPi2-Python/blob/master/examples/n5510-mcp23017.py“

    Grüße Michael

    • Hallo Michael,

      mit LCDs habe ich mich bisher leider nicht beschäftigt. Das Beispiel, dass du verlinkt hast, steuert das Display mit SPI an. Theoretisch wäre es möglich, SPI mit dem MCP in Software zu implementieren. Da der Raspberry Pi aber SPI nativ kann, dürfte es deutlich einfacher und stabiler sein, das Display direkt an den Pi anzuschließen.

      • Hallo,

        das ist nicht richtig es ging in dem Beispiel um den MCP23017 i an den ein Display angeschlossen wurde. Aber nichts für Ungut. Es klappt auch mit Deiner Klasse und ein paar Funktionen. Ist zwar noch nicht ganz fertig „softwaretechnisch“, aber es landen die ersten Zeichen auf meinem 2×16 Zeichen Display via I2C.

        Gibt Deine Klasse eine Funktion her,welcher ich entweder den HexCode oder eine 8Bit Folge gebe, und diese entsprechend H/L des jeweiligen PortA bzw. PortB Bereich schaltet. Also sprich als Beispiel: an GPIOA werden 0000001 gesendet. Entsprechend sind DB7 bis DB1 auf Low und DB0 auf High gesetzt. Ich konnte bisher nichts finden.. Bitte kurze Rückinformation

        Grüße Michael

        P.S: Die Klasse ist aber einfach Spitze. Danke

  11. Hallo Jan,
    ich würde deine Klasse gerne zum Steuern von ganzen Bytes verwenden und habe keine Idee, wie ich das ändern sollte. Hast Du da einen Ansatz?

    Grüße
    Andreas

    • Meine Klasse greift selbst auf die Funktion write_byte_data() zurück, die genau das tut. Wenn du die Konfiguration einheitlich lassen willst, kannst du meine MCP-Klasse trotzdem nutzen. Müsste so gehen:

      from mcp23017 import mcp23017

      mymcp=mcp23017()

      byte=0xff

      mymcp.bus.write_byte_data(mymcp.device, mymcp.gpioa, byte)

      Das müsste alle Bits von gpioa auf 1 setzen. Ich habe das jetzt nicht getestet, aber es sollte so funktionieren. 🙂

      • Hallo Jan,

        danke für die rasche Antwort. Es entsteht ein Fehler. In der letzten Zeile wird ein Integer erwartet. Das macht aber nix. Ich habe jetzt schon kleine Erfolge. Bin zwar erst seit Kurzem bei Python gelandet, aber deine Klasse ist mir viel klarer geworden. Ich werde deine Klasse als Basis für Neues verwenden.
        Gruß
        Andreas

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Spamschutz * Time limit is exhausted. Please reload the CAPTCHA.