Ich möchte an dieser Stelle kurz eine I²C Routine vorstellen, die sich in meinen AVR Projekten 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);