Forum: Mikrocontroller und Digitale Elektronik Frequenz zählen mit ATMega


von Matthias P. (matthias_p65)


Lesenswert?

Hallo zusammen,

ich bin gerade dabei mir einen Frequenzzähler zu basteln. Ich verwende 
einen ATMega8, ein HD44780 Display und die LCD-Lib von Peter Fleury. 
Soweit läuft alles.

Ich zähle die Impulse am PIN D2 mittels Interrupt:

unsigned long count;
int overflows;
1
ISR(INT0_vect)
2
{
3
  if ( !(PIND & (1<<PIND2)) ) {
4
5
    count++;
6
    while (!(PIND & (1<<PIND2))) {} // hold until pin changes
7
  }
8
9
}
10
11
ISR(TIMER1_OVF_vect) {
12
13
  if (overflows % 244 == 0) {
14
    lcd_clrscr();
15
    sprintf(buffer, "%u Hz", count);
16
    lcd_puts(buffer);
17
    count = 0;
18
    overflows = 0;
19
  }
20
  overflows++;
21
22
}

Die Main Methode:
1
int main(void) {
2
3
    _delay_ms(1000);
4
    lcd_init(LCD_DISP_ON);
5
6
    DDRD = (0<<DDD0) |
7
           (0<<DDD1) |
8
           (0<<DDD2) |
9
     (0<<DDD3) |
10
     (0<<DDD4);
11
12
    count = 0;
13
    overflows = 0;
14
15
    lcd_clrscr();
16
17
    // enable interrupts
18
    GICR = (1<<INT0);
19
    MCUCR = (1 << ISC01);
20
    sei();
21
22
    TCCR1A=0x00;
23
    // No prescale, we need full accuracy
24
    TCCR1B=0x01;
25
    OCR1AH=0x00;
26
    OCR1AL=0x00;
27
    OCR1BH=0x00;
28
    OCR1BL=0x00;
29
    // Start timer
30
    TIMSK |= (1<<TOIE1);
31
32
    while (1) {
33
    }
34
35
    return 0;
36
}

Nach 244 16-Bit Timer Überläufen, was bei 16MHz etwa einer Sekunde 
entspricht, wird die Anzahl der gezählten Trigger Impulse am Pin 
ausgegeben. Bis ziemlich genau 65535 Hz klappt das auch Prima. Alles was 
höher liegt, scheint für einen Überlauf zu sorgen, den ich mir nicht 
erklären kann.

Im Display wird dann F-65535 Hz angezeigt. Ich kann mir gerade nicht 
erkären woran das liegt, zumal die Variable count ja ein unsigned long 
ist und somit ausreichend dimensioniert sein sollte.

Hat jemand von euch eine Idee, wo hier mein Denkfehler liegt?

Gruß
Matthias

von Typ (Gast)


Lesenswert?

Der Fehler liegt im %u bei deinem sprintf()-Aufruf. Das gibt dir nur 
einen unsigned int aus, keinen unsigned long.

von Ulrich (Gast)


Lesenswert?

Der Fehler liegt bei der Ausgabe, genauer beim %u im sprintf. Das sorgt 
dafür das nur 16 Bits genutzt werden. Wobei die Ausgabe schon ein 
Problem an sich ist: das sprintf kann man besser durch ein kürzeres 
ltoa() ersetzen - das reduziert die Rechenzeit und den Speicherbedarf 
erheblich. Auch ist die LCD Ausgabe in der ISR nicht so gut. Damit 
verpasst man ggf. sogar einen Interrupt, und ggf. ein paar Pulse.

von Matthias P. (matthias_p65)


Lesenswert?

Oh Mann, manchmal hab ich echt Tomaten auf den Augen.

Das war das Problem.

Danke!

von Matthias P. (matthias_p65)


Lesenswert?

Hab das jetzt noch ein wenig umgebaut, allerdings steigt der Zähler 
jetzt bei etwa 190kHz aus, obwohl ich ein sauberes Signal reingebe.

von Marius W. (mw1987)


Lesenswert?

Nur so nebenbei: Dein Code mag zwar funktionieren, aber absolut 
schauderhaft.

1. Man wartet nicht in Interrupt-Routinen.
2. LCD/UART-Ausgaben gehören auch nicht in eine Interrupt-Routine.
3. overflows % 244 == 0 solltest du durch overflows == 244 ersetzen 
(vergleichen ist einfacher als dividieren!)
4. sprintf ist totaler Overkill. Schau dir itoa und Konsorten an.

Gruß
Marius

von Matthias P. (matthias_p65)


Lesenswert?

Wenn ich die PIN Abfrage in der Interrupt Routine weglasse und
den Interrupt mit

MCUCR = (1<<ISC01) | (1<<ISC00);

auf die fallende Flanke setze, komme ich schon bis etwa 260kHz.

Da ist dann allerdings wieder Schluss. Gibt es ggf. eine bessere Methode 
die Impulse am Pin zu messen, oder steckt hier noch ein Denkfehler?

von Matthias P. (matthias_p65)


Lesenswert?

Marius Wensing schrieb:
> Nur so nebenbei: Dein Code mag zwar funktionieren, aber absolut
> schauderhaft.

Ich weiss. ;) Siehe unten.

> 1. Man wartet nicht in Interrupt-Routinen.
> 2. LCD/UART-Ausgaben gehören auch nicht in eine Interrupt-Routine.
> 3. overflows % 244 == 0 solltest du durch overflows == 244 ersetzen
> (vergleichen ist einfacher als dividieren!)
> 4. sprintf ist totaler Overkill. Schau dir itoa und Konsorten an.

Schaut dann jetzt so aus, ich denke das ist wesentlich besser
1
char buffer[16];
2
3
unsigned long count;
4
int overflows;
5
6
int update;
7
8
ISR(INT0_vect)
9
{
10
  count++;
11
}
12
13
ISR(TIMER1_OVF_vect) {
14
15
  if (overflows == 244) {
16
    update = 1;
17
    overflows = 0;
18
  }
19
  overflows++;
20
21
}
22
23
int main(void) {
24
25
  _delay_ms(1000);
26
27
    lcd_init(LCD_DISP_ON);
28
29
    DDRD = (0<<DDD0) |
30
       (0<<DDD1) |
31
       (0<<DDD2) |
32
       (0<<DDD3) |
33
       (0<<DDD4);
34
35
36
  count = 0;
37
  overflows = 0;
38
39
  lcd_clrscr();
40
41
  // enable interrupts
42
  GICR = (1<<INT0);
43
  // interrupt on INT0 pin falling edge (sensor triggered)
44
  MCUCR = (1<<ISC01) | (1<<ISC00);
45
46
  // enable interrupts
47
  sei();
48
49
    TCCR1A=0x00;
50
    // No prescale, we need full accuracy
51
    TCCR1B=0x01;
52
    OCR1AH=0x00;
53
    OCR1AL=0x00;
54
    OCR1BH=0x00;
55
    OCR1BL=0x00;
56
    // Start timer
57
    TIMSK |= (1<<TOIE1);
58
59
    update = 0;
60
61
  while (1) {
62
63
    if (update == 1) {
64
      lcd_clrscr();
65
      ltoa (count,buffer,10);
66
      lcd_puts(buffer);
67
      update = 0;
68
      count = 0;
69
    }
70
71
  }
72
73
  return 0;
74
}

Dennoch ist die magische Grenze etwa 260kHz. Vorschläge?

von Marius W. (mw1987)


Lesenswert?

Sieht wirklich schon besser aus. Aber trotzdem noch Fehler drin.

1. Du musst alle Variablen, die du im Interrupt UND im normalen Programm 
benutzt volatile kennzeichnen. Sonst bekommt der Compiler möglicherweise 
die Änderung der Variablen nicht mit.

2. Du solltest count in deinem if-Block in eine lokale Variable 
umkopieren. Und zwar bei deaktivierten Interrupts (TIPP: util/atomic.h). 
Sonst könnte es passieren, dass sich während der Umwandlung der Wert von 
count ändert (durch einen Interrupt) und die Umwandlung total schief 
geht.

Gruß
Marius

von Typ (Gast)


Lesenswert?

Nutze am Besten die Counter-Funktion von Timer/Counter0. Der zählt "in 
Hardware", du musst also nicht ständig in die ISR springen.

Also so:
Den Counter so einstellen, dass er bei steigender Flanke hochzählt. 
Überläufe mit einem Software-Zähler mitzählen. (Mit dem 
Überlauf-Interrupt des Counters)

Mit Timer1 jede Seunde einen Interrupt erzeugen, in der du die gezählten 
Takte auswertest und zurücksetzt.

Und natürlich: Die schnellstmögliche Taktfrequenz nutzen. ;-)

von MaWin (Gast)


Lesenswert?

> ich denke das ist wesentlich besser

Ja.

> Dennoch ist die magische Grenze etwa 260kHz.

Das sind bei 16 Mhz CPU Takt etwa 60 Instruktionen.

Es ist nicht auszuschliessen, daß
ISR(INT0_vect)
{
  count++;
}
da dran kommt, schliesslich ist count ein 32 bit Wert
und du hast nur eine 8 bit CPU.

Mit Assembler holt man vermutlich etwas mehr raus,
aber letztlich den Schub bringt es, deinen Takteingang
auf PD4=T0 umzuhängen, 256 Impulse per Timer0 zählen
zu lassen, und bei Überlauf quasi gleich 256 auf den
count draufzuschlagen.

Dann muß man aber beim auslesen von count auch den
aktuellen Stand von T0 draufaddieren.

Das
      ltoa (count,buffer,10);
      lcd_puts(buffer);
      update = 0;
      count = 0;
ist ungünstig, denn count kann schon erhöht worden
sein, so daß einige Takt fehlen.

cli();
      i=count;
      count=0;
sei();
      ltoa (i,buffer,10);
      lcd_puts(buffer);
      update = 0;

ist besser. Beim Auslesen des Timers sollte man sogar
2 mal drauf zugreifen, um zu erfahren, ob er sich
zwischendurch verändert hat und einen Überlauf produzierte.

von Matthias P. (matthias_p65)


Lesenswert?

Danke Leute, ihr seid sehr hilfsbereit!

Ich werde mich mal an diese Variante machen:

Typ schrieb:
> Nutze am Besten die Counter-Funktion von Timer/Counter0. Der zählt "in
> Hardware", du musst also nicht ständig in die ISR springen.
>
> Also so:
> Den Counter so einstellen, dass er bei steigender Flanke hochzählt.
> Überläufe mit einem Software-Zähler mitzählen. (Mit dem
> Überlauf-Interrupt des Counters)
>
> Mit Timer1 jede Seunde einen Interrupt erzeugen, in der du die gezählten
> Takte auswertest und zurücksetzt.
>
> Und natürlich: Die schnellstmögliche Taktfrequenz nutzen. ;-)

Gruß
Matthias

von Axel S. (a-za-z0-9)


Lesenswert?

http://www.mikrocontroller.net/articles/Frequenzz%C3%A4hlermodul

Matthias P. schrieb:

> ich bin gerade dabei mir einen Frequenzzähler zu basteln. Ich verwende
> einen ATMega8, ein HD44780 Display und die LCD-Lib von Peter Fleury.

Ungefähr damit hab ich auch angefangen. Guckstu Frequenzzählermodul


XL

von m.n. (Gast)


Lesenswert?

Mach Deine Erfahrungen mit der Torzeit gesteuerten Lösung, die 
allerdings für niedrige Frequenzen < 10kHz keine sonderlich guten 
Ergebnisse liefert.

Als Anregung, wie man viele Probleme umgehen kann und niedrige+hohe 
Frequenzen elegant misst: 
http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp1

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.