mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Zwei PWM Signale einlesen


Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

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

lg, Frank

Autor: michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: mehrfacher STK500-Besitzer (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: michael (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
void _XXX_vect (void) __attribute__ ((signal,__INTR_ATTRS));
void _XXX_vect (void) /* Eigentliche ISR, Achtung Interupts sind eingeschaltet ! */
{
 /* Hier code einfügen */
}


void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS));
void XXX_vect(void) //Wrapper für _XXX_vect
{
 //asm volatile ("sbi  0x03, 1"  ::); //hier gewünschtes Flag setzen
 asm volatile ("sei"      ::);

 _INT0_vect(); //Original-ISR aufrufen
  
 asm volatile ("reti"      ::);
}

..oder gibt es Einwände gegen diese Vorgehensweise ?

lg, Frank

Autor: Stefan Kleinwort (_sk_)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Stefan Kleinwort (_sk_)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sorry, es muss natürlich
asm volatile ("sei"      ::);

 _XXX_vect(); //Original-ISR aufrufen
  
 asm volatile ("reti"      ::);

heissen.

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
void _XXX_vect (void) __attribute__ ((signal,__INTR_ATTRS));
void _XXX_vect (void) /* Eigentliche ISR, Achtung Interupts sind eingeschaltet ! */
{
 /* Hier code einfügen */
}


void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS));
void XXX_vect(void) //Wrapper für _XXX_vect
{
 //asm volatile ("sbi  0x03, 1"  ::); //hier gewünschtes Flag setzen
 asm volatile ("sei"      ::);
 //asm volatile ("cbi  0x03, 1"  ::); //hier muss das Flag wieder gelöscht werden
 _XXX_vect(); //Original-ISR aufrufen
  
 asm volatile ("reti"      ::);
}

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Ste (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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...

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
äh quatsch ignorieren, abschalten meinte ich :-)

Es ist zu spät..

Autor: Frank (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
...ein kleiner Bug war noch drin.
void XXX_vect(void) __attribute__ ((signal,naked,__INTR_ATTRS));
void XXX_vect(void) //Wrapper für _XXX_vect
{

 asm volatile ("sei"      ::);
 //asm volatile ("sbi  0x03, 1"  ::); //hier gewünschtes Flag setzen

/*

An genau dieser Stelle zwischen sbi und cbi werden ggf andere Interrupts
aufgerufen. Der nach "sei" folgende Befehl wird nämlich vor dem
Interrupt noch ausgeführt.

*/

 //asm volatile ("cbi  0x03, 1"  ::); //hier muss das Flag wieder gelöscht werden
 _XXX_vect(); //Original-ISR aufrufen
  
 asm volatile ("reti"      ::);
}

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.