Portexpander (PCF8574) – Einzelne Taster erkennen, einzelne Ausgänge schalten

Im letzten Artikel über den PCF8574 (Das Portexpander Modul per I2C einbinden), einem 8-bit Portexpander, wurde dieser in den I2C Bus eingebunden und erste Schreib- und Lesebefehle wurden programmiert und ausgeführt.

Das Portexpander-Modul ist Teil des GREETBoard Projekts, mit dem ich mich dem Thema AVR Mikrocontroller nähern möchte. Es besteht aus einzelnen Modulen mit unterschiedlichen Funktionen, die über Kabel und RJ-12-Stecker verbunden werden.

Das Problem – Nur ein einzelnes Bit setzen

Open Collector Ausgänge des PCF8574 - Portexpander

Open Collector Ausgänge des PCF8574 – Portexpander

Der PCF8574 bietet nur die Möglichkeit alle 8 Bits auf einmal auszulesen oder zu beschreiben. Was ist aber, wenn auf nur einen der vier Eingänge reagiert und nur einer der vier Ausgänge geschaltet werden soll? Unabhängig, ob bereits andere Ausgänge gesetzt sind oder nicht. Aber Moment mal, vier Ein- und vier Ausgänge? Wer hier stutzig wird hat Recht, der PCF8574 hat keine reinen Ein- oder Ausgänge. Er hat acht Pins (P0-P7), die als Open Collector Ausgang dienen (max 25mA pro Pin bei Schaltung gegen GND, insgesamt max 200mA)

Wie auf dem Bild zu erkennen ist, besitzt das von mir realisierte Portexpander – Modul vier LED und vier Taster, die am PCF8574 angeschlossen sind.

Was ist nun also zu tun, wenn der Taster S1 die LED1 einschalten soll. Dabei darf sich der Zustand der Pins P1-P3 nicht verändern? Wir könnten einfach ein 11111110 (P0 aktiviert) an den Portexpander schicken. Damit würden wir P1-P3 definitiv deaktivieren, da die Bits 1-3 auf High-Pegel gebracht werden. Hätte LED3 (P2) vorher geleuchtet (11111011, Bit 2 = 0), wäre es nun aus. Es muss also einen Weg geben, der den neuen Zustand herstellt aber den alten Zustand beibehält.

Die Lösung – Bitmanipulation

Im vorangegangenen Artikel über die Bitmanipulation habe ich bereits dargestellt wie die ausgelesenen Daten des Portexpanders verändert werden können. Jetzt erkläre ich, wie sie verändert werden müssen, um zu erkennen, ob ein Pin gesetzt ist oder nicht oder wie ein einzelnes Bit gesetzt wird, ohne die anderen zu verändern.

Einen einzelnen Taster erkennen

Für dieses Beispiel soll der Taster 1 ausgewertet werden. Um überhaupt irgendetwas erkennen und verändern zu können, wird der PCF8574 ausgelesen und das Ergebnis in der Variable d gespeichert. Bereits im nächsten Schritt werden zwei Manipulationen auf einmal durchgeführt – eine UND-Verknüpfung und eine Invertierung.

d = i2c_readNak();       //Schreibe Leseergebnis in d
if (~d & 0x10) {...)     //Wenn S1 gedrückt ist...

Als erstes wird die Variable d, also unser Leseergebnis, invertiert. Angenommen LED2 (P1) ist aktiviert und Taster 1 (P7) ist gedrückt (beide auf 0) wird aus 0111 1101 => 1000 0010. Interessant ist jedoch nur der Zustand des Bit 7. Daher wird als zweites eine UND-Verknüpfung mit dem invertierten Ergebnis durchgeführt. Das hierfür nötige Bitmuster (oder auch Bitmaske genannt) ist 1000 0000 (0x80).

Warum aber ausgerechnet 0x80? Bei einer UND-Verknüpfung können nur die Bits gleich 1 sein, bei denen jeweils das Bit des Musters 1 = 1 (hier die Variable d) und das des Musters 2 = 1 (hier 0x80) ist. Also:

  1000 0010
& 1000 0000
= 1000 0000

Da das Ergebnis in der Klammer des IF-Befehls >1 ist, wird der Code innerhalb der Klammern ausgeführt. Der Taster ist erkannt worden.

Die übrigen Taster können mit der hier beschriebenen Methode problemlos erkannt werden. Es muss lediglich das Bitmuster ausgetauscht werden (z.B. 0x40 für Taster 2, 0x20 für Taster 3 und 0x10 für Taster 4).

Einen einzelnen Ausgang einschalten (Bit löschen)

Taster 1 kann nun erkannt werden. Wenn dieser erkannt wird, dann soll LED 4 eingeschaltet werden. Dafür ist es erforderlich, das P3 auf low gesetzt wird. Schon wieder kann die UND-Verknüpfung gute Dienste leisten, in dem diese dafür sorgt, dass alle Bits, die zuvor high gesetzt waren, dies auch bleiben und nur Bit 3 gelöscht wird.

e = d & 0xf7;           //d UND F7 (1111 0111), aktiviert P3
i2c_start(adr1_w);      //Schreibbefehl
i2c_write(e);           //Schreibe e

Die erste Zeile beinhaltet bereits das Löschen des Bit 3. Da in dem Bitmuster nur das erforderliche Bit low ist, wird folgendes passieren:

   0110 1010           0 & 0 = 0
& 1111 0111           0 & 1 = 0   oder   1 & 0 = 0
= 0110 0010           1 & 1 = 1

Zwei Dinge sind zu erkennen. Erstens wird das Bit 3 gelöscht, da nicht beide Bit 3 gleich sind (fette Zahlen), zweitens bleibt jede 1 erhalten, da im Bitmuster (0xf7) alle anderen Bits gleich 1 sind (rote Zahlen). Anschließend wird das Ergebnis dieser Manipulation in e gespeichert und an den Portexpander gesendet.

Einen einzelnen Ausgang ausschalten (Bit setzen)

Wir wissen bereits, wie durch die Betätigung des Taster 1, die LED 4 eingeschaltet werden kann. Abschließend bleibt die Aufgabe die LED 4 wieder auszuschalten. Das entsprechende Bit muss dafür wieder auf 1 gesetzt werden. Wir gehen der Einfachheit halber vom Ergebnis der letzten Operation aus, am PCF8574 stehen daher folgende Signale an: 0110 0010 (0x62). Die Bitmaske, die wir für die Manipulation des Ergebnisses benötigen lautet in diesem Fall: 0000 1000 (0x08). Jetzt verwenden wir aber die ODER-Verknüpfung um das eine gesetzte Bit auf das Einlese-Ergebnis anzuwenden. Hier das Rechenbeispiel:

  0110 0010       0 | 0 = 0
| 0000 1000       0 | 1 = 1   oder   1 | 0 = 1
= 0110 1010       1 | 0 = 1

Die ODER-Regel besagt, dass das Manipulations-Ergebnis dort eine 1 erhält, bei dem das Bit der Maske 1 ODER der Maske 2 gleich 1 ist. Die fett gedruckten Bits zeigen dies. Da alle anderen Bits gleich 0 sind, werden die eingelesenen, gesetzten Bits nicht verändert. Die low-Bits bleiben sowieso unverändert.

e = d & 0xf7;           //d UND F7 (1111 0111), aktiviert P3
e = e | 0x10;           //löscht Signal von Taster an P4
i2c_start(adr1_w);      //Schreibbefehl
i2c_write(e);           //Schreibe e

Mit diesem Code-Schnipsel wird der Eingang des Tasters 4 zurückgesetzt, da es sonst zu einer Art Lauflicht-Effekt kommt, wenn ich danach noch die anderen Taster betätige. Ich bin jedoch noch nicht sicher, ob diese Lösung ideal ist und werde mir dazu noch weitere Gedanken machen. Mir ist jedoch wichtig, die Art und Weise der Bitmanipulation aufzuzeigen, so dass verständlich wird, wie einfach es ist, einzelne Bits zu ändern oder andere Bits zu überschreiben.

Das Programm

 

#ifndef F_CPU             //Wenn CPU-Takt nicht bereits definiert wurde...
  #define F_CPU 16000000  //...dann definiere ihn auf 16MHz
#endif

#include "i2clcd.h"           //Einbinden der .h Datei
#include "i2cmaster.h"        //Einbinden der .h Datei

unsigned char adr1_w = 0x42;  //Device 1 write-address
unsigned char adr1_r = 0x43;  //Device 1 read-address
unsigned char d;
unsigned char e;

int main(void) {                  //Hauptprogramm
 i2c_init();                       //Starte I2C Bus
 lcd_init();                       //Starte I2CLCD
 i2c_start(adr1_w);                //Schreibbefehl für Device 1
 i2c_write(0xff);                  //Alle Pins des PCF auf 0
 lcd_command(LCD_CLEAR);           //Leere das Display
 lcd_printlc(1,1,"GREETBoard");    //Schreibe auf Display
 lcd_printlc(2,1,"Tasterabfrage"); //Schreibe auf Display
 lcd_wait_ms(3000);                //Warte 3s
 lcd_command(LCD_CLEAR);           //Leere Display

 while(1) {                 //Hauptschleife
  i2c_start(adr1_r);        //Starte Lesezugriff
  d=i2c_readNak();          //Schreib Leseergebnis in d
  if (~d & 0x10) {          //Wenn S1 gedrückt ist...
   lcd_command(LCD_CLEAR);  //Leere Display
   lcd_printlc(1,1,"S1");   //Schreibe "S1" auf Display
   e = d & 0xf7;            //d UND F7 (1111 0111), aktiviert P3
   e = e | 0x10;            //löscht Signal von Taster an P4
   i2c_start(adr1_w);       //Schreibbefehl
   i2c_write(e);            //Schreibe e
   lcd_wait_ms(100);
  }
  if (~d & 0x20) {
   lcd_command(LCD_CLEAR);
   lcd_printlc(1,1,"S2");
   e = d & 0xfb;
   e = e | 0x20;
   i2c_start(adr1_w);
   i2c_write(e);
   lcd_wait_ms(100);
  }
  if (~d & 0x40) {
   lcd_command(LCD_CLEAR);
   lcd_printlc(1,1,"S3");
   e = d & 0xfd;
   e = e | 0x40;
   i2c_start(adr1_w);
   i2c_write(e);
   lcd_wait_ms(100);
  }
  if (~d & 0x80) {
   lcd_command(LCD_CLEAR);
   lcd_printlc(1,1,"S4");
   i2c_start(adr1_w);
   i2c_write(0xff);
   lcd_wait_ms(100);
  }
 }
 lcd_wait_ms(100);
 i2c_stop();
}

 

Schreibe einen Kommentar

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