"I2C-Stopp" wird nicht ausgeführt (HW-TWI, AVR)

Gast #2869932
Lesenswert?

Nachdem nur sporadisch Daten in das externe EEPROM (24C04) übernommen 
werden, habe ich mich auf die Fehlersuche gemacht.

Ausgangslage:
- Standard-Routinen für Hardware-TWI (Mega128)
- Bisher galten die Routinen als "fehlerfrei"
- Am Bus befinden sich ein 24C04 und zwei LM75 (Temp-Sens)
- Fehler: Sporadisch werden die in das EEP zu schreibenden Daten nicht 
übernommen
- Die Kommunikations-Routinen melden dabei keinen Fehler

Ich habe das Szenario soweit eingrenzen können, dass direkt nach einem 
fehlerhaften Schreibzyklus eine Abfrage eines LM75 erfolgt. Obwohl im 
Code die Anweisung "TWIM_Stop()" steht, wird diese nicht ausgeführt. Es 
folgt ohne ein I2C-Stopp direkt ein Re-Start für den LM75. Dadurch wird 
die EEP-interne Schreibroutine anscheinend nicht angestossen und die 
Daten sind weg.. :-(
(Ich habe davon einen Screenshot gemacht und die Zustände eingezeichnet)

Die TWIM-Stop() - Routine lautet wie folgt:
1
void TWIM_Stop (void)
2
{
3
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
  while (TWCR & (1<<TWINT));
5
}

Warum gibt die Hardware diese Stop-Anweisung nicht aus?
Setze ich nach der Stop-Anweisung noch ein Delay von 20us, so läuft das 
ganze. Das kann doch aber nicht die Lösung sein..?!? :-(
Angehängte Dateien:
Gast #2870031
Lesenswert?

Ich habe nun als Workaround 80(!!) NOP's in die Stop-Funktion eingefügt, 
um eine Verzögerung von ~7,2us (bei 11,0592MHz) zu erreichen.
=> siehe Screenshot!

Warum die TWI-Hardware jedoch nicht sofort reagiert, ist mir immer noch 
ein Rätsel.. :-(
(Auch in den Errata-Sheet's finde ich nichts passendes.)

Code:
1
void TWIM_Stop (void)
2
{
3
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
  while (TWCR & (1<<TWINT));
5

6
  NOP(); NOP(); NOP(); NOP(); NOP();
7
  NOP(); NOP(); NOP(); NOP(); NOP();
8
  NOP(); NOP(); NOP(); NOP(); NOP();
9
  NOP(); NOP(); NOP(); NOP(); NOP();
10
  NOP(); NOP(); NOP(); NOP(); NOP();
11
  NOP(); NOP(); NOP(); NOP(); NOP();
12
  NOP(); NOP(); NOP(); NOP(); NOP();
13
  NOP(); NOP(); NOP(); NOP(); NOP();
14
  NOP(); NOP(); NOP(); NOP(); NOP();
15
  NOP(); NOP(); NOP(); NOP(); NOP();
16
  NOP(); NOP(); NOP(); NOP(); NOP();
17
  NOP(); NOP(); NOP(); NOP(); NOP();
18
  NOP(); NOP(); NOP(); NOP(); NOP();
19
  NOP(); NOP(); NOP(); NOP(); NOP();
20
  NOP(); NOP(); NOP(); NOP(); NOP();
21
  NOP(); NOP(); NOP(); NOP(); NOP();
22
}
Angehängte Dateien:
#2870081
Lesenswert?

Der Techniker schrieb:
> Ich habe das Szenario soweit eingrenzen können, dass direkt nach einem
> fehlerhaften Schreibzyklus eine Abfrage eines LM75 erfolgt. Obwohl im
> Code die Anweisung "TWIM_Stop()" steht, wird diese nicht ausgeführt. Es
> folgt ohne ein I2C-Stopp direkt ein Re-Start für den LM75. Dadurch wird
> die EEP-interne Schreibroutine anscheinend nicht angestossen und die
> Daten sind weg.. :-(
> (Ich habe davon einen Screenshot gemacht und die Zustände eingezeichnet)
>
> Die TWIM-Stop() - Routine lautet wie folgt:void TWIM_Stop (void)
> {
>   TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
>   while (TWCR & (1<<TWINT));
> }
>
> Warum gibt die Hardware diese Stop-Anweisung nicht aus?
> Setze ich nach der Stop-Anweisung noch ein Delay von 20us, so läuft das
> ganze. Das kann doch aber nicht die Lösung sein..?!? :-(

Eine Fragen:
Wie wird die LM75-Kommunikation angestoßen?

Meine Glaskugel sagt:
- LM75 wird per Interrupt angestoßen.
- Ohne die NOPs kommt der LM75-IRPT ab und zu zufällig direkt vor der 
STOP-Routine des 24C04.

Abhilfe:
Atomare I2C-Zugriffe bis incl. STOP-Command.

Das ist aber alles Spekulation und ohne Source-Code nicht überprüfbar.

Bernhard
Gast #2870085
Lesenswert?

Hallo,

vielleicht änderst du deine Routine

Der Techniker schrieb:
> Die TWIM-Stop() - Routine lautet wie folgt:void TWIM_Stop (void)
> {
>   TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
>   while (TWCR & (1<<TWINT));
> }

so ab

void TWIM_Stop (void)
{
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
  while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
}
Gast #2870093
Lesenswert?

> Meine Glaskugel sagt:
> - LM75 wird per Interrupt angestoßen.
> - Ohne die NOPs kommt der LM75-IRPT ab und zu zufällig direkt vor der
> STOP-Routine des 24C04.

Leider nein. Momentan sind nur ein Timer-IRQ (zum setzen von 
Event-Flags) und die IRQ's für die UART aktiv. TWI läuft ausschließlich 
über Polling (while-Schleife). :-/
Gast #2870140
Lesenswert?

Stefan++ schrieb:
> Hallo,
>
> vielleicht änderst du deine Routine
> so ab
>
> void TWIM_Stop (void)
> {
>   TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
>   while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
> }

Gerade probiert: Dann bleibt die CPU dort hängen und der WDT greift nach 
1s. :-(
Gast #2870144
Lesenswert?

Klaus schrieb:
> Der Techniker schrieb:
>> Schon getestet -> leider nein.. :-(
>
> Ich vermute aber, es ist was ähnliches. Deine while() Schleife
> terminiert zu früh, dein Code überläuft den I2C Controler. Sehe gerade
> Stefan denkt auch in die Richtung
>
> MfG Klaus

Da stimme ich euch ja 100%ig zu, nur irgendwie habe ich dann Tomaten auf 
den Augen..?!?
Wenn ich es nicht selber sehen würde, würde ich es nicht glauben.. ;-b
Gast #2870194
Lesenswert?

Wieder etwas schlauer:
"Assuming that the status code is as expected, the application must 
write a specific value to TWCR, instructing the TWI hardware to transmit 
a STOP condition. Which value to write is described later on. However, 
it is important that the TWINT bit is set in the value written. Writing 
a one to TWINT clears the flag. The TWI will not start any operation as 
long as the TWINT bit in TWCR is set. Immediately after the application 
has cleared TWINT, the TWI will initiate transmission of the STOP 
condition. Note that TWINT is NOT set after a STOP condition has been 
sent."

D.h. zum Initiieren des TWI-Stops muss man das Stopp-Bit zusammen mit 
dem TWI-IRQ-Flag (und dem TWI-Enable) setzten. Erst NACHDEM (!) die 
Hardware das IRQ-Flag gelöscht hat, wird die Stopp-Prozedur gestartet..

Jetzt stellt sich mir nur die Frage, wie bekomme ich mit wann das 'Stop' 
gesendet wurde. Das Stop-Flag wird nämlich zusammen mit dem IRQ-Flag 
zurückgesetzt..? :-(
Gast #2870216
Lesenswert?

Ich mache es nun so, weil mir ein Delay nicht gefällt und ich keine 
bessere Lösung finde:

Header:
1
#define SCL_LINE  (PIND & (1<<PD0))
2
#define SDA_LINE  (PIND & (1<<PD1))

STOP/Warte-Routine:
1
void TWIM_Stop (void)
2
{
3
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
  while (TWCR & (1<<TWINT));
5
}
6

7
void TWIM_Ready (void)
8
{
9
  while (!(SCL_LINE && SDA_LINE));
10
}

An den Programmstellen, an denen ein Stopp zwingend gefordert ist, rufe 
ich nun die Funktion TWIM_Ready() nach dem TWIM_Stop() auf um zu 
warten.. (~5us)

Oder weiß noch jemand eine elegantere Lösung?
#2870314
Lesenswert?

HAllo Techniker

Das Problem kenn ich :-) siehe meinen Beitrag:
Beitrag "Re: [ASM] Hardware TWI-MASTER Interrupt basierend für Mega AVR"

Ich hab es damals so gelöst:
Ich habe einen wait to PIN CHANGE INTERRUPT auf die SDA-Leitung 
eingefügt, wenn in der Interrupt Aktion "twi_stop" in das TWCR 
eingetragen wurde.
1
.equ    TWI_PORT  = PORTC
2
.equ    TWI_SCL   = 0
3
.equ    TWI_SDA   = 1

Hier die Aufgabe im Interrupt das STOP zu senden:
1
twi_process_stop:
2
  ldi   r16, twi_stop_cmd
3
  lds   r17, (PCMSK2)
4
  sbr   r17, (1<<TWI_SDA)
5
  sts   (PCMSK2), r17         ; Pin Change Interrupt enabled
6
TWI_int_end:
7
  sts   (TWCR), r16
Da es ja kein Interrupt mehr gibt wenn der Prozessor den TWI-STOP 
ausgeführt hat und deine Leitungen für SDA und SCL im korrekten timing 
wieder auf Hi-Z stehen, muss man da mit einem anderen Pin den SDA 
überwachen und eben durch die lo-hi-Flanke des SDA einen Interrupt 
auslösen können.

Ist der PIN Change Interrupt erst mal ausgelöst musst du ihn wieder 
abschalten.
1
PIN_CHANGE_INT2:
2
  in    COPY_SREG, SREG      ; CPU-Status sichern
3
  push  r16
4
  lds   r16, (PCMSK2)
5
  cbr   r16, (1<<TWI_SDA)
6
  sts   (PCMSK2), r16       ; Pin Change Interrupt disabled
7
  lds   r16, (TWI_STATE)    
8
  cbr   r16, (1<<TWI_busy)  ; lösche busy Flag TWI FLAG Register
9
  sbr   r16, (1<<TWI_ready) ; setze ready Flag TWI FLAG Register
10
  sts   (TWI_STATE), r16
11
  pop   r16
12
  out   SREG, COPY_SREG
13
  reti

Da hat man dann zwar noch eine Leitung mehr, aber leider sah ich auch 
keine andere Möglichkeit wenn man nicht warten will. Und außerdem weißt 
du ja nicht genau wie lange man warten muss. Da kann ja immernoch irgend 
ein anderer Interrupt das ganze verlängern.

Gruß Steffen
Gast #2870342
Lesenswert?

Hallo,

nachdem
>  while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
nichts hilft (geht eigentlich immer) sehe ich jetzt nur noch zwei 
Möglichkeiten:

1. Du rufst dein TWIM_Stop bereits vor Ende der gerade laufenden
   TWI-Aktion auf (TWINT ist noch nicht gesetzt) oder

2. deine Hardware hat wircklich zu weiche Pull-Ups
   (hab schon gesehen dass deswegen Aktionen nicht zu Ende kommen)


Übrigens:
Die while-Schleife
>  while (TWCR & (1<<TWINT));
nach einem vorhergehenden Reset von TWINT (!!!) durch
>  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
macht keinen Sinn da TWINT ja immer 0
Das kannst du auch gleich weglassen !
#2870352
Lesenswert?

Stefan++ schrieb:
> nachdem
>>  while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
> nichts hilft (geht eigentlich immer) sehe ich jetzt nur noch zwei
> Möglichkeiten:
>
> 1. Du rufst dein TWIM_Stop bereits vor Ende der gerade laufenden
>    TWI-Aktion auf (TWINT ist noch nicht gesetzt) oder
>
> 2. deine Hardware hat wircklich zu weiche Pull-Ups
>    (hab schon gesehen dass deswegen Aktionen nicht zu Ende kommen)

Also auf TWSTO im TWSR zu warten bringt schonmal gar nichts. Denn da 
musst du dein TWSTO-Bit setzen um einen STOP ausführen zu lassen.

Wenn du Zustände des HW-TWI abfragen willst, dann musst du schon das 
TWI-Statusregister befragen. Und das ist TWSR. Allerdings gibt es da 
keinen Status für "TWI STOP ausgeführt".

Steffen
Gast #2870374
Lesenswert?

Hallo,

also heutzutage kann doch jeder lesen oder?

siehe Datenblatt:
• Bit 4 – TWSTO: TWI STOP Condition Bit
Writing the TWSTO bit to one in Master mode will generate a STOP 
condition on the 2-wire
Serial Bus. When the STOP condition is executed on the bus, the TWSTO 
bit is cleared automatically.

und
• Bit 7 – TWINT: TWI Interrupt Flag
This bit is set by hardware when the TWI has finished its current job 
and expects application software response.
....
Flag must be cleared by software by writing a logic one to it. Note that 
this flag is not automatically cleared by hardware when executing the 
interrupt routine. Also note that clearing this flag starts the 
operation of the TWI, so all accesses to the TWI Address Register 
(TWAR), TWI Status
Register (TWSR), and TWI Data Register (TWDR) must be complete before 
clearing this flag.
Gast #2870483
Lesenswert?

Stefan++ schrieb:
> 1. Du rufst dein TWIM_Stop bereits vor Ende der gerade laufenden
>    TWI-Aktion auf (TWINT ist noch nicht gesetzt) oder

Jein - eigentlich nicht.. ;-)
Einzige Ausnahme: Wenn etwas auf dem Bus schief geht. An diese 
Möglichkeit habe ich jedoch auch schon gedacht und vor jedem TWIM_Stop() 
der Fehlerroutinen eine UART-Ausgabe mit einem eindeutigen Buchstaben 
eingefügt.
So kann/konnte ich über das Logfile sehen, welche Unterroutinen der 
Reihe nach aufgerufen worden. (=> keine Einzige Fehlerroutine, da alles 
korrekt abläuft..)

> 2. deine Hardware hat wircklich zu weiche Pull-Ups
>    (hab schon gesehen dass deswegen Aktionen nicht zu Ende kommen)

Habe ich auch gerade mit 1k8 getestet - keine Veränderung.. :-(
(außer der Verlustleistung :-b)
Gast #2870643
Lesenswert?

Hallo,

ich bin noch immer darüber verwundert dass die Warteschleife
>  while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
bei dir nicht funktioniert

Ich verwende sie in all meinen Programmen und sie geht immer!
Überigens haben die LPC214x das gleiche TWI-Modul. Modul, Register, Bits 
etc. heissen nur anders und sind auch etwas anders anzusprechen aber 
funktionieren gleich. Die "State-Machine" ist absolut die gleiche.

Blöde Frage:
TWCR ist schon volatile (???), unter Umständen optimiert der Compiler 
die ständig neue Abfrage weg und dein WDT schlägt zu.
Bitte überprüf das mal, auch im List-file, was der Compiler da macht.

Ansonsten kann man aus der Ferne ohne genaue Code-Einsicht nicht mehr 
sagen.

Gruss Stefan++
#2870676
Lesenswert?

Stefan++ schrieb:
> Blöde Frage:
> TWCR ist schon volatile (???), unter Umständen optimiert der Compiler
> die ständig neue Abfrage weg
Wirklich blöde Frage. TWCR ist ein Hardwareregister. Wie/Was soll da 
volatile gemacht werden? Das ist doch keine Variable..

@ Techniker
Versuch mal bitte diesen Code Schnipsel.
1
void TWIM_Stop (void)
2
{
3
  TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
  while ((SCL_LINE && SDA_LINE));
5
}


Gruß Steffen
Gast #2870690
Lesenswert?

Was hast du gegen Stefan

was hinter einem Bezeichner wie TWCR steckt weiss der Compiler erst wenn 
es ihm einer sagt.

Das gleiche gilt für deine SCL_LINE und SDA_LINE. Die kennt der Compiler 
auch erst nach einer entsprechenden Deklaration. Wo ist die?

Karlheinz
Gast #2870695
Lesenswert?

Steffen H. schrieb:
> Wirklich blöde Frage. TWCR ist ein Hardwareregister. Wie/Was soll da
> volatile gemacht werden? Das ist doch keine Variable..

Doch, es ist eine Variable an einer festen Adresse. Und es ist der 
klassische Fall für volatile, ihr Wert kann und wird sich außerhalb des 
aktuellen Software Kontext ändern. Zwar nicht durch einen anderen 
Software Kontext aber durch die Hardware.

MfG Klaus
Gast #2871006
Lesenswert?

Steffen H. schrieb:
> Versuch mal bitte diesen Code Schnipsel.
>
1
> void TWIM_Stop (void)
2
> {
3
>   TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
>   while ((SCL_LINE && SDA_LINE));
5
> }
6
>
>

Warum soll ich endlos warten, wenn SCL und SDA high sind?

Dann überspringe ich doch den "Fehler" und sollte es doch einmal passen, 
bleibt das Programm hängen..?!?

Oder übersehe ich da etwas?
Gast #2871027
Lesenswert?

Stefan++ schrieb:
> ich bin noch immer darüber verwundert dass die Warteschleife
>>  while (TWCR & (1<<TWSTO));     // warte bis STOP ausgeführt
> bei dir nicht funktioniert
>
> Ich verwende sie in all meinen Programmen und sie geht immer!

Nicht nur du - aber wie gesagt, das Flag zeitgleich mit dem INT-Flag 
zurückgesetzt und das Stopp ist zu dem Zeitpunkt noch nicht ausgegeben 
(erst ~5us später).

Zum verifizieren habe ich jetzt noch ein neues Projekt angelegt, bei dem 
es nur den 24C04 und einen LM75 gibt. Gleiches verhalten, wenn direkt 
nach dem Schreiben der LM75 angesprochen wird. Baue ich zwischen den 
beiden Zugriffen (EEP und LM75) ein Delay von 10us ein läuft es. Dadurch 
schließe ich "Scheinfehler" durch die restliche Software aus - obwohl 
diese mangels IRQ's sowieso nicht existieren können. Für diesen Test 
habe ich sogar einen nagelneuen Mega128 verwendet - man weiß ja nie.. 
;-)

Kann es sein, dass du bei deiner Anwendung evtl. nicht direkt 
nacheinander den Bus belegst und dadurch den Fehler nicht bemerkst?
#7447064
Lesenswert?

Karlheinz schrieb:

Die kennt der Compiler auch erst nach einer entsprechenden Deklaration. Wo ist die?

Gäbe es keine, dann würde der Compiler gar nicht übersetzen. Offensichtlich HAT er aber übersetzt. ;-) (Es geht ja hier nicht um eine Sprache für Web-Skripts wie etwa JavaScript, Python usw., wo der Typ erst zur Laufzeit in manchmal unerwarteter Weise "erraten" wird).

Benutzt man ein vernünftiges Entwicklungssystem (Arduino, AVR Studio, PlatformIO, ...) dann enthält das eine oder mehrere Dateien mit sämtlichen für die jeweilige Hardware benötigten Deklarationen und zeigt die auch an ("IntelliSense"). Einschließlich "const" oder "volatile", wo sinnvoll.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren