Forum: Mikrocontroller und Digitale Elektronik Kann ein atmega32 eine Variable jede Mikrosekunde inkrementieren?


von Rainer (Gast)


Lesenswert?

Hallo Zusammen,

seit einigen Tagen versuche ich einen Kanal eines RC Empfängers 
auszulesen denn ich an INT0 eines atmega32 angeschlossen habe.
Ablauf:
-Nach Auftritt der steigenden Flanke an INT0 starte ich den Timer 0 (ISR 
gesteuert) im CTC Modus und inkrementiere eine Variable beim Erreichen 
des OCR0 Wertes (ISR gesteuert).
-Beim Erkennen der fallenden Flanke (ISR gesteuert) stoppe ich den Timer 
0 bis die nächste steigende Flanke erkannt wird und gebe die Variable 
aus.
-Der Timer arbeitet mit 16 MHz, also Vorteiler 1 und der OCR0 Wert ist 
auf 15 Eingestellt also 1Mhz. Somit wird theoretisch jede Mikrosekunde 
die Variable hoch gezählt. Die Zeit zwischen einer steigenden- und 
fallenden Flanke beträgt zwischen 1000 und 2000 Mikrosekunden, aber als 
Ergebnis bekomme ich immer etwas zwischen 192 und 330.
Kann der AVR überhaupt so schnell zählen? Theoretisch hat er 16 Takte um 
die Variable zu inkrementieren. Ist das ausreichend?
Beschäftige mich erst seit kurzem mit AVR’s, also bitte nicht erschlagen 
wenn ich etwas Grundlegendes nicht verstehe!
Den Quelltext liefere ich heute Abend nach.

Schon Mal Danke und Gruß
Rainer

von Stefan Noack (Gast)


Lesenswert?

zeig mal den code

von Paule H. (stk500-besitzer)


Lesenswert?

Nimm die InputCapture-Einheit.
Die macht das alles alleine.
Und: Nein, du bist der allererste, der ein RC-Empfangssignal auswerten 
will.

von Rainer (Gast)


Lesenswert?

@ Stefan: Den code stelle ich heute Abend ein, bin derzeit nicht an 
meinem Rechner.

@Paule: Es geht mir ums Verständnis, eine Funktionierende Lösung habe 
ich auch mit dem Timer1.

von Wusel D. (stefanfrings_de)


Lesenswert?

> Kann der AVR überhaupt so schnell zählen?

Ja kann er, vor allem die Timer, denn dafür sind sie gemacht.

Irgendwo hast Du sicher einen fehler im Programm.

von ossi (Gast)


Lesenswert?

Innerhalb von 16 Taktzyklen wird deine Interrupt-Routine sicher nicht 
fertig. Das heißt, es gehen viele Timer-Interrupts verloren.

Ich würde es so machen:

* Statt Timer0 den Timer1 verwenden, denn der zählt bis maximal 65535 
(16 Bits).
* Den Prescaler auf 8 setzen (Register TCCR1B). 16 wär einfacher, aber 
diese Option gibt es hier nicht.
* An Stelle einer eigenen Variablen einfach das Zählregister des Timers 
auf 0 setzen und am Ende auslesen (TCNT1L und TCNT1H, auf die 
Reihenfolge achten).
* Das Ergebnis halbieren (Assembler-Befehl "lsr").

von Stefan E. (sternst)


Lesenswert?

Rainer schrieb:
> Kann der AVR überhaupt so schnell zählen? Theoretisch hat er 16 Takte um
> die Variable zu inkrementieren. Ist das ausreichend?

Für das bloße Inkrementieren einer Variable wäre das ausreichend. Aber 
für den kompletten Interrupt nie und nimmer. Allein Ein- und Aussprung 
brauchen schon 11 Takte, und dazu kommt dann auch noch das Sichern und 
Wiederherstellen der verwendeten Register.

von Rainer (Gast)


Lesenswert?

Danke Euch allen,

damit habe ich auch die Antwort auf meine Frage – es geht nicht :(
Dann muss ich mir etwas anderes überlegen. Da der Timer1 später 
anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu 
realisieren.

Trotzdem Vielen Dank für die Hilfe.

Gruß
Rainer

von Karl H. (kbuchegg)


Lesenswert?

Rainer schrieb:
> Danke Euch allen,
>
> damit habe ich auch die Antwort auf meine Frage – es geht nicht :(
> Dann muss ich mir etwas anderes überlegen. Da der Timer1 später
> anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu
> realisieren.

Langsam.
Niemand sagt, dass man einen Timer nur für eine Sache verwenden kann. 
Der kann auch, bei geschicktem Vorgehen, dazu benutzt werden, mehrere 
Sache zu behandeln.

Und oft genug beginnt dieses 'geschickte Vorgehen' damit, dass man sich 
darüber im Klaren ist, dass dieses 'Wenn ... dann setze ich den Timer 
auf 0' Käse ist. Dieses Timer bewusst starten/stoppen/0-setzen braucht 
in Wirklichkeit meistens kein Mensch. Lass den Timer durchlaufen und 
nimm die numerische Differenz zwischen den Zählerständen 2-er Ereignisse 
und du hast genau das gleiche Ergebnis. Wenn du unsigned rechnest und 
der Timer in der bewussten Zeit gar nicht bis 65535 zählen kann, dann 
brauchst du einen Overflow arithmetisch noch nicht mal speziell 
berücksichtigen. Du rechnest einfach immer Ende-Anfang und es kommt 
durch die unsigned-Regeln IMMER das richtige Ergebnis raus.

Lässt du beispielsweise bei 16Mhz den Timer 1 durchlaufen (bei Prescaler 
1), dann bewegt sich die Differnz zwischen der steigenden und der 
fallenden Flanke eines Servosignals in der Größenordnung von 16000 bis 
32000. Ob du dabei Taktgenau den Timerstand abgreifst, spielt praktisch 
gesehen kaum eine Rolle, denn kein Servo kann tatsächlich seinen 
Servoweg in 16000 Positionen reproduzierbar auflösen. D.h. man wird die 
festgestellte Differenz der Zählerstände sowieso sofort verringern, 
wodurch dann auch der eine oder andere verpasste Timertakt sich 
rausmittelt.

von ossi (Gast)


Lesenswert?

Rainer schrieb:
> damit habe ich auch die Antwort auf meine Frage – es geht nicht :(
> Dann muss ich mir etwas anderes überlegen. Da der Timer1 später
> anderweitig verwendet wird, wird es wohl nicht so einfach das Ganze zu
> realisieren.

Nicht so leicht aufgeben. :-)

Ich wusste nicht, dass der Timer1 nicht zur Verfügung steht. Du kannst 
trotzdem die Lösung verwenden, die ich oben skizziert habe. Der Zähler 
des Timer0 läuft dann nur bis 255, das ist für dich das Low-Byte. Über 
den Timer-Overflow-Interrupt kannst du quasi manuell das High-Byte 
weiterzählen.

Natürlich passiert es dadurch, dass du den Zähler nicht 
mikrosekundengenau stoppen kannst, wenn er grad dabei ist, das Hihg-Byte 
zu erhöhen. Mit dieser kleinen Ungenauigkeit müsstest du leben.

Alternativ könntest du versuchen, die Interruptroutine in Assembler mit 
ein paar Tricks innerhalb der 16 Takte zum Laufen zu kriegen. Die 
restliche Rechenleistung des µC sinkt dann aber gewaltig ab.

Oder du misst die Zeit aktiv über Zählschleifen. Das geht natürlich nur, 
wenn der Mikrocontroller während dieser Zeit keine anderen Aufgaben 
erledigen muss.

Andere Frage: Steht denn der Timer2 auch zur Verfügung? Du könntest ihn 
parallel zum Timer0 laufen lassen. Der Timer2 besitzt einen eigenen 
Prescaler, du könntest ihn zum Beispiel mit 1/128 der Geschwindigkeit 
des Timer0 laufen lassen. Aus beiden gezählten Werten kannst du dann 
ohne Weiteres und ohne Umschaltfehler die tatsächliche Zeit ausrechnen.

von Rainer (Gast)


Lesenswert?

Nochmals Danke,

werde versuchen das Ganze als erstes mit dem Timer1 zu realisieren, wie 
Karl Heinz vorgeschlagen hat. Dann muss ich nur den Zwischenwert nach 
einem  Overflow speichern und diesen anschließend verrechnen. Und in der 
ISR bei einer fallenden Flanke eine Fallunterscheidung durchführen ob 
ein Overflow stattgefunden hat.

In etwa so:

ISR(INT0_...){
  if(steigende Flanke){
    berechne= 1;
    start = TCNT1;
    return;
  }
  if(fallende Flanke){
    stop = TCNT1;
    if(ueberlauf){
      signal = zwischenwert + stop
      zwischenwert = 0;
      ueberlauf = 0;
    }else{
      signal = stop – start;
    }
  }
  berechne = 0;
  start = 0;
  stop = 0;
}

ISR(OVF_..){
  if(berechne){
    ueberlaf++;
    zwischenwert = (timer_max - start)
  }
}

Bin gespannt ob das heute Abend so funktioniert :)

Gruß
Rainer

von Karl H. (kbuchegg)


Lesenswert?

Rainer schrieb:
> Nochmals Danke,
>
> werde versuchen das Ganze als erstes mit dem Timer1 zu realisieren, wie
> Karl Heinz vorgeschlagen hat. Dann muss ich nur den Zwischenwert nach
> einem  Overflow speichern

Nö. Nicht nach einem Overflow.

Du lässt den Timer laufen und wenn dein externer Interrupt (für die 
Signalflanke) kommt, dann liest du den Zahlenwert des Timers aus (oder 
du benutzt den Input Capture, was dasselbe macht nur etwas genauer und 
du hast nur 1 Input Capture Eingang)

Du brauchst dazu keinen Overflow. Bildlich gesprochen hast du eine 
Armbanduhr, deren Sekundenzeiger ständig kreist (das ist der Timer). 
Immer wenn du die Flanke siehst, schaust du auf die Uhr und siehst nach, 
wo der Zeiger gerade steht. 2 dieser Informationen kannst du dann 
verrechnen, wieviele Sekunden dazwischen lagen. Aber wie oft der 
Sekundenzeiger dabei die 0 überquert hat (und 1 Minute weitergeschaltet 
hat) interessiert dich überhaupt nicht, solange du weißt, dass maximal 1 
Minute zwischen den Ereignissen lag.

von Rainer (Gast)


Lesenswert?

Hallo Karl Heinz,

eventuell verstehe ich etwas falsch, aber was passiert wenn die 
steigende Flanke zu einem Zeitpunkt kommt, kurz bevor der Timer1 den 
Endwert 65535 erreicht. Da der Timer1 noch eine weitere Aufgabe hat, 
möchte ich ihn bei einer steigenden Flanke nicht auf 0 setzen.

Wenn ich von 16000 bis 32000 Takten für ein Signal ausgehe.

Beispiel 1:
Der Timer1 arbeitet im Normalmodus und die steigende Flanke kommt bei 
TCNT1
= 60535.
Somit habe ich als Startposition 60535.
Nach 65535 fängt der Timer1 bei 0 an.
Somit befindet sich mein Endwert (fallende Flanke) irgendwo zwischen 
11000 und 27000.

Beispiel 2:
Ein anderes Mal könnte die steigende Flanke bei z.B. TCNT1 = 10 
auftreten und mein Endwert würde sich irgendwo zwischen 16010 und 32010 
befinden.

Die Verrechnung im Beispiel 1  und Beispiel 2 unterscheidet sich doch.
Oder stehe ich gerade auf dem Schlauch?

Gruß
Rainer

von Stefan E. (sternst)


Lesenswert?

Rainer schrieb:
> Die Verrechnung im Beispiel 1  und Beispiel 2 unterscheidet sich doch.

Nein, tut sie eben nicht, wenn du innerhalb von unsigned-16-Bit 
rechnest.

11000 - 60535 = 16001

von Karl H. (kbuchegg)


Lesenswert?

Rainer schrieb:
> Hallo Karl Heinz,
>
> eventuell verstehe ich etwas falsch, aber was passiert wenn die
> steigende Flanke zu einem Zeitpunkt kommt, kurz bevor der Timer1 den
> Endwert 65535 erreicht. Da der Timer1 noch eine weitere Aufgabe hat,
> möchte ich ihn bei einer steigenden Flanke nicht auf 0 setzen.

Musst du ja auch nicht.
Unsigned Rechnerei berücksichtiggt das für dich.

Zwischen 65554 und 1 liegen 3 Takte

65534  65535   0   1

  |      |     |   |
  +------+-----+---+
     1      2    3

und unsigned gerechnet ergibt

       1 -  65534

genau diese 3.
Binär

   000000000000001
-  111111111111110
 ------------------
  1000000000000011

Das ist aber ein 17-Bit Ergebnis, d.h. die vorderste 1 fällt weg 
(UNterlauf) und das 16-Bit Ergebnis lautet

   000000000000011

also 3

Du rechnest einfach

unsigned int start;
unsigned int end;

unsigned int differenz = end - start;

und durch unsigned kommt IMMER das richtige raus: die Anzahl Werte 
dazwischen. Selbst dann, wenn da ein Overflow im Spiel ist. Es ist 
gerade dieses nicht berücksichtigen dieses Overflows, welches das 
möglich macht.

von Rainer (Gast)


Lesenswert?

Danke Euch,

wieder etwas gelernt, es läuft super.

:)
Gruß
Rainer

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Noch ein hinweis: Falls du mehr als einen kanal brauchst, versuch den 
Empfänger auf "Summensignal" umzustellen, der Code wird unwesentlich 
länger, und du hast auf einen Schlag alle 6 oder 12 oder 18 Kanäle...

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.