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
voidTWIM_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..?!? :-(
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:
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
> 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). :-/
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
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. :-(
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
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..? :-(
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
voidTWIM_Stop(void)
2
{
3
TWCR=(1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
4
while(TWCR&(1<<TWINT));
5
}
6
7
voidTWIM_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?
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
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 !
Steffen H. schrieb:> Da hat man dann zwar noch eine Leitung mehr, aber leider sah ich auch> keine andere Möglichkeit wenn man nicht warten will.
Muss mein eigenen Blödsinn korrigieren! Man braucht keine zusätzliche
Leitung. Man nimmt einfach den PIN Change Interrupt - Pin des SDA-Pins
dazu :-)
Steffen
Der Techniker schrieb:> Oder weiß noch jemand eine elegantere Lösung?
Nach einem Stop sollte der Bus Idle sein. Kann man irgendwie testen, ob
der Bus Idle ist?
MfG Klaus
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
Stefan++ schrieb:> Datenblatt nicht gelesen was !!!
Ich schon :-) Und du?
Lass euch bitte nicht täuschen von dem *TWINT*-Flag. Das ist wirklich im
TWCR Register enthalten und signalisiert, dass eine TWI Aktion
ausgeführt wurde. Allerdings weißt du erst über das Statusregister
TWSR was passiert ist.
Nichts für Ungut
Steffen
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.
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)
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++
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.
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
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
Der Techniker schrieb:> Habe ich auch gerade mit 1k8 getestet - keine Veränderung.. :-(> (außer der Verlustleistung :-b)
Da bellst du auch den falschen Baum an. Auf dem Scope-Bild ist ganz klar
zu sehen, daß auch mit den alten Werten als Pullups die Signale in
Ordnung sind.
MfG Klaus
>
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?
Klaus schrieb:> Da bellst du auch den falschen Baum an. Auf dem Scope-Bild ist ganz klar> zu sehen, daß auch mit den alten Werten als Pullups die Signale in> Ordnung sind.
Das dachte ich mir auch - aber ich bin ja für Anregungen jederzeit
offen.. ;-)
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?
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.
Rolf schrieb:> Gäbe es keine, dann würde der Compiler gar nicht übersetzen.> Offensichtlich HAT er aber übersetzt. ;-)
Meinst Du, daß das nach ganzen ELF JAHREN noch irgendwen interessiert?