Datum:
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:
#include <avr/interrupt.h> #include <EEPROM.h> //Deklaration der Variablen volatile unsigned int timer4_ovl_counter; volatile unsigned int timer4_value; volatile float high_time; volatile float low_time; volatile float period; volatile float frequency; volatile float prescale; volatile unsigned long timestamp; volatile unsigned long test1; volatile unsigned long test2; // Deklaration der Funktionen void timer4_config (byte, int, byte, byte, byte); //Prototyp Timer 4 Konfiguration void initialize_ir(void); //Prototyp Interrupt initialisieren void get_PWM(int); //PWM Messung starten, Übergabeparameter Zeit in ms void setup(){ digitalWrite(13, LOW); // EEPROM zu Null setzen for (int i = 0; i < 512; i++) EEPROM.write(i, 0); // LED anschalten wenn fertig digitalWrite(13, HIGH); Serial.begin(9600); //Kommunikation PC-Schnittstelle aktivieren initialize_ir(); //Interrupts aktivieren timer4_ovl_counter = 0; timer4_value = 0; high_time = 0.0; low_time = 0.0; period = 0.0; frequency = 0.0; prescale = 0; timestamp = 0; test1 = 0; test2 = 0; timer4_config(0,8,0,0,1); //Modus 0 = normal; Prescaler auf 8; Capture Compare Modus = 0; Start mit steigender Flanke Serial.println("Function Setup is done"); } void loop(){ Serial.println("High Time [s]: "); Serial.println(high_time,DEC); Serial.println("\t"); Serial.println("Low Time [s]: "); Serial.println(low_time,DEC); Serial.println("\t"); Serial.println("Periodendauer [s]: "); Serial.println(period,DEC); Serial.println("\t"); Serial.println("Frequenz [Hz]: "); Serial.println(frequency,DEC); Serial.println("\t"); Serial.println(timer4_value); Serial.println("\t"); Serial.println(timer4_ovl_counter); Serial.println("\t"); Serial.println(prescale); Serial.println("\t"); Serial.print(test1); Serial.print("\t"); Serial.print(test2); Serial.println("\t"); TIMSK4 |= (1 << ICIE4); timestamp = millis(); while((timestamp + 200) >= millis()){} TIMSK4 &=~ (1 << ICIE4); timestamp = millis(); while((timestamp + 2000) >= millis()){} // delay(2000); } ISR(__vector_default) { Serial.println("Bad ISR"); } void initialize_ir(void){ sei(); //globale Interrupt Freigabe TIMSK4 |= (1 << TOIE4); //Overflow Timer 4 Interrupt aktiviert TIMSK4 &=~ (1 << ICIE4); // TIMSK4 |= (1 << ICIE4); //Capture Event Interrupt aktiviert } // Overflow Interrupt: hochzaehlen Overflowvariable, beruecksichtigen beim Zaehlwert Interrupt Name: TIMER4_OVF_vect ISR(TIMER4_OVF_vect){ cli(); //Interrupts deaktivieren test1 = test1 + 1; timer4_ovl_counter = timer4_ovl_counter + 1; TIFR4 &=~ (1 << TOV4); //Interrupt Flag fuer Overflow zuruecksetzen sei(); } //Capture Event Interrupt: bei erkannter Flanke muss der Zaehlwert des Timers ausgelesen werden ISR(TIMER4_CAPT_vect){ cli(); test2 += 1; while((ASSR & 0x4) == 0x4){} //Pause: Maskierung um gesetztes drittes Bit zu erkennen: TCN2UB ist gesetzt solange TCNT4 beschrieben wird digitalWrite(13, HIGH); TCCR4A &=~( (1 << CS42) | (1 << CS41) | (1 << CS40)); //Timer anhalten timer4_value = ICR4L; // ICR4L 8 Bit Low Zaehlwert auslesen timer4_value |= (ICR4H << 8); // ICR4H 8 Bit High Zaehlwert auslesen //Wenn auf positive Flanke getriggert wird, ist die gemessene Zeit zuvor low_time (Bit ist ICES4) if((TCCR4B & 0x40) == 0x40){ low_time = ( float(timer4_value) + float(65535 * timer4_ovl_counter) ) * (prescale / 16000000); //beruecksichtigt Zaehler Ueberlauf, und Prescalefaktor timer4_ovl_counter = 0; TCCR4B &=~ (1 << ICES4); //wenn auf Positiv getriggert wurde, wird nun auf negative Flanke umgestellt } //Wenn auf negative Flanke getriggert wird, ist die gemessene Zeit zuvor high_time (Bit ist ICES4) else if((TCCR4B & 0x40) == 0x0){ high_time = ( float(timer4_value) + float(65535 * timer4_ovl_counter) ) * (prescale / 16000000); timer4_ovl_counter = 0; TCCR4B |= (1 << ICES4); //wenn auf Negativ getriggert wurde, wird nun auf positive Flanke umgestellt } //Zaehlerstand zuruecksetzten, High-Byte zuerst, dann Low-Byte TCNT4H = 0x0; TCNT4L = 0x0; digitalWrite(13, LOW); period = high_time + low_time; //Berechnung der Periodendauer und Frequenz frequency = 1 / period; TIFR4 &=~ (1 << ICF4); //Interrupt Flag fuer Capture Event zuruecksetzen sei(); } void timer4_config (byte modus, int prescaler, byte COM4A1_COM4A0, byte COM4B1_COM4B0, byte ICNC4_ICES4) { //Bitweise Maskierung modus &= 15; COM4A1_COM4A0 &= 3; COM4B1_COM4B0 &= 3; ICNC4_ICES4 &= 3; prescale = float(prescaler); byte takt = 0 ; switch (prescaler) { case -99: takt = 6; break; case -89: takt = 7; break; case 0: takt = 0; break; case 1: takt = 1; break; case 8: takt = 2; break; case 64: takt = 3; break; case 256: takt = 4; break; case 1024: takt = 5; break; default: takt = 1; } TCCR4A = (COM4A1_COM4A0 << 6) | (COM4B1_COM4B0 << 4) | (modus & 3); TCCR4B = (ICNC4_ICES4 << 6) | ((modus & 0xC) << 1) | takt; while((ASSR & 0x1) == 0x1){} //Maskierung um gesetztes erstes Bit zu erkennen: TCR2UB ist gesetzt solange TCCR4 beschrieben wird } |
Datum:
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,
ISR(TIMER4_OVF_vect){
cli(); //Interrupts deaktivieren
test1 = test1 + 1;
timer4_ovl_counter = timer4_ovl_counter + 1;
TIFR4 &=~ (1 << TOV4); //Interrupt Flag fuer Overflow zuruecksetzen
sei();
}
|
void initialize_ir(void){ sei(); //globale Interrupt Freigabe TIMSK4 |= (1 << TOIE4); //Overflow Timer 4 Interrupt aktiviert TIMSK4 &=~ (1 << ICIE4); // TIMSK4 |= (1 << ICIE4); //Capture Event Interrupt aktiviert } |
Auch bei der Initialisierung der Interupts sollte erst die Konfiguration und dann die Freigabe erfolgen.
Datum:
//Timer anhalten
|
macht man bei ICP grundsätzlich nicht.
Datum:
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..
Datum:
> 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.
Datum:
Die Prozessortaktfrequenz ist 16MHz. Zumindest gehe ich davon aus, weil ich nichts verstellt habe.
Datum:
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.
Datum:
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).
//die main besteht nur noch aus der textausgabe und der berechnung //Berechnung cli(); //interrupts deaktiveren if ((TCCR4B & 0x40) == 0x0) high_time_timer = timestamp; //wenn positive Flanke eingestellt ist, ist timestamp high_time else low_time_timer = timestamp; timer4_ovl_counter = timer4_ovl; sei(); timer4_ovl_counter = 0; high_time = ( float(high_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 16000000); low_time = ( float(low_time_timer) + float(65536 * timer4_ovl) ) * (prescale / 16000000); period = high_time + low_time; frequency = 1 / period; } |
//Capture Event Interrupt: bei erkannter Flanke muss der Zaehlwert des Timers ausgelesen werden ISR(TIMER4_CAPT_vect){ timer4_value_l = ICR4L; // ICR4L 8 Bit Low Zaehlwert auslesen timer4_value_h = ICR4H; // ICR4H 8 Bit High Zaehlwert auslesen if ((timer4_value_l < 128) && (TIFR4 & (1<<TOV4))){ // Falls wartender Timer overflow Interrupt gleichzeitig ansteht, vorziehen timer4_ovl += 1; TIFR4 &=~ (1 << TOV4); // timer overflow interrupt löschen, da jetzt hier ausgeführt } timestamp = timer4_value_l; timestamp |= (timer4_value_h << 8); TCCR4B ^= (1 << ICES4); //Flankentriggerung wechseln TCNT4H = 0x0; //Zaehlerstand zuruecksetzten TCNT4L = 0x0; //High-Byte zuerst, dann Low-Byte } |
Muss bei der Berechnung eigentlich 65536 oder 65535 stehen? Habe jetzt beides probiert aber keinen Unterschied gemerkt.
Datum:
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
Datum:
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.
Datum:
> 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.
Datum:
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:
timer4_value = ICR4L;
timer4_value |= (ICR4H << 8);
|
aber so:
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!!
Datum:
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.
_SFR_MEM16(mem_addr) |