Building an Arduino Digital Multimeter
“There are many like it - but this one is mine.”
This page is a combination of a How-To Guide, sharing my creation with the world, and my own documentation for future reference.
Why Build A DMM?
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.
Electrical Design
Analog to Digital Conversion: ADC Fundamentals and 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 for the Arduino built in one, others are ~0-6VDC or similarly small ranges). The analog measurement circuits will compress 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 degrees of gradation.
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 but not a DMM.
I ended up using an ADS1115 ADC. It can do up to 16-bits, although in most configurations (including mine) it is 15-bits. The ADS1115 has different input ranges. At the largest range (0-6.144vdc), the smallest resolvable difference with 15-bits is ~0.0001875VDC (15 bits = 32768 levels, 6.144VDC / 32768 = 0.0001875). That is 32 times more sensitive than the Arduino and as precise as I can reasonably get. (Getting finer measurements that are reliably accurate would require far more care with regard to noise than this project calls for.)
Resistance Circuit
Here I designed a circuit that can be switched (via X1 in the schematic, and via a relay 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:
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’s what I need.
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 (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.
Voltage Circuit
The requirement for voltage measurement circuit is:
Maximum Practical Range
Measure both positive and negative voltages
Maximum Possible Range?
Short Answer: +/-50VDC
Longer Answer:
There is a direct tradeoff between noise on the low end measurements and overall resolution.
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.
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.)
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.
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.
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.
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.
Logic / Control Circuitry
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 my code in the Dropbox, here:
https://www.dropbox.com/scl/fi/f9xrlq8bdm7t43jb6igu0/ClearMeter_RED.ino?rlkey=dvo7ihcv99v4sih2brpa3rcwx&dl=0
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)
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
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:
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. This would be useful for AC measurements.
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.