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
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.
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
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?
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?
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
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. ;-)
> 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.
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
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