New tricks for the Pico voltmeter
Tam Hanna expands his basic Raspberry Pi Pico voltmeter to improve accuracy and add a resistor measurement capability.
Tam Hanna expands his basic Raspberry Pi Pico voltmeter to improve accuracy and add a resistor measurement capability.
The voltmeter built at the end of the last issue introduced basic metrology. It suffered from low accuracy because the input current of the Raspberry Pi Pico caused high voltage losses across the protection resistor. In this follow-up instalment, we’ll use an operational amplifier to solve this problem. We’ll also add a mode for measuring resistances.
Just as in last month’s issue, the goal of this tutorial isn’t to create the perfect tabletop multimeter. Instead, we want to demonstrate additional metrological concepts and inspire you to perform more experiments on your own.
The current must flow
A resistor in series with the ADC input turned out to be a great way to make the multimeter more resilient. Connecting 5V to the ADC input would normally destroy the Raspberry Pi Pico, but our system survived even with the reduced value of the protection resistor. This, however, isn’t ideal. Practical multimeters regularly face hundreds or even thousands of volts in spurious inputs. Undoing this resistor value reduction would be beneficial for the longevity of the product, but we need to solve the problem of the ADC input current.
To recap, our problem is caused by the current flowing into the ADC. It’s a piece of circuitry that performs a relatively complex computation. Logic dictates that this requires current.
Solving this problem sounds easy in theory. If we could integrate another component that handles the output current of the ADC then all of our accuracy problems would vanish in the blink of a flashing LED.
Fortunately, such a component exists in the form of an operational amplifier. An Opamp is a highly versatile component that can be configured to act as a buffer. This configuration is commonly called a voltage buffer or voltage follower (see diagram, bottom-left).
We need to make the necessary adjustments and perform a quick test. First, connect the output to the 3.3 voltage rail. This should trigger the low mode that worked perfectly before – unfortunately, we see a value of about 2V. This problem is caused by the presence of the operational amplifier. The next attempt involves connecting 5V – switching action doesn’t take place.
This behaviour is caused by limitations of the Opamp circuitry, because reel-to-reel operation (the output being able to reach both ground and the positive supply voltage) isn’t possible on the LM324. This commonly available and affordable operational amplifier states that about 1.5V of headroom must remain between the positive supply and the maximum voltage possible on the output pin.
While we could address this problem by selecting a different operational amplifier, we’ll instead accept the limited range. This requires us to adjust the value for the lower switching limit, which now looks like this: if switchval > 37000 and low_on == 1: low_on = 0; mode_lovol.off() #CAVE SEQ! mode_hivol.on()
If you kept the circuit from last issue’s instalment, you can now proceed to connecting either 3.3V or 5V to the input. In both cases, switching will take place and the results returned will be accurate.
Given that the current flowing through the protection resistor is now significantly smaller, we could return to
undoing the circuit modifications. We could increase the protection resistors value to something like 50K. We could also increase the value of the lower resistor from 68K, which would give us a wider input range.
This text intentionally skimps on these improvements in order to make circuitry flaws in the ohmmeter easier to spot. If you plan to use this circuitry productively then please, by all means, adjust your resistor values.
It’s all about the ohms!
It’s straightforward to introduce current measurements to a voltmeter. Add a set of shunt resistors, route the current to be measured through them and then measure the voltage. Another challenge involves adding ohmmeter functionality to our little experiment.
For the following steps, we shall assume that the device to be tested is a classic resistor whose behaviour is fully described by Ohm’s law. If we know the values for voltage and current then we can easily determine the resistance.
If we were working on a desktop-class multimeter, the next step would be simple. We would create an adjustable current source that can produce a variety of highly accurate currents.
Selecting a current source has practical benefits. It means that we don’t have to worry about protective elements such as diodes placed in series between the current source and the resistor. The voltage drop caused by the protective element would be compensated for by an increased drive voltage in the current source (see diagram, left).
This, incidentally, also explains why we need different current levels. If the drive current is high and a highvalue resistor drops by, then our current source would need to settle to an insanely high (and dangerous) drive voltage.
Using the current source configuration simplifies measuring very low resistances. The testing leads that connect the multimeter to the resistor under test have resistance, which results in a voltage drop.
A current source permits the configuration that’s shown in the sketch (above right). The grey wires carry the drive current and experience a significant voltage drop. The green wires carry the small sensing current driving analog to digital converter.
They’re connected as close as possible to the resistor under test, leading to relatively accurate measurements - this author’s Youtube channel has a video tutorial at www.youtube.com/watch?v=lamc1_ PTYAS on the topic.
Do it cheaper!
Having a solid grasp of the fundamentals of electrical engineering greatly simplifies metrological tasks. An ingenious approach to resistor measurement has been in low-end meters for quite some time. It became possible thanks to cheap, high-performance microcontrollers. Software and modelling could then be used to improve measurement accuracy.
The idea behind this approach is to use a set of known resistors, which are connected to a known voltage reference. Figure four (overleaf) shows the resulting circuit that looks similar to a voltage divider.
The ADC voltage input is connected across the unknown resistor (commonly called Z). We measure a voltage determined by the selected range resistor, the reference voltage and the value of Z. Given that our multimeter has significant computational resources, we can use a circuit model to obtain a reasonably accurate swipe at the unknown resistor’s value.
The next question at hand determines the resistor values. Given that we want the maximum change over the active range, it’s obvious that one known resistor can’t handle all values. Bear in mind that our ADC only has a limited amount of resolution, and is happy to see “motion” over the measuring range.
Be that as it may, this author decided to use a 1K and a 100K resistor. The actual modifications to the circuit are relatively simple: we added two more relays, along with two additional GPIO pins for the relay driving circuit we discussed in last month’s tutorial.
Next, we return to the Thonny IDE to modify our micrometer’s firmware. Because this isn’t going to be a tutorial in user-interface design, the mode change between voltmeter and ohmmeter takes place in firmware. Should you want to add a professional touch to this little product, consider adding a switch to the breadboard installation.
Opamp production is a highly competitive market.
This leads to excellent datasheets and useful component selection web pages. If you feel like learning more about the magic of Opamps, visit Analog Devices, Texas Instruments, or Stmicroelectronics and see what options are available.
Cheap commodity multimeters usually come with limited documentation. Lygte-info from Denmark provides detailed analysis of low-end products. Visit https://lygteinfo.dk/info/ Dmmdesign Protection Uk.html and click through to learn more.
The following changes need to be made to the code:
mode_ohmlo = Pin(14, PIN.OUT) mode_ohmhi = Pin(15, PIN.OUT) low_ohm_on = 0; oled = SSD1306_SPI(128, 64, spi, dc, res, cs) adc = machine.adc(0)
. . . mode_ohmlo.on() mode_ohmhi.off()
First, we need to configure two more GPIO pins – they control the behaviour of the ohmmeter circuitry. Should you want to play it safe, feel free to run a quick test by switching the relays for a few times.
Second, we need to return to the measurement loop. As before, it’s responsible for collecting input and moving it to the SSD1306 display:
while True: reading = adc.read_u16() * conversion_factor
. . . voltage_seen = 0 if low_on == 1:
voltage_seen =reading / 4; else:
voltage_seen =reading * 7.8 / 4; z = 0 if low_ohm_on == 1: r = 1000 z = ( -1 * r * voltage_seen)/(voltage_seen - 5) else: r = 100000 z = ( -1 * r * voltage_seen)/(voltage_seen - 5) oled.text(str(voltage_seen) + “volt”,15,15) oled.text(str(z) + “ohms”,5,5)
Understanding the code isn’t difficult. Just as in last month’s instalment, we start by grabbing four samples from the ADC and averaging them to eliminate noise. Next, a bit of conversion is done to ensure that value in Ohms can be shown on the organic display. The formula can be derived from the law of voltage dividers.
With these modifications, we can proceed to run a few tests. Let’s start by shorting the input terminal to ground. The unit will now see about 0.14 volts, giving us a computed value of around 30 Ohms. We can use this information to make a small “nulling” correction – asking users to perform a manual null was common in older, high-end test equipment: voltage_seen = voltage_seen - 0.14 z = 0
After this change we measured a few resistors.
At this current stage our multimeter has two problems: first, the permanent switching when confronted with a 1K resistor; and second, the lack of a mode that’s well-suited to higher resistors.
Let’s address the second problem first; the following bit of code switches ranges on demand. It’s interesting in that it uses the actual measured value for switching. This is an approach that can save a lot of effort with conversion of data: oled.text(str(z) + “ohms”,5,5) if z > 2500 and low_ohm_on = 1: low_ohm_on == 0; mode_ohmlo.off() mode_ohmhi.on() elif z < 2500 and low_ohm_on = 0:
low_ohm_on == 1;
mode_ohmhi.off() mode_ohmlo.on()
At this point, we can repeat a test. It delivers improved values, but these still aren’t perfect. Interestingly, almost all values measured in the high range are far too small.
Metrology is fascinating because it’s a highly intricate science: improving parameter A can influence B and C negatively. The offset correction we applied above now bites us. The best solution involves removing the subtraction for now, yielding the improved situation in the following table:
100k Negative value
At this point, we find out that switching to the high ranges isn’t always accomplished. If the meters leads are left open (aka infinite, or almost infinite resistance), then we see a negative value. Given that high values are always best handled in the High mode, modify the switching routine like this: if z > 2500 and low_ohm_on == 1: low_ohm_on = 0; mode_ohmlo.off() mode_ohmhi.on() elif z < 0 and low_ohm_on == 1: low_ohm_on = 0; mode_ohmlo.off() mode_ohmhi.on() elif z < 2500 and low_ohm_on == 0: low_ohm_on = 1; mode_ohmhi.off() mode_ohmlo.on()
At this point, we can try the 100k resistor once again – it consistently reads as 45k or so.
Improving operations
It takes but a cursory glance at the table to realise that the very high values are starting to become inaccurate. This isn’t caused by contamination of the resistors, though. While high-value resistors don’t like being in contact with skin, the amount of fat an average person’s fingers can deposit isn’t enough to explain these value changes. In addition to that, the difference wasn’t visible on the Keithley 177 that was used for comparison. So, something else is afoot.
Understanding the problem requires us to take a step back. Earlier, we discussed how test equipment affects the behaviour of the device under test. In the case of the ohmmeter, the unknown resistor is switched in parallel to our measuring resistor chain. www.techradar.com/pro/linux
Given that we have a total measuring resistor bank of 78K, we apply the law of parallel resistances to the pair. It gives us an equivalent resistance of 43.82 kohms, which is very close to our measured value.
All the rambling about software correction now gives us one last hurrah: the law of parallel resistors enables us to compute the measurement resistor’s impact. Ignoring inaccuracies in the 10 per cent range – the resistors used for this project were notoriously inaccurate, cheap ones – we can proceed like this: z = 0 if low_ohm_on == 1: r = 1000 z = ( -1 * r * voltage_seen)/(voltage_seen - 5) else: r = 100000 z = ( -1 * r * voltage_seen)/(voltage_seen - 5) z = 1/((1/z) - (1/78000)) oled.text(str(voltage_seen) + “volt”,15,15) oled.text(str(z) + “ohms”,5,5)
At this point, the measurement of the 100k resistor turned out to be more accurate. However, the accuracy for smaller values has become even worse. Given that many of these values already were too small before, calibration and/or an analysis of the amplifier is required. This is a topic that’s beyond the scope of this article for now.
100k 113 kohm
Have the following steps always taken the most direct way to the target? Definitely not. Have our experiments shown a variety of valuable mental approaches to problems? Hopefully yes! Developing electronics is an iterative process – more than one prototype that initially looked hopeless turned out to be a successful consumer or military product. We hope that our little experiments have motivated you enough to take further steps into the realms of metrology…
Interactions between test equipment and the device under test are a interesting phenomenon. An old Austrian adage states that the brain of the metrologist must never be switched off during metrology. Keep this in mind if something is behaving oddly, or it might be your multimeter or oscilloscope that’s affecting the circuit’s operation.