Forum: Compiler & IDEs messen einer Periodendauer


von Matze (Gast)


Lesenswert?

Ich habe eine Atmel AT90PWM3 vor mir.

Ich habe vor, ein Rechtecksignal dass von meinem Lüfter erzeugt wird
auszulesen um so seine Umdrehungszahl zu ermitteln. Ich will also die
Periodendauer von dem Rechtecksignal messen.

Dabei hatte ich gedacht, während einer bestimmten Zeitspanne, zB 1s
oder 1/2s alle steigenden Flanke des Signals mit einem Zähler zu
zählen. Dafür würde mir der 8 Bit Counter ausreichen. Da die max Anzahl
von Flanken pro sec bei 40 liegt. Dass heißt ich hätte noch den 16 Bit
Timer, mit dem ich das Zeitfenster generieren kann.
Mein Problem ist nun, in welcher Reihenfolge ich vorgehen muss und wie
es letztendlich zu realisieren ist.

Wie kann ich den Counter anhalten wenn die Zeit verstrichen ist?

Macht mein Controller überhaupt noch was wenn ich den Counter starte,
läuft dass dann im Hintergrund?

von johnny.m (Gast)


Lesenswert?

>...Da die max Anzahl von Flanken pro sec bei 40 liegt...

Oben hast Du doch noch gesagt, Du wolltest eine Periodendauer messen.
Das was Du hier beschreibst ist aber eine Periodenzählung. Bei max.
40Hz und einem µC, der eine Input Capture-Einheit besitzt, solltest Du
wirklich eine Periodendauermessung machen. Das geht mit Input Capture
ganz hervorragend und ist bei derart niedrigen Frequenzen eigentlich
das einzig sinnvolle. Überleg doch mal, wie gering die Auflösung wäre,
wenn Du tatsächlich nur max. 40 Flanken pro Sekunde erhältst und nur
über eine halbe oder ganze Sekunde misst...

von Matze (Gast)


Lesenswert?

Da hast du Recht.
Habe mit gerade das mit dem Input Capturing durchgelesen.

Heißt das, das dieser Mode, wenn sich mein Signal ändert, den 16 Bit
Timer startet und dieser dann läft bis sich das Signal erneut ändert?
Dann muss ich praktisch nur den entsprechenden Timerwert aus dem TCNTn
Register auslesen und ensprechend der CPU Frequenz hochrechnen und
schon habe ich die Periodendauer?

von johnny.m (Gast)


Lesenswert?

Nein, der Timer läuft im Prinzip durch. Es wird lediglich bei Auftreten
einer Flanke der aktuelle Zählerstand in das Capture-Register
geschrieben (und zwar verzögerungsfrei). In der entsprechenden
Interrupt-Routine sicherst Du dann das Capture-Register in einer
Variablen und beim nächsten Capture-Ereignis musst Du im Prinzip nur
noch die Differenz zwischen den beiden Werten bilden. Die
Timer-Überläufe musst Du natürlich auch zählen und dann zur Differenz
jeweils den kompletten Wert addieren. Sinnvollerweise betreibst Du den
Timer im CTC-Modus mit einem Compare-Register als Reset-Wert und
stellst das ganze so ein, dass Du z.B. alle 10 ms einen Reset bekommst.
Dann über den entsprechenden Compare-Interrupt die Millisekunden zählen
und die Differenz der beiden Capture-Werte dazuaddieren (wenn die
Differenz negativ ist, muss natürlich ein 10 ms-Wert abgezogen werden).
Das ist vom Rechenaufwand minimal und sehr präzise. In Deinem Fall
reicht es wahrscheinlich sogar dicke aus, in 100 ms-Blöcken zu zählen,
zumal der 16-Bit Timer an sich schon eine sehr gute Auflösung zur
Verfügung stellt (wenn man ihn sinnvoll konfiguriert). Hängt alles ein
bisschen davon ab, wie präzise das ganze sein soll.

Gruß

Johnny

von Matze (Gast)


Lesenswert?

Also erst mal danke für deine Hilfe. Hab das Ganze jetzt mal
programmiert, sieht so aus:

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>

volatile uint32_t Zaehler[2];

int main(void)
{
  Zaehler[0]=0;
  for(;;)
  {
        /*CTC Timer (Mode 12) mit Prescaler auf CPU/256,
        fallende Flanke als Trigger, eine Timerzyklus = 100ms*/

  TCCR1A |= 0x00;
  TCCR1B |= (1<<WGM13)|(1<<WGM12)|(1<<CS12);
        ICR1 = 3200;   //Obergrenze Timer
  }
}

ISR(TIMER1_CAPT_vect)    //wenn fallende Flanke auftritt
{
  Zaehler[1]=ICR1;        //Zählerwert in Variable kopieren
}

int Drehzahl(void)
{
 /*Nicht berücksichtigt, dass bei Umdrehungen unter 10 rpm, ein
falsches Ergebnis entsteht,da dann 2 Zählerüberläufe entstehen.*/

uint32_t abs_Zaehler=0;
uint32_t Drehzahl=0;

if(Zaehler[0] < Zaehler[1])
{
  abs_Zaehler = Zaehler[1]-Zaehler[0];
}
else    /*Neuer Wert kleiner alter Wert ->Überlauf dh volle Periode
        dazu*/
{
  abs_Zaehler = ((Zaehler[1]-Zaehler[0])+3200);
}
Zaehler[0]=Zaehler[1];

Drehzahl = (1/(abs_Zaehler * 0.001875));  //Drehzahl in rpm
return Drehzahl;
}


Ablauf:
-CTC Timer mit 256 Prescale Obergrenze 3200 starten, ein Zyklus =
100mS
-Wenn Capture auftritt in Variable umspeichern
-Prüfen ob Überlauf stattfand
-Umrechnen auf Umdrehungen pro Minute

Hab ich das so richtig verstanden?
Bzw. Warum muss ich den CTC Timer wählen der ICR1 als TOP Value hat?
Der Wert im ICR1 Register wird doch immer wieder überschrieben!

Vielen Dank noch mal für deine super Hilfe
Gruß Matze

von johnny.m (Gast)


Lesenswert?

Nimm den CTC-Modus 4 (mit OCR1A als TOP). Das ICR1 brauchste ja für die
Capture. Und im CTC-Modus darfst Du natürlich nicht auf nen Überlauf
warten (der tritt ja nie auf, wenn OCR1A kleiner als MAX ist), sondern
musst den Compare-Interrupt benutzen, um die 100ms-Blöcke zu zählen.

von Matze (Gast)


Lesenswert?

>Und im CTC-Modus darfst Du natürlich nicht auf nen Überlauf
warten (der tritt ja nie auf, wenn OCR1A kleiner als MAX ist)

Das versteh ich jetzt nicht so ganz. Wenn ich CTC auf den Modus 4
umstelle und dann natürlich auch anstatt ICR1, OCR1A schreibe. Dann
zählt mein Counter bis zum Wert in OCR1A, also bis 3200, was bei 8MHz
Clock und 256 Prescaling 100ms entspricht.

Die Blöcke zu zählen hab ich weggelassen, wenn du das meinst, da sich
das Problem von 2 Überläufen, erst unterhalb von 10 rpm bemerkbar
macht. Anstatt hab ich den Vergleich eingeführt. Der fällt natürlich
ins Wasser wenn ein Signal mit niedrigerer Frequenz wie ein Zählzyklus
auftaucht.

Obwohl, wenn ichs mir recht überleg, ist ja auch blöd wenn ich meinen
Lüfter blockiere und auf einmal gibt die Software wieder grünes Licht,
obwohl der Lüfter fast steht.

von Matze (Gast)


Lesenswert?

Oh ich glaube jetzt weiß ich was du meinst:

ISR(TIMER1_CAPT_vect)
{
  Zaehler[1]=ICR1;        //Zählerwert in Variable kopieren
}

Das heißt der Interrupt tritt erst beim Zählerüberlauf auf?

von johnny.m (Gast)


Lesenswert?

Nein. Du musst ja die 100 ms-Blöcke zählen. Dazu musst Du den
Compare-Interrupt benutzen. Dieser ersetzt im CTC-Modus den
Overflow-Vektor. Im Prinzip kannst Du im Programm eine globale
Zählvariable setzen, die zwischen zwei Flanken die Timer-Resets (die ja
nach jeweils 100 ms auftreten) zählt. Zusätzlich hast Du dann zwei
Capture-Werte, deren Differenz Du bildest. Daraus errechnest Du die
Gesamtzeit, die zwischen zwei Flanken verstrichen ist. Bsp.:
-Globale Zählvariable für 100 ms-Blöcke auf Null setzen
-Flanke 1 kommt -> Zählerstand von TCNT1 wird in ICR1 gespeichert (z.B.
2500)
-In der Capture-Routine wird ICR1 in einer globalen Variable gesichert
-Bei erreichen von 3200 (Wert in OCR1) im TCNT1 wird TCNT1
zurückgesetzt und der Compare-Interrupt ausgelöst.
-In der Compare-Interrupt-Routine wird der 100 ms-Zähler um 3200 erhöht
(3200 entspricht ja 100 ms, wird für die Endberechnung gebraucht).
-Wenn in den nächsten 100 ms noch keine weitere Flanke am ICP auftritt,
gibts wieder ein Compare-Ereignis, wieder muss der 100 ms-Zähler um 3200
erhöht werden usw....
-Flanke 2 am ICP tritt auf -> TCNT1 wandert in ICR1
-In der Capture-ISR wird der oben gesicherte erste Capture-Wert vom
aktuellen abgezogen. Die Differenz zu dem Wert im 100 ms-Register
addieren und das ganze mit einer Division durch 32 in Millisekunden
umrechnen. Es ist darauf zu achten, dass für die Differenzbildung eine
vorzeichenbehaftete Variable verwendet wird, da die Differenz ja auch
negativ sein kann.
-Fertig, Periodendauer ist da!

(Alternativ zur Erhöhung des 100ms-Zählers um 3200 kann man ihn auch
jeweils um 1 erhöhen und am Ende für die Berechnung mit 3200
multiplizieren. Dann kann der Zähler i.A. auch eine 8-Bit-Variable
sein)

Wichtig ist nur, dass CTC-Mode 4 zum Einsatz kommt und nicht CTC-Mode
12, weil der tatsächlich das ICR benötigt.

So würde ich es machen, wenn ich es jetzt mal eben hinbiegen müsste.
Man kann es sicher noch anpassen und optimieren. Habe jetzt nur
dummerweise keine Zeit, mir den ganzen Code noch mal anzuschauen. Ich
hoffe aber, dass ich Dir helfen konnte.

Gruß

Johnny

von Matze (Gast)


Lesenswert?

Ja, auf jeden Fall. Ich werd es ausprobieren. Meld mich dann wenn es
klappt.
Vielen Dank
Matze

von Matze (Gast)


Lesenswert?

Also, dass mit der Drehzahlmessung funktioniert jetzt mal. Allerdings
habe ich immer noch ein Problem. Ich kann die Zählerüberläufe , wie von
Johnny vorgeschlagen nicht zählen, da der Overflow Interupt eine
niedrigere Priorität hat wie der des Input Capturing. Als Folge tritt
der Overflow Interupt nie auf, da er vom Input Capturing Interupt
praktisch blockiert wird. Gibt es eine Möglichkeit dieses Problem zu
beheben? Denn sonst kann ich nur Frequenzen > 5 Hz erkennen.
Grüße Matze

von johnny.m (Gast)


Lesenswert?

Es gibt beim AVR keine Interrupt-Prioritäten! Es gibt lediglich eine
Abarbeitungsreihenfolge gleichzeitig auftretender Interrupts. Der
Overflow-Interrupt tritt nicht auf, da der Timer im CTC-Modus nie
überläuft!

von johnny.m (Gast)


Lesenswert?

> da er vom Input Capturing Interupt praktisch
> blockiert wird.

Wieso blockiert??? Ich hoffe, Du bleibst nicht die ganze Zeit während
der Messung in der Input-Capture-ISR?!?!?!? Du musst natürlich nach dem
Sichern des Capture-Wertes und Initialisierung der Zählervariablen
wieder aus der ISR raus. Sonst blockierst Du alles andere!

von Matze (Gast)


Lesenswert?

ja, aber im CTC Modus wird doch, wenn der Counter von MAX nach 0 zählt,
das TOV Flag gesetzt. Daher dachte ich, dass dann auch ein Overflow
Interrupt ausgelöst wird.
Oder muss ich auf ein Counter Compare Match A warten? Und wenn ja, muss
ich dann ein Compare Wert definieren, oder mit welchem Wert wird dann
verglichen?

von johnny.m (Gast)


Lesenswert?

MAX ist der maximale Zählerstand (beim Timer 1 0xFFFF), der im CTC-Modus
nie erreicht wird, es sei denn, in OCR1A steht 0xFFFF...

Also nochmal zum Ablauf (um sämtliche Klarheiten zu beseitigen):

1. Timer 1 läuft im CTC-Modus 4 und Input Capture ist aktiviert
2. Es tritt eine Flanke am Input Capture Pin auf -> TCNT1 wird
automatisch in ICR1 gesichert
3. Die Input Capture ISR wird aufgerufen, ICR1 gesichert, der Zähler
für die 100ms-Blöcke auf null gesetzt. Anschließend WIRD DIE ISR WIEDER
VERLASSEN!!!
4. [optional, evtl. auch mehrmals] Es tritt ein Compare-Ereignis auf ->
die Compare-ISR wird aufgerufen. Darin wird der Zähler für die
100ms-Blöcke inkrementiert. Anschließend wird auch diese ISR wieder
verlassen!
5. Es tritt eine zweite Flanke am Input Capture Pin auf -> es wird
erneut TCNT1 in ICR1 übernommen.
6. Die Input Capture ISR wird wieder aufgerufen. Die Berechnungen
werden durchgeführt und das Ergebnis gesichert. Dann: RAUS AUS DER
ISR!

Natürlich musst Du in der ISR abfragen (z.B. eine Flag-Variable), ob
die jeweils aufgetretene Flanke die erste oder die zweite ist...

von Matze (Gast)


Lesenswert?

ICh glaub ich bin grad selber auf die Antwort gestoßen. Der Output
Compare Match vergleicht meinen Wert im OCR1A  Register(das ja acuh
meine Timer Obergrenze ist) und löst wenn beides gleich ist einen
Interrupt aus, wenn ich das entsprechende Bit in der TIMSK vorher
aktiviert habe.

von Matze (Gast)


Lesenswert?

Vielen Dank für deine Zusammenfassung.
Ich hab die einzelnen Schritte auch so realisiert, jedoch habe ich die
Drehzahlberechnung in ein eigenes Programm gepackt, dass von der der
Input Capturing ISR aufgerufen wird. Gibt das ein Problem, oder kann
ich das so lassen?
Vielen, vielen Dank noch mal   Matze

von Matze (Gast)


Lesenswert?

Ich hoff ich darf gleich noch ne Frage hinterher schicken. Ist auch ne
ganz einfache kurze. :-)

Wass passiert denn wenn zB ein Zähler für eine Variable zu groß wird.
Also zB wenn:

  uint8_t i=255;
  i++;

Läuft die Variable dann einfach über und ist danach 0, oder wächst die
dann "irgendwo" unbestimmtes hin.

von johnny.m (Gast)


Lesenswert?

Zu der einfachen, kurzen Frage: ja, die Variable läuft über, genau wie
die Hardware-Zähler (Timer). Weiter wachsen (irgendwohin) kann sie ja
nicht, weil nix mehr reinpasst...

Zu der anderen Angelegenheit: Der Code einer ISR sollte
1. Grundsätzlich so kurz wie möglich sein
2. Keine Warteschleifen o.ä. enthalten
3. Keine Funktionsaufrufe enthalten
Im Allgemeinen ist es am sinnvollsten, in der ISR nur das absolut
nötigste zu machen (Beispiel Input Capture: ICR sichern, Zähler auf
Null setzen oder Ergebnis berechnen) und alles andere, was mit dem
Interrupt zu tun hat im Hauptprogramm abarbeiten. Meist setzt man dazu
in der ISR eine Variable (bzw. ein Bit in einer Variablen, sog.
Job-Flag) und fragt dieses im Hauptprogramm ab. Die Berechnung des
Ergebnisses in Deinem Beispiel ist eigentlich auch ein Fall für ein
Jobflag und eine Bearbeitung im HP, aber da sie in diesem Fall noch
überschaubar ist, kann man die auch in der ISR lassen.

Mit Deiner Methode machst Du im Prinzip alles falsch, was man falsch
machen kann (Punkt 1. bis 3. von oben). Die ISR blockiert das ganze
System, so dass der Compare-Interrupt (der ja die 100ms-Abschnitte
zählen soll) gar nicht ausgeführt wird. Du musst auf jeden Fall aus der
ISR wieder raus, wenn das Capture gesichert ist, sonst läuft gar nix.

von Matze (Gast)


Lesenswert?

Vielen Dank noch mal für deine ausdauernde Hilfe.

Jetzt funktioniert es wunderbar. Habe die Drehzahlberechnung jetzt mit
in die ISR gepackt und dafür alles was auch ins main Programm kann
ausquartiert. Dann noch den entsrechenden Compare Match Interrupt
freigeschaltet.

Habe soeben auch das Ganze mit einem Papst Lüfter getestet, mein
Controller erkennt jetzt auch ernn der Lüfter vollständig blockiert
ist. Genau so habe ich mir das vorgestellt.

Viele Grüße und Besten Dank Matze

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.