Das Portexpander Modul per I2C einbinden

Nachdem nun das Portexpander-Modul fertig aufgebaut wurde, können die ersten Tests durchgeführt werden. Und während ich das hier schreibe, fallen mir auch gleich einige Dinge ein, die der kleine Versuchsaufbau aus GREETBoard ATMega32, I2CLCD– und Portexpander-Modul hergibt. Aber ich gehe Schritt für Schritt vor.

Im Artikel über die Ansteuerung des LCD-Displays habe ich bereits einen PCF8574 angesprochen. Die Kommunikation lief aber, mehr oder weniger, über die Include-Datei „i2clcd.h“. Diesmal möchte ich den direkten Weg gehen und den Baustein auch direkt ansprechen.

Die Adressierung

Zuerst wird eine Adresse am Portexpander-Modul eingestellt. Dafür sind die Adresspins mit Jumper verbunden um diese auf 1 oder 0 zu setzen. Diese drei Signale und die, vom PCF8574 vorgegebenen, Werte bilden die I²C-Adresse. Zu beachten ist hier lediglich, dass der PCF8574A andere interne Werte vorgibt und so, mit gleicher Pinbelegung an A0-A2, eine unterschiedliche Adresse entsteht.

Adressenbildung beim PCF8574

Adressenbildung beim PCF8574

Da parallel zum Portexpander das I2CLCD-Modul verwendet wird, ist die Adresse 0x40 bereits vergeben. Aus diesem Grund wird der Jumper am Adresspin A0 auf 1 gesetzt. Somit ergibt sich die Adresse 01000010 = 0x42

Adressenjumper für das Portexpander-Modul

Adressenjumper für das Portexpander-Modul

Aus der Abbildung ist jedoch ersichtlich, dass die Adresse auch noch eine Lese-/Schreibkomponente (R/W) aufweist. Dies ist sehr wichtig, da es tatsächlich zwei Adressen pro Portexpander gibt – eine Leseadresse (R/W=0) und eine Schreibadresse (R/W=1). Bei gesetztem A0 ergibt sich somit eine Schreibadresse von 0x42 und eine Leseadresse von 0x43.

Lese- und Schreibadresse des PCF8574

Lese- und Schreibadresse des PCF8574

Datenein- und ausgabe

Die Kommunikation mit dem PCF8574 startet mit dem Senden der entsprechenden Adresse – 0x42 zum Lesen und 0x43 zum Schreiben. Wurde das Schreibkommando erteilt, wartet der Baustein nun auf die Daten, also auf acht Bits. Soll nur der Pin an P0 gesetzt werden, wird ein 11111110 (Hex: FE) gesendet. Das Portexpander-Modul quittiert dies mit einem Acknowledge (hoffentlich). Ist dieses ACK 0, ist die Sendung erfolgreich verarbeitet worden. Wie dies genau im Programm erfolgt, ist weiter unten erklärt. Ich möchte in diesem Blog nicht in die Tiefen der I²C-Kommunikation eintauchen, sondern Tipps für die praktische Anwendung geben.

Das Programm

#ifndef F_CPU                  //CPU−Takt nicht definiert?
 #define F_CPU 16000000        //...dann 16MHz
#endif

#include "i2clcd.h"
#include "i2cmaster.h"

unsigned char adr1_w = 0x42;  //Device 1 write−address
unsigned char adr1_r = 0x43;  //Device 1 read−address
unsigned char data = 0xfd;    //P1 = Low, Rest = High

Wie immer wird zu Beginn die Taktfrequenz und im Anschluss die Include-Dateien definiert. Da hier nur das Display und der I2C-Bus benötigt werden, reichen diese beiden Dateien. Anschließend werden die Adressen des Portexpander und die zu schreibenden Daten in je einer Variable gespeichert. Damit müssen diese nicht ständig neu eingetippt werden. Das könnte bei umfangreicheren Anwendungen ganz hilfreich sein. Z.B. wenn sich die Adresse eines Busteilnehmers ändert. Die Variable data beinhaltet den Hex-Code um die LED 2 einzuschalten (fd = 11111101).

int main(void)             //Hauptprogramm ab hier
{
  i2c_init();              //Starte I2C Bus
  lcd_init();              //Starte I2CLCD
  lcd_command(LCD_CLEAR);  //Leere das Display
  char s[2];               //Erstelle Variable

Im Hauptprogramm wird der I²C-Bus gestartet, das I2CLCD-Modul initialisiert und das Display gelöscht. Danach erfolgt die Deklaration der char-Variable s zur Darstellung von Bits (Angabe [2]),

i2c_start(adr1_w);                //Schreibbefehl
  if (i2c_write(data) == 1){      //Senden NOK...
    lcd_printlc(1,1,"Daten NOK"); //Schreibe auf Display
  }

Nun wird der Portexpander beschrieben. Dafür nutze ich Befehle aus der Include-Datei „i2cmaster.h“ (Na gut, die Hilfe der i2cmaster.h Datei habe ich mir schon genommen). i2c_start(adr1_w) schickt die Schreibadresse für das angeschlossene PCF8574 Modul auf den Bus. Damit weiß dieses, dass mit dem nächsten Befehl Daten geschrieben werden. Sollten die zu schreibenden Daten (i2c_write(data)) am Portexpander erfolgreich angekommen sein, dann sendet dies eine „0“ zurück – das sogenannte Acknowledge. Ist dem nicht so (es kommt also eine 1 zurück), dann gibt es eine Fehlermeldung auf dem Display.

  else {                                 //Senden OK...
    lcd_command(LCD_CLEAR);              //Lösche Display
    lcd_printlc(1,1,"Daten OK ");        //Schreibe auf Display
    itoa(data, s, 2);                    //data zu String
    lcd_printlc(2,1,(unsigned char *)s); //Beschreibe LCD
  }
  lcd_wait_ms(1000);                     //Ca. 1sek warten
  lcd_command(LCD_CLEAR);                //Leert das Display

War die Antwort keine „1“, dann ist die Sendung erfolgreich angekommen, was prompt auf dem Display angezeigt wird. Die geschriebene Information wird von Hex in eine Bit-Darstellung umgewandelt (itoa(data, s, 2);) und ebenfalls auf dem Display ausgegeben.

  i2c_start(adr1_r);                    //Schreibzugriff
  itoa(i2c_readNak(),s,2);              //Lese Daten...
                                        //...Umwandlung in String
  lcd_printlc(1,1,"Daten gelesen");     //Beschreibe LCD
  lcd_printlc(2,1,(unsigned char *)s);  //Beschreibe LCD
  lcd_wait_ms(100);                     //Kurze Wartezeit
  i2c_stop();                           //Stoppe I2C Kommunikation
}

Der letzte Schritt besteht aus dem Auslesen des Portexpanders. Hierbei werden die Zustände aller Ein-/Ausgänge des PCF8574 auf den Bus gelegt. Ähnlich dem Beschreiben, ist das Senden der Leseadresse des Moduls erforderlich (i2c_start(adr1_r);). Die Antwort wird daraufhin in eine char-Variable umgewandelt (itoa(i2c_readNak(),s,2);) und auf dem Display ausgegeben. Nach einer kurzen Zeit wird die I²C-Kommunikation unterbrochen. Ohne die Pause wurden mir hin und wieder seltsame Zeichen auf dem Display angezeigt. Mit der Pause konnte ich das unterdrücken und die Anzeige war, bei jedem Durchlauf, einwandfrei.

Video

Wie das Ergebnis mit dem gesamten Versuchsaufbau aussieht, habe ich im folgenden Video dokumentiert.

Weitere Möglichkeiten

Polling vs. Interrupt

Die oben beschriebene Methode wird „Polling“ genannt. Dieser Begriff bezeichnet das zyklische Abfragen von Zuständen. Das bedeutet, dass in festen Abständen (z.B. jede Sekunde) die Zustände der Busteilnehmer abgefragt werden. Dabei ist es unerheblich, ob sich dieser an einem der Teilnehmer geändert hat oder nicht.

Im Gegensatz hierzu kann das Interrupt-gesteuerte Abfragen angesehen werden. Der Vorteil liegt in der geringeren Auslastung des Busses, da dieser nur bei Zustandsänderungen eine Abfrage startet. Dazu stellt der PCF8574 einen Interrupt-Pin zur Verfügung, der das Signal auf Low zieht, sobald sich der Zustand an einem seiner Pins (P0-P7) ändert. Dieses Signal wird über die I²C-Verbindung an INT2 des GREETBoard ATMega32 weitergeleitet.

Auslesen einzelner Ein-/Ausgänge des PCF8574

Das Ergebnis des Auslesens sind immer acht Bits, bzw. ein Hexwert. Diese Information beinhaltet die Zustände von P0-P7. Einzelne Pins werden nicht vom Portexpander auf den Bus gelegt. Diese Einzelinformation muss der Mikrocontroller selber herausrechnen. Außerdem ist es unerheblich, ob ein Pin des PCF8574 als Ein- oder Ausgang beschaltet ist. Ein Auslesen gibt den Zustand zurück, egal ob dieser aus einem Ein- oder Ausgang generiert wurde.

Im Beispielprogramm wird eine „fd“, also eine „11111101“, an den Portexpander gesendet. Damit wird die LED, die an P1 angeschlossen ist, eingeschaltet. Das Auslesen in Anschluss wird daher ebenfalls eine „fd“ ergeben. Wird jedoch während des Auslesens die Taste an P6 gedrückt, lautet das Auslese-Ergebnis „10111101“, also „bd“ in Hex.

Nächste Schritte

Aufgrund der beiden Möglichkeiten, wird der nächste Test vom „Auslesen“ einzelner Ein-/Ausgänge handeln. Da ich bereits bei Programmierversuchen auf Widerstand des INT2 des ATMega16 gestoßen bin, spare ich mir die härtere Nuss für später auf und widme mich dem Auslesen einzelner Bits 😉

Nachtrag (02.04.2013): Die unten aufgeführten „nächsten Schritte“ sind erfolgreich erledigt. Um direkt zu den Themen zu gelangen, habe ich entsprechende Links eingefügt. 😀

  1. LED setzen und Eingänge abfragen
    1. per Polling
    2. Ausgabe am LCD-Display
  2. Definierte LED setzen, je nachdem, welcher Taster gedrückt wurde
    1. Per Polling
    2. Ausgabe am LCD-Display
    3. Die vier Taster-Bits einzeln ausgewertet („Bit 7 == High => Setzte LED 1“)
  3. Interrupt gesteuertes Auslesen des PCF8574
    1. Initialisieren des INT2 (PB2) am ATMega32
    2. Herausfinden, welcher Bus-Teilnehmer interruptet
    3. Ist Entprellung am Portexpander-Modul notwendig?

Das Programm zum Kopieren

#ifndef F_CPU           //Wenn CPU-Takt nicht bereits definiert wurde...
#define F_CPU 16000000  //...dann definiere ihn auf 16MHz
#endif
#include "i2clcd.h"
#include "i2cmaster.h"
unsigned char adr1_w = 0x42;  //Device 1 write-address
unsigned char adr1_r = 0x43;  //Device 1 read-address
unsigned char data = 0xfd;    //P1 = Low, Rest = High
int main(void) {                 //Hauptprogramm ab hier
  i2c_init();                    //Starte I2C Bus
  lcd_init();                    //Starte I2CLCD
  lcd_command(LCD_CLEAR);        //Leere das Display
  char s[10];                    //Variable für Bitdarstellung (10 Bits)
  i2c_start(adr1_w);             //Schreibbefehl für Device 1
  if (i2c_write(data) == 1){     //Sende Daten an Device 1
  lcd_printlc(1,1,"Daten NOK");  //Schreibe auf Display
  }  
  else {                             //Wenn Daten nicht angekommen sind...
    lcd_command(LCD_CLEAR);          //Lösche Display
lcd_printlc(1,1,"Daten OK ");        //Schreibe auf Display
itoa(data, s, 2);                    //data in String umwandeln
lcd_printlc(2,1,(unsigned char *)s); //Schreibe data auf Display
  }
  lcd_wait_ms(1000);        //Ca. 3sek warten
  lcd_command(LCD_CLEAR);   //Leert das Display
  i2c_start(adr1_r);        //Starte Schreibzugriff an Device 1
  //i2c_readNak();          //Lese Daten von Device 1
  itoa(i2c_readNak(),s,2);  //Lese Daten von Device 1 und...
                            //...Umwandlung in String
  lcd_printlc(1,1,"Daten gelesen");    //Schreibe auf Display
  lcd_printlc(2,1,(unsigned char *)s); //Schreibe Byte auf Display
  lcd_wait_ms(100);                    //Kurze Wartezeit
  i2c_stop();                          //Stoppe I2C Kommunikation
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.