Raspberry Pi: Porterweiterung mit MCP23017 und I2C

Hier möchte ich auf die Erweiterung der GPIO Leiste des Raspberry Pi mit Hilfe des Portexpanders  “MCP23017” eingehen, sowie die Ansteuerung über I²C.

Als erstes benötigen wir das Bauteil.
Z.B bei Reichelt.de oder gleich fünf Stück mit Sockel bei Amazon.

Lange hat es gedauert bis ich mich an I²C herangetraut habe. Nun habe ich mich hingesetzt und es überraschend schnell verstanden.

Ein großes Manko war eine fehlende, verständliche und logische Anleitung. Bisher konnte ich leider nichts finden, was meine Fragen komplett auf einer Seite beantwortet. Daher möchte ich nun versuchen dies hier zu lösen.

Sehr wichtig bei diesem Thema ist das Verständnis des Bausteins und des I²C Protokolls.
Ich fange mal ganz vorne an:

Der MCP23017 ist ein Baustein, welcher die Aus- und Eingänge des RPis erweitert. Der Baustein kommuniziert mit dem Pi per I²C.
Demnach “opfere” ich also 2 Pins des RPis (SDA und SCL) und erhalte dafür aber 16 neue Ein- bzw. Ausgänge.

Verdrahtung

Pin 9 und 18 des Chips werden mit 3,3V des Pi verbunden.

MCP23017 optional mit LED und Taster

MCP23017 optional mit LED und Taster

Pins 10,15,17 des Chips werden mit GND des Pi verbunden.
Pin 12 des Chips wird mit Pin 5 (SCL) des Pi verbunden.
Pin 13 des Chips wird mit Pin 3 (SDA) des Pi verbunden.

Freischalten der I²C Treiber

Nun aktivieren wir I²C im System.

raspi-blacklist.conf bearbeiten per:

sudo nano /etc/modprobe.d/raspi-blacklist.conf

Folgende Zeile durch ein #-Zeichen auskommentieren:

blacklist i2c-bcm2708

Die neue Zeile sieht somit so aus:

#blacklist i2c-bcm2708

Datei speichern und schließen.

Nun die Datei /etc/modules per

sudo nano /etc/modules

öffnen und 2 Zeilen einfügen:

i2c-dev
i2c-bcm2708

Datei speichern und schließen.

Nachtrag für Raspberry Pi 2:

Beim Raspberry Pi 2 ist lediglich eine Zeile in der /boot/config.txt notwendig um Zugang zum I²C-Bus zu bekommen. Fügt dazu diese Zeile an das Ende der config.txt

dtparam=i2c_arm=on

Datei speichern und schließen und den Raspberry Pi neu starten. Danach ist der I²C-Bus auch auf dem Raspberry Pi 2 bzw dem Kernel 3.18 verfügbar. Eine Änderung der raspi-blacklist.conf ist nicht mehr notwendig. Ursache dafür ist der neue Device-Tree. Mehr dazu z.B. hier:

https://raspiprojekt.de/anleitungen/hardware/154-geraetetreiber-und-device-tree.html

Nun in der Shell das Paket “i2c-tools” installieren.

# apt-get update
# apt-get install i2c-tools

Um mit I²C in Python zu arbeiten wird noch das Paket “smbus” benötigt. Auch dies wieder mit dem Paketmanager installieren

apt-get install python-smbus

Funktionsweise

Jetzt geht´s ans Eingemachte!
Zuerst ein Blick ins Datenblatt.

Direkt auf der ersten Seite finden wir die Pinbelegung

MCP23017 Pinbelegung

MCP23017 Pinbelegung

Zu erkennen ist hier direkt, dass es 2 “Bänke” an nutzbaren Pins gibt. GPA0-7 sowie GPB0-7.
Um den Chip zum Leben zu erwecken haben wir bereits alle wichtigen Pins verdrahtet.

Wie spreche ich nun die 16 neuen Pins an?
Zunächst überprüfen wir ob der Baustein gefunden wurde. Dies erfolgt mit

i2cdetect -y 0

Erklärung:

  • ic2detect: Befehl aus i2c-tools zum “Auffinden” von I²C Peripherie.
  • -y: Befehl ausführen ohne Nachfrage (“Sind Sie sicher?..”)
  • 0: Nummer des Busses.

WICHTIG: Bei” RPi Model B Rev 1″ ist hier die “0” zu nutzen. Bei Rev2 muss der Bus 1 genutzt werden!

Die Ausgabe sollte wie folgt aussehen:

root@raspberrypi:/Python# i2cdetect -y 0

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

Das bedeutet, dass ein I²C Bauteil gefunden wurde an der Adresse “0x20”.

Jetzt schalten wir einen Ausgang.
Hier empfiehlt sich ein Blick auf eine weitere Tabelle im Datenblatt

Register

Register

Uns interessieren erstmal nur die Zeilen mit den Adressen 00 und 01 sowie 12-15. Aus diesem Grunde habe ich eine kleine Tabelle erstellt und die Zeilen die wir nicht benötigen entfernt

Adress(hex) Name Funktion Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
00 IODIRA In or Out GPA GPA7 GPA6 GPA5 GPA4 GPA3 GPA2 GPA1 GPA0
01 IODIRB In or Out GPB GPB7 GPB6 GPB5 GPB4 GPB3 GPB2 GPB1 GPB0
12 GPIOA On Off GPA GPA7 GPA6 GPA5 GPA4 GPA3 GPA2 GPA1 GPA0
13 GPIOB On Off GPB GPB7 GPB6 GPB5 GPB4 GPB3 GPB2 GPB1 GPB0
14 OLATA On Off GPA GPA7 GPA6 GPA5 GPA4 GPA3 GPA2 GPA1 GPA0
15 OLATB On Off GPB GPB7 GPB6 GPB5 GPB4 GPB3 GPB2 GPB1 GPB0

Vorstellen kann man sich den Inhalt des Bauteils wie die oben gezeigt Tabelle.
Die Zeilen sind bezeichnet mit einem Hexwert (00-15).
Hier ist der erste Moment an dem man sich nicht in die Irre führen lassen sollte. 00-15 sind nicht 16 Zeilen!
Die Zeilennummern sind mit Hexwerten bezeichnet.
Also ist z.B. 15 ein Hexwert und entspricht demnach dem Dezimalwert 21!

Die Spalten ergeben sich aus den Bits (Bit0 – Bit7)
Diese Tabelle kann nun beschrieben werden. Je nach Position des Wertes in der Tabelle, können wir Pins als Ein- oder Ausgang deklarieren oder auf High und Low schalten.
Ebenso kann jeder Wert aus der Tabelle ausgelesen und wiedergegeben werden.
Das beschreiben erfolgt mit dem Befehl

i2cset -y 0 "Bausteinadresse" "Zeile" "Wert(hex)"

Wir starten, indem wir alle Pins der Bank A (GPA) als Ausgänge deklarieren.
Hierfür ist die Zeile “IODIRA” zuständig (Input Output Direction A). Wir schreiben in jedes der 8 Bits eine “0”.
Eine “1” in jedem der Bits würde alle Pins zu Eingängen deklarieren. Dies ist auch der standardmäßig der Fall.

i2cset -y 0 0x20 0x00 0x00

Die Zeile OLATA oder auch GPIOA sind nun für das schalten der Ausgänge zuständig.
Ich nutze die Zeile 14 – OLATA.

i2cset -y 0 0x20 0x14 0x01

GPA0 ist nun auf High (3,3V). Es empfiehlt sich natürlich nun dort vorher eine LED angeschlossen zu haben um den Effekt auch beobachten zu können.

An dieser Stelle bin ich bei den meisten Anleitungen verzweifelt.
Wieso bedeutet “0x01”, dass GPA0 auf 1 ist? Ist GPA3 dann “03”? Wie setze ich mehr als einen Ausgang?

Die Erklärung ist einfach.
Der letzte Hexwert des Befehls “0x01” ist wie gesagt ein Hexwert 😉
Einen Hexwert kann man auch umrechnen.
Als Dezimalwert hätten wir nun den Wert “1”
Als Binärwert ergibt sich ebenfalls der Wert “1”. Um genau zu sein der Wert “00000001”
Und das ist auch der Schlüssel. Bit7-1 haben immer noch den Wert 0. NUR Bit0 hat den Wert 1!

Mit diesem Wissen kann jeder beliebige Ausgang geschaltet werden.

Beispiel

i2cset -y 0 0x20 0x14 0x10

Wieder arbeiten wir in Zeile 14 (Bank A), schreiben aber nun den Hexwert “10” in diese Zeile. Eine Umrechnung in das Binärsystem zeigt aber welcher Ausgang hierdurch auf High geschaltet wird.

Hexadezimal: 10
Dezimal: 16
Binär: 00010000

Bits 7-5 sind auf 0, Bit 4 auf 1 und Bit 3-0 ebenfalls auf 0.
Ein Blick in die Tabelle aus dem Datenblatt zeigt, dass Bit 4 in Zeile 14, dem Pin GPA4 zugeordnet ist.

2. Beispiel

Nun umgekehrt.
GPA0, GPA3 und GPA7 sollen gleichzeitig auf High schalten.
Binär: 10001001
Dezimal: 137
Hexadezimal: 89

Daraus ergibt sich folgender Befehl:

i2cset -y 0 0x20 0x14 0x89

Für die 8 Pins der Bank GPB wird das Ganze in Zeile 0x15 ausgeführt

Das Auslesen

Das Auslesen des Bausteins erfolgt für die ganze Zeile.

i2cget -y 0 0x20 0x14

Ein Wert wird nicht benötigt.

Erklärung:

  • i2cget Befehl aus i2c-tools zum Auslesen
  • -y: Befehl ausführen ohne Nachfrage (“Sind Sie sicher?..”)
  • 0: Nummer des Busses.
  • 0x20 Adresse des Bauteils
  • 0x14 Auszulesende Zeile

Die Ausgabe des Befehls, wenn alle Ausgänge 0 sind:

root@raspberrypi:/Python# i2cset -y 0 0x20 0x14 0x00
root@raspberrypi:/Python# i2cget -y 0 0x20 0x14
0x00

Ausgabe des Lesebefehls nach dem 2. Beispiel:

root@raspberrypi:/Python# i2cset -y 0 0x20 0x14 0x89
root@raspberrypi:/Python# i2cget -y 0 0x20 0x14
0x89

Für die 8 Pins der Bank GPB wird das Ganze in Zeile 0x15 ausgeführt.

Der Baustein verfügt über integrierte Pull-Up Widerstände, welche nach dem gleichen System in den Zeilen 0D bzw 0C gesetzt werden.

Verwendung in Python

Um den Baustein innerhalb eines Python Programmes anzusprechen, wird das anfangs installierte Paket “smbus” benötigt.

Ich habe ein kleines Programm geschrieben, welches die Nutzung verdeutlicht.
Ich füge später noch Kommentare hinzu und versuche es vor falscher Bedienung zu schützen.
Bis dahin warne ich vor falscher Benutzung!
Ich weiß nicht, was geschieht, wenn man ein Bit beschreiben möchte, welches nicht existiert. Daher bitte sehr genau auf die Eingaben achten, wenn ihr das Script testet.

#! /usr/bin/python
import smbus
import time
import sys

b = smbus.SMBus(0)
address = 0x20 # I2C Adresse
PinDict= {"7": 0x80, "6":0x40, "5":0x20, "4":0x10,"3":0x08, "2":0x04, "1":0x02, "0":0x01, "all":0xff, "off":0x00}

def get(row):
    read = b.read_byte_data(address,row)
    return read

def set(row,data):
    b.write_byte_data(0x20,0x00,0x00) # Bank A Ausgang
    b.write_byte_data(0x20,0x01,0x00) # Bank B Ausgang
    write = b.write_byte_data(address,row,data)
    read = get(row)
    return read

print "Waehle Bank a oder b"
bank =raw_input("Auswahl: ")

if bank == "a":
    row = 0x14
elif bank == "b":
    row = 0x15

print "Waehle Ausgang 0-7 \n"
print "Tippe 'all' fuer alle Pins der gewahlten Bank \n"
print "Tippe 'off' um alle Pins der gewaehlten Bank auszuschalten  \n"
var =raw_input("Auswahl: ")
data = PinDict[var]
data = int(data)

result = set(row,data)

print "Bank",bank,"hat nun Wert",hex(result)

#####---------Test---------###########
#b.write_byte_data(0x20,0x00,0x01) # Bank A Eingang
#b.write_byte_data(0x20,0x01,0x01) # Bank B Eingang

#block = "GPA"
#var = get (0x12)

#if var == 0x00:
#    var = get (0x13)

#    block = "GPB"
#    if var == 0x00:
#    print "Kein Schalter aktiv"
#    sys.exit()

#print "Der Schalter ist aktiviert an", block, "Wert:",hex(var)
######--------Test Ende-----------############

Viel Spass beim Basteln.
Bei Fragen oder Fehlern in der Anleitung, hinterlasst ruhig einen Kommentar.

51 Gedanken zu “Raspberry Pi: Porterweiterung mit MCP23017 und I2C

  1. Danke für dieses endlich mal verständliche (deutsche) Tutorial zum Thema i2c. Ich hab zwar trotzdem ein paar Anläufe gebraucht um zu kapieren, das mit der letzten Hexzahl 0x00 alle Bits in dem Register auf 0 geschaltet werden, aber ich glaube jetzt kann ich mit arbeiten.

    Schön das auch mal jemand die i2c-tools durchnimmt… die meisten springen ja gleich von Installation zur Hochsprache ihrer Wahl.

  2. Hi Christoph,
    vielen Dank für das Tutorial. Endlich habe ich verstanden wie ich die Hex-Werte einsetze, bzw. dass ich die Werte über das Datenblatt herausfinde. Ich habe den kleinen Bruder im Einsatz: MCP23008. Leider berufen sich alle Tutorials im Netz auf den MCP23017, bei dem sich die Hex-Adressen für GPIO, OLAT usw. unterscheiden. Erst durch dein Tutorial habe ich begriffen, dass dort bei mir der Fehler liegt.

    Eine Frage habe ich jedoch noch: Wo liegt der Unterschied, ob ich nun OLAT oder GPIO nutze? Egal welche Adresse ich verwende, bei beiden passiert das gleiche.

    • Hey,
      entschuldige die späte Antwort. Ich war über die Feiertage nicht im Lande.
      Ich musste hier selber nochmal nachschauen. Es sollte so sein, dass man beim Ändern eines Pegels ins OLAT register schreibt, beim Lesen den Wert aus dem GPIO Register zieht.
      Hintergrund ist, dass das GPIO Register die OLAT Register beeinflusst, das OLAT Register ändert direkt die Ausgänge.
      Ich habe es bei mir demnach ebenfalls nicht richtig gemacht. Um beim Lesen sicher zu gehen, auch den wirklichen Wert des Ausgangs auszulesen, würde ich allerdings den Weg, der im Datenblatt beschrieben ist wählen. Ich werde meine Anleitung diesbezüglich auch noch einmal überarbeiten.

      Ein Auszug aus dem Datenblatt:
      "The GPIO module is a general purpose, 16-bit wide,
      bidirectional port that is functionally split into two 8-bit
      wide ports.
      The GPIO module contains the data ports (GPIOn),
      internal pull-up resistors and the output latches
      (OLATn).
      Reading the GPIOn register reads the value on the
      port. Reading the OLATn register only reads the
      latches, not the actual value on the port.
      Writing to the GPIOn register actually causes a write to
      the latches (OLATn). Writing to the OLATn register
      forces the associated output drivers to drive to the level
      in OLATn. Pins configured as inputs turn off the
      associated output driver and put it in high-impedance."

      Hoffe das hat geholfen 😉

      Gruß
      Christoph

  3. Hallo Christoph,

    ich muss auch sagen ein super Bericht, vor allem endlich mal auf Deutsch, macht es einfach einfacher. Ich habe schon viel über i2c gelesen, aber hier hat es dann auch bei mir endlich klick gemacht 😉

    Mir sind ein paar Sachen aufgefallen bei denen ich mir nicht ganz sicher bin ob ich das einfach nicht verstehe, oder dir vielleicht kleine Flüchtigkeitsfehler unterlaufen sind 😉

    – Ich glaube der Pin 16 muss auch mit GND vom Pi verbunden werden, zumindest laut deiner Zeichnung.

    – Das i2c Modul muss aus der Blacklist raus, also muss es glaube ich auskommentiert werden, also das "#" an den Anfang der Zeile stellen.

    – Ohne ein "sudo apt-get upgrade" kommt es bei manchen Installationen zu fehlern bei der Installation der i2c-tools

    – "apt-get install" kann nur als root oder mit sudo aufgerufen werden

    Ein Punkt fehlt mir leider noch, den habe ich auch noch nicht woanders gefunden, ich weiß schon das die Pins A0, A1 und A2 für die Adresse des I2C Devices zuständig sind. Vielleicht hast du ja noch die Chance herrauszufinden wie man diese Adresse manipuliert um noch weitere I2C Devices anzuschließen.

    Gruß
    Hendrik

  4. auch wenn ich nicht der Verfasser des Artikels bin möchte ich ein paar Kommentare zu deinen Punkten los werden.

    erste 2 punkte: obwohl ich es mehrfach durchgelesen hatte ist mir das vorher nicht aufgefallen, aber du hast absolut recht. :-)

    es ist richtig das apt-get meist nur als root funktioniert, trotzdem kann es sein, das man nicht explizit sudo davor schreiben muss. Die meisten Distros die ich kenne "merken" sich ein Weilchen, wenn sich ein User per sudo zusätzliche rechte verschaffen durfte. Dann kann man ggf. mehrfach auf sudo verzichten. Aber wenn man es immer davor schreibt oder sich für die Installation zum root macht, ist man auf alle Fälle auf der sicheren Seite

    apt-get upgrade ist eigentlich eine Sache die man regelmässig vor Installationen egal welcher Programme machen sollte. Ob man das bei einer speziellen i2c Anleitung mit dazu packen sollte darüber lässt sich wahrscheinlich streiten.

    Zur adressierung von i2c. Die Bausteine hab eine "nennen wir es mal" Grund Adresse (nicht mal falsch wenn man bedenkt, das es diese Adresse wenn A0-A2 auf Ground gelegt werden) Das ist etwa bei MCP23017 die 20. Diese Grund Adresse ist meist in den Specsheets der Bauteile zu erfahren. Danach geht es Bitweise durch, das Bit wird gesetzt in dem Spannung auf die Adressleitungen gelegt werden: Also wenn Port 15 und/oder 16 und/oder 17 mit Plus statt mit minus verbunden werden. So kann man bei 3 Adress Bits also 8 i2c Komponenten mit der gleichen Grund Adresse adressieren.

    Am besten man probiert es ein wenig aus, einfach nach und nach mal die Ports auf Plus legen und neu scannen … das sind jetzt ja keine Sachen die schwerwiegend explodieren kann oder die Welt kosten.

    Bei den Anzeige Schildern von Adafruit ist meist ein solcher MCP23017 bei und fest zur 20 verdratet … will man aber auf verschiedenen Schildern verschiedene Anzeigen mit einem Raspi steuern, muss man sich etwas einfallen lassen.

    • Wollte auch nicht kleinlich wirken, wollte nur etwas dazu beitragen, wenn ich es schon nutze 😉

      Ich gehe mal davon aus das ich dann auf die A0-A2 3,3V legen muss?

      Mir ist gestern noch aufgefallen das der Link zur Raspberryprojekt Seite nen 404 bringt 😉

      Gruß

      Hendrik

  5. Hi

    an und für sich scheint es eine gute Anleitung zu sein.

    Jedoch komme ich hier garnicht weiter, ich habe jetzt schon die 2. Anleitung getestet. Aber bei mir wird kein device gefunden. wenn ich den befehl i2cdetect -y 1 setze bekomme ich zwar die register angezeigt aber die 20 fehlt dort. Dies war auch schon in der ersten anderen Anleitung der Fall. Ich dachte erst das ich was falsch mache, aber so daneben kann ich ja nicht sein. Vielleicht mache ich ja sogar tatsächlich was falsch bitte um kurze Info woran es liegen kann.

    MFG

    Zappelmann

    • Hey,
      das kann viele Gründe haben. Erstmal bedeutet das, dass kein I²C Gerät erkannt wurde.
      Ich habe gestern den Schaltplan ausgetauscht. Im alten war SDA und SCL vertauscht. Schau doch mal ob die die beiden richtig angeschlossen hast.
      Der jetzige Schaltplan ist korrekt. Sobald VCC, SDA und SCL angeschlossen sind, sollte das Bauteil erkannt werden.
      Falls nicht. Bauteil defekt? I²C hast du freigeschaltet? Also "blacklist i2c-bcm2708…" und Neustart?
      Achso.. und du hast auch einen RPi Rev 2? Die ganz alte Version mit 256 MB RAM nutzt den Bus 0, daher " i2cdetect -y 1"

      Viel mehr kann ich von hier aus nicht sagen. Vielleicht hilft dir das ja trotzdem schon etwas.

      Gruß
      Christoph

  6. Moin,

    also den Schaltplan hab ich gestern schon richtig gehabt. Bauteil defekt hmmm, kann ich mir kaum vorstellen da ich hier 5 Stück davon habe und mittlerweile 3 Stück getestet habe. Ja ist Rev. 2 und freigeschaltet isses auch. Schau mal hier habe es auch hier nach der Anleitung getestet, leider mit dem selben Ergebnis.

    Irgendwie fällt mir dazu nix ein.

    vielleicht hast du noch ein weiteren Tip was es sein könnte.

    MFG

    Zappelmann

  7. Hallo,

    kann man auch 2 Chips hintereinander setzen und Ansprechen? Leider muss ich sagen das 16 Ports bei mir etwas zu wenig sind. Oder gibt es vll. einen Chip der mehr als 16 Ports kann?

    • Hi,

      hintereinander nicht direkt. Aber parallel. Du kannst die chips praktisch über A0-A2 adressieren, indem du sie entweder auf Masse oder mit Widerstand an 3,3/5V hängst.

      Es sind also 2^3 = 8 Chips möglich (bitte korrigieren, falls falsch. Ich meine ich hätte sogar mal von mehr gelesen).

      Grüße

      Stefan

      • es gehen theoretisch auch mehr. aber normalerweise nicht von der gleichen art. die Basis Adresse des multiplier muss anders sein damit mehr gehen. Im Artikel steht die Adresse 0x20 dort geht es dann bis 0x27 mit der 3 pin adressierung.

        Basis Adresse und ob man durch Spannung programmieren kann steht normalerweise im SpecSheet und das kann man bei vielen Anbietern vorab herunterladen

        • Hallo,
          kleine frage:

          Wen ich den Python script ausfuhre gibt er mir den error:

          Waehle Bank a oder b
          Auswahl: a
          Waehle Ausgang 0-7

          Tippe 'all' fuer alle Pins der gewahlten Bank

          Tippe 'off' um alle Pins der gewaehlten Bank auszuschalten

          Auswahl: 0
          Traceback (most recent call last):
          File "gpa-b.py", line 37, in
          result = set(row,data)
          File "gpa-b.py", line 16, in set
          b.write_byte_data(0x20,0x00,0x00) # Bank A Ausgang
          IOError: [Errno 5] Input/output error

          Mit :
          /usr/sbin/i2cset -y 1 0x20 0x14 0x01
          /usr/sbin/i2cset -y 1 0x20 0x14 0x00

          Geht den led an und wieder aus…. also chip und connections sind ok.

          Grusse.

  8. Hej, das ist definitv mal eine wirklich sehr sehr gute Anleitung, die das wesentiche auf den Punkt bringt. Ich habe schon viele I/Os geschaltet und bin nun an der Stelle, wo ich auch mal abfragen möchte :-). Deine Anleitung beschreibt ja sehr gut, wie man den Port auf IN setzt und wie man abfragt, was ich aber will, ich will wissen, WANN ein Port auf IN geht, also quasi nen Interrupt auslösen oder so, sonst müsste ich ja zyklisch pollen und immer die Eingänge alle lesen.

    Anwendungsbeispiel:

    Ein Taster wird gedrückt, dieses Ereignis soll sofort getriggert werden, woraufhin ein anderer PortPin, dann meinetwegen eine LED einschaltet.

    Ich sehe bisher aber nur die Lösung ganz oft (while(true){…}) eine I2C-Adresse abzufragen, was sicherlich den Bus ziemlich belastete. Kennst du da eine bessere Lösung? Zumal ein Taster ja tendentiell eher selten gedrückt wird, würden 99,99% aller Abfragen keine Statsuänderung erkennen.

    /Andreas

    • Hey,
      vielen Dank für das Lob.

      Der MCP231017 hat Interrupt-Ausgänge (INTA, INTB)

      Diese geben den zuvor getriggerten Eingang aus.

      Damit sparst Du dir die Pythonabfrage und lässt dir vom Baustein die Eingänge überwachen.

      Am Besten schaust du dazu ins Datenblatt.

      Das war, was ich dir auf die Schnelle mitgeben konnte. Falls Du das dazugehörige Programm benötigst, musst du mir ein paar Wochen Zeit geben. Komme momentan wenig zum Basteln.

      Hoffe dir schon geholfen zu haben.

      Gruß
      Christoph

      • Ja Danke Christoph, genau sowas meinte ich, vielleicht hätte ich auch selbst mal in das Datenblatt schauen können, bevor ich frage :-).

        Grundsätzlich denke ich aber, dass das Auslesen immer nur über die Interrupts gehen sollte dann, denn ich denke, man fragte in den seltensten Beispielen die Ports "pauschal" ab.

        Aber wenn du wirklich die Zeit finden solltest, ein Beispiel in Python zu coden und hier zu veröffentlichen, dass auf diese Methode zurückgreift, wäre das echt cool.

        Ich habe mir soeben schonmal ein paar MCP23017 gekauft, so dass ich also in ein paar Tagen auch mal experimentieren kann :-).

  9. Hallo,

    Danke für diese super Anleitung! Hat mir wirklich sehr weitergeholfen!

    Ein Problem hätte ich aber noch. Und zwar funktioniert bei mir der import des smbus Moduls nicht. Also wenn ich in meinem python script den smbus importierte (import smbus) bekomme ich folgende Fehlermldung:

    Traceback (most recent call last):

    File "./smbus.py", line 2, in

    import smbus

    File "/home/pi/smbus.py", line 6, in

    b = smbus.SMBus(1)

    AttributeError: 'module' object has no attribute 'SMBus'

    Hast du eine Idee woran das liegen kann? ich hab den python-smbus genau deiner Anleitung installiert..

      • Danke für die schnelle Hilfe.

        Mhm, keine Ahnung woran es lag, aber de- und wieder installieren hat geholfen.. 😉

        (ich glaub ich sitze für heute einfach schon zu lange an meinem neuen Spielzeug, da passieren dann irgendwann komische Sachen…)

        Vielen Dank! Echt ein super Tutorial!! :)

        Lg Tina

  10. hi

    muss sagen, das ist sogar für mich verständlich, und ich bin kein programmierer oder so was… echt gut gemacht… aber ich habe eine frage, ich brauch den befehl "GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP)" für den MCP23017 umgemünzt… soweit habe ich schon rausbekommen, dass pin 20 und 21 die Interrupt-Pins für A und B sind… aber wie diese funktionieren bzw. wie ich das anwende … keine ahnung… ich hätt das nämlich gerne in diesen code:

    import RPi.GPIO as GPIO

    import time

    import smbus

    import sys

    def bin2dec(string_num):

    return str(int(string_num, 2))

    data = []

    b=smbus.SMBus(0)

    adress = 0x20 # I2C Bausteinadresse

    PinDict= {"7": 0x80, "6":0x40, "5":0x20, "4":0x10,"3":0x08, "2":0x04, "1":0x02, "0":0x01, "all":0xff, "off":0x00}

    def get (row):

    read = b.read_pyte_data(adress,row)

    return read

    def set(row,data) :

    b.write_byte_data

    #GPIO.setmode(GPIO.BCM)

    #GPIO.setup(4,GPIO.OUT)

    i2cset -y 1 0x20 0x01 0x00 #setze Reihe B auf Ausgang

    #GPIO.output(4,GPIO.HIGH)

    i2cset -y 1 0x20 0x14 0x80 #setze GPB7 auf high

    time.sleep(0.025)

    #GPIO.output(4,GPIO.LOW)

    i2cset -y 1 0x20 0x14 0x00 #setze GPB7 auf low

    time.sleep(0.02)

    #GPIO.setup(4, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    i2cset -y 1 0x20 0x14

    for i in range(0,500):

    data.append(GPIO.input(4))

    bit_count = 0

    tmp = 0

    count = 0

    HumidityBit = ""

    TemperatureBit = ""

    crc = ""

    try:

    while data[count] == 1:

    tmp = 1

    count = count + 1

    for i in range(0, 32):

    bit_count = 0

    while data[count] == 0:

    tmp = 1

    count = count + 1

    while data[count] == 1:

    bit_count = bit_count + 1

    count = count + 1

    if bit_count > 3:

    if i>=0 and i=16 and i=0 and i=16 and i 3:

    crc = crc + "1"

    else:

    crc = crc + "0"

    except:

    print "ERR_RANGE"

    exit(0)

    Humidity = bin2dec(HumidityBit)

    Temperature = bin2dec(TemperatureBit)

    if int(Humidity) + int(Temperature) – int(bin2dec(crc)) == 0:

    print "Humidity:"+ Humidity +"%"

    print "Temperature:"+ Temperature +"C"

    else:

    print "ERR_CRC"

    eingebaut….

    hat jemand ne idee bzw. kann mir helfen????

    danke

  11. Hey super Tutorial, hat mir echt geholfen weiter so! :)

    Eine Sache ist mir allerdings noch unklar.

    Wenn ich beispielsweise GPA0 und GPA1 als Ausgang deklariere und beiden ins Registger eine 1 schreibe, sodass beide auf HIGH gesetzt sind und möchte nun nur GPA0 auf 0 (LOW) setzen… wie kann ich das realisieren?

    Denn schreibe ich 0x00 ins Register setzt er ja alle 8 Bits auf 0

    Vlt. kannst du mir da weiterhelfen? Ist dies evlt in deinem Python-Code schon irgendwo enthalten?

    Danke und Gruß

    Markus

    • Hi,

      meine experimente sind zwar schon ne weile her, aber ich glaube du hast nur nen kleinen denkfehler drin.

      man schaltet sie ja nicht einzeln, sondern gibt einen bit-code in einem byte an. Wenn man also G0 und G1 auf 1 also high hat ist das also entweder 11000000 oder 00000011 (glaube das 2te) 0x03. Du willst eine bitfolge 00000010 als 0x02 wenn ich das richtig sehe.

      • Sagen wir G0 und G1 sind, wie du schon richtig geschrieben hast mit HEX 0x03 (00000011) auf 1 gesetzt.

        Angenommen G0 und G1 sind als Ausgänge definiert wobei G0 eine LED schaltet und G1 einen kleinen Motor.

        Schreibe ich nun 0x03 ins Register setzt er auf G0 und G1 eine 1 wodurch sowohl LED und Motor eingeschaltet werden. Nun schaltet im Beispiel an G0 zeitversetzt den Ausgang auf HIGH und wieder auf LOW damit die LED bsplsws blinkt oder so… wie kann ich nun realisieren, dass ich NUR G1 (Motor) unabhängig davon dass ich den derzeitigen Wert von G0 kenne, der sich ja durch das blinken dauernd ändert, schalten lässt?

        Fragt man an der Stelle im Code erst den Zustand von G0 ab vlt.?

        Ich hoffe es ist einigermaßen verständlich :)

        • wenn du nur mit einem register arbeitest hast du immer eine abhängigkeit.

          Ein blinkendes LED mit laufendem Motor wäre ein wechsel zwischen 0x03 und 0x02.

          Wenn man es anders machen will, entweder 2 Register oder über das auslesen des Status durch die Software

          • genau das meine ich… heißt also durchs setzen der Bits verändere ich immer alle 8 Werte eines Registers… dann komm ich ums auslesen nicht herum.

            Hast du zufällig einen Beispiel-Code für das Auslesen?

          • Ich leider nicht auf die schnelle… dafür bin ich zu lang raus.

            Im Python bespiel code hier auf der Seite gibt es aber ein def read(row), was das sein müsste was du willst. Ist zwar eher für schalter die man damit abfragen will, das sollte aber allgemein nur zeigen, wo gerade ein bit high oder low ist.

            Wenn man der Englischen Sprache auch einigermassen mächtig ist, es gibt von Adafruit ein LCD Shield (Character Display) mit 5 Knöpfen, die über den gleichen i2c multiplexer angesteuert werden. Es ist zwar eigentlich für einen Arduino UNO, es gibt aber afaik auch eine Anleitung wie man das über den RasPi über Python tut… da könnte man sich vielleicht auch noch ein paar tricks raus lesen

  12. Hallo, ich hätte zwei kleine fragen, warum muss ich Pin 18 (Reset) an 3,3V legen?

    Würde es nicht ausreichen, wenn ich nur Pin 15 (A0) auf 3,3V lege?

    LG Björn

    • Hallo Björn,
      Reset ist "low-aktiv", also wird der Reset durchgeführt, wenn an Pin 18 Masse anliegt. Durch 3,3 V am Reset-Pin, deaktivierst du quasi die Resetfunktion.
      A0 hat damit nichts zutun. Dies ist nur für die Adressierung gedacht (und A0-A2 sollten erstmal auf GND, es sei denn du planst noch weitere MCP23017 anzuschließen)

      Gruß
      Christoph

      • das die Adressierung mit dem Reset nicht zu tun haben, da bin ich mir im klaren … eventuell habe ich mich einfach falsch ausgedrückt.

        also wenn die Schaltung fertig ist bei mir, werde ich vermutlich auf 6 oder 7 MCP´s kommen. 5 werden es aber ganz sicher ….

  13. Hallo,

    erst einmal danke für das super tutorial.

    Aber:

    Gibt es auch eine möglichkeit wenn ich z.B. "i2cset -y 1 0x20 0x14 0x01" aufrufe und im Anschluss "i2cset -y 1 0x20 0x14 0x20" das auch der erste Befehl erhalten bleibt und nicht z.B. die LED von i2cset -y 1 0x20 0x14 0x01 wieder aus geht.

    Mfg der tkt

    • Hallo,

      nein.

      Du musst vorher den Zustand der definierten I/O´s abfragen um einen vorher gesetzten Zustand erhalten zu können.

      Hab ich in der Form via Python + Raspberry Pi mal gemacht.

      Ist nur n bisschen Tipp- und Rechenarbeit für den Code…

      Gruß

      opc

      • Kannst du mir da einen Tipp geben wo ich solch ein Python finden kann? Denn meine Suche diesbezüglich waren bis jetz erfolglos.

        MfG Tobi

      • nein.

        mit:

        "smbus.SMBus(1).read_byte_data(0x20, 0x12)"

        kannst du unter Python die IO Werte der Pins von Bank A auslesen (der Chip hat Bank A und Bank B mit jeweils 8 Pins. Wie im Tutorial oben bereits aufgezeigt handelt es sich hierbei um das Auslesen des Registers "GPIOA" –> Auslesen Bank A

        GPIOB –> Auslesen Bank B

        Gruß

        opc

  14. Hallo,

    ich habe soweit alles exakt so durchgeführt, wie du es beschrieben hast. Allerdings bekomme ich beim Befehl "i2cdetect -y 0" (mit Bus 1 habe ich es auch schon ausprobiert, und beides mit sudo) die Fehlermeldung "Could not open file '/dev/i2c-0' or '/dev/i2c/0': No such file or directory".

    Woran könnte das liegen?

    Gruß

    Paddy

  15. Vielen Dank für das gute Tutorial.

    Ich scheitere leider beim Verbindungstest mit dem MCP23017. Der Pi gibt folgendes aus:

    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: — — — — — — — —

    Ich habe den gesamten Aufbau nun nochmals mit einem anderen Baustein gesprüft und bekomme erneut dieses Ergebnis?! Kann mir jemand sagen was ich falsch mache?

    MfG

    Christian

    • Hallo Christian.

      Das bedeutet erstmal nur, dass der Baustein gar nicht gefunden wurde. Wenn nicht anders adressiert, wird der Baustein unter der Adresse 0x20 gefunden.
      "UU" bedeutet lediglich, dass die dort angezeigte Adresse in Verwendung ist, hat aber nicht mit deinem Baustein zutun.
      Leider musst du noch einmal die Verdrahtung prüfen. Mehr kann ich dir auf Anhieb dazu nicht sagen.

      Gruß
      Christoph

  16. Es gab auch mal ein deutsches Tutorial, wo die AdaFruit-Anzeigeplatine sozusagen auf einer Lochrasterplatine nachgebaut wurde; allerdings ohne Tasten, dafür aber mit 3 Jumpern für die TWI-Adresse. Leider habe ich dies nicht mehr gefunden…:-(

Kommentar verfassen