Der I2C-Bus, der von Atmel als TWI (Two-Wire-Interface) bezeichnet wird, ist einer der einfachsten synchronen Übertragungsmöglichkeiten, die uns ein Mikrocontroller bieten kann. Die meisten AVRs haben das TWI hardwareseitig verbaut. Das bietet die Möglichkeit auch während des Empfangs von Daten andere Aktionen ausführen zu können. Da aber die Implementierung einer TWI-Schnittstelle in Software eine gute Übung ist, erläutert dieses Buch ebenfalls diese Möglichkeit. Das Protokoll und der Aufbau vo I2C-Bus kann im Datenblatt[1] von NXP Semiconductors nachgelesen werden.

Grundlagen Bearbeiten

Der Name I2C, abgeleitet von Inter-Integrated-Circuit, lässt uns schon vermuten, dass der Bus zwischen integrierten Schaltkreisen aufgebaut wird. Zu finden ist er meißt zwischen Mikrocontrollern und einigen Peripheriebausteinen wie EEPROMs oder Portexpandern. Oft wird auch die vereinfachte Schreibweise I2C verwendet.

Busaufbau Bearbeiten

Der ganze Bus wird auf zwei Leitungen aufgebaut. Wir benötigen eine Taktleitung (SCL) und eine Datenleitung (SDA). Um eine Kommunikation nach dem Standard ermöglichen zu können, müssen die einzelnen Leitungen des I2C-Busses mit PullUp-Widerständen auf den Highpegel gezogen werden. Üblicherweise werden 120Ω Widerstände verwendet.

 

Beim I2C-Bus gibt es immer nur einen Master, der die Kommunikation startet und auch über die Taktleitung steuert. Trotzdem kannst du über entsprechende Protokolle auch einen multimasterfähigen Bus aufspannen.

Adressierung Bearbeiten

Die einzelnen Empfänger lassen sich über 8 Bit adressieren. Zu jedem Empänger gehören laut Standard eine Lese- und Schreibadresse. Das letzte Bit der Adresse gibt also an, welche Adresse des Empfängers gemeint ist. Zur unterscheidung von einzelnen Empfängern stehen dir bzw. den Herstellern also genau 7 Bit zur Verfügung. Aus diesem Sachverhalt ergeben sich also erst einmal 128 mögliche Adressen. Von diesen 128 Adressen sind wiederum 16 Adressen für Sonderzwecke reserviert und sollten nicht verwendet werden.

Jeder einzelne Empfänger bekommt vom Hersteller werksseitig eine feste Adresse vergeben, von der die unteren 3 Bits von dir selbst vergeben werden können. Oftmals haben die Chips drei Eingänge, die entweder gegen Masse oder 5V geschaltet werden, um die endgültige Adresse einzustellen.

Adressbeispiel
Hersteller Benutzer R/W
0 1 1 0 1 0 0 0

Das R/W-Bit gibt an, um welche Adresse es sich handelt. Ist das R/W-Bit nicht gesetzt, also 0, handelt es sich bei der Adresse um eine Leseadresse. Ist das Bit gesetzt kannst du Daten in den Empfänger schreiben.

Signalverlauf Bearbeiten

Wichtig beim I2C-Bus ist, dass der Master, in den meisten Fällen dein Mikrocontroller, den Takt auf dem Bus generiert. Der Takt sollte aber so zeitlich abgestimmt sein, dass auch die langsamsten Empänger die Chance haben an der Kommunikation teilzuhaben.

Besonderheiten im Protokoll wie Repeated-Start können im Standard nach gelesen werdenund sollen hier nicht Thema sein.

Startbedingung Bearbeiten

Eine beginnende Kommunikation musst du den anderen Teilnehmern auf dem Bus auch mitteilen. Zum Starten, nutzt du die Startbedingung. Die Startbedingung erzeugst du, indem du, während an der Taktleitung Highpegel anliegt, die Datenleitung von HIGH nach LOW ziehst. Die Empfänger erkennen dann eine neue Kommunikation und können entsprechend darauf reagieren.

 

Stoppbedingung Bearbeiten

Eine Kommuniaktion stoppst du, indem du eine Stoppbedingung erzeugst. Dabei ziehst du die Datenleitung von LOW nach HIGH, während die Taktleitung HIGH ist. Dadurch wird der Bus wieder freigegeben und der nächste Master kann eine Verbindung aufbauen.

 

Bit übertragen Bearbeiten

Die Empfänger lesen ein Bit von der Datenleitung, wenn die Taktleitung HIGH ist. Das bedeutet für dich, dass du die Datenleitung nur setzen kannst, wenn die Taktleitung LOW ist. Diese Voraussetzung ist sehr wichtig, da das Ändern der Datenleitung sonst als Start- bzw. als Stoppbedingung interpretiert wird. Hast du die Datenleitung erfolgreich gesetzt erzeugst du auf der Taktleitung eine steigende und eine fallende Flanke. Damit ist das Bit erfolgreich übertragen.

 

Acknowledge Bearbeiten

Sobald 8 Bit übertragen wurden, erzeugt der Empfänger ein Acknowledge. Dieses Acknowledge zeigt dir, dass der Empfänger auch alle 8 Bit richtig empfangen hat. Um ein Acknowledge zu bekommen, musst du pro Byte, das du überträgst, einen 9. Takt einfügen. Während dieses Taktes kannst du dann auf das Acknowledge prüfen.

 

Bei diesem Vorgang wird die Datenleitung vom Empfänger gesetzt. Durch die PullUp-Widerstände aus dem Busaufbau ergibt sich bei einer erfolgreichen Übertragung (ACK) ein LOW-Pegel auf der Datenleitung und bei einer fehlerhaften Übertragung (NACK) ein HIGH-Pegel. Die Datenleitung wird nämlich nur von dem Empfänger auf LOW gezogen, der auch erfolgreich empfangen hat. Hat kein Empfänger richtig empfangen passiert auch nichts.

//==Softwarelösung== // funktioniert super mit ATtiny Famillie

  1. include <avr/io.h>
  1. include <util/delay.h>
  1. define SCLPORT PORTA //TAKE PORTD as SCL OUTPUT WRITE
  1. define SCLDDR DDRA //TAKE DDRB as SCL INPUT/OUTPUT configure
  1. define SDAPORT PORTA //TAKE PORTD as SDA OUTPUT WRITE
  1. define SDADDR DDRA //TAKE PORTD as SDA INPUT configure
  1. define SDAPIN PINA //TAKE PORTD TO READ DATA
  1. define SCLPIN PINA //TAKE PORTD TO READ DATA
  1. define SCL PA4 //PORTD.0 PIN AS SCL PIN
  1. define SDA PA6 //PORTD.1 PIN AS SDA PIN
  1. define SOFT_I2C_SDA_LOW SDADDR|=((1<<SDA))
  1. define SOFT_I2C_SDA_HIGH SDADDR&=(~(1<<SDA))
  1. define SOFT_I2C_SCL_LOW SCLDDR|=((1<<SCL))
  1. define SOFT_I2C_SCL_HIGH SCLDDR&=(~(1<<SCL))
  1. define Q_DEL _delay_loop_2(3)
  1. define H_DEL _delay_loop_2(5)

void i2c_init()

{

SDAPORT&=(1<<SDA);

SCLPORT&=(1<<SCL);


SOFT_I2C_SDA_HIGH;

SOFT_I2C_SCL_HIGH;


}

void i2c_start()

{

SOFT_I2C_SCL_HIGH;

H_DEL;


SOFT_I2C_SDA_LOW;

H_DEL;

}

void i2c_stop()

{

SOFT_I2C_SDA_LOW;

H_DEL;

SOFT_I2C_SCL_HIGH;

Q_DEL;

SOFT_I2C_SDA_HIGH;

H_DEL;

}

uint8_t i2c_write(uint8_t data)

{

uint8_t i;


for(i=0;i<8;i++)

{

SOFT_I2C_SCL_LOW;

Q_DEL;


if(data & 0x80)

SOFT_I2C_SDA_HIGH;

else

SOFT_I2C_SDA_LOW;


H_DEL;


SOFT_I2C_SCL_HIGH;

H_DEL;


while((SCLPIN & (1<<SCL)) == 0 ); // Hier kann er sich aufhängen, wenn Slave tot !!!


data=data<<1;

}


//The 9th clock (ACK Phase)

SOFT_I2C_SCL_LOW;

Q_DEL;


SOFT_I2C_SDA_HIGH;

H_DEL;


SOFT_I2C_SCL_HIGH;

H_DEL;


// ----- ACK des Slaves einlesen

uint8_t ack =! (SDAPIN & (1<<SDA));

// --- Bus loslassen

SOFT_I2C_SCL_LOW;

H_DEL;


return ack;

}


// Lesefunktion

// 0 = Letztes Byte, sende NAK

// 1 = Es kommen noch welche, sende ACK

uint8_t i2c_read(uint8_t ack)

{

uint8_t data=0x00;

uint8_t i;


for(i=0;i<8;i++)

{

// SCL clocken

SOFT_I2C_SCL_LOW;

H_DEL;

SOFT_I2C_SCL_HIGH;

H_DEL;

while((SCLPIN & (1<<SCL)) == 0);

if(SDAPIN & (1<<SDA))

data |= (0x80>>i);

}

SOFT_I2C_SCL_LOW;

Q_DEL; //Soft_I2C_Put_Ack

if(ack) { // ACK

SOFT_I2C_SDA_LOW;

}

else {

SOFT_I2C_SDA_HIGH; // NACK = Ende

}

H_DEL;

SOFT_I2C_SCL_HIGH;

H_DEL;

SOFT_I2C_SCL_LOW;

H_DEL;

SOFT_I2C_SDA_HIGH; // was missing!!

return data;

}

Hardwarelösung Bearbeiten

  1. http://www.nxp.com/documents/user_manual/UM10204.pdf