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:
[python]
#!/usr/bin/python
import smbus
[/python]
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:
[python]
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")
[/python]
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:
[python]
#!/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)
[/python]
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.
Pingback: Vorbereiten des Raspberry Pi auf I2C | G-SURFG-SURF
Pingback: Raspberry Pi: MCP23017 für die Steuerung von Funksteckdosen | G-SURFG-SURF
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.
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.
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.
Die Klasse gibt das eigentlich längst her. Mit newvalue = value_old | 1<
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
Hallo Jan,
perfekt das war die Lösung. Jetzt merkt er sich die vorhandenen Einstellungen.
Herzlichen Dank
Michael
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.
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.
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
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
Wie funktioniert das mit den Pins als Eingang?