Forum: Mikrocontroller und Digitale Elektronik Periodenmessung


von Thomas R. (thenew5)


Lesenswert?

Hallo euch,
versuche gerade eine Periodenmessungsprogramm zu schreiben und hab mir 
gedacht eine reine Zeitmessung würde auch gehen. Dafür hab ich in einem 
Buch einen Programmvorschlag gefunden. Jedoch funktioniert dieser leider 
nicht. Es wird immer auf eine static declaration of (TIMER0_COMPA_vect) 
follows non-static declaration hingewiesen. Leider versteh ich noch 
nicht was ich machen soll.
Hier mal mein Programmchen:
nt main(void)
{
  //-----Initialisierung----------------------------------------
  DDRC = 0xFF; // Port C sind Ausgänge
  DDRA = 0x00; // Port A sind Eingänge
  volatile double sec=0;
  volatile unsigned char run = 0; // 0=stop, 1=run
  volatile unsigned int ctr=0; // counter latch
  char t [8]; // Text
  ISR (TIMER0_COMPA_vect)
  {

    if (run)
    {
      run = 0; ctr = 1; sec = 100;
    }
  }
  ISR (TIMER1_CAPT_vect)
  {
    if (run)
    {
      ctr=ICR1; sec = ctr/(F_CPU/256.0);run = 0;
    }
    else
    {
      TCNT1 =0; run=1;
    }
  }
  uart1_write("Start\r");
  _delay_ms(999);
  TIMSK1 |= (1<<ICIE1)|(1<<OCIE1A);
  OCR1A = F_CPU/256;
  TCCR1B|=(1<<WGM12)|(1<<CS12);
  sei(); // Globale Interrupts ein
  //-----Endlosschleife-----------------------------------------
  while(1)
  {
    //Start counter
  if (ctr)
  {
    dtostrf (sec, 8,2,t);
    uart1_write_str(t);
    uart1_write("Pulse\r");
  }

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Die beiden ISR haben im Hauptprogramm nichts zu suchen und globale 
Variablen auch nicht. Schreib z.B.:
1
  volatile double sec=0;
2
  volatile unsigned char run = 0; // 0=stop, 1=run
3
  volatile unsigned int ctr=0; // counter latch
4
5
int main(void)
6
{
7
  char t [8]; // Text
8
  //-----Initialisierung----------------------------------------
9
  DDRC = 0xFF; // Port C sind Ausgänge
10
  DDRA = 0x00; // Port A sind Eingänge
11
  uart1_write("Start\r");
12
  _delay_ms(999);
13
  TIMSK1 |= (1<<ICIE1)|(1<<OCIE1A); // solltest du eigentlich erst machen, nachdem der Timer initialisiert ist
14
  OCR1A = F_CPU/256;
15
  TCCR1B|=(1<<WGM12)|(1<<CS12);
16
  sei(); // Globale Interrupts ein
17
  //-----Endlosschleife-----------------------------------------
18
  while(1)
19
  {
20
    //Start counter
21
  if (ctr)
22
  {
23
    dtostrf (sec, 8,2,t);
24
    uart1_write_str(t);
25
    uart1_write("Pulse\r");
26
  }
27
}
28
}
29
ISR (TIMER0_COMPA_vect)
30
  {
31
32
    if (run)
33
    {
34
      run = 0; ctr = 1; sec = 100;
35
    }
36
  }
37
ISR (TIMER1_CAPT_vect)
38
  {
39
    if (run)
40
    {
41
      ctr=ICR1; sec = ctr/(F_CPU/256.0);run = 0;
42
    }
43
    else
44
    {
45
      TCNT1 =0; run=1;
46
    }
47
  }

von Karl H. (kbuchegg)


Lesenswert?

Und die eine ISR ist sowieso überflüssig. Timer 0 wird im Programm ja 
gar nicht benutzt.


Ist das das Programm, welches genau so im Buch abgedruckt war? Wenn ja, 
dann kauf dir zusätzlich ein ordentilches C-Buch und arbeite das erst 
mal zur Hälfte durch, damit du verstehst, wo dein jetziger Buchautor 
Defizite in der Sprache C hat.
.

von Karl H. (kbuchegg)


Lesenswert?

Autsch. Jetzt seh ichs erst
1
  TIMSK1 |= (1<<ICIE1)|(1<<OCIE1A); // solltest du eigentlich erst machen, nachdem der Timer initialisiert ist
2
3
....
4
5
ISR (TIMER0_COMPA_vect)
6
  {

das wird in die Hose gehen. So wie das jetzt ist, ist das ein 
Dauerreset. Wenn du den Output Compare Interrupt A für den Timer 1 frei 
gibst, dann musst du auch die richtige ISR dafür schreiben.
Nämlich eine für den Timer 1.

Das hier

ISR (TIMER0_COMPA_vect)

wäre die ISR für den Timer 0, wie unschwer am Namen erkennbar ist. (und 
wenn dein µC am Timer 0 das gar nicht kann, dann ist das auch die 
Erklärung für deine Fehlermeldung)

von Thomas R. (thenew5)


Lesenswert?

Super danke,

hab die Timer Initialisierung raus gegeben und schon funktionierts.
Beim Timer hab ich mich verschrieben das ist natürlich der Timer1.

Was mir noch aufgefallen ist das der Timer Relativ ungenau ist.
Denn je höher die Frequenz wird desto mehr schwankt der Ausgegebene 
Wert.
Kann man hier noch was verbessern?

von Karl H. (kbuchegg)


Lesenswert?

Thomas Reiterer schrieb:

> Was mir noch aufgefallen ist das der Timer Relativ ungenau ist.

Der ist so genau, wie der Takt ist, mit dem du den µC versorgst, bzw. 
was du dann mit dem Prescaler weiter machst.

Benutzt du den interenen Taktgenerator, dann hast du Schwankungen. 
Benutzt du aber einen Quarz, dann kannst du problemlos auf ein paar 
Quarzschwingungen genau messen. Genau aus dem Grund ist der Input 
Capture Mode erfunden worden.

Allerdings sollte man dann auch mal darüber nachdenken, wie man die 
Prescaler vom Timer einsetzt. Der einzige UNterschied zwischen dem 
Minutenzeiger einer Uhr und dem Sekundenzeiger ist ein 'Prescaler' von 
60 auf dem Minutenzeiger. Mit einem Minutenzeiger einer Uhr kann man 
ganz wunderbar Zeiten von 30 Minuten aufwärts stoppen. Aber um 48 
Sekunden zu stoppen, tut man sich schwer. Da ist es dann besser, den 
kleineren Prescaler zu nehmen, sprich den Sekundenzeiger, und mit dem 
die Zeit zu stoppen.

von Thomas R. (thenew5)


Lesenswert?

Ja ich hab schon einen Externen Quarz mit 10MHz angeschlossen. Also 
sollte das schon genau sein. Ja vielleicht ist auch mein Frequenz 
Generator etwas ungenauer...
Werd noch ein paar Sachen versuchen...

von Karl H. (kbuchegg)


Lesenswert?

Thomas Reiterer schrieb:
> Ja ich hab schon einen Externen Quarz mit 10MHz angeschlossen.

Ja ber: ist der auch aktiv?

> Also
> sollte das schon genau sein. Ja vielleicht ist auch mein Frequenz
> Generator etwas ungenauer...
> Werd noch ein paar Sachen versuchen...

Ich wiederhol mich nur ungern: Prescaler!

von Karl H. (kbuchegg)


Lesenswert?

Im übrigen ist der Ansatz mit dem 0-setzen von TCNT1 nicht wirklich 
optimal. Beim Input Capture lässt man den Timer einfach durchlaufen und 
rechnet mit Differenzen der Timer-Zählerstände, wobei das, solange das 
Zählergebnis nicht größer als 65535 wird, sogar durch die unsigned 
Rechnerei recht problemlos ist, weil man den Overflow des Timers 
überhaupt nicht berücksichtigen muss. Einfach
   ctr = ende - start;
und fertig.

von Thomas R. (thenew5)


Lesenswert?

Hab gesehen das ich da noch Aufholbedarf hab.
So die Fuses für den Quarz hab ich so gesetzt:
SUT_CKSEL -> EXTOSC_0MHZ4_0MHZ9_1KCK_0MS
und am Anfang meines Programmes definiere ich den F_CPU auf 10000000.

Den Prescaler versteh ich soweit dieser teilt die Taktfrequenz.
Und nun soll ich den Zähler starten und mit der ersten Flanke den 
Startwert speichern und mit der zweiten Flanke den Endwert und den 
Startwert vom Endwert abziehen und schon hab ich die Periodendauer.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Reiterer schrieb:
> Hab gesehen das ich da noch Aufholbedarf hab.
> So die Fuses für den Quarz hab ich so gesetzt:
> SUT_CKSEL -> EXTOSC_0MHZ4_0MHZ9_1KCK_0MS
> und am Anfang meines Programmes definiere ich den F_CPU auf 10000000.

Na was jetzt?
Läuft dein µC mit 10Mhz oder tut er das nicht?

Das lässt sich recht einfach mit dem µC selbst feststellen und dann 
weißt du es genau. Denn ein _delay_ms liefert nur dann einigermassen 
genau 1 Sekunde, wenn bei einem F_CPU Wert von 10Mio der µC dann auch 
tatsächlich mit 10Mhz getaktet wird. Einfach mittels _delay_ms eine LED 
1 Sekunde ein / 1 Sekunde aus. Stimmt die Zeit, dann läuft der auch mit 
10Mhz. Denn die Alternative von 1Mhz würde bedeuten, dass die LED dann 
nicht 1 Sekunde an ist, sondern 10 Sekunden. Und das sieht man auch mit 
freiem Auge.
Ob der Quarz mit exakt 10Mhz läuf oder doch mit 9.9999 kann man so nicht 
feststellen, aber das macht nichts. Zwischen den Alternative 10Mhz, 1 
Mhz oder auch 8Mhz kann man so immer noch gut genug unterscheiden.

>
> Den Prescaler versteh ich soweit dieser teilt die Taktfrequenz.
> Und nun soll ich den Zähler starten und mit der ersten Flanke den
> Startwert speichern und mit der zweiten Flanke den Endwert und den
> Startwert vom Endwert abziehen und schon hab ich die Periodendauer.

Ganz genau.
Ja, man kann auch mit einer ganz gewöhnlichen Uhr mit Sekundenzeiger 
Zeiten stoppen, indem man sich einfach die Zeigerstellung merkt, wann 
der Rennläufer losfährt und mit der Zeigerstellung vergleicht wann er 
durchs Ziel fährt. Man muss nicht unbedingt eine Uhr haben, die 2 
Knöpfchen hat um eine eigene Stoppuhr zu starten und zu stoppen. Und im 
Falle eines Timers mit Input Capture ist das paradoxerweise sogar 
genauer, weil das 'Festhalten der Zeigerstellung' eine Hardware 
verzögerungsfrei macht und man nicht darauf angewiesen ist, die (etwas) 
variable Verzögerung bis es zum Aufruf der ISR kommt, ins Kalkül ziehen 
zu müssen.

von Thomas R. (thenew5)


Lesenswert?

So das mit dem Quarz hab ich überprüft das funktioniert genau mit einer 
Sekunde LED ein-aus.

Für die Zeitstoppen mit Anfangswert merken und Endwert merken da hab ich 
noch nicht die Durchsicht wie ich das schreibe.

von Karl H. (kbuchegg)


Lesenswert?

Thomas Reiterer schrieb:
> So das mit dem Quarz hab ich überprüft das funktioniert genau mit einer
> Sekunde LED ein-aus.
>
> Für die Zeitstoppen mit Anfangswert merken und Endwert merken da hab ich
> noch nicht die Durchsicht wie ich das schreibe.

Prinzipskizze:


uint16_t startWert;
uint16_t endWert;

...

uint16_t Differenz;


ISR( ... Capture .... )
{
  endWert = ISR1;
  Differenz = endWert - startWert;

  startWert = endWert;
}


machs nicht komplizierter als es ist. Was du dann genau als startWert 
bzw. endWert hernimmst, hängt davon ab, was du eigentlich messen willst. 
Die Pulslänge oder die Periodenlänge oder ....

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.