Ultra-precise milliOhm meter - Part 2/2

In this video I demonstrate the capabilities of the device I assembled and then I show you the code. I haven’t done the current and voltage adjustment yet, because I need some precise resistors for the adjustment. Nevertheless, I get reasonably good values already. I show you how the circuit is built up, what components I used and how the measurement is done using the 4-wire sensing clips. I also spend a bit longer time explaining the principles of the AD converter. I show the ADC-readout in a very detailed way because the principles can be applied to most of the AD converters, so it is worth to know how the things work.



Arduino source code


#include <SPI.h> //for the ADC
#include <Wire.h> //for the display

//Display related
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1305.h>

#define OLED_RESET 9
Adafruit_SSD1305 display(128, 32, &Wire, OLED_RESET);
//---------------------------------------------------

const int CS_pin = 10;
long rawADCdata = 0;
long registerData = 0;
float voltage = 0;
float current = 100; //100 mA
float resistance = 0;
float VREF = 4.096;

void setup()
{
  pinMode (CS_pin, OUTPUT);
  digitalWrite(CS_pin, HIGH);

  //For the ADC
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
  //-----------------------------------------------------
  Serial.begin(9600);
  //-----------------------------------------------------
  //For the display
  while (! Serial) delay(100);
  Serial.println("SSD1305 OLED test");

  if ( ! display.begin(0x3C) )
  {
    Serial.println("Unable to initialize OLED");
    while (1) yield();
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.println("    Ultra-precise");
  display.println("   milliOhm meter");
  display.println("  Curious Scientist");
  display.println("         2022");
  display.display();
  delay(3000);
}


void loop()
{
  calculateVoltage();

  calculateResistance();

  printOLED();
}

void calculateResistance()
{
  //R = U / I
  resistance = 1000 * (voltage) / current; //x1000 -> mOhm (because the current is in mA)

  Serial.print("Voltage: "); //print the average voltage
  Serial.print(voltage);
  Serial.println(" mV");

  Serial.println(" "); //empty line

  Serial.print("Resistance: "); //print the resistance
  Serial.print(resistance, 2);
  Serial.println(" mOhm");

  Serial.println("--------------------------------------------"); //Separator

}

void printOLED()
{
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0); //(x,y)
  display.print("R= ");
  display.setCursor(25, 0);
  display.print(resistance, 2);
  //
  display.setTextSize(1);
  display.setCursor(0, 16);
  display.print("U = ");
  display.setCursor(20, 16);
  display.print(voltage, 2);
  display.setCursor(70, 16);
  display.print(" mV");
  //
  display.setCursor(0, 25);
  display.println("I = 100 mA, fixed");
  display.display();
}

void calculateVoltage()
{
  //Zero the values to avoid mistakes in the summation
  rawADCdata = 0;
  voltage = 0;

  for (int i = 0; i < 10; i++) //read 10x
  {
    rawADCdata += readADC(); //read the ADC, put the raw data into a variable
    delay(200); //wait (conversion time) - see page 4.
  }

  rawADCdata /= 10; //divide it by the number of reads - averaging

  Serial.print("Raw bits: ");
  Serial.println(rawADCdata);
  Serial.println(" "); //empty line

  voltage = rawADCdata * 4.096 / (16777216) * 100; // *100 -> express it in mV (only 100, because the INA106 already gives a x10)
}

long readADC()
{
  //How LTC2400 works? - Manual page 9-10.
  //3 steps:
  //1.) LTC2400 performs the conversion
  //2.) Enters a sleep state
  //3.) Once CS pulled low, the device exits the sleep state and it starts to output the results
  //The data consists of 32 bits. After all is shifted out CS automatically goes HIGH
  //Data format:
  //0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 | 0000
  //  a     b       c      d      e      f      g      h
  //a: status information indicating the sign, input range and conversion state. bit 31 is the EOC -> LOW when conversion is done
  //b to g are conversion. b is the MSB
  //h: sub LSB, can be discarded

  //First, CS must go low to tell the ADC that we want the conversion results
  digitalWrite(CS_pin, LOW);
  //Then, we have to wait the D12 (MISO) pin to become available
  while (digitalRead(12) == 1) {} //we get stuck here until the pin 12 (MISO) is HIGH
  registerData = 0; //zero registerData to start clean

  registerData = SPI.transfer(0xff); //pull out the first byte -- 00000000 00000000 00000000 aaaabbbb
  registerData &= B00001111; //Remove the first four (a-part) bits and keep the rest which are part of the MSB --  00000000 00000000 00000000 0000Bbbb (B = bit27 = MSB)
  registerData <<= 8;         //MSB gets shifted LEFT by 8 bits --  00000000 00000000 0000Bbbb 00000000
  registerData |= SPI.transfer(0xff); //Mid-byte1 comes in and combined with the previous data --  00000000 00000000 0000Bbbb cccccccc
  registerData <<= 8; //MSB+MID1 left shift: 00000000 0000Bbbb cccccccc 00000000
  registerData |= SPI.transfer(0xff); //MSB+MID1 is combined with MID2 -- 00000000 0000Bbbb cccccccc dddddddd
  registerData <<= 8; //MSB+MID1+MID2 left shift: 0000Bbbb cccccccc dddddddd 00000000
  registerData |= SPI.transfer(0xff); //MSB+MID1+MID2+LSB is combined -- 0000Bbbb cccccccc dddddddd eeeeeeee
  //Final, valid part is between the brackets: 0000[Bbbb cccccccc dddddddd eeee]eeee
  registerData >>= 4; //shift out 4 bits (sub-LSBs) // 00000000 Bbbbcccc ccccdddd ddddeeee

  digitalWrite(CS_pin, HIGH);
  return (registerData);
}


Get the PCB from PCBWay

You can order my PCB through my affiliate link using PCBWay’s services. Click on the picture below and you will be redirected to PCBWay’s website.


Previous
Previous

Ultra-precise milliOhm meter - Calibration and full assembly

Next
Next

Ultra-precise milliOhm meter - Part 1/2