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


von Stefan F. (sfrings)


Lesenswert?

Die folgende Prozedur führt eine vollständige I2C Kommunikation zu einem 
I2C Slave durch.
1
// Sendet beliebig viele Bytes an den adressierten Slave und empfängt
2
// anschließend beliebig viele Bytes von dem Slave. 
3
4
// slave_address ist die Adresse des Slave in 7bit Schreibweise (0-127).
5
// send_data zeigt auf die zu sendenden Daten, z.B. ein Array von Bytes. 
6
// send_bytes gibt an, wieviele Bytes gesendet werden sollen.
7
// rcv_data zeigt auf einen Puffer, wo die empfangenen Daten abgelegt werden
8
// sollen, z.B. ein Array von Bytes. 
9
// rcv_Bytes gibt an, wieviele Bytes empfangen werden sollen.
10
// Der Rückgabewert zeigt an, wie viele Bytes tatsächlich empfangen wurden.
11
12
// Wenn man nur senden will, gibt man rcv_data=0 und rcv_bytes=0 an.
13
// Wenn man nur empfangen will, gibt man send_data=0 und send_bytes=0 an.
14
// Es ist erlaubt, bei send_bytes und rcv_bytes Zeiger auf den selben Puffer zu übergeben.
15
16
uint8_t i2c_communicate(uint8_t slave_address, void* send_data, uint8_t send_bytes, void* rcv_data, uint8_t rcv_bytes) {
17
    uint8_t rcv_count=0;
18
            
19
    // Adresse ein Bit nach links verschieben, um Platz für das r/w Flag zu schaffen
20
    slave_address=slave_address<<1;
21
           
22
    if (send_bytes>0) {
23
        // Sende Start 
24
        TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTA);
25
        while (!(TWCR & (1<<TWINT)));
26
        uint8_t status=TWSR & 0xf8;
27
        if (status != 0x08 && status != 0x10) goto error;
28
    
29
        // Sende Adresse (write mode)
30
        TWDR=slave_address;
31
        TWCR=(1<<TWINT) | (1<<TWEN);
32
        while (!(TWCR & (1<<TWINT)));
33
        if ((TWSR & 0xf8) != 0x18) goto error;
34
        
35
        // Sende Daten
36
        while (send_bytes>0) {
37
            TWDR=*((uint8_t*)send_data);
38
            TWCR=(1<<TWINT) | (1<<TWEN);
39
            while (!(TWCR & (1<<TWINT)));
40
            if ((TWSR & 0xf8) != 0x28) goto error;
41
            send_data++;
42
            send_bytes--;
43
        }
44
    }
45
    
46
    if (rcv_bytes>0) { 
47
        // Sende START
48
        TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTA);
49
        while (!(TWCR & (1<<TWINT)));
50
        uint8_t status=TWSR & 0xf8;
51
        if (status != 0x08 && status != 0x10) goto error;
52
        
53
        // Sende Adresse (read mode)
54
        TWDR=slave_address + 1;
55
        TWCR=(1<<TWINT) | (1<<TWEN);
56
        while (!(TWCR & (1<<TWINT)));
57
        if ((TWSR & 0xf8) != 0x40) goto error;
58
    
59
        // Empfange Daten
60
        while (rcv_bytes>0) {    
61
            if (rcv_bytes==1) {
62
                // das letzte Byte nicht mit ACK quittieren
63
                TWCR=(1<<TWINT) | (1<<TWEN); 
64
            } 
65
            else {   
66
                // alle anderen Bytes mit ACK quittieren
67
                TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWEA);
68
            }
69
            while (!(TWCR & (1<<TWINT)));
70
            uint8_t status=TWSR & 0xf8;
71
            if (status!=0x50 && status != 0x58) goto error;
72
            *((uint8_t*)rcv_data)=TWDR;
73
            rcv_data++;
74
            rcv_bytes--;
75
            rcv_count++;
76
        }
77
78
    }
79
    
80
    // Sende STOP
81
    TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
82
    return rcv_count;
83
84
    error:
85
    // Sende STOP
86
    TWCR=(1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
87
    return 0;
88
}

Anwendungsbeispiele für einen Slave mit Registern:
1
uint8_t[3] buffer;
2
#define SLAVE 0x70;
3
4
// Schreibe 0 in Register 5.
5
buffer[0]=5;
6
buffer[1]=0;
7
i2c_communication(SLAVE,buffer,2,0,0);
8
9
// Schreibe 0 in Register 5 und 1 in Register 6
10
buffer[0]=5;
11
buffer[1]=0;
12
buffer[2]=1;
13
i2c_communication(SLAVE,buffer,3,0,0);
14
15
// Lese Register 7 aus
16
buffer[0]=7;
17
i2c_communicate(SLAVE,buffer,1,buffer,1);
18
uint8_t value=register[0];
19
20
// Lese Register 8 und 9 als 16bit Wert aus
21
buffer[0]=8;
22
i2c_communicate(SLAVE,buffer,1,buffer,2);
23
uint16_t value=(uint16_t) (register[1]<<8) | register[0];
24
25
// Schreibe 2 Bytes in die Register 10 und 11, lese dann Register 12.
26
buffer[0]=10;
27
buffer[1]=0;
28
buffer[2]=1;
29
i2c_communicate(SLAVE,buffer,3,buffer,1);
30
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.

von Steffen H. (avrsteffen)


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.

von Stefan Frings (Gast)


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.

von Stefan F. (sfrings)


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.

von Stefan F. (sfrings)


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

von Achim S. (achims)


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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.