Forum: Mikrocontroller und Digitale Elektronik Probleme mit AVR Frequenzmessung


von pit (Gast)


Lesenswert?

Hallo, habe im Internet eine Schaltung für einen LCF Meter entdeckt, 
leider ist es schlecht dokumentiert und nun wollte ich mal ein eigenes 
Projekt erstellen. Leider habe ich am Anfang schon ein 
Verständnisproblem. Habe mit einem Atmega8 den Timer 1 im CTC Modus 
einen 1s Takt erzeugt, mit dem Timer0 möchte ich die Frequenz erfassen. 
Habe als Referenz einen 4MHz Quarzoszillator, die Schaltung misst aber 
3.97xxxxxMhz. Warum die Abweichung, da der Oszillator auf 500 ppm genau 
sein soll. Zweitens habe ich eine Frage zur Genauigkeit. Bei der 
Addition von doble und int kommt ein falscher Wert heraus. Ich addiere 
zur Frequenz testweise 0.05 dazu, aber statt z.B. 3975123.05 kommt 
3975123.00 heraus. Warum?
Anbei der Code. Wäre toll wenn mir jemand eine Erklärung und Tipps geben 
könnte, da ich noch sehr am Anfang stehe, danke.
Hier der Code:
1
// Einbinden der erforderlichen Bibliotheken
2
3
#include <avr/io.h>
4
#include <avr/interrupt.h>
5
#include <stdio.h>
6
#include <util/delay.h>
7
#include "lcd.h"
8
9
    // benutzt zum Berechnen der Ergebnisse
10
volatile uint32_t frequenz;               // aktuelle Frequenzmessung
11
volatile uint32_t counter;                // inkrementiert bei jedem Timer 0 Überlauf
12
13
// F Frequenz messen
14
void messe_f(double ausgabe)
15
{
16
  char line1_txt[16];
17
  char line2_txt[16];
18
  sprintf(line1_txt,"Fx=%.0f Hz\n",ausgabe);
19
  double test2=0.05;
20
  test2=frequenz+test2;
21
  sprintf(line2_txt,"t =%.2f",test2);
22
  lcd_clrscr();
23
  lcd_puts(line1_txt);
24
  lcd_puts(line2_txt);
25
}
26
// initialisiere timer
27
void timer_init()
28
{
29
  // Setzen für Frequenzmessung
30
  // Timer/Counter 0 initialisieren
31
  // Bit2 = 1 CS02: Externer takt an T0 pin (PD4). steigende Flanke
32
  // Bit1 = 1 CS01:                  "
33
  // Bit0 = 1 CS00:                  "
34
  TCCR0 |= (1 << CS00) | (1 <<CS01) | (1 << CS02);  //0x07
35
  
36
  // initialisiere timer0
37
  TCNT0 = 0;
38
  
39
  // set up timer1 CTC mode und prescale = 256
40
  TCCR1B |= (1 << WGM12)|(1 << CS12);
41
  
42
  // initialisiere timer1
43
  TCNT1 = 0;
44
  
45
  // initialisiere compare value
46
  OCR1A = 62499;      // 1s, 16 MHz, Teiler 256
47
  
48
  // erlaube compare interrupt
49
  TIMSK |= (1 << OCIE1A) | (1 << TOIE0);
50
  
51
  // Analog Comparator: Off
52
  ACSR |= (1 << ACD);
53
}
54
55
// Timer 0 Überlauf Interrupt Service Routine, zählt den counter bei Overflow hoch
56
ISR(TIMER0_OVF_vect)
57
{
58
  counter++;
59
}  
60
61
ISR (TIMER1_COMPA_vect)
62
{
63
  // Interrupts verbieten
64
  cli();
65
  // Frequenz ist die Anzahl der Timer 0 counts.
66
  frequenz = (255 * counter) + TCNT0;
67
  messe_f(frequenz);
68
  counter = 0;
69
  TCNT0 = 0;
70
  sei();
71
}
72
73
74
// Hauptprogramm
75
int main(void)
76
{
77
  // IO-Ports initialisieren und vorbelegen
78
  DDRC |= (1 << DDC5) | (1 << DDC4) | (1 << DDC3) | (1 << DDC2);
79
  DDRC &= ~ ((1 << DDC6) | (1 << DDC1) | (1 << DDC0));
80
  // PC6=in, RESET
81
  // PC5=out, PC4=out, PC3=out, PC2=out, Display DB7-DB4
82
  // PC1=in, PC0=in, Mode2 und Mode1
83
  PORTC |= (1 << PC0);    // Methode F (0x01)
84
  
85
  DDRB |= (1 << DDB0) | (1 << DDB1) | (1 << DDB2) | (1 << DDB3) | (1 << DDB4) | (1 << DDB5) | (1 << DDB6) | (1 << DDB7);
86
  // PB0=out, PB1=out, PB2=out, RS-R/W-E Display
87
  // PB3=out, PB4=out, PB5=out, MOSI-MISO-SCK
88
  // PB6=out, PB7=out, XTAL1-XTAL2
89
  PORTB &= ~ ((1 << PB0) | (1 << PB1) | (1 << PB2) | (1 << PB3) | (1 << PB4) | (1 << PB5) | (1 << PB6) | (1 << PB7));
90
  
91
  DDRD |= (1 << DDD3) | (1 << DDD2) | (1 << DDD1) | (1 << DDD0);
92
  DDRD &= ~ ((1 << DDD7) | (1 << DDD6) | (1 << DDD5) | (1 << DDD4));
93
  // PD0=out, PD1=out, PD2=out, PD3=out, high-low-entladen-ladenled
94
  // PD4=in, T0
95
  // PD5=NC, PD6=in, PD7=in, AIN0-AIN1
96
  PORTD |= (1 << PD2) | (1 << PD1) | (1 << PD0);
97
  
98
  {
99
    timer_init();  
100
  }
101
  // Globale Interrupts verhindern
102
  // LCD module initialisieren
103
  cli();
104
  lcd_init(LCD_DISP_ON);
105
  lcd_clrscr();
106
  lcd_puts("Initialisierung des Messgeraets.");
107
  _delay_ms (500);
108
  // loop forever
109
  sei();
110
  while(1)
111
  {
112
    
113
  };
114
}

von Detlef K. (adenin)


Lesenswert?

pit schrieb:
> Bei der
> Addition von doble und int kommt ein falscher Wert heraus. Ich addiere
> zur Frequenz testweise 0.05 dazu, aber statt z.B. 3975123.05 kommt
> 3975123.00 heraus. Warum?

Versuch mal:
test2=(double)frequenz+test2;

von Peter II (Gast)


Lesenswert?

pit schrieb:
> Ich addiere
> zur Frequenz testweise 0.05 dazu, aber statt z.B. 3975123.05 kommt
> 3975123.00 heraus. Warum?

double ist bei Atmel nur float, also nur 7-8 echte stellen. Damit kann 
das nicht genauer rechnen.

http://de.wikipedia.org/wiki/Einfache_Genauigkeit

von Ulrich H. (lurchi)


Lesenswert?

Die gemessene Frequenz stimmt nicht ganz, weil die Berechnung der 
Frequenz und Ausgabe einige Zeit benötigt. Damit geht eine Teil der 1 s 
Messzeit zum Zählen verloren.
Umgehen ließe sich dieses Problem, indem der Zähler nicht zurück gesetzt 
wird, sondern die Differenz zum letzten Zählerstand berechnet wird.

Ein zusätzliches Problem ergibt sich dadurch, dass der Zähler für die 
Überläufe von Timer0 und das Auslesen von Timer0 nicht unbedingt 
synchron sind. Hier gibt es 2 mögliche Lösungen:
Der erste wäre den Timer0 an zu halten und erst dann die Werte 
auszulesen, incl. eines ggf. noch ausstehenden Interrupts. Die dabei 
verlorene Zeit muss man berücksichtigen, etwa indem man Timer 1 neu 
programmiert.
Alternativ kann man auch den laufenden Timer auslesen, muss dann aber 
aufpassen ob ggf. eine Interrupt für den Überlauf noch aussteht. Das 
geht, ist aber nicht ganz trivial.

Schließlich gäbe es für eher niedrige Frequenzen (z.B. 50 kHz) noch die 
Möglichkeit eine Reziproke Zeitmessung mit Hilfe der ICP Funktion zu 
nutzen. Das gibt etwas mehr Auflösung, bzw. eine schnellere Messung. Die 
Schwierigkeiten (und Lösungen dazu) bei der Software sind aber fast die 
gleichen.

von m.n. (Gast)


Lesenswert?

Ich zeig Dir ein Beispiel, was Du vielleicht als Referenz nehmen kannst, 
und was auch funktioniert: 
http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp1

Es ist zwar für den ATmega88 geschrieben, sollte aber auch auf dem 
ATmega8 laufen. Die LCD-Ausgabe kannst Du ja sicher an Deine Hardware 
anpassen.

von Hans-Peter D. (pitdahl)


Lesenswert?

Detlef Kunz schrieb:
> pit schrieb:
>> Bei der
>> Addition von doble und int kommt ein falscher Wert heraus. Ich addiere
>> zur Frequenz testweise 0.05 dazu, aber statt z.B. 3975123.05 kommt
>> 3975123.00 heraus. Warum?
>
> Versuch mal:
> test2=(double)frequenz+test2;

Danke, aber nein, daran liegt es nicht.
Gruß Pit

von Hans-Peter D. (pitdahl)


Lesenswert?

Peter II schrieb:
> pit schrieb:
>> Ich addiere
>> zur Frequenz testweise 0.05 dazu, aber statt z.B. 3975123.05 kommt
>> 3975123.00 heraus. Warum?
>
> double ist bei Atmel nur float, also nur 7-8 echte stellen. Damit kann
> das nicht genauer rechnen.
>
> http://de.wikipedia.org/wiki/Einfache_Genauigkeit

Danke für den Tip, scheint daran zu liegen, das nur mit 7-8 Stellen 
gerechnet wird.
Gruß Pit

von Hans-Peter D. (pitdahl)


Lesenswert?

Ulrich H. schrieb:
> Die gemessene Frequenz stimmt nicht ganz, weil die Berechnung der
> Frequenz und Ausgabe einige Zeit benötigt. Damit geht eine Teil der 1 s
> Messzeit zum Zählen verloren.
> Umgehen ließe sich dieses Problem, indem der Zähler nicht zurück gesetzt
> wird, sondern die Differenz zum letzten Zählerstand berechnet wird.
>
> Ein zusätzliches Problem ergibt sich dadurch, dass der Zähler für die
> Überläufe von Timer0 und das Auslesen von Timer0 nicht unbedingt
> synchron sind. Hier gibt es 2 mögliche Lösungen:
> Der erste wäre den Timer0 an zu halten und erst dann die Werte
> auszulesen, incl. eines ggf. noch ausstehenden Interrupts. Die dabei
> verlorene Zeit muss man berücksichtigen, etwa indem man Timer 1 neu
> programmiert.
> Alternativ kann man auch den laufenden Timer auslesen, muss dann aber
> aufpassen ob ggf. eine Interrupt für den Überlauf noch aussteht. Das
> geht, ist aber nicht ganz trivial.
>
> Schließlich gäbe es für eher niedrige Frequenzen (z.B. 50 kHz) noch die
> Möglichkeit eine Reziproke Zeitmessung mit Hilfe der ICP Funktion zu
> nutzen. Das gibt etwas mehr Auflösung, bzw. eine schnellere Messung. Die
> Schwierigkeiten (und Lösungen dazu) bei der Software sind aber fast die
> gleichen.
Hi Ulrich,

das scheint mir plausibel. Muss mir dann mal Gedanken machn wie ich das 
Programm umschreiebe um die Genauigkeit zu steigern. Gibt es eine 
Möglichkeit herauszufinden wieviele Takte "verloren" gehen für die 
Berechnungen? Kann man das mit dem Simulator herausfinden? Oder ist das 
bei Timerbenutzung nicht möglich.
P.S. Kennt jemand einen gutes Tutorial im Umgang mit dem Simulator?
Gruß Pit

von Hans-Peter D. (pitdahl)


Lesenswert?

m.n. schrieb:
> Ich zeig Dir ein Beispiel, was Du vielleicht als Referenz nehmen kannst,
> und was auch funktioniert:
> http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp1
>
> Es ist zwar für den ATmega88 geschrieben, sollte aber auch auf dem
> ATmega8 laufen. Die LCD-Ausgabe kannst Du ja sicher an Deine Hardware
> anpassen.

Hi,
danke für den Link, werde mal schauen ob mir das weierhilft.
Gruß Pit

von Ulrich H. (lurchi)


Lesenswert?

Wie lange ein Stück Code braucht, kann man im Simulator recht gut 
ansehen. Allerdings kann sich die Laufzeit durch eine andere 
Compiler-version oder ggf. auch Änderungen im Code, die zu anderer 
Optimierung führen, ändern.

Der bessere Weg ist es die Abhängigkeit von der Laufzeit weitgehend zu 
vermeiden. Dafür gibt es verschiedene Möglichkeiten, z.B. am Anfang und 
Ende der Laufzeit den Timer mit dem gleichen Code auslesen und die Zahl 
durch Differenzbildung zu bestimmen. Wenn man sich mit den Latenzzeiten 
für den Interrupt richtig mühe gibt kriegt man das auch auf den Zyklus 
genau hin, ist aber nicht so einfach, weil beim Aufruf der ISR 
Verzögerungen vorkommen können. Alternativ kann man auch so ähnlich mit 
dem selben code Teil den externen Eingang freigeben bzw. blockieren.

Der bessere Weg ist aber die Nutzung der ICP Funktion für eine Reciproke 
Frequenzmessung. Das ist wohl auch einfacher als die klassische Zählung 
mit exakter Torzeit in Software.

von Hans-Peter D. (pitdahl)


Lesenswert?

Ulrich H. schrieb:
> Wie lange ein Stück Code braucht, kann man im Simulator recht gut
> ansehen. Allerdings kann sich die Laufzeit durch eine andere
> Compiler-version oder ggf. auch Änderungen im Code, die zu anderer
> Optimierung führen, ändern.
>
> Der bessere Weg ist es die Abhängigkeit von der Laufzeit weitgehend zu
> vermeiden. Dafür gibt es verschiedene Möglichkeiten, z.B. am Anfang und
> Ende der Laufzeit den Timer mit dem gleichen Code auslesen und die Zahl
> durch Differenzbildung zu bestimmen. Wenn man sich mit den Latenzzeiten
> für den Interrupt richtig mühe gibt kriegt man das auch auf den Zyklus
> genau hin, ist aber nicht so einfach, weil beim Aufruf der ISR
> Verzögerungen vorkommen können. Alternativ kann man auch so ähnlich mit
> dem selben code Teil den externen Eingang freigeben bzw. blockieren.
>
> Der bessere Weg ist aber die Nutzung der ICP Funktion für eine Reciproke
> Frequenzmessung. Das ist wohl auch einfacher als die klassische Zählung
> mit exakter Torzeit in Software.

Hi Ulrich,
danke schon mal für die Infos. Ich werde mich noch mal etwas einlesen 
müssen in Sachen Timer, Interrupts und Simulator. Vor Allem die ICP 
Funktion werde ich mir mal ansehen.
Ich bin aber schon mal ganz froh, die im Internet entdeckte Schaltung 
von einem 8515 auf einen Atmega8 umgeschrieben und geändert zu haben. 
War einfach mal die Herausforderung die vielen ungenutzten Eingänge beim 
8515 zu optimieren. Die Schaltung funktioniert schon mal fürs Messen von 
Frequenzen, kleinen Kondensatoren, sowie Spulen und über die "Tau 
Messung" auch von großen Elkos.
Die Ungenauigkeiten muss ich nun über Programmänderungen optimieren, da 
ich aber einen ISP Anschluß mit eingebaut habe, kann ich neue Programme 
gut testen. Auf jeden Fall lernt man imens durch so ein Projekt.
Gruß Pit

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.