Forum: Mikrocontroller und Digitale Elektronik Problem mit Timer-Genauigkeit


von Andreas K. (totem)


Lesenswert?

Hallo Forum,

ich verwende einen ATmega2560 mit STK600 und verzweifle langsam an 
meinen Timern. Habe mittlerweile diverse Sachen ausprobiert, finde aber 
keine Lösung. Mein µC soll eine Drehzahl messen (und auch über die 
Erzeugung einer PWM-Spannung an einem Netzteil einstellen). Als 
Grundwert wird ein Drucksensor genutzt, dessen Signal auf ein 0-5 V 
Rechtecksignal gewandelt wird. Prinzipiell funktioniert das Ganze auch, 
nur die Zeitbasis, die für die Drehzahlbesttimmung nötig ist macht 
Quatsch. Ich versuche, alle 1 s eine Berechnung eines 95-100 Hz-Signals 
durchzuführen, komme aber auf Werte zwischen 98 und 124(!!!) Hz.
Hier mein Code
1
ISR(PCINT0_vect)
2
{
3
  uint8_t pinNow = PINB;
4
  uint8_t diff = pinNow ^ pinOld;
5
  
6
  if (diff & (1<<PB0))  //Pin B0 hat ausgelöst
7
  intZaehler++;    //Die LS wurde durchschnitten
8
  
9
  if (diff & (1<<PB1))  //oder / UND(!) Pin B1 hat ausgelöst
10
  {  
11
        flDruck++;  //Der Drucksensor hat zu einer Flanke geführt
12
  }
13
  pinOld = pinNow;  //Merken der aktuallen Pinlevel, um die nächste Unterscheidung durchzuführen
14
}
15
16
/*
17
Der Compare Interrupt Handler wird aufgerufen, wenn 
18
TCNT0 = OCR0A = 125-1 ist (125 Schritte), d.h. genau alle 1 ms
19
*/
20
ISR (TIMER0_COMPA_vect)
21
{
22
  millisekunden++;
23
  if (millisekunden % 50 == 0)
24
  {
25
    SwitchA = true;
26
  }
27
  
28
  if(millisekunden == 1000)
29
  {
30
    sekunde++;
31
    millisekunden = 0;
32
   
33
   /*Flags setzen, um Berechnung zu starten*/
34
    blComputeDruck = true;  
35
    blComputeDZ = true;
36
    
37
  //Alle 3 Sekunden Abgleich starten
38
  /*if (sekunde % 3 == 0)
39
  {
40
    blCompareSollIst = true;
41
  }*/
42
  
43
  if(sekunde == 60)
44
    {
45
      minute++;
46
      sekunde = 0;
47
    }
48
    if(minute == 60)
49
    {
50
      stunde++;
51
      minute = 0;
52
    }
53
    if(stunde == 24)
54
    {
55
      stunde = 0;
56
    }
57
  }
58
}  
59
  
60
int main(void)
61
{
62
  dblVsoll = 0;
63
  
64
  /*Zählerwert, bis zu dem der PWM-Zähler zählt*/
65
  ICR1 = 2000;  //1 MHz Clock-Time, kein Prescaler, 500 Hz = 2000 Schritte
66
  
67
  /*Schwelle des Zählerwertes, bei dem der Ausgang geschalten wird*/
68
  /*Also auch Stellschraube für das Tastverhältnis*/
69
  OCR1A = 0;            //Initialisierungswert
70
  
71
  
72
  /*Setzen der nötigen Reister, vgl. Datenblatt 136-189*/
73
  /*Hier wird auch bestimmt, ob es sich um eine invertierte oder nicht-invertierte PWM handelt*/
74
  TCCR1A = (1 << COM1A1) | (1 << WGM11);
75
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
76
  
77
  /* Hier Definition der PWM*/ 
78
  DDRB = (1 << PB5);        //Ausgangspin in PWM Modus setzen
79
  PORTB = (0 << PB5);        //Abschalten des PWM-Pins bis zur vollständigen Initialisierung
80
    
81
  /* Hier Definition der ISR */
82
  PCICR |= (1 << PCIE0);      //Enable Pin Change Interrupt Pin (0 = PinB0)
83
  PCMSK0 |= (1 << PCINT0);    //Enable Pin Change Interrupt handling on PinB0
84
  PCMSK0 |= (1 << PCINT1);    //Enable Pin Change Interrupt handling on PinB1
85
    
86
  
87
  /*Hier wird der Timer-Interrupt definiert*/
88
  TCCR0A = (1<<WGM01);      //CTC Modus
89
  TCCR0B |= (1<<CS01);      //Prescaler 8
90
  OCR0A = 125-1;      //1 MHz ist die interne Clock-Source, ohne dass die Fuses verstellt werden, Wie viele Schritte soll gezählt werden?
91
  
92
  TIMSK0 |= (1<<OCIE0A);      // Compare Interrupt erlauben
93
  
94
  /*************************************/
95
  
96
  DDRA = 0xff; /*Port A als Ausgangsport definieren*/
97
  DDRC = 0xff; /*Port C als Ausgangsport definieren, um mit LEDs hoch zu zählen*/
98
  DDRD = 0x00; /*Port D als Eingangsport definieren*/
99
  
100
  minDruck = 1000; //für Maximalwertbetrachtung später
101
  
102
  //Flags mit false initialisieren
103
  blCompareSollIst = false;
104
  blComputeDruck = false;
105
  blComputeDZ = false;
106
  
107
  intTilgerDZSOLL = 1000;
108
  
109
  /*Programm-Routine*/
110
  while(1)
111
        {
112
    sei();  //Enable Interrupts
113
      
114
    /*Drehzahlberechnung durchführen, wenn das entsprechende Flag gesetzt ist*/
115
    //Drehzahlbestimmung: Ist-Wert Bestimmung des Tilgers
116
    if (blComputeDZ==true)
117
    {
118
      
119
      TilgerDZ = (int)(intZaehler*7.5);
120
      intDruckLED = TilgerDZ;
121
      intZaehler = 0;      //Rücksetzen des Zählers
122
      blComputeDZ =false;  //Rücksetzen des Flag
123
    }
124
    
125
    /*Berechnung der Frequenz des Drucksensors, wenn das entsprechende Flag gesetzt ist*/
126
    if (blComputeDruck == true)
127
    {  
128
      cli(); //Unterdrückung der Interrupts, um die Variablen während der Berechnung nicht zu manipulieren
129
      flwatchDruck = flDruck;                //Zwischenspeichern in Variable
130
      ComputeDruck = flwatchDruck/2;            //Doppelte Flankenbewertung in ISR --> /2 für Takt
131
      flDruck =0;                      //Rücksetzen des Zählers
132
      sei();                        //Interrupts wieder zulassen
133
      
134
      if (ComputeDruck > 0)                
135
      {
136
      
137
      }
138
      if (ComputeDruck == 0)                      }
139
      {
140
      
141
      }  
142
      //Bestimmung der Extremwerte
143
      if (ComputeDruck > maxDruck)
144
      {
145
        maxDruck = ComputeDruck;
146
      }
147
      if (ComputeDruck < minDruck)
148
      {
149
        minDruck = ComputeDruck;
150
      }
151
      intDruckLED = (int)(ComputeDruck);
152
    
153
      //Zeit für Berechnungnen neu starten
154
      blComputeDruck = false;  
155
      PORTC = intDruckLED;
156
    }    
157
    //Anpassung der Spannung
158
    
159
/*    if (blCompareSollIst == true)
160
    {
161
      if (TilgerDZ < intTilgerDZSOLL)
162
      {
163
        if (dblVsoll < 4000)
164
        {
165
          dblVsoll = dblVsoll+50;            //Sollspannung um 50 mV erhöhen
166
        }
167
        
168
        dbltein = (((dblVsoll-0.8)/3.28)/dblVaus)*ICR1; //Berechnung der anzulegenden PWM-Einschaltzeit
169
        intSchritte = (int)(dbltein);          //Rundung auf ganzzahlige Integer zur Bestimmung PWM-Schrittweite
170
        OCR1A = intSchritte;
171
      } 
172
      if (TilgerDZ > intTilgerDZSOLL)
173
      {
174
        if (dblVsoll > 900)
175
        {
176
          dblVsoll = dblVsoll-100;            //Sollspannung um 100 mV verringern
177
        }
178
        
179
        dbltein = (((dblVsoll-0.8)/3.28)/dblVaus)*ICR1; //Berechnung der anzulegenden PWM-Einschaltzeit
180
        intSchritte = (int)(dbltein);          //Rundung auf ganzzahlige Integer zur Bestimmung PWM-Schrittweite
181
        OCR1A = intSchritte;
182
      }
183
      blCompareSollIst = false;              //Flag zurücksetzen
184
    }*/
185
    if (SwitchA == true)
186
    {
187
      if (PORTA == 0b10000000)
188
      {
189
        PORTA = 0b00000000;
190
      }
191
      else
192
      {
193
        PORTA = 0b10000000;
194
      }
195
      SwitchA = false;
196
    }    
197
    }  
198
}

Nehme ich jetzt aber das Signal des Druksensors oder eines 
funktionsgenerators mit 100 Hz, dann erhalte ich Abweichungen von über 
20% der gezählten Flanken und damit der ermittelten Drehzahl. Versuche 
ich, den Wechsel am Port A über das Interrupt-Erkennen zu messen, passt 
es perfekt. Am Oszilloskop dauert ein 50 ms-High aber 63,xx ms, also 
auch mit einer Abweichung von über 20 %. Das beduetet für mich: Erzeuge 
ich ein Signal und messe es direkt auf dem Board wieder aus, so rechnet 
sich der Zeitfehler wieder raus. Komme ich aber von extern mit einem 
korrekten Signal, so schlägt der Fehler voll zu.
Sooo viele Berechnungen, die mir die Zeit dermaßen verziehen können, 
sind aber nicht enthalten.
Beim Nachlesen im Forum bin ich auf das Capture Input Interrupt 
gestoßen. Das wäre ein Möglichkeit, aber freuen würde ich mich, wenn ich 
die Zeit korrekt messen könnte. Die Lichtschranken-Drehzalhmessung an 
einem Pin scheint nämlich grob zu stimmen... Prelleeffekte für zu 
häufiges Flankenzählen sind an der Stelle auch kein Thema...

Weis jemand Rat?

Hülfääääää - Andreas

von Karl H. (kbuchegg)


Lesenswert?

> OCR0A = 125-1;      //1 MHz ist die interne Clock-Source

Wer für einigermassen genaue Zeitberechnungen den internen RC-Takt 
benutzt, handelt grob fahrlässig.
Oder hast du den mit OSCAL abgeglichen?

Mach dir einen Quarz an dein Messgerät und du hast eine stabile 
Taktbasis, die bis auf ein paar ppm genau ist.

von Krapao (Gast)


Lesenswert?

Liegt ein Oszillogramm vor aus dem man sehen kann, ob der Sensor saubere 
Daten liefert?

Alternativ kann man das Programm auch mit einer bekannten sauberen 
Signalquelle testen. Wenn kein Frequenzgenerator vorhanden ist, kann man 
z.B. einen zweiten µC dafür abstellen.

Wenn du noch einen O-Pin frei hast, kannst du die eingehenden IRQs nach 
aussen durchreichen und im Oszi kontrollieren ob die dem Eingangssignal 
entsprechen.

Dabei kann dir auffallen, dass nach dem Setzen der Flags "Messzeit 
abgelaufen" weitere IRQs in intZaehler und flDruck aufaddiert 
werden! Diese Zähler musst du sofort nach Erkennen des Messzeitendes 
sichern und nullen und nicht erst beim Auswerten in main() nullen!

von Krapao (Gast)


Lesenswert?

Wenn du das Sichern und Nullen richtig angehst, kannst du auch das 
Sperren der IRQs in main() kritisch beäugen.

Vielleicht kannst du die Zählvariable uint8_t machen, so dass du 
automatisch einen atomaren Zugriff hast.

Und vielleicht ist der Schleifendurchlauf <<1s, so dass keine Gefahr 
besteht, dass die gepufferte Zählvariable aktualisiert wird.

von Karl H. (kbuchegg)


Lesenswert?

Das
>       TilgerDZ = (int)(intZaehler*7.5);
kann man so rechnen, muss man aber nicht

    TilgerDZ = intZaehler * 7 + intZaehler / 2;

dürfte aber grob gepeilt 20 mal so schnell abgearbeitet werden.

von Andreas K. (totem)


Lesenswert?

Danke Euch soweit. Eine klare Rückmeldung kann ich euch morgen liefern!

@Karl Heinz: Danke für den Tipp mit der Rechenzeit. Die Konvertierung in 
Integer (die ich ja dann noch brauche) ist wahrscheinlich das kleinere 
Sparpotential. Der Haupteffekt kommt aus der ersparten Fließkommazahl, 
oder?

@Krapao: Das sichern im Interrupt habe ich versucht, bringt aber keinen 
Unterschied, der Größenordnungen ausmachen würde.... Morgen mehr dazu.

von Andreas K. (totem)


Lesenswert?

Hallo Leute,

war ein chaotischer Tag, aber ich habe das Problem gefunden. Die Timer 
funktionieren für unsere Bedürfnisse prächtig. Das Problem ist 
elektromagnetischer Art... Der angesteuerte Bürstenmotor koppelt 
Strahlung auf das Board ein. Ich suche gerade noch nach dem Pfad, das 
wird ein wenig komplizierter, denn sobald ich die Signalleitung der 
Lichtschranke zur Drehzahlmessung vom Board trenne ist meine Messung 
eines anderen Sensors (!!!) in Ordnung. Auf dem Oszi sehen die Signale 
jedoch prima aus. Wie sich diese gegenseitige Störungen unterbinden 
lassen, versuche ich herauszufinden. Auf jeden Fall scheint das STK600 
EMV-Anfällig zu sein...

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.