I²C Routine
Ich möchte an dieser Stelle kurz eine I²C Routine vorstellen, die sich in meinen AVR Projekten (ohne Arduino) mehrfach bewährt hat. Du darfst sie gerne kopieren.
// Sendet beliebig viele Bytes an den adressierten Slave und empfängt
// anschließend beliebig viele Bytes von dem Slave.
// slave_address ist die Adresse des Slave in 7bit Schreibweise (0-127).
// send_data zeigt auf die zu sendenden Daten, z.B. ein Array von Bytes.
// send_bytes gibt an, wieviele Bytes gesendet werden sollen.
// rcv_data zeigt auf einen Puffer, wo die empfangenen Daten abgelegt werden
// sollen, z.B. ein Array von Bytes.
// rcv_Bytes gibt an, wieviele Bytes empfangen werden sollen.
// Der Rückgabewert zeigt an, wie viele Bytes tatsächlich empfangen wurden.
// Wenn man nur senden will, gibt man rcv_data=0 und rcv_bytes=0 an.
// Wenn man nur empfangen will, gibt man send_data=0 und send_bytes=0 an.
// Es ist erlaubt, bei send_bytes und rcv_bytes Zeiger auf den selben Puffer
// zu übergeben.
uint8_t i2c_communicate(uint8_t slave_address, void* send_data,
uint8_t send_bytes, void* rcv_data, uint8_t rcv_bytes) {
uint8_t rcv_count=0;
// Adresse ein Bit nach links verschieben,
// um Platz für das r/w Flag zu schaffen
slave_address=slave_address<<1;
if (send_bytes>0) {
// Sende Start
TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTA);
while (!(TWCR & (1<<TWINT)));
uint8_t status=TWSR & 0xf8;
if (status != 0x08 && status != 0x10) goto error;
// Sende Adresse (write mode)
TWDR=slave_address;
TWCR=(1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
if ((TWSR & 0xf8) != 0x18) goto error;
// Sende Daten
while (send_bytes>0) {
TWDR=*((uint8_t*)send_data);
TWCR=(1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
if ((TWSR & 0xf8) != 0x28) goto error;
send_data++;
send_bytes--;
}
}
if (rcv_bytes>0) {
// Sende START
TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTA);
while (!(TWCR & (1<<TWINT)));
uint8_t status=TWSR & 0xf8;
if (status != 0x08 && status != 0x10) goto error;
// Sende Adresse (read mode)
TWDR=slave_address + 1;
TWCR=(1<<TWINT) | (1<<TWEN);
while (!(TWCR & (1<<TWINT)));
if ((TWSR & 0xf8) != 0x40) goto error;
// Empfange Daten
while (rcv_bytes>0) {
if (rcv_bytes==1) {
// das letzte Byte nicht mit ACK quittieren
TWCR=(1<<TWINT) | (1<<TWEN);
}
else {
// alle anderen Bytes mit ACK quittieren
TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWEA);
}
while (!(TWCR & (1<<TWINT)));
uint8_t status=TWSR & 0xf8;
if (status!=0x50 && status != 0x58) goto error;
*((uint8_t*)rcv_data)=TWDR;
rcv_data++;
rcv_bytes--;
rcv_count++;
}
}
// Sende STOP
TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
return rcv_count;
error:
// Sende STOP
TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
return 0;
}
Anwendungsbeispiele für I²C Bausteine ohne Register, wie dem PCF8574:
// Bus-Taktfrequenz einstellen (=100kHz bei 16Mhz Quarz)
TWBR=72;
// Sende ein einzelnes Byte
uint8_t value=0xFF
i2c_communicate(0x20,&value,1,0,0);
// Sende drei Bytes aus einem Array
uint8_t buffer[1]={0xFF,0xEE,0xDD};
i2c_communicate(0x20,buffer,3,0,0);
// Empfange ein einzelnes Byte
uint8_t value;
i2c_communicate(0x20,0,0,&value,1);
// Empfange drei Bytes in das Array
uint8_t buffer[3];
i2c_communicate(0x20,0,0,buffer,3);
Anwendungsbeispiele für I²C Bausteine mit Registern:
// Bus-Taktfrequenz einstellen (=100kHz bei 8Mhz Quarz)
TWBR=32;
// Sende Register Nummer 2, dann drei Bytes die in Register 2, 3 und 4 landen
uint8_t buffer[4]={2,10,20,30};
i2c_communicate(0x70,buffer,4,0,0);
// Sende Register Nummer 3, lese dann fünf Bytes aus diesem und den folgenden Registern aus
uint8_t buffer[5];
buffer[0]=3;
uint8_t count=i2c_communicate(0x70,buffer,1,buffer,5);
// Sende Register Nummer 12, lese dann sechs Bytes (= drei Integer) aus diesem und den folgenden Registern aus
uint8_t register=12;
uint16_t buffer[3];
uint8_t count=i2c_communicate(0x70,®ister,1,buffer,6);