Port Expander MCP23017 - [Teil 2]

Wir nehmen den MCP23017 unter die Lupe - [Teil 2]

Im ersten Teil haben wir gesehen, wie wir mit dem I2C-Bus und dem elektronischen Baustein MCP23017, genannt Port Expander, 16 zusätzliche Ports als Ein- oder Ausgänge gewinnen können.

Dabei hatten wir schon ersten Kontakt mit den möglichen Adressen, mit der diese ICs auf dem I2C-Bus angesprochen werden, damit der Datenaustausch eindeutig vonstatten geht. Diese Adressen heißen 0x20 bis 0x27. Wen das voran gestellte 0x verwirrt, wird am Ende dieses Beitrags mehr über Hexadezimalzahlen, ihren Bezug zu den Dualzahlen (auch Binärzahlen genannt) und zu Registern als Speichereinheit für Informationen wissen.

Ich gehe davon aus, dass alle Leser wissen, dass unser Computer (bislang) nur zwei Schaltzustände kennt: Licht an oder Licht aus, 1 oder 0, True oder False, HIGH oder LOW. Während wir mit 10 Fingern auf die Welt gekommen sind, und die zum Zählen und dann zum Rechnen zu Hilfe genommen haben (Dezimalsystem), müssen wir im Dualsystem mit zwei Ziffern, 0 und 1, zurechtkommen. Die ersten acht bzw. 16 Zahlen sehen wie folgt aus:

7

6

5

4

3

2

1

0

0111

0110

0101

0100

0011

0010

0001

0000

 

15

14

13

12

11

10

9

8

1111

1110

1101

1100

1011

1010

1001

1000


Überraschung: Wir sind gar nicht bis 8 bzw. 16 gekommen, denn der Computer beginnt seine Zählung bei null. Und wir haben von rechts nach links gezählt, weil wir es genauso bei unseren weiteren Betrachtungen brauchen.

Die Register, die wir im Folgenden betrachten wollen, können acht Informationseinheiten – Bits genannt – speichern. Die Zusammenfassung dieser 8 Bits nennen wir 1 Byte. In unserer kleinen Tabelle haben wir gesehen, dass wir mit 4 Bits maximal 16 Zahlen, nämlich von 0 bis 24 - 1 = 15 darstellen können. Entsprechend können wir mit 8 Bits maximal 28 = 256 Zahlen darstellen, die höchste Zahl ist jedoch 2 8 - 1 = 255, weil wir bei null anfangen zu zählen.

In Bits und Bytes dachten anfänglich nur die Programmierer; die interessierten sich aber nicht für die Dezimalzahl des Speicherinhalts, sondern sie suchten eine übersichtliche Darstellung der Registerinhalte – der Bits -, ohne dass ihnen von den vielen Nullen und Einsen schwindelig wurde. Deshalb haben sie immer vier Bits gruppiert und in anderer Form dargestellt. Bis neun konnte man das Dezimalsystem verwenden, und, um nicht zweistellig zu werden, hat man dann mit A, B, C, D, E und F weitergezählt.

15

14

13

12

11

10

9

8

1111

1110

1101

1100

1011

1010

1001

1000

F

E

D

C

B

A

9

8


Dieses neue Zahlensystem, das nur für Programmierer Sinn macht, wurde Hexadezimalsystem genannt – ein Kunstwort, abgeleitet aus dem griechischen Hexa für 6 und dem lateinischen decem für 10.

So wie im Dezimalsystem die zweite Ziffer von rechts die Wertigkeit mal 10 hat, ist dies im Hexadezimalsystem die Zahl mal 16. Beispiel: Unsere Adresse 0x20 ist gleichbedeutend mit 2*16 + 0 =32(dez), aber der Programmierer „sieht“ 0x20 = 0b0010 0000, das fünfte Bit ist gesetzt.  Mit dem 0x am Anfang wird übrigens die nachfolgende Hexadezimalzahl gekennzeichnet, mit 0b die nachfolgende Dual- oder Binärzahl. Die größte Zahl, die man in einem Byte mit den 8 Bits speichern kann, ist 0xFF = 15 * 16 + 15 = 255 = 28-1 = 0b1111 1111, aber das wussten wir ja schon.

Für Umrechnungen kann man in Windows den Rechner benutzen, der einen Modus für Programmierer unter den drei waagerechten Strichen verborgen hält.

 


Die erste Hürde ist genommen. Wir haben Hexadezimalzahlen kennen gelernt. Nun wollen wir Daten in Register schreiben und die Werte von Registern auslesen und weiterverarbeiten. Leider passen die Angaben aus einem 48-seitigen Datenblatt nicht in einen Blog. Deshalb hier der Link zu dem „datasheet und eine Auswahl von Programmierbeispielen.

Wir verwenden dieses Mal den Raspberry Pi und die Programmiersprache Python. Was in der Arduino IDE die Bibliotheken (Libraries) sind, sind in Python die Programm-Module, die zu Beginn des Programms importiert werden. Außer dem Modul gpiozero (für die Alarm-LED) importieren wir für den I2C-Bus das entsprechende Modul mit import smbus    # I2C-Bus. Dann können wir mit bus=smbus.SMBus(1) ein Objekt mit Namen bus instanziieren.

Außerdem erhalten wir neue Methoden, um das Objekt zu beeinflussen, z.B. write_byte_data mit 3 Argumenten, die wir auf das Objekt bus anwenden.

bus.write_byte_data(DEVICE,IODIRA,0x00)    # alle GPA pins als Ausgang

Das erste Argument (wir haben es vorher mit DEVICE = 0x20 definiert) ist die Adresse des MCP23017 auf dem I2C-Bus.

Das zweite Argument enthält die Registeradresse, in der die Daten für die Richtung der Daten der Pins von GPA gespeichert werden sollen. Diese haben wir dem Datenblatt entnommen und zuvor IODIRA = 0x00 definiert.

Und nun kommt der Teil, auf den es hier ankommen soll: Das dritte Argument enthält die Daten, die im Register IODIRA (In Out DIRection GPA) gespeichert werden sollen: für alle 8 Ports von GPA jeweils die Anweisung, als Eingang (1) oder als Ausgang (0) zu fungieren. Das sieht wie folgt aus, wenn die ersten drei Ports (GPA0 bis GPA2) als Eingänge und GPA3 bis GPA7 als Ausgänge definiert werden sollen:

GPA7

GPA 6

GPA 5

GPA 4

GPA 3

GPA 2

GPA 1

GPA 0

0

0

0

0

0

1

1

1


Als Zahl ausgedrückt: 0b0000 0111 oder 0x07 oder 7(dez).

Unsere Python-Anweisung heißt also übersetzt

bus.write_byte_data(0x20,0x00,0x07)

Hallo Gerät mit Adresse 0x20, schreibe in dein Register 0x00 die Zahl 0x07, damit du weißt, dass GPA0 bis GPA2 Eingänge sein sollen und der Rest Ausgänge.

Nun schauen wir uns das Python-Programm für unsere simulierte Alarmanlage aus Teil 1 an. Die Schaltung bleibt grundsätzlich unverändert. Die vier Leitungen zum Micro-Controller schließen wir am Raspberry Pi an Pin1 = 3,3 V, Pin3 = SDA, Pin5 = SCL und Pin6 = GND an.

Selbstverständlich muss der I2C-Bus in der Konfiguration aktiviert sein. Das geht heutzutage ja sehr einfach: im Menü unter /Einstellungen/Raspberry Pi-Konfiguration, dann auf der dritten Registerkarte das entsprechende Feld anklicken. Zur Sicherheit, ob alles geklappt hat, führen wir im Terminal das Programm i2cdetect -y 1 aus. Dann wird wie erwartet die Adresse 0x20 angezeigt.

import smbus

bus = smbus.SMBus(1) # Rev 2 Pi
led = LED(13)

DEVICE = 0x20 # Device Address (A0-A2 to GND)
IODIRA = 0x00 # Pin Register for direction
IODIRB = 0x01 # Pin Register for direction
GPIOA = 0x12 # Register for Input (GPA)
GPIOB = 0x13 # Register for Input (GPB)
GPPUA = 0x0C # Register for Internal Pull-up-Resistors GPA
GPPUB = 0x0D # Register for Internal Pull-up-Resistors GPB

# Definiere alle GP Pins als Input (1111 1111) = 0xFF)
# Binaer: 0 bedeutet Output, 1 bedeutet Input
bus.write_byte_data(DEVICE,IODIRA,0xFF)
bus.write_byte_data(DEVICE,IODIRB,0xFF)

#Endlosschleife, die auf Unterbrechung der Sensoren wartet
while True:
# Status von Register GPIOA und GPIOB auslesen
AlarmA = bus.read_byte_data(DEVICE,GPIOA)
AlarmB = bus.read_byte_data(DEVICE,GPIOB)
if AlarmA !=0 or AlarmB != 0:
print( "Alarm ausgelöst")
print("Sensoren: ",AlarmA, AlarmB)
led.on()
for i in range(8):
if AlarmA & 2**i == 2**i:
print("Sensor-Nr. = ", i)
else:
pass
for i in range(8):
if AlarmB & 2**i == 2**i:
print("Sensor-Nr. = ", i+8)
else:
pass
bus.write_byte_data(DEVICE,GPPUA,0xFF)
bus.write_byte_data(DEVICE,GPPUB,0xFF)

time.sleep(3)
led.off()

In der Endlosschleife ab Zeile 22 wenden wir die zweite Methode aus dem Modul smbus an, um den Inhalt der Eingabe-Register GPIOA und GPIOB auszulesen: bus.read_byte_data(DEVICE,GPIOA).

Hier benötigen wir nur zwei Argumente: die Adressen des Geräts und des Eingabe-Registers. Damit werden den Variablen AlarmA und AlarmB die jeweiligen Registerinhalte in der Größe von 1 Byte zugewiesen. Sowie ein Wert ungleich null ist, weil ein Kontakt geöffnet wurde, soll Alarm ausgelöst werden, also led.on().   

Wir wollen aber auch wissen, welcher Kontakt ausgelöst (welches Fenster, welche Tür geöffnet) wurde.

Nehmen wir an, es wurde zunächst das Fenster mit Kontakt 1 und wenig später die Tür mit Kontakt 5 geöffnet. Dann wird an den entsprechenden Stellen das Bit auf 1 gesetzt.

Kontakt 7

Kontakt 6

Kontakt 5

Kontakt 4

Kontakt 3

Kontakt 2

Kontakt 1

Kontakt 0

GPIOA7

GPIOA 6

GPIOA 5

GPIOA 4

GPIOA 3

GPIOA 2

GPIOA 1

GPIOA 0

0

0

1

0

0

0

1

0


Im Register GPIOA steht nun 0b0010 0010 bzw. 0x22 bzw. 34(dez)

Für die Auswertung machen wir uns zunutze, dass jede Zahl als Summe von Zweierpotenzen dargestellt werden kann. So ist 255 = 27 + 26 + 25 + 24 +23 + 22 +21 + 20 , alle 8 Bits von 0 bis 7 gesetzt. In unserem Fall waren Bit 5 mit der Wertigkeit 25 = 32 und Bit 1 mit der Wertigkeit 21 = 2 gesetzt.

In den Zeilen 30 bis 32 (bzw. 35 bis 37) setzen wir in einer Schleife jeweils eine Maske mit den Wertigkeiten 1, 2, 4, 8, 16, 32, 64 und 128. Wenn die Und-Verknüpfung aus unserem Daten-Byte und der Maske gleich dem Wert der Maske ist, so ist der Exponent die Nummer des Kontaktes, der geöffnet wurde (für GPB addieren wir noch 8 dazu). Diesen Wert geben wir dann auf dem Bildschirm aus.

Nachdem ich bei meinem Versuchsaufbau fehlerhafte Ausgaben erlebt hatte, habe ich mich entschieden, die Eingabe-Register daran zu „erinnern“, dass die internen Pull-up-Widerstände den Eingang auf HIGH ziehen sollen. Dazu schreiben wir in die Register GPPUA und GPPUB den Wert 0xFF, also lauter Einsen. Danach gab es keine Störungen durch undefinierte Zustände mehr.

Wir haben im zweiten Teil gesehen, wie wir die Register des MCP23017 beschreiben oder auslesen können. Im dritten Teil wollen wir das noch vertiefen und ein 7-Segment-Display mit 36 „Beinchen“ als Zeit- und Datumsanzeige benutzen.

Auf vielfachen Wunsch geht es hier zum Download.

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert