Hallo, ich habe hier für einen Roboter einen adxl202 und einen Modellbaukreisel, jeweils mit PWM-Ausgang. Vom adxl benötige ich nur eine Achse, daher lassen ich den anderen Ausgang des adxl unbeschaltet. Mir geht es nun darum, mit einem Mega8 das PWM-Signal des adxl und des Kreisels einigermassen genau zu messen. Das adxl-Signal wird etwa alle 3.75 ms Sekunden wiederholt (leicht schwankend), der Kreisel wiederholt sein Signal alle etwa 20ms mit einer Länge von 1-2ms. Derzeit habe ich den ADXL am INT0, und den Kreisel an ICP. Da der ICP ja den Zeitpunkt der Flanke im zugehörigen Register festhält, habe ich damit kein Problem. Beim INT0 jedoch geht das nur, indem ich im zugehörigen Interrupt den Timer auslese. Funktioniert soweit ganz gut und genau. Da ich nun aber noch andere Interupts verwenden möchte, befürchte ich, dass meine Messung des ADXL ungenau wird, da ja evtl die zusätzlichen Interrupts den INT0 für unbestimmte Zeit verzögern können (Ist "Softwarejitter" die richtige Bezeichnung dafür ?) Hat jemand evtl eine Idee, wie ich die Pulbreite trotz anderer Interrupts relativ (soll heissen: so genau wie möglich :-) genau messen kann ? Eine Messung der Analogausgänge des adxl scheidet für mich aus diversen Gründen zunächst aus. lg, Frank
...gibt es evtl. einen "Kniff", mit dem ich beide Signale auf den ICP-Pin legen kann ? lg, Frank
hallo. schau mal im datenblatt auf seite 15 bzw. such nach "nested interrupts". du kannst dafür sorgen, daß kein anderer interrupt deinen INT0, aber der INT0 jeden anderen interrupt unterbrechen kann. da der INT0 außerdem die höchste priorität nach dem RESET hat, wirst du auch kein problem kriegen, wenn zwei interrupts gleichzeitig anliegen. gruß michael
Hm, das wäre also ein "sei" so früh wie möglich in den zusätzlichen Interrupts. Damit würden mir aber immer noch einige Takte verloren gehen wenn ich das richtig verstanden habe, und ich würde einen kleinen "Mess-Jitter" (sorry, ich kenne nicht den richtigen Begriff dafür) haben. Der wäre nicht sonderlich hoch, wohl je nachdem wieviele "push" mir der GCC an den Anfang der Interrupts einfügt, und evtl (das muss ich erstmal testen) wäre das ausreichend. Dennoch: Gibt es noch eine bessere Möglichkeit ? lg, Frank
Man könnte es über Polling realisieren: Einen Timer mit einer möglichst feinen Auflösung eine ISR aufrufen lassen, in der die beiden Eingänge abgefragt werden. Da guckt man dann nach, ob sich was geändert hat. Hannes Luxx hat so einen Fernsteuerbaustein (oder sowas in der Richtung) realisiert.
Polling.. hm. da habe ich aber doch das gleiche Poblem mit dem "Jitter". Nämlich wenn der Timer-ISR durch einen anderen ISR verzögert wird. Oder habe ich Dich falsch verstanden ? Kann man vielleicht mit einigen Gattern die beiden PWM's verknüpfen so dass man als Output für den ICP-Eingang einen kurzen Puls bekommt, wenn sich einer der Level ändert ? Die wirklichen Zustand, ob 0 oder 1 kann man ja dann "irgendwann später" ohne große Timinprobleme erfassen. Mir ist nur grade ein Rätsel wie so eine Schaltung aussehen müsste :-)
Hab grade folgendes Entdeckt: #include <avr/interrupt.h> //... void XXX_vect(void) __attribute__((interrupt)); void XXX_vect(void) { //... } Damit scheint der erste Befehl einer ISR ein "sei" zu sein, damit wird das ganze also nur um 1 Takt verzögert. Das reicht dicke. @Michael: Danke für den Tip. Frank
@ Frank (Gast) >Damit scheint der erste Befehl einer ISR ein "sei" zu sein, damit wird >das ganze also nur um 1 Takt verzögert. Das reicht dicke. Nicht ganz. Der Sprung in den Interrupt dauert auch 4 Takte, macht zusammen 5. MFG Falk
Was müsste ich machen, um vor dem "sei" ein Flag zu setzen ? Dann könnte man in Abhängigkeit des Flags ggf. einige Takte zum Messergebnis hinzuaddieren. Ich vermute, das geht dann nur noch mit Inline-Assembler, oder gibt es einen Weg, das dem GCC beizubringen ? Wie würde soewtas (Inline-assembler) aussehen ? An dieser Stelle verlassen mich meine GCC-Kenntnisse... lg, Frank
hallo frank. wenn du genau weißt, wie viele takte du "verlierst", könntest du die natürlich nachträglich beim ergebnis berücksichtigen. kenne jetzt deine anwendung nicht im detail, aber ich meine, für "einigermaßen genau" mußt du solche klimmzüge nicht machen. gruß michael
Hi Michael, stimmt schon, aber wenn es mit wenig Aufwand besser geht, warum sollte ich diese Möglichkeit nicht nutzen :-) Jedenfalls wäre diese Lösung "perfekt", genauer geht es nicht mehr. Und da die einzige Änderung halt ein bischen umschreiben der Software ist, nehme ich "Klimmzüge" gern in Kauf, der Aufwand ist vermutlich vernachlässigbar, und selbst wenn sich herausstellt, dass es unnötig ist, habe ich zumindest so einiges dabei gelernt. Und das ist doch fast noch wichtiger als das Endergebnis :-) Vielleicht kommte ja dabei als Nebeneffekt ein nützliches Codeschnippsel für die Codesammlung heraus, das wäre doch auch was. lg, Frank
Hab mal geforscht, hier das Ergebnis für die, die es interessiert. So müsste es gehen. Ist aber noch nicht getestet, das kann ich erst daheim. Mithilfe dieser Konstruktion erhält man dann für INT0 und INT1 die gleiche Genauigkeit was timerstände angeht wie mit ICP. Man benötigt nur einen Wrapper. Wenn es getestet ist, schreibe ich das in die Codesammlung. XXX durch den gewünschten ISR ersetzen:
1 | void _XXX_vect (void) __attribute__ ((signal,__INTR_ATTRS)); |
2 | void _XXX_vect (void) /* Eigentliche ISR, Achtung Interupts sind eingeschaltet ! */ |
3 | {
|
4 | /* Hier code einfügen */
|
5 | }
|
6 | |
7 | |
8 | void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS)); |
9 | void XXX_vect(void) //Wrapper für _XXX_vect |
10 | {
|
11 | //asm volatile ("sbi 0x03, 1" ::); //hier gewünschtes Flag setzen
|
12 | asm volatile ("sei" ::); |
13 | |
14 | _INT0_vect(); //Original-ISR aufrufen |
15 | |
16 | asm volatile ("reti" ::); |
17 | }
|
..oder gibt es Einwände gegen diese Vorgehensweise ? lg, Frank
Hallo Frank, ich sehe das mit dem "sei" etwas problematisch. Damit schaltest Du ALLE Interrupts frei, inkl. dem, der gerade aufgerufen wird. Es kann also passieren, dass ein IR sich selbst unterbricht. Das kann von problemlos bis zum Stack-Overflow alle möglichen Auswirkungen haben und ist sehr schwer debugbar. Ich würde eher ein Multiplexen der beiden PWM-Eingänge vorschlagen, so wie Du es in Deinem zweiten Post auch schon gefragt hast: >>...gibt es evtl. einen "Kniff", mit dem ich beide Signale auf den >>ICP-Pin legen kann ? Der Input-Capture lässt sich sowohl vom ICP-Pin als auch vom Analog-Comparator triggern. Ich würde beide Geräte an diese beiden Inputs schalten und abwechselnd einlesen. Bei den von Dir angegebenen Timings müsstest Du eigendlich beide Werte alle 20ms updaten können. Viele Grüße, Stefan
@ Stefan Kleinwort (sk) >ich sehe das mit dem "sei" etwas problematisch. Damit schaltest Du ALLE >Interrupts frei, inkl. dem, der gerade aufgerufen wird. Es kann also >passieren, dass ein IR sich selbst unterbricht. Nöö, das passiert nur bei denen, die das jeweilige Interrupt Flag nicht automatisch per Harware löschen. Z.B. der UART RXC. MFG Falk
Falk Brunner wrote: > @ Stefan Kleinwort (sk) > >>ich sehe das mit dem "sei" etwas problematisch. Damit schaltest Du ALLE >>Interrupts frei, inkl. dem, der gerade aufgerufen wird. Es kann also >>passieren, dass ein IR sich selbst unterbricht. > > Nöö, das passiert nur bei denen, die das jeweilige Interrupt Flag nicht > automatisch per Harware löschen. Z.B. der UART RXC. Schon klar. Ich habe geschrieben: "es kann passieren". Z.B. bei solchen Sachen wie dem UART. Aber auch, wenn die IR-Routine zu lang ist bzw. die IR-Frequenz zu hoch. Und am allerbesten: auf dem Schreibtisch passt es, im Einsatz ist aber eine Kleinigkeit anders und der Stack läuft über. Richtig sauber ist das meiner Meinung nach nur, wenn der eigene IR vor dem "sei" disabled wird. Viele Grüße, Stefan
Sorry, es muss natürlich
1 | asm volatile ("sei" ::); |
2 | |
3 | _XXX_vect(); //Original-ISR aufrufen |
4 | |
5 | asm volatile ("reti" ::); |
heissen.
Ich hatte noch einen Denkfehler :o) Der Messwert muss natürlich nur korrigiert werden, wenn der INT0 vor dem "sei" des anderen Interrupts ("XXX") auftritt. Daher nun folgende, auf den ersten Blick etwas merkwürdig aussehende Konstruktion (sbi sei cbi) : Der Messwert muss um 4 (jmp)+ 2( sbi) + 1 (sei) = 7 Takte korrigiert werden. Das "reti" am Ende kann man auch durch ein "ret" ersetzen, das ist in diesem speziellem Fall dasselbe.
1 | void _XXX_vect (void) __attribute__ ((signal,__INTR_ATTRS)); |
2 | void _XXX_vect (void) /* Eigentliche ISR, Achtung Interupts sind eingeschaltet ! */ |
3 | {
|
4 | /* Hier code einfügen */
|
5 | }
|
6 | |
7 | |
8 | void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS)); |
9 | void XXX_vect(void) //Wrapper für _XXX_vect |
10 | {
|
11 | //asm volatile ("sbi 0x03, 1" ::); //hier gewünschtes Flag setzen
|
12 | asm volatile ("sei" ::); |
13 | //asm volatile ("cbi 0x03, 1" ::); //hier muss das Flag wieder gelöscht werden
|
14 | _XXX_vect(); //Original-ISR aufrufen |
15 | |
16 | asm volatile ("reti" ::); |
17 | }
|
Ok, ich habe es nun getestet. Funktioniert prima. Ich hatte vorher nicht bedacht, dass mein Input-Capture ja auch den ISR für INT0 beeinflusst und mich über ziemliches Rauschen gewundert. Ich kam trotz "8-point moving average filter" kaum auf 1 Grad nutzbare Genauigkeit (der ADXL202 ist ein Beschleunigungssensor, ich benutze ihn zur Bestimmung des Winkels zum Boden). Jetzt habe ich den Input-Capture ISR und einen zusätzlichen Timer-ISR wie oben implementiert, und trotz des zusätzlichen Timer-ISR flackern nur noch die Nachkommastellen des Winkels ein bischen. Es ist also mindestens um den Faktor 5 - wenn nicht mehr, genau habe ich es noch nicht untersucht- besser geworden. lg, Frank
Hallo! Ich habe bei der Diskussion mitgelauscht. Bin Was AVR angeht eigentlich schon ziemlich fit, vorallem in ASM. In C habe ich einige kleinere Sachen gemacht und kenne mich auch aus, aber nicht sehr gut. Deshalb frage ich mich ob jemand die oben geschriebenen Zeilen ein bisschen kommentieren könnte. So einige Dinge fallen mir da auf, die ich noch nie benutzt/gebraucht habe und die mir auf den ersten Blick auch nicht gleich trivial vor kommen: Was bedeutet "__attribute__"? Hab ich wahrscheinlich schon mal in einer Bibliothek die ich eingebunden habe gesehen. Die Dinger mit "naked" und so sind mir auch schleierhaft... Über ein par kurze Kommentare zu den paar Zeilen wäre ich sehr dankbar.
Hi Ste, ist schon spät, darum nur kurz: Mit Assembler brauchst du solche Hilfskonstruktionen bzw. den Wrapper nicht, da reicht ein kurzer Abschnitt "sbi, sei, cbi". Das ganze drumherum dient nur dazu, dem GCC das verständlich zu machen. Attribute "naked" bedeutet, dass der Compiler keinen Prolog/Epilog um die Funktion erzeugt, in diesem Fall verhindet es, dass - obwohl es sich beim Wrapper um eine Interrupt-Routine handelt (was durch die anderen beiden Attribute gekennzeichnet wird) erstmal das sreg, r1 usw auf den Stack gesichert wird. Das setzen des Flags (sbi blahblah..) hat denn Sinn, dass man es im ISR für INT0 abfragen und ggf. den Timerwert um 7 Takte "nach unten" korrigieren kann. Der Wrapper ruft dann eine "normale" ISR auf. An dieser Stelle kommt übrigens eine "Warning" "missspelled interrupt handler". Die kann man aber ignorieren, weil es in diesem Fall Absicht (Unterstrich vor dem Namen) ist. Wenn jemand einen Tip hat, wie ich die Warning ignorieren kann...
...ein kleiner Bug war noch drin.
1 | void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS)); |
2 | void XXX_vect(void) //Wrapper für _XXX_vect |
3 | {
|
4 | |
5 | asm volatile ("sei" ::); |
6 | //asm volatile ("sbi 0x03, 1" ::); //hier gewünschtes Flag setzen
|
7 | |
8 | /*
|
9 | |
10 | An genau dieser Stelle zwischen sbi und cbi werden ggf andere Interrupts
|
11 | aufgerufen. Der nach "sei" folgende Befehl wird nämlich vor dem
|
12 | Interrupt noch ausgeführt.
|
13 | |
14 | */
|
15 | |
16 | //asm volatile ("cbi 0x03, 1" ::); //hier muss das Flag wieder gelöscht werden
|
17 | _XXX_vect(); //Original-ISR aufrufen |
18 | |
19 | asm volatile ("reti" ::); |
20 | }
|
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.