Home Power Usage Monitor

Gather power data from the breaker panel and beam it to a Raspberry Pi for storage and web serving

Similar projects worth following
After an unusually high electric bill with no obvious cause, I decided I needed a device that would monitor my power usage. A clamp meter was fine for just checking things out, but I needed something that would look at usage over time and preferably tell me how much power is being used by any one appliance. Thus the Power Monitor was conceived!

It works by gathering voltage and current data to calculate power. Voltage is measured from an AC/AC adapter wall plug (which also supplies power to the unit). Current is measured from 3 split core current transformers. Two for each phase of incoming power to my apartment, and another to put on a wire connected to any of the circuit breakers in the panel to measure individual loads (like Heating/Cooling, Washing Machine, Bedroom, etc.).

The brains of the unit will be a PIC18F26J13. I like PIC's because I'm familiar with them, and this one has a 12-bit ADC which is handy for those voltage/current measurements. It also has plenty of I/O to interface with extras. Extras like an LCD! Which is where power info will be displayed locally. However, in order to have a web server with pretty graphs I'll need to send the data elsewhere. One of the cheapest ways to do that is with one of those nRF24L01+ modules that you can get for about a $1 each.

Raspberry Pi's seem to be pretty good as stand alone web servers, so I'll try that. As a bonus it has some exposed I/O pins that will allow me to use another nRF24L01+. From there I just need to store the data in a base(!) (probably SQLite), and serve up some chart plotting javascript, or some such.

  • 1 × PIC18F26J13 Microprocessors, Microcontrollers, DSPs / Microcontrollers (MCUs)
  • 1 × AC/AC Wall Adapter 120Vin/9Vout
  • 1 × 16x2 Character LCD
  • 3 × SCT-013-000 Split Core 100A:50mA Current Transformer
  • 2 × nRF24L01+ Nordic Radio, SPI interface

View all 7 components

  • Back in Action

    Drew Yenzer02/27/2016 at 19:20 0 comments

    After a brief 6 month long hiatus, I've started working on this again. Some things I've done since the last project log:

    • Made nearly all components surface mount to reduce cost and space
      • The board is now 5x5cm, which is the sweet spot price point at many board fabs.
    • LDO's were out of Vin spec
      • My main clue for this is when one burned up upon applying power.
    • Changed values on the analog measurement resistors
      • Found out no load voltage from the wall transformer is 11.3Vrms (Nominal rating is 9Vrms), which prompted some new resistor values
    • Changed diode to standard silicon
      • For reasons that escape me now I changed the schottky to standard silicon, 0.5A rating changed to 1A. Probably because I was no longer bumping up against the 5V LDO's Vin requirement with the new 11.3Vrms power supply.
    • Changed sample rate to 2400Hz, adequate to 10th harmonic
      • The PIC couldn't handle the higher sample rate with all the calculations I'm doing. Still need to experiment if the higher sample rate is worth while.
    • Got NRF24 working
      • This bogged me down for a while. I finally set up an NRF24 on a Raspberry Pi and used the NRF24 python library for it. Couldn't figure out why it wasn't communicating for the longest time until I looked at the SPI1Write() function I was using from the PIC18 peripheral library that comes with the compiler. It was polling for an interrupt flag by default instead of the Buffer Full status bit that indicates an SPI transfer is complete. I'm not using the SPI interrupt so I changed that part and it works now.
    • Added RF stats and status to LCD
      • New screen shows status and # dropped packets
    • Latest version of XC8 compiler gets rid of PIC18 peripheral libraries.
      • After updating the compiler I found out they removed the PIC18 peripheral libraries that I used for a couple things. Namely setting up timers. Perhaps eventually I'll take those libraries out, but for now I'll be lazy and continue using XC8 v1.33 instead of the latest.
    • Needed to add a delay on power up before talking to LCD and NRF24
      • Found out the radio would only work after programming the PIC, but not on power up. Also the display would sometimes show strange characters after power on. Added a 5ms initialization delay before talking to either of them and that fixed the problem.

    The actual power monitor device has all the functionality to be standalone now, but before I can call it that I'd like to finish up a few more things:

    • Add phase compensation
      • I can measure the voltage phase shift from the wall transformer easily enough, but I'll need to set up a resistive load to measure the phase shift from the current transformer. Need to see if I have any old light bulbs...
      • Once that's measured I'll need to add some kind of interpolation between samples for the phase shifts.
    • Adjust Vin scale when LCD backlight is on/off
      • The wall transformers' output voltage varies between 11.3Vrms at no load to 9Vrms at full load. It's about 11.1Vrms with the LCD backlight on. I just need to quantify the different scalings, then with the average of the analog input determine which scale to use. Right now I'm summing the square of the analog input for the voltage measurement, so I'll need to keep track of this separately.
    • Delve into noise on analog inputs
      • In trying to get the radio working I've been testing with the current transformers unplugged. There's a small amount of noise on those inputs which will have them read about 8mA each. This combined with the phase errors is showing power readings of 2-3W/VA. I'll need to characterize the noise and see if anything can be done for it in software, or see if it goes away once the current transformer is attached.

    As for the rPi, the plan is:

    • Capture transmitted data
      • I'm currently just displaying it on screen, but I'll transition into storing it in a database.
    • Display data in a chart
    • Access chart over the network via http

    If anyone was following this waiting for an opportunity to build, the hardware is working now and you should be able to recreate it. Just software development from here...

    Read more »

  • Assemblage

    Drew Yenzer08/26/2015 at 00:39 0 comments

    Well I was able to get the board soldered up, and plastic case modified to mount everything. It looks pretty good so far, I think. Here's some pictures:

    You probably noticed the ribbon cable dangling off the LCD. Just when I went to clamp on the IDC connector, I found out the cable was much too big for it. Turns out for a 0.05" pitch connector you need half of that, or 0.025" pitch cable. No where does it explicitly tell you this, but if you look at the connector drawing, then find out how wide the ribbon cable is, you'll see it. So I've got some smaller cable on order now.

    Once I get that settled I can actually start testing out some code!

  • Simulation & Optimizations

    Drew Yenzer08/10/2015 at 23:28 0 comments

    If you'll remember, I want to sample voltage and current at 3840Hz. With a 2MHz instruction clock, that gives me 520 instructions between samples. On face value, that should be enough to capture 6 ADC values, multiply 16-bit numbers 9 times, and add 16 and 32-bit values 9 times. That's all I need to do, and without doing any calculations I naively figured that would certainly take less than 500 instructions. Well when I actually simulated it, it took...wait for it.... 4398 instructions! What! Even at the max oscillator frequency of 48MHz, that only allows 3125 instructions per 3840Hz. My fallback plan was shot.

    So I took a look at the assembly the XC8 compiler generated and it's largely garbage. Lots of NOPs, useless branches, useless moving values around (they're never used). After looking around the web for a bit I found this is normal for the free version. I can understand not optimizing the assembly for the free version, but come on inserting garbage instructions is not cool! I really don't want to write any assembly for this project if I don't have to, so I took a look at how I can change the C code around to produce fewer assembly instructions.

    Starting off with the

    tempV[i] = tempV[i] >= midPtV ? tempV[i]-midPtV : midPtV-tempV[i];

    line, I tried changing it to

    if (tempV[i] >= midPtV) tempV[i] -= midPtV;
    else tempV[i] = midPtV-tempV[i];

    That saved 9 instructions total for the entire function. Since there is another line that's basically the same thing except with tempA[], that' 18 total saved (it actually saves 26 with both of them..go figure)... This is going to take a while.... UNTIL I stepped through the code to this part:

    dp[i].instVolts += (uint32_t)tempV[i] * tempV[i];

    It takes 370-390 cycles to do one of those lines, and with the loop there is a total of 9, which is insane. But that's not the interesting part. I found that if you step into this line it branches to a file called Umul32.c in the sources\common directory of XC8. This file has multiple methods for doing a 32x32 bit multiplication with a 32-bit result. One of these methods has an

    #if ... && defined(__OPTIMIZE_SPEED__)

    directive preceding it. That was not the method the compiler chose for me, of course. After a couple failed attempts at defining that value, commenting out the #if lines and the slower methods (it still executed the comments when stepping through!), I just copied the faster code into a new function and used that instead.

    I'm not sure on the legality of posting the code, but the 32x32 algorithm is probably a standard one (again, not sure), and I believe I've given you enough to go on to replicate it. The results were pretty fantastic. Cycle count for the GatherData() function went from 4362 (after the first optimization I tried) to 2190 with the new 32-bit multiply code. Awesome. Now I can run the PIC at 48MHz and have it complete the data gathering with cycles to spare. Mission accomplished!

    This can of course be improved upon further, but I don't want to take the time to do that. If I really wanted to easily change things I would go with a 32-bit micro (PIC32) that could natively handle these calculations without having to convert between types, and it has a 32-bit hardware multiplier instead of the 8-bit in PIC18. Knowing what I know now I would have gone with the PIC32 from the start. I found out its compiler is gcc based, and you can find instructions on the web how to enable the paid optimizations for free on it. XC8 is a different compiler entirely and is hobbled in the free version. In other words, if this project gets as far as a Rev 2.0 and power consumption is an issue, look for it using a PIC32 at a lower clock speed. It would be ironic after all for a power meter to be power inefficient, right?

  • Power Calcs Design Pt2

    Drew Yenzer08/07/2015 at 23:09 0 comments

    I'll just dive right in and show you what I've got so far.

    /* Main Loop */
    while(1) {
            if (_adcCounter >= NUM_SAMPLES){
                CalcPower(instData,powerData);  //tally up power data
                UpdateLcd(powerData,display_state);  //Display on LCD

    This is the main loop. Currently it does the power calculations and updates the LCD. Eventually it will also transmit data over the nRF24. Outside of this loop I've set up a timer that will trigger every (60Hz*64) 3840Hz. Inside of that timer interrupt I call the GatherData() method shown below:

    //Read each voltage/current pair
    void GatherData(struct datapoint *dp, uint16_t midPtV, uint16_t midPtA[3]) {
        uint16_t tempV[3],tempA[3];
        for (uint8_t i=0; i < 3; i++) {//read ADCs
            MySetChanADC(VIN_ADC_CHAN);    //read voltage
            ConvertADC();    //starts ADC capture
            tempV[i] = ReadADC();
            MySetChanADC(i+1);    //read current transformer
            tempA[i] = ReadADC();
        for (uint8_t i=0; i < 3; i++) {//Scale and store data
            //scale voltage and current readings
            tempV[i] = tempV[i] >= midPtV ? tempV[i]-midPtV : midPtV-tempV[i];  //calculate difference about midpoint value
            tempV[i] *= V_SCALE_FACTOR;
            tempA[i] = tempA[i] >= midPtA[i] ? tempA[i]-midPtA[i] : midPtA[i]-tempA[i];
            tempA[i] *= A_SCALE_FACTOR;
            //accumulate values for inst power and RMS volts/amps
            dp[i].instVolts += (uint32_t)tempV[i] * tempV[i];
            dp[i].instAmps += (uint32_t)tempA[i] * tempA[i];
            dp[i].instPower += (uint32_t)tempV[i] * tempA[i];

    All this function does is read in a voltage and current for each power channel, scales it, then accumulates for the RMS and power calculations. The ADC values are scaled about the alternating voltage/current midpoint, which would be the value read by the ADC when no voltage or current sensor is present. I'll determine those midpoint values by a yet unimplemented calibration routine that runs on startup. It'll average the ADC values over some power of 2 number of 60Hz cycles.

    In case you're wondering why the datapoint structure uses uint32 types, it's because it needs to store 3840 squared ADC values. That adds up to a larger than uint24 value. I tried looking for a way to save that data without squaring it and without a large array, but couldn't come up with anything else.

    void CalcPower(struct datapoint *dp, struct power_data *pwr){
        struct datapoint lastDp[3];
        for (uint8_t i=0; i < 3; i++) {//transfer data to temps first
            lastDp[i].instAmps = dp[i].instAmps;
            lastDp[i].instPower = dp[i].instPower;
            lastDp[i].instVolts = dp[i].instVolts;
            dp[i].instAmps = 0; //clear data to prepare for next wave
            dp[i].instPower = 0;
            dp[i].instVolts = 0;
        for (uint8_t i=0; i < 3; i++) {
            lastDp[i].instAmps *= A_SCALE_FACTOR_SQ;  //scale ADC values
            lastDp[i].instVolts *= V_SCALE_FACTOR_SQ;
            lastDp[i].instPower *= VA_SCALE_FACTOR;
            pwr[i].voltsRMS = sqrt(lastDp[i].instVolts / NUM_SAMPLES); //RMS calc
            pwr[i].ampsRMS = sqrt(lastDp[i].instAmps / NUM_SAMPLES);
            pwr[i].realPower = lastDp[i].instPower / NUM_SAMPLES;
            pwr[i].apparentPower = pwr[i].voltsRMS * pwr[i].ampsRMS;
            pwr[i].powerFactor = pwr[i].realPower / pwr[i].apparentPower;
    Lastly is the actual power calculation. This is called once a second from the main loop. The first loop in this function tries to quickly transfer the instantaneous voltage/current/power data into temporary variables, then clear those accumulations before the next timer interrupt. The next loop is the meat of it. First it scales the data from ADC values to actual units (power_data structure is all floats). The scaling factors are squared since the data is squared before it's accumulated. The rest is just the standard RMS and power calculations

    If you're looking at this and thinking it's going to take a lot of instructions to accomplish not only the math but the conversions between 16-bit, 32-bit, and float data types then you are right! It actually took 10 times more cycles than I allotted for in the GatherData()...

    Read more »

  • Power Calcs Design

    Drew Yenzer08/06/2015 at 23:23 0 comments

    Hi there! These numbers may not mean much, but this project has had over 2000 page views, 26 followers, and 5 skulls (no idea what those are)! That's exciting! It's encouraging to know there are people out there that are interested in what I'm doing. Thanks!

    Now then, today I'm going to talk about my software design for calculating power. I'm nearly done implementing a first draft of it. It still needs to be tested with hardware, or simulated, before I'd call it ready, but I'll use some code snippets to help explain things.

    If you'll remember, the hardware is composed of a line voltage ADC sensor, and 3 current ADC sensors. I'm calling that the 3 power channels. There are 5 main values that I'm interested in:

    • Volts_rms = sqrt( mean( instVolts^2) )
    • Amps_rms = sqrt( mean( instAmps^2) )
    • Real Power = instVolts * instAmps
    • Apparent Power = Volts_rms * Amps_rms
    • Power Factor = Real Power / Apparent Power

    As you can see, in order to calculate these values you need instantaneous voltage and current measurements. For each power channel I'll need to measure both voltage and current at a certain sampling rate. I'm going to measure voltage for each channel for accuracy. If I measured voltage once, then the 3 current values, the delay between the voltage reading and the last current reading could be significant enough to make it an inaccurate power measurement.

    Sampling speed and processing time could be an issue then. Depending on the load, current draw can change very rapidly within a 60Hz period. In order to capture that I'll try to sample the waveform as much as possible with enough overhead to process the data within the PIC. I'll also use a power of 2 as the sampling rate over a 60Hz period in order to make the Mean calculation faster, theoretically. I'll start with 64 samples per 60Hz period, or 3840Hz. This will allow me to adequately capture out to the 16th harmonic (64/4). By the way, for an excellent read on sampling for engineers, check out

    At that rate, if the PIC clock is running at 8MHz (2MHz instruction cycle), I'll have 520 instructions available between samples. This should be enough to capture and do some accumulation, but not enough to do the math needed for the power calculations above. However, each second I'll have 2 million instructions available, which should be more than enough for the square roots and divisions. If it's not, I have a max clock of 48MHz in the PIC to play with.

    To capture the data I'll use a timer interrupt at 3840Hz with the capture function within the interrupt. It's normally not good practice to do much other than setting a flag within an interrupt, but in this case I need to ensure the data is captured at specific intervals. The power calculations can continue on in the main loop of the program, initiated once every second. After the calculations are done it will update the LCD and transmit data with the nRF24, all while new data is being collected through interrupts.

    This post is already getting a little long. For next time I think I'll post some of the code and also try simulating it. Stay tuned!

  • Latest Circuit

    Drew Yenzer08/02/2015 at 20:41 0 comments

    It's been a little while since my last update. Haven't accomplished a whole lot since then, but I have updated the schematic to what will hopefully be a final "base" version. Anything added on will be in a later revision. So, this is what's changed:

    • Fixed mistakes from previous post
      • The big change here is swapping out full wave rectification with half wave. I chose a schottky diode because the lower voltage drop affords the use of a smaller smoothing capacitor, without any ill side effects from the small reverse leakage current.
    • Moved RF connections around to default SPI pins on PIC. There was really no reason to keep it as is and this saves me from having to remap pins in software.
    • Connected IRQ RF pin. Not sure if I'll use this but it's there now.
    • I was going to add a fuse, but it turns out the wall wart transformer has one built in. Bonus!
    • Added test points. This is something I always mean to do but forget about. Really useful for troubleshooting.

    I thought about throwing an LED in there for some diagnostics, but I have an LCD so I can just update that with pertinent error info. I'll go ahead and order this board in a couple days, and it should get here in a couple weeks.

    You'll also notice some ADC scaling information on the schematic. This is because I've already started on that part of the code and I'll probably post an explanation for my methodology in the next few days.

  • Schematic Mistakes

    Drew Yenzer07/17/2015 at 00:38 0 comments

    The good news is the PCBs I ordered should get here Saturday. The bad news is there are horrible flaws in the design that would burn up one or more components. I may have jumped the gun in ordering those PCBs... stings.... Here's a list of them. The first 2 are from SJ Greenfield.

    1. Since the 9Vac source is grounded by the voltage divider AND the diode bridge, it shorts itself across the diode between pins 2 and 4 on the bridge every negative cycle. That diode bridge definitely would have smoked.
      1. SOLUTION: Go with a single diode half wave rectifier. This will require a larger smoothing capacitor.
    2. Resistor R4 should have a junction between R12 and R11.... Duh.
    3. Currently when the current transformer is disconnected, the switch in the stereo jack pulls the ADC line to ground. After looking at it again I think it would be better to send the divided 1.65V to the ADC so I can calibrate that measurement.
    4. Button1 is tied to pin RA7 on the PIC. RA7 just so happens to not have any interrupt associated with it, nor is it a remappable pin. Nice.
    5. Turns out the CSN pin on the nRF24 is more than just a simple chip select. You know, the simple kind that just enables/disables the entire chip. It actually needs to be pulled high and low when sending commands. Go figure.

    Let me know if you guys notice anything else!

  • Schematic Posted

    Drew Yenzer07/12/2015 at 00:42 0 comments

    I made a github account to post all the project files at. So far it has the schematic and board layout.

    In other exciting news I received parts from Digikey today. Now all I'm waiting on is the PCB and I'll have everything hardware wise. Still haven't started any coding, but hey, I've got time, right? :)

    Here's pictures of the schematic and board layout for your viewing pleasure. All the related files are on github. Enjoy!

  • First Post

    Drew Yenzer07/09/2015 at 04:29 0 comments

    It took longer than I expected to get the initial project info in there, so this post will be brief. So far, I've got a schematic done up, PCB laid out, and BOM made. All the parts have even been ordered. I expect to receive the PCB in a couple weeks from OSH Park. Between then I'll have started writing the PIC code. Probably...

    In my head, it's basically divided into 3 main sections:

    1. ADC reads and power calculation

    2. LCD Display

    3. RF Transmission

    I'll tackle it in that order as well. The RF transmission is last because I haven't quite decided how I want to handle that. MQTT looks interesting and definitely scale-able to work with other projects, but it might be too much overhead for the PIC. Plus I'd need to find a C library implementation of it. The alternative is to make my own quick and dirty protocol. It would be faster for me to implement and for the short term is all this project really needs. If I structure it right then it should be easy to switch protocols later on if need be.

    Anyway that's all for now. I'll post the schematic, PCB, and BOM once I figure out a good way to do so.

View all 9 project logs

Enjoy this project?



Steven J Greenfield wrote 07/13/2015 at 11:53 point

The ground at the bottom of R12 also grounds pin 3 of the diode bridge D1. This puts the 9Vac input across D1 pins 2 and 4, so it is essentially shorting the transformer during 1/2 of the cycle.

Should there be a connection where the wire from R4 crosses over the wire between R11 and R12? With this connection and the above ground connected to the transformer, you'll get a sine wave at ADC0, but the output of the bridge is a half-wave, and the 9Vac transformer and the bridge rectifier are going to get very hot and eventually fail.

If you remove the ground from one side of the incoming 9Vac, I don't see how this will give you a sine wave centered on Vcc/2 as neither side of the transformer is ground referenced from the output side of a full wave bridge rectifier. For that, you really need to either use a half-wave rectifier, a single diode, so that one side of the transformer is connected to ground, or a center-tapped transformer and 2 diodes for a full-wave bridge.

  Are you sure? yes | no

Drew Yenzer wrote 07/14/2015 at 00:55 point

Hi Steven, thank you for your feedback. You're absolutely right that the source will short across the diode.  I can't believe I missed that.  I've actually made so many mistakes on this schematic that it's embarrassing!

I originally had it as half wave rectified, but put in the full bridge to allow a smaller capacitor value (it was about double otherwise, and rather large spatially).  I'll need to revisit this, and your 2 suggestions are good starting points.  

  Are you sure? yes | no

Steven J Greenfield wrote 07/14/2015 at 03:28 point

Glad I could help. Tough to find a center tapped transformer these days, so half wave and a larger capacitor may be the simplest way.

It is always good to have more than one set of eyes check your work.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates