Elektroheizungen netzverträglich schalten             

von Ralph Maas                       

Elektronik-Labor  Projekte  Mikrocontroller  Raspberry            



Motivation

Wir haben zwei elektrische Fußleistenheizungen gegen Schimmelbildung. Diese müssen im Winter nicht im Dauerbetrieb laufen, es reichen wenige Stunden am Tag. Wenn aufgrund der Gas-Knappheit immer mehr elektrisch geheizt wird, kann das zur Überlastung des Stromnetzes führen.

An der Netzfrequenz kann man ablesen, wenn das gesamte Stromnetz mehr Erzeugung als Last hat. Die Netzbetreiber gleichen das kurzfristig mit (Primär-)Regelleistung aus. (Details siehe netzfrequenzmessung.de oder netzfrequenz.info).Daher kam die Idee auf, die elektrische Heizung nur zu betreiben, wenn die Netzfrequenz so hoch ist, dass die Netzbetreiber sowieso schon Anbieter dafür bezahlen, Energie aus dem Netz zu ziehen. (Im Gegensatz zu diesen Anbietern bringt uns das aber keinen monetären Vorteil). Eine mögliche lokale Überlastung und das Auslösen der Sicherungen (Hausanschlusskasten, Trafo-Abgang) kann man damit nicht verhindern.

Konzept

Die Netzfrequenz wird über einen PiPico gemessen. Dazu wird der hochohmige Eingangswiderstand der GPIO-Pins genutzt, um das vom 50Hz Netz erzeugte elektrische Feld berührungslos zu erfassen. Eine Schaltschwelle mit Hysterese (Einschalten über 50,01 Hz, Ausschalten unter 50,00 Hz) und eine Mittelwertbildung über 10 Sekunden führt dazu, dass nicht zu häufig geschalten wird. In der Mittelwertbildung werden gelegentlich auftretenden unplausible Messungen ausgeblendet.

Weil der Stromhandel nur in 15-Minuten-Intervallen erfolgt, die Lastveränderung aber in kleineren Zeitintervallen erfolgt, gibt es häufig einzelne Minuten in jeder 15-Minuten-Periode, in denen die Frequenz über der Schaltschwelle ist. (Beispielverläufe gibt es bei „https://www.netzfrequenz.info/verlauf-1-stunde-mit-daten-aus-den-vorjahren“, oder nach Aufbau der Schaltung im Seriellen Plotter der Arduino-IDE). Im Code ist eine Einstellmöglichkeit, ob im Seriellen Plotter die Mittelwertbildung der Frequenz, oder das (analog dargestellte) Eingangsniveau dargestellt wird. Die Schaltung der Verbraucher erfolgt über Funksteckdosen (siehe: https://www.elektronik-labor.de/Raspberry/Pico20.html)

Verhältnis zur Primärregelleistung und Risikoabschätzung

Bei 50,01 Hz werden 30 MW negative Primär-Regelleistung (nur in Deutschland) aktiviert. (Bei 50,2 Hz sind es 600 MW).

Wenn 30.000 Anwender 1kW Heizleistung nach diesem Konzept betreiben, kommen wir in dieselbe Größenordnung.

Daher werden wir hier wohl nicht das Konzept der Primärregelleistung beeinflussen.

Material

Messungen

Spannungsverlauf am Analog/Digitaleingang und digitales Signal:

 

Darstellung der Netzfrequenz (gemessene Frequenz: dunkelblau, Mittelwert: beige, Schaltfrequenz/Schaltvorgänge: hellblau)

Aufbau

Die LED-Anzeige entspricht nicht ganz dem Datenblatt im Internet. Daher habe ich sie durchgemessen. Die Verbindung erfolgt dann immer zu den nächstliegenden PIN des PiPico:

Schaltplan:

 

Von oben sieht es dann so aus:

Vor dem Zusammenbau:

Erfahrungen

Wenn der Frequenzmesser an einem USB-Netzteil angeschlossen ist, dann reicht der verbleibende „Netzbrumm“ für die berührungslose Übertragung der 50Hz. Beim Anschluss an den Laptop musste ich meist noch ein Netzkabel in die Nähe des Frequenzmessers bringen.

Code

// Programm zur Messung der Netzfrequenz auf 1mHz genau und Anzeige auf 7-Segment-Anzeige
// mit berührungsloser Einkopplung und mit schalten von Funksteckdosen
// Pico_Freq1 Beispiel Buch Seite 60 Burkhard Kainka, RPi Pico Schaltungen und Projekte
// Erweitert um Multicore-Beispiel Seite 62
// Ergänzt um Ideen von Jens Mueller www.netzfrequenzanzeige.de ; www.pc-projekte.de
// Ergänzt um Funktionen für 7-Segment-Anzeige Buch Seite 99
// Ergänzt um Analog-Messung des berührungslos eingespeisten Signals, Seite 52

#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "pico/multicore.h"
#include "hardware/adc.h"

#define Analog // Wenn das definiert ist, werden auf Seriellem Plotter die Spannungen ausgegeben
// Wenn auskommentiert, wird die Frequenz ausgegeben
// Schalten über Funksteckdose
const int pin_on = 6, pin_off = 22; // Diese PIN sind auch auf der LED-Anzeige links von den Ziffern
bool Schalter = false, Schalter_old = true;
const word Einschalt = 50010; // Bei dieser Frequenz wird eingeschaltet
const word Ausschalt = 50000; // Bei dieser Frequenz wird ausgeschaltet
const word Ueber_unplausibel = 50200; // Oberhalb dieser Frequenz wird der Wert nicht zur Mittelwertbildung verwendet
const word Unter_unplausibel = 49800; // Unterhalb dieser Frequenz wird der Wert nicht zur Mittelwertbildnung verwendet
// Diese beiden Variablen helfen, wenn die berührungslose Einkopplung zu schwach ist und Wert unplausibel
// Auf 7-Segment wird der unplausible Wert aber trotzdem angezeigt
word Frequenz_mittel = 49999; // Über die Mittelwertbildung wird erreicht, dass nicht so schnell hin und her geschaltet wird

// Analogmessung
unsigned int messV=0, digitalV=0;
const int analog_in = 26;

// Variablen für Frequenzmessung
const int pin_in = 1; // Das ist der digitale Eingang. Pin 1 und Pin 26 werden elektrisch verbunden
bool in_old = false, in_new = false;
word Frequenz = 50001; // Frequenz in mHz
const word Ueberfrequenz = 50010; // Überfrequenz ab 50,01 Hz (Totband 10mHz) für LED-Anzeige
const word Unterfrequenz = 49990; // Unterfrequenz unter 49,99 Hz (Totband 10mHz) für LED-Anzeige
uint32_t Startzeit = 0; // Zwischenspeicher für Startzeit
uint32_t Laufzeit = 0; // Zwischenspeicher für Zeitdifferenz
byte Periode = 1; // Laufende Periode
const byte Ziel_Perioden = 50 ; // Anzahl der Perioden über die gezählt werden soll
// Mindestens 10 Perioden sind ein guter Wert, hier entspricht 1us 0,25mHz,
// Schwankungen um 1-2 us beeinflussen also die letzte Ziffer nicht
const unsigned long Multiplikator = 1000000 * Ziel_Perioden; // Für die Berechnung der Frequenz in Hz aus den us
// (Faktor 1000 für mHz muss in Rechnung ergänzt werden, weil sonst overflow)
// Weil unsigned long maximal
// 4,294,967,295 abbilden können, funktioniert die Rechnung nur bis 4 Perioden ohne Tricks
// 1.000.000 Multiplikator für Hz
// 1.000.000.000 Multiplikator für mHz

// Variablen für LED-Anzeige
const int led_Ueberfrequenz = 15; // GPIO 15 ist an Anzeige-12 (+)
const int led_FrequenzTotband = 14; // GPIO 14 ist an Anzeige-11 (o)
const int led_Unterfrequenz = 17; // GPIO 17 ist an Anzeige-14 (-)
// Nicht verwendete LED an GPIO 9,11

// 7-Segment
const int led_Dezimalpunkt = 10; // GPIO 10 ist an Anzeige-6 (p)
int Ziffer[6]; // Ziffern für Anzeige
uint32_t Segment[14];
// 7-Segment-Ausgabe incl. der Einzel-LED
// GPIO-1.ste 2221111111111
// GPIO-2.ste 21098765432109876543210
// Rückwärts #decb-g+oa3.p.1f^42.... // a...g - Segmente; 1..4 Kathoden; -o+ - Unter/Totband/Überfrequen ^# - Schalter an/aus
uint32_t Output = 0b11111111111010111110000; // Diese werden auf einmal auf Output gestellt
uint32_t Maske = 0b01111010011000110110000; // Diese werden über maskierten Output nach Segment auf 1 gestellt

// Watchdog ob die Messung läuft
int Watchdog = 5; // Frequenzanzeige in Core 0 reduziert, Messung in Core 1 erhöht den Wert

void setup() { // Core 0 für Kommunikation und Anzeige
// 7-Segment-Ausgabe
// 221111111111
// 1298765432109876543210
// Rückwärts decb.g..a3...1f.42....
Segment[0] = 0b1111000011000110110000;
Segment[1] = 0b0011000001000100110000;
Segment[2] = 0b1101010011000100110000;
Segment[3] = 0b1011010011000100110000;
Segment[4] = 0b0011010001000110110000;
Segment[5] = 0b1010010011000110110000;
Segment[6] = 0b1110010011000110110000;
Segment[7] = 0b0011000011000100110000;
Segment[8] = 0b1111010011000110110000; // Das ist dezimal: 4.010.416 ; uint32 kann bis 4,294,967,295
Segment[9] = 0b1011010011000110110000;
Segment[10]= 0b0110010001000100110000; // n für Anzeige "nono" wenn keine Frequenzmessung
Segment[11]= 0b1110010001000100110000; // 0 für Anzeige "nono" wenn keine Frequenzmessung

gpio_init_mask(Output);
gpio_set_dir_out_masked(Output); // Stellt alle in Output mit 1 belegten GPIO auf OUTPUT

gpio_set_drive_strength(7, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(13, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(16, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(18, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(19, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(20, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
gpio_set_drive_strength(21, GPIO_DRIVE_STRENGTH_2MA); // Alle Anoden auf 2mA
pinMode(led_Dezimalpunkt, OUTPUT);
gpio_set_drive_strength(led_Dezimalpunkt, GPIO_DRIVE_STRENGTH_2MA);

// LED für Unter-/Überfrequenz/Totband
//pinMode(led_Ueberfrequenz, OUTPUT); // Oben schon in Summe über Output
//pinMode(led_FrequenzTotband, OUTPUT);
//pinMode(led_Unterfrequenz, OUTPUT);

gpio_set_drive_strength(led_Ueberfrequenz, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(led_FrequenzTotband, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(led_Unterfrequenz, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(pin_on, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände
gpio_set_drive_strength(pin_off, GPIO_DRIVE_STRENGTH_2MA); // erspart die Vorwiderstände

// Allgemein
set_sys_clock_khz(125000, true);
Serial.begin(115200);

// Analoganzeige
adc_init();
adc_gpio_init(analog_in);
//gpio_pull_up(26);
adc_select_input(0);

gpio_put(pin_off, 1); // Schalter ausschalten
}

void loop() {
ZiffernAufteilen();

/*// Nur Test ob die Aufteilung auf Ziffern funktioniert hat
Serial.print(Ziffer[1]); Serial.print(Ziffer[2]); Serial.print(Ziffer[3]); Serial.print(Ziffer[4]); Serial.print(Ziffer[5]);*/

gpio_put(led_Ueberfrequenz, LOW);
gpio_put(led_Unterfrequenz, LOW);
gpio_put(led_FrequenzTotband, LOW);
Schalter = false;

if ((Frequenz < Ueber_unplausibel) && (Frequenz > Unter_unplausibel)) {
//Frequenz_mittel = (word) (((Frequenz_mittel * 9.0) + (Frequenz *1.0) + 5.0 ) /10.0); // OK
//Frequenz_mittel = 49800 + (word) (((Frequenz_mittel - 49800) * 9.0) + ((Frequenz - 49800)*1.0) + 5.0 )/10.0; // Das geht, wenn auch nicht auf 1mHz genau
Frequenz_mittel = 49800 + (word) (((Frequenz_mittel - 49800) * 9) + ((Frequenz - 49800)*1) + 5 )/10; // Das geht, wenn auch nicht auf 1mHz genau
//Frequenz_mittel = (word) ((Frequenz_mittel * 0.9) + Frequenz * 0.1 + 0.5); // Das ist nicht auf 1mHz ran
} else {
#ifndef Analog
Serial.print(" Unpl. " );
Frequenz = 50000; // Sonst wird die Skala im Seriellen Plotter zu groß
#endif
Watchdog--; // Unplausible Frequenzen werden noch angezeigt,
}


if (Watchdog >=0) {
if (Frequenz >= Ueberfrequenz) gpio_put(led_Ueberfrequenz, HIGH);
else if (Frequenz <= Unterfrequenz) gpio_put(led_Unterfrequenz, HIGH);
else gpio_put(led_FrequenzTotband, HIGH);
Watchdog--;
if (Frequenz_mittel >= Einschalt) Schalter = true; // Einschalten über der Einschaltfrequenz
else if ((Frequenz_mittel >= Ausschalt) && Schalter_old) Schalter = true; // Nicht ausschalten, wenn über der Ausschaltfrequenz
}
else {
Ziffer[1] = 10; // "n"
Ziffer[2] = 11; // "o"
Ziffer[3] = 10; // "n"
Ziffer[4] = 11; // "o"
}

Anzeige7Segment(); // Summe der Anzeigeschleife ist 1000ms = 1s passt zu 50 Perioden bei 50Hz = 1s
Schalter_old = Schalter;
}

void setup1() { // Core 1 für zeitkritische Messung
//pinMode(pin_in, INPUT_PULLUP); // Wenn nicht berührungslos gemessen wird, dann benötigt man den Pull-Up für den Optokoppler
pinMode(pin_in, INPUT); // Eingang definiert ohne Pull-Up, um berührungslose Einkopplung zu nutzen

while (true) {
while (gpio_get(pin_in));
while (!gpio_get(pin_in));
// Hier ist pin_in gerade auf true gegangen, die erste Periode beginnt
Startzeit = time_us_32();
while (Periode <= Ziel_Perioden) {
in_new = (gpio_get(pin_in));
if (in_new && !in_old) {
Periode++;
Serial.print(""); // Der Aufruf sorgt für die Übertragung der Variablen an den anderen Core
}
if (in_old && !in_new) {
Serial.print(""); // Der Aufruf sorgt für die Übertragung der Variablen an den anderen Core
}
in_old = in_new;
}
Laufzeit = time_us_32() - Startzeit;

// Frequenz = 1000 * Multiplikator / Laufzeit; // Diese Variante geht nur bis 4 Perioden, weil sonst overflow
Frequenz = Multiplikator * ( 1000.0 / float(Laufzeit)); // Mit diesem Umweg können auch mehr als 4 Perioden gemessen werden

Periode = 1; // Neustart
Serial.print(""); // Der Aufruf sorgt für die Übertragung der Variablen an den anderen Core

Watchdog = 5;
}
}


void loop1() {
}

void ZiffernAufteilen(){
word Rest = Frequenz;

Ziffer[5] = Rest%10; // ....0 mHz
Rest = Rest/10;
Ziffer[4] = Rest%10; // ...0. mHz
Rest = Rest/10;
Ziffer[3] = Rest%10; // ..0.. mHz
Rest = Rest/10;
Ziffer[2] = Rest%10; // .0... mHz
Rest = Rest/10;
Ziffer[1] = Rest%10; // 5.... mHz
Rest = Rest/10;
}

void Anzeige7Segment() {
#ifndef Analog
Serial.print(Frequenz);
Serial.print(" mHz Mittel: ");
Serial.print(Frequenz_mittel);
Serial.print(" mHz ");
Serial.print("Schaltfrequenz: ");
if (Schalter) Serial.println(Einschalt); // Zeigt die Frequenz an, ab der eingeschaltet wird, wenn eingeschaltet ist
else Serial.println(Ausschalt); // Zeigt die Frequenz an, ab der ausgeschaltet wird, wenn ausgeschaltet ist
/*Serial.print("Startzeit: ");
Serial.print(Startzeit);
Serial.print(" Laufzeit: ");
Serial.println(Laufzeit);*/
#endif

if (Schalter && !Schalter_old) {
gpio_put(pin_on, 1); // Einschaltimpuls
}
else if (!Schalter && Schalter_old) {
gpio_put(pin_off, 1); // Ausschaltimpuls
}
// Summe der Anzeigeschleife ist 1000ms = 1s passt zu 50 Perioden bei 50Hz = 1s
for (int i=0; i<250; i++) {
gpio_put(led_Dezimalpunkt, 1);
gpio_put_masked(Maske, Segment[Ziffer[1]]);
gpio_put(8,0);
AnalogAnzeige();
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[2]]);
gpio_put(4,0);
AnalogAnzeige();
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[3]]);
gpio_put(12,0);
AnalogAnzeige();
sleep_ms(1);
gpio_put_masked(Maske, Segment[Ziffer[4]]);
gpio_put(5,0);
AnalogAnzeige();
sleep_ms(1);
if (i==50) {
gpio_put(pin_on, 0); // Nach 0,2 Sekunden Impuls für Schalter beenden
gpio_put(pin_off, 0);
}
}

}

void AnalogAnzeige() {
messV = adc_read()*3300/4095;
digitalV = 1180; // Das ist das untere Ende der Hysterese
if (in_new) digitalV = 1590; // Das ist das obere Ende der Hysterese

#ifdef Analog
Serial.print("mess: "); Serial.print(messV);
Serial.print(" digi: "); Serial.println(digitalV);
#endif

}



Elektronik-Labor  Projekte  Mikrocontroller  Raspberry