Start AVR-KursStartseitenicht verfügbar

22. AVRs und der I2C-Bus = TWI (Two Wire Interface)

Debugging von Programmen

Dieser Artikel ist weder die Übersetzung der Datenblätter zum ATMEGA 8, PCF8574 oder 24C64
noch eine Sammlung von Pinbelegungen der Bausteine. Man wird auch die grundlegenden Signaldiagramme nicht finden.
Der Beitrag soll vielmehr zeigen, wie man an die Lösung eines komplexen Problems mit hauseigenen Mitteln ohne großen aparativen Aufwand herangeht.


Kurze Anmerkungen zum I2C-Bus

Über den I2C-Bus, der auf den ATMEGAs als TWI (Two - Wire - Interface = Zweidraht-Zwischengesicht ;-) hardwaremäßig implementiert ist, gibt es im Web vielfältige Informationen - auch in deutscher Sprache. Ich verkneife mir also die Einzelheiten darüber wie die Signale auf dem Bus aussehen. Ebenso erspare ich mir die Beschreibung, wie "START-conditions" und "STOP-conditions" auf dem Bus realisiert werden. Das kann jeder, den es interessiert in den Datenblättern der Hersteller (ATMEL), (SIEMENS) etc. nachlesen. Das Aussehen der Signale ist für die Anwender der Schnittstelle in der Regel bedeutungslos, da zeitliche Abfolge sowieso nur mit einem DSO (Digitales Speicher Oszilloskop) sichtbar gemacht werden kann.

Worum es in diesem Artikel geht, ist schnell gesagt. Als Assemblerprogrammierer hat micht der undurchsichtige BASIC-Dschungel (BASCOM) mit seinen unendlich langen und verschlungenen Pfaden der Compilierung mit ungewissem Ergebnis ebenso generft wie die kryptischen Konstrukte der C-Programmierer wo man letzten Endes auch wieder nicht weiß, was da für ein Maschinencode herauskommt. Dabei muss man zur Ehrenrettung der Letzterern sagen, dass deren Code im Zweifelsfall noch am brauchbarsten bei der Realisierung dieses Projekts war. Es kam im Wesentlichen darauf an, wie maschiennah die C-Programme ausfiehlen, manche waren in C codierte Assemblersequenzen und daher gut umsetzbar.

Das größte Problem war am Anfang die Frage: "Wie kann ich kontrollieren, ob der AVR wirklich etwas sendet?". Mit Hilfe meines "SEINTEK S2405" hatte ich bald Klarheit darüber, was auf den SDA- und SCL-Leitungen vorging. Das gab mir nach anfänglichen Fehlschlägen die Gewissheit, dass Signale ausgetauscht wurden und dass z. B. auch die Frequenz auf der SCL-Leitung wie programmiert ausfiehl. Die START-conditions waren ausmachbar und die übertragenen Bits der Deviceadressen (CSW = ChipSelectWrite) ebenso. Eine genaue Zuordnung war wegen der geringen Bandbreite des S2405 allerdings nicht möglich. Hilfreich könnte für diese Messungen auch der Frequenzzähler mit dem ATTINY 2313 sein, wie er in der Zeitschrift ELEKTOR (Mai 2008) veröffentlicht wurde. Eine von mir modifizierte Version des Minicounters kann nicht nur Frequenzen messen sondern auch Impulse absolut zählen.


Abb.01: SCL- (Kanal A) und SDA-Leitung (Kanal B) bei 50µs/Div
Gesendet wurden die Deviceadresse 0x70 und ein Byte 0xAA

Ein weiteres, viel besseres Hilfsmittel waren die Debugsequenzen im Programm, also AVR eigene Hausmittel. Anfangs als Kommentare zu- und abschaltbar, führte ich bald wieder die Variable "debuglevel" ein, welche die Testsequenzen ein- und ausblendeten. Das hat den Vorteil, dass ohne langes Kommentieren und Auskommentieren, Teststufen ein- und ausgeblendet werden können, gemanagt durch das Ändern des Werts der Variablen "debuglevel". ".if - .endif"-Konstrukte sorgen für das bedingte Einassemblieren der Testsequenzen. Diese Testsequenzen sind in den Quelllistings immer noch enthalten und bei Bedarf zuschaltbar. Dadurch wird die Art des Debugging sehr deutlich.

In einigen Fällen erwiesen sich die Testsequenzen, die ausnahmslos Rückmeldungen auf die RS232 ausgaben (38400 Baud, 8n1), als gute Helferlein zu Timing-Problemen. Durch das plötzliche Funktionieren der I2C-Proceduren nach Einschalten der Testsequenzen wurde klar, dass z. B. einfach länger auf das Eintreffen eines bestimmten Bits im TW-Controlregister (TWCR) oder TW-Statusregister (TWSR) gewartet werden musste. In anderen Fällen wurde durch Auswertung der Rückmeldungen bewusst, dass das Programm wegen einer unvollständigen oder falschen Auswertung der Statusmeldung hing oder der Übrtragungsvorgang falsch angestoßen wurde. Hilfe brachte in den meisten Fällen dann das genaue Studium der umfangreichen ATMEGA8-Datenblätter zum Thema TWI, besonders die Tabellen mit den Statusrückmeldungen (ATMEL Datenblatt zum ATMEGA8: Tabelle 20.3, Seiten 179/180; Tabelle 20.4, Seiten 182/183).

Der Leser wundere sich daher nicht über die vielfältigen Testsequenzen, die überall im Projektpaket verstreut sind. Natürlich kann man zur Straffung des Codes alle ".if - .endif"-Konstrukte herauslöschen. Dieses und die Möglichkeit der Erweiterung/Straffung der Programmbibliothek bleibt jedem User selbst überlassen. Auch erhebt die vorliegende Procedurensammlung keineswegs den Anspruch auf Vollständigkeit. Jede spezielle Anwendung bedarf gewisser Anpassungen.

 

Lesen und Schreiben auf dem I2C-Bus

Nein, ich werde hier nicht noch einmal die schon manigfach an anderer Stelle behandelten Diagramme der Signalpegel auf den Busleitungen beschreiben. Wer das haben möchte, der möge sich einfach die Datenblätter zu den AVR-Processoren (Seiten 169/170) oder die Unterlagen der EEPROM-Hersteller etc. ansehen. Für mich war es ungewöhnlich, wie das TWI beim Schreiben und Lesen angesteuert werden muss. Und genau darüber kann man hier etwas erfahren.

Zum Vergleich: Um ein Byte über die RS232 zu senden, warte ich, bis die Schnittstelle frei ist, was ich am gesetzten UDRA-Bit im Register UCSRA erkennen kann, schreibe mein Byte in das Datenregister - das wars. Der Schreibvorgang in das Datenregister löst also bereits die Funktion der USART-Einheit im AVR aus. Natürlich muss das Modul dazu eingeschaltet sein, was wie üblich beim Programmstart erfolgt.

Beim I2C-Bus ist das anders. Das TWI-Modul wird jedes Mal bei Benutzung erneut aktiviert, enabled (TWEN-Bit) und das Interruptbit TWINT hat Auslösefunktion. Beide Bits sind mit weiteren Steuerbits im TWCR = Two Wire Control Register untergebracht. Auf dieses Register wird ebenso wie auf TWSR, TWDR, TWBR und TWAR mittels IN- und OUT-Assemblerbefehlen zugegriffen. Sobald nun das TWEN-Bit gesetzt ist, wird die normale Funktion derjenigen Anschlüsse des AVR, welche die Leitungen SDA und SCL repräsentieren, überschrieben. Das TWI übernimmt also die Steuerung der Bits, normale IN/OUT-Operationen sind nicht mehr möglich. Soll ein Byte gesendet werden, dann wird es in das Datenregister TWDR geschrieben und man prüft auf das Erscheinen einer 1 im TWINT-Bit. Ist TWINT = 1, dann wird es durch das Schreiben einer 1 in dieses Bit gelöscht und erst jetzt beginnt das TWI-Modul zu arbeiten und taktet das Byte über die Leitung. TWINT und TWEN werden in der Regel gleichzeitig ins TWCR eingeschrieben. Aber Vorsicht! Wird versucht, das TWDR-Register zu beschreiben, so lange TWINT = 0 ist (dann läuft gerade irgend eine Aktion) dann registriert das TWI diese Kollision des Schreibzugriffs und setzt das TWWC-Bit. Meist hängt dann das Prgramm, wenn diese Eventualität nicht abgefangen wird.

Ein weitere Besonderheit des TWI ist es, dass Start- und Stop Informationen nicht wie bei der RS232 automatisch geschrieben werden, sondern separat gesendet werden müssen. Das hat mit der Philosophie der Bussteuerung zu tun und bringt auch gewisse Vorteile. Das Schreiben einer START-CONDITION erfolgt durch das TWI, wenn gleichzeitig mit TWINT und TWEN auch noch das Bit TWSTA im TWCR gesetzt wird. Analog geht das Senden einer STOP-CONDITION vor sich. Mit dem Senden einer START-CONDITION versucht ein Controller die Steuergewalt über den Bus zu bekommen.

Jetzt tauchen bei der TWI-Nutzung die ersten Probleme auf. Denn als nächstes muss nun die gewünschte Slaveeinheit am Bus angesprochen werden. Dazu sendet man die Hardwareadresse des Chips in Verbindung mit einem Schreib/Lese-Bit. Die Hardwareadresse, auch als Chipselect (CS) bezeichnet, setzt sich aus dem Highnibble (=höchstwertige 4 Bit), das bei typischen Slavedevices bausteinspezifisch von Hersteller vergeben und fest einprogrammiert ist und den Adress-Bits 3:1 zusammen. Diese drei Bits werden durch den Konstrukteur der Schaltung bestimmt, indem die Anschlüsse A0:A2 des Chips entweder an Masse (GND) oder über einen Widerstand (10 k) an die Betriebsspannung (Vcc) gelegt werden. Das Bit 0 ist das R/-W - Bit. Zum Schreiben hat es den Wert 0, zum Einleiten einer Leseaktion den Wert 1. Für den im Projekt eingesetzten EEPROM-Chip 24C64 (2 KB Adressen) sieht das so aus:

Bitnummer:
7
6
5
4
3
2
1
0
Chipkennung des Herstellers (Datenblatt)
1
0
1
0
Adressleitungen A2, A1, A0 (Platine)
0
0
0
Schreiben (-W) / Lesen (R) (Programmaktion)
X
CS-Byte
1
0
1
0
0
0
0
X
CSW-Adresse
1
0
1
0
0
0
0
0
CSR-Adresse
1
0
1
0
0
0
0
1
Tab. 01: Zusammensetzung von CSW- und CSR-Byte

Gut, das war nicht schwer - also wo ist das Problem? Es liegt im Absenden des, z. B., CSW-Bytes. Bevor man CSW senden kann müssen zwei Bedingungen erfüllt sein.

1. Das TWINT-Bit muss durch die TWI-Hardware gesetzt worden sein.

2. Das TWI muss dem Programm mitgeteilt haben, dass die START-condition abgesetzt worden ist, das geschieht über das Statusbyte in TWSR.

Zur korrekten Interpretation des Statusbytes müssen dessen 3 niederwertigste Bits ausmaskiert werden, die enthalten die sog. Prescalerbits, mit denen der Systemtakt herunter geteilt werden kann (Datenblatt Seite 193). Enthält der Rest dann 0x08, dann wurde die START-condition korrekt auf den Bus gebracht und der Controller kann nun das nächste Byte, also hier das CSW-Byte, senden. Das wird im Einzelnen noch gezeigt.

Diese ganz einfache Situation zeigt die übliche Vorgehensweise beim Buszugriff. Die Vorgänge werden etwas komplexer, wenn außer START und STOP auch noch Datenbytes übertragen werden sollen. Die Prüfung auf ein gesetztes TWINT-Bit ist Standard und kann daher durch ein Unterprogramm erledigt werden. Die Statusmeldungen sind differenziert und müssen deshalb stets angepasst zur Situation abgefragt werden.

Die Routinen in diesem Projekt enthalten keine Fehlerdiagnose oder die Feststellung eines Timeouts sondern beschränken sich lediglich auf den Kern der Datenübermittlung. Eine Erkennung und Behandlung von Eventualfällen, die an einer bestimmten Programmposition eintreten könnten, muss für den jeweiligen Einsatzzweck stets erneut erwogen werden. Falls nötig muss der Anwender diese Sonderfälle durch entsprechende Programmstrukturen berücksichtigen. Als Hilfe dazu dienen die Diagramme auf den Seiten 180 uns 183 im Datenblatt des ATMEGA8.

Noch eine Bemerkung zur herstellerspezifischen Hardwareadresse und zur Anzahl der Adressleitungen der Bausteine. Der Portexpander PCF8574 ist baugleich mit dem PCF8574A, hat jedoch einen anderen Hardwareanteil der Adresse. Ein weiterer in die engere Wahl gezogener Baustein ist der PCF8591, ein Analog-Digitalwandler (ADC) und Digital-Analog-Wandler (DAC).

Baustein
Hardwareanteil der Adresse
Anzahl Adressleitungen
Funktion
24C64
0b1010
3
2KB EEPROM
24C512
0b10100
2
64KB EEPROM
PCF8574
0b0100
3
Portexpander
PCF8574A
0b0111
3
Portexpander
PCF8591
0b1001
3
AD-DA-Wandler
Tab. 02: Zusammenstellung der verwendeten Bausteine

 

Die Platinen

Zum Projekt wurde die MEGA8-Entwicklungsplatine Aus Kapitel "17. Experimentierplatine für den ATMega 8-16" als Master eingesetzt. Das Pinlayout lässt erkennen, dass die Pins 27 und 28 die Leitungen SDA und SCL für das TWI bereitstellen. Wie bereits erwähnt sind die Portleitungen PC4 und PC5 nicht verwendbar, wenn das TWI verwendet wird.

Abb. 02: Pinbelegung des ATMEGA 8

 


 

Ein Miniplatinchen beherbergt das EEPROM vom Typ 24C64. Dieser Baustein versteht direkt das I2C-Prptokoll. Mit den 3 Adressleitungen A0 bis A2 kann man insgesamt 8 dieser Bausteine an einem Bus betreiben. Optimaler ist jedoch dann der 24C512, der im gleichen Gehäuse 64 KB Speicher zur Verfügung stellt.

Mit Ausnahme der Processorplatine findet man alle Layouts dieses Projekts als PDF für den Laserausdruck zum Aufbügeln.

Abb. 03: EEPROM-Testplatine

Auch der Portexpander PCF8574 ist auf einer eigenen Platine untergebracht. Interface und Adressleitungen weisen die gleiche Struktur auf wie der 24C64. Daher konnte für beide Platinen der gleiche 4-polige Verbindungsstecker verwendet werden. Die Ausgänge des Ports können 8 LEDs über jeweils einen Widerstand gegen Masse schalten. Ich habe jedoch ein vorhandenes Modul verwendet, das 8 LEDs aus einer Balkenanzeige über einen Treiber ULN 2804 versorgt. Dieses Modul wurde bereits in Kapitel "15. Spannungen vergleichen - Anschluss von Sensoren" verwendet und dort beschrieben.

Abb. 04: Platine für den Portexpander PCF8574 (A)

Abb. 05: LED-Balkenanzeige über ULN2804

Zum Testen der Eingangsfunktionalität des PCF8574 (A) wurde eine Platine mit 8 DIL-Schaltern (Mäuseklavier) eingesetzt, die alle gegen Masse (GND) schließen.

Abb. 06: Die linke Schaltereinheit wurde verwendet

Abb. 07 zeigt das im Projekt verwendete Blockschaltbild. Für den Test der Eingabfunktionalität des PCF8574 A wurde statt der LED-Einheit der linke Schalterblock der Platine aus Abb. 06 eingesetzt. Die freien Schalterenden wurden jeweils mit einem Portpin des PCF8574 verbunden. Beim Betrieb von mehreren I2C-Devices wird pro Busleitung nur ein Pullup-Widerstand verwendet. Dabei spielt es keine Rolle, wo diese Widerstände eingebaut werden.


Abb. 07: Blockschaltschema der Versuchsanordnung

 

Vorbereiten des TWI-Moduls

Wie jedes Modul eines AVR-Controllers müssen, wenn auch sehr wenige, Voreinstellungen getätigt werden. Die beiden Prescalerbits (Bit 0 und 1) des TWSR sind zu belegen und ein Wert für das TWBR (Bitratenregister) muss berechnet werden. Die Formel dazu steht im Datenblatt des ATMEGA8.


Formel 01: Bittakt auf dem I2C-Bus

In dieser Form nützt sie allerdings wenig, denn die SCL-Frequenz gibt der Baustein vor und den Wert der Prescalerbits wird für die maximale Taktfrequenz auf der SCL-Leitung Null sein. Also sollte die Formel besser nach dem Wert für das TWBR aufgelöst werden. Ja und dann wollen wir den Wert nicht jedesmal, wenn wir die Frequenz des Prozessortakts ändern die SCL-Frequenz neu berechnen, das kann der Assembler machen. Dafür bedarf es allerdings eines mathematischen Tricks. Denn der Assembler stellt keine Möglichkeit zur Verfügung, Potenzen mit der Basis 4 zu berechnen. Also wird das gute alte Potenzgesetz zum Potenzieren von Potenzen angewandt. Für 2er-Potenzen gibt es nämlich die Funktion "exp2"


Formel 02: Basisumwandlung

Mit ein wenig 8-Klass-Mathematik kann jetzt die Formel umgebaut werden.


Formel 03: Berechnung des Werts für das Bitratenregister

Im Programm schaut das dann mit Xtal als CPU-Taktfrequenz so aus:

"tw_prescaler & 0b00000011" sorgt dafür, dass wirklich nur diese beiden Bits in das TWSR geschrieben werden. Jetzt kann's losgehen!

 

Einfache I2C-Busbausteine am Beispiel des PCF 8574 (A)

Der PCF8574(A) ist ein sogenannter Portexpander, ein Baustein also, der zwei Eingangsleitungen nutzt und auf 8 Leitungen aufbläht. Damit erweitert er die Anzahl von IN/OUT-Leitungen, die einem Controller zur Verfügung stehen. Jedes Schieberegister mit mindestens einem Takt- und einem Dateneingang kann ähnliches leisten (SN74165, SN74595, CMOS 4094). Diese Bausteine werden aber nach keinem festen Protokoll angesprochen, besitzen selbst keine Adressleitungen für die Chipauswahl und benötigen in der Regel auch noch eine dritte Leitung für die Freigabe des Registerinhalts auf die Ausgangsleitungen. Außerdem sind sie entweder nur Seriell-Paralel-Wandler oder umgekehrt, nie aber beides. Der PCF8574(A) kann als Ausgabe- und Eingabeeinheit benutzt werden und nimmt Stromstärken bis zu 25 mA pro Portleitung von Vcc auf.

Den Ausgang einer Portleitung des PCF8574(A) kann man sich vereinfacht als Schalter vorstellen, der in Reihe zu einem internen Widerstand liegt. Bei offenem Schalter kann man einen externen Widerstand mit maximal 100 µA versorgen. Die Stromstärke wird in diesem Fall durch den internen Widerstand limitiert. Der Schalter selbst kann aber Ströme von bis zu 25 mA verkraften, die im über den externen Widerstand im rechten Bild zugeführt werden. Das reicht z.B. zum Schalten von LEDs.


Abb. 08: Ersatzschaltbild für die Ausgangsstufe des PCF8574(A)

Einzelheiten zum Chip findet man auf vielfältigen Datenblättern, die im Web existieren. Hier wird je nach Hersteller auch wieder mehr oder weniger intensiv auf die Signale auf dem I2C-Bus eingegangen. Dass der I2C-Bus mit einem Protokoll gefahren wird, dessen funktionale und zeitliche Bedingungen erfüllt werden müssen, sollte bereits klar geworden sein. Die Abfolge zum Senden eines Bytes an einen einfachen Chip zeigt Tabelle Tab. 02.

MEGA8 sendet eine START-condition 0b10100100 -> TWCR

TWINT ist 0
TWINT-Bit wird gesetzt
Statusbit wird 0x08
MEGA8 schreibt CSW-Byte in TWDR
MEGA8 startet Bytetransfer 0b10000100 -> TWCR
TWINT ist 0

TWINT-Bit wird gesetzt

Statusbit wird 0x28
MEGA8 sendet eine STOP-condition
Daten erscheinen an den Portausgängen
Tab. 03: Schema zum Senden eines Bytes an den Portexpander PCF8574(A)

Nach diesem Ablauf wurden die folgenden UPs erstellt. Das Vorgehen wird im Wesentlichen aus den Komentaren klar. Auf weitere Details nimmt der Text Bezug.

 

Schreiben auf den Port

Die Ausgabe eines Bytes an einen einfachen I2C-Chip wie den PCF8574(A) erledigt die Routine "i2c_write". Die Chipselect-Informationen werden in R16, das auszugebende Byte in R17 an die Routione "i2c_write" übergeben. Diese Procedur ruft ihrerseits die entsprechenden UP auf, welche selbst für die Einhaltung des I2C-Protokolls sorgen. Als Ausgabeeinheit muss der Baustein PCF8574A durch CSW = 0b01110000 = 0x70 adressiert werden (siehe Tab. 01 und Tab.02).

Am Beginn jedes I2C-Buszugriffs muss eine START-condition stehen, also auch hier. Das managt die Routine "i2c_start". Nachdem R16 mit dem Inhalt 0b10100100 ins TWCR geschrieben ist, wird das TWINT-Bit rückgesetzt und es beginnt die Wartephase auf die Statusmeldung tw_start=0x08. Sobald dieser Status erreicht ist, wird die Procedur verlassen. Die wichtigsten Statusmeldungen sind im folgenden Skript zusammengestellt.

Nach der START-condition muss das CSW-Byte gesendet werden. In Register R16 wird es an die Procedur i2c_write übergeben. Die Procedur "i2c_send_csw" erwartet das CSW-Byte in eben diesem Register R16 und gibt es mit Handshake auf dem Bus aus.

Hat das Slavedevice mit einem ACK geantwortet, kann das Datenbyte gesendet werden. Das erledigt die Routine i2c_send_byte. Auch diese Procedur erwartet das zu sendende Byte in Register R16 weshalb es vor dem Aufruf von R17 nach R16 kopiert wird. TWINT=1 wurde zuvor von der Procedur "i2c_send_csw" sicher gestellt.

Eine STOP-condition gibt nach erfolgtem Transfer den Bus wieder frei. Mit dem ACK vom PCF8574(A) wird das gesendete Byte an den Portleitungen sichtbar.

 

 

Einlesen der Quasi-Eingänge

Der PCF8574(A) hat keine Steuerlogik für den Port wie ein AVR, er besitzt also kein Datenrichtungsregister, mit dem die Eigenschaft Eingang/Ausgang eingestellt werden könnte. Trotzdem kann er die Pegel an den Portleitungen einlesen.

Hierzu ist es nötig, die gewünschte Portleitung Px auf HIGH-Pegel = Logisch 1 zu legen, damit der Schalter des vereinfachten Schaltbildes geöffnet ist. An diesem Portpin Px liegt jetzt über den internen Widerstand die Betriebsspannung Vcc. Ein externer Schalter/Taster oder der Ausgang eines Sensors kann nun den Spannungspegel an Px gegen Masse ziehen. Der sich ergebende Strom kann höchstens 100 µA betragen, da er durch den internen Widerstand begrenzt wird. Abhängig von der Stellung des externen Schalters wird der PCF8574(A) an dieser Portleitung Px ein HIGH = 1 (Schalter offen) oder LOW = 0 (Schalter geschlossen) feststellen. Wäre der interne Schalter bereits geschlossen (Ausgänge führen LOW = 0), dann könnte ein externer Schalter keine Pegeländerung mehr bewirken.

Abb. 09: Arbeitsweise einer Portleitung des PCF8574(A) als Eingang

Die zugehörige Routine heißt "i2c_read". Sie nutzt die Tatsache, dass nach dem Einschalten der Betriebsspannung alle Ausgänge auf HIGH = 1 liegen. Will man sichergehen, dass sich das auch während des Betriebs so verhält, dann müsste man einen Scheibbefehl (i2c_write) absetzen und damit die entsprechenden Portleitungen auf HIGH = 1 setzen. Zum Decodieren der Eingangsinformationen sind wie üblich Maskenbytes zusammen mit einem AND- oder ANDI-Befehl zu verwenden.

Das Warten auf TWINT=1 wurde zur Straffung des Codes in eine weitere Unterprogrammroutinie (UPR) "i2c_wait_for_twint" verpackt. Alle internen Routinen sind wie üblich so geschrieben, dass sie keine Register verändern außer diese werden als Parameter zur Eingabe oder Ausgabe benutzt.

Die UPR "i2c_sen_csr" ist ähnlich aufgebaut wie die zu CSW gehörige. Sie unterscheiden sich nur im Wert des Statusbytes mr_sla_ack(=0x40) statt mt_sla_ack (=0x18).

Die Routine "i2c_receive_byte" bedarf noch einer Bemerkung. Der Empfang eines Bytes wird I2C-üblich mittels Rücksetzten des TWINT-Bits initiiert, zum Einschalten des TWI muss auch TWEN = 1 sein. Mit dieser Konstellation wird ein ACK seitens des Controllers (AVR) an den Slave (PCF8574) unterdrückt damit der MEGA8 im Anschluss eine STOP-condition absetzen kann. Sollen mehrere Bytes empfangen werden, dann muss der Controller nach jedem empfangenen Byte ein ACK an den Slave senden. Hierfür ist es nötig im TWCR das Bit TWAE (TWI-Acknowledge Enable) zu setzen. Natürlich muss dann im Anschluss an den Empfang nicht auf "mr_data_nack" (=0x58) sondern auf "mr_data_ack" (=0x50) getestet werden. Wenn das funktioniert, dann klappt auch der Kontakt zu den I2C-EEPROMS (Ich bin bei der Entwicklung mangels PCF-Bausteinen den umgekehrten Weg gegangen).

 

EEPROMS der Serie 24CXXX

Ein Electrical Erasable Programable Read Only Memory, kurz EEPROM, umfasst ein Array von Speicherzellen, deren Inhalt wie der Name schon sagt, elektrisch löschbar, und danach wieder neu programmierbar ist. Diesen Vorgang kann man etliche zig-1000 mal wiederholen, weitere Information findet man in den Datenblättern der Hersteller. Der entscheidende Vorteil der I2C-EEPROMS gegenüber den Paralleltypen ist die geringe Anzahl von Anschlusspins und damit verbunden die winzige Erscheinungsform. Unterschied zum PCF8574: Es muss nicht nur eine Adresse angesprochen werden sondern je nach Umfang des Speicherbereichs bis zu 64KB Zellen. Das erfordert eine Zweiteilung bei der Adressierung - Hardwareadresse = CSW/CSR und Adressierung der Zelle. Weil der Adressraum 256 übersteigt, müssen zur Zellenadressierung auch 2 Bytes übermittelt werden - der höherwertige Anteil geht linksbündig voraus, das Lowbyte folgt. Nach der Adressierung wird entweder das Datenbyte gesendet oder auf Empfang umgeschaltet und ein Datenbyte empfangen.

Eine gelöschte Speicherstelle enthält übrigens ähnlich wie beim EEPROM-Modul oder beim Flash-Speicher des Controllers den Wert 0xFF. Beim Programmieren werden dann die 0-Bits auf GND-Potential gebracht, die 1-Bits bleiben unangetastet. Im Gegensatz dazu werden die Bits der UV-löschbaren EPROMS auf 0 gesetzt.

 

Random Write

Der einfachere Teil ist das Schreiben eines Bytes, deshalb beginne ich damit, die Routine heißt "i2c_eewrite" und unterscheidet sich von der einfacheren Sequenz für den PCF8574(A) wie eben schon beschrieben. Wegen der manigfachen Parameter werden diese nicht mittels Registern sondern über das SRAM übergeben. Hinsichtlich der Register verhält sich die Procedur transparent, Registerinhalte erscheinen also nach dem Aufruf unverändert. Die Modularisierung der einzelnen Transfervorgänge in UPR ermöglicht ein übersichtliches Erscheinungsbild der Routine. Die ersten beiden Komentarzeilen informieren über die Ablagestellen der zu übergebenden Parameter. Die erforderlichen RAM-Zellen werden in der Datei "i2c_inc_h.asm" definiert und sind überdies im Kopfteil dokumentiert.

Bei einem EEPROM muss das gesendete Byte nicht einfach nur an einen Port weitergegeben werden, das geht ratz fatz, sondern die Information ist in die Speichermatrix zu übertragen. Das kostet etwas Zeit (wie auch im AVR selbst). Deshalb muss zum Abschluss der Procedur gewartet werden, bis der Schreibvorgang im EEPROM fertig ist. Der Hersteller setzt dafür 8 ms an, für den 24C64 habe ich ca. 4 ms empirisch ermittelt. Der in R17 übergebene Wert 82 für die äußere Zählschleife gilt bei einer Quarzfrequenz (Xtal) von 16 MHz. R17 wird in der Warte-UPR auf Null heruntergezählt und so zurückgegeben.

Wird der Parameter für die Zählschleife zu klein gewählt, dann gibt es Aussetzer bei schnell auf einander folgenden Aufrufen der i2c_eewrite-Procedur. Beim Test ergab das Lesen einer mit 41 statt 82 geschriebenen Reihe fortfolgender Zahlenwerte statt 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... 0, 255, 2, 255, 4, 255, 6, ... Mit 82 geschrieben ergab das Auslesen genau die Werte der Vorgabe. Die Wartezeit ergibt sich übrigens einzig aus der Übertragung des Datenbytes. Die Übernahme der beiden Adressbytes in den Adresszähler geht ebenso rasch wie die Übernahme des Bytes auf die Portleitungen beim PCF8574(A).

 

Page Write

Eine Besonderheit der I2C-EEPROMS besteht darin, dass man nicht nur einzelne Bytes übertragen kann sondern bis zu 32 Byte auf einmal. Natürlich werden die Datenbytes nicht gleichzeitig sondern auch wieder sequentiell übertragen. Was die Sache aber vereinfacht und stark beschleunigt, ist dass sowohl das Senden einer START-condition sowie die Übermittlung der Zellenadresse nur einmal am Beginn stattfinden müssen. Danach werden nur noch Datenbytes übertragen. Das EEPROM zählt die Speicherzellenadresse nach jedem Byte selbständig hoch.

Ein Datenpuffer im SRAM ist für Page Write sehr sinnvoll, weil dadurch der Code sehr übersichtlich wird und außerdem universell verwendbar ist. Den Aufbau des Pufferinhalts kann das übergeordnete Programm individuell erledigen. Immer wenn dann ein Datensatz komplet ist, wird er mit einem UPR-Aufruf ins EEPROM geschrieben. i2c_eePwrite verwaltet einen Pufferzeiger im x-Registerpaar. Die Daten werden aus dem Puffer also indirekt über das Registerpaar XL:XH adressiert gelesen. Die Anzahl der zu übertragenen Bytes wird in der SRAM-Zelle tw_buffptr übergeben und zunächst auf Plausibilität überprüft. Die 3 HSBs werden ausmaskiert, damit ergeben sich Zahlenwerte von maximal 31. Rechnet man das Bit an Pufferposition 0 mit hinzu ergeben sich maximal 32 zu übertragende Bytes.

Nach dem Stezen des Pufferpointers wird eine START-condition abgesetzt, das EEPROM über CSW adressiert und dann nur einmal die Startadresse des Speicherbereichs im EEPROM als HIGH-Byte und LOW-Byte übertragen. In der Schleife i2c_eePwrite_01 wird danach der Puffer ausgelesen und jedes Byte an das EEPROM übertragen. Wenn der Zähler in R17 auf 0 angekommen ist, hat die gesamte Übertragung auch geklappt. STOP-condition senden und das Schreiben der Bytes abwarten - fertig.

Zum Page Write muss man allerdings noch ein paar interessante Dinge wissen.

1. Es müssen nicht unbedingt 32 Byte geschrieben werden, es dürfen auch weniger sein. Eigentlich auch mehr! Aber das führt zum Datenverlust, denn die überzähligen Bytes überschreiben den Puffer im EEPROM von vorne beginnend. Schreibt man 35 Bytes, dann hätte man sich das senden der ersten drei sparen können, denn die werden von den letzten drei Werten überschrieben. Der letzte Satz gilt auch nur dann, wenn die Startadresse des Speichers im EEPROM ein Vielfaches von 32 ist, also 0, 32, 64, 96, 128, ... 0x1FE0 = 8160. Im Hexcode muss die vorletzte Ziffer gerade sein und die letzte Stelle eine 0.


Abb. 10: Das geschieht, wenn man mehr als 32 Bytes beim Pagewrite sendet.
Die Startadresse im EEPROM-Speicher ist durch 32 teilbar

2. Es werden weniger als 32 Bytes gesendet, dann ist alles OK. NEIN! Auch hier müssen verschiedene Situationen unterschieden werden.

2.a) Der garantiert sichere Fall: Wir wollen 5 Bytes senden und haben eine EEPROM-Zieladresse von 0x0364

Weshalb ist der Fall garantiert sicher? Die gewünschte Zieladresse liegt um 5 Positionen über dem letzten 32-er -Vielfachen 0x0360. Es sind also noch 27 Speicherzellen bis zum nächsten Vielfachen von 32 als Zellenadresse (0x0380) frei. Die 5 Bytes werden also 1:1 an die Adressen 0x0364 bis 0x0368 geschrieben. Natürlich werden vorher dort befindliche Werte überschrieben, aber das ist jetzt nicht Gegenstand der Betrachtung. Genau genommen werden die Werte nicht überschrieben, das ginge selten gut, sondern zuerst gelöscht und dann neu geschrieben. Der AVR-Controller kriegt davon nichts mit außer dass er eine Zeit lang warten muss, die besagten 4 ms, bis der nächste Schreibzugriff erfolgen kann.


Abb. 11: Kein Problem, das nächste 32er-Vielfache wird als Speicheradresse nicht erreicht oder überschritten

2.b) Der gemeinste Fehler: Wieder senden wir 5 Bytes. Die Startadresse ist jetzt 3 Stellen vor dem nächsten 32er-Vielfachen.

Was ist so besonders gemein an dieser Situation? Die ersten drei Bytes werden wie erwartet an die gewünschten Zieladressen geschrieben. Die restliche beiden Bytes landen aber nicht in der nächsten 32er-Zone sondern, völlig daneben, am Begin der Zone in der eben die anderen drei Bytes gespeichert wurden. Dort sucht und dort findet sie kein weiblicher landwirtschaftlicher Schlammwälzer. Nachdem das Ziel dieses Projekts kein unmittelbares Produkt war sondern Information über das Verhalten von Bauteilen am I2C-Bus nebst den zugehörigen Protokollen hat es Spaß gemacht genau diese Erscheinungen zu studieren. Im Produktionsfall wird man sich ohne diese Erkenntnisse wohl ein Monogramm in den Allerwertesten beißen. Nicht genug damit, dass die neuen Werte nicht an der erwarteten Stelle zu finden sind, nein, auch die überschriebenen Werte gibt es natürlich nicht mehr. Für das Aufnehmen einer Messreihe ein fataler Umstand!


Abb. 12: Datenverlust beim Überschreiten einer 32er-Zone in der Datenlandschaft eines I2C-EEPROMs

 

Löschen des Speichers

Das Löschen des EEPROM-Speichers kommt dem Beschreiben mit lauter 0xFF-Bytes gleich. Das kann für einzelne Zellen ebenso wie für größere Bereiche gelten. Dem entsprechend kann man die Proceduren i2c_eewrite oder i2c_eePwrite dafür einspannen.

1. Löschen einzelner Zellen

Einzelne Zellen werden durch einschreiben eines 0xFF-Bytes gelöscht. Das Löschen größerer Bereiche kann über i2c_clear erfolgen.

Die Procedur ist so eingerichtet, dass ein kompletter 24C64 Chip gelöscht wird. Das High-Byte der Adresse der letzten zu löschenden 256-Byte-Speicherbank wird im SRAM in der Zelle tw_last übergeben, die Startadresse ist in jedem Fall 0x0000. Die Chipadresse icnl. W-Bit steht in tw_hwadr, die Speicheradresse wird in den Registern R16 (LOW) und R17(HIGH) hochgezählt und in tw_addrl:h an i2c_eewrite übergeben. Das Löschen des 8KB-Chips dauert auf diese Weise ca. 34 Sekunden.

 

2. Löschen mit Page Write

Page Write löscht ebenfalls den gesamten Speicher. Zu Beginn wird der SRAM Puffer einmalig mit 32 0xFF-Bytes gefüllt. Danach wird dieser SRAM-Puffer 256 mal im Page Write Modus zum EEPROM übertragen und in den Speicher geschrieben. Nach jedem Sendevorgang muss die Speicheradresse um 32 erhöht und der Pufferzeiger in den SRAM-Puffer neu gesetzt werden. Das Löschen des gesamten EEPROMs dauert mit dieser Methode eben mal 2 s!

Schlussfrage: "Warum soll man Speicher überhaupt löschen, wenn die Zellen vor dem Beschreiben eh zuerst gelöscht werden?"

Antwort: Das ist eine Frage der Philosophie. Wenn ich beim Auslesen eines Speicherbereichs erkennen möchte, ob z. B. meine Messdaten richtig erfasst wurden, dann kann es eine Hilfe sein, an unbeschriebenen Stellen eine 0xFF auszulesen, was als Messwert selten vorkommt. Ich weiß dann, dass es evtl. Probleme mit der Speicherung gegeben hat. Ein Durcheinander von Werten aus früheren Messreihen lässt mich das nicht erkennen. Deshalb lösche ich den Arbeitsspeicher selbst bevor ich mit einer neuen Messreihe beginne.

 

Random Read

Auch die letzte Routine im Umfang dieses Projekts bringt noch einmal etwas neues - die Repeated START-condition. Weshalb dafür eine weiter UPR geschrieben wurde liegt daran, dass auf eine andere Statusmeldung reagiert werden muss wie bei einer normalen START-condition.

Notwendig wird die wiederholte Startmeldung, weil vom Schreibbetrieb der Adressen auf Lesebetrieb umgestellt werden muss. Sonst verhält sich alles wie inzwischen gewohnt.

Software und Platinenvorlagen

Für eigene Versuche gibt es hier noch die Platinenvorlagen zum Ausdrucken als PDF und das gesamte Assemblerpaket mit allen Quellfiles, die man zum Übersetzen braucht. Die Baudrate für das RS232-Interface ist auf 38,4 kBaud (8n1) voreingestellt. Als Testoberfläche dient das Hauptprogramm, das die Kommandos vom Terminal auf dem PC empfängt, parst und in die Testaufrufe umsetzt. Anworten des MEGA 8 landen wieder in der Terminalanzeige.


 

To-Do's

Interrupt-Einbindung vom PCF8574

Serielles Lesen vom 24C64

Berücksichtigung von Fehlersituationen

AVR als Slave

 


 

Start AVR-KursAn den Seitenbeginnnicht verfügbar