Einfaches I2C Beispiel

Erstellt am 11.03.2002 22:50:56Klicks [8571]

Verwandte Themen und Zusätze: Download zu zu Einfaches I2C Beispiel |
Aufbauend auf die Schaltung aus unserem ersten Projekt können wir nun wie versprochen ein wenig mehr basteln. Ziel soll es sein ein I2C Bus aufzubauen, um damit ein kleines Display und ein Chipkarte anzusteuern.

Das Display habe ich bei Conrad Electronik gekauft. Dort findet man es unter der Artikelnummer 187012-62. Natürlich geht auch jeder andere I2C Slave, aber so sieht man wenigstens gleich was man macht. Den Chipkarten Adapter kann man ebenfalls bei Conrad beziehen. Unter der Artikelnummer 730513-62 findet irh in im Katalog.

Nun als erstes sollten wir kurz verstehen lernen wie der I2C Bus funktioniert. Um auf dem Bus Daten übertragen zu können muß ein Protokoll eingehalten werden, dass mit einer Startbedingung anfängt und mit einer Stopbedingung endet und immer zwischen einem Master und einem Slave gesprochen wird. Nach der Startbedingung lauschen alle angeschlossenen Teilnehmer (Slaves), ob die nun folgende Adresse ihnen zugeordnet ist. Jedes Gerät am I2C Bus hat demnach eine eigene Adresse und darf den Bus nur dann ansprechen, wenn er nicht belegt ist. Nach 8 übertragenen Bit wird ein ACKnowledge Bit übertragen..


In vielen Fällen werden Speicherbereiche des Slaves zum schreiben oder lesen adressiert. Ist das der Fall folgt als nächstes die Speicheradressangabe und danach das zu schreibende Byte. Folgen nun weitere Bytes inkrementieren die meisten Slaves automatisch ihre Adressangabe und erlauben so das zügige Füllen des Speichers.

Wir verwenden für unsere Tests alte Krankenversicherten Karten oder neue, die man bei Conrad bestellen kann. All diese Karten sind I2C Speicherkarten, die einfach angesprochen werden können. Jeder I2C Speicherkarte kann über die Adresse 0xA0 erreicht werden. Schreiben und Lesen funktioniert dann so einfach wie man es weiter unten in diesem Artikel sehen kann.

Jeder, der unsere erste Schaltung aufgebaut hat, kann nun einfach mittels zwei Widerstände und ein paar Kabel den I2C Bus in die Testschaltung integrieren. Ich habe dies weiterhin auf einer Lochrasterplatine gelötet, da das so am schnellsten geht.

Vergleicht man dieses Schaltbild mit dem aus unserem ersten Projekt, sieht man keine große Veränderung. Lediglich zwei Pullupwiderstände und der Connector für die I2C Kabel ist dazugekommen. Bislang ist alles recht simpel ;-)

Wer möchte kann die Platine wie auf dem Foto zu sehen auch auf einer Experimentierplatine aufbauen. Ich lege ein GIF zum belichten in den Dowloadbereich.

Ich wünsche viel Spass beim Expermimentieren und bin wie immer für Anmerkungen und Verbesserungen Dankbar. Das komplette Projektfile gibt's hier.

Bernhard Ritter


Begleitende Layoutangaben


Begleitende Codebeispiele und Erklärungen

Um die Schaltung mit Hilfe von CV zu initialissieren reicht folgender Code aus :

#include <90s2313.h>

// I²C Bus functions
#asm
.equ __i2c_port=0x12
.equ __sda_bit=5
.equ __scl_bit=4
#endasm
#include
#include
#include
#include

#define DISPLAY_ADRESS 0x74
#define CHIP_ADRESS 0xA0

... mehr INIT Code der später erklärt wird ...

// I²C Bus initialization
i2c_init();

Nun ist der Bus initialisiert und bereit. Das ist doch klasse oder ? Für das Ansteuern des Displays habe ich ein paar Funktionen geschrieben, die den weiteren Programmablauf simplifizieren sollen.

 

Die Funktion initLCD() zeigt, wie im Bild weiter oben gesehen, den korrekten Datentransfer auf dem I2C Bus. Ein einleitendes Startkommando gefolgt von der Adresse des I2C Slaves eröffnen das Senden. Danach folgen die Nutzdaten, bis der Bus wieder mit einem Stopkommando freigegeben wird.
void initLCD() { //Init Display 
      i2c_start(); 
      i2c_write(DISPLAY_ADRESS); 
      i2c_write(0x00); 
      i2c_write(0x25); 
      i2c_write(0x06); 
      i2c_write(0x24); 
      i2c_write(0x0f); 
      i2c_write(0x84); 
      i2c_stop(); 
      delay_ms(PAUSE); 
	  }
Diese Funktion erlaubt es aus dem Programmablauf Kontrollkommandos an das Display zu schicken. Diese sind notwendig für das löschen des Display oder setzten der aktiven Zeile ... In dem Datenblatt zum Display findet ihr all die Kommandos.
// Erlaubt Kontrollkomandos an das LCD zu schicken
void ctrlLCD(unsigned char data) {
     i2c_start();
     i2c_write(DISPLAY_ADRESS);
     i2c_write(0x00);
     i2c_write(data);
     i2c_stop();
     delay_ms(PAUSE);
}
Diese Funktionen ermöglichen das Ausgeben einzelner Zeichen, oder eines ganzen Strings auf das Display. Die Funktion writeLCD schreibt die Daten RAW, wohingegen die Funktionen writeCharLCD und writeLineLCD die ASCII Zeichen korrekt umrechnen, um sie auf den Zeichensatz des Displays anzupassen
// Schreibe ein Zeichen auf das Display
// Die Position muß vorher festliegen
void writeLCD(unsigned char data) {
     // Schreibe Zeichen auf LCD
     i2c_start();
     i2c_write(DISPLAY_ADRESS);
     i2c_write(0x40);
     i2c_write(data);
     i2c_stop();
     delay_ms(PAUSE);
} 
// schreibt ein einzelnes zeichen auf das LCD Display
void writeCharLCD(unsigned char data) {
     writeLCD(data+0x80);
}

// Schreibt einen String aus dem RAM auf das LCD Display
void writeLineLCD(unsigned char *data) { int i; for(i=0;i
Um nun die Chipkarte ansprechen zu können, habe ich mir drei kleine Funktionen geschrieben, die diese Aufgabe übernehmen.. Die Kommentare zu den Funktionen sagen eigentlich schon alles. Die Funktionen, die keine Adresse als Parameter erwarten nutzen den Umstand aus, dass der Chip nach jedem Zugriff ein Autoincrement durchführt. Schreiben oder lesen wir in folge auf dem Chip, benötigen wir das Adressargument nur einmal.
// Schreibe ein Zeichen in Chipmemory
// Die Position wird in Adress angegeben
unsigned char writeChipA(unsigned char adr,unsigned char data) {
	unsigned char ret;
	// Schreibe Zeichen in Chipmemory
	i2c_start();
	i2c_write(CHIP_ADRESS); 
	i2c_write(adr); 
	ret=i2c_write(data);
	i2c_stop();
	delay_ms(PAUSE);
	return ret;
}
// Ließt ein Zeichen aus dem Chip ab angegebener Adresse
// Die Position wird in Adress angegeben
unsigned char readChipA(unsigned char adr) { unsigned char data; // Lese Zeichen aus Chip i2c_start(); i2c_write(CHIP_ADRESS); // +1 für lesen i2c_write(adr); // Memory Adresse i2c_start(); i2c_write(CHIP_ADRESS+1); // +1 für lesen data=i2c_read(0); // Nun auslesen 1=mit ACK i2c_stop(); delay_ms(PAUSE); return data; } // Ließt ein Zeichen aus dem Chip ab intern gespeicherter Adresse // Die Position muß vorher festliegen ( durch readChipA() ) unsigned char readChip() { unsigned char data; // Schreibe Zeichen auf LCD i2c_start(); i2c_write(CHIP_ADRESS+1); // +1 für lesen data=i2c_read(0); i2c_stop(); delay_ms(PAUSE); return data; }
Jetz bleibt nur noch zu erwähnen, dass eine kleine Interruptroutin eim Hintergrund die Sekunden zählt und so ein kleines Softwareuhrwerk bildet. Folgender Code realisiert dies.
// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void) { 
     static int count=0;
     // Uhrwerk 
     count++; 
     // halbe Frequenz (15625) plus Korrektur wegen vorlaufen (2) 
     if(count==15627) {
     sekunde++;
     count=0;
     } 
     if(sekunde==60) {
     minute++;
     sekunde=0;
     }
     if(minute==60) {
     stunde++;
     minute=0;
     }
     if(stunde==24) {
     stunde=0;
     } 
     // Uhrwerk ende
     TCNT0=0xFE;
}

Das Hauptprogramm versucht nun mittels der oben beschriebenen Funktionen Daten von der eingesteckten I2C karte zu lesen. Aber vorsicht ! Das Programm schreibt auch Daten zurück. Also darf man nur Karten verwenden, deren Inhalt nicht mehr benötigt wird.

Ist die Karte also eingesteckt, wenn das System bootet, versucht das Programm Daten zu lesen. Die gelesenen Daten werden als Uhrzeit interpretiert und angezeigt. Im weiteren Verlauf wird die Uhrzeit auf dem Chip gespeichert. Trennt man das System von der Stromquelle und schließt es später wieder an, wird die Uhrzeit an der letzten Uhrzeit weitergezählt.

 

Schlußendlich wird in einer Dauer while Schleife die Uhrzeit regelmäßig auf das I2C Display ausgegeben und auf den Chip gespeichert. Das ist bestimmt nicht ergonomisch, aber für Tests ganz ok.

 // Sekundenzähler initialisieren 
 sekunde=0; 
 minute=0;
 stunde=0;
 altsekunde=0; 
 // Global enable interrupts
 #asm("sei")

	PORTB=0xFF;
	initLCD();  
	ctrlLCD(CLEARLCD);
	ctrlLCD(LINE1);
	// Prüfe ob Chipkarte reagiert
	if (writeChipA(0x05,'H')==1) {
		// Chipkarte reagiert nun Daten auslesen
 		writeChipA(0x06,'a');
		writeChipA(0x07,'l');
		writeChipA(0x08,'l');
		writeChipA(0x09,'o');
		writeChipA(0x0a,' ');
		writeChipA(0x0b,' ');

		data=readChipA(0x05);
		writeCharLCD(data); 
		for(i=0;i<6;i++) {
			data=readChip();
			writeCharLCD(data);
		}
		stunde=readChipA(0x10);
		minute=readChip();
		sekunde=readChip();
	} else {
		// Keine Chipkarte eingesteckt
		writeLinefLCD("no Card");
		PORTB=0xFE;
	}
	// Auf zweite Zeile setzen für Zeitausgabe
	ctrlLCD(LINE2); 
	while (1) {     
		if(sekunde!=altsekunde) {      	
			sprintf(zeit,"%02u:%02u:%02u",stunde,minute,sekunde);
			ctrlLCD(LINE2);     
			writeLineLCD(zeit);      
			altsekunde=sekunde;
			writeChipA(0x10,stunde);
			writeChipA(0x11,minute);
			writeChipA(0x12,sekunde);
		}
	};
}