MoinMoin,
Meine interruptgesteuerte TWI-Routine läuft nun. Siehe Quelltext.
Hierzu hab ich eine Frage. Meine ganzen TWI_Puffer und alles was mit dem
TWI zu tun, hab ich als globale Variablen definiert. Ich sehe halt
keinen anderen Weg, meine Daten in den Interrupt zu bekommen. Kennt von
euch jdm einen?
Auch in der Appnote (AVR315) wird von Pointern gesprochen, die man der
Statemachine übergeben soll... Wie bekomm ich den denn in den Interrupt?
MfG Chaos
P.S. auch die Suche hier im Forum brachte nur ungelöste Beiträge von vor
frischestens 4 Jahren.
> Wie bekomm ich den denn in den Interrupt?
Machst du doch schon (unbewusst?) mit den Variablen TWI_SendBuffer und
TWI_ReadBuffer: Globaler Pointer, auf den du in der ISR zurgreifst.
Denk' nur dran, alle Variablen, die du in der Interruptroutine
veränderst, als 'volatile' zu deklarieren, sonst sind merkwürdige Fehler
vorprogrammiert.
Um welche Datenmengen handelt es sich? In vielen Fällen sind I2C Daten
nur wenige Bytes gross. Da kann es auch sinnvoll sein, einen statischen
Puffer an Stelle von Pointern zu verwenden.
A. K. schrieb:> Um welche Datenmengen handelt es sich? In vielen Fällen sind I2C Daten> nur wenige Bytes gross. Da kann es auch sinnvoll sein, einen statischen> Puffer an Stelle von Pointern zu verwenden.
Bisher handelt es sich um eher kleine Mengen. Darum hab ich meinen
Sendbuffer auch nur mit 10Byte, und den ReadBuffer mit 40 Byte
initialisiert. Meinst du das mit statisch? Oder noch sowas wie "const"
oder "static"? Aber als const könnte ich doch nichts mehr an den Puffer
ändern?
J. T. schrieb:> Meinst du das mit statisch? Oder noch sowas wie "const"> oder "static"?
Globale Daten sind statisch, d.h. permanent vorhanden und werden mit
fester Adresse abgesprochen. Lokale Daten (ohne "static") hingegen haben
keine feste Dauer und keine feste Adresse.
Es geht um etwas anderes. 'Statisch' im Gegensatz zu 'dynamisch
allokiert'. Denn warum sollte man sonst Pointer verwenden wollen?
Alles was an Daten aus einer ISR rein oder raus geht, muss in
irgendeiner Form über globale Variablen laufen. Ob so ein Datenarray
statisch global allokiert wird, oder ob es zb in main() dynamisch
allokiert wird und es dann einen globalen Pointer darauf gibt, ändert
daran wenig. Irgendetwas muss global sein. Nur dass letzterer Fall
(dynamische Allokierung + Pointer) komplexer ist als eine simple
statische Allokierung.
Wie immer gibt es Ausnahmen. Zb wenn die Buffergrösse zur Laufzeit
veränderbar sein soll, oder wenn man nur Abschnittweise den Buffer
benötigt und man den wegen Speicherverbrauchs nicht statisch allokieren
will.
Rainer B. schrieb:> Denk' nur dran, alle Variablen, die du in der Interruptroutine> veränderst, als 'volatile' zu deklarieren, sonst sind merkwürdige Fehler> vorprogrammiert
Auch globale Variablen? Ich warte ja in meiner TWI-Send/Weite darauf,
daß der vorherige Schreib/Lesevorgang beendet ist, bevor ein neuer
beginnen kann. Wobei das irgendwie den Sinn von den Interrupts
auffrisst. Evtl die Puffer als Ringpuffer implementieren?
Ausserdem frage ich mich, ob der TWI-Watchdog überhaupt notwendig ist.
Eigentlich werden ja schon alle Fehler die auftreten können von der
Statemachine abgefangen
J. T. schrieb:> Auch globale Variablen?
Ja.
> Ausserdem frage ich mich, ob der TWI-Watchdog überhaupt notwendig ist.> Eigentlich werden ja schon alle Fehler die auftreten können von der> Statemachine abgefangen
Auch Timeouts? Die Implementierung mit Zählschleife ist aber
zweifelhaft.
> Evtl die Puffer als Ringpuffer implementieren?
Das hängt von der Anwendung ab. I2C wird meist mit irgendwelchen Geräten
im Request-Response-Done Schema verwendet. Da ergibt ein Ringpuffer
wenig Sinn.
Karl H. schrieb:> Es geht um etwas anderes. 'Statisch' im Gegensatz zu 'dynamisch> allokiert'. Denn warum sollte man sonst Pointer verwenden wollen?
Ok, das hilft mir schonmal weiter. Und ja genauso hab ichs mir auch
gedacht. Die Idee das ganze in der Main mit einem eigenen Puffer zu
machen und dann den globalen Zeiger drauf zeigen lassen, kam ich auch
schon, aber gestern Abend wollten mir keine Vorteile dessen mehr
einfallen, und darum hab ichs erst mal gelassen.
J. T. schrieb:> Wobei das irgendwie den Sinn von den Interrupts auffrisst.
Solche I2C Operationen, bei denen man im Hauptprogramm ohnehin drauf
wartet, kann man auch ohne Interrupts implementieren. Man sollte dann
aber genau drauf achten, dass der Code bei unerwarteten Reaktionen nicht
einfach hängenbleibt. In einer von der Hardware geführten Statemachine
ist das mitunter einfacher.
A. K. schrieb:> Auch Timeouts? Die Implementierung mit Zählschleife ist aber zweifelhaft
Hab ich so gemacht, ohne groß drüber nachzudenken. Einfach nen größeren
Prescaöer auf den Timer? Weil mit diesen Einstellungen kam der WD
immerhin schonmal bis 4 beim Durchsteppen.
Wie implementiert man sowas denn "normalerweise"?
Das ist im Prinzip in Ordnung so, wenn die globalen Variablen in der
Datei bleiben, die sich mit TWI beschäftigt, und nicht an den ganzen
Rest der Software exportiert werden.
Es fehlt aber "volatile" an allen Variablen, die sowohl im Interrupt als
auch im normalen Programm benutzt werden. Alternativ kannst du auch
Speicherbarrieren (memory barrier) verwenden.
Wenn du das nicht machst, wirst du in höheren Optimierungsstufen
seltsame Fehler bekommen. Z.B. kann der Compiler dann Werte nur einmal
aus der globalen Variablen in ein Register lesen und nicht mehr merken,
dass der Interrupt den Wert ändert. Oder er ändert die Reihenfolge von
Zuweisungen, so dass z.B. das Flag für gültige Daten gesetzt sein kann,
bevor die Daten wirklich da sind.
Juergen schrieb:> Wenn du das nicht machst, wirst du in höheren Optimierungsstufen> seltsame Fehler bekommen
Ja das wird auch noch interessant werden. Noch bin ich auf O-0, da er
bei O-1 schon so viel wegoptimierte, das ich die ISR nicht mehr
vernünftig Durchsteppen konnte.
Aber nun läufts so weit, da kann man mal anfangen mit optimieren.
Juergen schrieb:> Es fehlt aber "volatile" an allen Variablen, die sowohl im Interrupt als> auch im normalen Programm benutzt werden. Alternativ kannst du auch> Speicherbarrieren (memory barrier) verwenden.
Das interessiert mich jetzt aber: wie ersetzt man ein volatile durch
eine Speicherbarriere? Bitte mit praktischem Beispiel.
Macht die Memory Barriere nicht nur, dass alle Load/Store Befehle
garantiert abgeschlossen werden, bevor der Befehl danach ausgeführt
wird?
Wenn der Compiler aber die Variable gar nicht aus dem Speicher holt
bringt es nix.
Jan K. schrieb:> Wenn der Compiler aber die Variable gar nicht aus dem Speicher holt> bringt es nix.
Eben.
IMHO ist das nämlich ein ganz anderes paar Schuhe.
Ich hab auch schon mit Compiler-Barrieren experimentiert und
erstaunlicherweise bewirken so Sachen wie
__asm__("":::"memory");
(das ist aber keine memory-Barriere im wirklichen Sinne) daß er dann
plötzlich ein wegoptimiertes
if (nonVolatileGobal) {...}
plötzlich wieder hervorholt und auch brav jedesmal die Variable neu aus
dem Speicher liest aber mir ist unwohl dabei. Ich bin mir nicht sicher
ob er dann wirklich immer davon ausgeht daß diese leere(!) asm-Anweisung
den kompletten Speicher als vermuteten Seiteneffekt hat und das somit
für nonVolatileGlobal ebenfalls annimmt, ob ich mich auf sowas wirklich
verlassen kann.
Nocheinmal die Frage, ob der Watchdog überhaupt notwendig ist. Wenn ein
Slave nicht antwortet, weil er nicht am Bus oder gestorben oder sonst
was ist, dann kommt ein NACK zurück --> Fehlerbehandlung
Eigentlich kommt doch in jedem Fehlerfall ein NACK zurück? Ausser
irgendwelchen seltsamen Übertragungsfehlern, aber die erkennt das TWSR
ja nicht, dafür müsste man wohl irgendeine Form von Prüfsumme mit
übertragen.
Also auch keine Baustelle für nen Watchdog.
Könnt ihr mir nen Fehlerfall nennen/konstruieren, in dem der Watchdog
wirklich notwendig wäre?
irgendwie sendet er keine Stopcondition. Wenn ich es durchsteppe (auf
dem Chip und nicht im Simulator) springt er zwar den else an, aber auf
dem LA kommt keine Stopcondition. Auch wenn ich den if-Fall nur bis
DataSize-2 anspringen lasse, empfängt er trotzdem 12Byte, springt beim
durchsteppen den else-Fall an aber der Stop wird einfach nicht
gesendet??
Weiß dazu evtl wer was zu zu sagen?
Hm... wahr wohl ein Fehlalarm... Oder ich oder mein LA hatte n Knick in
der Linse beim Zählen :D
Ich hab jetzt mal den Buffer geleert vor dem Auslesen, dann bleiben die
letzten Werte auch auf 0. Auch das Statusregister zeigt an, das es
wieder bereit ist.
Wobei auf dem LA, auch wenn ich auch DataSize-2 schalte nach dem 11ten
statt dem 12ten, ein ACK kommt. Eigentlich müsste da doch aber ein Stop
und somit NACK sein?
J. T. schrieb:> Hab ich so gemacht, ohne groß drüber nachzudenken.
Zählschleifen darf der Compiler ersatzlos entfernen, wenn aus seiner
Sicht wirkungslos. Zeitverbrauch ist aus seiner Sicht keine Wirkung.
> Wie implementiert man sowas denn "normalerweise"?
In manchen Programmierumgebungen existieren fertige Delay-Funktionen.
Für Timeouts kann man einen System-Tick verwenden. In vielen Fällen habe
ich im Programm einen Timer, der alle soundsoviel Millisekunden
interrupted. Der kann für Tastenentprellung sein, irgendwelche Schedules
erledigen, und was auch immer.
Dessen ISR zählt nebenbei eine Variable hoch, die so typisiert ist, dass
sie bis zu nächsten Restart des Controllers nicht überlauft (geht auch
anders, ist so aber einfacher). Der Code sieht dann ungefähr so aus:
1
uptime_ttimeout=uptime()+TIMEOUT;
2
while(...){
3
...
4
if(uptime()>timeout)
5
...timeout...
6
}
7
}
> Eigentlich kommt doch in jedem Fehlerfall ein NACK zurück?
Warum?
A. K. schrieb:> Zählschleifen darf der Compiler ersatzlos entfernen, wenn aus seiner> Sicht wirkungslos. Zeitverbrauch ist aus seiner Sicht keine Wirkung.
Schnelle Abhilfe: Zählvariable als volatile definieren.
Schreibzugriffe auf Volatile sind per Definition eine solche Wirkung
(observable behavior) und müssen daher erfolgen, und zwar in exakt der
selben Anzahl und Reihenfolge wie es im Quelltext steht.
> Ausserdem frage ich mich, ob der TWI-Watchdog überhaupt notwendig ist.> Eigentlich werden ja schon alle Fehler die auftreten können von der> Statemachine abgefangen
Ja, Eigentlich werden alle Fehler abgefangen, aber im praktischen
hatte ich es schon das das ganze hängen geblieben ist.
Zur Praktischen Implementierung:
Ich gehe mal davon aus das das TWI ständig genutzt wird.
Zähle eine globle Variable im Timer-IRQ runter.
Wenn die dort bei 0 angekommen ist mach einen
Init vom TWI + Alle dazugehörenden (globalen)
Variablen. Im TWI IRQ setzt du dann die Variable
immer auf einen festen Wert.
Wenn das TWI hängt gibt es keine IRQs mehr und
der Timeout läuft auf 0 was ein Init vom TWI auslöst.
Wie lange das dauern soll musst du selbst festlegen.
Und wegen der Pointer Geschichte:
IRQs sollen schnell bearbeitet werden, Doppelte
indirekte Adressierungen (Pointer auf Array[i])
haben dagegen einen höheren Rechenaufwand.
Zum Code:
Du fängst den TWI-Statuscode $00 nicht ab.
Dein Buffer ist 40 Byte gross. Wieso willst du 65535
Bytes addressieren können?
uint16_t TWI_SendCnt = 0;
uint16_t TWI_ReadCnt = 0;
uint8_t reicht, Ausserdem fehlt volatile.
Besser währe es die als
static uint8_t TWI_SendCnt;
static uint8_t TWI_ReadCnt;
im IRQ zu definieren, da du ausserhalb vom IRQ nicht
darauf zugreifst.
uint16_t TWI_DataSize = 0;
uint8_t reicht, volatile fehlt.
Generell fehlen viel volatile.....
A. K. schrieb:>> Eigentlich kommt doch in jedem Fehlerfall ein NACK zurück?>> Warum
Weil wenn kein Fehler vorliegt ein ACK kommt. Das Ausbleiben selbigen
ist doch schon das NACK?
A. K. schrieb:> Für Timeouts kann man einen System-Tick verwenden. In vielen Fällen habe> ich im Programm einen Timer, der alle soundsoviel Millisekunden> interrupted. Der kann für Tastenentprellung sein, irgendwelche Schedules> erledigen, und was auch immer.>> Dessen ISR zählt nebenbei eine Variable hoch, die so typisiert ist, dass> sie bis zu nächsten Restart des Controllers nicht überlauft (geht auch> anders, ist so aber einfacher). Der Code sieht dann ungefähr so aus:
Genauso hab ich es doch implementiert?
Bernd K. schrieb:> Schnelle Abhilfe: Zählvariable als volatile definieren.> Schreibzugriffe auf Volatile sind per Definition eine solche Wirkung> (observable behavior) und müssen daher erfolgen, und zwar in exakt der> selben Anzahl und Reihenfolge wie es im Quelltext steht
Inzwischen sind nun alle globalen Variablen volatile. Ich kann wenn ich
wieder zu haus bin, ja nochmal ne aktuelle Version hoxhladen
Tim schrieb:> Du fängst den TWI-Statuscode $00 nicht ab.> Dein Buffer ist 40 Byte gross. Wieso willst du 65535> Bytes addressieren können?> uint16_t TWI_SendCnt = 0;> uint16_t TWI_ReadCnt = 0;> uint8_t reicht, Ausserdem fehlt volatile. Besser währe es die als> static uint8_t TWI_SendCnt;> static uint8_t TWI_ReadCnt;> im IRQ zu definieren, da du ausserhalb vom IRQ nicht> darauf zugreifst
Die volatiles sind nun nachgelgt. Den Contergan hab ich 16 IT breit
gemacht falls ich mal mehr als nur ein paar Byte lesen/schreiben will.
Bspw wenn man Speicherbausteine per I2C anspricht. Das war schon bewusst
so gemacht.
LOL
Der letzte Beitrag war vom Handy geschrieben... Mit Contergan hab ich
nix am Hut.
Ohne Textcontergan(Autokorrektur) hätte das hier:
J. T. schrieb:> Den Contergan hab ich 16 IT breit> gemacht
eigentlich so lauten sollen:
Den Counter hab ich 16 Bit breit gemacht.
Tim schrieb:> Du fängst den TWI-Statuscode $00 nicht ab.
Danke, den hatte ich übersehen. Warum verstecken die den auch ganz am
Ende der Tabelle. Ich hatte nur die MT und MR abgearbeitet, gesehen oh
hab hier kommen die Slavegeschichten, dann bin ich ja fertig... :D
So hier nun nochmal die aktuelle Version, als .c und .h. Dazu noch eine
kleine Frage. Warum klappt es, wenn ich in der main.c die .h UND .c
includiere, aber wenn ich die .c IN der .h indluciere, und in der main.c
dann nur die .h, klappt es nicht? Dann meckert er, er würde die globalen
Variablen nicht finden, bzw das sie undefiniert sind. Und die Funktionen
implizit deklariert sind. Was es halt so mit sich bringt, wenn die
Sachen die in der .c stehen, nicht da sind.
Er ist übrigens der AVR_GCC im Atmel Studio 6.2
MfG Chaos
Tim schrieb:> Besser währe es die als> static uint8_t TWI_SendCnt;> static uint8_t TWI_ReadCnt;> im IRQ zu definieren, da du ausserhalb vom IRQ nicht> darauf zugreifst.
Ich glaub, das mit dem static versteh ich irgendwie doch noch nicht
ganz.
Ich dachte, das sagt dem Compiler, dass die Variable sich nicht ändert?
Sollte die nicht besser als volatile innerhalb der ISR erzeugt werden?
Wobei man dann wieder Vorkehrungen treffen müsste, dass sie nicht
jedesmal beim Anspringen der ISR neu initialisiert wird?
1
ISR(TWI_vect)
2
{
3
volatileuint8_tReadCnt=0;
4
.
5
.
6
.
7
casefoo:
8
...
9
ReadCnt++;
10
break;
11
12
casebar:
13
...
14
ReadCnt++;
15
break;
16
}
ReadCnt würde doch jedesmal vor dem switch auf 0 gesetzt?
Und mit static würde sie sich garnicht verändern?
> jedesmal beim Anspringen der ISR neu initialisiert wird?
und genau das verhinderst Du mit "static"
> Und mit static würde sie sich garnicht verändern?
nein, Du verwechselst "static" mit "const"
Mit static wird die Variable nur einmal initialisiert und die Variable
behält nach dem Verlassen der Funktion weiterhin ihren Wert.