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:
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
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.
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
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?
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
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
@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
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.
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
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
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.
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
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
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
volatileuint8_tindex_read=0;
2
...
3
uint8_ttmpindex_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
@ 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
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
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
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
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
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
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 ...
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
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
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