Forum: Mikrocontroller und Digitale Elektronik AVR I2C Probleme


von restfet (Gast)


Angehängte Dateien:

Lesenswert?

Hallo!

Ich bin gerade dabei, mir eine interruptgesteuerte I2C-Lib zu stricken. 
Ich sitze daran auch schon einige Wochen und komme einfach nicht weiter. 
Momentan teste ich alles auf einem ATmega2560.

Mit der Library von Peter Fleury funktionert das Ganze einwandfrei, also 
kann es schon mal nicht an der Verdrahtung liegen.

Im Anhang finden sich meine Quelldateien. Kurze Erklärung:
Mit i2c_initMaster() wird die Schnittstelle mit dem AVR als Master 
initialisiert, übergeben wird die SCL-Clock in kHz. Mit i2c_send() 
sollen Daten zum Senden bzw. ein Datenpuffer zum Reinlesen 
bereitgestellt werden.

1
/* Initialize I2C interface as master. Requires global interrupts to be enabled.
2
 * uint16_t freq:    bit rate in kHz
3
 * 
4
 */
5
void i2c_initMaster(uint16_t freqInKHz);
6
7
/* Puts an address and pointer to a data buffer into a transmit FIFO. If the
8
 * I2C interface is in idle, a transmission is initiated.
9
 * uint8_t addr:        slave address
10
 * uint8_t mode:        write (transmit) ('w', 't', 0) or read (receive) ('r', 1)
11
 * uint8_t *pData:      write mode: pointer to a data packet to be transmitted. data will be copied into a FIFO.
12
                        read mode: pointer to a data buffer which is being written to
13
 * uint8_t dataSize:    size of the specified data packet respectively buffer
14
 */
15
void i2c_send(uint8_t addr, uint8_t mode, uint8_t *pData, uint8_t dataSize);


Im Hauptprogramm soll das dann ca. so aussehen:

1
void main()
2
{
3
    uint8_t write[3] = {1, 2, 3};
4
    uint8_t read[3];
5
    
6
    /* ... */
7
    i2c_initMaster(100);
8
    sei();
9
    /* ... */
10
11
    while(1)
12
    {
13
        /* ... */
14
        // Datenbytes aus write versenden
15
        i2c_send(0x1E, 'w', write, 3);
16
        // Empfangene Datenbytes in read reinschreiben
17
        i2c_send(0x1E, 'r', read, 3);
18
        /* ... */
19
    }
20
}

Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert 
von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.

Testweise hab ich mal ein Progrmm geschrieben, das mit der Debug-ISR 
arbeitet. Nachdem die ISR abgearbeitet wurde, stehen an PORTA 0xB3 und 
an PORTC 0x20. Über den UART wurden 7 Leerzeichen (?) und ein Rufzeichen 
gesendet.

Grüße

von Sebastian (Gast)


Lesenswert?

Hab das ZIP nicht aufgemacht.

Aber dein i2c_send interface sieht nicht besonders geeignet für 
Interrupt-Betrieb aus. Das muss doch blockieren bis alles gesendet ist, 
warum dann Interrupt-Betrieb?

Oder willst du eine große Queue haben und schreibst so selten rein, dass 
sie nicht überläuft?

von restfet (Gast)


Lesenswert?

Sebastian schrieb:
> Oder willst du eine große Queue haben und schreibst so selten rein, dass
> sie nicht überläuft?

Genau. Im Interrupt werden dann nur die Daten aus diesem Puffer 
abgearbeitet.

von MWS (Gast)


Lesenswert?

restfet schrieb:
> Genau. Im Interrupt werden dann nur die Daten aus diesem Puffer
> abgearbeitet.

Und was passiert wenn der leer ist? Du startest dann genau welche neue 
TWI-Operation mit dem Löschen des TWINT am Ende der TWI ISR?

Also TWI Interrupt nach letzter Operation abschalten, TWINT nicht 
löschen und im i2c_send den Interrupt wieder anschalten und TWINT 
löschen.

von restfet (Gast)


Lesenswert?

Also hier eine genauere Erklärung nur mal vom Schreib- (Transmitter) 
Vorgang:


Mit i2c_send() werden eine Slave-Adresse, Datenbytes und deren Anzahl in 
einen Puffer kopiert. Wenn das I2C gerade arbeitet, passiert nichts 
weiter. Wenn das I2C aber grade inaktiv ist, wird eine Startbedingung 
ausgelöst. Danach sollte ja ein Interrupt auftreten.

Im Interrupt ist eine State-Machine mittels switch-case implementiert. 
Der aktuelle Status entspricht dabei (TWSR & 0xF8).

Der sollte im ersten Moment anzeigen, dass eine Startbedingung 
aufgetreten ist. Im ersten case-Zweig wird dann die Slave-Adresse und 
das R/W-Bit (in dem Fall W) in TWDR geladen. Dieser Zweig wird auch 
ausgeführt, wenn eine Repeated-Start-Bedingung auftritt. Danach wird 
noch TWINT gelöscht (auf 1 gesetzt) und die ISR verlassen.

Nachdem vom Slave ein ACK zurückkommt, tritt der nächste Interrupt auf.
In dem case-Zweig wird ein Index auf 0 gesetzt. Danach wird gleich in 
den nächsten case-Zweig gesprungen, wo überprüft wird, ob sich noch 
Daten im aktuell bearbeiteten Puffer befinden (Index < Anzahl 
Datenbytes). Wenn das der Fall ist, wird das nächste Datenbyte in TWDR 
geladen und der Index erhöht. Danach wird wieder TWINT gelöscht und die 
ISR verlassen.
Das wird so lange wiederholt, wie sich Datenbytes im aktuellen Puffer 
befinden.

Sobald das letzte Datenbyte übermittelt wurde und ein ACK vom Slave 
zurückkommt, wird die Slave-Adresse im aktuellen Puffer auf 0xFF gesetzt 
(um anzuzeigen, dass dieser fertig abgearbeitet wurde) und der Leseindex 
des Puffers erhöht.

Wenn die Slave-Adresse des nun aktuellen Puffers nicht 0xFF ist, wird 
eine Repeated-Start-Bedingung ausgeführt und die Daten dieses Puffers 
abgearbeitet. Wenn die Slave-Adresse 0xFF ist, wird damit angezeigt, 
dass sich nicht (mehr) gültige Daten im Puffer befinden und eine 
Stop-Bedingung ausgeführt. Danach wird wieder TWINT gesetzt und die ISR 
verlassen.


Der Read- (Receiver) Vorgang läuft ähnlich ab, mit dem Unterschied, dass 
eben die Daten vom Bus (von TWDR) in den Puffer kopiert werden. Sobald 
die in i2c_send() angegebene Anzahl der Datenbytes empfangen wurde, wird 
ein NAK vom Master ausgegeben.


Ich hoffe, ich konnte hier meine Absichten ausreichend verständlich 
schildern...
Ich kann in meinem Programm einfach nicht den Fehler finden.

von (prx) A. K. (prx)


Lesenswert?

restfet schrieb:
> Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert
> von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.

Ist das echter Quellcode, oder hast du den eigens fürs Forum 
zusammengeklebt? Immerhin gibts 2 gleichnamige ISRs im gleichen File, 
ohne dass eine davon ausgeblendet wäre.

von restfet (Gast)


Lesenswert?

Ja, das passt schon so. Das einzige, das ich daran fürs Forum geändert 
habe, waren die Tabulatoren, die ich gegen 4 mal Leerzeichen ersetzt 
habe ;-)

A. K. schrieb:
>2 gleichnamige ISRs im gleichen File

siehe

restfet schrieb:
> Die zweite ISR(TWI_vect) ist für Debugging-Zwecke gedacht, die den Wert
> von TWSR auf Ports rausschreibt bzw. über den UART an den PC sendet.
>
> Testweise hab ich mal ein Programm geschrieben, das mit der Debug-ISR
> arbeitet. Nachdem die ISR abgearbeitet wurde, stehen an PORTA 0xB3 und
> an PORTC 0x20. Über den UART wurden 7 Leerzeichen (?) und ein Rufzeichen
> gesendet.

von (prx) A. K. (prx)


Lesenswert?

Ich würde empfehlen, in der ISR nicht einfach nur einzelne Bits von TWCR 
zu setzen, sondern dieses Register stets komplett mit dem gewünschten 
Inhalt zu schreiben. So macht das meiner Erinnerung nach auch Atmels 
Vorlage, und so macht es auch mein Code.

So vermisse ich bei dir beispielsweise die Handhabung vom TWSTA, das 
setzt sich nämlich nicht von allein zurück.

von (prx) A. K. (prx)


Lesenswert?

Was soll das
  TWSR |= (0xF8);
bringen? Diese Bits sind durchweg nur lesbar.

von restfet (Gast)


Lesenswert?

A. K. schrieb:
> Was soll das
>   TWSR |= (0xF8);
> bringen? Diese Bits sind durchweg nur lesbar.

Oha, das hab ich ganz übersehn. Damit wollte ich TWSR resetten.

von (prx) A. K. (prx)


Lesenswert?

So sieht das bei mir aus, mit etwas einfacherer Pufferstruktur:
1
ISR(TWI_vect)
2
{
3
    uint8_t state = TW_STATUS;
4
    uint8_t ctrl  = 0;
5
6
    switch (state) {
7
8
    case TW_REP_START:  //10
9
        // Repeated START has been transmitted, change to read mode
10
        i2c_buffer[0] |= TW_READ;
11
    case TW_START:      //08
12
        // START has been transmitted  
13
        i2c_bufptr = 0;                                         // initialize buffer pointer
14
        goto addr;
15
16
    case TW_MT_SLA_ACK: //18
17
    case TW_MT_DATA_ACK://28
18
        // SLA+W has been transmitted and ACK received
19
        // Data byte has been transmitted and ACK received
20
    addr:
21
        if (i2c_bufptr < i2c_wrsize) {
22
            // Send next byte
23
            TWDR = i2c_buffer[i2c_bufptr++];
24
            ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWIE);              // reset interrupt, enable interrupt
25
        } else if (i2c_rdsize) {
26
            // Send repeated START when followup read is desired
27
            ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWIE)|_BV(TWSTA);   // reset interrupt, enable interrupt, START
28
        } else {
29
            // Send STOP after last byte
30
            ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWSTO);             // reset interrupt, disable interrupt, STOP
31
            i2c_state = I2C_SUCCESS;
32
        }
33
        break;
34
35
    case TW_MR_DATA_ACK://50
36
        // Data byte has been received and ACK transmitted
37
        i2c_buffer[i2c_bufptr++] = TWDR;
38
        goto next;
39
40
    case TW_MR_SLA_ACK: //40
41
        // SLA+R has been transmitted and ACK received
42
    next:
43
        if (i2c_bufptr < (uint8_t)(i2c_rdsize-1)) {             // detect the last byte to NACK it.
44
            ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWIE)|_BV(TWEA);    // reset interrupt, enable interrupt, ACK
45
        } else {
46
            ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWIE);              // reset interrupt, enable interrupt
47
        }    
48
        break;
49
50
    case TW_MR_DATA_NACK://58
51
        // Data byte has been received and NACK transmitted
52
        i2c_buffer[i2c_bufptr++] = TWDR;
53
        ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWSTO);                 // reset interrupt, disable interrupt, STOP
54
        i2c_state = I2C_SUCCESS;
55
        break;
56
57
    case TW_MT_ARB_LOST: //38 == MR_MR_ARB_LOST
58
        // Arbitration lost
59
        ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWIE)|_BV(TWSTA);       // reset interrupt, enable interrupt, START
60
        break;
61
62
    case TW_NO_INFO:    //F8
63
        break;
64
65
    default:
66
        i2c_state = state;
67
        ctrl = _BV(TWEN)|_BV(TWINT)|_BV(TWSTO);                 // reset interrupt, disable interrupts, STOP
68
        break;
69
    }
70
71
    if (ctrl)
72
        TWCR = ctrl;
73
}

von restfet (Gast)


Lesenswert?

A. K. schrieb:
> So vermisse ich bei dir beispielsweise die Handhabung vom TWSTA, das
> setzt sich nämlich nicht von allein zurück.

Damit hast du mich wohl auf die richtige Fährte gelockt! Jetzt 
funktionierts schon viel besser!

von M. T. (restfet)


Angehängte Dateien:

Lesenswert?

Melde mich hier mal wieder zurück.

Ich habe zwischenzeitlich versucht an meinem I2C-Interface 
weiterzuarbeiten und habe alles ein bisschen umgebaut. Im Wesentlichen 
solls jetzt drei verschiedene Funktionen für die Übertragung geben: zwei 
zum Versenden (Master Transmitter) und eine zum Empfangen (Master 
Receiver). Zusätzlich noch eine Initialisierungsfunktion, eine um den 
Status des Interface zu überprüfen und eine um die Daten aus dem Puffer 
zu holen.

Das funktioniert soweit auch so wie ich es mir vorstelle. Nur leider 
habe ich es nicht geschafft, das ganze gepuffert hinzubekommen. Somit 
muss ich immer die aktuelle Übertragung abwarten, bis ich mit der 
nächsten beginnen kann. Für komplexere Übertragungen muss ich mir im 
Hauptprogramm umfangreiche State Machines zusammenfrickeln.

Ich habe mich auch schon an einer gepufferten Version versucht, nur 
denke ich, dass mir dabei der Compiler einen Strich durch die Rechnung 
macht und die Funktionalität "wegoptimiert". Kann mir hier jemand sagen, 
was ich dabei anders machen muss, damit das funktioniert?

Andere Verbesserungsvorschläge werden auch gerne angenommen ;-)

Im Anhang finden sich meine beiden Versionen des Interfaces, die 
funktionierende ungepufferte und mein nicht funktionierender Versuch 
eines gepufferten Interfaces.

Hier kurz meine Vorstellung, wie das alles im Hauptprogramm zur 
Anwendung kommen soll:
1
#define SLAVE_ADRESSE  0x34
2
3
void main()
4
{
5
  uint8_t dataArray[3] = {1, 2, 3};
6
  uint8_t stateMachine = 0;
7
  
8
  i2c_initMaster(100);
9
  
10
  while(1) {
11
    if((i2c_getState() & I2C_ST_IDLE) == I2C_ST_IDLE) {
12
      switch(stateMachine) {
13
      case 0:
14
        /* einzelnes Byte senden */
15
        i2c_transmitSingle(SLAVE_ADRESSE, 0xAA);
16
        stateMachine++;
17
        break;
18
      case 1:
19
        /* drei Bytes aus dataArray senden */
20
        i2c_transmitMultiple(SLAVE_ADRESSE, dataArray, 3);
21
        stateMachine++;
22
        break;
23
      case 2:
24
        /* zwei Bytes sollen empfangen werden */
25
        i2c_receive(SLAVE_ADRESSE, 2);
26
        stateMachine++;
27
        break;
28
      }
29
      
30
      if((i2c_getState() & I2C_DTA_RDY) == I2C_DTA_RDY) {
31
        /* zuvor empfangene Bytes werden in dataArray abgelegt */
32
        memcpy(dataArray, i2c_getData().data, 2);
33
      }
34
    }
35
    
36
    /*
37
      ... etwas anderes machen ...
38
    */
39
  }
40
}

Wenn alles gepuffert wird, könnte es so aussehen...
1
#define SLAVE_ADRESSE  0x34
2
3
void main()
4
{
5
  uint8_t dataArray[3] = {1, 2, 3};
6
  
7
  i2c_initMaster(100);
8
  
9
  /* einzelnes Byte senden */
10
  i2c_transmitSingle(SLAVE_ADRESSE, 0xAA);
11
  
12
  /* drei Bytes aus dataArray senden */
13
  i2c_transmitMultiple(SLAVE_ADRESSE, dataArray, 3);
14
  
15
  /* zwei Bytes sollen empfangen werden */
16
  i2c_receive(SLAVE_ADRESSE, 2);
17
  
18
  while(1) {
19
    if((i2c_getState() & I2C_ST_DTA_RDY) == I2C_ST_DTA_RDY) {
20
      /* zuvor empfangene Bytes werden in dataArray abgelegt */
21
      memcpy(dataArray, i2c_getData().data, 2);
22
    }
23
    
24
    /*
25
      ... etwas anderes machen ...
26
    */
27
  }
28
}

von M. T. (restfet)


Lesenswert?

Hat niemand eine Idee?

von (prx) A. K. (prx)


Lesenswert?

Bin nur mal kurz quer drüber:

Kein START jenseits der ersten message.

Der Code enthält Sequenzen, die Chaos produzieren, wenn sie von der ISR 
unterbrochen werden. Versuch mal, dich mit dem Gedanken anzufreunden, 
dass Interrupts überall auftreten können, wo sie nicht explizit 
ausgeschlossen sind.

von M. T. (restfet)


Angehängte Dateien:

Lesenswert?

A. K. schrieb:
> Kein START jenseits der ersten message.

Ja, in der ISR würde ein Flag in der state-Variable gesetzt werden, das 
anzeigt, dass noch mehr Daten im Puffer vorhanden sind. Im Hauptprogramm 
wäre dann die state-Variable abgefragt worden und entsprechend eine 
Start-Bedingung ausgelöst worden.
Ich hab das jetzt wieder so geändert, dass in der ISR ein Start 
ausgelöst wird, wenns mehr zu versenden gibt.

> Der Code enthält Sequenzen, die Chaos produzieren, wenn sie von der ISR
> unterbrochen werden. Versuch mal, dich mit dem Gedanken anzufreunden,
> dass Interrupts überall auftreten können, wo sie nicht explizit
> ausgeschlossen sind.

Ich hab die Interrupts in den I2C-Funktionen mal global ausgeschaltet.

Die Änderungen haben aber leider nichts gebracht.

Anbei auch ein Oszi-Screenshot einer Übertragung. Diese wurde durch 
folgenden Code prodziert..:
1
void main()
2
{
3
  uint8_t daten[2] = {0, 0};
4
  
5
  i2c_initMaster(100);
6
7
  i2c_transmitMultiple(0x1E, daten, 2);
8
  i2c_transmitSingle(0x1E, 0xFF);
9
  
10
  while(1);
11
}

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.