The first three DMMs I built (left to right, #2, #3, and #1). All rather defunct by now…
Under Development: An Arduino Digital Multimeter Shield
“There are many like it - but this one is mine.”
This is something I’m working on. I haven’t decided when, if ever, or in what form factor it will be offered for sale.
Update: Late Jan, 2025:
Work continues. I have made several significant design and functional improvements in the last month. These are:
Switching from banana jacks to barrel jacks.
Current measurement.
Data Logging (and a bigger screen).
Simpler, better GUI.
My two current DMMs with various accessories, Late Jan, 25
Barrel vs Banana Jack. One is a lot bigger than the other.
Barrel Jacks:
I’ve replaced the DMM-standard banana jacks with barrel jacks. This is mostly for physical compactness reasons. Barrel jacks are much smaller, so I can fit everything in tighter together. (Especially given that you need two banana jacks.) It also means I don’t have to worry about the banana jacks being spaced the standard 3/4” apart (which also takes up a lot of space).
Instead I just have a barrel-to-banana jack adapter (I also made a couple test cables directly connected to a barrel jack).
The barrel jack is rated for 50vdc, which coincides nicely with my voltage range (that said, I accidentally, briefly, measured a ~300vdc rail recently and the meter wasn’t damaged).
A nice perk of this system is also that I can plug in / unplug my leads at the same time.
Standalone ACS712 module left, and then a PCB with an ACS712 plus my low range op-amp circuits.
Current Measuring:
I don’t need to measure current very often, but sometimes I do, so I’ve implemented the ability to do so via plug-in modules. I have a couple different modules for different ranges.
High (0.1-5A): For this I use an off-the-shelf ACS712 module. The IC has a Hall-sensor that you run current through, and it generates a voltage on the signal pin corresponding to the current. The IC comes in a couple different ranges (+/- 5, 20, and 30A), and I’m using the 5Amp version. This is a good write up about how it works: https://www.makerguides.com/acs712-current-sensor-and-arduino-a-complete-guide/
This module works well down to about 0.1A, where the reading starts to become unreliable, and it does a rather poor job of reporting currents below 50mA.
The IC requires three wires (Vcc, Signal, and Gnd), which means I can’t use a barrel jack (annoyingly), so instead I just ran three wires off the PCB.
Measuring 4 amps with the Hall effect sensor: Pretty close.
Not so great at the lower range (~84mA read as 130mA).
Op-amp circuit.
For the low range I made a little PCB with a 1-ohm resistor to put in series with my test subject, I then have an op-amp read and amplify the voltage drop, and calculate current from that. The functional range is ~1.5-600mA.
This arrangement also uses three wires from my DMM, so I can plug the same wires into it as the Hall sensor IC.
I’ve also programmed my meter to detect if / which current board is attached. The zero amp point for the Hall sensor IC is Vcc/2 (~2.5vdc in this case), when the op-amp board is attached the ADC input is pulled very close to zero, and when nothing is attached it generally floats at a few hundred millivolts. When the meter boots up it reads the ADC input for current and configures itself appropriately.
The lowest reading I can get is about 1.5mA.
Up to 500mA.
1mA (which is a drop of 1mV across the resistor) is too low for the op amp to pick up.
Data Logging / Bigger Screen
The biggest cosmetic change in the most recent version is the full-color 2.2” display (see all the above photos). This is nice, but has more design tradeoffs than might not be worth it. These tradeoffs are: battery life and slowing down the code. Battery life is pretty obvious. This screen uses more power.
Slowing down the code wasn’t something I realized would be a problem. Because it’s a lot of pixels and full color, this screen takes a lot longer to write data to than the small black and white one. Half-filling the screen with text can take hundreds of milliseconds, which dramatically slows things down. Because of this I had to break up the screen code to pre-populate static text and then only update the dynamic text as the program runs (so instead of printing “Min: ### / Max: ###”, the “Min” and “Max” are already there and I only update the values). This wasn’t hard, but was tedious. I’ve also put code in to bypass the screen update when any data gets logged, lest I miss the next data point because the screen was updating.
Even with the only updating the dynamic text, the code takes 80 to 120ms (depending on if I show min/max info) to complete a loop when updating the screen (as opposed to 13ms normally).
On a fundamental level this does severely limit the screen’s usefulness. I wanted to do fun things like display voltage readings as number of Pikachus, or resistance on a scale from Gyarados to Graveler, but the images would have to be very small not to nerf the usability.
By the way, if you are using an Adafruit 2.2” TFT with the Adafruit_ILI9341.h library and you are also using the Adafruit_ADS1X15.h and/or the SPI.h library, there is a very frustrating bug where the screen will work during setup but then not update again if the libraries are initiallized in the wrong order. You must initialize the ADS1115, and then initialize the display. This was very frustrating to learn, but hopefully this paragraph has the right keyword that other people will find it.
Data Logging Example: Logging an AC current (Blue) rectified with a single diode through an LED and Voltage (Red, either side of the LED) over one second, adding a capacitor across the LED leads for the second half. The reason the lines don’t better match is the time difference between measuring V and I. Optimizing this goes on my todo list…
But, Data Logging: The screen has a built-in micro SD card holder, and this is a big functional improvement. I can now log data automatically based on different conditions. I have it setup right now to log current, voltage, and time whenever one of those hits a new high, a new low, or every 100mA interval on the way down after a new high. (It also logs when I press the red button.)
The main reason I want to log data is to get current and voltage data on failing components, such as when there’s a current surge, was there a voltage spike beforehand? Or is voltage falling so a power supply is trying to pull more current get the voltage back up? This will hopefully help me track down the nature and specific cause of failures.
As I have it currently set up I can get a reading about 25ms. Getting that faster is an area for further development.
The GUI in current mode.
Better GUI
I’ve also redone the GUI. It’s simpler and works better. The buttons send commands or I can type the command I want.
Update: Late Dec, 2024
Current Version, as of late December, 24.
Work continues. I’ve made a V2 PCB with various improvements.
Builds #4 (right), and #5 (left), from the early Dec revision.
Revision: Early Dec, 2024:
I’ve updated this post because I revised and expanded the design.
These revisions included both designing and ordering a PCB that mostly uses SMD components, but leaves the circuitry as the breadboard version largely the same (except I replaced a relay with a MOSFET, and I dropped the analog voltage down to <5vdc so I didn’t need a regulator for the ADS1115), and another one where the analog circuitry runs at a low enough voltage (~2vdc) that I don’t need a voltage booster. They also both have batteries now.
I have updated and revised the below where most appropriate.
Final Result (until the next idea…)
Why did I spend so much time on this?
Several Reasons:
Directly typing values into Excel.
I can save a lot of time and reduce potential keystroke errors by using the Arduino’s keyboard emulation feature to type values directly into Excel (or a different program). With an added right arrow key press I can input many readings by only moving a probe and pressing a button. No more “apply probe - use my eyes to read the value - awkwardly type the value the in with one hand so I don’t have to set the probe down - move eyes back to whatever I’m measuring.”
Computer GUI to view and save readings.
I can use the serial interface on the Arduino plus Python on my computer to have a GUI where the readings are on my screen, plotted on a graph at a specified interval. I can also export the readings as a CSV.
I can have a very sensitive low resistance mode.
Sometimes I am testing the integrity of relays, where you need to know the different between 0.1, 0.2, and 0.3 ohms. Most non-expensive meters are not set up for this, but I can design and implement a circuit for it.
Silent continuity alarm.
I can have an LED illuminate at a specified resistance threshold instead of / in addition to an audible beep. The coworker I sit closest to has express his appreciate for this feature when I ohm-out boards.
Teaching myself instrumentation.
Every electronics device that makes any measurement has a volt and/or ammeter at its heart. Building my own is a good way to learn more about how those devices work.
Data Logging with Custom Triggers.
This was only a later development, but I’ve now built a meter with an SD card that logs data. It will automatically log voltage and current with either hits a peak (the meter can measure both simultaneously), or any other conditions I care to program.
The Electrical Design
Analog to Digital Conversion: ADC Fundamentals, Math, and the ADS1115
The core of function of a DMM is the analog to digital converter (ADC), which converts the analog signal into digital information. ADCs have limited input ranges (0-5VDC or similarly small ranges), so the analog measurement circuits need to either compress (in the case of voltage) or convert (in the case of resistance and current) their entire range of measurement values into this overall range. Because the overall range is fixed, the way you get more precise measurements is a function of your ADC’s number of degrees of gradation (generally specified per the number of bits). How many digits of precision you have are ultimately a function of how many bits your ADC has.
Most Arduinos have 10-bit ADCs with a 0-5VDC input range, meaning that the ADC can resolve differences of ~0.005VDC (10 bits = 1023 different levels, 5VDC / 1023 = 0.0049VDC). This would be fine for many applications, and maybe okay for a low end DMM (10 bits translates to a maximum precision of three digits) but I want more precision.
I ended up using an ADS1115 ADC. It can do up to 16-bits, although in most configurations (including mine) it is 15-bits (which is better than 4 digit precision). When I started this project I decided to use the the ADS1115 because the Arduino I was using had only a 10-bit ADC and I wanted more precision, but I since upgraded to an Arduino with a 14 bit ADC so that’s less of a factor. Instead, I keep using the ADS1115 because it has different input ranges, from +/- 0.256 to 6.144vdc. This is extremely useful because I can use the same analog circuits for far greater ranges than I could use the built in Arduino ADC for. (A 0.1Vdc signal is very small and close to zero in a 0-5vdc range, but nice and big in the 0.256vdc range.)
For anyone who wants to get into the weeds on measurement theory and I highly recommend the Low Level Handbook: https://www.tek.com/en/documents/product-article/keithley-low-level-measurements-handbook---7th-edition
Resistance Circuit in Constant Voltage Mode. R5 is the resistor being measured.
Resistance Circuit: Turning Ohms into Volts
Here I designed a circuit that can be switched (via X1 in the schematic, and via a MOSFET in the current version) between constant current mode (with the unknown resistor in parallel with about 330 ohm resistance to ground, and a constant voltage mode (with a 22k resistance added in series before the unknown resistor). The overall voltage is clamped at ~5.7vdc via a Zener diode.
The voltage / current source I1 is an LM317 regulator set for 20mA constant current
Constant Current (Low Resistance) Mode:
Constant Current Resistance Calculation as a Function of the Voltage Reading
The DMM is very sensitive in the constant current mode, but only up to about 900 ohms. The minimum detectable resistance is less than 0.01 ohm (which should measure 0.00022vdc, about a 1 step above the minimum ADC level of 0.00015). In reality 0.01 ohms is hard to see due to noise, but I can easily see 0.1 ohm, and that meets my goal.
The equation in my code for the constant current circuit is:
resultRx = (voltageCh0/(constantI - (voltageCh0/R6))) - minR,
where:
voltageCh0 is the ADC’s output integer (0-32768).
minR is a variable to subtract out the lead resistance.
Constant Voltage Resistance Calculation as a Function of the Voltage Reading
Constant Voltage (Normal) Mode:
That’s where the constant voltage comes in. The 22k resistor means the voltage is always at the Zener clamping level, so I just have a voltage divider network. It’s not particularly sensitive at the low end (1 ohm equals about 0.00026vdc, less than 2 steps above the minimum), but I can easily distinguish between 1 and, say, 5 ohms, which is good enough for continuity checks.
You can see on the voltage vs resistance graph that ~0-1k is fairly compressed, I spread the range of 1K to 100K out in a fairly linear fashion, and then above 100k becomes compressed again.
Here is the equation for the constant voltage circuit:
resultRx = (r7*(voltageCh0/(Vref - voltageCh0))) - minR
This puts the effective range up to about 1M ohms. Higher would be nice, but I run up against another problem, that the input impedance of the ADS1115 is between 5 and 10M ohms, and when I approach those values I have to start worrying about the effect to the ADC on the measurement. I could do that, but for now I’ve decided that an upper range of 1M is an acceptable limit.
Overall, this means I can measure from 0.01 to 1M ohms with only two different ranges.
By default my program now also auto-ranges, so I don’t have to think about what range I’m in. It doesn’t noticeably slow the reading down.
Voltage Circuit
The requirement for voltage measurement circuit is:
Maximum Practical Range
Measure both positive and negative voltages
Maximum Practical Range?
Short Answer: +/-50VDC
Longer Answer:
There is a direct tradeoff between sensitivity, noise on the low end measurements, and overall range.
Ideally the circuit would handle +/- 170 volts so I could theoretically read standard US wall power, but this limits the sensitivity more than I would like. This would be an overall range of 340 volts over the ADC’s 15-bit range compressed down to 0-5VDC, a factor of 68, meaning that each volt of measured signal would need to be divided into 0.0147VDC into the ADC. That is about 100 times more than the ADC step size of 0.00015, giving me a resolution of about 0.01VDC. That’s a bit less precise than I’d like and would mean paying close attention to any noise in the system.
I ultimately decided on a range of +/-50VDC, which means I’m only dividing the signal by a factor of 20 (100 volt range into 0-5vdc). With a 100volt range each measured volt turns into 0.05 volts into the ADC and I have a resolution of 3mV. This was still a bit noisy (I didn’t quantify it at the time), but it looks good enough I can smooth it in the programming so I decided to move on.
I didn’t spend more time attempting to build a filtering circuit because I want the theoretical ability to read AC signals of varying power and frequency, and adding in capacitors would interfere with that. If I were building a more elaborate DMM I would have separate VAC and VDC circuits and build filtering into the DC one.
Up-Bias Voltage Divider. V2 is the measured voltage.
Polarity
I need the ability to measure both positive and negative voltage primarily because I will often be probing a PCB and not know for sure where to expect positive or negative voltages. A meter that couldn’t handle negative voltages would be a major design problem. Unfortunately the ADS1115 only measures positive DC (technically it can go down to -0.3VDC. (Arduino boards also have the same limitation.)
The 50VAC out of V2 about becomes 0-5VAC at ADC2
I used another LM317 set to a constant 2.5VDC as the circuit base voltage, with the output connected directly to one of the ADC’s inputs and then connected to another of the ADC’s inputs across a 7.5K resistor. (Two ADC inputs allow me to measure the voltage differential, although I am unsure if they is better than just using a coefficient in the code.) The two test points for measuring voltage are then connected across 71K resistors to either side of the 7.5K resistor. This means that when I’m not making a measurement the ADC reads 2.5VDC at each of its two inputs, and when I measure a voltage the it either adds a little bit or drains a little bit from across the 7.5K resistor. -50VDC in pushes the 2.5 volts all the way to zero, while +50VDC brings it to to 5VDc.
I am somewhat surprised, but this seems to work fairly well.
Note: I have accidentally measured both 120VAC and 300VDC with this circuit and while I didn’t get accurate readings (I saw over 100V on the screen, and immediately removed the probes), the meter wasn’t harmed.
4 seconds or so of reading a 1VDC source with my laptop plugged in (left), and running off battery (right). This is with the averaging enabled.
Battling Ground Loop Noise
I ran into a problem with my voltage measurements where they weren’t stable, and instead I had clear and persistent oscillations. I attempted to solve this with coding the reading so I got a rolling average, but any average that was accurate was slow to the point of uselessness (several seconds).
Eventually I discovered the source of the noise wasn’t inherent in my circuit or the Arduino, but rather the power source. I was powering that version off a dedicated 9vdc supply (from the wall), and it was also plugged into my laptop (almost always plugged in). The sources I measure tend to also be plugged in.
Version 2, where the 9v rail is provided by a USB-C adapter (the black module), and the Arduino must also be plugged into a computer directly for comms. Very bad idea.
All these different connections meant I was getting something apparently called ground loop noise. I built a new version that was powered solely off a single USB connection (with a boost board for the analog circuitry). It is still frustratingly noisy when plugged in, but at least now if I unplug my laptop I get a mush cleaner reading. My code also has a toggle between a immediate mode where it tells me the actual voltage reading and an averaged mode that gives me a 20-reading average (all taken ~10mS apart).
The two basic ways to deal with this problem are either to air gap the Arduino and communicate with it wirelessly, or buy a somewhat expensive USB-isolator. I’ve done some Bluetooth testing and it’s an area for further development.
Incidental Circuitry
Power Circuity:
The voltage regulators I’m using (LM317s) require a supply 3vdc above the output you want. I need the regulator for the resistance circuit to go up to ~5.8vdc, which means I need a ~8.8vdc rail. The Arduino unfortunately can’t supply this. I also need a steady 6vdc for the ADS1115.
Because of this I added a small DC boost power supply set for a 9VDC output, powered off the Arduino’s 5VDC line. I then have an extra LM317 to supply 6VDC to the ADS 1115.
*Update: The two most recent boards use lower (< 5vdc) analog circuitry, so I don’t need an extra regulator for the ADS1115. I also used LM1086s with their lower dropouts (~1.5vdc) for a version that doesn’t require a voltage booster at all.
The DMM draws 1-1.6w depending on the mode (constant current is higher), or 200-300mA at 5V. The Arduino’s R4’s limit for the 5V pass-though from the USB is something around 500mA (citation forgotten), so I’m well within the limit there.
If I want to power this off a battery, some quick math suggests I should get about 30 hours off a 10,000mAh power bank.
Note, in the V2 I planned both to power the analog circuitry with a rail separate from the Arduino and my circuitry drove a constant current of 50mA instead of V3’s 20mA. This means that V2 was a bit more sensitive for low-ohm readings, but also I wanted a fan to keep to keep the resistor at a constant temp and make sure nothing over heated (hence why it’s there). For V3 I gave up some resistance sensitivity for lower power requirements and less heat dissipation.
An earlier version, with all the control wires attached.
Logic / Control Circuitry
The same version inside a housing, with the buttons installed and my son testing it out.
The current version uses an Arduino R4 and is controlled via either the serial port or an IR remote control.
A previous version used an Arduino “MicroPro,” which is smaller but ultimately didn’t have enough room for the size of program. It also had physical buttons and switches for the controls, but these took a while to wire and I did not want to devote that time and energy to repeating the work on the new version. The physical wires to the switches also take a lot of space and eliminating made it more compact (note the awkward gold spacer in the design).
I switched to the R4 for both greater memory, and because the R4 has Bluetooth and Wifi built in so I can eventually implement wireless communication.
The Code
There’s nothing particularly special or interesting here.
I have an older version of my code in the Dropbox, here:
https://www.dropbox.com/scl/fi/f9xrlq8bdm7t43jb6igu0/ClearMeter_RED.ino?rlkey=dvo7ihcv99v4sih2brpa3rcwx&dl=0
The most recent is frequently changing and not really ready for other people to see.
It was, more-or-less, a collaborative effort between AI and me. During this project I’ve gone from knowing very little Arduino code and never having written more than a line to having learned how to write many different functions and troubleshoot different problems.
Note that this version of the code doesn’t have an audio output for continuity. The code that worked on the MicroPro version doesn’t work on the R4, and I haven’t troubleshot this yet.
Results




(Above, views of the final version)
My calibration record.
Surprisingly good.
I have a set of known reference resistors from 1 to 1M ohms. After calibrating the coefficients in my formula to these values, the meter now read to within 1.1% for most of the functional range (1o to 100,000, depending on setting). I lose accuracy at the extreme ends, but I consider this acceptable.
If I wanted more accuracy I would write a step-wise coefficient into the code, where the measurement was automatically adjusted based on the nearest known reference I measured.
Voltage is accurate to some millivolts, at when I am avoiding the ground loops.
It is also very useful for ohming out boards. Sometimes at my job I’m troubleshooting PCBs and it’s useful to map out the resistance / impedance from sets of pins to one of the voltage rails. No particular illustration of this, but being to press a single button on the remote for the meter to type the value into a spreadsheet and then press the right arrow key is pretty great.
Computer GUI
Screenshot of the GUI after dialling a 10K pot back and forth and then disconnecting it. The left reading it resistance (log scale), the middle reading is the voltage reported by the ADC (using for troubleshooting), and the right reading is voltage (which was open).
Not a lot to say here either. I had an AI write me quick Python GUI that made a couple modifications / expansions to. The Python program sends letters to the DMM over the serial line to change settings or request data. The DMM responds appropriately. It works. The data is plotted and can either be continuously read in or only updated on demand.
Component Parts List (for the v3, before I made a PCB):
Micro-controller
Arduino R4 or compatible (if you want an IR remote, the MicroPro doesn’t have enough memory for this and the other libraries required ). ($20-30) I used this one.
Arduino “MicroPro” or compatible (if not using the IR remote).
ADS1115 ADC (~$6/each). Probably on a breakout board with the standard caps and resistors in place. I used these.
DC Boost Module ($2/each) (I used this one)
Small Screen ($3/each) (I used this one)
LM317 voltage regulators.
Banana Jack Sockets
Small speaker, if you want audio
Mics Electronics including an IR Remote Controller / Receiver, 5VDC relay module (these I got 2nd hand in a kit, so no particular link).
Resistors (you probably want metal film for better temperature stability than carbon film), caps for stability on the LM317 input and resistor reading ADC line, Zener diode for the constant current voltage clamp.
Solderable breadboard.
Link non-disclosure: the above are plain links. I am not tracking you (as far as I’m aware, maybe Squarespace is?), and will not know nor make any money if you purchase anything using the above links.
Future Plans / Expansion
Wifi / Bluetooth communication to air-gap the meter from ground loops.
Capturing bursts of data for voltage and either analyzing it on the board or sending it over the serial port.
I might build one using more of the Arduino R4’s internal circuitry instead of external components. The ADC has less resolution (14 bit), but this would make the overall device much smaller. The reason I haven’t is the math for a sensitive low range ohmmeter doesn’t work great in a constant voltage mode, and constant current mode doesn’t work much better, which effectively means I still need regulators and the ability to switch the circuit. The regulators need ~3vdc of overhead to operate, so if I’m eliminating the DC boost board I can only make a 2vdc constant voltage. I can make my measurement range from 0-2vdc instead of 0-5.7 and then use the R4’s internal op-amp to boost the voltage back up for the ADC (or drop the reference on the ADC). All this to say it would be possible and I might get good results (at least good enough that the tradeoff for the small size is worth it), but I haven’t had a chance to test it out yet.
(Some brief testing of this idea worked, more or less, but was pretty noisy with the lower voltage reference).
Bigger screen would be nice (although it would use more power if I run off batteries).
Physically rearrange the components to make it smaller.