Genaue Netzfrequenzmessung mit Arduino Nano <5mHz

Aber scheinbar geht ja kein Weg drum herum einen Quarz zu verwenden und auf die Arduino ide zu verzichten

Der erste Teil ist wohl richtig, der zweite ist falsch.

Lässt sich das Programm irgendwie anpassen, dass ich die Nachkommastellen bekomme?

Bei Arduino gibt es nur offene Software. Jedes Programm lässt sich also anpassen oder als Vorlage für etwas Besseres/Anderes verwenden.

Aber "Nachkommastellen" klingt nach der falschen Richtung: float ist ungenauer als long.
Du musst dir schon genaue Gedanken machen, wenn du genaue Ergebnisse willst. Da ist Ganzzahl-Arithmetik nur ein Teilaspekt.
Musst Müsstest die Library nur von Hz auf "milliHertz" umrechnen, und bist mit Ergebnissen zwischen ca. 49800 und 50200 gut bedient. Brauchst Nachkommastellen höchstens für die Anzeige...

Ein knapper Blick lässt mich allerdings fürchten, dass du mit einer "milliHertz" Variante bei dem in der Library gewählten Verfahren nicht glücklich wirst

Schuppeste:
5mHZ sind 200 Sekunden!

So lange müsstest du Pulse zählen, um die gewünschte Auflösung zu erreichen.

Statt zählen musst du Pulsdauern messen...

Ich sehe da auch nur 3 Möglichkeiten.. Kann man sich den besten nach Preis/Genauigkeit aussuchen

  1. Für 20€ einen OCXO..(auf 25° beheizter Quarz)

  2. Einen TCXO, zb. 32768Hz, mit dem man einen Sekundeimpuls generiert.

  3. Einen GPS Empfänger der jede Sekunde einen 60ns genauen Puls raushämmert.

Mit den Sekundenimpulsen kann man die aktuelle Abweichung des 16Mhz Quarzes Messen und in der Rechnung berichtigen.

Arduino rundet Millis und Micros im Core.. wenn Du bei F_CPU 15,995555Mhz in der Board.txt eingibst misst der zu langsam weil der intern zur Berechnung auf 12.00/15.00Mhz recnet aber 15,955555 läuft.

Statt zählen musst du Pulsdauern messen... eine Pulsdauer ist aber kein Hertz?

es gibt 50,005 Hz .. aber nicht 0,005mHz, ein Pseudobegriff.

ein Pseudobegriff

?

Wieso das? Etwas ungebräuchlich vielleicht. Aber genormt.

1000 Hz = 1 kHz
1000 kHz = 1MHz
0,001 Hz = 1 mHz

50,005 Hz = 50005 mHz

Ausserdem sehe ich
4. Einen handelsüblichen Quarz mit 30 ppm Genauigkeit als ausreichend genau.

Im Fertig-Paket für 30 ct

Nur als Anregung, nicht als Reklame für bestimmte Händler zu sehen, bitte.

Richtig:
1000 Hz = 1 kHz
1000 kHz = 1MHz
0,001 Hz = 1 mHz

50,005 Hz = 50005 mHz
[/quote]

Aber 0,005 / 5mHz bleiben 200 Sekunden
Natürlich weiß ich auch was gemeint ist… ist ja nu auch gut.

Hm… Mit dem Quarz könnteste Richtig liegen… ich versuche das selbst zu durchleuchten…

Der TO möchte also mit 0,005 mHZ Auflösung messen… dazu muss er sowieso in 0,001 Auflösung messen…
das wären also 2 Mikrosekunden Differenz zwischen 50 und 50,001.

50 und 51 Hz trennen in der Berechnung 392 Mikrosekunden die er in 200 Schritten auflösen möchte.

also über den daumen 2Mikrosekunden Auflösung, da das 32 Takte Präzision macht ist das für das erste okay…

Die Messung selbst dauert aber länger… also schonmal mindestens 19Millisekunden bzw ca 304000 Takte, bei 30PPM würde das eine Abweichung von 9,12 Takten auf eine Messung ergeben und wäre bei 32 Takten auch okay.

Nur habe ich auch schon eben mal 10000 oder 20000 bei normalen Quarzen Vermisst, das wären dann 285 Takte und ca 19mHz daneben… und der TO müsste das sowieso regelmäßig gegen eine Referenz messen.

Als Hobby würde ich einfach sagen, Kauf nen Quarz und denk es Dir hübsch!

EDIT: Womöglich erreicht man Blind eine Hohe Präzision mit 16MHz 10ppm/10ppm mit 8ter Teiler und rechnet nur noch 8.000.000/gezählte Takte.

Hier noch ein Beispiel mit Pin8 als Eingang… leider hatte ich nicht viel zeit um es zu testen.

#include <util/atomic.h>
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  //pinMode(8, INPUT);


  // initialize timer1
 // 

            // disable all interrupts

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  // TCCR4A = (1<<WGM12);

  OCR1A = 49999;            // compare match register 16MHz/256/2Hz
  TCCR1B |= (1 << ICNC1);   // icp4
  TCCR1B |= (1 << CS10);    // clock
  TCCR1B |= (0 << CS11);
  TCCR1B |= (0 << CS12);
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  TIMSK1 |= (1 << ICIE1);
sei();
delay(1000);
Serial.println("start");
}
unsigned long tcnttmpm=0;
unsigned long  tctmpt1=0;
volatile long  zm=0;
volatile boolean myready=false;
ISR(TIMER1_CAPT_vect)          // timer compare interrupt service routine
{
  ATOMIC_BLOCK(ATOMIC_FORCEON)
     {
        // your atomic/uninterrupted code here
  tcnttmpm = TCNT1;
  tctmpt1 = zm;
  zm = 0;
  TCNT1=0;
  myready=true;
     }


}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
    zm++;
TCNT1=0;
  }
void loop() {
  // put your main code here, to run repeatedly:

if(myready)
{

unsigned long longTemp=tctmpt1*50000+tcnttmpm;  
double Temp= 16000000.0/longTemp;  
printDouble(Temp,3);
Serial.println("");

myready=false;

}
}
void printDouble( double val, byte precision){
 // prints val with number of decimal places determine by precision
 // precision is a number from 0 to 6 indicating the desired decimial places
 // example: lcdPrintDouble( 3.1415, 2); // prints 3.14 (two decimal places)

 if(val < 0.0){
   Serial.print('-');
   val = -val;
 }

 Serial.print (int(val));  //prints the int part
 if( precision > 0) {
   Serial.print("."); // print the decimal point
   unsigned long frac;
   unsigned long mult = 1;
   byte padding = precision -1;
   while(precision--)
 mult *=10;

   if(val >= 0)
frac = (val - int(val)) * mult;
   else
frac = (int(val)- val ) * mult;
   unsigned long frac1 = frac;
   while( frac1 /= 10 )
padding--;
   while(  padding--)
Serial.print("0");
   Serial.print(frac,DEC) ;
 }
}

Ein 10€ GPS Modul vom freundlichen Chinesen kannst du als Zeitnormal missbrauchen. Die GPS Zeit ist atomuhrgenau, also ein paar Zehnerpotenzen besser, als der Quarz. Meist haben die Module auch einen Ausgang, wo eine LED im 1 Hz Takt blinkt. Auch dieser Takt ist entsprechend stabil. Kannst dann ja z.B. 100 Pulse des 1 Hz Anschlusses nehmen und gucken, wieviel Impulse du in den 100s mit 50 Hz reinbekommst.

Hab ich auch schon vorgeschlagen.. Man kann auch alle 10 Sekunden die aktuelle Frequenz des Quarzes über Timer2 mit dem gps ppm messen und so die Rechnung von vornherein korrigieren.

Hab den Code nochmal korrigiert.. war noch nen bisschen fail vom Handchreiben drin.

Hallo,

Danke Schuppeste, dass du dir so viel Mühe gegeben hast. Habe dein Programm mal ausprobiert mit meinem Nano und der Hardware die ich selbst entworfen habe. Garnicht so leicht da im Programmcode durchzusteigen, habe bisher immer nur relativ oberflächlich mit dem Arduino gearbeitet.

Bei meinem ursprünglichen Programm habe ich ja immer 100 Periodendauern mit dem Attachedinterrupt aufgenommen und dann den Mittelwert der letzten Sekunde gebildet. Wenn ich meine Werte mit der Website Netzfrequenzmessung.de vergleiche habe immer immer nur eine Abweichung von +-1 mHz von deren Wert.

Ist mit deinem Programm auch eine Mittelwertbildung über 1 Sekunde möglich und inwiefern beeinflusst dies die Genauigkeit. Hatte es vorhin mal kurz mit einer For Schleife versucht die frac werte aufzuaddieren, aber leider hatte ich dann keine Nachkommastellen mehr in der Ausgabe.

Vielen Dank für die tolle Hilfe hier im Forum!

Ich habe den Code nochmal geändert das der nun auch Synchron mit Netzfrequenzmessung.de ist…

Dazu noch auf Int0 umgestellt, Die Mittelwertfunktion eingebaut und alles beschriftet.

Ich benutze die average Library dafür. http://playground.arduino.cc/Main/Average

Sonst einfach nochmal Fragen.

#include <util/atomic.h>
#include <Average.h>
Average<double> ave(100); //die Anzahl der letzten Werte zur Mittelwertbestimmung
void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(8, INPUT);


  //Timer Arduino Vorkonfiguration zurücksetzen
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  //Timerkonfiguration
  OCR1A = 49999;            // Rufe alle 50000Quarzticks ISR(TIMER1_COMPA_vect)
  TCCR1B |= (1 << CS10);    // Die 3 CS kombiniert ergeben Ticks aus reinem Quarztakt 1:1
  TCCR1B |= (0 << CS11);
  TCCR1B |= (0 << CS12);
  TIMSK1 |= (1 << OCIE1A);  // Aufruf von ISR(TIMER1_COMPA_vect) bei OCR1A
  //TCCR1B |= (1 << ICNC1);   // Aktiviere Pin8 als timer1 Interrupt // Im Prinzip kann man das gleiche auch in die ISR von Int0 schreiben
  //TIMSK1 |= (1 << ICIE1);   // 1Steigend, 2 Fallende Flanke Pin8
  sei();
  attachInterrupt(digitalPinToInterrupt(2), resetTimer, RISING);

  delay(1000);
  Serial.println("start");
}
boolean onlycycle = false; //Bei 100Hz 2 Pulse zählen
unsigned long tcnttmpm = 0; //Übergabevariable Zählerstand 0-50000
unsigned long  tctmpt1 = 0; //Übergabevariable für zm
volatile uint8_t  zm = 0; //Zähler++ bei TCNT1 50000 erreicht
volatile boolean myready = false; // Einerseits neue Daten, auf der anderen Seite warten bis Erfassung fertig
void resetTimer() {
  ATOMIC_BLOCK(ATOMIC_FORCEON) //Andere interrupts zurückhalten falls dise auch auf die Variablen zugreifen
  {
    if (onlycycle && !myready)
    {
      tcnttmpm = TCNT1;
      tctmpt1 = zm;
      zm = 0;
      TCNT1 = 0;
      myready = true; //neue Daten!
    }
  }
  onlycycle = !onlycycle; //immer 2 Impulse zählen
}
//
//ISR(TIMER1_CAPT_vect)          // timer compare interrupt service routine
//{
//  ATOMIC_BLOCK(ATOMIC_FORCEON)
//  {
//    if (onlycycle && !myready)
//    { // your atomic/uninterrupted code here
//      tcnttmpm = TCNT1;
//      tctmpt1 = zm;
//      zm = 0;
//      TCNT1 = 0;
//      myready = true;
//    }
//
//  }
//  onlycycle = !onlycycle;
//}

ISR(TIMER1_COMPA_vect)          // Wird aufgerufen wenn OCR1A=TCNT1, alle 50000 Ticks
{
  zm++;
  TCNT1 = 0;
}
unsigned long mylastmillis = 0;
unsigned long longTemp = 0;
double Temp = 0;
void loop() {

  if (myready)
  {
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
      longTemp = tctmpt1 * 50000 + tcnttmpm; //im Prinzip zm*zählerMatch +aktuellen TCNT1 Stand
      Temp = 16000000.0 / longTemp; //Echte Quarzfrequenz/Takte=Echte frequenz
    }
    //printDouble(Temp,3);
    //Serial.println(longTemp);
    ave.push(Temp);//hängt immer hinten an einen Double Array das neu Objekt an und verschiebt den rest nach oben

    myready = false;

  }
  if (millis() > mylastmillis + 100)
  {
    //printDouble( Temp,3);  Serial.println("");
    printDouble( ave.mean(), 3);
    Serial.println("");
    mylastmillis = millis();
  }
}

void printDouble( double val, byte precision) {
  // prints val with number of decimal places determine by precision
  // precision is a number from 0 to 6 indicating the desired decimial places
  // example: lcdPrintDouble( 3.1415, 2); // prints 3.14 (two decimal places)

  if (val < 0.0) {
    Serial.print('-');
    val = -val;
  }

  Serial.print (int(val));  //prints the int part
  if ( precision > 0) {
    Serial.print("."); // print the decimal point
    unsigned long frac;
    unsigned long mult = 1;
    byte padding = precision - 1;
    while (precision--)
      mult *= 10;

    if (val >= 0)
      frac = (val - int(val)) * mult;
    else
      frac = (int(val) - val ) * mult;
    unsigned long frac1 = frac;
    while ( frac1 /= 10 )
      padding--;
    while (  padding--)
      Serial.print("0");
    Serial.print(frac, DEC) ;
  }
}

Dazu muss ich gestehen das ich im moment noch “Timer” Neuling bin und noch nicht ganz verstehe wo hier und da mal Takte verloren gehen… das sollte in diesem aber keine Rolle spielen.

Danke für die Mühen Schuppeste, habe das neue Programm gerade mal mit meiner Hardware getestet nur leider bekomme ich nur Nullen. Das alte Programm hatte allerdings funktioniert, hast du eine Idee wo der Fehler liegen könnte?

Eingang wäre jetzt Pin2.

Kein ding, ich hatte zufällig eine galvanische 230V<->AVR Trennung auf der Platine und hab das gleich mal als neue Spielerei mit 100HZ Pulsen getestet. Da ich letztens schon auf Netzfrequenz.de vorbeigekommen bin hat mich das auch interessiert.

Achja für 50Hz Pulse musste das onlycycle=!onlycycle; auskommentieren und als standard auf true setzen.

Habe das Programm nochmal getestet allerdings sind mir zwei Sachen aufgefallen, die gemessene Frequenz hat durchgehend einen Offset von 10 mHz im Vergleich zu Netzfrequenzmessung.de, ist das eine mögliche Abweichung durch die Taktrate und wie kann es es eventuell anpassen?

Und auf meinem Nano bekomme ich bei dem gleichen Programm 50 Hz Werte, lasse ich das Programm aber auf meinem Uno laufen kommen Werte um die 100 Hz, woran kann das liegen?

Mit wieviel Hertz gehst Du denn und überhaupt wie auf den Pin? Galvansich getrennt, mit Optokoppler oder ganz anders.. Mit nulldurchgang (100Hz) oder fertigem Cycle(50Hz)?

Bei mir funktioniert das direkt rauskopiert in Arduino 1.5 auf jedem Board gleich.

Wenn Du einen Resonator hast kann der weit daneben liegen, das vorher beschriebene Problem.

Ich weiß zum Beispiel das mein 12.000Mhz/20ppm/20ppm Quarz bei Zimmertemperatur 11993000 Taktet.. Das bedeutet das ich in der Rechnung 16.000.000 durch 11993000 ersetze.

Du musst mal mehr damit rechnen.. zum Beispiel wie schonmal geschrieben hat der SMD-Quarz(Resonator) meines "Mega2560" Boards 15.599.900Hz.. das ist knapp ein 1/40 und würde eine Messung z.B von einer Frequenz um 1/40 verschieben.. also anstatt 50 eben mal 51,5 anzeigen. Ausserdem denke ich das man nicht einfach auf Netzfrequenzmessung.de 1 mHz vergleichen kann. vielleicht den Durschnitt seit 00.00 Uhr oder so.. aber bestimmt nicht live!

EDIT:8Mhz Uno?

Anforderung zur Frequenzmessung
Aufzuloesende Zeitdifferenz der Netzperiode: 1/(50Hz + 0.005Hz) - (1/(50Hz - 0.005Hz) = 4 us
Untere Frequenz fuer die Zeitaufloesung: 1/4us = 250 kHz
Fuer die Zeitaufloesung und einsynchronisieren der Frequenzimpulse wuerde ich 2MHz bis 5MHz
empfehlen. Damit die Frequenz genau genug berechnet werden kann, wird an einer geeigneten Zusatzhardware kein Weg vorbei gehen.
Hardware: Die Frequenzimpulse werden über ein D-FlipFlop einsynchronisert und mit einem 8Bit breiten
Impulszaehler aufgezaehlt. Bei jedem einlaufenden Impuls wird auch der Zeitwert in einem Register
gespeichert. Impuls- und Zeitzaehler werden von der Software gelesen. Der Zeitzaehler muss bei 50Hz Netzfrequenz einen Zeitbereich von minimal (20ms / 50 = 400us)
abdecken. Ergibt bei 5MHz (400us / 20ns = 20 000 --> 15Bit Breite, d.h. 16 Bit Zeitzaehler.
D.h.dann auch, dass die Software laengstens alle 400us die Hardware gelesen haben muss. In
der Praxis verwendet man meist 32Bit breite Zeitzaehler.
Die Frequenz wird aus der Impuls- und Zeitdifferenz berechnet.
f = (delta impulse / delta Zeit)

Viele Gruesse MKch

Jo, haben wir schon 10 mal im "Anfänger" Wortlaut durchgekaut :wink:

Dann mal her mit dem Code.

Mkch:
Anforderung zur Frequenzmessung
Aufzuloesende Zeitdifferenz der Netzperiode: 1/(50Hz + 0.005Hz) - (1/(50Hz - 0.005Hz) = 4 us
Untere Frequenz fuer die Zeitaufloesung: 1/4us = 250 kHz
Fuer die Zeitaufloesung und einsynchronisieren der Frequenzimpulse wuerde ich 2MHz bis 5MHz
empfehlen. Damit die Frequenz genau genug berechnet werden kann, wird an einer geeigneten Zusatzhardware kein Weg vorbei gehen.
Hardware: Die Frequenzimpulse werden über ein D-FlipFlop einsynchronisert und mit einem 8Bit breiten
Impulszaehler aufgezaehlt. Bei jedem einlaufenden Impuls wird auch der Zeitwert in einem Register
gespeichert. Impuls- und Zeitzaehler werden von der Software gelesen. Der Zeitzaehler muss bei 50Hz Netzfrequenz einen Zeitbereich von minimal (20ms / 50 = 400us)
abdecken. Ergibt bei 5MHz (400us / 20ns = 20 000 --> 15Bit Breite, d.h. 16 Bit Zeitzaehler.
D.h.dann auch, dass die Software laengstens alle 400us die Hardware gelesen haben muss. In
der Praxis verwendet man meist 32Bit breite Zeitzaehler.
Die Frequenz wird aus der Impuls- und Zeitdifferenz berechnet.
f = (delta impulse / delta Zeit)

Viele Gruesse MKch

Zum einen ist ein Rechenfehler in deiner Berechung. Es sollen 5/1000 Genauigkeit und nicht 1/5000 Genauigkeit erreicht werden. Zum anderen muss keine Amplitude mit 4µs Abgebildet werden, was nur geht wenn man genug Abtastungen vornimmt, sondern es soll auf 100µs genau eine Zeit gemessen werden. Damit reichen 10KHz Abtastrate aus um zwischen 100µs und 200µs zu unterscheiden aka 5mHz Abweichung.

Und die Abweichungen werden auch bei den Stromversorgern in Hz und mHz gemessen. Eben weil es Mathematik ist.

Sollwert 50,000 Hz
Istwert 50,005 Hz
Differenz 0,005 Hz und das sind 5mHz.

Was man natürlich braucht ist eine Zeitbasis die genau genug ist für diesen Fehler. Aber da reicht ein Quarz völlig, der hat typisch 25ppm Abweichung pro Grad heist das man +-20 Grad Temperaturabweichung haben darf bis man einen Fehler in Höhe der genauigkeit bekommt. Also nicht mehr genau sagen kann ob 5 oder 10mHz Abweichung ist. Soll es genauer sein, spart man sich einen beheizten Quarz und misst die Temperatur. Kostet weniger Energie als einen Quarz Temperaturstabil zu betreiben. Zumindest in diesem Anwendungsfall. Wenns natürlich genauer sein muss, dann sollte man den Quarz kompensieren oder eine andere referenzquelle nutzen.

Alles in allem eine recht einfache Aufgabe für den Arduino, solange er einen Quarz als Takt benutzt. Ohne Quarz ist eine solche Genauigkeit nur mit wessentlich höherem Aufwand zu erreichen. GPS-Signale zb, was aber ein entsprechendes Shield benötigt, mehr Stromverbrauch und Antenne sowie nur in Bereichen funktioniert, wo man auch den Himmel sehen kann. Im Schaltschrank eingebaut klappt das eher nicht bzw man hat einiges an Mehraufwand bis die Antenne dort ist wo sie sein soll.