# Computing stuff tied to the physical world

## Power Measurement (ACS) – code

In Software on Sep 15, 2011 at 00:01

For yesterday’s setup, I had to write a bit of code. Here’s the main part:

(this “powerACS” sketch can be found here)

The idea is to keep reading the analog pin as often as possible (several thousand times per second), and then keep track of its moving average, using this little smoothing trick:

``````    avg = (499L * avg + value) / 500;
``````

Each new measurement adds 1/500th weight to the average. I didn’t want to use floating point, so it takes some care to avoid long int overflow. Here’s how I scale the input (0 .. 1023) to microvolts (0 .. 3,300,000):

``````    uint32_t value = measure.anaRead() * (3300000L / 1023);
``````

Note that 500 x 3300000 is under the max of a 32-bit int, so the above “avg” calculation is ok.

It’s easy to calculate the offset, once we know the average voltage. Keep in mind that we’re measuring 50 Hz AC, and that the result we’re after is the fluctuation – i.e. what you get when putting a multimeter in VAC measurement mode. The way to do that is to calculate the “average absolute difference from the average”:

IOW, what I want is the average height of those red bars. I flipped the lower half up because we’re interested in the absolute difference (otherwise the bars would just cancel out).

Here’s the calculation:

``````    if (value > avg)
total += value - avg;
else if (value < avg)
total += avg - value;
++count;
``````

When the time comes to report a “range” reading, we calculate it as follows and reset the other variables:

``````    uint32_t range = total / count;
total = count = 0;
``````

Here’s some sample debug output on the serial port:

``````    [powerACS]
...
2560650 2569286 5916 31973363 5404
2576775 2569256 5911 32175178 5443
2563875 2569266 5917 32888792 5558
2567100 2570205 5911 33200506 5616
2573550 2569935 5917 32798060 5543
2580000 2565675 5910 183251691 31007  <- 100 W light bulb
2573550 2565108 5916 356840654 60317
2570325 2565479 5916 355916529 60161
2512275 2565467 5910 355676919 60182
2525175 2565688 5916 356664359 60288
2486475 2566547 5909 354854663 60053
2480025 2566631 5916 355567509 60102
2480025 2568658 5910 355608745 60170
2583225 2569370 5916 178542547 30179  <- off
2576775 2569122 5915 33193018 5611
2570325 2569274 5911 33236267 5622
2560650 2568988 5917 32873935 5555
2576775 2569290 5911 32733630 5537
2567100 2568564 5917 33390738 5643
...
2560650 2569179 5917 32917499 5563
2576775 2568640 5911 32354114 5473
2570325 2569121 5916 32820006 5547
2576775 2568319 5917 33896284 5728
2570325 2569996 5911 33120608 5603
2567100 2568514 5917 37697489 6371    <- 1.1 W power brick
2550975 2568582 5911 36764137 6219
2567100 2568767 5916 37434853 6327
2567100 2568813 5911 36272763 6136
2563875 2568659 5917 37738504 6377
2563875 2568355 5911 37911395 6413
2567100 2568586 5917 37444158 6328
2563875 2568672 5916 37607002 6356
2580000 2568879 5911 37293083 6309    <- off
2570325 2568330 5917 33082874 5591
2563875 2567713 5911 33007567 5584
2567100 2568380 5917 32770050 5538
2580000 2568846 5910 33002639 5584
2570325 2568844 5917 33201661 5611
``````

You can see the 60 mV signal when the power consumption is 100 W. Which is a bit low: I would have expected 100 W / 230 V * 0.185 V/A = 80 mV. But it turns out that the lamp really is an 87 W lamp, so that makes it 70 mV. I’ll attribute the remaining difference to my crude setup, for lack of a better explanation.

What’s worse though, is that there is always a 5.5 mV “hum” when no current is flowing (even when the whole circuit isn’t anywhere near a power line). One possible explanation could be ADC measurement noise: the 10-bit ADC resolution is 3.3 mV. Then again, note how multiple measurements do lead to a much finer resolution: the 60 mV readout fluctuates by only a fraction of one millivolt. Noise actually improves resolution, when averaged over multiple readings.

Still, the 5.5 mV baseline value makes it hard to reliably measure really low power levels. The 1.1 W test was from an unloaded power brick, i.e. an inductive load. It causes the output to jump from 5.5 mV to only 6.3 mV, which is not a very distinct result. Another 0.3 W test load didn’t even register.

Conclusion: this detects current fairly well, was very easy to set up, and offers galvanic isolation protection. But it does require a hookup to AC mains power and it’s not super effective at really low power levels.

1. JC, you are measuring the arithmetic mean value of the current. This is not equivalent to the ‘effective’ value which you get when calculating the RMS. The factor between RMS and arithmetic mean is 1.11 for sine wave, so if you take your 100W bulb, you will make an error of 11% which fits nicely to your ‘measured’ value of 87W. If you ‘know’ that your currents will always be pure sine, than you can just put the factor 1.11 into your current calculation, but many loads nowadays tend to be far away from drawing sine currents.

BR, Jörg. PS: can’t you measure the bulbs ‘real’ power value with a commercial power meter?

• Re: PS – yes, that’s what I did. The 87 W was measured, and now the results are properly explained – thank you.

• I agree – measuring the mean absolute value is not correct (my mains is nothing like a sine wave BTW – everyone in the area is just using their digital devices it seems ;)

Its dead easy to calculate the RMS directly by squaring the values and summing, taking square root only at the end. No correction factor for sine wave or whatever is then needed.

Of course all this is flawed from the start as current does not equal power.

2. With this low resolution at low power values it could also help to ’round’ the avg value at bit better with:

avg = ((499L*avg + value) + 250) / 500;

BR, Jörg.

• Tried it – no difference, as far as I can tell.

3. I’d suggest looking at the OpenEnergy monitor Emon library for Arduino. I think it has the hairy RMS arithmetic already sorted out. http://openenergymonitor.org/emon/node/68

4. Thanks – great tips, I’ll look into it. The Emon stuff is also on GitHub now.

Note that my first goal is just reliable detection of a power consumption threshold, so proper correction to W or VA is not essential. Accurate conversion to power use is a nice extra, of course.

5. According to the specs of the sensor, there is a 21 mV noise figure. When you bring it down to 5.5 mV with averaging, it is already a great achievement!

A narrow bandpass FIR filter @50 Hz might bring down the noise even further. It should be possible to do it in software on the AVR. I guess the limiting factor will be RAM, not CPU performance.

PS: I agree, that an RMS algorithm should/could be used. It would not be too difficult and can be done with integer arithmetic. However, I suspect, that an RMS measurement will make the noise problem even worse…