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?
Doch noch ein kleines Problemchen....
1 | case TW_MR_DATA_ACK: |
2 | |
3 | TWI_Watchdog = 0; |
4 | |
5 | if (TWI_ReadCnt < (TWI_DataSize - 1)) |
6 | {
|
7 | TWI_ReadBuffer[TWI_ReadCnt] = TWDR; |
8 | TWI_ReadCnt++; |
9 | TWCR = (1 << TWINT) | (1 << TWEA) | (1 << TWEN) | (1 << TWIE); |
10 | }
|
11 | else
|
12 | {
|
13 | TWI_ReadBuffer[TWI_ReadCnt] = TWDR; |
14 | TWI_Flags = 0; |
15 | TWI_ReadCnt = 0; |
16 | TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN) | (1 << TWIE); |
17 | }
|
18 | break; |
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_t timeout = 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 | volatile uint8_t ReadCnt = 0; |
4 | .
|
5 | .
|
6 | .
|
7 | case foo: |
8 | ...
|
9 | ReadCnt++; |
10 | break; |
11 | |
12 | case bar: |
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.
Ahhhh das erklärt einiges! Danke dafür.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.