Grundlagen, wie eine I2C-Kommunikation zwischen einem Mikrocontroller und dem Portexpander MCP23008 hergestellt wird, werden in den folgenden beiden Posts vermittelt. In diesem Post wird der Portexpander MCP23008 und dessen großer Bruder MCP23017 über einen Arduino angesteuert.
Arduino und I2C - MCP23017
Arduino und I2C - MCP23017 und MCP23008
Aufbau
Vom Calliope Mini werden vier Kabel zum Steckbrett mit dem Portexpander geführt: Masse, 3.3 V und die beiden Anschlüsse der I2C-Schnittstelle: SDA und SCL. Diese beiden Anschlüsse werden direkt mit dem Portexpander verbunden.
Die Adresse des Portexpanders wird durch die Pins A0, A1, A2 eingestellt. Da diese alle auf L-Pegel liegen, ist die Adresse des MCP23008 0x20
. Diese Adresse muss im Programm eingestellt werden, damit eine I2C-Verbindung vom Calliope aufgebaut werden kann.
Der Reset-Pin des MCP23008 befindet sich auf H-Pegel, damit der MCP23008 nicht resettet wird und verwendet werden kann.
An den GPIO-Pins GP0 und GP1 ist jeweils eine LED mit Vorwiderstand angeschlossen. Zwei Taster sind an den Pins GP6 und GP7 angeschlossen.
Programmierung von Ausgängen
Programmierung
Das folgenden Beispielprogramm lässt die am Portexpander angeschlossen LEDs im Wechsel blinken. Dieses Programm muss in einem neuen Projekt der PXT-Programmierumgebung (http://pxt.calliope.cc/index.html) im JavaScript-Quelltext eingefügt werden. Anschließend sollte die Block-Ansicht nicht mehr aufgerufen werden oder man löscht direkt im Explorer die Datei main.blocks
.
const ADDR = 0x20;const REG_IODIR = 0x00;const REG_GPPU = 0x06;const REG_GPIO = 0x09;;basic{pins}
In den ersten Zeilen findet die Konstantendefinition statt. In der Konstante ADDR
wird die I2C-Adresse des Portexpanders abgelegt. Die folgenden Konstanten sind die Adressen der Register innerhalb des Portexpanders.
Die Programmzeile writeRegister(ADDR, REG_IODIR, 0xfc)
sorgt dafür, dass die beiden LED-Pins als Ausgängen geschalten werden. Dies geschieht, indem in das Register IODIR
der Wert 0xfc
geladen wird. 0xfc
ist als Binärzahl 0b11111100
. Aus diesem Grund werden die Pins GP0 und GP1 zu Ausgängen.
Die Funktionalität der Funktion writeRegister()
wird im nächsten Unterabschnitt verdeutlicht.
Innerhalb der Unendlichschleife wird nun abwechselt der Wert 0x01
und 0x02
in das GPIO-Register geladen. Da gilt 0x01 = 0b00000001
und 0x02 = 0b00000010
werden die LEDs abwechselt angeschalten.
Setzen von Registern
Das Setzen eines Registers findet innerhalb der Funktion writeRegister()
statt. Grundlagen hierfür sind im Post Arduino und I2C - MCP23017 zu finden.
Um den Inhalt eines Registers zu setzen, müssen zwei Bytes über die I2C-Schnittstelle an den Portexpander gesendet werden. Das erste Byte enthält die Adresse des Registers und das zweite Byte die Daten, welche in das Register geschrieben werden sollen.
Durch die Anweisung reg * 256 + value
werden Adresse und Daten aneinandergehangen und es entsteht eine 16-Bit-Zahl. Dies erklärt den ersten Teil des letzten Argumentes: NumberFormat.UInt16
Die Abkürzung BE
steht für Big Endian und beschreibt die Reihenfolge, in der die zwei Bytes der entstandenen 16-Bit-Zahl über die I2C-Schnittstelle gesendet werden sollen (siehe https://de.wikipedia.org/wiki/Byte-Reihenfolge).
Wird Big Endian eingestellt, so wird zuerst das höchstwertige Byte, also die Adresse versendet und anschließend das niedrigstwertige Byte, also die Daten, gesendet.
Aufnahme mit Logikanalysator
Während des Setzen des GPIO-Registers auf den Wert 0x02
wurde diese Aufnahme mit dem Logikanalysator gemacht. Es sind kaum Unterschiede zur Aufnahme zu erkennen, die bei der Kommunikation zwischen Arduino und Portexpander entstanden ist.
Wie beim Arduino dauert die Kommunikation etwa an. Auffallend sind jedoch kleine Spikes, die zu Beginn des ACK-Bit im Signal entstehen. Diese haben anscheinend aber keinen Einfluss auf die I2C-Kommunikation.
Programmierung von Eingängen
Programmierung
Das folgende Beispielprogramm soll auf das Drücken des Tasters an GP7 reagieren. Ist dieser Taster gedrückt, so soll die LED an GP1 leuchten, ansonsten leuchtet die LED an GP0.
const ADDR = 0x20;const REG_IODIR = 0x00;const REG_GPPU = 0x06;const REG_GPIO = 0x09;;;basic{pins}: number {pins;return pins}
Zu Beginn des Programmes wird wie beim vorhergehenden Programm das Register IODIR
auf den Wert 0xfc
gesetzt, um die beiden LED-Pins als Ausgänge zu deklarieren. Durch das Setzen des Registers GPPU
auf den Wert 0xc0
, der 0b11000000
entspricht, werden an den Pins der beiden Taster die internen Pullup-Widerstände aktiviert.
Durch die Anweisung readRegister(ADDR, REG_GPIO)
wird der Wert des GPIO-Registers ausgelesen. Dieser wird anschließend in der Variablen value
abgelegt.
Durch die Bedingung value & 0x80
kann geprüft werden, ob das höchstwertige Bit dieses Register 0 oder 1 ist und damit kann bestimmt werden, ob sich der Pin des Tasters auf L- oder H-Pegel befindet.
Ist der Taster gedrückt, so ist der Pin auf L-Pegel und der else
-Block wird ausgeführt. Das Register GPIO
wird auf den Wert 0b00000010
gesetzt und die zweite LED leuchtet.
Ist der Taster nicht gedrückt, so wird das Register GPIO
auf den Wert 0b00000001
gesetzt und die erste LED leuchtet.
Lesen von Registern
Das Lesen von Registern wird durch die Funktion readRegister()
übernommen. Um ein Register vom MCP23008 zu lesen, werden zwei I2C-Nachrichten über die I2C-Schnittstelle ausgetauscht.
In der ersten Nachricht wird dem Portexpander innerhalb eines Bytes die Adresse des Registers übertragen, welches gelesen werden soll. Diese Nachricht wird durch die Anweisung pins.i2cWriteNumber(addr, reg, NumberFormat.Int8LE)
gesendet.
In der zweiten Nachricht wird der Portexpander aufgefordert ein Byte an den Mikrocontroller zu übertragen. Dieses Byte wird von der Funktion readRegister()
zurückgegeben. Daher resultiert der folgende Befehl: return pins.i2cReadNumber(addr, NumberFormat.Int8LE)
Beispielprogramm: Fußgängerampel
Schaltung
GPIO | Funktion |
---|---|
0 | Autos grün |
1 | Autos gelb |
2 | Autos rot |
3 | Fußgänger grün |
4 | Fußgänger rot |
7 | Schalter |
Programmierung
const ADDR = 0x20;const REG_IODIR = 0x00;const REG_GPPU = 0x06;const REG_GPIO = 0x09;;;basic{pins}: number {pins;return pins}