Plotten der 50Hz-Netz-Frequenz       

von Ralf Beesner

Elektronik-Labor   Projekte   AVR  Tiny85



Motivation

Vor einigen Wochen wurde es mal wieder im europäischen 50Hz-Verbundnetz eng; bei Heise gab es einen Artikel über die Gründe:

Bekanntlich muss stets so viel elektrische Energie erzeugt werden, wie gerade verbraucht wird, und die elektrische Energie wird zum größten Teil aus mechanischer Energie gewonnen. Steht nicht genügend mechanische Antriebsleistung zur Verfügung, stellen die Generatoren eine zu große mechanische Last für die Turbinen dar. Die Turbinen drehen langsamer, die Netzfrequenz sinkt.

Umgekehrt steigt die Frequenz, wenn mehr Leistung erzeugt als verbraucht wird.

In solchen Fällen stellt sich kein stabiles neues Gleichgewicht ein, sondern das Ungleichgewicht führt zu netzweiten Schieflagen, die letztlich die Kraftwerke und Transformatoren gefährden können. Daher kommt es bei etwa 49,8 Hz zum großflächigen "Abwurf" von Verbrauchern oder gar zu einem Zerfallen des Verbundnetzes in Inselnetze.

Neben Grund- und Mittellast-Kraftwerken gibt es Regelkraftwerke, die Schwankungen des Verbrauchs kurzfristig ausgleichen können und sich das gut bezahlen lassen. Sie springen immer ein, wenn die Netzfrequenz um mehr als 10 mHz (milliHertz) vom Soll abweicht.

Auf einigen Websites, z.B. www.netzfrequenzmessung.de wird der sekundengenaue Verlauf der Netzfrequenz grafisch dargestellt. Dort kann man den Verlauf ohne besonderen Aufwand beobachten, aber eigentlich bekommt man die Rohdaten ja "frei Haus" von seinem Elektrizitätsversorger; man muss sie nur messen und auswerten. Und da es um 50 Hz und eine Auflösung im Millisekunden-Bereich geht, ist das nicht sehr aufwendig.


Hardware

Man könnte ein Arduino-Board einsetzen, da aber lediglich 4 Portpins für das 50 Hz-Signal, TxD und einen Quarz benötigt werden, ist eine Lösung mit einem AtTiny 85 ökonomischer (ein AtTiny 25 oder 45 dürfte auch reichen). Der AtTiny wird mit 16 MHz Quarztakt betrieben.




Das Schaltbild zeigt die Beschaltung mit dem 16 MHz-Quarz und Bürdekondensatoren, eine LED blitzt kurz im Sekundentakt auf, wenn das 50Hz-Signal anliegt, und die serielle Ausgabe erfolgt über den 1kOhm-Schutzwiderstand R3 und eine RS232-DSUB9-Buchse. Ausgewertet wird das 50Hz-Signal über den Eingang T0/PB2, dies ist der Zähleingang von Timer0.

Ist PB2 unbeschaltet, kann man einfach ein Stück Draht (etwa 1m) frei im Zimmer aufhängen; er fängt das in der Wohnung allgegenwärtige 50Hz-Streufeld auf. Falls das nicht zuverlässig klappt, kann man versuchen, den Draht um eine 230V- Netzanschlussleitung zu schlingen oder mit Klebeband so zu befestigen, dass sie parallel zur Netzleitung verläuft. R4, R5 und die Z-Diode entfallen dann.

Ist PB2 beschaltet, z.B auf den Franzis-LP-Mikrocontroller-Boards, benötigt man eine niederohmige Quelle, z.B ein Stecker-Netzteil mit etwa 5V bis 12V Wechselspannungsausgang. R4, R5 und die Z-Diode D1 sorgen dafür, dass der AtTiny-Eingang nicht "gegrillt" wird. Man kann aber auch die drei Bauteile weglassen und stattdessen den störenden Widerstand an PB2 vorübergehend von dem Board ablöten oder im DSUB9- Stecker die RxD-Leitung unterbrechen:-)

Auf dem LP Mikrocontroller-Board braucht man dann nur noch 2 externe Bauteile: den Quarz und eine LED mit eingebautem Vorwiderstand. Die Bürdekapazitäten kann man weglassen und die zu hohen Messwerte im PC-Programm korrigieren (ggf. kann man den Wert durch Vergleich mit www.netzfrequenzmessung.de feintunen). Nachteil: Der Quarzoszillator wird anfälliger für Schwankungen der Raumtemperatur.




Der AtTiny muss mit einem ISP-Programmer für Quarz-Betrieb gefust werden (Fuse-Werte: siehe Bascom-Quellcode). Ein evtl. vorhandener 8MHz-Bootloader wird dadurch überschrieben.


Firmware

Die Firmware lässt sich mit der kostenlosen BASCOM-Demoversion kompilieren.

Timer 0 zählt 50 Flankenwechsel des 50Hz-Signals und wirft dann einen Interrupt, also im (ungenauen Netzfrequenz-) Sekundentakt. Timer1 zählt den 16 MHz-Quarztakt. Da er nur 8 Bit Auflösung hat und nach 255 Schritten überläuft, wird der Vorteiler (1:128) genutzt, damit es nicht zu viele Timer1-Interrupts gibt. Die Anzahl der Überläufe wird in der Variablen Ovfcnt gezählt (es sind etwa 488 Überläufe pro Sekunde).

In der Timer0-Interrupt-Routine werden der Timer1-Zählerwert und die Zahl der Überläufe wieder zu der Gesamtzahl der gezählten Taktschritte zusammengesetzt (also etwa 16.000.000 / 128 = 125000) und über die serielle Schnittstelle an den PC gesendet. Die Umrechnung in 50 Hz würde viel Zeit kosten (aufwendige Fließkomma-Arithmetik); das kann der PC besser und schneller.

Eigentlich soll man Interrupt-Routinen möglichst kurz halten. Da in der Interruptroutine Tim0isr Werte berechnet werden und die serielle Schnittstelle bedient wird, ist es wichtig, als erstes die Timer- Register TCNT1, TCNT0 und die Variable Ovfcnt auszulesen bzw. zurückzusetzen.

Da der AtTiny keine serielle Hardware-Schnittstelle hat, muss das Sendesignal Bit für Bit "zu Fuß" geformt werden und Timer1 darf nicht mit seinen Interrupts "dazwischenfunken", denn dann würde das Sendesignal verstümmelt. Bei 3.686 MHz Takt, Vorteiler 1:32 und 19200 Bit/s gab es Probleme: es gingen etwa 250 Zählschritte verloren (14950 statt 15200). Den Fehler kann man zwar im Python-Programm ausgleichen, aber bei den letztlich gewählten 16 MHz Quarz-Takt, 115200 Bit/s Sende-Takt und Timer1-Vorteiler 1:128 läuft alles ohne Tricks rund.

Verwendet man als serielle PC-Schnittstelle keinen klassischen RS232-USB-Wandler, sondern einen TTL-USB-Wandler (z.B. den auf dem Mikrocontroller-LP85-Board), darf das Sendesignal nicht invertiert werden.

' Netzfreq-2.bas
' Fuses: L = 0xff    H = 0xdf    E = 0xfe
' Compiler BASCOM 2.0.7.5 DEMO 

$Regfile = "attiny85.dat"
$Crystal = 16000000
$Hwstack = 40
$Swstack = 4
$Framesize = 4


Dim F As Long
Dim Ovfcnt As Long

Config Portb.0 = Output
Config PortB.1 = Output

' bei Verwendung eines RS232-USB-Wandlers: Open "comb.1:115200,8,n,1,INVERTED" For Output As #1 ' bei Verwendung eines TTL-USB-Wandlers stattdessen: ' Open "comb.1:115200,8,n,1" For Output As #1 Config Timer0 = Counter , Edge = Falling
Enable Timer0
On Timer0 Tim0isr

Config Timer1 = Timer , Prescale = 128 Enable Timer1
On Timer1 Tim1isr

Enable Interrupts


Do If Ovfcnt < 31 Then ' erzeugt Blinksignal PortB.0 = 1 Else PortB.0 = 0 End If Loop Tim0isr: F = TCNT1
TCNT1 = 0 ' Counter1 zuruecksetzen TCNT0 = 206 ' Counter0 zuruecksetzen Shift Ovfcnt, Left, 8 ' ist schneller als Multiplikation mit 256 F = F + Ovfcnt ' Werte um 125000 Ovfcnt = 0 Print #1 , F
Return Tim1isr: Incr Ovfcnt ' Ovfcnt: Overflow-Counter Return End



PC-Software



Screenshot mit Plot-Anzeige und www.netzfrequenzmessung.de im Browser


Die PC-Plot-Software ist ein Python2-Programm. Geschrieben wurde es von Frank Behlich für den Raspberry Pi; (siehe hier) , ich habe lediglich den Zufallswerte-Generator durch die Abfrage der seriellen Schnittstelle ersetzt und die Achsen anders parametriert.

Python2 gibt es auch für Windows (siehe www.python.org, derzeit Version 2.7.15). Zusätzlich wird die Bibliothek "pyserial" benötigt. Ich kann es mangels Windows nicht testen, aber wenn man im Internet nach "pyerial windows site:adafruit.com" sucht, findet man ein Dokument "Installing Python and PySerial", in dem das Vorgehen erklärt wird. Im Python-Programm muss noch die Schnittstellen-Bezeichnung angepasst werden (statt /dev/ttyUSBx muss es comx heissen).

Python2 gilt als veraltet, wird aber noch gepflegt. Unter Python3 steigt das Programm leider mit einer Fehlermeldung aus, weil Frank irgendwo im Programm eine Integer-Variable mit einer Variablen des Typs "None" vergleicht und das unter Python3 nicht mehr zulässig ist.

In den Zeilen
v = (125030.0 / u)
v = v * 50.0 werden die ca. 125030 Zählertakte in den Bereich um 50 Hz umgerechnet (für die Konsolen-Ausgabe), in der Zeile
u = 260 + (u - 125030.0) werden die ca. 125030 Zählertakte in den Bereich 10 ...510 umgemappt (ergibt im Plot einen Wert von 50,1 Hz ... 49,9 Hz).

Der Wert 125030 gilt für einen Mikrocontroller ohne Bürde-Kapazitäten. Verwendet man die "odentliche" Schaltung, ist der Wert auf 125000 zu ändern.

Bei der Initialisierung der seriellen Schnittstelle werden DTR und RTS auf 1 gesetzt: falls man das alte LP-Mikrocontroller-Board mit DSub-9-Buchse verwendet, wird aus den beiden Signalpegeln die Betriebsspannung des Boards gewonnen.

# Serial Scope
# fuer Python 2.7
# fuer Mikrocontroller ohne Quarz-Buerdekondensatoren (Takt 4 kHz zu hoch)
# mit Buerdekondensatoren: 125030.0 durch 125000 ersetzen!

import Tkinter as tk
import serial

step_x = 2 def scope(cv, x, step_x, pt): def measure_point(): try: u = int(ser.readline()) # u = AtTiny-Zaehlerschritte v = (125030.0 / u) # normierter Kehrwert v = v * 50 # Frequenz in Hz print ("{0:2.3f}".format(v)) u = 260 + (u - 125030.0) # zufaellig kann "u" ohne Multiplikator verwendet werden if u < 0 or u > 520: # bei Fehlmessungen auf Mittellinie setzen u = 260
print "Fehler" except: u = 260 print "Fehler" return u

if x < 720: if pt > 0: last_y = cv.coords(pt)[-1] else: cv.delete("line_point") last_y = 250 x = x + step_x
pt = cv.create_line(x-step_x, last_y , x, measure_point(), \
fill = "blue", tag="line_point", width=2) else: x = 0 pt = None cv.after(1, scope, cv, x, step_x, pt) root = tk.Tk() root.title("Zeitlicher Verlauf der Netzfrequenz") cv = tk.Canvas(root, width=730, height=550, bg="white") cv.pack(padx=5, pady=5) for n in range (0,50): cv.create_line(0, 510-n*25, 720, 510-n*25, fill = "lightblue") # waagrechte Rasterlinien for n in range (1,14): cv.create_line(n*60, 10, n*60, 510, fill = "lightblue") # senkrechte Rasterlinien for n in range (1,20): cv.create_text(0, 510-n*50, text=str(49.9 + n*0.02) , anchor="w") # senkrechte Beschirftung for n in range (1,8): cv.create_text(n*120, 530, text=str(n*60), anchor="s") # waagrechte Beschriftung cv.create_line(0, 235, 720, 235, fill = "black") # schwarze Linie Beginn Regelbereich cv.create_line(0, 285, 720, 285, fill = "black") # schwarze Linie Beginn Regelbereich tk.Label(root, text="senkrecht: Netzfrequenz in Hz waagrecht: Sekunden").pack() scope(cv, 0, step_x, None) ser = serial.Serial('/dev/ttyUSB1', 115200, dsrdtr=1, rtscts=1) # dsrdtr=1 und rtscts=1: Spannungsversorgung fuer LP Mikrocontroller-Board ser.flush() tk.mainloop()


Beobachtungen

Großverbraucher bezahlen nicht nur die elektrische Arbeit (kWh bzw. MWh), sondern auch eine zusätzliche Tarifkomponente für die anfallende Spitzenleistung (also kW bzw. MW). Das soll den Energieverbrauch verstetigen, damit das Netz nicht für extreme Spitzenleistungen ausgelegt werden muss. Der Spitzenlast wird allerdings nicht sekundengenau erfasst, sondern über größere Zeitintervalle gemittelt (in Deutschland sind es meist 15 Minuten). Für Großverbraucher lohnt es sich also, nicht zeitkritische intermittierende Verbraucher (z.B. Kühlanlagen oder Wärmeerzeuger) ein paar Minuten später einzuschalten, nachdem das neue Zählintervall begonnen hat.

Möglicherweise gelten im europäischen Ausland 30-Minuten-Intervalle, denn insbesondere zur halben und vollen Stunde sieht man oft ein rasches Absacken der Netzfrequenz auf z.B. 49,94 Hz, das dann in den folgenden Minuten wieder ausgeregelt wird. Vielleicht werden aber auch lediglich zahlreiche Verbraucher zu "runden" Zeiten automatisch ein- bzw. ausgeschaltet.

Download der Quellcode-Files: 0219-netzfreq.zip

Siehe auch: Netzfrequenzanzeige im Arduino-Plotter


Screenshot-Erweiterung von Frank Behlich

So sollte auch die Speicherung des aktuellen Fensters möglich sein:

import Tkinter as tk
import serial
import datetime

step_x = 2

def scope(cv, x, step_x, pt):

def measure_point():

try:
u = int(ser.readline()) # u = AtTiny-Zaehlerschritte
v = (125030.0 / u) # normierter Kehrwert
v = v * 50 # Frequenz in Hz
print ("{0:2.3f}".format(v))
u = 260 + (u - 125030.0) # zufaellig kann "u" ohne Multiplikator verwendet werden
if u < 0 or u > 520: # bei Fehlmessungen auf Mittellinie setzen
u = 260
print "Fehler"
except:
u = 260
print "Fehler"
return u

if x < 720:
if pt > 0:
last_y = cv.coords(pt)[-1]
else:
cv.delete("line_point")
last_y = 250

x = x + step_x
pt = cv.create_line(x-step_x, last_y , x, measure_point(), \
fill = "blue", tag="line_point", width=2)
else:
x = 0
pt = None
cv.after(1, scope, cv, x, step_x, pt)

root = tk.Tk()

root.title("Zeitlicher Verlauf der Netzfrequenz")
cv = tk.Canvas(root, width=730, height=550, bg="white")
cv.pack(padx=5, pady=5)
for n in range (0,50):
cv.create_line(0, 510-n*25, 720, 510-n*25, fill = "lightblue") # waagrechte Rasterlinien
for n in range (1,14):
cv.create_line(n*60, 10, n*60, 510, fill = "lightblue") # senkrechte Rasterlinien
for n in range (1,20):
cv.create_text(0, 510-n*50, text=str(49.9 + n*0.02) , anchor="w") # senkrechte Beschirftung
for n in range (1,8):
cv.create_text(n*120, 530, text=str(n*60), anchor="s") # waagrechte Beschriftung
cv.create_line(0, 235, 720, 235, fill = "black") # schwarze Linie Beginn Regelbereich
cv.create_line(0, 285, 720, 285, fill = "black") # schwarze Linie Beginn Regelbereich

tk.Label(root, text="senkrecht: Netzfrequenz in Hz waagrecht: Sekunden").pack()
scope(cv, 0, step_x, None)
tk.Button(root, text="Speichern", command=lambda : cv.postscript(
file="cv.{0:%d%b%Y_%H_%M_%S.%f}.ps".format(
datetime.datetime.utcnow()))).pack()

ser = serial.Serial('/dev/ttyUSB1', 115200, dsrdtr=1, rtscts=1)
# dsrdtr=1 und rtscts=1: Spannungsversorgung fuer LP Mikrocontroller-Board
ser.flush()

tk.mainloop()

Ralf hat es getestet: Mit dem "Speichern"-Knopf erzeugt man eine PostScript-Datei. Ich habe mal eine solche ps-Datei angehängt und einen JPG-Screenshot, falls auf Deinem Rechner *.ps-Dateien nicht angezeigt werden.



Eigentlich bräuchte das Programm noch einen Auswahldialog für die serielle Schnittstelle und eine kontinuierliche Logging-Funktion für die Messwerte, z.B. in eine csv-Datei; dann könnte man auch längere Verläufe loggen und ggf. in einem Kalkulationsprogramm bearbeiten.



Elektronik-Labor   Projekte   AVR  Tiny85