Wir nehmen den MCP23017 unter die Lupe - [Teil 1]
Der Name Port Expander sagt alles über den Verwendungszweck dieses ICs. Wir erhalten 16 zusätzliche Ein- oder Ausgänge, um digitale Sensoren, z.B. einen Taster, abzufragen oder LEDs usw. anzuschließen. Dafür müssen wir im Gegenzug nur zwei Ports hergeben, die wir dann als I2C-Anschluss verwenden.
Der Begriff I2C oder IIC steht für Inter-Integrated Circuit, ein Bussystem für den Datenaustausch zwischen elektronischen Bausteinen. Unser Micro Controller ist dabei der Master und eine Vielzahl von Sensoren, Analog-Digital-Wandlern usw. sind die Slaves. Viele kann man parallel am selben Bus anschließen. Man muss nur beachten, dass jeder eine individuelle Adresse hat. Aber bei über 100 möglichen Adressen sollte es keine Konflikte geben.
Unser neu erstandener MCP23017 bietet insgesamt 8 verschiedene Adressen von 0x20 bis 0x27 an. Wir können also bis zu acht Port Expander parallel schalten und so 128 zusätzliche Ein- oder Ausgänge erhalten, abzüglich der zwei I2C-Ports am Micro Controller. Wie das geht, erkennt man beim Blick auf die Anschlüsse (s.u.). Bei unterschiedlichen Bauformen des ICs ist die Benennung der Pins jedoch die Gleiche. Auf den Bildern zunächst die DIP-Variante, die seit einigen Jahren am Markt ist, sowie das neue Breakout Board mit dem deutlich kleiner gewordenen IC.
Auf dem folgenden Bild erkennen wir in der oberen Hälfte die gewonnenen GPIO-Ports mit den Bezeichnungen GPA0 bis GPA7 sowie GPB0 bis GPB7. Wenn Sie diese Bauform verwenden, beachten Sie bitte den „Dimple“, die kleine runde Einkerbung im Gehäuse. Hier befindet sich Pin1 = GPB0.
Die Spannungsversorgung des ICs erfolgt über die Anschlüsse Pin9 =VDD = 3,3 bis 5 V und Pin10 = VSS = GND. Die Pins 11 bis 14 sind für das Bussystem gedacht. Unser I2C-Bus hat jedoch nur die Leitungen SDA für die Daten (Serial Data) und SCL für den Takt (Serial Clock). Pin 11 und 14 werden bei der I2C-Variante des Chips nicht angeschlossen (NC = Not Connected).
Eine SPI-Variante des Port Expanders (MCP23S17) benötigt alle vier Pins. Das Serial Peripheral Interface ist ein Vier-Draht-Bus. Die Bezeichnungen dafür sieht man auf der Rückseite des Breakout Boards. Das bei AZ-Delivery erhältliche Produkt enthält jedoch den MCP23017 – die I2C-Variante (siehe Bild der Rückseite).
Weiter geht es auf der rechten Seite mit den Adresspins A0 bis A2. Der Anschluss dieser Pins an GND oder 3,3 V bestimmt die jeweilige Adresse. Für die Grundeinstellung 0x20 liegen alle 3 Pins an GND. Wenn A0 an 3,3 V angeschlossen wird, ergibt sich Adresse 0x21 usw. Bei 2 hoch 3 = 8 Möglichkeiten liegt die höchste Adresse bei 0x27.
Den Reset-Anschluss Pin18 verbinden wir über einen 10 kOhm-Pull-up-Widerstand mit VDD. Und die letzten beiden Pins INTA und INTB sind für besondere Möglichkeiten der Ports reserviert. Mit diesen sogenannten Interrupts können wir in unseren Programmen Befehle einbauen, die bei Änderungen an einem Port den Programmablauf unterbrechen, um auf die Änderung reagieren zu können. Davon werden wir in diesem Tutorial keinen Gebrauch machen. Die Anschlüsse bleiben frei.
Im ersten Teil wollen wir von der Komplexität dieses ICs nicht gleich erschlagen. Deshalb habe ich als erstes Beispiel einen Sketch für Arduino und andere Micro Controller ausgewählt, bei dem wir – wie bei vielen anderen elektronischen Bauteilen – auf eine Programmbibliothek zurückgreifen können.
Wir sehen am Beginn des Sketches die Zeilen:
#include <Wire.h>
#include "Adafruit_MCP23017.h"
Die Bibliothek Wire.h für den I2C-Bus gehört zur Grundausstattung der Arduino IDE. Die Bibliothek für den MCP23017 hat Adafruit entwickelt und bereitgestellt. Wir implementieren diese mit dem Bibliotheksverwalter.
Die Schaltung zu unserem Sketch soll eine Alarmanlage simulieren. An 16 Türen oder Fenstern sind Kontakte befestigt, die im geschlossenen Zustand Verbindung zu GND, also einem Masseanschluss, haben. Wird ein Fenster oder eine Tür geöffnet, wird der Alarm ausgelöst (in unserem Fall eine LED zum Leuchten gebracht) und im Seriellen Monitor wird angezeigt, welcher Kontakt oder welche Kontakte geöffnet wurden. Wenn Sie den beigefügten Sketch verwenden möchten, beachten Sie bitte, dass Sie ggf. die Zeile:
int ledPin=13;
ändern müssen. Ich habe bei meinem Arduino Uno die eingebaute LED an Pin 13 verwendet.
Also wie auf dem Bild alle GPIOs auf GND, A0 bis A2 an GND, Reset über 10kOhm an VDD, SDA an Arduino A4, SCL an Arduino A5, VDD an 3,3 V, VSS an GND und los geht’s.
Zu Beginn des Sketches noch einmal im Kommentar die wichtigsten Anmerkungen:
// Basic pin reading and pullup test for the MCP23017 I/O expander
// public domain! Adafruit's code slightly changed
// Connect pin #12 of the expander to Analog 5 (i2c clock)
// Connect pin #13 of the expander to Analog 4 (i2c data)
// Connect pins #15, 16 and 17 of the expander to ground (address selection)
// Connect pin #9 of the expander to 5V (power)
// Connect pin #10 of the expander to ground (common ground)
// Connect pin #18 through a ~10kohm resistor to 5V (reset pin, active low)
// Input is tested on all pins, so connect any port to ground shortly
// "Alarm" is indicated by LED(13), builtin for Arduino Uno
// port number is shown in the serial monitor
// multiple ports possible
// Physial Pin# PinName PinID
// 21 GPA0 0
// 22 GPA1 1
// 23 GPA2 2
// 24 GPA3 3
// 25 GPA4 4
// 26 GPA5 5
// 27 GPA6 6
// 28 GPA7 7
// 1 GPB0 8
// 2 GPB1 9
// 3 GPB2 10
// 4 GPB3 11
// 5 GPB4 12
// 6 GPB5 13
// 7 GPB6 14
// 8 GPB7 15
#include <Wire.h>
#include "Adafruit_MCP23017.h"
bool buttonState = HIGH;
int ledPin=13;
Adafruit_MCP23017 mcp;
void setup() {
pinMode(ledPin, OUTPUT); // use the p13 LED as alarm signal
digitalWrite(ledPin, 0);
Serial.begin(9600);
Serial.println("Alarm System activated");
mcp.begin(); // use default address 0x20 A0=GND, A1=GND, A2=GND
// (1) for 0x21 A0=3V3, A1=GND, A2=GND and so on until
// (7)for 0x27 A0=3V3, A1=3V3, A2=3V3
for (int i=0;i<16;i++)
{
mcp.pinMode(i, INPUT);
mcp.pullUp(i, HIGH); // turn on a 100K pullup internally
}
}
void loop() {
//
for (int i=0;i<16;i++)
{
buttonState = mcp.digitalRead(i);
if (buttonState == HIGH) {
Serial.println("Alarm is triggered");
Serial.print("Sensor number: ");
Serial.println(i);
digitalWrite(ledPin,1);
delay(500);
}
else {
digitalWrite(ledPin, 0);
}
}
}
Nach dem Start bzw. Reset des Sketches erhalten wir im Seriellen Monitor zunächst nur die Anzeige Alarm System activated. Sobald ein Kontakt geöffnet wird, leuchtet die LED auf und im Seriellen Monitor wird „Alarm is triggered“ sowie die Nummer des Kontakts bzw. der Kontakte angezeigt.
Zusammenfassend stellen wir für den ersten Teil fest, dass wir 16 Ports für die Eingabe gewonnen haben. Mit der Zeile:
Adafruit_MCP23017 mcp
Initialisieren wir das Objekt mcp, das unseren IC am I2C-Bus mit der Adresse 0x20 anmeldet. Mit:
mcp.pinMode(i, INPUT);
legen wir fest, dass die Ports als Eingänge definiert werden. Und mit:
mcp.pullUp(i, HIGH); // turn on a 100K pullup internally
legen wir die Voreinstellung des Eingangs fest, damit die Unterbrechung der Eingänge zu GND sofort erkannt wird.
Damit haben wir den MCP23017 erstmalig benutzt, aber die Geheimnisse der Register kennen wir noch nicht. Die wollen wir im zweiten Teil untersuchen.
Auf vielfachen Wunsch geht es hier zum Download.
11 commentaires
Andreas Wolter
@Sven: da die I2C-Schnittstelle verwendet wird, könnte man theoretisch zwei Module parallel an A4 und A5 (SDA/SCL) schalten. Dann müsste man mit den Adresspins A0 bis A2 unterschiedliche Adressen festlegen. Wenn man dann zwei verschiedene Adafruit_MCP23017 Objekte instanziiert, sollte es theoretisch funktionieren.
Also quasi so:
Adafruit_MCP23017 mcp_A;
Adafruit_MCP23017 mcp_B;
und dann später im Code auch die Objekte mit mcp_A und mcp_B ansprechen.
Versuchen Sie es und teilen uns gern Ihr Ergebnis mit.
Grüße,
Andreas Wolter
AZ-Delivery Blog
Gerhard Gleixner
Der chip hatte wohl schon immer einen Fehler, der aber angeblich nur sporadisch und selten auftritt. Wenn man die Eingänge ständig pollt, fällts vielleicht gar nicht auf.
Der Hersteller hat tatsächlich einfach das Datenblatt geändert, um eventuellen Beschwerden vorzubeugen.
Sven
Hey,
erst mal vielen Dank für die genau Beschreibung!
Würde gerne wissen wie die Programmierung bzw. Konfiguration von mehreren Expandern aussieht,
-wie spreche ich im Programm dann z.B. den zweiten an.
Vielen Dank schon mal im Vorraus.
Harry
Eine nicht ganz unwichtige Info zum MCP23017 ( Quelle = aktuellstes Datenblatt):
Seitens Microchip gab es 2022 eine Korrektur bei den Ports GPA7 und GPB7 – sie können nicht (mehr) bidirektional betrieben werden, sondern nur noch als Ausgang! Diese in der Byte-Welt unübliche Anzahl von Eingängen (7 statt 8) finde ich blöd.
Unklar ist, ob beide Pins schon immer so eingeschränkt waren und es nur ein Datenblatt-Fehler war, oder ob es 2022 tatsächlich eine Chip-Änderung gab … diverse Webseiten vertreten die Version mit der Chip-Änderung, weil es bei höheren Portfrequenzen zu I2C-Bus Problemen gekommen sein soll …
sam
ich hab den code mal auf die aktuelle adafruit library angepasst und den event trigger quasi invertiert (insgesammt ist der Code so nun lauffähig auch wenn in meiner version active LOW als trigger gillt, sollte der unterschied erkennbar sein und der compiler zumindest zum durchlaufen bewegt werde können ;) )
[code]
// Basic pin reading and pullup test for the MCP23017 I/O expander
// public domain! Adafruit’s code slightly changed
// Connect pin #12 of the expander to Analog 5 (i2c clock)
// Connect pin #13 of the expander to Analog 4 (i2c data)
// Connect pins #15, 16 and 17 of the expander to ground (address selection)
// Connect pin #9 of the expander to 5V (power)
// Connect pin #10 of the expander to ground (common ground)
// Connect pin #18 through a ~10kohm resistor to 5V (reset pin, active low)
// Input is tested on all pins, so connect any port to ground shortly
// “Alarm” is indicated by LED, builtin for Arduino Uno
// port number is shown in the serial monitor
// multiple ports possible
// Physial Pin# PinName PinID
// 21 GPA0 0
// 22 GPA1 1
// 23 GPA2 2
// 24 GPA3 3
// 25 GPA4 4
// 26 GPA5 5
// 27 GPA6 6
// 28 GPA7 7
// 1 GPB0 8
// 2 GPB1 9
// 3 GPB2 10
// 4 GPB3 11
// 5 GPB4 12
// 6 GPB5 13
// 7 GPB6 14
// 8 GPB7 15
#include
#include “Adafruit_MCP23017.h”
bool buttonState = HIGH;
int ledPin=13;
Adafruit_MCP23017 mcp;
void setup() {
pinMode(ledPin, OUTPUT); // use the p13 LED as alarm signal
digitalWrite(ledPin, 0);
Serial.begin(9600);
Serial.println(“Alarm System activated”);
mcp.begin(); // use default address 0×20 A0=GND, A1=GND, A2=GND
// (1) for 0×21 A0=3V3, A1=GND, A2=GND and so on until
// (7)for 0×27 A0=3V3, A1=3V3, A2=3V3
for (int i=0;i<16;i++)
{
mcp.pinMode(i, INPUT);
mcp.pullUp(i, HIGH); // turn on a 100K pullup internally
}
}
void loop() {
//
for (int i=0;i<16;i++)
{
buttonState = mcp.digitalRead(i);
if (buttonState == HIGH) {
Serial.println(“Alarm is triggered”);
Serial.print("Sensor number: ");
Serial.println(i);
digitalWrite(ledPin,1);
delay(500);
}
else {
digitalWrite(ledPin, 0);
}
}
}
[/code]
Jo
Ich schließe mich den Vorschreibern an.
Aber der wichtigste Hinweis wird immer noch nicht berücksichtigt:
Bitte Keine Bilder, auf denen man nichts erkennen kann. Es läuft doch auch eine Fritzing-Anleitung!
Kann man die nicht einfach in den Beiträgen umsetzen?
Werden keine Fragen beantwortet?
Klaus Hagemann
Eine Frage:
Kann ich an den Baustein ein / mehrere DS18B20 Temperatursensoren mit der entsprechenden Library verwenden? Und wenn ja, wie? Gibt es ein Beispiel?
Vielen Dank und Grüße aus Hannover von
Klaus
Lars
Schöne Anleitung und Beschreibung! Auch der Schaltplan ist kein Problem. Aber: “Also wie auf dem Bild” – nein, auf dem Bild kann man nicht wirklich etwas erkennen. Vorschlag: weglassen oder so darstellen, das man es ‘nachstecken’ kann. (ähnliche Wünsche gab es schon bei anderen Anleitungen hier)
Sam
Also ich kommentiere ja sehr wenig im Netz aber hier muss auch ich mal ein dickes Lob loswerden. Top erklärt und gute Preise im Laden, da kommt man schnell zu Ergebnissen auch mit wenig Zeit für solche Projekte. Weiter so.
PS: Ich liebe die neue Downloadfunktion.
Peter Mauss
Wirklich super!! Toll und verständlich erklärt. Früher waren einige Blog-Beiträge lieblos und teilweise
auch fehlerhaft. Das hat sich seit den letzen paar Beiträgen dramatisch verbessert. Nur so geht die
Lernkurve sowohl bei Einsteigern, als auch bei Fortgeschrittenen nach oben und es kommt kein Frust auf
wenns mal nicht klappt.
Weiter so.
Arni
So stelle ich mir ein Tutorial vor, mit einem praktischen Beispiel, nicht mit Text überladen, einfache Erklärung. Super, weiter so!