Forum: Mikrocontroller und Digitale Elektronik Problem bei PWM Signal Erfassung Atmega2560


von Christian (Gast)


Lesenswert?

Hallo Leute,

ich versuche mich zur Zeit mit der PWM Erfassung auf einem Arduino 
Atmega 2560 Board. Ich benütze den externen Timer ICP4 Portpin um das 
PWM Signal einzulesen. Testen tue ich das Programmm mit einem 
Frequenzgenerator. Leider habe ich einen prozentualen Fehler, der mir 
das Ergebnis bei hohen Frequenzen ziemlich deutlich weglaufen lässt. Bei 
2kHz ist mein Rechenergebnis bereits mit 2.8kHz deutlich daneben. Ich 
kann mir leider nicht erklären woran es liegt. Ich hoffe, ihr habt einen 
Tipp für mich. Hier der Code:
1
#include <avr/interrupt.h>
2
#include <EEPROM.h>
3
4
5
//Deklaration der Variablen
6
volatile unsigned int timer4_ovl_counter;
7
volatile unsigned int timer4_value;
8
volatile float high_time;
9
volatile float low_time;
10
volatile float period;
11
volatile float frequency;
12
volatile float prescale;
13
volatile unsigned long timestamp;
14
volatile unsigned long test1;
15
volatile unsigned long test2;
16
17
18
// Deklaration der Funktionen
19
void timer4_config (byte, int, byte, byte, byte);    //Prototyp Timer 4 Konfiguration
20
void initialize_ir(void);                           //Prototyp Interrupt initialisieren
21
void get_PWM(int);                                 //PWM Messung starten, Übergabeparameter Zeit in ms
22
23
24
void setup(){
25
  
26
  digitalWrite(13, LOW);
27
  //  EEPROM zu Null setzen
28
  for (int i = 0; i < 512; i++)
29
    EEPROM.write(i, 0);   
30
  // LED anschalten wenn fertig
31
  digitalWrite(13, HIGH);
32
  
33
    Serial.begin(9600);     //Kommunikation PC-Schnittstelle aktivieren
34
    
35
   initialize_ir();       //Interrupts aktivieren
36
   
37
   timer4_ovl_counter = 0;  
38
   timer4_value = 0;
39
   high_time = 0.0;
40
   low_time = 0.0;
41
   period = 0.0;
42
   frequency = 0.0;
43
   prescale = 0;
44
   timestamp = 0;
45
   test1 = 0;
46
   test2 = 0;
47
   timer4_config(0,8,0,0,1);    //Modus 0 = normal; Prescaler auf 8; Capture Compare Modus = 0; Start mit steigender Flanke
48
    
49
    Serial.println("Function Setup is done"); 
50
51
}
52
53
54
55
void loop(){
56
    
57
  Serial.println("High Time [s]: "); 
58
  Serial.println(high_time,DEC); 
59
  Serial.println("\t");
60
  
61
  Serial.println("Low Time [s]: "); 
62
  Serial.println(low_time,DEC); 
63
  Serial.println("\t");
64
  
65
  Serial.println("Periodendauer [s]: "); 
66
  Serial.println(period,DEC); 
67
  Serial.println("\t");
68
  
69
  Serial.println("Frequenz [Hz]: "); 
70
  Serial.println(frequency,DEC); 
71
  Serial.println("\t");
72
 
73
  Serial.println(timer4_value); 
74
  Serial.println("\t");
75
  
76
  Serial.println(timer4_ovl_counter); 
77
  Serial.println("\t");
78
79
  Serial.println(prescale); 
80
  Serial.println("\t");
81
82
  Serial.print(test1); 
83
  Serial.print("\t");
84
  Serial.print(test2); 
85
  Serial.println("\t");
86
87
88
  TIMSK4 |= (1 << ICIE4);
89
  
90
  timestamp = millis();
91
  while((timestamp + 200) >= millis()){}
92
93
  TIMSK4 &=~ (1 << ICIE4);  
94
  
95
  timestamp = millis();
96
  while((timestamp + 2000) >= millis()){}
97
//  delay(2000);
98
99
100
}
101
102
103
ISR(__vector_default)
104
{
105
   Serial.println("Bad ISR"); 
106
}
107
108
109
void initialize_ir(void){
110
    sei();                         //globale Interrupt Freigabe
111
    TIMSK4 |= (1 << TOIE4);       //Overflow Timer 4 Interrupt aktiviert
112
    TIMSK4 &=~ (1 << ICIE4);                             //  TIMSK4 |= (1 << ICIE4);      //Capture Event Interrupt aktiviert
113
}
114
115
  // Overflow Interrupt: hochzaehlen Overflowvariable, beruecksichtigen beim Zaehlwert Interrupt Name: TIMER4_OVF_vect 
116
  ISR(TIMER4_OVF_vect){ 
117
    cli();                                            //Interrupts deaktivieren    
118
      test1 = test1 + 1;
119
       timer4_ovl_counter = timer4_ovl_counter + 1;
120
       TIFR4 &=~ (1 << TOV4);                       //Interrupt Flag fuer Overflow zuruecksetzen
121
    sei();
122
  } 
123
  
124
  //Capture Event Interrupt: bei erkannter Flanke muss der Zaehlwert des Timers ausgelesen werden
125
  ISR(TIMER4_CAPT_vect){
126
    cli();  
127
    test2 += 1; 
128
    while((ASSR & 0x4) == 0x4){}                //Pause: Maskierung um gesetztes drittes Bit zu erkennen: TCN2UB ist gesetzt solange TCNT4 beschrieben wird 
129
    digitalWrite(13, HIGH);
130
    TCCR4A &=~( (1 << CS42) | (1 << CS41) | (1 << CS40)); //Timer anhalten
131
  
132
    timer4_value  = ICR4L;                  // ICR4L 8 Bit Low Zaehlwert auslesen
133
    timer4_value |= (ICR4H << 8);          // ICR4H 8 Bit High Zaehlwert auslesen
134
    
135
    //Wenn auf positive Flanke getriggert wird, ist die gemessene Zeit zuvor low_time (Bit ist ICES4)
136
    if((TCCR4B & 0x40) == 0x40){ 
137
       low_time = ( float(timer4_value) + float(65535 * timer4_ovl_counter) ) * (prescale / 16000000); //beruecksichtigt Zaehler Ueberlauf, und Prescalefaktor        
138
       timer4_ovl_counter = 0;
139
       TCCR4B &=~ (1 << ICES4);            //wenn auf Positiv getriggert wurde, wird nun auf negative Flanke umgestellt
140
    }
141
    
142
    //Wenn auf negative Flanke getriggert wird, ist die gemessene Zeit zuvor high_time (Bit ist ICES4)
143
    else if((TCCR4B & 0x40) == 0x0){
144
      high_time = ( float(timer4_value) + float(65535 * timer4_ovl_counter) ) * (prescale / 16000000);         
145
       timer4_ovl_counter = 0;
146
       TCCR4B |= (1 << ICES4);          //wenn auf Negativ getriggert wurde, wird nun auf positive Flanke umgestellt  
147
      }  
148
    
149
    //Zaehlerstand zuruecksetzten, High-Byte zuerst, dann Low-Byte
150
    TCNT4H = 0x0;        
151
    TCNT4L = 0x0;
152
    digitalWrite(13, LOW);
153
    
154
      period = high_time + low_time;      //Berechnung der Periodendauer und Frequenz
155
      frequency = 1 / period;
156
    
157
    
158
    TIFR4 &=~ (1 << ICF4);       //Interrupt Flag fuer Capture Event zuruecksetzen
159
    sei();  
160
    }
161
162
163
void timer4_config (byte modus, int prescaler, byte COM4A1_COM4A0, byte COM4B1_COM4B0, byte ICNC4_ICES4)
164
{
165
  //Bitweise Maskierung
166
  modus          &= 15;
167
  COM4A1_COM4A0 &= 3;
168
  COM4B1_COM4B0 &= 3;
169
  ICNC4_ICES4   &= 3;
170
  prescale = float(prescaler);
171
172
  byte takt = 0 ;                          
173
  switch (prescaler)
174
  {
175
    case -99:   takt = 6; break;
176
    case -89:   takt = 7; break;
177
    case 0:     takt = 0; break;
178
    case 1:     takt = 1; break;
179
    case 8:     takt = 2; break;
180
    case 64:    takt = 3; break;
181
    case 256:   takt = 4; break;
182
    case 1024:  takt = 5; break;
183
    default:
184
                takt = 1;
185
  }
186
  
187
  TCCR4A = (COM4A1_COM4A0 << 6) | (COM4B1_COM4B0 << 4)    | (modus & 3);
188
  TCCR4B = (ICNC4_ICES4 << 6)   | ((modus & 0xC) << 1)    | takt;
189
  
190
  while((ASSR & 0x1) == 0x1){}  //Maskierung um gesetztes erstes Bit zu erkennen: TCR2UB ist gesetzt solange TCCR4 beschrieben wird
191
}

von STK500-Besitzer (Gast)


Lesenswert?

Ich kenne mich zwar nicht mit den Arduino-Sachen aus, aber ein "sei" hat 
in einer ISR zumindest beim gcc nichts zu suchen.
Damit kann man sich das "cli" auch gleich sparen,
1
  ISR(TIMER4_OVF_vect){ 
2
    cli();                                            //Interrupts deaktivieren    
3
      test1 = test1 + 1;
4
       timer4_ovl_counter = timer4_ovl_counter + 1;
5
       TIFR4 &=~ (1 << TOV4);                       //Interrupt Flag fuer Overflow zuruecksetzen
6
    sei();
7
  }
1
void initialize_ir(void){
2
    sei();                         //globale Interrupt Freigabe
3
    TIMSK4 |= (1 << TOIE4);       //Overflow Timer 4 Interrupt aktiviert
4
    TIMSK4 &=~ (1 << ICIE4);                             //  TIMSK4 |= (1 << ICIE4);      //Capture Event Interrupt aktiviert
5
}

Auch bei der Initialisierung der Interupts sollte erst die Konfiguration 
und dann die Freigabe erfolgen.

von STK500-Besitzer (Gast)


Lesenswert?

1
 //Timer anhalten
macht man bei ICP grundsätzlich nicht.

von Christian (Gast)


Lesenswert?

Danke für die Hinweise. Habe die Zeilen vorhin schon rausgenommen um den 
Code auf das Nötigste zu reduzieren, bin mit dem Umschreiben aber noch 
nicht fertig. Mal sehn obs besser wird. Außerdem verlagere ich die 
Berechnung in die main, das sollte auch besser sein als im Interrupt..

von Karl H. (kbuchegg)


Lesenswert?

> Bei 2kHz

Bei welcher Prozessor-Taktfrequenz?

Deine Verwendung von float macht der eventuell einen Strich durch die 
Rechnung. Je nach Prozessor-Frequenz kommt der µC mit der Rechnerei 
nicht mehr hinterher.

von Christian (Gast)


Lesenswert?

Die Prozessortaktfrequenz ist 16MHz. Zumindest gehe ich davon aus, weil 
ich nichts verstellt habe.

von Karl H. (kbuchegg)


Lesenswert?

Christian schrieb:
> Danke für die Hinweise. Habe die Zeilen vorhin schon rausgenommen um den
> Code auf das Nötigste zu reduzieren, bin mit dem Umschreiben aber noch
> nicht fertig. Mal sehn obs besser wird. Außerdem verlagere ich die
> Berechnung in die main, das sollte auch besser sein als im Interrupt..


Wenn du auf das Nötigste reduzieren willst, dann mach das so:

In der ISR:
lediglich die Kennwerte sichern und für die nächste Messung vorbereiten:
  Timerwert
  Anzahl der Overflows
  (  ruhig getrennt nach High-Time und Low-Time )
  Flankenrichtung umstellen
  Timer auf 0 setzen!

  Die Interrupt Flags lässt du allersdings in Ruhe. Auch das ICF4


In der Hauptroutine lässt du dir die gesicherten Werte ausgeben. Hier 
darauf achten, dass du die Interrupts nur kurzzeitig sperren musst! 
Daher: mit Hilfsvariablen arbeiten und die Kennwerte erst unter 
Interruptsperre dothin umkopieren.

Die so gewonnenen Kennwerte würde ich mir erst mal so wie gemessen 
ausgeben lassen und mit der Hand nachrechnen, was da rauskommt.

von Christian (Gast)


Lesenswert?

So das Programm läuft jetzt genauer. Bei einem Frequenzwechsel hinkt die 
Ausgabe aber deutlich um 5-6 Sekunden hinterher, was ich nicht ganz 
verstehe. Außerdem berechet er Blödsinn bei Eingangsfrequenzen < 30Hz. 
Bis 5kHz sieht das Ergebnis wiederrum echt gut aus, bei höheren 
Frequenzen läuft es wieder davon (eingestellt 10kHz Ausgabe 11,6kHz).
1
//die main besteht nur noch aus der textausgabe und der berechnung
2
3
//Berechnung
4
5
cli();                                                             //interrupts deaktiveren
6
 if ((TCCR4B & 0x40) == 0x0) high_time_timer = timestamp;          //wenn positive Flanke eingestellt ist, ist timestamp high_time
7
 else low_time_timer = timestamp;
8
 
9
 timer4_ovl_counter = timer4_ovl;
10
sei();
11
12
13
 timer4_ovl_counter = 0;
14
15
 high_time = ( float(high_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 16000000); 
16
 low_time =  ( float(low_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 16000000); 
17
18
19
 period = high_time + low_time;
20
 frequency = 1 / period;
21
}
1
//Capture Event Interrupt: bei erkannter Flanke muss der Zaehlwert des Timers ausgelesen werden
2
ISR(TIMER4_CAPT_vect){
3
    timer4_value_l = ICR4L;                                 // ICR4L 8 Bit Low Zaehlwert auslesen
4
    timer4_value_h = ICR4H;                                // ICR4H 8 Bit High Zaehlwert auslesen
5
    
6
    if ((timer4_value_l < 128) && (TIFR4 & (1<<TOV4))){   // Falls wartender Timer overflow Interrupt gleichzeitig ansteht, vorziehen
7
     timer4_ovl += 1;         
8
     TIFR4 &=~ (1 << TOV4);                              // timer overflow interrupt löschen, da jetzt hier ausgeführt
9
   }
10
       timestamp  = timer4_value_l;
11
       timestamp |= (timer4_value_h << 8); 
12
    
13
    TCCR4B ^= (1 << ICES4);                           //Flankentriggerung wechseln
14
    
15
    TCNT4H = 0x0;                                   //Zaehlerstand zuruecksetzten     
16
    TCNT4L = 0x0;                                  //High-Byte zuerst, dann Low-Byte
17
}

Muss bei der Berechnung eigentlich 65536 oder 65535 stehen? Habe jetzt 
beides probiert aber keinen Unterschied gemerkt.

von STK500-Besitzer (Gast)


Lesenswert?

Christian schrieb:
> Muss bei der Berechnung eigentlich 65536 oder 65535 stehen? Habe jetzt
> beides probiert aber keinen Unterschied gemerkt.

1/65535 und 1/65536 liegen zeimlich dich beieinander...

Die Berechnung in Kombination bedingt, dass mit 65536 gerechnet wird, da 
das die nächsthöhere Stelle ist.
Hexadezimal ausgedrückt:

655535 = 0x0FFFF
655536 = 0x10000

von Christian (Gast)


Lesenswert?

Wisst ihr bis zu welchen Frequenzen man generell sicher Überwachen kann 
ohne Rechenzeiten der abzuarbeitenden Zwischenschritte berücksichtigen 
zu müssen? Bei niedrigen Frequenzen stimmt irgendwas mit der Overflow 
Berechnung noch nicht.. Bei hohen Frequenzen schätze ich läuft mir das 
Ergebnis davon, weil die Zeit in der die Interrupt Routine ausgeführt 
und im main-code die Berechnung läuft, zeitlich nicht berücksichtige.

von Karl H. (kbuchegg)


Lesenswert?

> high_time = ( float(high_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 
16000000);
> low_time =  ( float(low_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 
16000000);


So, so.
High Time und Low Time hatten also die gleiche Anzahl an Overflows.

Wozu kopierst du dir eigentlich
 timer4_ovl_counter = timer4_ovl;
den Wert unter Interrupt Schutz um, wenn du dann sowieso
 timer4_ovl_counter = 0;
den Wert verwirfst um dann wieder mit dem direkten Wert aus der ISR zu 
rechnen?

:-)

NOch ein  Tip

       timestamp  = timer4_value_l;
       timestamp |= (timer4_value_h << 8);


Der Timestamp umfasst ALLES. Zählerstand UND Anzahl der Overflows!

Wenn es dann hier
    TCNT4H = 0x0;                                   //Zaehlerstand 
zuruecksetzten
    TCNT4L = 0x0;                                  //High-Byte zuerst, 
dann Low-Byte

wieder bei 0 losgeht mit der Zählerei, geht es auch wieder mit 0 
Overflows los.

Ist wie bei einer Stoppuhr. Das ganze macht nur Sinn, wenn du Sekunden 
UND Minuten als integrale Einheit betrachtest und dies die gestoppte 
Zeit nennst. Sobald du die Dinge auseinanderlaufen lässt und die 
Sekunden hier und die Minuten dort bearbeitest, ist die Gefahr sehr sehr 
hoch, das du dir Inkonsistenzen einhandelst.

von Christian (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> So, so.
>
> High Time und Low Time hatten also die gleiche Anzahl an Overflows.
>
>
>
> Wozu kopierst du dir eigentlich
>
>  timer4_ovl_counter = timer4_ovl;
>
> den Wert unter Interrupt Schutz um, wenn du dann sowieso
>
>  timer4_ovl_counter = 0;
>
> den Wert verwirfst um dann wieder mit dem direkten Wert aus der ISR zu
>
> rechnen?
>
>
>
> :-)

Ja da ist mir irgendwas schief gelaufen :D

ich habe es jetzt raus bekommen, das auslesen der 8 Bit Register des 
Timers hat irgendwie nicht funktioniert. Sobald im Register der Zählwert 
über 2^15 ging, ist der Timer plötzlich komplett weggelaufen.

So gings nicht:
1
timer4_value  = ICR4L;            
2
timer4_value |= (ICR4H << 8);

aber so:
1
     timer4_value=ICR4 + 65535 * timer4_ovl_counter;

und die Abweichung bei hohen Frequenzen kommt zustande, weil ich den 
Timer am Ende der ISR auf 0 gesetzt habe und die Abarbeitungszeit der 
ISR vernachlässigt habe. Ich setze den Timer am Ende jetzt bei einem 
Prescaler von 1 auf 170.

Hast du eine Idee warum das mit dem einzelnen Auslesen der 8-Bit 
Register nicht zuverlässig funktioniert?

Danke!!

von Christian (Gast)


Lesenswert?

Das Makro im Hintergrund _SFR_MEM16(mem_addr) übernimmt das 
Zusammenführen von den beiden 8 Bit Registern zu einem 16 Bit Wert. Ich 
suche gerade noch wie genau das Makro das macht.
1
_SFR_MEM16(mem_addr)

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.