IIC-Bus - Fernsehtechnik am AVR
Materialbedarf
Anz. | Bezeichnung | Datenblatt |
1 | Batterie/Spannungsquelle 9V | |
1 | Spannungsregler 7805 | |
1 | ATMega8 AVR-Prozessor | |
1 | PCF8574 | |
2 | Transistor BC548C (BC546C-BC550C) | |
2 | Widerstand 220 Ohm | |
1 | Widerstand 10 kOhm | |
2 | Widerstand 4,7 kOhm | |
4 | Widerstand 47 kOhm | |
1 | Elektrolytkondensator 100 µF/16V | |
1 | Kondensator 100nF | |
2 | Mikrotaster | |
2 | Standard-Leuchtdiode 3mm oder 5mm | 3mm, 5mm |
Was IIC eigentlich ist
AVR-Mikrokontroller bieten schon Einiges an Hardware 'On Board' an. AD-Wandler, PWM oder Zähler um nur Einige zu nennen. Doch oft ist man gezwungen, den Prozessor um einige Komponenten zu erweitern weil man z.B. eine Echtzeituhr, erheblich größeren EEPROM-Bereich oder etliche weitere Ports benötigt. Hierzu gibt es mehrere Möglichkeiten. Eine davon ist der Anschluss von IIC-Komponenten am AVR. IIC (oder I2C) wird bei Atmel auch TWI genannt, stellt aber das gleiche System dar.
IIC wurde ursprünglich von der Firma Philips (Heutiges NXP) dazu entwickelt um die Steuerung von digitalen Elementen in Unterhaltungsgeräte zu vereinfachen. 2 Leitungen sind doch erheblich leichter zu verlegen als etliche 10 Stück. Um z.B. eine 2-stellige LED-Anzeige zu steuern, reichen nun die erwähnten 2 Leitungen anstelle von 14. Kommen noch z.B. Kanalwahltasten hinzu, hätte man ohne IIC recht schnell 100 Leitungen erreicht.
Heute wird dieses System für alles Mögliche verwendet. Angefangen von einfachen Porterweiterungen, über Speicher und Uhren-IC, Displays bis hin zu Multiprozessor-Kommunikation.
Der
IIC-Bus besteht aus 2 Leitungen. Eine Leitung (SCL) gibt den Takt vor,
mit dessen Geschwindigkeit die Daten auf der 2. Leitung (SDA)
transportiert werden sollen. Hierbei wird der Takt immer von einem
Master vorgegeben. Während die Datenleitung, je nach Erfordernis, vom
Master zum Slave oder umgekehrt geschaltet ist. Beim IIC-Bus können mehrere Bausteine im Bussystem angeschlossen werden. Damit die einzelnen Bausteine wissen, welches Modul gerade angesprochen werden soll, wird beim Start der Kommunikation immer erst die Slave-Adresse übermittelt. Das Modul, welches angesprochen wird, meldet sich mit einem 'Acknowledge' und wartet auf Befehle. Die anderen Bausteine hingegen machen 'dicht' und bleiben passiv während der Transaktion. Da dieses Bussystem schon etwas älter ist, wurden hier so genannte Open Collector oder Open Drain-Ausgänge verwendet. Dies bedeutet, es wird nur das 0-Signal durchgeschaltet. Um ein 1-Signal zu bekommen, werden PullUp-Widerstände im Bus benötigt. Der Wert dieser Widerstände hängt vom verwendetem Baustein ab. Hier gibt das Datenblatt näher Auskunft. Sind mehrere IIC-Bausteine angeschlossen, so sollten die Widerstände sich an dem kleinsten Wert orientieren. Die Taktfrequenz der SCL-Leitung ist auf Standardmäßig 100 kHz festgelegt. Es gibt auch einen Fast Mode. Hier wird die Taktfrequenz auf 400 kHz erhöht. Jüngere Bausteine können sogar den 'Fast Mode Plus' mit 1 MHz Taktfrequenz. Die neuesten Entwicklungen in der IIC-Technik gehen inzwischen schon in Richtung 'High Speed Mode' mit 3,4 MHz. Leider muss man die Taktfrequenz immer am langsamsten Baustein im Bus orientieren. Werden also mehrere verschiedene Elemente im Bus betrieben, wird man meistens nicht über 100 kHz oder 400 kHz hinaus kommen. |
Kommunikation mittels IIC
Für
die ersten Versuche, nehmen wir erst einmal einen Portexpander mit der
Bezeichnung
PCF8574. Es ist hierbei aber
zwingend darauf zu achten, dass es wirklich der PCF8574
ist. Es existiert noch ein ähnlicher Typ, den PCF8574A. Dieser besitzt aber eine
andere Slave-Adresse und würde bei den Versuchen hier nicht
funktionieren. Neben der Spannungsversorgung, den 8 Ein-/Ausgängen und
den bereits erwähnten SCL und SDA-Leitung findet man auch noch den
Anschluss 'INT' und drei Pins mit der Bezeichnung A0-A2. Mit den
Leitungen ist es möglich dem Baustein 1 von 8 Adressen zuzuweisen. Somit
kann man von diesem Baustein 8 ICs gleichzeitig an einem Bus betreiben. Die Portpins des PCF8574 liefern im '1'-Zustand gerade einmal 100µA. Hierdurch müssen wir die Ausgänge, an denen wir LEDs anschließen wollen, mit Transistoren verstärken. |
Damit wir den PCF8574 ansprechen können, müssen wir erst einmal wissen, unter welche Adresse dieser reagiert. Hierzu wurde der Adressaufbau dieses IC hier einmal aufgestellt:
Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Funktion: | 0 | 0 | 1 | 0 | A2 | A1 | A0 | R/W |
Soll dieses IC angesteuert werden so müssen wir dies mit der Basisadresse 64 (40h) tun. Jeder IC-Tyo besitzt eine andere Basisadresse. Welche dies genau ist, findet man im entsprechenden Datenblatt. Hätten wir noch weitere ICs des gleichen Typs im Bus, so müssten wir die A0-A2 Leitungen entsprechend anders setzen. Wäre z.B. A0 auf 1 gesetzt, würde das IC auf die Slave-Adresse 66 (42h) reagieren.
Bit 0 teilt dem Baustein mit, ob auf das IC geschrieben werden soll (Bit=0) oder ob man Daten auslesen möchte (Bit=1). Dieses Bit ist bei jedem IIC-Baustein vorhanden, während die Adressleitungen von IC zu IC unterschiedlich sind. Es gibt IIC-Schaltkreise die sogar gar keine weitere Adressierung, neben der Basisadresse, zulassen.
Damit wir die Kommunikation mit dem PCF8574 testen können, bauen wir uns ein kleines Testboard:
|
Wird nun das nachfolgende Programm übertragen, blinken die beiden LEDs D1 und D2.
$regfile "m8def.dat" $crystal = 1000000 Config Sda = Portc.4 Config Scl = Portc.5 I2cinit Do I2cstart : I2cwbyte &H40 : I2cwbyte 3 : I2cstop Waitms 300 I2cstart : I2cwbyte &H40 : I2cwbyte 0 : I2cstop Waitms 300 Loop |
Blinkende LEDs sind ja nun nichts besonderes mehr. Aber dieses geschieht nun nicht mehr direkt am AVR, sondern wir steuern das Ganze über IIC. Wie das Ganze vonstatten geht, schauen wir uns einmal näher an.
Als erstes legen wir, nach der üblichen AVR-Konfiguration, die Ports für SCL und SDA fest:
Config Sda = Portc.4
I2cinit |
Der nachfolgende I2cinit-Befehl ist hierbei schon interessanter. Hier werden die Porteigenschaften festgelegt und die Leitungen SDA und SCL auf '1' gesetzt. Dieser Zustand ist der Ruhezustand des IIC-Buses, wenn zur Zeit keine Aktivität statt findet.
Nun öffnen wir eine Endlosschleife. In der ersten Zeile dieser Schleife, übermitteln wir unserem PCF8574 den Befehl, dass er D1 und D2 einschalten soll:
I2cstart : I2cwbyte &H40 : I2cwbyte 3 : I2cstop |
Hierbei sind mehrere Schritte nötig. Mit I2cstart wird der IIC-Bus geöffnet und alle angeschlossenen Bausteine in einen 'Achtung'-Zustand gebracht. Hierzu wird erst SDL auf '0' gesetzt und anschließend SCL. Die Reihenfolge ist äußerst wichtig, sonst 'denken' die Bausteine, es ist noch eine Transaktion aktiv und reagieren nicht.
Nachdem die Start-Bedingung hergestellt worden ist, wird die Slave-Adresse übermittelt. Als erstes wird das höchste Bit übermittelt. Nach den 8 Bit der Slave-Adresse muss der Master den SDA-Port auf Eingang stellen und einen 9. Takt senden. Nun sendet der IIC-Baustein, der angesprochen wurde ein ACK-Signal (Acknowledge). Hierbei wird die SDA-Leitung auf '0' gezogen. Gibt es kein Baustein mit der Adresse oder ist der Baustein momentan nicht in der Lage zu kommunizieren, so bleibt die SDA-Leitung auf '1'. Es wird also ein 'NACK' (No Acknowledge) empfangen. Hierdurch kann der Master prüfen, ob der Baustein vorhanden und aktiv ist.
Jetzt ist unser PCF8574 bereit, Daten zu empfangen, was mit dem zweiten I2cwbyte-Befehl geschieht. Da wir hier die beiden Portpins P0 und P1 aktivieren möchten, senden wir eine 3 zum Baustein. Auch hier wird in der Regel ein ACK-Signal zurück gesendet.
Zum Schluss muss die Transaktion noch beendet werden. Hierzu wird die Stop-Bedingung mit dem I2cstop-Befehl gesendet. Dabei wird erst SCL auf '1' gesetzt und anschließend SDA. Hierbei muss ebenso auf die Reihenfolge geachtet werden, damit die Bausteine wissen, dass die Übertragung beendet ist.
In unserem Programm folgt nun eine Wartezeit und anschließend eine weitere Übertragung, aber dieses Mal mit 0 als Datenwert. Die LEDs gehen also nun wieder aus. Danach erfolgt eine weitere Wartezeit und durch den Loop-Befehl wiederholt sich alles. Die LEDs blinken.
Der PCF8574 als Eingabeport
Wie im obigen Schalt-/Bauplan schon zu sehen ist, kann man unseren Interface-Baustein auch als Eingabeport verwenden. Hierzu müssen wir dem PCF8574 nur sagen, dass wir, anstatt Ports zu setzen, Ports auslesen wollen. Dies tun wir indem wir bei der Adressierung das Bit 0 auf 1 setzen. Wir als bei unserem Beispiel anstelle 64 (40h) den Wert 65 (41h) als Adressierung senden. Bei den nächsten Takten sendet der PCF8574 nun den Zustand der Ports.
Schauen wir uns das Ganze mal wieder mit einem kleinen Programm an:
$regfile "m8def.dat" $crystal = 1000000 Dim Dat As Byte Config Sda = Portc.4 Config Scl = Portc.5 I2cinit Do I2cstart : I2cwbyte &H41 : I2crbyte Dat , Nack : I2cstop Shift Dat , Right , 4 I2cstart : I2cwbyte &H40 : I2cwbyte Dat : I2cstop Loop |
Wird dieses Programm gestartet und eine oder beide Tasten betätigt, leuchten die LEDs entsprechend auf. Bei diesem Programm wird permanent die beiden Tasten abgefragt und wieder am Port ausgegeben.
Um dies zu ermöglichen, benötigen wir eine Puffer-Variable 'Dat' um den Tasten-Zustand zwischen zu speichern. Daher wird diese im Programmkopf definiert.
Interessant ist die Tastenabfrage selbst:
I2cstart : I2cwbyte &H41 : I2crbyte Dat , Nack : I2cstop |
Mit I2cstart wird, wie bekannt, der IIC-Bus geöffnet. Als nächstes adressieren wir unseren PCF8574. Da wir hier aber nun vom Port lesen wollen, müssen wir bei der Adressierungs-Adresse das Bit 0 setzen. Daher wird jetzt 41h auf den Bus geschrieben.
Nun können wir den Port auslesen. Dies geschieht mit dem I2crbyte-Befehl. Neben der Variablen, in der das empfangene Byte gespeichert werden soll, gibt es noch eine weitere Angabe. 'Nack' sagt dem AVR das er nach dem Empfangen des Datenbytes ein 'No Acknowledge' senden soll. Hiermit teilen wir dem Port-Baustein mit, dass wir keine weitere Daten wünschen. Würden wir 'Ack' (Acknowledge) senden, liefert der PCF8574 munter weiter Daten. Der IIC-Bus könnte hier dann auch nicht mehr richtig geschlossen werden, was die Kommunikation erheblich stören würde.
Es gibt IIC-Bausteine, bei dem es möglich ist, ganze Datenpakete zu übertragen. Hier muss dann so lange 'Ack' gesendet werden, bis das letzte gewünschte Byte übertragen wurde.
Nachdem das Byte empfangen und der IIC-Bus geschlossen wurde, wird mit 'Shift Dat,Right,4' dafür gesorgt, das der Tastanstatus an die Bit-Position der Ausgabe-LEDs verschoben wird. Die nachfolgende IIC-Befehlsfolge sendet den erhaltenen Tastanstatus wieder an den Port und schaltet entsprechend die Leuchtdioden.
Interrupt-Steuerung durch den PCF8574
Es ist oftmals nicht sinnvoll, dauernd den IIC-Port abzufragen, um festzustellen, ob sich etwas am Port getan hat. Dies blockiert den AVR für andere Aufgaben massiv. Hier wäre es doch besser, der PCF8574 'meldet' sich, wenn eine Taste betätigt wurde.
Dies tut er auch. Wenn man sich die Pinbelegung weiter oben genauer ansieht, so sieht man ein Pin Namens 'INT'. Dieser Pin geht kurzzeitig auf Low, wenn sich an einem Portpin das Signal ändert. Wenn wir diesen Pin nun mit einem Interrupt-Eingang verbinden, so können wir unser Programm so umschreiben, dass der PCF8574 nur dann abgefragt wird, wenn eine Taste betätigt oder los gelassen wird.
Um dies einmal zu testen, bauen wir den Aufbau etwas um. Leider können wir, aufgrund des Aufbaus, nur noch einen Taster anschließen:
Damit der AVR auch wirklich nur die IIC-Portabfrage durchführt, wenn sich der Port-Baustein per Interrupt-Signal meldet, brauchen wir ein anderes Programm:
$regfile "m8def.dat" $crystal = 1000000 Dim Dat As Byte Config Sda = Portc.4 Config Scl = Portc.5 Config Int0 = Falling On Int0 Isr_int0 Enable Int0 Enable Interrupts I2cinit Do : Loop Isr_int0: I2cstart : I2cwbyte &H41 : I2crbyte Dat , Nack : I2cstop Shift Dat , Right , 4 I2cstart : I2cwbyte &H40 : I2cwbyte Dat : I2cstop Return |
Nun leuchtet die LED D1 immer auf, wenn S1 gedrückt wird. Im Unterschied zu der vorherigen Programmversion wird hier der AVR nur aktiv, wenn der PCF8574 meldet, dass sich der Zustand des Tasters geändert hat.
Bevor der AVR auf den Interrupt-Eingang reagiert müssen wir natürlich den Interrupt konfigurieren. Dies geschieht hier:
Config Int0 = Falling On Int0 Isr_int0 Enable Int0 Enable Interrupts |
Der PCF8574 schaltet den INT-Ausgang auf 0, womit wir also beim AVR auf eine fallende Flanke mit 'Config Int0=Falling' reagieren müssen. Meldet der Portbaustein eine Änderung, so soll die Routine 'Isr_int0' angesprungen werden. Anschließend wird noch der INT0-Eingang aktiviert und Interrupts insgesamt erlaubt.
Im weiteren Programmlauf wartet jetzt der AVR in einer Leerschleife, bis der PCF8674 eine Änderung meldet. Es wird dann die Unterroutine 'Isr_int0' angesprungen und die uns schon bekannte Routine abgearbeitet. Danach kehrt der AVR wieder in die Endlosschleife zurück.
In dieser Endlosschleife können nun natürlich andere Aufgaben ausgeführt werden.
Eine Anmerkung noch zum INT-Ausgang des PCF8574. Dieser bleibt so lange auf 0-Pegel, bis der Portbaustein adressiert wurde. Es spielt also keine große Rolle, wie schnell der angeschlossene Prozessor auf das Signal reagiert.
Wird fortgesetzt ...