www.mikrocontroller.net

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


Important announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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:


#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
}




Autor: STK500-Besitzer (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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,
  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.

Autor: STK500-Besitzer (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
 //Timer anhalten
macht man bei ICP grundsätzlich nicht.

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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..

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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.

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht lesenswert
Die Prozessortaktfrequenz ist 16MHz. Zumindest gehe ich davon aus, weil 
ich nichts verstellt habe.

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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.

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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).

//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.

Autor: STK500-Besitzer (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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.

Autor: Karl Heinz Buchegger (kbuchegg) (Moderator)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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.

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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:
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!!

Autor: Christian (Gast)
Datum:

Diesen Beitrag bewerten:
lesenswert
nicht 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.
_SFR_MEM16(mem_addr)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel




Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder GIF-Format hochladen.
Siehe Bildformate
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken erkennst du die Nutzungsbedingungen an.

webmaster@mikrocontroller.netImpressumNutzungsbedingungenWerbung auf Mikrocontroller.net