Portable Arduino

Power Logger - Part 2

Liam Davies

Issue 48, July 2021

This article includes additional downloadable resources.
Please log in to access.

Log in

We finish off the Arduino-powered Power Logger project by building a PCB, 3D printing a case and adding some handy features into the code.


In Part 1 of the project, we built a power logger capable of tracking voltage, current, power, and energy. This month, we’re finishing off the project by installing all of the electronics onto a custom PCB, and expanding the software to add some handy features ready for an electronics workshop. It’s worth noting that this month’s project will be functionally identical to Part 1, besides the addition of the new Arduino program. That means that if you’ve already built, or planning to build, Part 1’s project, all of the code is interchangeable and backwards compatible. The electronic schematics are virtually identical besides some minor changes and additions.

For the first part of the project, we built the main build electronics on a prototyping board. While it’s an affordable and quick way of getting a single circuit soldered together, it’s a big pain to debug when things go haywire. It is also difficult to make it look presentable, especially if your soldering skills aren’t up to scratch. To fix that, we’ve designed a Printed Circuit Board that provides a neat platform for our components and prevents the hassle of checking all of the necessary solder connections are made. We’ve provided the schematic and Gerber files for all of our PCBs, so if you’re not interested or experienced in designing your own, just head to your preferred PCB manufacturer and upload them.

PCB Design

All PCBs start with a schematic which not only communicates the layout and functionality of a circuit, but also informs the design software which components are connected. There are many software packages available that can be used for generating the PCB files, but we used EasyEDA as it’s convenient, free, and features a fully online interface. Other software such as EAGLE and Altium are good options depending on your expertise and what you can afford.

Note: We published a beginners guide to EAGLE PCB design in Issue 34 if you want further reading on the subject.

EasyEDA has a ‘Convert to PCB’ option that essentially generates a blank PCB and dumps all your component footprints onto a new document. It’s then your job to move the components to logical locations and then connect the ratlines to create your circuit traces.

It’s very important to check your component footprints. i.e. the physical shape of your components, their leg spacing, etc. One of the most annoying things about ordering a PCB is discovering a component doesn’t quite fit. Best case scenario, a bit of bending or drilling will allow it to fit into the slots. Worst case scenario, you’ll need to completely re-order your PCB, so take your time.

The purple lines connecting each component are its ‘ratlines’, meaning circuit traces that need to be routed to complete the circuit described by the schematic. We recommend turning off the Ground ratline when making PCBs since it’ll be sorted out later when filling the ground plane. It only adds confusion in the meantime. EasyEDA also includes ‘Design Rule Checking’ that ensures that necessary tolerances are added to trace thicknesses, via diameters, etc. This is a great way of avoiding manufacturing issues with your PCB and ensures that you meet a manufacturer’s minimum tolerance levels. If you have high power traces on your PCB, it can be useful to specify large trace spacing and width using this method. We used this for our current path from the input and output terminals, to ensure the load current is minimally impacted.

Adding a ground plane to a PCB is completely optional but is an easy way of adding the negative (Ground) connection to components without manually routing traces. Having a large, conductive ground plane is also beneficial to the circuit in several ways, including decreasing voltage drop between the ground paths of separate components. This means that the ground reference of each component will be much closer together, preventing major ground loops and other frustrating issues from occurring. Components that are sinking or sourcing large amounts of current to drive LEDs or high-power load can adequately return the current through the ground plane. Finally, remember that the ground plane is copper, and thus, is highly conductive both electrically and thermally. Components that generate heat can dissipate their heat into the ground plane to help keep their temperature in check. Obviously, components that generate a lot of heat should have dedicated heatsinks and high-frequency digital electronics may be impacted by components dumping their signal noise into the ground plane.

Our Power Logger is a dual-layer PCB, which will have ground planes on the top and bottom. To prevent ground loops from occurring, we’re using a grid fill of vias to connect the ground planes together throughout the board.


By default, components include their footprint and a part number when placed into the PCB layout, but you can also add your own logos, text, and information to supplement them. This is an awesome way to spruce up your PCB, add important installation info, or add version numbers.

Manufacturing PCB

There are a variety of ways to transform the newly designed circuit into a workable board for placing your components onto. You can go the old-fashioned way of etching your own board, using Ferric Chloride or similar etching fluids and a transparent sheet with a black outline of the copper traces. We do find that this process tends to be somewhat messy and inconsistent in a DIY context though, so it might be best left to the professionals especially for larger batches of boards. If you’re still set on making the PCB at home, some CNC mills can be used to drill out PCBs, with some specialised machines like the Bantom Tools CNC Miller creating extremely high quality versions.

Note: Refer to our How to Etch a PCB tutorial in Issue 36 if you plan to make your own.

But otherwise, a commercial manufacturer will trump all these methods at the cost of, well, cost and lead time. Saying that, many manufacturers are offering very affordable manufacturing costs for small batches, and combined with fast shipping methods means, in many cases, you can go from a design to a physical board in around a week! Not only are these boards very high quality, but silkscreening and soldermasking make your PCB much more user-friendly and safe to use.

The Build: Final Assembly and Wiring

To make finding and assembling the components needed for this project easier, all of the parts (minus the USB port) are through-hole solderable. This means there’s no messing about with tiny SMD components and getting them lined up!

Parts Required:IDJaycarAltronicsCore Electronics
1 x Dot Matrix White on Blue LCD 20x4 CharacterCN1QP5522-ADA198
1 x 40 Pins Male Header StripsCN1, H1HM3212P5430FIT0084
1 x LM358 Op-ampU1ZL3358Z2540CE05262
1 x RGB Illuminated Rotary EncoderSW2--COM-15141
1 x 15Ω 10W Ceramic Resistor or similar values* %-RR3354R0425KIT-13053
1 x Relay SPDT Sealed - 10A (G5LE)K1SY4066S4160CCOM-15207
1 x 1N4001 Diodes or Equivalent*D1ZR1004Z0109COM-14884
1 x 5.1V Zener DiodeD2ZR1403Z0614COM-10301
2 x 5.08mm-spaced Screw TerminalsCN2, CN3HM3130P2040A-
1 x N-channel MOSFET (30N06 or similar)Q2ZT2466Z1537COM-10213
1 x 0.1Ω 50W Power Resistor ^R8RR3206R0401002-284-HS10-0-1
2 x 220k Resistors*R2, R13RR0628R7614FIT0119
6 x 10k Resistors*R1, R7, R12, R14, R15, R16RR0596R7582COM-10969
1 x 680k Resistor*R4RR0640R7626FIT0119
2 x 10k Horizontal TrimpotsVR1, VR2RT4360R2480BCOM-09806
4 x 0.1µF Ceramic Capacitor or Equivalent*C1, C3, C4, C8RC5496R2930AFIT0119
1 x 10µF Electrolytic CapacitorC2, C7RE6066R4767CE05274
1 x 5mm Red LED*LED1ZD1690Z0800ADA297
1 x ATmega328PU3ZZ8727Z5126002-556-ATMEGA328P-PU
2 x 22pF Capacitors* (if not included with ATmega328P)C5, C6RC5316R2814CE05206
1 x 16MHz Quartz Oscillator (if not included with ATmega328P)X1Included with ZZ8727V1289ACOM-00536
1 x USB-A Female SocketJP1-P1300APRT-09011
1 x 1k Resistors*R3RR0572R7558-
1 x 7805 RegulatorU4ZV1505Z0505CE05291
1 x 9V Battery Holder9VPH9235S5048-
2 x Red High Quality Binding Post-PT0460P2000PRT-09739
2 x Black High Quality Binding Post-PT0461P2001PRT-09740
4 x Eye Connectors (8mm Outer Diameter)*-PT4514H1825A-
1 x Sub-miniature DPDT Switch-SS0812S2010-
4 x 10mm M3 Screws*-HP0403H3120APOLOLU-2695
8 x 15mm M3 Screws*-HP0406--
4 x 10mm M3 Shaft Couplers*-HP0900H1216-
4 x 15mm M3 Shaft Couplers*-HP0904H1234-
12 x M3 Hex Nuts/Washers*-HP0425H3175POLOLU-1069

Parts Required:

* Quantity required, may only be sold in packs. A breadboard and prototyping hardware are also required.

% A variety of 10W ceramic resistors for testing. In our test, we used a 15Ω 10W.

^ We couldn't find the exact shunt resistor we used in our build from our suppliers, so we've provided some similiar ones with lower power ratings. Be sure to check the power rating of the unit you use.

When soldering together the power logger, we recommend starting with the smallest components so as the board is flipped over, the parts won’t fall out. The resistors and diodes were first added, making sure to use high-quality metal film resistors with low tolerances.

Then, slightly larger components are added, making sure that sockets are seated correctly and the screw terminals make good contact with the PCB holes. The USB socket is SMD mounted, so first insert the housing clips through the board and add small amounts of solder to bond the power and data terminals.

Next up is the components that tend to get bent if soldered first, such as the LEDs and capacitors. There is one red LED for power indication, and a couple of small 22pF and 100nF ceramic capacitors for decoupling. Don’t forget the potentiometers for contrast adjustment of the LCD and for the current sensing!

Now it’s time to add the big components. We started with the headers. When soldering in headers, a good method to avoid placing them on a weird angle is to solder them with the target component already inserted. Our 20x4 LCD display needs 16 pins, which we held in place with some 10mm standoffs and nuts while soldering.

The tallest components on the board such as the relay, battery holder, and shunt resistor are fairly simple to install, but be aware, you will need to add a lot of heat to their solder joints to overcome the thermal capacity of the metal inside them. This is especially true if it’s a ground point. The shunt resistor can be connected by using some thicker wire and filling it with solder to create a low resistance current path.


After everything is soldered in, double check your connections and finally turn it on using the switch on the PCB. Since we haven’t programmed our microcontroller yet, the backlight, the power LED and the encoder lights should turn on.

3D Printing

Similar to Part 1, we’re going to make a 3D-printed enclosure that will protect against shorts from occurring on your board and tidy up everything.

We’ve managed to shave off an entire 10mm of chassis depth in this version thanks to the super compact PCB. Due to space constraints, the banana plugs have been moved to the side of the enclosure, although this could be considered an improvement. If the first version of the Power Logger was standing upright, the additional weight on the connectors often caused it to topple over.

Assembly & Wiring

Assembly is fairly simple, however, you’ll need to create some small eye or spade connectors and splice them onto short lengths of wire – four in total (two for each set of terminals). Additionally, since the ON/OFF switch is mounted directly onto the board, we piggybacked another two wires onto the switch terminals so we can run them to the chassis-mounted switch (the two orange wires at the top of the picture shown here).

After that, it’s just a matter of slotting the entire board into the enclosure. This must be done before any banana plugs or switches are added as they’ll block the board from entering. The banana plugs can be inserted, taking care to slide the eye connectors over their ends and tighten them down well. You’ll need a set of M3 couplers to screw in the circuit board from both the top and bottom, which should securely hold in the main board.

The switch can then be soldered onto the two orange wires, with heatshrink to prevent shorts. Any two connections that can open and close the switch will work just fine.

Position the top lid over the bottom half of the enclosure and tighten down the screws. All done! Now, if you’re like us, this is about the stage where you forget to actually program the chip and have to take off the lid again to expose the programming port. Whoops. Speaking of which, a standard USB-to-Serial converter chip will work great for programming our Power Logger. Make 100% sure that the programmer is plugged in the right way in the six-pin header.

The Code

In the first part of the project, our power logger program was quite basic and was primarily to demonstrate that the base electronics works. While simple, it gets the correct readings from the current amplifier setup and prints them to the LCD screen.

Right now, our project is more of a power display than a power logger. We want to implement some more features to make use of the powerful math functionality of the Arduino microcontroller. There is substantially more code than the previous part of the project, so we’re not showing or explaining all of it here. The menu system and LCD handling code is quite extensive and, frankly, pretty boring! You can check out the rest in the project files.

Besides the main code file, we have two separate Class files called LoggerData and Calibrator. These will be explained in more detail shortly, but essentially these additional code classes allow much better code organisation and must be placed in the same directory as the .ino file we’re using. While the extensions of the files are mainly for convention (e.g. .cpp and .h), adding #include headers to the correct files is extremely important. Here is how our file system works:

Relay Controller

To start off with, we want to implement a system for switching the relay on or off when a condition is met. We’re going to implement our own very basic programming ‘language’ that can be set up on the LCD display. Our program will use a very simple version of Boolean logic, which is a statement following this format:

When [ DATA ] is [ CONDITION ][ VALUE ], then [ ACTION ]

The first ‘data’ variable is the data we’re sourcing from the electronics – Current, Voltage, Power, Energy, etc. This can be selected from a list and will simply use the Arduino’s ADC to provide data readings.

The condition is, in programming terms, a relational operator that effectively compares two values and can result in true or false. Our power logger will have the following six operators (including the “or equal to” versions).

Note: The N/A trigger prevents any “smart” relay functionality and only allows manual triggering.

The ‘value’ is a number that we will compare to. In this project, this will be set as a user-changeable constant and cannot be programmed to source data – for sake of simplicity.

Finally, the ‘action’ is what action we will tell the relay to do when the condition is met.

There is an important distinction between permanent and temporary triggers. Temporary triggers will constantly be checking whether the relay should be open or closed according to the condition and change it accordingly. Permanent triggers only fire once, and then the relay becomes permanently set to that state until the user resets it manually. Depending on the circuit you are using, a permanent or temporary trigger may be more appropriate. For example, an overcurrent detection relay may be permanent since we do not want to start up the load again as soon as we disconnect it. On the other hand, a temporary trigger could be used to cut off anything above 5V provided to the load to avoid killing sensitive microcontrollers but will enable the circuit again when the unsafe voltage subsides. We can imagine a ton of uses for this simple relay programming system.

bool evaluateRelayState() {
  switch(relay_condition) {
    case 0:
      return false;
    case 1:
      return getRelaySource() < relay_value;
    case 2:
      return getRelaySource() > relay_value;
    case 3:
      return getRelaySource() <= relay_value;
    case 4:
      return getRelaySource() >= relay_value;
    case 5:
      return getRelaySource() != relay_value;
    case 6:
      return getRelaySource() == relay_value;

This is some simple code that evaluates whether the relay should be on or off at any given time. It compares source data (getRelaySource()) with the relay_value - the constant the user entered. Depending on the condition type we entered, the relational operator will be used one of 7 ways, with one disabling smart relay system entirely.

In the main loop function, we're calling evaluateRelayState() every 200ms (by default) and enabling or disabling the relay according to it's return value. We also handle permanent and temporary triggers in the main loop.

Reading System

To make managing our Power Logger data easier, we’re using a class to hold all of the currently accessed information in a separate code file. That way, we prevent clogging up the main file of the PowerLogger file and can use object-orientated programming to group key groups of related information together. To create a class in Arduino, we need to create two files from the Arduino IDE, LoggerData.h and LoggerData.cpp, a header file and a source file respectively. The header file defines variables and methods that can be accessed by other code files, while the source file implements the functionality. In this case, our LoggerData contains five public variables:

CURRENT: The last recorded current readings in amps

VOLTAGE: The last recorded voltage reading in volts

ENERGY: The last recorded energy reading in joules

STARTTIME: The time in milliseconds that the recording was started

LASTUPDATE: The time in milliseconds since the last reading

You may notice we are not including power as a variable. Since power is solely dependent on the product of voltage and current, we’ve added getPower() function to the class that can be called and will return the power in Watts.

class LoggerData {
    float energy;
    float voltage;
    float current;
    unsigned long startTime;
    unsigned long lastUpdate; 
    void updateData(float voltage, float current);
    float getSeconds();
    float getHours();
    float getPower();
    float getWh();
    float getDollars();
    void resetData();
    void updateEnergy(float timeDelta);


After the first part of the project, we noticed that the first version of the logger was drifting somewhat significantly in its readings and needed calibrating every week or so. It could’ve been due to the slow drop in battery voltage, the thermal drift of the Op-amp, and probably a thousand other factors. To avoid the hassle of plugging the logger into a computer and reprogramming the chip, we’re adding an inbuilt calibration function that can use known resistors in your workshop to accurately calibrate itself.

Our calibration will be accomplished with a simple two-point calibration process. By generating a gradient and offset for both the voltage and current measurement readings, we can then use a function to convert these into final values.

Step 1: Zero Readings

While our voltage measurement should consistently read zero with no voltage applied, our current measurement has been designed with an adjustable offset so it’s super important we zero the readings. Our plan is to request the user to disconnect the source and load, and thus we know that whatever value the Arduino reads will represent zero for both voltage and current.

Step 2: Known Voltage

We are not going to be able to calibrate our Power Logger if we don’t compare it with anything. We need a stable reference voltage to use and calibrate with. For example, if we plug in a 5V voltage and the Arduino reads a value of 1000, we can treat this calibrator point as (1000, 5).

It’s a similar story to calibrate our current readings. Since we know our source voltage is 5V, and we have a known resistor with a value of 100Ω, for example, we know that the current through the resistor must be 50mA according to Ohms law. If the Arduino reads an ADC value of 250 on its ADC, our second current calibrator point will be (250, 0.05). Pretty simple!

Step 4: Creating the Function

We now have a small collection of data points that can be used for building a conversion function. We’re looking for an equation with the format of y = mx + b, the slope-intercept form of a line. When we plug in the value x, it will be converted to the actual value y for displaying on the LCD display. During calibration, we need to solve for the unknown constants m (the gradient of the line) and b (the y-offset of the line). We can do that with these equations:

We also want our calibration data saved to memory, so when the power logger is turned off and on again, we can retain our reading accuracy. We’ll use the inbuilt EEPROM storage of the ATmega328P, which has a maximum of 1024 bytes that can be stored after power-off. Since floats in Arduino are stored as 4 bytes each, we need to use 16 bytes of EEPROM memory in total, calibration parameters for voltage and current, both of which have a gradient and offset float variable. It’s worth noting that there is no point in storing the calibration parameters as “double” variables for double precision, since the double and float datatypes on AVR-based microcontrollers are the same size.


Since Part 2 of our power logger is electronically the same as Part 1, we know that it should be functionally the same deal getting our circuitry working. Minus a few hiccups like accidentally swapping around a two LCD control pins, the code should be virtually plug-and-play.

After getting everything connected up and doing a test calibration, we found that the power logger is more than capable of making accurate measurements of the connected load's power usage. The limitations of the ATmega328P's ADC is a slight roadblock to getting some super useful readings, but overall we were pretty impressed with how well the whole system works.

If you do have issues with building this project, definitely check out your Op-amp current sensing circuit. It tends to be the first one to go wrong, so make sure your gain resistor values are as close together as possible. Remember, our calibration process is designed to account for imperfect Op-amp behavior, but not unexpected or inconsistent. As long as the voltage on the LM358's output is proportional to current as read by the ATmega328P, this circuit should work well. Otherwise, head into the program code and fiddle around with the #define parameters at the top of the file - these are the constants that can be customised to better suit or otherwise troubleshoot your power logger.

Where To From Here?

Since we first published Part 1 of the Power Logger project, we had a number of readers comment on future improvements and changes to the Power Logger system. Among them was adding an SD card for file-based power logging - a great way to collect long-term data without continually checking the display. Speaking of which, the LCD display tends to drain the included 9V battery, so a cutoff for the LCD's backlight and nearby LEDs could be added to make this project truly portable and sustainable over longer durations.

Alternatively, we could go the way many DIY projects tend to go now, using IoT (Internet of Things). We could relay power data to a web server and access the interface via a mobile device instead, saving a lot of power and space by cutting out the display hardware. (We do like the tactility of a clicky rotary encoder though)! There are, of course, some potential functional improvements, like the way we're actually reading our data. By moving from a 10-bit ADC (as the ATmega328P uses), to a 16-bit for example, we turn 5mV of precision into a whopping 0.07mV of precision! There are dedicated ICs that can accomplish this, with even higher precision if desired. However, we also recommend upgrading the current sensing amplifier if accuracy is what you're looking for. There are better (and more complex) designs for getting highly accurate current measurements, including digital ICs and dedicated Arduino modules.