Forum: Mikrocontroller und Digitale Elektronik Anfänger braucht Hilfe mit TWI / I2C


von Borsty B. (mantabernd)


Angehängte Dateien:

Lesenswert?

Guten Abend liebe Forumsgemeinde,

ich sitz nun schon seit einiger Zeit an meiner ersten TWI  / I2C 
Schaltung und verzweifle langsam.

Controller ist ein Mega32 und Slave ein PCM3070 (wobei das erstmal egal 
sein sollte). Controller läuft mit dem int. Quarz auf 1MHz und der 
Bustakt läuft mit 10kHz.

Ich hab mich hier in die Thematik eingelesen und soweit auch mal die 
Theorie verstanden. 
(http://elk.informatik.fh-augsburg.de/da/da-21/Tutorial/tutorial.html).

In der Praxis hat folgender Code die ersten Minuten funktioniert (siehe 
Bild scope_37):
1
#include <avr/io.h>
2
#include <util/twi.h>
3
4
int Wert;
5
int TWBR_VAL = 40;
6
7
int main(void)
8
{
9
    while(1)
10
    {
11
        TWBR = TWBR_VAL;
12
        TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);
13
        while (!(TWCR & (1<<TWINT)));
14
        TWDR = (0000000<<1) | TW_WRITE;  // Hier adressiere ich alle Slaves!
15
        TWCR = (1<<TWINT) | (1<<TWEN);
16
        while (!(TWCR & (1<<TWINT))) ;
17
        TWDR = Wert;
18
        TWCR = (1<<TWINT) | (1<<TWSTO) | (1<<TWEN);
19
    
20
    }
21
}

Auf dem Bild scope_37 erkennt man dass 7 Nullen und das SLA_W Bit 
gesendet werden und der Slave die Anfrage akzeptiert was er mit einem 
HIGH als ACK zurückgibt. (Was nach dem ACK das weitere HIGH soll weiß 
ich allerdings noch nicht, ist das mein Wert der übertragen wird?).

So nun dachte ich mir läuft die Kiste aber Pustekuchen, wenn ich z.b. 
0000111 als Adresse sende sieht das Ergebnis aus wie auf Bild 38 wo ich 
def. keine Logik mehr erkennen kann weil nirgends 4 Nullen auftauchen.

Interessanterweise kann ich den PCM3070 mit der Adresse 0011000 gar 
nicht ansprechen, das Ergebnis sieht aus wie auf Bild 37. Kann doch 
nicht sein, oder?

Ich würde mich freuen wenn ich Tips von euch krieg :) Vielen Dank!

Gruß
Bernhard

von Steffen H. (avrsteffen)


Lesenswert?

Hallo,

Ich vermisse hier ein 'STOP' auf dem Bus. Außerdem ist das 'HIGH' zum 
9.CLK ein NACK vom Slave.

Die CLK-Leitung sieht auch schon leicht verschliffen aus. Verringere mal 
den Rp.


Gruß Steffen

von Borsty B. (mantabernd)


Angehängte Dateien:

Lesenswert?

Steffen H. schrieb:
> Die CLK-Leitung sieht auch schon leicht verschliffen aus. Verringere mal
> den Rp.

Wurde behoben, hat sich fälschlicherweise ein 100k Widerstand 
eingeschlichen.

Steffen H. schrieb:
> Ich vermisse hier ein 'STOP' auf dem Bus.

Hab ich behoben, es muss erst das Startbit gelöscht werden bevor ein 
Stop gesendet werden kann.

>Außerdem ist das 'HIGH' zum 9.CLK ein NACK vom Slave.

Jop, im Tutorial stehts leider genau andersrum ist aber ein NACK wie du 
richtig erkannt hast.

So, nun klappt neben dem Start auch der Stop und ich kann sehen dass der 
Slave nicht reagiert. (siehe Bild 40).

Mein Code sieht dabei folgendermaßen aus:
1
      TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN); // TWI Interrupt aktivieren, TWI starten und einschalten.
2
      while (!(TWCR & (1<<TWINT))); // Warten bis der Start erfolgreich war.
3
      TWDR = (0110000<<1) | TW_WRITE; // Senden des Schreibbefehles an die Slaveadresse.
4
      TWCR &= ~(1<<TWSTA); // Startbit löschen.
5
      TWCR = (1<<TWINT) | (1<<TWEN); // TWINT setzen.
6
      while (!(TWCR & (1<<TWINT))) ; // Warten bis Twint gesetzt ist.
7
      TWCR = (1<<TWINT) | (1<<TWSTO) | (1<<TWEN); // Stop senden.

Hier wird doch ganz klar die Adresse des Slaves angesprochen?! (hab ich 
aus dem Datenblatt), aber warum sendet der Master lauter Nullen? Ich 
schaffe es nicht den Slave anzusprechen. Ändere ich TW_Write zu TW_Read 
dann geht Bit 8 von 0 auf 1, das klappt also. Nur verstehe ich nicht wie 
ich den Slave adressiere. Muss ich die Adresse hier im Hexformat 
eintragen?
1
TWDR = (0110000<<1) | TW_WRITE;

Vielen Dank,
Gruß
Bernhard

von Ein (Gast)


Lesenswert?

Bernhard W. schrieb:
> TWDR = (0110000<<1) | TW_WRITE;

Wieso schreibst du 0110000?
Im Datenblatt (p.29 I²C Control) steht 0011000.

von Borsty B. (mantabernd)


Lesenswert?

Ein schrieb:
> Wieso schreibst du 0110000?
> Im Datenblatt (p.29 I²C Control) steht 0011000.

Ok sorry ganz klar mein Fehler. Aber trotzdem sollten doch  nicht lauter 
Nullen gesendet werden, da stimmt doch was nicht?!

von Steffen H. (avrsteffen)


Lesenswert?

Hallo,

Wie ist denn TW_Write bzw. TW_Read definiert?
1
#define  TW_Write 0
2
#define  TW_Read  1
Etwa so?

Hast du mal versucht ohne die Veroderung es direkt in TWDR einzutragen? 
Etwa so?
1
TWDR = (01100000<<0); // Senden des Schreibbefehles an die Slaveadresse.
So jedenfalls sollte dein TWI erstmal richtig senden. Probier mal bitte 
und schick ein Foto davon.


Gruß Steffen

von Borsty B. (mantabernd)


Angehängte Dateien:

Lesenswert?

Danke Steffen für den Tipp das hat mich auf eine Idee gebracht.

Wenn man die Adresse dezimal eingibt dann läuft die Kiste.

1
TWDR = (24<<1) | TW_WRITE ; // Senden des Schreibbefehles an die Slaveadresse.

Eventuell eine Einstellung am Compiler? Ich arbeite mit dem AVR Studio 
6.

Gruß
Bernhard

von GCC-Guru (Gast)


Lesenswert?

Bernhard W. schrieb:
> TWDR = (24<<1) | TW_WRITE ; // Senden des Schreibbefehles an die Slaveadresse.

Was dann doch das gleiche wäre wie 0b0011000

von Steffen H. (avrsteffen)


Lesenswert?

Lief es mit der Einstellung
1
TWDR = (01100000<<0); // Senden des Schreibbefehles an die Slaveadresse.
auch??

Dann liegt dein Problem in der Veroderung mit TW_Write/TW_Read..


Gruß Steffen

von Steffen H. (avrsteffen)


Lesenswert?

Ah, vieleicht aber so:
1
TWDR = (0b0011000<<1) | TW_Write; // Senden des Schreibbefehles an die Slaveadresse

Bitte mal probieren.

von Borsty B. (mantabernd)


Lesenswert?

Steffen H. schrieb:
> Lief es mit der EinstellungTWDR = (01100000<<0); auch??

Nein da lief es nicht. Sobald die Zahl mit einer 0 angefangen hat kamen 
nur Nullen raus.

Steffen H. schrieb:
> Ah, vieleicht aber so:
> TWDR = (0b0011000<<1) | TW_Write;

> Bitte mal probieren.

Werd ich heute Abend mal probieren.

Andere Frage:

Bei dem PCM3070 wird das Routing und die DSP Einstellungen mit einer 
Software von Ti erstellt. Diese Software hab ich auch bereits 
installiert und ein erstes Setup gebastelt. Man kann sich dann daraus 
eine .cfg Datei erstellen lassen die alle Register und Werte enthält. 
Laut Ti soll man diese Datei in ein Eeprom schreiben (hat 56KByte so wie 
sie jetzt ist) und beim starten mit einem µC vom Eeprom in den PCM3070 
laden.

Das klingt ja theoretisch ganz einfach, praktisch hab ich da allerdings 
so meine Problemchen.

Kann ich (wenn ich die Datei als hex im Eeprom liegen hab) mit dem µC 
einfach eine Schleife laufen lassen die jedes Byte aus dem Eeprom 
ausliest, auf sich selbst zwischenlagert und in den PCM3070 schreibt?

Das wären dann 56KByte = 56.000Byte = 56.0000 mal folgende 
Vorgehensweise:

TWI Start
TWI Connect Eeprom Read
TWI Read Byte n
TWI Stop
Byte auf lokalem Controllerspeicher ablegen
TWI Start
TWI Connect PCM3070 Write
TWI Write Byte n
TWI Stop

TWI Start
TWI Connect Eeprom Read
TWI Read Byte n+1
TWI Stop
Byte auf lokalem Controllerspeicher ablegen
TWI Start
TWI Connect PCM3070 Write
TWI Write Byte n+1
TWI Stop

Bei einem Bustakt von 10KHz würde das knapp 6 Sekunden dauern. Ist diese 
Vorgehensweise so ansatzweise richtig oder macht man das anders?

Gruß
Bernhard

von Walter S. (avatar)


Lesenswert?

Bernhard W. schrieb:
> Nein da lief es nicht. Sobald die Zahl mit einer 0 angefangen hat kamen
> nur Nullen raus.

logisch, schau Mal nach was eine mit Null beginnende Zahl für ein 
Zahlenformat ist

von PittyJ (Gast)


Lesenswert?

Wenn eine Zahl mit 0 beginnt, danach aber kein b oder x kommt, dann wird 
sie als Octal interpretiert. Braucht heute kein Mensch mehr, war wohl in 
den 70er Jahren noch wichtig.

0110 is also
0*512 + 1*64 + 1*8 + 0
also 72 dezimal.

von Borsty B. (mantabernd)


Lesenswert?

PittyJ schrieb:
> Wenn eine Zahl mit 0 beginnt, danach aber kein b oder x kommt, dann wird
> sie als Octal interpretiert. Braucht heute kein Mensch mehr, war wohl in
> den 70er Jahren noch wichtig.
>
> 0110 is also
> 0*512 + 1*64 + 1*8 + 0
> also 72 dezimal.

Super, danke , kurz und verständlich erklärt.

Kann mir bei meinem anderen Problem auch jemand weiterhelfen?

von Steffen H. (avrsteffen)


Lesenswert?

Hallo Bernhard,

Ich hab mir gerade mal das Datasheet zum PCM3070 angesehen. Leider finde 
ich kein Protokoll zum I2C Bus.

Aber wenn man sich die Sample-Configurations ansieht, dann denk ich es 
wird:

- Start
- Device-Adresse
- Page-Adresse
- Data-Byte
- Stop

via I2C jedesmal geschrieben. Wie das Tool von TI die Config-Datei 
aufgebaut hat, welches du ja in den EEPROM schreiben willst, musst du 
noch raus finden. Ich hab dazu leider nichts gefunden.

von Busbauer (Gast)


Lesenswert?

Hat dein Oszi die Serial-Funktion freigeschaltet?

Wenn ja kannst du da auf I2C umschalten und wenn du dem Oszi dann 
mitteilst was SCL und was SDA ist, zeigt er dir unten an was du da für 
einen Brei sendest.
Kannst auch den Trigger auf I2C Start setzen ;)

von Borsty B. (mantabernd)


Lesenswert?

Busbauer schrieb:
> Hat dein Oszi die Serial-Funktion freigeschaltet?
>
> Wenn ja kannst du da auf I2C umschalten und wenn du dem Oszi dann
> mitteilst was SCL und was SDA ist, zeigt er dir unten an was du da für
> einen Brei sendest.
> Kannst auch den Trigger auf I2C Start setzen ;)

Leider nein da ich bisher noch nie mit Bussystemen zu tun hatte, es ist 
mein allererstes Projekt mit I2C / TWI. Werde mich am Wochenende 
vermutlich weiter damit beschäftigen, hab grad keine Zeit dafür.

Danke für eure Antworten,
Gruß

von Wusel D. (stefanfrings_de)


Lesenswert?

Alternativ könnte man das Start Signal per Software auf einen freien Pin 
ausgeben und mit dem Trigger-EIngang des Oszilloskpes verbindne.


Oder man baut eine 10ms Warteschleife vor dem Start Signal ein und 
triggert auf das Ende  der 10ms Pause (falls Dein Oszilloskop das kann).

von Borsty B. (mantabernd)



Lesenswert?

Hi,

ich hab mich jetzt mal hingesetzt und mit der TruePath Software ein 
Programm erstellt.

Grafisch sieht das wie auf Bild 1 aus, man kann daraus dann die .cfg 
Datei erstellen lassen und bekommt dann die Register mit den Werten 
(glaube ich) -> siehe Anhang!

Wie krieg ich nun am einfachsten die .cfg Datei mit einem ATmega32 über 
I2C in den PCM3070?

Danke, Gruß und frohe Ostern.
Bernhard

von Borsty B. (mantabernd)


Lesenswert?

Hallo zusammen,

aaaalso ich habs geschafft dem PCM3070 Daten zu übermitteln, wie Steffen 
geschrieben hat geht es ganz einfach so:

- Start
- Device-Adresse
- Page-Adresse
- Data-Byte
- Stop

In Code ausgedrückt sieht das dann so aus:
1
void TWI_send (add, reg, val) {
2
3
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);   // send START condition
4
      while (!(TWCR & (1<<TWINT)));             // wait for response
5
      if ((TWSR & 0xF8) == TW_START)            // if START was successful
6
      {
7
        TWDR = (add & 0xFE);                // put slave address+WRITE to TWDR
8
        TWCR = (1<<TWINT)|(1<<TWEN);            // and start transmission
9
        while (!(TWCR & (1<<TWINT)));         // wait for response
10
        if ((TWSR & 0xF8) == TW_MT_SLA_ACK){     // if acknowledge ok  
11
          PORTB |= (1<<PINB0);
12
          TWDR = reg;                      // put regbyte  to TWDR
13
          TWCR = (1<<TWINT)|(1<<TWEN);        // and start transmission
14
          while (!(TWCR & (1<<TWINT)));         // wait for response
15
          if ((TWSR & 0xE8) == TW_MT_DATA_ACK){   // if acknowledge ok
16
            TWDR = val;              // put valbyte to TWDR
17
            TWCR = (1<<TWINT)|(1<<TWEN);    //start transmission
18
            while (!(TWCR & (1<<TWINT)));         // wait for response
19
            if ((TWSR & 0xE8) == TW_MT_DATA_ACK){   // if acknowledge ok
20
              PORTB |= (1<<PINB0);
21
            }
22
            else
23
            {
24
              PORTB &= ~(1<<PINB0); //LED leuchtet bei Fehler.
25
            }
26
          }
27
          else
28
          {
29
            PORTB &= ~(1<<PINB0); //LED leuchtet bei Fehler.
30
          }            
31
        }
32
        else
33
        {
34
          PORTB &= ~(1<<PINB0); //LED leuchtet bei Fehler.
35
          
36
        }
37
      }
38
      else
39
      {
40
      PORTB &= ~(1<<PINB0); //LED leuchtet bei Fehler.
41
      }
42
      while (!(TWCR & (1<<TWINT)));             // wait for response
43
      TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN);   // transmit STOP condition
44
      _delay_ms(10);            
45
}

Der Code funktioniert, man sieht am Oszi wie jede Übertragung mit ACK 
bestätigt wird. Soweit so gut.

Ich sende nun also mit
1
TWI_send(add,reg,val);

meine Befehle nacheinander ab.

Die TruePath Software gibt mir nun eine Datei aus wie ich sie schon 
vorher angehängt habe.

Dort sehen die Befehle folgendermaßen aus:

#      # reg[0][84] = 0
w 30 54 00
#      # reg[0][94] = 32
w 30 5e 20
#      # reg[0][95] = 254
> fe
#      # reg[0][96] = 0
> 00
#      # reg[0][97] = 104
> 68
#      # reg[0][98] = 168
> a8
#      # reg[0][99] = 6

w steht für write, ein TWI Befehl würde dann für die erste Zeile also 
wie folgt aussehen:
1
TWI_send(0x30, 0x54, 0x00);

Das funktioniert auch, hab ich getestet.

Bei den Befehlen mit dem > Zeichen muss man die Register hochzählen, 
hier im Beispiel also 95/96/97/... und in Hex eben 5f/60/61/...

Soweit ja alles klar aber ich brauch irgend eine Routine die mir das 
macht, also die Werte aus der Datei nimmt und mir in meine TWI_send(); 
Funktion bastelt.

Kennt jemand einen einfachen Weg das zu machen? Die Datei ist einfach zu 
groß um die ganzen Befehle per Hand zu ändern.

Bis vor ein paar Tagen wusste ich noch gar nicht wie I2C überhaupt 
funktioniert, bin jetzt echt froh soweit gekommen zu sein und alles 
verstanden zu haben aber ich habe leider keinen Ansatz wie ich hier nun 
weiter komme.

Vielen Dank für die Hilfe,
Gruß
Bernhard

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.