Startseite

I²C Routine

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,&register,1,buffer,6);