Atmel AVR Mikrocontroller mit OpenSuse: I2C, BeispielEEPROM-Ansteuerung

I2C (auch TWI=Two Wire Interface genannt) ist ein sehr hardware-naher De Facto-Standard, mit dem zwei oder mehrere Geräte oder auch Bausteine über einen gemeinsamen Bus miteinander kommunizieren können. Geräte können am Bus als Master oder als Slave teilnehmen. Über die Geräteadresse können mehrere Geräte unterschieden werden. Von verschiedenen Herstellern werden Chips angeboten, die das I2C Protokoll beherrschen. Die größeren AVRs bieten auch Unterstützung für dieses Protokoll.

Die Kommunikation erfolgt bei I2C über zwei Leitungen, SCL (serial clock) und SDA (serial data). Grob gesprochen wird pro Clock-Impuls ein Datenbit übertragen. Über diesem grundlegenden Protokoll sind bestimmte Abfolgen der übertragbaren Kommandos vorgeschrieben. Die Kommandos werden inhaltlich durch das jeweilige Gerät oder den jeweiligen Chip bestimmt. Die grundlegende Spezifikation von I2C ist unter http://www.nxp.com/acrobat/literature/9398/39340011.pdf zu finden.

Im folgenden sind die einfachen Kommandos des Busses kurz dargestellt: START, STOP, Bit-Übertragung, Acknowledgement. Komplexere Dinge bitte in der Spezifikation studieren.


START Condition: Beginn einer Übertragung
Während SCL high gehalten wird, wechselt SDA von high auf low

STOP Condition: Ende einer Übertragung. Während SCL high gehalten wird, wechselt SDA von low auf high

Übertragung eines einzelnen Bits.
Während einer positiven Taktflanke muss SDA stabil gehalten werden auf high oder auf low. Dies ist der übertragene Bit Wert

Acknowledge
Der Slave signalisiert dem Master dass er alle 8 Bits eines Bytes erhalten hat. Dazu gibt der Master die SDA Leitung frei (die unbelastet über den Pullup Widerstand auf high gehalten wird). Der Slave zieht seinerseits die Leitung auf low während der ganzen folgenden (neunten) positiven Taktflanke. Dies interpretiert der Master als Acnowledgement.

Praxis

Beispielhafte Chips sind die seriellen EEPROMs der Reihe 24Cxxx. Ich habe ein EEPROM 24C512 zum Experimentieren verwendet. Der Wert „xxx“ steht dabei für die Speichergröße in KBits (02…1024). Das 24C512 mit 512 KBit=64KByte kann mittels I2C gelesen und beschrieben werden. Das EEPROM kann benutzt werden, wenn die Größe des On-Chip EEPROMs des AVR nicht mehr ausreicht.

Das Protokoll kann in Software implementiert werden oder, wenn vorhanden,  kann die I2C Hardware eines Controllers verwendet werden.

Peter Fleury hat auch hierfür eine Bibliothek geschrieben, für den Hardware-Fall, bei der der AVR als I2C Master auftritt. Diese Bibliothek kann unter  http://homepage.hispeed.ch/peterfleury/i2cmaster.zip heruntergeladen werden. Als Beispiel ist der Zugriff auf ein 24C02 im ZIP File enthalten. Die kleineren EEPROMs kommen mit einer 8-Bit Adresse aus, für die größeren wie das 24C512 benötigt man eine 16-Bit-Adresse.


Pin-Belegung des 24C512.

Das 24C512 hat eine 2-Bit Adresse, so dass 4 solcher Bausteine am I2C-Bus unterschieden werden können. Die „Bausteinklasse“ 24Cxxx hat den festen Adressteil 1010xxxx (Binär) bzw. 0xAx. Die Chip-Adresse wird festgelegt, indem z.B. A0=A1=0 an GND gelegt wird. Dann kann man mit der I2C-Adresse 0xA0 diesen Chip ansprechen. Ein anderer, bei dem A0=1 und A1=0 gelegt wurde, ist mit 0xA1 anzusprechen. A0 und A1 sind 0, wenn sie nicht anders beschaltet werden. „WP“ heißt „Write Protect“, ein Hardware-Schreibschutz, der aktiv ist, wenn WP auf Vcc gelegt wird. Wird er offen gelassen oder auf GND gelegt, kann man das EEPROM auch beschreiben.


Simple Beschaltung des 24C512

Achtung: Auf dem Pollin Experimentierboard ist der WP-Anschluss leider fest auf Vcc gelegt, d.h. man kann ein normal eingestecktes EEPROM nicht beschreiben. Abhilfe schafft Einstecken mit abgebogenem WP-Pin.


24C512 auf dem Pollin Experimentierboard 2.0.  Da bei diesem Board der WP-Pin fest auf Vcc liegt,  muss man das EEPROM mit abgebogenem PIN einstecken, um es beschreiben zu können.

Im folgenden ist der beispielhafte Schreibzugriff dargestellt (der Code von Peter Fleury wurde im wesentlichen nur um die 2-Byte Adressierung erweitert):

#include "i2cmaster.h"

#define Dev24C512  0xA0      // device address of EEPROM 24C512, see datasheet

int main(void) {
     unsigned char val, ret;
     int addr;
 
     i2c_init(); // init I2C interface
 
     ret = i2c_start(Dev24C512+I2C_WRITE); // set device address and write mode
     if ( ret ) {
         /* failed to issue start condition, possibly no device found */
         printf("failed to issue start condition, possibly no device found.n");
         i2c_stop();
     } else {
         /* write 0x75 to eeprom address 0x05 (Byte Write) */
         /* issuing start condition ok, device accessible */
         addr=5;
         val=0x75;
         printf("writing %0x to address %0xn", val, addr ); 
         ret = i2c_write( (addr/256) ); // write hi address 
         if (ret!=0) 
             printf("error in writing value, ret=%0xn", ret );
         ret = i2c_write(  (addr%256) ); // write lo address 
         if (ret!=0) 
             printf("error in writing value, ret=%0xn", ret );
         ret = i2c_write(val); // ret=0 -> Ok, ret=1 -> no ACK 
         //printf("ret=%0xn", ret);
         if (ret!=0) 
             printf("error in writing value, ret=%0xn", ret );
         i2c_stop(); // set stop conditon = release bus
     }
}

Die ganzen dargestellten Prüfungen der Return-Werte sind nicht unbedingt nötig, aber bei der Fehlersuche hilfreich. Das Lesen geschieht wie folgt:

i2c_start_wait(Dev24C512+I2C_WRITE); // set device address and write mode
printf("reading value from address %0x: ", addr ); 
ret = i2c_write( (addr/256) ); // write hi address 
ret = i2c_write(  (addr%256) ); // write lo address 
i2c_rep_start(Dev24C512+I2C_READ); // set device address and read mode
ret = i2c_readNak(); // read one byte
printf("read value=%xn", ret );
i2c_stop();

Mit einem Logik Analysator wurde die Übertragung von Signalen betrachtet. Diese sind im folgenden dargestellt.

Ein AVR überträgt mit Hardware I2C Daten an das EEPROM.

Der Logikanalysator wurde auf asynchron, 200ns clock period gesetzt. Eine positive Taktflanke ist 8 Takte lang, also 1600ns=1,6µs. Die Übertragung eines ganzen Bytes dauert 21µs, so dass man auf eine Übertragungsrate von etwa 50KByte/s kommt. Der AVR kann noch deutlich schneller.


Das erste übertragene Byte, 1010.0000 = 0xa0

Das Byte hier dargestellt mit zusätzlichen Infos

ein weiteres übertragenes Byte, 0000.0101 = 0x05

Das Byte hier dargestellt mit zusätzlichen Infos

Atmel AVR Mikrocontroller mit OpenSuse: USB Nutzung

Serielle Schnittstellen sind an heutigen PCs immer seltener vorhanden. Der logische Ersatz des alten RS232 Standards ist die USB-Schnittstelle.

Neuere AVR Controller unterstützen den USB-Standard direkt, indem auf dem Chip USB-Controller-Funktionen integriert wurden. Die „älteren“ AVR-Modelle besitzen selbst keine USB-Unterstützung. Dies ist jedoch kein Hindernis, da externe USB-Controller am Markt verfügbar sind.

Bei der Nutzung von RS232 (ohne USB) wird die UART-Funktionalität des AVR Controllers genutzt (UART=Universal Asynchronous Receiver and Transmitter). Diese erzeugt aus eingehenden Bytes einen Bit-Strom. Der Bit-Strom besteht aus den Nutz-Bits (also den 8 Bits des Datenbytes) sowie Steuerbits, die die UART selbsttätig in den Datenstrom einfügt und die das RS232 Leitungsprotokoll umsetzen. Wesentlich ist dabei das immer eingefügte Start-Bit, welches den Start eine Bytes markiert und optionale weitere Start-Bits und optionale Stop-Bits sowie ein Paritätsprüfungsbit. Die Anzahl der Start/Stop-Bits, Parität und vor allem die Baudrate wird über Kontrollregister der UART geregelt. Da der AVR Controller üblicherweise mit +5V betrieben wird, die RS232 Physik aber mit höheren Spannungen (+-12V) funktioniert, wird nur noch ein externer Chip zur Umsetzung der Spannungspegel benötigt. Dies ist typischerweise ein Baustein der Art MAX232.


Der MAX232 für die „alte“ RS232 Schnittstelle, hier in SMD-Bauform, es sind aber auch DIL-Gehäuse verfügbar

Bei der Nutzung von USB mit dem AVR gibt es unterschiedliche Konzepte:

  • V-USB, einer reinen Software-Implementierung der USB-Anbindung. Dabei werden einfach Port-Pins eines AVRs als USB-Leitungen verwendet und die gesamte USB-Logik im AVR implementiert. Diese Lösung ist simpel, aber leider nicht allzu schnell.
  • Nutzung eines externen USB-Controller-Chips. Dieser Ansatz ist im folgenden ausführlich beschrieben.

AVR mit externem USB-Controller

Am Markt verfügbar sind diverse USB Controller Chips. Ein beliebter Kandidat ist der FT232. FTDI hat diesen Chip sicher bewusst ähnlich benannt wie den „alten“ MAX232. Ganz grob formuliert: Während der MAX232 die Signale eines Mikrocontrollers auf RS232-Physik umsetzt, kann man beim FT232 sagen, dass dieser die Signale des Controllers auf die USB-Physik umsetzt.

Damit ist auch schon in etwa beschrieben, wie der AVR mit den F232 verbunden wird. Wie schon beim reinen RS232 wird die UART genutzt, um aus einem Datenbyte einen Bitstrom zu erzeugen. Dieser Bitstrom wird in den FT232 eingespeist. Den „Rest“, also die Umsetzung in USB-Pakete, erledigt der FT232. Die umgekehrte Richtung funktioniert genauso, der FT232 extrahiert aus dem USB-Datenstrom die für ihn bestimmten Bits und legt diese an den Eingang der AVR UART. Diese macht daraus in gewohnter Weise Datenbytes.
Man könnte also auch sagen, dass der serielle Datenstrom im USB-Datenstrom getunnelt wird.


Der FT232RL für USB, nur in SMD Gehäusen verfügbar

PC-seitige Voraussetzung

Wenn AVR und USB-Controller an ihre Betriebsspannung angelegt werden und der USB-Controller per USB-Kabel mit einem PC verbunden wird, meldet sich der USB-Controller mit einer Art eindeutiger Kennung beim USB-System des PCs an (VendorId + ProductId). Beim FT232 ist dies z.B. idVendor=0403 und idProduct=6001. Anhand dieser Parameter kann das Subsystem den dazu passenden USB-Treiber laden und verwenden. Beim FT232 und unter OpenSuse gilt:

  • Der Treiber von FTDI ist bereits im Standardkernel verfügbar und wird automatisch geladen, wenn der FT232 sich am USB-System anmeldet
  • Die Geräteklasse des FT232 ist eine serielle Schnittstelle. Linux bindet das Gerät daher über eine /dev/ttyUSBxx Gerätedatei ins System ein.

Unter Windows muss vermutlich der Treiber von FTDI nachinstalliert werden.
Web-Adresse zu den Treibern: http://www.ftdichip.com/FTDrivers.htm

(unter Windows noch zu machen)

Die dmesg-Ausgabe unter Linux bringt folgendes:

[ 4251.658015] usb 6-2: new full speed USB device using uhci_hcd and address 2
[ 4251.847030] usb 6-2: New USB device found, idVendor=0403, idProduct=6001 
[ 4251.847034] usb 6-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 4251.847037] usb 6-2: Product: FT232R USB UART
[ 4251.847040] usb 6-2: Manufacturer: FTDI
[ 4251.847042] usb 6-2: SerialNumber: A9009NnL
[ 4251.886707] usbcore: registered new interface driver usbserial
[ 4251.886722] USB Serial support registered for generic
[ 4251.886758] usbcore: registered new interface driver usbserial_generic
[ 4251.886761] usbserial: USB Serial Driver core
[ 4251.894285] USB Serial support registered for FTDI USB Serial Device
[ 4251.894362] ftdi_sio 6-2:1.0: FTDI USB Serial Device converter detected
[ 4251.894424] usb 6-2: Detected FT232RL 
[ 4251.894428] usb 6-2: Number of endpoints 2
[ 4251.894431] usb 6-2: Endpoint 1 MaxPacketSize 64
[ 4251.894433] usb 6-2: Endpoint 2 MaxPacketSize 64
[ 4251.894435] usb 6-2: Setting MaxPacketSize 64
[ 4251.896108] usb 6-2: FTDI USB Serial Device converter now attached to ttyUSB0 
[ 4251.896125] usbcore: registered new interface driver ftdi_sio
[ 4251.896127] ftdi_sio: v1.5.0:USB FTDI Serial Converters Driver

Aus der dmesg-Ausgabe erkennt man auch die maximale Paketgröße von 64 Bytes.

Wenn man nun AVR-seitig ein kleines Programm schreibt, dass über eine UART Daten ausgibt und man das bisher durch einen an die UART angeschlossenen MAAX232 als RS232-Datenstrom über die serielle Schnittstelle eingelesen hatte, kann man nun dieselben Daten via USB einlesen, wenn man einfach die UART mit den FT232 verbindet. Da der FT232 z.B. als /dev/ttyUSB0 eingebunden wurde, kann man PC-seitig einfach ein Terminalprogramm starten und statt /dev/tty… nun /dev/ttyUSB… als zu verwendende Gerätedatei angeben. Im Terminal werden die Daten dann wie gewohnt dargestellt. Ich verwende für solche Sachen das Programm kermit, und dort legt man die zu verwendende Schnittstelle fest mit:
    set line /dev/ttyUSB0

Hier der Start von kermit und die Ausgaben des AVRs:

dennis@socraggio:~> kermit 
 C-Kermit 8.0.211, 10 Apr 2004, for Linux 
  Copyright (C) 1985, 2004, 
   Trustees of Columbia University in the City of New York. 
 Type ? or HELP for help. 
 (/home/dennis/) C-Kermit>set line /dev/ttyUSB1 
 (/home/dennis/) C-Kermit>c 
 Connecting to /dev/ttyUSB1, speed 19200 
  Escape character: Ctrl- (ASCII 28, FS): enabled 
 Type the escape character followed by C to get back, 
 or followed by ? to see other options. 
 ---------------------------------------------------- 
 21178 (mo=0;fa=0;ma=0;be=0),steps (r/d): 0000/0000, (mark=X,A=-60,B=60,act=0), last cmd: (nop)

Die letzte Zeile, beginnend mit 21178… ist die vom AVR erzeugte Ausgabezeile.

Daten via USB lesen und schreiben

Als nächstes soll betrachtet werden, wie man von einem eigenen Programm aus via USB kommuniziert.

Linux

Da der FT232 linux-seitig wie ein normaler serieller Port aussieht, kann man auch die normalen Kommandos zu Nutzung des Ports verwenden. Alles was man zur Programmierung eines seriellen Ports wissen muß findet man in der „Serial Programming HowTo„.
Die zu nutzenden Funktionen sind:

  • open(), read(), write(), close() für den Datentransfer
  • Für die Konfiguration des Ports die Funktionen tcgetattr(),  tcsetattr() und tcflush()

Im erwähnten HowTo ist auch Beispielcode zu finden.

Nutzbare Geschwindigkeit

Da der FT232 genauso angesprochen wird wie ein altes Terminal, sind auch die Definitionen für die Baud-Raten gültig. Ich habe einige Tests gemacht, hier die Ergebnisse. Die Daten wurden immer in 64 Byte großen Blöcken aus dem Hauptprogramm im AVR an die UART übergeben.

19200 und 38400 Baud (oder noch weniger) sind kein Problem. Da ich auch eine Zeitmessung durchführe und die tatsächliche Transferrate (Nettorate) berechne, stelle ich fest, dass diese ziemlich genau 80% der Baudrate beträgt (durch Start- und Stopbit verursacht, die allgemein bekannten Baudraten-Werte sind also Brutto-Zahlen: 8 Nutzbits+2 Protokollbits ergeben  den Faktor von 8/10).

Bei 57600 Baud gibt es unreproduzierbare Lesefehler.
Nachlesen bringt die Erkenntnis, dass die Baudraten unterschiedlich gut unterstützt werden, je nach Quarzfrequenz des Controllers. Durch Teilung und Rundungen entsteht eine leichte Abweichung in der tatsächlich gefahrenen Baudrate, die dann im PC eventuell nicht sauber erkannt wird. Ein Rechner, der zu einer Taktfrequenz die unterschiedlichen Rundungsfehler zu den Baudraten berechnet findet sich hier: http://www.gjlay.de/helferlein/avr-uart-rechner.html

Ich probiere auch 115.200 und 230.400 Baud, die bei 16Mhz eine ziemlich große Abweichung von den Sollwerten haben. Und tatsächlich erkennt mein PC nichts im Datenstrom.

500.000 Baud haben bei 16Mhz einen Fehler von 0. Ich stelle es mutig ein und tatsächlich, diese Rate wird wieder sauber erkannt. Auch 250.000 Baud hätten einen Fehler von 0, aber das kann mein PC nicht. Unter Linux/GNU-gcc findet man die möglichen Baud-Raten übrigens unter /usr/include/bits/termios.h. Diese Datei wird via „#include <termios.h>“ indirekt eingebunden. Die Baudraten sind Defines der Art „B57600“ und gehen bis 4Mega-Baud.

Schließlich probiere ich noch 1, 2 und 4 MBaud. Mehr als 4MBaud kann der PC nicht und auch beim FT232 ist laut Foren irgendwo bei 3MBaud Schluss.
1 MBaud funktioniert ebenfalls sehr gut. Dabei kann ich Netto-Datenraten von 55KByte/s (etwa 445KBit/s) messen. Das ist ein sehr hoher Wert, den ich eigentlich nicht erwartet hätte.
2 und 4 MBaud bringen keine brauchbaren Ergebnisse, im Dump des Datenstrom kann ich keine korrekt übertragenen Daten erkennen.

Vertiefende Infos, Links