Firmware Measurement Model
This page documents how the EMS firmware converts raw ADC samples from the voltage sensor (ZMPT101B) and current sensor (ACS712 hall-effect, or SCT-013 clamp) into electrical quantities: true RMS voltage and current, active power, apparent power, power factor, and an estimated phase angle. It also covers the calibration routine, the accuracy limits of the present approach, and the methods that would improve it.
:::info Notation
Throughout, is the number of samples per measurement window (bufferSize = 200),
is a raw ADC count in the range with (12-bit), and
is the ADC reference. is the line frequency (50 Hz here).
:::
1. Signal Chain
Each analog channel follows the same path:
- Voltage: the ZMPT101B outputs an attenuated, level-shifted copy of the mains voltage, biased to roughly mid-rail.
- Current (ACS712): outputs a voltage centred on with sensitivity (5 A module). Because that can swing to 5 V, a resistor divider of ratio brings it into the 3.3 V ADC window.
- Current (SCT-013): a current transformer whose signal is biased to mid-rail and scaled so that .
The firmware fills two integer buffers, sampling the channels sequentially inside one loop:
for (int i = 0; i < bufferSize; i++) {
voltageBuffer[i] = analogRead(VOLT_SENSOR_PIN);
currentBuffer[i] = analogRead(CURR_SENSOR_PIN);
delayMicroseconds(sampleDelayUs); // 500 us
}
With two conversions plus the 500 µs delay, the effective sample interval is , giving a rate of roughly and a window length
2. From ADC Counts to Volts
A raw count maps to the pin voltage by
The ESP32 uses ADC_11db attenuation, whose true full-scale is closer to
than the nominal 3.3 V and is mildly non-linear near the
rails. The voltage gain factor (below) absorbs this constant scale error,
but the non-linearity itself is not corrected.
3. DC Offset Removal and Calibration
Both channels carry a DC bias that must be removed before computing AC quantities. The firmware treats the two channels differently.
3.1 Voltage offset (Step 2 of calibration)
With no special excitation, 1000 reads are averaged. Over s (50 cycles) the AC component averages to zero, leaving the DC bias in counts:
This stored value is then subtracted from every voltage sample at runtime.
3.2 Current offset (Step 1 of calibration)
Measured the same way with no load:
:::warning Calibrated, but unused
calculatePower() does not use . It removes the per-window mean
instead, and the ACS712 instantaneous
branch even subtracts a hard-coded midpoint. So is
effectively dead state. Real power is largely unaffected (the offset cancels
because the voltage term is zero-mean), but absolute instantaneous current is
biased. Wiring into the current conversion is a recommended fix
(Section 9).
:::
3.3 Voltage gain (Step 3 of calibration)
A single-point linear gain. The firmware measures the raw RMS over s,
the operator enters the true mains RMS from a multimeter, and
This assumes linearity and a zero intercept (single point, no offset term).
3.4 Current gain
There is no current gain calibration. Current amplitude relies entirely on the datasheet sensitivity () and resistor-divider tolerance. Since the ACS712 sensitivity spread is and is ratiometric to its 5 V supply, this is the dominant amplitude-error source.
4. True RMS
For a discrete signal of samples with the DC component removed, the RMS is
This is a true RMS: it is exact for any periodic waveform sampled adequately, not just sinusoids.
Voltage RMS (fixed stored offset , gain ):
Current RMS, SCT-013 (per-window mean , scale ):
Current RMS, ACS712 (divider ratio , sensitivity ):
A noise dead-band forces below 50 mA to suppress jitter at idle.
5. Active (Real) Power
Real power is the time-average of instantaneous power:
For Discrete Real Power calculation
For Analog Real Power calculation
where the per-sample physical values are
The instantaneous-product formulation is waveform-agnostic: it captures the real power of distorted loads automatically, because power is computed before any sinusoidal assumption is made. A small dead-band zeroes when .
:::note ACS712 branch inconsistency The ACS712 instantaneous current uses a fixed reference, while its RMS uses the dynamic mean . The AC gain is identical in both, so only the DC reference differs; since is zero-mean, the difference largely cancels in . It is still cleaner to use the same mean-removed value in both (Section 9). :::
6. Apparent power, power factor, phase angle
Because comes from instantaneous products and from true RMS, is the true power factor, already including any distortion component. This is correct.
The phase angle, however, is only an equivalent angle:
It is valid as a displacement angle only for sinusoidal signals, and it is always unsigned — , so it cannot distinguish leading (capacitive) from lagging (inductive) loads. Section 8 shows why it diverges from the true angle under distortion, and Section 9 gives the proper method.
7. The sinusoidal case (where the model is exact)
Let and . Then
So for a clean sine wave, every quantity the firmware reports — , , , , , and — is correct, limited only by sampling, ADC, and calibration error.
8. The non-sinusoidal case
Expand both signals in a Fourier series:
Because harmonics of different orders are orthogonal over a period, only matched orders contribute to real power:
and the RMS values aggregate all harmonics:
The firmware's time-domain sums compute exactly these quantities — provided the sampling resolves the harmonics (Section 10). So , , , , and the true remain correct for distorted loads.
8.1 Why breaks
Total harmonic distortion of the current is
For a clean (fundamental-only) voltage feeding a distorted current, the true power factor factors into a displacement part and a distortion part:
Hence whenever : the reported angle folds distortion into displacement and overstates the true phase shift. The firmware's should therefore be read as a "sinusoid-equivalent angle," not as the fundamental displacement angle.
8.2 Dead-band artifact
When the dead-bands force or near idle, collapses to 0 and reports . This is a cosmetic artifact of the thresholds, not a real reading.
9. Sampling considerations and error sources
| Effect | Cause | Consequence |
|---|---|---|
| Aliasing | , no anti-alias filter | Harmonics above fold back into the band, corrupting RMS and for sharp-edged waveforms |
| Spectral leakage | Window is cycles, non-integer, not zero-cross locked | Small RMS / error |
| Inter-channel skew | V and I sampled sequentially, not simultaneously | Phase error ; power error (negligible near , ~2–3% at for a 50 µs gap) |
| Timing jitter | delayMicroseconds + variable ADC time | Non-uniform smears any frequency-dependent result |
| ADC non-linearity | ESP32 SAR ADC, worse near rails | Absolute amplitude error |
| Software floating point | ESP32-C3 has no FPU; classic ESP32 has only single-precision | Per-sample double math is slow, limiting achievable |
A useful skew estimate: at 50 Hz, one ADC conversion of between channels is
10. Accuracy summary
| Quantity | Sinusoidal | Non-sinusoidal | Limiting factors |
|---|---|---|---|
| , | Accurate | Accurate (if harmonics within Nyquist) | ADC cal, aliasing |
| (real power) | Accurate | Accurate (instantaneous product) | Skew, aliasing, offsets |
| (apparent) | Accurate | Accurate | RMS accuracy |
| Accurate | Accurate (true PF) | and accuracy | |
| Accurate (unsigned) | Inaccurate | Distortion folded in; sign lost | |
| Lead / lag direction | Not available | Not available | is symmetric |
11. Recommended improvements
11.1 Use the calibrated current offset
Replace the per-window mean and the fixed reference with the stored for a consistent, drift-aware zero:
with the (newly calibrated) current gain.
11.2 Add a current gain calibration
Apply a known resistive load , measure , and store
This removes the dominant amplitude error from sensitivity/divider tolerance.
11.3 Anti-aliasing filter
A first-order RC low-pass on each analog input with a corner around attenuates content above Nyquist before it can fold back.
11.4 Deterministic, timer-driven sampling
Drive the ADC from a hardware timer (or the ESP32 continuous / DMA ADC mode) at
a fixed, known instead of delayMicroseconds. This removes jitter and
makes the window length exact.
11.5 Simultaneous V/I sampling
Eliminate inter-channel skew by sampling both channels at the same instant (sample-and-hold front end, or a simultaneous-sampling ADC). A software-only stopgap is to linearly interpolate one channel to the other's timestamp:
11.6 Synchronous / whole-cycle windowing
Detect zero crossings (or lock a PLL to the line) and integrate over an integer number of cycles to eliminate leakage.
11.7 Signed displacement angle and reactive power
Extract the fundamental of each channel with the Goertzel algorithm at to obtain and , then
The sign of distinguishes inductive (, lagging) from capacitive (, leading). A total non-active power consistent with IEEE Std 1459 is
11.8 Report THD
With the harmonic magnitudes from a DFT/Goertzel bank,
11.9 Energy accumulation
Integrate power over time for kWh:
11.10 Numerical performance
On the FPU-less C3, accumulate integer sums of squares and products in int64_t
inside the loop and convert to float once at the end; precompute constant
scale factors outside the loop. Prefer float over double everywhere the
extra precision is not needed.
11.11 Robust offset and ADC calibration
Use a median (not mean) for offset estimation to reject spikes, and apply the
ESP32 eFuse Vref characterization (esp_adc_cal) to linearize the ADC.
12. Alternative measurement methods
- Dedicated metering AFEs. ICs such as the ADE7953, ADE7758, ATM90E26/E32, or CS5490 perform simultaneous sampling, harmonic-aware power, signed reactive power, and energy accumulation in calibrated hardware, communicating over SPI/I²C/UART. This is the most accurate path and offloads the MCU entirely.
- External simultaneous-sampling ADC. A delta-sigma ADC like the ADS131M02 samples both channels at the same instant with high resolution, removing skew and ADC-linearity concerns while keeping the power math on the MCU.
- Frequency-domain processing. Run a windowed FFT (e.g. Hann window) on each channel to obtain per-harmonic magnitude and phase, then compute , , and THD spectrally. Heavier on the MCU but gives a full harmonic picture.
- Goertzel single-bin extraction. Cheaper than a full FFT when only the fundamental (and a few harmonics) are needed for displacement PF and angle.
13. Equation ↔ code reference
| Symbol | Meaning | Code variable |
|---|---|---|
| samples per window | bufferSize | |
| sample interval | sampleDelayUs (+ conversion time) | |
| ADC reference | referenceVoltage | |
| ADC full scale | adcMax | |
| voltage DC offset | voltageOffset | |
| current DC offset (unused) | currentOffset | |
| voltage gain | voltageCalibration | |
| SCT scale (V/A) | mVperAmp / 1000 | |
| ACS712 divider ratio | voltageDividerRatio | |
| ACS712 sensitivity | hall_sensitivity | |
| RMS voltage | voltageRMS | |
| RMS current | currentRMS | |
| active power | realPower | |
| apparent power | apparentPower | |
| power factor | powerFactor | |
| phase angle | phaseAngle |
:::caution Mains safety Calibration Step 3 requires measuring live mains with a multimeter, and the sensors connect to mains during normal operation. Perform these steps only with the circuit properly enclosed and isolated, and only if you are competent to work around live AC. :::