A DIY Micro-Ohm Meter with Arduino (vollrathd's design & concept)

rsaeon

Patron
Pursuant (corporate speak, ha) to this helpful video:


It became apparent that I needed empirical verification for the connections I would be making in my LiFePO4 projects, unexpected resistances result in heat build-up, and can become disastrous if unchecked — the prismatic cells I'm using are capable of 5C discharge, that's over 500A!

From: https://www.omnicalculator.com/physics/joule-heating

Screen Shot 2024-07-03 at 5.25.26 AM.png

That's (potential) heat generated by a 500A fault over a 1 milli-ohm conductor every minute. Essentially, you would want your conductors to be in the 10's of micro-ohms for safety.

But most commercially available meters look terrible and are priced beyond what I'm able to spend:

Screen Shot 2024-07-03 at 5.14.15 AM.png

So the moment had finally arrived:

maybe even finally learn about arduino

And I'm entirely impressed with how intuitive and rewarding the process of building and coding Arduino is! It cannot be stated enough. I'm almost convinced that if we had these technologies back when I was learning about electronics (mid-teenage years in the 90s), we'd be building space stations today. It's just incredible.

Most tutorials have you blink a led as your first Arduino project but I skipped right into making a prototype of this Micro-Ohm Meter and I loved every moment of it!

Starting with depopulating a popular prototyping board for Arduino, to serve as the carrier PCB for an Arduino Nano:

photo_2024-07-03 05.12.11.jpeg

Attached headers and a trimpot to adjust contrast for a LCD:

photo_2024-07-03 05.12.12.jpeg

Also seen above is a 0.1uF decoupling capacitor in the bottom left of the proto pcb.

First test with the JHD 8x1 LCD, naturally:

photo_2024-07-03 05.12.13.jpeg

Vollrathd's design incorporated a very cool but tiny OLED — I wanted a larger, easier to read display.

Power is with a 1.2Ah 18650 cell, with a TP4056 module serving as a charger & low voltage disconnect:

photo_2024-07-03 05.12.15.jpeg

Vollrathd's design has a 2S pack for a higher voltage that'll push >1A through the conductor-under-test. A single cell like this pushes about half that much (through a 5 Ohm power resistor):

photo_2024-07-03 05.12.16.jpeg

A little boost converter provides 5V for the Arduino and the 18-bit Analogue to Digital Converter, a MCP3422. The other components are two zeners, three resistors and a capacitor for the input protection of the ADC. There are two more resistors arranged as a voltage divider for the Arduino to measure and display the battery voltage (details in a later post).

photo_2024-07-03 05.12.22.jpeg

There are only two ways to put this chip on this adapter PCB, so it's not a surprise that I got it wrong (white mark on the top right indicates pin 1, but pin 1 is in the bottom left).

In other news, I seem to have lost my soldering mojo, this is definitely not my best work:

photo_2024-07-03 05.12.18.jpeg

The premise of this meter is that it measures the voltage drop across a known 0.1 Ohm 1% resistor (ideally it should be 0.1%) and derives current through Ohm's law (i = v / r):

photo_2024-07-03 05.12.19.jpeg

And then it uses that current value, along with the ADC reading of the voltage drop across the conductor-under-test, to derive the resistance of the conductor-under-test.

It's refreshingly simple and clever.

I'm still waiting on the kelvin probes to be delivered (they allow four-terminal sensing: https://en.wikipedia.org/wiki/Four-terminal_sensing) but here's a quick test:

photo_2024-07-03 05.12.23.jpeg

That's 0.05 milli-ohms or ~50 micro-ohms across the bus bar that was installed in the Muscle Grid battery. Cross-sectional area of it would be 20 sq mm (20 x 1mm).

Compare that with this 40 sq mm bus bar (20 x 2mm):

photo_2024-07-03 05.12.30.jpeg

Working as expected, the meter shows about half the resistance at 0.02 milli-ohms or ~20 micro-ohms. Schematic and code to follow.

edit: comparing to the calculated resistance of a somewhat ideal conductor without holes, the uncalibrated reading is within 5 micro-ohms:

https://www.omnicalculator.com/physics/wire-resistance

Screen Shot 2024-07-03 at 6.37.17 AM.png



Vollrathd's original schematic and code are here: https://www.rcgroups.com/forums/showthread.php?3647559-MicroOhmmeter-Project-Revisited/page10
 
Last edited:
Great, you picked up real quick, fast learner I must say.
And I'm entirely impressed with how intuitive and rewarding the process of building and coding Arduino is! It cannot be stated enough. I'm almost convinced that if we had these technologies back when I was learning about electronics (mid-teenage years in the 90s), we'd be building space stations today. It's just incredible.
You just scratched the surface, wait till you fully see the possibilities.

the voltage drop across a known 0.1 Ohm 1% resistor (ideally it should be 0.1%)
Finding a 0.1Ω 0.1% resistor is hard and also very expensive, however you can get 1Ω 0.1% resistor and if you put 10 of those in parallel you get 0.1Ω resistance with 0.1% tolerance.

This is the part number RT1206BRD071RL, 10 of those will cost you around ₹600.

The second benefit of this if you pass 1A through that single 0.1Ω resistor, that resistor will dissipate 100mW as it will heat up, which will make it's 0.1Ω value drift with temperature. But with multiple 1Ω resistors in parallel, 100mW will spread among them, so each resistor will dissipate only 10mW and they won't drift as much.
 
IGreat, you picked up real quick, fast learner I must say.

You just scratched the surface, wait till you fully see the possibilities.


Finding a 0.1Ω 0.1% resistor is hard and also very expensive, however you can get 1Ω 0.1% resistor and if you put 10 of those in parallel you get 0.1Ω resistance with 0.1% tolerance.

This is the part number RT1206BRD071RL, 10 of those will cost you around ₹600.

The second benefit of this if you pass 1A through that single 0.1Ω resistor, that resistor will dissipate 100mW as it will heat up, which will make it's 0.1Ω value drift with temperature. But with multiple 1Ω resistors in parallel, 100mW will spread among them, so each resistor will dissipate only 10mW and they won't drift as much.
The tolerance will be 10 * 0.1% not 0.1%.
 
I did a few quick searches and found that tolerance of resistors, whether in parallel or series, will be the same if they're all the same (it gets complicated if you're mixing different tolerances).

It makes sense, if you have two 100 ohm resistors that have hypothetically 50% tolerance, then you'll have effective resistance between 25 ohms (two 50 ohm resistors in parallel) and 75 Ohms (two 150 ohms in parallel) — that range of 25 to 75 is basically a 50 ohm resistor with 50% tolerance.

The power dissipation will be distributed equally with multiple parallel resistors, so there's that plus point.

This micro-ohm meter should be used for a few seconds at a time to prevent any drift. Maybe I can wire in a relay somewhere to cut off current after 5 seconds but have the display remain active. Then I'll need a button to resume measurement — or just reset the Arduino.

I found 0.1% 750mW four-point current sense resistors under Rs 70 on element14. Just need to to get a GST number and buy 1000 of them, ha.

You just scratched the surface, wait till you fully see the possibilities.

I am SO excited for this. The original nano died during botched programming and a few digital pins on the other nano died because I was swapping connections while the nano was powered on. I looked it up and these digital pins don't like being driven directly to ground or 5V when they've already been assigned as output pins (sounds obvious in hindsight). I haven't had any issues other than that.
 
The tolerance will be 10 * 0.1% not 0.1%.
I think chat-gpt is correct.

1720105628419.png


I found 0.1% 750mW four-point current sense resistors under Rs 70 on element14. Just need to to get a GST number and buy 1000 of them, ha.
No you don't need GST, the trick is to use semikart.com, when you enter the part number there, they will show all the vendors like digikey, element14, mouser offering that part, and most likely some will be offering it with pricing of single piece, so you don't have to buy 1000, semikart will take care of import duty charges, gst, and domestic shipping charges.

I am SO excited for this. The original nano died during botched programming and a few digital pins on the other nano died because I was swapping connections while the nano was powered on. I looked it up and these digital pins don't like being driven directly to ground or 5V when they've already been assigned as output pins (sounds obvious in hindsight). I haven't had any issues other than that.
As long as mistakes are happening, you are learning. When the pin is set as input, you can connect it to any voltage between 0-5V but when the pin is set as output, then it can only be set in 2 states, HIGH or LOW.
  • HIGH state pin when directly connected to GND 0V is a dead short.
  • LOW state pin when directly connected to 5V is a dead short.
When unsure, just put 1k resistor on the pin which is set as OUTPUT pin, then you can connect it to GND or 5V or to some other 5V tolerant chip through the resistor without worrying, the resistor will limit current to 5mA, which won't damage anything most of the time.

Happy Tinkering. Sooner or later somewhere on your bench, magic smoke will release, it's okay, it's part of the process. ;)
 
I
I think chat-gpt is correct.

View attachment 200658


No you don't need GST, the trick is to use semikart.com, when you enter the part number there, they will show all the vendors like digikey, element14, mouser offering that part, and most likely some will be offering it with pricing of single piece, so you don't have to buy 1000, semikart will take care of import duty charges, gst, and domestic shipping charges.


As long as mistakes are happening, you are learning. When the pin is set as input, you can connect it to any voltage between 0-5V but when the pin is set as output, then it can only be set in 2 states, HIGH or LOW.
  • HIGH state pin when directly connected to GND 0V is a dead short.
  • LOW state pin when directly connected to 5V is a dead short.
When unsure, just put 1k resistor on the pin which is set as OUTPUT pin, then you can connect it to GND or 5V or to some other 5V tolerant chip through the resistor without worrying, the resistor will limit current to 5mA, which won't damage anything most of the time.

Happy Tinkering. Sooner or later somewhere on your bench, magic smoke will release, it's okay, it's part of the process.;)
I did the correct calculation but didn't divide by the new value to get the percentage. You & chat GPT are correct.
 

Attachments

  • IMG_0404.jpeg
    IMG_0404.jpeg
    151.2 KB · Views: 26
Hey look it's my first schematic! Lot's of firsts happening these days, ha. It's not very good but hopefully, it's clear enough.



Some notes:

This is powered by a 18650 Lithium-Ion cell

The cell is plugged into a TP4056 module which handles charging and low-voltage disconnect.

The output of the TP4056 module is then plugged into the proto pcb.

The proto pcb then distributes the battery voltage to the voltage divider R1 & R2, the power resistor R3 and out to a MT3608 boost converter module.

The boost converter module sends back 5V to the proto pcb.

It's done this way because of what modules I had available, there are easier and better ways to do this.



Some other notes:

The voltage divider steps down the 4.2V of a fully charged cell to 0.84V for the Arduino's ADC that's using its internal 1.1V voltage reference.

It's been stated by Arduino engineers that you should keep the input impedance of analog inputs to 10K, which is why chose 8.2K and 2K.

In the code, the ADC reading is remapped to output a number that'll match the voltage at the divider — a reading of 840 for 840 millivolts or 0.84 volts. That reading can then be simply multiplied by 5 to get the actual voltage. In reality though, due to tolerances and inaccuracies, the multiplier will be either a little higher or lower than expected.

Low voltage disconnect is at 2.5V so the analog pin will read between ~490 and ~823 for the voltage range of 2.5V to 4.2V. If I had used a 10:1 voltage divider, like Vollrathd's original 10k and 1k resistor, then that range would've been much smaller at between ~227 and ~381. In the end, none of this actually matters so long as the voltage calculated is close enough to what the actual battery voltage is.

The purpose of the battery reading is to let you know when the cell needs to be recharged (though the low voltage disconnect would probably do that for you, making this entire portion redundant, ha).




arduino-micro-ohm-meter-mcp3422.png
 
Last edited:
Hey look it's my first schematic! Lot's of firsts happening these days, ha. It's not very good but hopefully, it's clear enough.
Looks great for the first timer.



The MCP3422 is divided in 3 three sections which becomes little bit hard to read, from the first glance it looks like three MCP3422 is used, if you look at the arduino nano and the LCD1 both have all their pins together, similarly you can make the MCP3422 like that.

1720232772192.png



It's been stated by Arduino engineers that you should keep the input impedance of analog inputs to 10K, which is why chose 8.2K and 2K.
Hmmn, interesting I didn't know that. Considering the amount of arduino circuits I have seen, people use very high value resistor voltage dividers (to avoid draining the battery). Googling further I found that for ADC the input impedance only matters when we have to measure a fast changing input signal. Since we are measuring a static signal here which is basically a battery voltage, which changes very slowly over time, we don't need to worry about this. You can use much higher value resistors.

Also calculating input impedance for ADC input is not that simple, based on your 8.2k and 2k voltage divider, the input impedance is 1.6k. Based on this formula Zin=(R1+R2)/(R1×R2).

The purpose of the battery reading is let you when the cell needs to be recharged (though the low voltage disconnect would probably do that for you, making this entire portion redundant, ha).
The TP4056 will probably disconnect at 2.5V which is very very low, you probably should disconnect at 3.0V. So it is not redundant.
 
The MCP3422 is divided in 3 three sections which becomes little bit hard to read, from the first glance it looks like three MCP3422 is used, if you look at the arduino nano and the LCD1 both have all their pins together, similarly you can make the MCP3422 like that.

Yeah, it would not have been my preference or choice, but that's what was available with this schematic software, LibrePCB on a mac. It took a few hours of learning the quirks of the software and I think now I like it. They do have a way to make custom symbols but I need to learn more about that, it wasn't intuitive. There's something about KiCad that I really don't like, probably because it's now mainstream. Or maybe because it does a lot of things?

Looks great for the first timer.

Thanks, I tried to mimic the schematics I remember in Elektor Electronics magazines back when I was a teenager and I tried really hard to avoid what I didn't like about the schematics in Electronics For You magazines of the same era. Then I watched a couple of videos about "top design mistakes in schematics" or something and did it all over again — mostly making sure all text reads left to right.

I have made a PCB before with OsmondPCB on mac, but that was without a schematic:


I'm probably making things difficult for me by using a mac. I like to work slow, I guess.

Based on this formula Zin=(R1+R2)/(R1×R2).

Thanks for this, I never actually needed to calculate that before — but it's good to know now.

I followed on through to the datasheet for the 328P:

Screen Shot 2024-07-06 at 6.38.22 PM.png

Further reading about what this could mean: https://electronics.stackexchange.c...-output-impedance-for-adc-input/107755#107755

Since we are measuring a static signal here which is basically a battery voltage, which changes very slowly over time, we don't need to worry about this. You can use much higher value resistors.

That is true, if this was a critical reading I'd just use a INA219 since it's a far more elegant solution. Prolonging battery life is less of a concern here, since this'll just be used for a few seconds at a time to check connections. I did notice however that the battery voltage fell drastically after 3V —

The TP4056 will probably disconnect at 2.5V which is very very low, you probably should disconnect at 3.0V. So it is not redundant.

— so this makes sense.

I'm already designing a PCB in my head for this, but I need to pace myself and not rush into it. I tend to get hyper-focussed on a single thing and forget about the rest of the world. This circuit works, so now I'll stuff it in a food container and continue with the other projects and revisit this later and possibly make it into a product.

It has been fun though. I'll post the code next, after I finish the comments and attributions.
 
The code, hopefully it's bug-free:

edit: fixed stuff

C:
/*

Micro-Ohm Meter by RSA first published: https://techenclave.com/threads/a-diy-micro-ohm-meter-with-arduino-vollrathds-design-concept.220788/

Original design & concept by VollrathD: https://www.rcgroups.com/forums/showthread.php?3647559-MicroOhmmeter-Project-Revisited/page10

*******************************************************************************
WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING

    DO NOT DO RESISTOR TESTS WITH ONLY THE USB POWER TO THE METER!

    THAT RESISTOR UNDER TEST REVERSES BIASES THE ARDUINO NANO 5 VOLT REGULATOR
    AND WILL BLOW THE REGULATOR. A POWER SOURCE MUST BE CONNECTED TO THE METER
    ANY TIME THE USB PC PORT IS PLUGGED INTO THE ARDUINO NANO WHEN CONNECTING
    A TEST RESISTOR TO THE MICRO OHM METER.

WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
*******************************************************************************

*/

#include <Wire.h>
#include <MCP342x.h> // https://github.com/stevemarple/MCP342x
#include <LiquidCrystal.h>

const int en = 12, rs = 11, d4 = 10, d5 = 9, d6 = 8, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

uint8_t address = 0x68; // default for MCP3422
MCP342x adc = MCP342x(address);

const int voltage_adjust = 1045; // find via trial & error
float voltage_multiplier = 5.15; // find via mathematics
float battery_volts = 0;
int battery_input = 0;

float calibration = 1.0; // uncalibrated
float conductor;
double current;

void setup()
{
  //Serial.begin(9600);
  //Serial.print("\n\nRESET\n");

  analogReference(INTERNAL); // 1.1V
  Wire.begin();

  MCP342x::generalCallReset();
  delay(1);

  lcd.begin(8, 1);
  lcd.print("\xE4\xF4");
  delay(500);
  for (int animate = 1; animate < 7; animate++) // cool animation
  {
  lcd.scrollDisplayRight();
  delay(250);
  }
  delay(1000);
  lcd.clear();
}

void check_battery()
{
  battery_input = analogRead(6);
  battery_volts = map(battery_input, 0, 1023, 0, voltage_adjust); // remap reading to match measured microvolts
  battery_volts = battery_volts / 1000 * voltage_multiplier; // comment out this line while doing above
  //Serial.print("\nBattery: ");
  //Serial.print(battery_volts);
}

void measure_shunt()
{
 long value = 0;
  MCP342x::Config status;
  uint8_t err = adc.convertAndRead(MCP342x::channel1, MCP342x::oneShot, MCP342x::resolution16, MCP342x::gain8, 1000000, value, status);
  if (!err)
  {
    current = value * 2.048 / 8 / 32768 / 0.1 * calibration;
    // nominal 3.7V / 5R = 0.74A
    //Serial.print("\nCurrent: ");
    //Serial.print(current);
  }
}

void calc_8x18bit()       
{
 long value = 0;
  MCP342x::Config status;
  uint8_t err = adc.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution18, MCP342x::gain8, 1000000, value, status);
  if (!err && value > -131071 && value < 131071)
  {
    conductor = value * 2.048 / 8 / 131072 / current;
    // LSB = 0.256V / 0.74A = 0.3459 R (max) / 131072 = 2.6 micro-ohm
    //Serial.print("\n8x18bit Range: ");
  }
}

void calc_4x16bit()       
{
 long value = 0;
  MCP342x::Config status;
  uint8_t err = adc.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution16, MCP342x::gain4, 1000000, value, status);
  if (!err && value > -32767 && value < 32767)
  {
    conductor = value * 2.048 / 4 / 32768 / current;
    // LSB = 0.512V / 0.74A = 0.6918 R (max) / 32768 = 21 micro-ohm
    //Serial.print("\n4x16bit Range: ");
  }
}

void calc_2x14bit()       
{
 long value = 0;
  MCP342x::Config status;
  uint8_t err = adc.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution14, MCP342x::gain2, 1000000, value, status);
  if (!err && value > -8191 && value < 8191)
  {
    conductor = value * 2.048 / 2 / 8192 / current;
    // LSB = 1.048V / 0.74A = 1.4162 R (max) / 8192 = 172 micro-ohm
    //Serial.print("\n2x14bit Range: ");
  }
}

void calc_1x12bit()       
{
 long value = 0;
  MCP342x::Config status;
  uint8_t err = adc.convertAndRead(MCP342x::channel2, MCP342x::oneShot, MCP342x::resolution12, MCP342x::gain1, 1000000, value, status);
  if (!err && value > -2047 && value < 2047)
  {
    conductor = value * 2.048 / 1 / 2048 / current;
    // LSB = 2.048V / 0.74A = 2.7675 R (max) / 2048 = 1.35 milli-ohm
    //Serial.print("\n1x12bit Range: ");
  }
}

void loop()
{
  check_battery();
  measure_shunt();
  lcd.setCursor(0, 0);
  if (current > 0) // if test conductor is connected
  {
    calc_8x18bit(); // start with highest resolution range
    if (!conductor) // switch to lower ranges if saturated
    {
      calc_4x16bit();
    }
    if (!conductor)
    {
      calc_2x14bit();
    }
    if (!conductor)
    {
      calc_1x12bit();
    }
    if (conductor < 0)
    {
      conductor = conductor * -1; // convert to positive if negative (shorted probes)
    }
    //Serial.print(conductor);
    if (conductor >= 2)
    {
      lcd.print("OL1.999\xF4"); // overlimit
    }
    else if (conductor >= 1) // auto-range below for 8x1 display
    {
      lcd.print(conductor, 4); // 1.0000 to 1.999 ohms
      lcd.print(" \xF4");
    }
    else if (conductor * 1000 >= 100)
    {
      lcd.print(conductor * 1000, 1); // 100.0 to 999.9 milli-ohms
      lcd.print(" m\xF4");
    }
    else if (conductor * 1000 >= 10)
    {
      lcd.print(conductor * 1000, 2); // 10.00 to 99.99 milli-ohms
      lcd.print(" m\xF4");
    }
    else if (conductor * 1000 >= 1)
    {
      lcd.print(conductor * 1000, 3); // 1.000 to 9.999 milli-ohms
      lcd.print(" m\xF4");
    }
    else if (conductor * 1000000 >= 100)
    {
      lcd.print(conductor * 1000000, 1); // 100.0 to 999.9 micro-ohms
      lcd.print(" \xE4\xF4");
    }
    else if (conductor * 1000000 >= 10)
    {
      lcd.print(conductor * 1000000, 2); // 10.00 to 99.99 micro-ohms
      lcd.print(" \xE4\xF4");
    }
    else
    {
      lcd.print(conductor * 1000000, 3); // 1.000 to 9.999 micro-ohms
      lcd.print(" \xE4\xF4");
    }
    conductor = 0; // reset for next run
  }
  else // if no test conductor is connected
  {
    lcd.print("b: " + String(battery_volts) + "v");
  }
  delay(1000);
}
 
Last edited:
Back
Top