Forum: Compiler & IDEs Frequenzzähler PCINT2 TIMER1 Verzweifelung


von trm_1904 (Gast)


Angehängte Dateien:

Lesenswert?

Hallo AVR-Experten,
ich habe mich gestern und heute mit einen Problem rumgeärgert und immer 
noch keine Lösung dazu gefunden.
Ich habe mir einen Frequenzmesser geschrieben bei dem ich die Frequenz 
von über 100 Herz über den Port PD5/PCINT21 einlese.
Das Programm arbeit größtenteils korrekt, doch es gibt Situation indem 
nur ein Viertel der Frequenz ausgegeben wird.
Die ISR werden wie beim Oszi zu sehen aber immer abgearbeitet.
Die CPU läuft mit 20.000.000 Hz. Es ist ein Atmega328.


Ich habe echt viel probiert Timereinstellungen andere Berechnung aber 
bisher ohne Erfolg. Vllt hab ich auch etwas übersehen und finde es nicht 
mehr weil ich blind geworden bin.

Danke für eure Hilfe!

1
#include "main.h"
2
#include <avr/io.h>
3
#include <util/delay.h>
4
#include <stdlib.h>
5
6
#include "freqcounter.h"
7
8
9
10
11
12
int main(void){
13
  
14
  // Als Eingang konfigurieren
15
  DDRD|=((1<<DDD2));
16
  DDRD|=((1<<DDD3));
17
  // PD2+3 0 Setzen
18
  PORTD&=~((1<<PORTD2));
19
  PORTD&=~((1<<PORTD3));
20
21
  initFreqCounter();  // Frequenzzähler initialisieren
22
23
  while(1){
24
    // Frequenzen aktualisieren
25
    volatile uint16_t dummy=(uint16_t)getFreqValue(0);
26
    dummy++;
27
28
    }
29
30
31
}
1
 
2
#include "freqcounter.h"
3
#include "register.h"
4
#include <util/delay.h>
5
6
#define UDIFF(a,b) ((a)>(b) ? (a-b) : (b-a))
7
#define MAX(a,b) ((a)>(b) ? (a) : (b))
8
9
10
void initFreqCounter(void){
11
  // Durchflussmesser initilaisieren
12
  // Interruptpins aktivieren PD4..7
13
  // Als Eingang konfigurieren
14
  DDRD&=~((1<<DDD4)|(1<<DDD5)|(1<<DDD6)|(1<<DDD7));
15
  // Pullups deaktvieren
16
  PORTD&=~((1<<PORTD4)|(1<<PORTD5)|(1<<PORTD6)|(1<<PORTD7));
17
  // Pins PD4..7 bzw PCINT20..23 als Interrupteingang maskieren
18
  PCMSK2=((1<<PCINT20)|(1<<PCINT21)|(1<<PCINT22)|(1<<PCINT23));
19
  // Interrupt bei Port D aktivieren
20
  PCICR=(1<<PCIE2);
21
  //
22
  prev_reg=PIND&0xF0;
23
24
  
25
  // 16-Bit Timer initialisieren
26
  TCCR1B=0;
27
  TCCR1A=0;
28
  TCCR1C=0;
29
  GTCCR=0;
30
  TIMSK1=0;
31
  // Zeiteinheit festlegen dt=0,4 mikros CS12..10=010 Vorteiler=clk/8
32
  TCCR1B|=((1<<CS11));
33
  //TCCR1B&=~((1<<CS12)|(1<<CS10));
34
  //// Interrupts aktivieren
35
  TIMSK1|=(1<<TOIE1);
36
  timer1_high_val=0;
37
38
  sei();
39
}
40
41
42
43
uint16_t getFreqValue(uint8_t id){
44
  cli();
45
  // t0 erzeugen
46
  volatile uint32_t t0=freq0_t0_high_val;
47
  t0=t0<<16;
48
  t0|=freq0_t0_low_val;
49
  // t1 erzeugen
50
  volatile uint32_t t1=freq0_t1_high_val;
51
    t1=t1<<16;
52
    t1|=freq0_t1_low_val;
53
  volatile uint32_t udiff0;
54
55
  // Differenz zwischen t0 und t1 berechnen
56
  if(t0>t1)
57
    udiff0=t0-t1;
58
  else
59
    udiff0=t1-t0;
60
61
  // Frequenz berechnen
62
  udiff0=250000000UL/udiff0;
63
64
        // Frequenz kleiner 60,00 Hz
65
  if(udiff0<6000){
66
    // PD3 1 Setzen
67
    PORTD|=((1<<PORTD2));
68
  }
69
  else{
70
    // PD3 0 Setzen
71
    PORTD&=~((1<<PORTD2));
72
  }
73
74
  sei();
75
  return udiff0;
76
77
}
78
79
80
// Interruptroutinen
81
ISR(PCINT2_vect){
82
  // PD3 1 Setzen
83
  PORTD|=((1<<PORTD3));
84
85
  // Unteren Bits der Uhr einlesen
86
87
  unsigned char sreg;
88
  /* Save global interrupt flag */
89
  sreg = SREG;
90
  /* Disable interrupts */
91
  cli();
92
  volatile uint16_t timer1_low_val=TCNT1;
93
  SREG = sreg;
94
95
  // aktuelles Register einlesen
96
  volatile uint8_t act_reg=PIND&0xF0;
97
  // reg gibt an ob ein Sprung von 1 auf 0 erfolgt ist für jeden Pin
98
  volatile uint8_t reg=((prev_reg)&(~act_reg));
99
100
101
  // Frequenzeingang 0
102
  if(reg&(1<<(1+4))){
103
    freq0_t1_high_val=freq0_t0_high_val;
104
    freq0_t1_low_val=freq0_t0_low_val;
105
    freq0_t0_high_val=timer1_high_val;
106
    freq0_t0_low_val=timer1_low_val;
107
108
109
  }
110
  prev_reg=act_reg;
111
  // PD3 0 Setzen
112
  _delay_us(20);
113
  PORTD&=~((1<<PORTD3));
114
115
116
117
118
}
119
120
ISR(TIMER1_OVF_vect){
121
  timer1_high_val++;
122
123
}


1
#ifndef FREQCOUNTER_H_
2
#define FREQCOUNTER_H_
3
4
#include <avr/io.h>
5
#include <inttypes.h>
6
#include <avr/interrupt.h>
7
#include <float.h>
8
9
volatile uint16_t      freq0_t0_high_val;
10
volatile uint16_t      freq0_t0_low_val;
11
volatile uint16_t      freq0_t1_high_val;
12
volatile uint16_t      freq0_t1_low_val;
13
volatile uint16_t      freq0_deadtime;
14
volatile uint16_t      freq0_pulses;
15
volatile uint16_t      freq0_values[3];
16
17
volatile uint16_t      timer1_high_val;  // Überlaufregister für Timer 1
18
volatile uint8_t      prev_reg;      // Vorheriges Register von Port D Eingängen
19
20
21
void initFreqCounter(void);
22
uint16_t getFreqValue(uint8_t id);
23
24
25
26
27
28
#endif /* FREQCOUNTER_H_ */

von Uwe (de0508)


Lesenswert?

Hallo,

ich denke Du solltest auch erklären nach welcher Methode dein 
Frequenzzähler funktionieren soll.
Welches Problem besteht genau ? Das wird nicht beschrieben !

So etwas ist unsinnig, da ja gerade jetzt keine globale 
Interruptfreigabe vorhanden ist.
1
/* Save global interrupt flag */
2
  sreg = SREG;
3
  /* Disable interrupts */
4
  cli();
5
  volatile uint16_t timer1_low_val=TCNT1;
6
  SREG = sreg;

Über den Einsatz von
1
volatile
 musst du nochmal nachdenken. Da sind bestimmt einige zu viel.

von trm_1904 (Gast)


Lesenswert?

Danke für die Anmerkung ich habe alles versucht wie gesagt. Da ich mit 
meinen Latein am Ende war. Sicherlich ist es nicht nötig, aber ich habe 
es vorsichthalber mit reingetan.

Der Frequenzzähler arbeitet wie folgt:

Es gibt einen 32-Bit Timer der aus zwei 16 Bit Timern besteht:
timer1_low_val entspricht dem TCNT1 Register
timer1_high_val wird bei OVF des Timer1 hochgezählt

bei negativer Flanke wird der neue Timer-Wert gespeichert der davor als 
t0 und t1 gespeichert

bei Aufruf von getFreqValue(uint8_t id) werden t0 und t1 verglichen und 
dabei die Frequenz berechnet.

Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur 
ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich 
genau ein Viertel. Ich verstehe aber nicht warum...

Danke für deine Mühe

von Karl H. (kbuchegg)


Lesenswert?

1
  if(t0>t1)
2
    udiff0=t0-t1;
3
  else
4
    udiff0=t1-t0;

das ist falsch.

Wenn du unsigned rechnest (was grundsätzlich richtig ist), dann 
errechnet sich die diff in allen Fällen zu
1
    udiff0=t0-t1;

das Ergebnis ist auch dann richtig, wenn es einen Overflow gibt.

Die Sache mit dem sei und cli ist schon angesprochen worden. Das hat in 
einer ISR nichts verloren. Um das Interrupt Flag brauchst du dich in 
einer ISR nicht kümmern. Während eine ISR läuft, sind Interrupts sowieso 
abgeschaltet und werden beim Beenden der ISR Funktion automatisch wieder 
aktiviert.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:
>
1
>     udiff0=t0-t1;
2
>
>
> das Ergebnis ist auch dann richtig, wenn es einen Overflow gibt.

(Und tu dir einen Gefallen und nenn die Dinge nicht irgendwas0 und 
irgendwas1. Da muss man ständig aus dem Code entnehmen welches jetzt der 
'alte' Wert ist und welches der 'Neue'. Das ist nur eine sprachliche 
Falle, auch für dich selbst.

von Karl H. (kbuchegg)


Lesenswert?

trm_1904 schrieb:

> Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur
> ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich
> genau ein Viertel. Ich verstehe aber nicht warum...

Beobachte mal, ob die Aussetzer einigermassen regelmässig kommen. Ist 
ein Muster erkennbar?

(lass dir auch mal an die Anzeige t1 ausgeben)

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:
> trm_1904 schrieb:
>
>> Das Problem ist, dass die Frequenzzähler Aussetzer hat, und machmal nur
>> ein Viertel der tatsächlichen Frequenz ausgibt und das sind ziehmlich
>> genau ein Viertel. Ich verstehe aber nicht warum...
>
> Beobachte mal, ob die Aussetzer einigermassen regelmässig kommen. Ist
> ein Muster erkennbar?
>
> (lass dir auch mal an die Anzeige t1 ausgeben)

Du kennst ja offenbar den Wert der sich ergeben müsste. Zumindest so 
ungefähr. Wenn sich also eine Frequenz errechnet, die kleiner als sagen 
wir mal die Hälfte dieses erwarteten Wertes ist, dann lässt du dir t1 
ausgeben. Sonst wirst du vermutlich mit Werten geflutet und kannst nicht 
sagen, welcher Zählerstand bei deinen falschen Werten vorgelegen hat.

von Karl H. (kbuchegg)


Lesenswert?

Du hast hier natürlich
1
  volatile uint16_t timer1_low_val=TCNT1;
eine Race Condition.

Wenn der Timer kurz vor dem nächsten Puls knapp vor dem Überlauf stand 
dann kann dir die Situation passieren, dass
* die ISR aufgerufen wird
* während sich der µC bis zur Stelle vorarbeitet, an der du TCNT1 holst 
läuft der Timer natürlich weiter, erreicht seinen Overflow und beginnt 
wieder bei 0. Der springende Punkt ist nun, dass du hier ja in einer ISR 
bist. D.h. die Overflow-ISR muss noch warten, bis die hier abgearbeitet 
ist. Als Konsequenz davon hast du mit TCNT1 einen Wert von 0, 1, 2, ... 
(etwas sehr kleines), aber  timer1_high_val ist noch nicht erhöht, weil 
der Overflow Interrupt noch keine Chance hatte.

(genau deshalb wären diese Zähler Werte alle interessant. Und zwar dann, 
wenn es zu einer Fehlmessung kommt. Das Problem ist real, das hab ich 
jetzt nicht erfunden. Aber ich kann nicht sagen, ob es genau dieses 
Problem ist, das dir zu schaffen macht)

von Karl H. (kbuchegg)


Lesenswert?

Musst du denn die Frequenz wirklich so genau messen?
Die Probleme werden nicht kleiner, wenn man die Genauigkeit bzw. 
Auflösung in die Höhe treibt. Schon alleine das rumwerfen mit uint32 ist 
für den AVR nicht so toll. Würde es nicht reichen, wenn du mit 16 Bit 
Werten hantieren könntest? Dann bräuchtest du die ganze Overflow 
Problematik gar nicht weiter behandeln.

von asdfasd (Gast)


Lesenswert?

Ich denke, die zwei gröbsten Fehler sind die schon erwähnte falsche 
udiff-Berechnung und das falsche Auslesen des Timers in der ISR.  Du 
kannst nicht einfach das low-Word aus dem Register lesen und dann 
irgendwann später mal das high-Word aus der Variablen - 
zwischenzeitliche Overflows liefern dann falsche Werte.  Google mal nach 
AVR 32bit Timer.

Und schmeiss bitte die volatiles für lokalen Variablen raus - das tut 
weh ;-)

von asdfasd (Gast)


Lesenswert?

Da war mal wieder einer schneller ;-)

von trm_1904 (Gast)


Lesenswert?

Vielen Dank für eure Anmerkungen. Ich habe nun endlich eine 
funktionierende Version. Ich habe statt dem Timer1 Timer0 verwendet.
Statt die Differenz zweier uint32 Zahlen zu bilden, zähle ich jetzt mit 
f0_n_ref_actual hoch und setze ihn bei der negativen Flanke 0, dabei 
speicher ich auch f0_n_ref_actual in f0_n_ref_stored. Ich denke der 
Fehler lag beim auslesen von TCNT1. Aber da es jetzt so funktioniert bin 
ich glücklich und zufrieden. Am Oszi habe ich noch bebachten können, 
dass der Fehler dann auftrat wenn PCINT ISR knapp vorher aufgerufen 
worden war.

1
void initFreqCounter(void){
2
  // Durchflussmesser initilaisieren
3
  // Interruptpins aktivieren PD4..7
4
  // Als Eingang konfigurieren
5
  DDRD&=~((1<<DDD4)|(1<<DDD5)|(1<<DDD6)|(1<<DDD7));
6
  // Pullups deaktvieren
7
  PORTD&=~((1<<PORTD4)|(1<<PORTD5)|(1<<PORTD6)|(1<<PORTD7));
8
  // Pins PD4..7 bzw PCINT20..23 als Interrupteingang maskieren
9
  PCMSK2=((1<<PCINT20)|(1<<PCINT21)|(1<<PCINT22)|(1<<PCINT23));
10
  // Interrupt bei Port D aktivieren
11
  PCICR=(1<<PCIE2);
12
  // Initialregister speichern
13
  prev_reg=PIND&0xF0;
14
15
  
16
  // Timer0 initialisieren
17
  // Timer aktivieren mit Prescaler=8
18
  TCCR0A=0x00;
19
  TCCR0B=(1<<CS01);
20
  // Output Compare Interrupt erlauben
21
  TIMSK0=(1<<TOIE0);
22
  // ((20*10^6)/8)/10000 = 250
23
  OCR0A=250-1;
24
25
  // Timmer 0 setzen
26
  TCNT0=0x00;
27
28
  sei();
29
}
30
31
32
33
uint16_t getFreqValue(uint8_t id){
34
35
  return (uint16_t)(250000000UL/f0_n_ref_stored);
36
37
}
38
39
40
// Interruptroutinen
41
42
// Änderung an den Frequenzpins
43
ISR(PCINT2_vect){
44
  // aktuelle Zeit sichern
45
  uint8_t time_actual=TCNT0;
46
47
  // aktuelles Register einlesen
48
  uint8_t act_reg=PIND&0xF0;
49
  // reg gibt an ob ein Sprung von 1 auf 0 erfolgt ist für jeden Pin
50
  uint8_t reg=((prev_reg)&(~act_reg));
51
52
  // Frequenzeingang 0
53
  if(reg&(1<<(1+4))){
54
    f0_n_ref_stored=f0_n_ref_actual|time_actual;
55
    f0_n_ref_actual=0;
56
  }
57
  // Aktuelles Register wird zum letztem Register
58
  prev_reg=act_reg;
59
60
61
}
62
// Überlauf Timer0
63
ISR(TIMER0_OVF_vect){
64
  f0_n_ref_actual+=256;
65
}
1
#ifndef FREQCOUNTER_H_
2
#define FREQCOUNTER_H_
3
4
#include <avr/io.h>
5
#include <inttypes.h>
6
#include <avr/interrupt.h>
7
#include <float.h>
8
9
10
volatile uint32_t    f0_n_ref_actual;
11
volatile uint32_t    f0_n_ref_stored;
12
volatile uint8_t    prev_reg;      // Vorheriges Register von Port D Eingängen
13
14
15
16
void initFreqCounter(void);
17
uint16_t getFreqValue(uint8_t id);
18
uint32_t get_ticks( void );
19
20
21
22
23
24
#endif /* FREQCOUNTER_H_ */

von asdfasd (Gast)


Lesenswert?

Ich denke, du hast immer noch Raceconditions, nur jetzt etwas kleinere 
Zeitfenster.  Und das resetten des Overflowcounters klappt so nicht: du 
müsstest auch TCNT0 resetten und evtl ausstehende Overflowinterrupts. 
Klappt dann natürlich nicht mehr bei mehreren Pins.  Ausserdem muss das 
Auslesen in getFreqValue atomar sein - bei 32-bit-Werten kann das der 
AVR nicht.  Also cli/sei drum herum.

Ich tipp hier mal ein, wie ich das angehen würde (completely untested!):
1
volatile static uint32_t cycles[1];
2
static uint32_t time_prev[1];
3
static uint32_t timer0_ovf;
4
static uint8_t pins_prev;
5
6
ISR(TIMER0_OVF_vect)
7
{
8
    timer0_ovf += 256;
9
}
10
11
ISR(PCINT2_vect)
12
{
13
    uint8_t cnt, ifr, pins, edges;
14
    uint32_t time;
15
16
    cnt = TCNT0;
17
    ifr = TIFR;
18
    pins = PIND;
19
20
    time = timer0_ovf + cnt;
21
    if (ifr & (1 << TOV0) && ~cnt & 0x80) // overflow before reading TCNT0
22
        time += 256;
23
24
    edges = pins_prev & ~pins; // falling edge
25
    pins_prev = pins;
26
27
    if (edges & (1 << PD5))
28
    {
29
        cycles[0] = time - time_prev[0];
30
        time_prev[0] = time;
31
    }
32
}
33
34
uint16_t getFreqValue(uint8_t id)
35
{
36
    uint32_t n;
37
38
    cli();
39
    n = cycles[id];
40
    sei();
41
42
    return 25000000ul / n;
43
}

Die 1er-Arrays um anzuzeigen, welche Variablen bei mehreren Pins 
anzupassen wären.  Btw, wenn der 16-Bit-Timer frei ist, würd ich eher 
den nehmen - generiert weniger Overflowinterrupts.  Noch besser 
natürlich Karl-Heinz' Hinweis: gar keine Overflows ;-)

[Die Overflow-Handling-Idee kommt übrigens aus: 
Beitrag "AVR Timer mit 32 Bit"]

von asdfasd (Gast)


Lesenswert?

Noch vergessen: nimm doch den sei() in initFreqCounter() raus - der 
gehört in main().

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.