Guten Morgen zusammen,
in einer Applikation möchte ich den Strom an drei Phasen messen. Nutzen möchte ich dazu einen ESP32, da dieser mehrere Analog-Input-Kanäle hat und direkt die Konnektivität herstellen kann. Es gibt dazu wunderbares Beispiel, wie etwa unter dem Link Open_Energy_Monitor_mit_dem_ESP32. Nach näherer Analyse dieser Lösung und der Sketches unter openenergymonitor habe ich habe mal wieder grundlegende Fragen, die ich gerne mit Euch diskutieren würde, da ich das gerne nachvollziehen möchte. Es geht um zwei Dinge: Zum einen den ADC des ESP32 und zweitens -wieder einmal eine Frage zu dessen Timing-.
-
ADC des ESP32
Ich habe in Foren gesehen, dass der ADC Schwächen hat und zwar erstens, eine Nichtlinearität und zweitens dass nicht der gesamte Bereich ausgeschöpft wird. Das führt mich zur Einstiegs-Frage:
Für Applikationen, die einen Analog-Input benötigen, nutzt man den ESP32 besser nicht?
Meine nächsten Fragen adressieren die Angaben zum Eingangsbereich des ESP32. Oftmals steht einfach, dass die maximale Spannung 3,3 Volt beträgt. Jetzt steht aber unter https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/adc.html unter dem Stichwort “Attenuation”, dass die Eingangsspannung bis zu 4,9 Volt betragen kann (wenn 11dB attenuation (ADC_ATTEN_DB_11) gewählt werden.
Hier ist Frage: Ist das richtig?
Dieser Spannungsbereich wird irgendwie dann noch einmal verringert, da -ebenfalls unter obigem Link- folgendes steht:
0dB attenuaton (ADC_ATTEN_DB_0) between 100 and 950mV
2.5dB attenuation (ADC_ATTEN_DB_2_5) between 100 and 1250mV
6dB attenuation (ADC_ATTEN_DB_6) between 150 to 1750mV
11dB attenuation (ADC_ATTEN_DB_11) between 150 to 2450mV
Meine Aufgabe, bzw. Idee ist/war es, den ESP32 für die Strommessung an drei Phasen heranzuziehen. Nutzen will ich diesen Sensor. Dieser liefert jeweils eine Ausgangsspannung von 0-1 Volt. Ich denke, ich muss denn letztendlich um den Sensor und die Auflösung folgende Einstellungen vornehmen:
//…
adc1_config_width(ADC_WIDTH_BIT_12); // 12 Bit für maximale Auflösung
adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0); // Messbereich 100 and 950mV → Sensor hat 0-1 V
//…
Ist das für meinen Sensor korrekt oder übersehe ich da etwas? Ich muss natürlich “hoffen” bzw. überprüfen, ob meine Sensorwerte nicht über 950mV hinausgehen, bzw. nicht unter 100 mV fallen…?
-
Timing der Stromerfassung
Hier geht mir es um den korrekten und saubersten Ansatz zur Erfassung der Messwerte. Wenn ich die ganzen Beispiele korrekt nachvollzogen habe, werden bei der Erfassung von Wechselgrößen die Anzahl der Nulldurchgänge (der Sinusschwingungen) gezählt und dann die Effektivwerte berechnet. Jetzt stellt sich mir die Frage, wie ich eine korrekte Zeitbasis erhalte, bzw. ob das für meine Anforderung überhaupt nötig ist. Dazu habe ich einmal mögliche Ansätze zusammengefasst:
Ansatz 1: "Einfaches Sampling"
Im Beispiel unter Open_Energy_Monitor_mit_dem_ESP32 wurde die Datenerfassung gelöst, indem in der loop jeweils für jede der Phasen einfach analogRead benutzt wurde:
//Phase1
for (int n=0; n<numberOfSamples; n++)
{
//Used for offset removal
lastSampleI=sampleI;//Read in voltage and current samples.
sampleI = analogRead(inPinI1);//Used for offset removal
lastFilteredI = filteredI;//Digital high pass filters to remove 1.6V DC offset.
filteredI = 0.9989 * (lastFilteredI+sampleI-lastSampleI);//Root-mean-square method current
//1) square current values
sqI = filteredI * filteredI;
//2) sum
sumI += sqI;
delay(0.00002);
}
//**************************************************************************
//Phase2+3 siehe oben
Hier stellen sich mir mehrere Fragen: Was hat es hier mit dem delay(0.0002) auf sich? Sind das nicht immer ganzzahlig Millisekunden-Werte? Passt das allgemein so? Ich denke das geht, wenn die Anforderungen an die Genauigkeit nicht ganz so anspruchsvoll sind, denn die Stromwerte von Phase 1, 2 und 3 sind ja zeitlich um die delay versetzt…?
Ansatz 2: "Einfaches Sampling mit maximaler Samplingrate und Zählen der Nulldurchgänge"
Ein weiterer Ansatz wäre es sicherlich, die Nulldurchgänge wirklich zu zählen und dann die 3 Kanäle auf einmal zu lesen:
// code from https://community.openenergymonitor.org
unsigned long start = millis(); //millis()-start makes sure it doesnt get stuck in the loop if there is an error.
while(st==false) //the while loop…
{
startV = analogRead(inPinV); //using the voltage waveform
if ((startV < (ADC_COUNTS0.55)) && (startV > (ADC_COUNTS0.45))) st=true; //check its within range
if ((millis()-start)>timeout) st = true;
}//-------------------------------------------------------------------------------------------------------------------------
// 2) Main measurement loop
//-------------------------------------------------------------------------------------------------------------------------
start = millis();while ((crossCount < crossings) && ((millis()-start)<timeout))
{
numberOfSamples++; //Count number of times looped.
lastFilteredV = filteredV; //Used for delay/phase compensation//-----------------------------------------------------------------------------
// A) Read in raw voltage and current samples - PHASE 1, 2 & 3
//-----------------------------------------------------------------------------
sampleI1 = analogRead(inPinI); //Read in raw current signal - PHASE 1
sampleI2 = analogRead(inPinI); //Read in raw current signal - PHASE 2
sampleI3 = analogRead(inPinI); //Read in raw current signal - PHASE 3lastVCross = checkVCross;
if (sampleV > startV) checkVCross = true;
else checkVCross = false;
if (numberOfSamples==1) lastVCross = checkVCross;if (lastVCross != checkVCross) crossCount++;
}
}
Ich denke, die “Zeitbasis” passt hier schon besser…?
Ansatz 3: "Sampling mit festgelegter Samplingrate über Timer-Interrupt und Zählen der Nulldurchgänge oder ADC Conversion Complete Interrupt Service Routine (ISR)"
Ein weiterer -und wahrscheinlich der sauberste- Ansatz bezüglich der Zeitbasis wäre vielleicht der Einsatz eines Timer-Interrupts, der sicherstellt, dass der ADC mit einer festen Abtastrate abgefragt wird und zudem sicherstellt, dass die drei Stromsamples auch wirklich zu einem Zeitwert gehören. Hier habe ich mal einen ersten “Pseudocode” erstellt:
void IRAM_ATTR onTimer() {
led=!led; // Toggle LED-Status
sampleI1 = analogRead(inPinI); //Read in raw current signal - PHASE 1
sampleI2 = analogRead(inPinI); //Read in raw current signal - PHASE 2
sampleI3 = analogRead(inPinI); //Read in raw current signal - PHASE 3
counterISR ++; // Hochzählen des Counters für jeden Interrupt
}
void setup() {
Serial.begin(9600);
pinMode(LEDPIN, OUTPUT); // LED-Pin als Ausgang
Serial.println("Timer-Start");
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 1000000, true);
timerAlarmEnable(timer); // Enable Timer
}
void loop() { /*Hier werden jetzt nur die Nulldurchgänge (counterISR) gezählt und die Variablen in ein Array geschrieben */ }
Meine Annahme wäre, dass ich hier nun bei jedem Interrupt gleichzeit drei Werte habe, die ich zu diesem Zeitpunkt den drei Stromwerten zuordnen kann, oder benötige ich hier noch weiteren Code, der auf die Umwandlung der einzelnen ADC-Kanäle wartet, z.B. über
bool IRAM_ATTR __adcStart(uint8_t pin){
bool IRAM_ATTR __adcBusy(uint8_t pin){
uint16_t IRAM_ATTR __adcEnd(uint8_t pin)
[/li]
Es wäre sehr nett von Euch wenn sich der Eine oder Andere mal Zeit nehmen und mit mir in die Diskussion einsteigen könnte. Mir geht es vor allem darum, etwas zu lernen.
Ich freue mich auf Eure Rückmeldungen!
Viele Grüße
Bire