mikrocontroller.net

Forum: Projekte & Code universelle I2C Master Prozedur für AVR


Autor: Stefan F. (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die folgende Prozedur führt eine vollständige I2C Kommunikation zu einem 
I2C Slave durch.
// 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 einen Slave mit Registern:
uint8_t[3] buffer;
#define SLAVE 0x70;

// Schreibe 0 in Register 5.
buffer[0]=5;
buffer[1]=0;
i2c_communication(SLAVE,buffer,2,0,0);

// Schreibe 0 in Register 5 und 1 in Register 6
buffer[0]=5;
buffer[1]=0;
buffer[2]=1;
i2c_communication(SLAVE,buffer,3,0,0);

// Lese Register 7 aus
buffer[0]=7;
i2c_communicate(SLAVE,buffer,1,buffer,1);
uint8_t value=register[0];

// Lese Register 8 und 9 als 16bit Wert aus
buffer[0]=8;
i2c_communicate(SLAVE,buffer,1,buffer,2);
uint16_t value=(uint16_t) (register[1]<<8) | register[0];

// Schreibe 2 Bytes in die Register 10 und 11, lese dann Register 12.
buffer[0]=10;
buffer[1]=0;
buffer[2]=1;
i2c_communicate(SLAVE,buffer,3,buffer,1);
uint8_t value=register[0];

Anstelle eines Byte-Arrays kann man auch komplexere Strukturen als 
Puffer verwenden. Das brächte z.B. den Vorteil mit sich, dass man zwei 
8bit Werte nicht nachträglich zu einem 16bit Wert kombinieren muss.

Die Adresse des Slave wird als 7bit Wert angegeben, also ohne 
Platzhalter für das R/W Bit.

Autor: Steffen H. (avrsteffen)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

Warum so komliziert wenn du im polling deinen TWI bedienst?

Sorry, aber schöner wäre die Prozedur doch wenn sie die Kommunikation im 
Interruppt erledigt. Dann wären auch die vielen an die Prozedur zu 
übergebenden Daten gerechtfertigt.

Autor: Stefan Frings (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich finde die Prozedur nicht kompliziert. Zumindest bestand die Absicht 
darin, sie einfach zu halten, damit der Code auch gut nachvollziehbar 
ist. Es sollte sozusagen ein Beispiel für die ersten Versuche mit I2C 
sein.

Was könnte ich denn einfacher machen?

Ich hatte bisher noch nie Bedarf, die I2C Kommunikation asynchron per 
Interrupt-Routine zu machen. Wenn Du Bedarf hast, taugt meine Prozedur 
für Deinen Anwendungsfall natürlich nicht.

Autor: Stefan F. (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich möchte noch drauf hinweisen, dass in der obigen Prozedur nur wenig 
Fehlerbehandling drin ist. Wenn man z.B. die Pull-Up Widerstände 
vergisst, wird sich das Programm aufhängen, weil es endlos wartet. Und 
wenn eine Kommunikationsstörung auftritt, wird dies nicht ans aufrufende 
Programm zurück gemeldet.

Wer meint, Hardwarefehler erkennen und behandeln zu müssen, sollte dazu 
noch ein wenig zusätzlichen Code einfügen. Ich denke, dass man 
Hardwaredefekte in den meisten Fällen nicht per Software behandeln muss.

Autor: Stefan F. (sfrings)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Übertragungsrate beim Senden hängt von der Konfiguration der 
Schnittstelle ab. Wenn man den Prescaler auf default Wert lässt, ist die

Bitrate = FCPU/(16+2*TWBR)

TWBR=75 ergibt 100kHz bei 16Mhz Quartz

Autor: Achim S. (achims)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan
habe zu deinem Code ein Problem. So wie du ihn hier reinstellst, ok.
Ich habe vom Hersteller eines Bots einen Code mit I2C drin. Jetzt weiss 
ich aber nicht, ob der Erweitert werden kann auf andere Adressen und 
Anschlüsse für weitere I2C IC. Wie kann ich so was rauskriegen?
achim

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.