Plotten der 50Hz-Netz-Frequenz
von Ralf Beesner
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