Forum: Mikrocontroller und Digitale Elektronik Eine interruptgesteuerte TWI/I2C Routine und eine Frage dazu. Pointer/Variable an IRQ?


von J. T. (chaoskind)


Angehängte Dateien:

Lesenswert?

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.

von Rainer B. (katastrophenheinz)


Lesenswert?

> 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.

von (prx) A. K. (prx)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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

von (prx) A. K. (prx)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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"?

von Juergen (Gast)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von Jan K. (jan_k)


Lesenswert?

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.

von Bernd K. (prof7bit)


Lesenswert?

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.

von J. T. (chaoskind)


Lesenswert?

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?

von J. T. (chaoskind)


Lesenswert?

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?

von J. T. (chaoskind)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

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?

von Bernd K. (prof7bit)


Lesenswert?

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.

von Tim (Gast)


Lesenswert?

> 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.....

von J. T. (chaoskind)


Lesenswert?

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

von J. T. (chaoskind)


Lesenswert?

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.

von J. T. (chaoskind)


Angehängte Dateien:

Lesenswert?

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

von J. T. (chaoskind)


Lesenswert?

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?

von Andreas W. (geier99)


Lesenswert?

> 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.

von J. T. (chaoskind)


Lesenswert?

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
Noch kein Account? Hier anmelden.