Forum: Compiler & IDEs Interrupt Fehlerquellen - FIFO Buffer


von chrysator (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Leute

Ich habe viel gesucht im Forum und im Tutorial aber finde nichts genaues 
zu meinen Fragen. Ich habe einen FIFO-Buffer Programmiert, der durch 
Interrupts Inputs in einem Struct ablegt und diese, wenn er Zeit hat, im 
Main abarbeitet.
Das ganze sieht schon sehr gut aus bei Tests mit dem Oszi, eigentlich 
genau das was ich wollte. Da Der Buffer allerdings eine heikle Aufgabe 
übernehmen soll möchte ich wirklich alles sauber programmieren.
Ich hab das ganze C-File angehängt, werde allerdings einige Stellen noch 
"zitieren", da vermutlich nicht alle den ganzen Code lesen wollen.

Meine Probleme: Im Tutorial steht ja, dass bei Variablen mit mehr als 
einem Byte, die Interrupts zu Problemen führen können. Wann passiert 
das? Immer wenn im main eine Variable mit mehr als einem Byte 
geschrieben wird? Oder nur wenn diese Variable dann durch die ISR 
verändert wird?

Ich habe ein Bitfeld, also ein Struct (eigentlich ein Array aus 
structs), welches eine Komponente time beinhaltet(Buffer[].time), die 2 
Byte gross ist. Diese werden nur in den ISR Routinen geschrieben. Im 
main werden sie nur gelesen und ihr Wert wird einer neuen Variable 
(wait_time) zugewiesen. Diese wird nur im main verwendet
Muss ich da jetzt irgendwo die Interrupts aus- und wieder einschalten, 
damit nicht Fehler geschehen bei ISR unterbrüchen? Oder kann man durch 
strikte Trennung von main-Variabeln und ISR-Variabeln das Problem lösen?

Eine weitere Frage wäre, wie ist es bei if abfragen? Wenn ich das mache:

58:
1
 if(wait_time == 0 && Buffer[index_read].sensor_answer == 0)

wait_time ist eine uint16_t und Buffer[].sensor_answer ist 1 Bit gross!

Wenn jetzt während dieser if Abfrage ein Interrupt ausgelöst wird, wie 
wirkt sich das aus? Kann das Programm dann ohne weitere Probleme wieder 
weitermachen?
Sagen wir mal er prüft ob wait_time 0 ist, bekommt true zurück --> 
Interrupt unerbricht genau hier
--> Interrupt ist fertig Buffer[].sensor_answer ist auch 0 (true && true 
also sollte er die Schleife durchlaufen.
Geht das?

Vielen Dank für jede Hilfe

Chrysator

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Innerhalb des Interupts ist alles "ok", in der "main" solltest du die 
Interupts für die Zeit in der du Variablen liest welche in der ISR 
verändert werden sperren.
Gleiches gilt natürlich wenn du ISR relevante Variablen änderst.
Wie weit diese Sperre reichen muss hängt dann von deiner Programlogik 
ab, am besten ist es, wenn du dann häufige Operationen in Funktionen 
auslagerst, welche sich um das speren der ISR kümmern, so kann es nicht 
vergessen werden.

von Oliver (Gast)


Lesenswert?

Das Problem ist einfach zu beschreiben:

Der Zugriff auf Variable, die aus mehr als einem Byte besteht, und auf 
die sowohl im Hauptprogramm als auch in der ISR zugeriffen wird, muß im 
Hauptprogramm atomar erfolgen.

Der Hintergund ist einfach: Ein Lese- oder Schreibzugriff erfogt 
naturgemäß in mehreren Bytezugriffen auf den Speicher. Wenn der 
Interrupt genau zwischen diesen Zugriffen eines Schreibzugriffs 
zuschlägt, steht in einem Teil der Bytes schon der neue Wert, während in 
dem restlichen Bytes noch die alten Werte stehen. Das Ergebnis ist dann 
ziemlicher Murks.

Oliver

von chrysator (Gast)


Lesenswert?

Ok aber eine der aufgaben meines buffers ist es möglichst genau die zeit 
zwischen den interrupts zu "messen" damit diese pausen auch beim 
abarbeiten eingehalten werden (also damit der output die gleiche 
frequenz hat wie der intuput)
dies wird immer ungenauer je mehr ich die interrupts aus und wieser 
einschalte oder?
gibts noch ne effizientere lösung?

von Peter D. (peda)


Lesenswert?

chrysator schrieb:
> dies wird immer ungenauer je mehr ich die interrupts aus und wieser
> einschalte oder?

Interrupts sind nie zyklengenau.
Ein atomarer Zugriff auf ne 32Bit Variable sind etwa 6 Zyklen 
Interruptsperre, also Pillepalle.
Und wenn Du noch andere Interrupts hast, dann verzögern diese auch um 
etwa 50..100 Zyklen.
Für ne RISC-CPU sind 100 Zyklen nichts.

Alles unter 1000 Zyklen sollte keine Rolle spielen. Sonst mußt Du Deinen 
Programmablauf überarbeiten oder ne CPU mit Prioritätsleveln nehmen.


Peter

von Oliver (Gast)


Lesenswert?

chrysator schrieb:
> möglichst genau

Genau ist relativ. Wenn es tatsächlich auf jeden einzelnen Taktzyklus 
ankommt, stört das natürlich. Dann ist allerdings dein ganzer Ansatz 
nicht geeignet, denn die benötigte ISR-Einsprungzeit ist ja auch nicht 
konstant.

Oliver

von chrysator (Gast)


Lesenswert?

@Oliver

Nein, so genau muss das nicht sein aber es wäre schon nicht schlechtm, 
Das ganze soll nämlich für eine CNC-Fräse den Output des EMC (Linux-CNC) 
Buffern, damit ich "regeln" kann. Das bedeutet, dass die Frequenz des 
Inputs die Vorschubsgeschwindigkeit ist. Die muss nicht exakt 
reproduziert werden aber sollte schon einigermassen gleich sein.

U

Oliver schrieb:
> Der Zugriff auf Variable, die aus mehr als einem Byte besteht, und auf
> die sowohl im Hauptprogramm als auch in der ISR zugeriffen wird, muß im
> Hauptprogramm atomar erfolgen.

versteh ich das richtig, wenn ich atomar auf diese Variabeln zugreife, 
muss ich die Interrupts nicht ausschalten?
Das könnte eventuell meine gesuchte Lösung sein.
Wie mache ich das in einer if abfrage?

Danke Chrys

von Karl H. (kbuchegg)


Lesenswert?

chrysator schrieb:

> Nein, so genau muss das nicht sein aber es wäre schon nicht schlechtm,
> Das ganze soll nämlich für eine CNC-Fräse den Output des EMC (Linux-CNC)
> Buffern, damit ich "regeln" kann. Das bedeutet, dass die Frequenz des
> Inputs die Vorschubsgeschwindigkeit ist. Die muss nicht exakt
> reproduziert werden aber sollte schon einigermassen gleich sein.

Ähm.
Wir reden hier von Mykro-Sekunden. Millionstel Sekunden.

Mach lieber dein Programm erst mal fehlerfrei und fange die ganzen Array 
Overflows ab bzw. kümmere dich um eine saubere Struktur, ehe du dir 
darüber sorgen machst, dass das Sperren der Interrupts für ein paar 
Taktzyklen dir das Timing versaut.


Im Ernst. Wenn dir das Kopfzerbrechen macht, dass ein Interrupt ein paar 
µSekunden zu spät seine ISR auslöst, dann ist dein Timing sowieso viel 
zu knapp. Was machst du eigentlich wenn 2 Interrupts so kurz 
hintereinander auftreten, dass die eine ISR noch nicht fertig 
abgearbeitet wurde wenn der 2.te Interrupt kommt? Da ist der Zeitversatz 
noch viel größer, als wie wenn du unter Interrupt-Sperre mal kurz eine 
16 Bit Variable ausliest.

von Oliver (Gast)


Lesenswert?

chrysator schrieb:
> versteh ich das richtig, wenn ich atomar auf diese Variabeln zugreife,
> muss ich die Interrupts nicht ausschalten?

Nein. Atomar zugreifen bedeutet immer, die Interrupts auszuschalten. 
Geht nicht anders.

Den Rest hat dir Karl Heinz schon geschrieben.

Oliver

von chrysator (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Mach lieber dein Programm erst mal fehlerfrei und fange die ganzen Array
> Overflows ab bzw. kümmere dich um eine saubere Struktur, ehe du dir
> darüber sorgen machst, dass das Sperren der Interrupts für ein paar
> Taktzyklen dir das Timing versaut.

War das jetzt eine Analyse meines Angehängten C-Files oder nur so ne 
generelle Null-Aussage??
Bin nämlich schon ne Zeit an diesem Projekt und glaube jetzt die Logik 
richtig hingekriegt zu haben.
Der Grund warum ich frage ist nicht nur um möglichst genau zu sein mit 
der Zeit

Ist mir schon klar, dass zwei "gleichzeitige" Interrupts mein Zeitplan 
durcheinanderbringen. Aber das kann ich glaub ich nicht umgehen.

Der wichtigste Grund warum ich frage ist damit ich es VERSTEHE und was 
LERNE! Dann sind solche Aussagen nicht besonders hilfreich :)

Chrys

von Karl H. (kbuchegg)


Lesenswert?

chrysator schrieb:
> Karl Heinz Buchegger schrieb:
>> Mach lieber dein Programm erst mal fehlerfrei und fange die ganzen Array
>> Overflows ab bzw. kümmere dich um eine saubere Struktur, ehe du dir
>> darüber sorgen machst, dass das Sperren der Interrupts für ein paar
>> Taktzyklen dir das Timing versaut.
>
> War das jetzt eine Analyse meines Angehängten C-Files oder nur so ne
> generelle Null-Aussage??

Das ist schon auf deinen konkreten Code bezogen.
Wenn ich zwar sehe, dass deine Index-Variablen laufend erhöht werden, 
aber nie auf 0 zurückgesetzt werden, dann liegt da ein Array Overflow in 
der Luft.
Wenn ich sehe, dass du in den Indexberechnungen einen Ausdruck mit -1 
hast, dich aber nicht darum kümmerst, dass da bei einem Index von 0 auch 
insgesamt -1 rauskommen kann, dann hast du einen Zugriff Out Of Bounds.
Wenn ich sehe, dass du das Interrupt Bit mittels Zugriff auf das SREG 
selbst setzt, anstatt sei() und cli() zu benutzen, dann ist das unklug.
Wenn ich sehe, dass über deinen Code Pinnummern direkt im Code verstreut 
sind, dann ist das programmstrategisch unklug.


> Der wichtigste Grund warum ich frage ist damit ich es VERSTEHE und was
> LERNE! Dann sind solche Aussagen nicht besonders hilfreich :)

Ist ja ok.
Das erste was du lernen solltest:
Dein µC ist so schnell, dass alles was wir Menschen tun für ihn eine 
Super-Super-Super Zeitlupe darstellt
Dein µC ist so schnell, dass alles was eine Mechanik tun kann (zb ein 
Schrittmotor) für ihn eine Super-Super (nur 2 mal Super) Zeitlupe 
darstellt.
Erst dann, wenn es darum geht, irgendwas mit externer Elektronik 
anzustellen (zb den Elektronenstrahl auf einem Monitor hin und her zu 
scheuchen um ein Bild zu erzeugen), erst dann bist du in Zeit-Bereichen, 
in denen Timing wichtig werden kann.
Ob dein Schrittmotor ein paar Millionstel Sekunden früher oder später 
weiterschaltet, spiel so gesehn kaum eine Rolle. Und solange du das 
Schritttiming in weiterer Folge mit _delay_us machst, ist sowieso alles 
andere Makulatur. Exakte Timings erreicht man nie mit den _delay_xx 
Funktionen.

von chrysator (Gast)


Lesenswert?

Ok danke

Das waren jetzt sehr viele hilfreiche Hinweise.

Also mein Array sollte nicht overflowen, da die index variabeln ja 8-bit 
sind und somit mit-overflowen. Hab ich in diversen Foren und Codes 
gefunden. Sollte doch stimmen.

Die anderen Fehler werde ich so schnell wie möglich überprüfen. Danke

Ich bin mir allerdings nur halb sicher ob meine Anwendung nicht 
zeitkritisch ist. Hänge mal das File des Oszis für NUR eine Achse an. 
Dauert relativ lange bis Der Buffer das ausführt was der Interrupt 
bemerkt hat.

Zu den delay Funktionen: Wie mach ich das dann? Ich mag diese Funktionen 
auch nicht aber immer wenn ich was mit ner schleife probiere wird das 
wegoptimiert, auch wenn ich die Optimierung versuche auszuschalten. Wie 
wird das sauber gemacht?

Chrys

von Peter D. (peda)


Lesenswert?

Da Du Dich ja standhaft um konkrete Zeitangaben rumdrückst, mal ein 
Beispiel:

Angenommen Dein AVR (@16MHz) führt gerade einen Interrupt aus (~100 
Zyklen). Nun kommt ein weiterer Interrupt.
Dann wird dieser um 16MHz / 100 = 6µs verspätet ausgeführt.
Kannst Du damit leben?


Peter

von Konrad S. (maybee)


Lesenswert?

chrysator schrieb:
> Also mein Array sollte nicht overflowen, da die index variabeln ja 8-bit
> sind und somit mit-overflowen.

Deine Absicht ist lobenswert, aber wie soll hier
1
volatile uint8_t index_read = 0;
2
...
3
uint8_t tmpindex_read = index_read;
4
...
5
Buffer[tmpindex_read - 1]

der Compiler wissen, dass du den Index nur als vorzeichenlosen 
8-Bit-Ausdruck berechnet haben willst.
Bei
1
tmpindex_read--; ...  Buffer[tmpindex_read]
wäre der Fall klar, da kann nichts schiefgehen.

von chrysator (Gast)


Angehängte Dateien:

Lesenswert?

@ Konrad S.

Klar, das habe ich bisher nicht bemerkt ist aber falsch. Muss und werde 
ich heute noch ändern!

Peter Dannegger schrieb:
> Angenommen Dein AVR (@16MHz) führt gerade einen Interrupt aus (~100
> Zyklen). Nun kommt ein weiterer Interrupt.
> Dann wird dieser um 16MHz / 100 = 6µs verspätet ausgeführt.
> Kannst Du damit leben?

Ja kann ich (im moment) verkraften.

Mal ein paar Zahlen:

uC mit 16 MHz
Eingang vom EMC: pro Achse (hab 3 (x,y,z)) max 1KHz
Daneben läuft noch ein 100 KHz timer (um die Zeit zu "messen")

Aber der eigentliche Sinn des Buffers ist folgender: Wenn ein Schritt 
gemacht werden soll, schaut der Buffer ob vom Encoder am Schrittmotor 
der Schritt bestätigt wurde, wenn nicht dann nochmals machen!
Damit wird das ganze schon etwas zeitkritischer meiner meinung nach. 
Also ist effizienz mein grösster wunsch (neben fehlerfreiheit und so 
natürlich)

Häng noch das File vom Oszi an (ist .csv - kann auch ein Bild machen 
wenn ihr kein Zugang zu matlab oder ähnliochem habt)

Und wie kann ich den Compiler dazu bringen meine Warteschleifen nicht 
wegzuoptimieren??

Chrys

von Peter D. (peda)


Lesenswert?

chrysator schrieb:
> Ja kann ich (im moment) verkraften.

Nur "im moment" reicht nicht, Du wirst es mit dem AVR nicht viel besser 
hinkriegen. Das ist schon ziemlich worst case.


chrysator schrieb:
> Daneben läuft noch ein 100 KHz timer (um die Zeit zu "messen")

Alle 160 Zyklen einen 100kHz Interrupt, läßt der CPU nur noch wenig 
Luft. Dein Projekt hat keinerlei Reserven. Entweder Konzept oder CPU 
ändern.


Peter

von Klaus (Gast)


Lesenswert?

chrysator schrieb:
> Also mein Array sollte nicht overflowen, da die index variabeln ja 8-bit
> sind und somit mit-overflowen. Hab ich in diversen Foren und Codes
> gefunden. Sollte doch stimmen.

Endlich weiß ich, warum so oft 8-Bit Variablen benutzt werden. Das spart 
ganz schön Code.

MfG Klaus

von Walter T. (nicolas)


Lesenswert?

chrysator schrieb:
>
> Aber der eigentliche Sinn des Buffers ist folgender: Wenn ein Schritt
> gemacht werden soll, schaut der Buffer ob vom Encoder am Schrittmotor
> der Schritt bestätigt wurde, wenn nicht dann nochmals machen!
> Damit wird das ganze schon etwas zeitkritischer meiner meinung nach.
> Also ist effizienz mein grösster wunsch (neben fehlerfreiheit und so
> natürlich)
>

Du weißt aber schon, daß EMC die closed-loop auch selbst machen kann und 
dafür keinen µC, sondern nur noch eine Schnittstelle braucht?

Viele Grüße
Nicolas

von chrysator (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Alle 160 Zyklen einen 100kHz Interrupt, läßt der CPU nur noch wenig
> Luft. Dein Projekt hat keinerlei Reserven. Entweder Konzept oder CPU
> ändern.

Ehm ja ist mir auch schon durch den Kopf gegangen, aber AVRs sind ja 
generell auf 16 MHz limitiert oder? Also ich könnte einen zweiten nehmen 
oder auf Digital umsteigen aber hab noch nie etwas mit solchen CPU's 
gemacht. Kann da warscheinlich nicht mit avr-gcc draufschreiben oder?

Konzept ändern? wenn du irgendeine gute Idee hast hör ich gern zu. Ich 
hab ziemlich lange darüber nachgedacht und intensiv recherche betrieben 
und fand einen FIFO-Buffer die effizienteste Lösung.

Konrad S. schrieb:
> ...der Compiler wissen, dass du den Index nur als vorzeichenlosen
> 8-Bit-Ausdruck berechnet haben willst.
> Beitmpindex_read--; ...  Buffer[tmpindex_read]wäre der Fall klar, da kann nichts 
schiefgehen.

Ist nicht erwünschenswert. Ich will mit
1
 Buffer[tmpindex_read - 1].irgendwas;
 nicht tmpindex_read dekrementieren sondern nur auf das vorhergehende 
Element zugreifen. Werde mir was überlegen

von chrysator (Gast)


Lesenswert?

Nicolas S. schrieb:
> Du weißt aber schon, daß EMC die closed-loop auch selbst machen kann und
> dafür keinen µC, sondern nur noch eine Schnittstelle braucht?

Nee wusst ich nicht!!!
Wo? Wie? :P
Hab lange probiert aber nix gefunden.
Wäre schon mal eine sehr gute Lösung ... Allerdings möchte ich in 
Zukunft nicht immer ein PC mitlaufen lassen, sondern G-Code 
interpretieren mit einem uC.

Wäre toll wenn du mir erklären kannst wie das gehen soll!
Allerdings werde ich meinen Buffer weiterentwickeln auch wenns 
schlussendlich in der Realität nicht gebraucht wird.

Chrys

von Konrad S. (maybee)


Lesenswert?

chrysator schrieb:
> aber AVRs sind ja
> generell auf 16 MHz limitiert oder?

Oder! Bei den 8-Bit-AVRs geht es bis zu 32MHz (ATxmega...), aber das 
geht am Problem vorbei ...

von bitte löschen (Gast)


Lesenswert?

chrysator schrieb:
> Ist nicht erwünschenswert. Ich will mit
>
1
Buffer[tmpindex_read - 1].irgendwas;
> nicht tmpindex_read dekrementieren sondern nur auf das vorhergehende
> Element zugreifen. Werde mir was überlegen

Ich würde es erstmal mit
1
Buffer[(tmpindex_read - 1) & 0xff].irgendwas;
probieren.

von Oliver (Gast)


Lesenswert?

Philipp K. schrieb:
> Ich würde es erstmal mitBuffer[(tmpindex_read - 1) & 0xff].irgendwas
> probieren.

Was genau versprichst du dir davon?

Oliver

von Walter T. (nicolas)


Lesenswert?

chrysator schrieb:
> Nee wusst ich nicht!!!
> Wo? Wie? :P
> Hab lange probiert aber nix gefunden.
> Wäre schon mal eine sehr gute Lösung ... Allerdings möchte ich in
> Zukunft nicht immer ein PC mitlaufen lassen, sondern G-Code
> interpretieren mit einem uC.

Hallo Chrysator,
WIE es geht weiß ich leider nicht (ich nutze selbst Mach3), DASS es geht 
habe ich bei einem in Peter's (sic!) CNC-Ecke vorgestellten Projekt 
gesehen.

Für einen direkt G-Code interpretierenden µC gibt es übrigens auch schon 
ein Arduino-Projekt.

Ich habe generell ja nichts dagegen, wenn das Rad mehrmals täglich neu 
erfunden wird (Räder erfinden ist eine gute Fingerübung), aber 
vielleicht spart das ein wenig Aufwand.

Viele Grüße
Nicolas

von chrysator (Gast)


Lesenswert?

Nicolas S. schrieb:
> Für einen direkt G-Code interpretierenden µC gibt es übrigens auch schon
> ein Arduino-Projekt.

Werd ich mir gleich mal anschauen. Danke

Nicolas S. schrieb:
> WIE es geht weiß ich leider nicht (ich nutze selbst Mach3), DASS es geht
> habe ich bei einem in Peter's (sic!) CNC-Ecke vorgestellten Projekt
> gesehen.

Hab einiges gefunden aber sieht wieder mal nach ein paar Tagen 
lesearbeit aus :)

Danke für all eure Hilfe, der Buffer ist mal auf Eis gelegt bis ich die 
PID Funktion des EMC's mal angeschaut habe.

Allerdings für Zukunftsprojekte:
-Wie verhindert man das Optimieren des Compilers? (für 
Zeit-vertreibsschleifen)
-Wie löst man das folgende Problem elegant?

chrysator schrieb:
> Ist nicht erwünschenswert. Ich will mit
[c]Buffer[tmpindex_read - 1].irgendwas; [c]
>nicht tmpindex_read dekrementieren sondern nur auf das vorhergehende
> Element zugreifen.

Chrys

von Oliver (Gast)


Lesenswert?

chrysator schrieb:
> Allerdings für Zukunftsprojekte:
> -Wie verhindert man das Optimieren des Compilers? (für
> Zeit-vertreibsschleifen)

Gar nicht. Wenn man wirklich sinnlos Zeit vertreiben will, nimmt man die 
dleay-Funktionen der avrlibc, besser aber nutzt man Timer.

Oliver

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.