Forum: Compiler & IDEs Programm zur Frequenzmessung


von Peter (Gast)


Lesenswert?

Hallo Forum

Ich bin noch Anänger in Mikrocontroller Programmierung. Ich möchte ein 
Programm schreiben das die Freuqenz eines Rechtecksignals Berechnet
Ich möchte dieses mit der Capture Mode des Timer1 (ATMEGA168) 
realisieren.

Ich hab das Programm unten mal angehängt Im Debuggermodus scheint das 
Programm das zu tun was es soll villeciht kann mir jemand einen Tip 
geben ob ich auf dem richtigen weg bin. Bzw bin ich über alle 
verbesserungsvorschläge dankbar


mfg





1
//*********** Includes***************************************************
2
#include <avr\io.h>
3
#include <avr\interrupt.h>
4
#include  <util\delay.h>
5
6
7
8
//************************************************************************
9
10
11
//*********** Systemkonstanten******************************************
12
#define F_CLOCK    2E7     // 20MHz CPU Takt
13
#define Zahn    6     // 6 Grad Zahn Teilung
14
15
16
//**********************************************************************
17
18
19
//*********** Globale Variablen******************************
20
double test,b_f;// b_f=1 wenn negative flanke
21
double ueberlauf=0;
22
double init=0; //initialisierung bei 1.neg Flanke
23
double   counter=0;
24
double  counterold=0; //Zählerstand N-1.fallende Flanke
25
double counternew=0; //Zählerstand N fallende Flanke
26
double diff=0;  //Differnez Zählerstand N und Zählerstand N-1
27
double cmax=65535; // max Zähler des 16 Bit counters
28
double help=0;//Hilfsgrösse
29
double flanke=0;//Hilfsgrosse ür dsp
30
double Frequenz=0;
31
long zahl=0;
32
unsigned int ICR1Htemp;
33
unsigned int ICR1Ltemp;
34
//**********************************************************************
35
36
//*********** Interrupt für fallende Flanke******************************
37
ISR(TIMER1_CAPT_vect)
38
39
{
40
41
//PORTB ^= 0xFF;
42
43
ICR1Htemp=ICR1H;
44
ICR1Ltemp=ICR1L;
45
46
counter=ICR1Htemp*0x100+ICR1Ltemp;
47
//counter=ICR1H*0x100+ICR1L;//16 Bit Verrechung H und L Reg Timer 1 capure Register
48
49
50
  
51
  
52
  
53
  
54
    if(b_f==0) //Erkennung 1. Flanke 
55
56
    {
57
    
58
    
59
    
60
    counterold=counter;//Zählerstand merken
61
    ueberlauf=0;    
62
    b_f=1;        
63
    }
64
65
    //if(b_f==1)
66
    else
67
    {
68
    counternew=counter;//Zählerstand merken
69
    //diff=counternew-counterold;
70
    
71
    
72
    
73
    help=(ueberlauf-1)*cmax;
74
    diff=cmax-counterold+(ueberlauf-1 *cmax+counternew;//Berechnungsformel 
75
    
76
    ueberlauf=0;
77
    counterold=counternew;
78
79
    
80
81
82
83
        
84
  
85
    }
86
87
88
89
90
}
91
92
93
//************************************************************************
94
95
96
//***********Interrupt für Zähler Überlauf********************************
97
ISR( TIMER1_OVF_vect)
98
{
99
ueberlauf++;
100
}
101
//***********************************************************************
102
103
104
 
105
int main(void)
106
{
107
 
108
109
110
//---------------------Timer 1 Konfiguration----------------------------
111
112
  TCCR1B = 0x01;  // capture neg. Flanke und prescaler 1
113
114
115
116
    TIMSK1 = 0x21;  // interrupt Enable ovfer flow Enable &Capture Enable
117
  sei();// interrups global zulassen
118
119
120
121
//---------------------------------------------------------------
122
 
123
   
124
 
125
  
126
while(1==1)
127
 
128
129
}

  

von Stefan (Gast)


Lesenswert?

Was mir nicht gefällt, ist die Formatierung des Quellcodes. Aber das ist 
ein kleines Problem ;-)
1
//****************
2
//*** Includes ***
3
//****************
4
#include <avr\io.h>
5
#include <avr\interrupt.h>
6
#include <util\delay.h>
7
8
//************************
9
//*** Systemkonstanten ***
10
//************************
11
#define F_CLOCK 2E7  // 20MHz CPU Takt
12
#define Zahn    6    // 6 Grad Zahn Teilung
13
14
//*************************
15
//*** Globale Variablen ***
16
//*************************
17
double test,b_f;     // b_f=1 wenn negative flanke
18
double ueberlauf=0;
19
double init=0;       // initialisierung bei 1.neg Flanke
20
double counter=0;
21
double counterold=0; // Zählerstand N-1.fallende Flanke
22
double counternew=0; // Zählerstand N fallende Flanke
23
double diff=0;       // Differnez Zählerstand N und Zählerstand N-1
24
double cmax=65535;   // max Zähler des 16 Bit counters
25
double help=0;       // Hilfsgrösse
26
double flanke=0;     // Hilfsgrosse für dsp
27
double Frequenz=0;
28
long zahl=0;
29
unsigned int ICR1Htemp;
30
unsigned int ICR1Ltemp;
31
32
//***********************************
33
//***Interrupt für fallende Flanke***
34
//***********************************
35
ISR(TIMER1_CAPT_vect)
36
{
37
    // PORTB ^= 0xFF;
38
39
    ICR1Htemp = ICR1H;
40
    ICR1Ltemp = ICR1L;
41
42
    // 16 Bit Verrechung H und L Reg Timer 1 capture Register
43
    counter = ICR1Htemp * 0x100 + ICR1Ltemp;
44
    // counter = ICR1H * 0x100 + ICR1L;
45
46
    if(b_f == 0) //Erkennung 1. Flanke
47
    {
48
        counterold = counter; //Zählerstand merken
49
        ueberlauf = 0;
50
        b_f = 1;
51
    }
52
    else
53
    {
54
        counternew = counter; //Zählerstand merken
55
        // diff = counternew-counterold;
56
57
        help = (ueberlauf-1) * cmax;
58
59
        //Berechnungsformel
60
        diff = cmax - counterold + (ueberlauf-1) * cmax + counternew; 
61
62
        ueberlauf = 0;
63
        counterold = counternew;
64
    }
65
}
66
67
//**********************************
68
//***Interrupt für Zählerüberlauf***
69
//**********************************
70
ISR(TIMER1_OVF_vect)
71
{
72
    ueberlauf++;
73
}
74
75
//*******************
76
//***Hauptprogramm***
77
//*******************
78
int main(void)
79
{
80
    //------Timer 1 Konfiguration-------------------------
81
    TCCR1B = 0x01;  // capture neg. Flanke und prescaler 1
82
    TIMSK1 = 0x21;  // Interrupt Enable 
83
                    // & Overflow Enable 
84
                    // & Capture Enable
85
86
    sei();          // interrups global zulassen
87
88
    //----------------------------------------------------
89
    while(1==1);
90
}

Dann stört mich die Rechnerei mit den vielen double Variablen. Der Timer 
liefert von Natur aus einen Ganzzahlwert und den sollte man nicht in 
einen Gleitkommawert vergewaltigen. Bleibst du bei Ganzzahlen, 
vereinfacht sich die Rechnung auf Assemblerebene. Das spart Platz und 
bringt Speed.

Speed ist ggf. notwendig, wenn du sie Berechnung innerhalb der 
Interruptroutine machst. Der nächste Interrupt ist dir nämlich schon auf 
den Fersen und lauert nur darauf, dass er dir Variablen überschreiben 
kann, mit denen du noch fleissig am rechnen bist.

Abhilfe ist entweder schnell den Interrupt abzuarbeiten oder während der 
Interruptbearbeitung Interrupts zu sperren (weniger sinnvoll bei einem 
Timer/Zähler).

Die schnellere Bearbeitung könnte z.B. so aussehen, dass du zunächst nur 
Variablen füllst, die später im Hauptprogramm ausgewertet werden. Auch 
den Spezialfall der 1. Flanke kannst du bei der Auswertung behandeln und 
du musst nicht bei jedem Interrupt den alten Käse vom Anfang per if 
testen. Vor dem Einschalten der Interrupts den Zählerstand holen und 
pronto hast du den counterold-Wert. Und wie gesagt, keine oder sowenig 
wie möglich Gleitkommabearbeitung. Maximal diff als Gleitkommawert 
berechnen. Besser wäre es sich Gedanken über den Wertebereich zu machen 
und einen passenden Typ auswählen. Mit unsigned long kann man z.B. bis 
über 4 GHz ausgeben ;-)

Für unveränderliche Werte wie dieses cmax solltest du auch eine 
Konstante nehmen, also z.B. ein Makro definieren.

von Peter (Gast)


Lesenswert?

Hallo Stefan

Danke erstmal für deine Antwort. Wenn ich im Debugger das Programm 
durchspiele funktioniert alles soweit. Wenn ich es aber auf dem u 
controller spiele und will mir eine Frequenz ausgeben lassen. Kommt nur 
müll raus. Kannst Du mir eventuell bei meinem Problem helfen??


mfg

von Karl H. (kbuchegg)


Lesenswert?

Sicher.
Stell mal das komplette Programm rein.

Ansonsten:
* Stell sicher, dass die Ausgafunktion funktioniert.
* Lass dir erst mal die gezählten counter Ereignisse ausgeben
  (eine bekannte Frequenz anlegen und nachschauen ob das
   gewünschte Ergebnis rauskommt)
* Erst wenn counter den richtigen Wert hat, rechne um
  auf die Frequenz
* ISR Funktionen kurz halten.
  In der ISR wirklich nur die Flanken zählen (Dazu reicht
  auch ein int + ein int für die Overflows). Die eigentliche
  Berechnung und Ausgabe in der Hauptschleife machen.
* Für einen Frequenzzähler brauchst du auch noch einen
  zweiten Timer, der dir zb. 0.1 Sekunden Intervalle
  generiert.

* Verzichte auf double. Du brauchst sie in der ISR nicht.
* Achte auf deine Programmformatierung.
  Zumindest ich weigere mich so ein unübersichtliches Chaos
  wie da oben auch nur näher in Augenschein zu nehmen.

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Karl heinz

Anbei mal das ganze programm. Ich hab versucht es ein wenig bessert zu 
ormatieren. Auch das sperren der Interrupts hat nix gebracht. Villeicht 
hättest Du mal zeit eine Blick darauf zu werfen weis momentan echt nicht 
wo der fehler liegen könnt. Wenn ich es im Debugger durchspiele kann ich 
einen ehler Finden aber am uc kommt nur Müll raus




merci
Peter

ps: Ich möcht das Programm mal zum laufen bringen, da es mir ausreicht 
frequenzen mit 10khz zu messen kann ich es bzgl doubles auch später noch 
abspecken

von Karl H. (kbuchegg)


Lesenswert?

Du hast nichts von den bereit svorgeschlagenen Dingen
umgesetzt.
Ersetz mal die double durch int!

In einer ISR ist Zeit sparen oberstes Gebot! Floating
Point Berechnungen, die man auch in int oder long
machen kann, gehören da auch dazu!

Ansonsten: Zuerst muss der counter Wert stimmen. Hast du
das überprüft? Mach das mal.

von Karl H. (kbuchegg)


Lesenswert?

Und sag bitte nicht: Da kommt nur Müll raus.
Darunter kann sich nämlich keiner was vorstellen. Das
hilft niemandem weiter, gibt keinerlei Hinweise in
welche Richtung man suchen soll.

von Karl heinz B. (kbucheg)


Lesenswert?

> ps: Ich möcht das Programm mal zum laufen bringen, da es mir
> ausreicht frequenzen mit 10khz zu messen kann ich es bzgl doubles
> auch später noch abspecken

Das nicht vollzogene 'Abspecken' ist einer der Gründe warum
dein Pgm nicht funktioniert. Du kommst mit dem Timing nicht
hin!
Wahrscheinlich. Ohne die Hardware hier zu haben
und Messungen vornehmen zu können ist das aus der Entfernung
schwierig zu sagen.
Ausserdem: Ein Programm das ordentlich formatiert ist und
vernünftig geschrieben ist hat meistens weniger Fehler.
Das sagt die Erfahrung. Ich hab schon ne Menge Programme
gesehen die grauslich formatiert waren, wo Code aus vorhergehenden
Versuchen entweder einfach drinnen gelassen wurde oder auskommentiert
wurde. Das Ergebnis: Ein Programm in dem man absolut nichts mehr
sehen konnte. AUch keine Fehler. Nach einer halben Stunde Arbeit
durch Code-vereinfacheung, Umformatierung und was sonst noch
so nötig ist um das ganze übersichtlich zu kriegen ist man
dann plötzlich in einem Zustand, wo selbst ein Blinder mit
Krückstock den oder die Fehler aus 2 Meter Entfernung ertasten
kann.

von unsichtbarer WM-Rahul (Gast)


Lesenswert?

Ich hab nur mal einen Blick auf deine ISR geworfen:
1
ISR(TIMER1_CAPT_vect)
2
3
{
4
cli(); // völlig unnötig!
5
counter=ICR1H*0x100+ICR1L; // auch Quatsch: counter = ICR1; sollte reichen
6
flanke=1;    
7
sei(); // siehe cli();
8
}

Im Hauptprogramm:
1
oldcounter = 0;
2
// Timer konfigurieren und starten...
3
while(1)
4
{
5
.
6
.
7
.
8
9
  if (flanke)
10
  {
11
   flanke = 0; // Ereignis zurücksetzen.
12
   Periode = counter - oldcounter;
13
   oldcounter = counter;
14
   frequenz = 1/Periode; // das muss noch entsprechend optimiert werden...F_Clock...
15
  // Frequenz für Ausgabe formatieren...
16
  // LCD akutalisieren
17
  }

Es hilft übrigens ungemein, sich vor dem Programmieren Gedanken (auch 
auf Papier) zum eigentlichen Ablauf zu machen. Da kann man sogar schon 
optimieren...

von unsichtbarer WM-Rahul (Gast)


Lesenswert?

Was mir gerade noch auffällt:
1
lcd_home();
2
          flanke=0;
3
          lcd_clear();
4
          }


Mächtig gewaltig sinnvoll, erst etwas auf dem Display auszugeben, um es 
dann sofort wieder zu löschen...

von unsichtbarer WM-Rahul (Gast)


Lesenswert?

>F_CLOCK*cmax

ist doch eine Konstante, oder habe ich da was übersehen?
Warum wird cmax dann als double deklariert?

von Karl heinz B. (kbucheg)


Lesenswert?

Zum Thema Formatierung:
Stefan hat weiter oben schon mal gezeigt, wie eine
vernünftige Formatierung aussieht. Nichts gegen
Whitespace (also Leerraum). Der kann gut sein! Aber
4 Leerzeilen untereinander bringen nichts. Die erzeugen
nur Unübersicht, weil Dinge unnötiger weise über ner Menge
Platz verstreut werden.

Schau dir mal das hier an:
1
int main(void)
2
{
3
 
4
unsigned long i=0;   //lokale Zähler Variable
5
unsigned long rest=0;
6
b_f=0;  //flanken erkennung init
7
ueberlauf=0;//
8
diff=0;
9
counterold=0; //Zählerstand N-1.fallende Flanke
10
counternew=0; //Zählerstand N fallende Flanke
11
Frequenz=0;
12
long anz;
13
14
15
//---------------------Timer 1 Konfiguration----------------------------
16
17
TCCR1B = 0x01;  // capture neg. Flanke und prescaler 1
18
19
TIMSK1 = 0x21;  // interrupt Enable ovfer flow Enable &Capture Enable
20
sei();// interrups global zulassen
21
22
23
24
//---------------------------------------------------------------
25
 
26
wait_ms(200);
27
lcd_init();
28
lcd_write(' ');
29
lcd_write(' ');
30
lcd_write(' ');
31
lcd_write(' ');
32
lcd_write(' ');
33
lcd_write(' ');
34
lcd_write(' ');
35
lcd_write(' ');
36
lcd_write(' ');
37
lcd_write(' ');
38
lcd_write(' ');
39
lcd_write('H');
40
lcd_write('z');
41
lcd_home();
42
lcd_write ('0');
43
44
 
45
 
46
 
47
  while( 1==1 ) 
48
  {
49
  
50
51
      if(flanke==1)
52
53
      {
54
      
55
        Frequenz=F_CLOCK*cmax/diff;
56
        rest=Frequenz;
57
        for(i=1000000;i>0;i/=10)
58
          {
59
  
60
            anz=rest/i;
61
            rest %=i;
62
63
            if(rest!=zahl)
64
            {
65
66
67
              lcd_write (anz +'0');
68
              
69
            
70
            
71
            }
72
73
            
74
75
            }
76
          lcd_home();
77
          flanke=0;
78
          lcd_clear();
79
          }
80
81
82
83
84
85
86
87
}
88
}

Da schüttelts mich.

Jetzt das ganze nochmal:
1
int main(void)
2
{
3
  unsigned long i = 0;   //lokale Zähler Variable
4
  unsigned long rest = 0;
5
  long anz;
6
7
  b_f        = 0;  // Flag für die allerste Flanke auf: noch nicht da gewesen
8
  ueberlauf  = 0;  // Zählt die Anzahl der Überläufe des Timers von einem Capture zum nächsten
9
  diff       = 0;  // Die Zaehldifferenz dees Timers von einer Flanke zur nächsten
10
  counterold = 0;  // Zählerstand N-1.fallende Flanke
11
  counternew = 0; //Zählerstand N fallende Flanke
12
  Frequenz   = 0;
13
14
15
//---------------------Timer 1 Konfiguration----------------------------
16
17
  TCCR1B = ( 1 << CS10);                       // capture neg. Flanke und prescaler 1
18
  TIMSK1 = ( 1 << TICIE1 ) | ( 1 << TOIE0 );   // interrupt Enable ovfer flow Enable &Capture Enable
19
20
  sei();// interrups global zulassen
21
22
23
24
//---------------------------------------------------------------
25
 
26
  wait_ms(200);
27
  lcd_init();
28
  lcd_write(' ');
29
  lcd_write(' ');
30
  lcd_write(' ');
31
  lcd_write(' ');
32
  lcd_write(' ');
33
  lcd_write(' ');
34
  lcd_write(' ');
35
  lcd_write(' ');
36
  lcd_write(' ');
37
  lcd_write(' ');
38
  lcd_write(' ');
39
  lcd_write('H');
40
  lcd_write('z');
41
  lcd_home();
42
  lcd_write ('0');
43
 
44
  while( 1==1 ) 
45
  {
46
    if( flanke == 1 )
47
    {
48
      Frequenz = F_CLOCK * cmax / diff;
49
      rest=Frequenz;
50
51
      for( i = 1000000; i > 0; i /= 10 )
52
      {
53
        anz  = rest / i;
54
        rest %= i;
55
56
        if( rest != zahl )
57
        {
58
          lcd_write( anz +'0' );
59
        }
60
      }
61
62
      lcd_home();
63
      flanke = 0;
64
      lcd_clear();
65
    }
66
  }
67
}

Hier in der Zeile:
1
  TIMSK1 = ( 1 << TICIE1 ) | ( 1 << TOIE0 );   // interrupt Enable ovfer flow Enable &Capture Enable

fällt dir was auf? Ursprünglich stand da
  TIMSK1 = 0x21;

Obiges ist komplett äquivalent dazu. Trotzdem sehe ich in meiner Version
etwas, was du in deiner Version nicht siehst: Du aktivierst den Capture 
für
Timer 1, aktivierst aber den Overflow für Timer 0 !

Ob das hier:
1
      for( i = 1000000; i > 0; i /= 10 )
2
      {
3
        anz  = rest / i;
4
        rest %= i;
5
6
        if( rest != zahl )
7
        {
8
          lcd_write( anz +'0' );
9
        }
eine Zahl korrekt ausgibt, wage ich mal zu bezweifeln. Teil meines 
Zweifels
liegt hier
1
        if( rest != zahl )
Da Zahl den Wert 0 hat und, soweit ich auf die Schnelle gesehen habe, 
auch
nie geändert wird, bedeutet das, dass eine 0 nicht ausgegeben wird. Ein
Wert von 500 wird also als 5 ausgegeben.

Wozu der ganze Aufwand? Es ist ok, wenn man sich mal Gedanken drüber 
macht, wie
man einen int in seine Stringrepräsentierung überführt. Aber für realen 
Code
verwendet man fertige Funktionen itoa() bzw. ltoa().
Dazu braucht man natürlich eine Funktion, die einen String ausgeben 
kann.
Die ist aber kein Problem:
1
void lcd_write_string( char* String )
2
{
3
  while( *String )
4
    lcd_write( *String++ );
5
}

Damit reduziert sich die auch sofort die Initialisierungssequenz in der 
main()
1
int main(void)
2
{
3
  unsigned long i = 0;   //lokale Zähler Variable
4
  unsigned long rest = 0;
5
  long anz;
6
7
  b_f        = 0;  // Flag für die allerste Flanke auf: noch nicht da gewesen
8
  ueberlauf  = 0;  // Zählt die Anzahl der Überläufe des Timers von einem Capture zum nächsten
9
  diff       = 0;  // Die Zaehldifferenz dees Timers von einer Flanke zur nächsten
10
  counterold = 0;  // Zählerstand N-1.fallende Flanke
11
  counternew = 0;  //Zählerstand N fallende Flanke
12
  Frequenz   = 0;
13
14
15
//---------------------Timer 1 Konfiguration----------------------------
16
17
  TCCR1B = ( 1 << CS10);                       // capture neg. Flanke und prescaler 1
18
  TIMSK1 = ( 1 << TICIE1 ) | ( 1 << TOIE1 );   // interrupt Enable ovfer flow Enable &Capture Enable
19
20
  sei();// interrups global zulassen
21
22
23
24
//---------------------------------------------------------------
25
 
26
  wait_ms(200);
27
  lcd_init();
28
  lcd_write_string( "           Hz" );
29
30
  lcd_home();
31
  lcd_write ('0');
32
 
33
  while( 1==1 ) 
34
  {
35
    if( flanke == 1 )
36
    {
37
      Frequenz = F_CLOCK * cmax / diff;
38
      rest=Frequenz;
39
40
      for( i = 1000000; i > 0; i /= 10 )
41
      {
42
        anz  = rest / i;
43
        rest %= i;
44
45
        if( rest != zahl )
46
        {
47
          lcd_write( anz +'0' );
48
        }
49
      }
50
51
      lcd_home();
52
      flanke = 0;
53
      lcd_clear();
54
    }
55
  }
56
}

Noch die Ausgabe durch ltoa() ersetzen, da ein long
dicke reicht um 10000 zu speichern und wir kriegen:
1
int main(void)
2
{
3
  unsigned long i = 0;   //lokale Zähler Variable
4
  unsigned long rest = 0;
5
  long anz;
6
  char Buffer[10];       // Ausgabepuffer für itoa, 10 Zeichen sollte für einen int
7
                         // locker reichen
8
9
  b_f        = 0;  // Flag für die allerste Flanke auf: noch nicht da gewesen
10
  ueberlauf  = 0;  // Zählt die Anzahl der Überläufe des Timers von einem Capture zum nächsten
11
  diff       = 0;  // Die Zaehldifferenz dees Timers von einer Flanke zur nächsten
12
  counterold = 0;  // Zählerstand N-1.fallende Flanke
13
  counternew = 0; //Zählerstand N fallende Flanke
14
  Frequenz   = 0;
15
16
17
//---------------------Timer 1 Konfiguration----------------------------
18
19
  TCCR1B = ( 1 << CS10);                       // capture neg. Flanke und prescaler 1
20
  TIMSK1 = ( 1 << TICIE1 ) | ( 1 << TOIE1 );   // interrupt Enable ovfer flow Enable &Capture Enable
21
22
  sei();// interrups global zulassen
23
24
//---------------------------------------------------------------
25
 
26
  wait_ms(200);
27
  lcd_init();
28
  lcd_write_string( "           Hz" );
29
30
  lcd_home();
31
  lcd_write ('0');
32
 
33
  while( 1==1 ) 
34
  {
35
    if( flanke == 1 )
36
    {
37
      Frequenz = F_CLOCK * cmax / diff;
38
39
      ltoa( Frequenz, Buffer, 10 );
40
      lcd_write_string( Buffer );
41
42
      lcd_home();
43
      flanke = 0;
44
      lcd_clear();
45
    }
46
  }
47
}

Das sieht doch schon mal besser aus als das Original.

Bist du dir sicher, dass du zuerst die Zahl am Display ausgeben
willst und danach das Display löschen möchtest? Ich denke nicht,
dass du das willst. Denn in der Zeit die zwischen dem
lcd_write_string und dem lcd_clear liegt, wirst du wahrscheinlich
am Display nichts ablesen können. Das Display ist aber leer
bis zur nächsten Ausgabe und besonders bei kleinen Frequenzen
(< 10Hz) kann das ganz schön dauern. Also, wenn schon löschen,
dann lösch das Display bevor du eine Ausgabe machst!
1
 while( 1==1 ) 
2
  {
3
    if( flanke == 1 )
4
    {
5
      Frequenz = F_CLOCK * cmax / diff;
6
7
      ltoa( Frequenz, Buffer, 10 );
8
      lcd_home();
9
      lcd_clear();
10
      lcd_write_string( Buffer );
11
12
      flanke = 0;
13
    }
14
  }

Für itoa() musst du am Anfang noch stdlib.h inkludieren:
1
  #include <stdlib.h>

Schaun wir uns mal den Interrupt Handler an:
1
ISR(TIMER1_CAPT_vect)
2
{
3
  cli(); //intterrupt spreeren
4
5
  counter = ICR1H*0x100+ICR1L;//16 Bit Verrechung H und L Reg Timer 1 capure Register
6
7
  if( b_f == 0 )  //Erkennung 1. Flanke 
8
  {
9
    counterold = counter;//Zählerstand merken
10
    ueberlauf=0;    
11
    b_f = 1;        
12
  }
13
  else
14
  {
15
    counternew=counter;//Zählerstand merken
16
    diff = cmax - counterold + (ueberlauf-1) *cmax + counternew;//Berechnungsformel 
17
    ueberlauf = 0;
18
    counterold = counternew;
19
    flanke = 1;
20
  }
21
22
  sei();// interrups global zulassen
23
}

Das cli() bzw. sei() sind unnötig. Das macht der µC ganz von alleine.
Auch das Auslesen des Capture Registers muss man nicht so kompliziert 
machen.
Das kann der Compiler auch alleine:

   counter = ICR1;

An dieser Stelle:
    diff = cmax - counterold + (ueberlauf-1) *cmax + 
counternew;//Berechnungsformel
musste ich mal scharf überlegen, ob das auch stimmen kann, bzw. was
hier überhaupt passieren soll. Der Kommentar ist dabei nicht
hilfreich. Das das eine Formel ist, die ausgewertet wird, seh ich
auch alleine. Da brauch ich keinen Kommentar dazu. Was mir aber
ein Kommentar erzählen sollte, aber nicht tut, ist: Was passiert
denn hier überhaupt.
Hier wird die Differenz zum vorhergehenden Zählerstand ausgerechnet.
Warum du allerdings von der Anzahl der Überläufe 1 abziehst und dafür
noch mal cmax dazuzählst ist mir ehrlich gesagt ein Rätsel. Ich würde
es so schreiben:
1
   counternew = ueberlauf * cmax + counter;  // Auf welchem Wert steht
2
                                             // der Counter jetzt unter Berücksichtung
3
                                             // der inzwishcen aufgetretenen Überläufe
4
5
   diff = counterold - counternew;           // seit dem letzten Interrupt sind daher
6
                                             // wieviele Ticks vergangen ?

Ohne die LCD Funktionen, sieht dein Pgm also so aus, nachdem etwas
aufgeräumt wurde. Ich hab mal unnötige Variablen rausgeschmissen,
hab einige Kommentare so geändert, dass mir der Kommentar auch
etwas erzählt und nicht nur das wiedergibt, was sowieso schon im
Code steht:
1
//******************************************************** Includes*********************************************************
2
#include <stdlib.h>
3
#include <avr\io.h>
4
#include <avr\interrupt.h>
5
#include <util\delay.h>
6
7
//*********** Systemkonstanten************************************************************************************************
8
#define F_CLOCK    2E7     // 20MHz CPU Takt
9
#define Zahn    6     // 6 Grad Zahn Teilung
10
#define CMAX        65535L
11
12
//****************************************************************** Globale Variablen******************************
13
unsigned char b_f;               // b_f == 0 wenn erste negative flanke
14
long ueberlauf;                  // Anzahl Ueberlaeufe zwischen 2 Capture Interrupts
15
long counterold;                 // Zählerstand N-1.fallende Flanke
16
long counternew;                 // Zählerstand N fallende Flanke
17
volatile long diff;              // Differnez Zählerstand N und Zählerstand N-1
18
volatile unsigned char flanke;   // Zeigt an, dass eine Messung erfolgt ist
19
20
//*******************************************************************************************************************************
21
//*********** Interrupt für fallende Flanke***************************************************************************************
22
ISR(TIMER1_CAPT_vect)
23
{
24
  unsigned int counter = ICR1;
25
26
  if( b_f == 0 )  //Erkennung 1. Flanke 
27
  {
28
    counterold = counter;//Zählerstand merken
29
    ueberlauf=0;    
30
    b_f = 1;        
31
  }
32
  else
33
  {
34
    counternew = ueberlauf * CMAX + counter;  // Auf welchem Wert steht
35
                                              // der Counter jetzt unter Berücksichtung
36
                                              // der inzwischen aufgetretenen Überläufe
37
38
    diff = counterold - counternew;           // seit dem letzten Interrupt sind daher
39
                                              // wieviele Ticks vergangen ?
40
41
    /*
42
    ** alles für den naechsten Interrupt herrichten
43
    */
44
    ueberlauf = 0;
45
    counterold = counternew;
46
    flanke = 1;
47
  }
48
}
49
50
//***********Interrupt für Zähler Überlauf**************************************************************************
51
ISR( TIMER1_OVF_vect)
52
{
53
  ueberlauf++;
54
}
55
56
//*********************Display***********************************************************************************************
57
58
...
59
60
void lcd_write_string( char* String )
61
{
62
  while( *String )
63
    lcd_write( *String++ );
64
}
65
...
66
67
//*************************************************************************
68
int main()
69
{
70
  long Frequenz;
71
  char Buffer[10];       // Ausgabepuffer für itoa, 10 Zeichen sollte für einen int
72
                         // locker reichen
73
74
  b_f        = 0;  // Flag für die allerste Flanke auf: noch nicht da gewesen
75
  ueberlauf  = 0;  // Zählt die Anzahl der Überläufe des Timers von einem Capture zum nächsten
76
  diff       = 0;  // Die Zaehldifferenz des Timers von einer Flanke zur nächsten
77
  counterold = 0;  // Zählerstand N-1.fallende Flanke
78
  counternew = 0;  //Zählerstand N fallende Flanke
79
80
81
//---------------------Timer 1 Konfiguration----------------------------
82
83
  TCCR1B = ( 1 << CS10);                       // capture neg. Flanke und prescaler 1
84
  TIMSK1 = ( 1 << TICIE1 ) | ( 1 << TOIE1 );   // interrupt Enable ovfer flow Enable &Capture Enable
85
86
  sei();// interrups global zulassen
87
88
//---------------------------------------------------------------
89
 
90
  wait_ms(200);
91
  lcd_init();
92
  lcd_write_string( "           Hz" );
93
94
  lcd_home();
95
  lcd_write ('0');
96
 
97
  while( 1==1 ) 
98
  {
99
    if( flanke == 1 )
100
    {
101
      Frequenz = F_CLOCK * CMAX / diff;
102
103
      ltoa( Frequenz, Buffer, 10 );
104
      lcd_write_string( Buffer );
105
106
      lcd_home();
107
      flanke = 0;
108
      lcd_clear();
109
    }
110
  }
111
}


Und dann würde ich mal hergehen und würde am µC mal ausprobieren, ob
in diff vernünftige Werte rauskommen. D.h. zum Testen ersetzte ich
in der Ausgabe

      ltoa( Frequenz, Buffer, 10 );

durch

      ltoa( diff, Buffer, 10 );

und wenn das auch noch kein vernünftiges Bild ergibt, dann seh
ich mir die Werte von counternew und counterold an.


Das war jetzt noch lang nicht alles. Es gibt noch immer viel zu tun
in dem Programm. Aber es soll dir mal einen Einblick geben wie
man an solche Dinge rangeht. Der völlig falsche Weg ist es
Unmengen an Code zu schreiben, der nie oder nicht ausreichend
getestet wird und dann glauben, dass wird schon iregendwie klappen.
Das tut es nämlich nicht. Ich kenne keinen Programmierer (inklusive
mir) der in der Lage ist mehr als 100 Zeilen in einem Rutsch zu
schreiben ohne dass ein Fehler (Syntax oder Logik) drinnen ist.
Und ich würde sagen: Ich bin nicht der schlechtesten einer. Bei
einem Anfänger kannst du getrost aus den 100 Zeilen 5 Zeilen
machen.

von Michael U. (Gast)


Lesenswert?

Hallo,

hab nur mal schnell rübergeschaut, weil ich immernoch hoffe, mehr von C 
zu verstehen (nur ASM sonst). ;)

Frage: wenn ich richtig gelesen habe, will er ca. 10kHz per 
Periodendauer messen? Wären dann rund 100µs/Periode.
Jede erkannte Flanke will er in main() dann umgerechnet anzeigen?
Mit einer Displayroutine, die bei jedem Zugriff mindestens 1ms wartet?

@unsichtbarer WM-Rahul:
Da spielt es dann auch keine Rolle, wenn er es gleich wieder löscht. ;)

Falls ich mich irre, bitte korrigieren.

Gruß aus Berlin
Michael


von Karl heinz B. (kbucheg)


Lesenswert?

> Frage: wenn ich richtig gelesen habe, will er ca. 10kHz per
> Periodendauer messen? Wären dann rund 100µs/Periode.
> Jede erkannte Flanke will er in main() dann umgerechnet anzeigen?
> Mit einer Displayroutine, die bei jedem Zugriff mindestens 1ms wartet?

Das ist nicht so schlimm. Er muss ja nicht den Zählerstand
für jede gemessene Periode ausgeben. Vermisst er halt
zwischen 2 Ausgaben 100 Perioden.

von Karl heinz B. (kbucheg)


Lesenswert?

Ich hab übrigens noch einen logischen Fehler übersehen.
Ist mir erst beim jetzigen durchlesen aufgefallen:

Das hier:
1
    counternew = ueberlauf * CMAX + counter;  // Auf welchem Wert steht
2
                                              // der Counter jetzt unter Berücksichtung
3
                                              // der inzwishcen aufgetretenen Überläufe
4
5
    diff =  counterold - counternew;          // seit dem letzten Interrupt sind daher
6
                                              // wieviele Ticks vergangen ?

kann klarerweise nicht stimmen. Die Differenz ist natürlich
jetzt minus vorher. Muss also heissen
1
    counternew = ueberlauf * CMAX + counter;  // Auf welchem Wert steht
2
                                              // der Counter jetzt unter Berücksichtung
3
                                              // der inzwishcen aufgetretenen Überläufe
4
5
    diff =  counternew - counterold;          // seit dem letzten Interrupt sind daher
6
                                              // wieviele Ticks vergangen ?

Siehst du. Mit einem vernünftigen Stil, kann man solche Dinge
tatsächlich sehen, ohne das Programm jemals laufen gelassen
zu haben.

von Stefan (Gast)


Lesenswert?

char Buffer[10];       // Ausgabepuffer für itoa, 10 Zeichen sollte
                       // für einen int locker reichen

Das hatten wir schon mal ;-)

http://www.mkssoftware.com/docs/man3/itoa.3.asp
"The resulting string may be as long as seventeen bytes."

Um ohne Gefahren einen kleineren Puffer als als 17 Bytes zu verwenden, 
muss man die Implementierung für die verwendete Library im Sourcecode 
checken.

von Peter (Gast)


Lesenswert?

Hallo Karl heinz

Danke erstmal für die konstruktive Kritik. Ich werd heut abend mal 
versuchen deine Vorschläge umzusetzen und dann nochmal zu testen.
Wenn man das letztde mal vor 5 jahren programiert hat ist halt aller 
anfang wieder schwer. Ich dachte wenn ich zu beginn überall double nehme 
dann kann sicher nix falsch laufen.
Ich hab mir eigentlich schon vor dem programmierung mittels Flowchart 
gedanken gemacht, nur wenn man keien erfahrung hat muss man es halt 
irgendwie lösen und kann nicht abschätzen was gut/schlecht ist. Den Tip 
mit abschalten der Innerrupts (cli, sei) hab ich von einem anderem 
Teilnehmer bekommen.
Danke nochmals für die Mühe. Wenn man erst mal siet wie es besser geht 
kann man auch draus lernen


mfg

von Karl heinz B. (kbucheg)


Lesenswert?

Stefan wrote:
> char Buffer[10];       // Ausgabepuffer für itoa, 10 Zeichen sollte
>                        // für einen int locker reichen
>
> Das hatten wir schon mal ;-)
>
> http://www.mkssoftware.com/docs/man3/itoa.3.asp
> "The resulting string may be as long as seventeen bytes."
>
> Um ohne Gefahren einen kleineren Puffer als als 17 Bytes zu verwenden,
> muss man die Implementierung für die verwendete Library im Sourcecode
> checken.

Wir reden hier von einem AVR. Der hat 16 Bit int.
Wie du mit 16 Bit eine Zahl mit 16 Stellen hinkriegen willst,
musst du mir erst mal zeigen, bei einer Basis von 10. Bei
einer anderen Basis ist das allerdings klar. Wenn ich einen
16 Bit in zur Basis 2 ausgeben lasse, krieg ich klarerweise
16 Stellen + abschliessendes '\0' also 17 Stellen.
(Daher die 17, das hat nichts mit der Implementierung zu tun)

Aber du hast Recht. Ich verwende nämlich gar kein itoa, sondern
ltoa. Und ein long geht auch auf einem AVR höher.
Also nicht kleckern, klotzen: Rauf mit der Zahl!


von Stefan (Gast)


Lesenswert?

Karl Heinz Buchegger, ich habe da was falsch im Kopf gehabt, sorry.

Das Stringdrehen (strreverse) in ltoa/itoa passiert auf der Stelle am 
Anfang des Puffers und nicht wie ich fälschlich angenommen habe zwischen 
Anfang und Ende des Puffers. Da müsste es auch mit deinem kurzen Puffer 
gehen.

BTW vgl. die verschiedenen Implementationen insbesondere die 
Kernighan/Ritchie Version bei 
http://www.jb.man.ac.uk/~slowe/cpp/itoa.html

Und beim Nachschlagen, wie es in der avr-libc aussieht, bin ich auf die 
interessanten speedoptimierten *toa Funktionen mit den Namen *toa_fast 
gestossen. So hat denn alles sein gutes ;-)

von Stefan (Gast)


Lesenswert?

Peter, ich, Stefan, hatte "Interrupts abzuschalten" als eine von zwei 
Möglichkeiten genannt und extra mit dem Hinweis versehen "(weniger 
sinnvoll bei einem Timer/Zähler)" sowie anschliessend ausführliche 
Optimierungstipps zu der anderen Möglichkeit (schnellere Abarbeitung) 
gegeben.

von Karl heinz B. (kbucheg)


Lesenswert?

> Und beim Nachschlagen, wie es in der avr-libc aussieht, bin ich auf
> die interessanten speedoptimierten *toa Funktionen mit den Namen
> *toa_fast gestossen. So hat denn alles sein gutes ;-)

Ganz genau.
Die kenne ich nämlich auch nicht.
Werde ich mir aber gleich mal anschauen.

von kein_guter_nic_mehr_frei (Gast)


Lesenswert?

ICH BIN KEIN LEICHENSCHÄNDER!

(hallo alle..)

aber ich habe 2 tage damit zugebracht, um zu sehen dass der code von 
Karl-Heinz mir falsche ergebnisse liefert. ich bin dumm. bei allem 
respekt betreffend der formatierung - sollte man doch sein ziel nicht 
aus den augen verlieren. ich bezweifel, dass unser lieber karl den von 
ihm veröffentlichen code jemals an einen atmel geschickt hat. ein 
einfacher taschenrechner hat mir bessere ergebnisse geliefert.

als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht 
verstehe, was da passiert.

nichts für ungut,
schönes wochenende
bernd

von Karl H. (kbuchegg)


Lesenswert?

kein_guter_nic_mehr_frei schrieb:
> ICH BIN KEIN LEICHENSCHÄNDER!
>
> (hallo alle..)
>
> aber ich habe 2 tage damit zugebracht, um zu sehen dass der code von
> Karl-Heinz mir falsche ergebnisse liefert.

Dann hast du sicher auch den Abschnitt hier gelesen:

> Wahrscheinlich. Ohne die Hardware hier zu haben
> und Messungen vornehmen zu können ist das aus der Entfernung
> schwierig zu sagen.

Was schliesst du daraus: Ich konnte dieses Programm nie testen. Warum? 
Weil ich seine Hardware nicht hier hatte.

> dass unser lieber karl den von
> ihm veröffentlichen code jemals an einen atmel geschickt hat.

Da hast du recht. Ich habe den Thread noch einmal durchgelesen. Ich 
denke ich habe genug Hinweise hinterlassen, wie man bei der Fehlersuche 
vorgeht.

> ein
> einfacher taschenrechner hat mir bessere ergebnisse geliefert.

Wow. Du hast einen Taschenrechner, der Frequenzen messen kann :-)

> als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht
> verstehe, was da passiert.

Gratulation.
Du hast das Wichtigste bei der Übernahme von fremden Code erkannt. Code 
zu übernehmen ist OK. Aber nur dann wenn man nicht einfach stumpfsinnig 
abtippt. Man muss das Abgetippte auch verstehen! In den seltensten 
Fällen kann man ein Programm einfach so übernehmen ohne es anzupassen. 
Und die Gefahr von Fehlern besteht natürlich immer.

> nichts für ungut,

ALso ich habe kein Problem damit, wenn du sagst, dass die letzte Version 
nicht funktioniert. Es gibt im Forum wahrscheinlich 1000-e Stellen an 
denen ich mich geirrt habe. Es wäre allerdings schön gewesen, wenn du 
auch noch gesagt hättest was das Problem war und wie du es behoben hast.

(Ich schätze mal, dass zb. der nicht atomare Zugriff auf diff bei 
schnellen Frequenzen ein Problem darstellt)

von Gast (Gast)


Lesenswert?

>als 'newbie' werde ich in zukunft nichts mehr kopieren wenn ich nicht
>verstehe, was da passiert.

Das gilt auch für 'oldbies'. Eigenes Hirn zu haben, ist immer noch 
aktuell.

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.