Forum: Mikrocontroller und Digitale Elektronik Zwei PWM Signale einlesen


von Frank (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

...gibt es evtl. einen "Kniff", mit dem ich beide Signale auf den 
ICP-Pin legen kann ?

lg, Frank

von michael (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

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

von mehrfacher STK500-Besitzer (Gast)


Lesenswert?

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.

von Frank (Gast)


Lesenswert?

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 :-)

von Frank (Gast)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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

von Frank (Gast)


Lesenswert?

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

von michael (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

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

von Stefan K. (_sk_)


Lesenswert?

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

von Falk B. (falk)


Lesenswert?

@ 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

von Stefan K. (_sk_)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

Sorry, es muss natürlich
1
asm volatile ("sei"      ::);
2
3
 _XXX_vect(); //Original-ISR aufrufen
4
  
5
 asm volatile ("reti"      ::);

heissen.

von Frank (Gast)


Lesenswert?

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
}

von Frank (Gast)


Lesenswert?

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

von Ste (Gast)


Lesenswert?

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.

von Frank (Gast)


Lesenswert?

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

von Frank (Gast)


Lesenswert?

äh quatsch ignorieren, abschalten meinte ich :-)

Es ist zu spät..

von Frank (Gast)


Lesenswert?

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