Milliohm meter Picuino - Page 1
Forum section: electronics / projects

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 08:25:03 pm
content: In this thread I am going to post the results of an active and reactive milliohm measuring device that I have developed in this other thread:https://www.eevblog.com/forum/projects/homebrew-lock-in-amplifier/Characteristics:Measuring: resistance, capacitance and inductance with ESR.Operating principle: Synchronous detection of a triangular signal of 512Hz and 1mAFull scale: 2000 milli Ohms approxResolution: 0.1 milli OhmsKelvin connection (4 wires sense)Arduino Nano powered.Single 5V power supply.Measurements are sent via USB to a computer. (Pending the addition of an LCD display).Open source hardware and software.Tasks to do:1. Calibrate the instrument and check its linearity, accuracy, etc.2. Select a cheaper instrumentation amplifier.3. Add a signal input protected against charged capacitors.4. Extend the measurement range.Arduino Program:Code:/*Version 5.0 (12/05/2024)Copyright 2024 PicuinoPermission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be includedin all copies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISINGFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGSIN THE SOFTWARE.*/#include <stdint.h>#define CLK_BOARD 16000000#define UART_BAUDS 115200#define MEASURE_TIME 1.0#define PIN_SIGNAL_OUT 3#define PIN_ANALOG A6#define PIN_ANALOG_MUX 6#define PIN_DEBUG_OUT 6#define SAMPLES_16#ifdef SAMPLES_16#define SAMPLES_PER_WAVE 16#define ADC_PRESCALER (0b111)#define TIMER2_PERIOD 244#define TIMER2_FREQ (CLK_BOARD / (1.0 * TIMER2_PERIOD * 64 * 2))#define TIMER0_PERIOD (244 - 1)#define TIMER0_PHASE_ADJUST (0.40)#define TIMER0_FREQ (CLK_BOARD / (1.0 * (TIMER0_PERIOD + 1) * 8))#define SAMPLES_PER_MEASURE (SAMPLES_PER_WAVE * (long) ((MEASURE_TIME) * (TIMER0_FREQ) / SAMPLES_PER_WAVE))const float BOARD_CALIBRATION = 0.2410 / (SAMPLES_PER_MEASURE); // Converts measure to milliohmsconst float BOARD_PHASE_ADJUST = -0.079; // Radians adjustconst float BOARD_ADDED_RESISTOR = 0.0; // Board probes resistor in milliohms. Sistematic Errorconst int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {12, 34, 51, 60,60, 51, 34, 12,-12, -34, -51, -60,-60, -51, -34, -12,12, 34, 51, 60,};#endifvolatile int32_t adc_acc_inphase;volatile int32_t adc_acc_quadrature;volatile int32_t adc_samples;volatile uint8_t adc_measuring;volatile uint8_t level_state;volatile uint8_t level_state_old;float impedance_inphase;float impedance_quadrature;float impedance_sign;void setup() {Serial.begin(UART_BAUDS);// Set up output reference signal pinpinMode(PIN_SIGNAL_OUT, OUTPUT);pinMode(PIN_DEBUG_OUT, OUTPUT);// Print initial infoprint_info();// Set up peripheralstimer0_setup();timer2_setup();timer_synchronize();adc_setup();// Inits measuremeasure_init();while (adc_measuring == 1);measure_init();}void loop() {// Main Loopwhile (1) {if (adc_measuring == 0) {read_float_values();phase_adjust();print_values();measure_init();}}}void read_float_values(void) {// Read accumulator valuesimpedance_inphase = -adc_acc_inphase;impedance_quadrature = -adc_acc_quadrature;// Rescale valuesimpedance_inphase *= BOARD_CALIBRATION;impedance_quadrature *= BOARD_CALIBRATION;}void phase_adjust(void) {// Get impedance signif (impedance_quadrature > 0) {impedance_sign = 1;}else {impedance_sign = -1;}impedance_quadrature = abs(impedance_quadrature);// Phase adjustfloat module = sqrt(impedance_inphase * impedance_inphase + impedance_quadrature * impedance_quadrature);float phase;if (abs(impedance_inphase) < 0.1) {phase = 90;}else {phase = atan(impedance_quadrature / impedance_inphase);}phase *= impedance_sign;phase += BOARD_PHASE_ADJUST;impedance_inphase = module * cos(phase);impedance_quadrature = module * sin(phase);// Get new impedance signif (impedance_quadrature > 0) {impedance_sign = 1;}else {impedance_sign = -1;}impedance_quadrature = abs(impedance_quadrature);// Substract board resistance (sistematic error)impedance_inphase -= BOARD_ADDED_RESISTOR;}void print_info(void) {Serial.println();Serial.print("SAMPLE_FREQUENCY = ");Serial.print(1.0 * TIMER0_FREQ);Serial.println(" Hz");Serial.print("MEASURE_SIGNAL_FREQUENCY = ");Serial.print(1.0 * TIMER2_FREQ);Serial.println(" Hz");Serial.print("SAMPLE_TIME = ");Serial.print(1.0 * SAMPLES_PER_MEASURE / TIMER0_FREQ);Serial.println(" s");}void print_values(void) {Serial.print(impedance_inphase, 1);Serial.print("\tmOhm R \t");if (impedance_sign > 0) {Serial.print(impedance_quadrature, 1);Serial.print("\tmOhm Z_L \t");if (impedance_quadrature > 5.0) {Serial.print(impedance_quadrature * 1000.0 / (TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuHenrys");}else {Serial.println();}}else {Serial.print(impedance_quadrature, 1);Serial.print("\tmOhm Z_C \t");if (impedance_quadrature > 5.0) {Serial.print(1000000000.0 / (impedance_quadrature * TIMER2_FREQ * 2.0 * 3.1415927));Serial.println("\tuFarads");}else {Serial.println();}}}void adc_setup(void) {analogRead(PIN_ANALOG);cli(); // Stop interruptsADMUX = (1 << 6) |(0 << ADLAR) |(PIN_ANALOG_MUX << 0);ADCSRA = (1 << ADEN) |(0 << ADSC) |(0 << ADATE) |(0 << ADIE) |(ADC_PRESCALER); // Division factorADCSRB = 0x00;sei(); // Allow interrupts}void measure_init(void) {delayMicroseconds(1000);cli();adc_acc_inphase = 0;adc_acc_quadrature = 0;level_state = SAMPLES_PER_WAVE * 0.25;level_state_old = 0;adc_samples = 0;ADCW = 0;sei();while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);adc_measuring = 1;}void timer0_setup(void) {cli(); // Stop interrupts// set compare match registerTCCR0A = (0 << 6) | // OOM0A. 0=OC0A disconnected. 1=Toggle OC0A on compare match (p.84)(0 << 4) | // COM0B. 0=OC0B disconnected. 1=Toggle OC0B on compare match (p.85)(2 << 0); // WGM0. PWM mode. 1=phase correct 2=CTC (p.86)TCCR0B = (0 << 7) | // FOC0A.(0 << 6) | // FOC0B.(0 << 3) | // WGM02.(2 << 0); // CLOCK source.OCR0A = TIMER0_PERIOD;OCR0B = TIMER0_PERIOD / 2;TIMSK0 = (0 << 2) | // OCIE0B. Match B Interrupt Enable(1 << 1) | // OCIE0A. Match A Interrupt Enable(0 << 0); // TOIE0. Overflow Interrupt EnableTIFR0 = 0;TCNT0 = 0; // Initialize Timer0 countersei(); // Allow interrupts}void timer2_setup(void) {cli(); // Stop interruptsTCCR2A = (1 << 6) | // OOM2A. 0=OC2A disconnected. 1=Toggle OC2A on compare match (p.128)(2 << 4) | // COM2B. 2=Clear OC2B on compare match (p.129)(1 << 0); // WGM2. PWM mode. 1=phase correct (p.130)TCCR2B = (0 << 7) | // FOC2A.(0 << 6) | // FOC2B.(1 << 3) | // WGM22.(4 << 0); // CLOCK source.OCR2A = TIMER2_PERIOD;OCR2B = TIMER2_PERIOD / 2;TIMSK2 = (0 << 2) | // OCIE2B. Match B Interrupt Enable(0 << 1) | // OCIE2A. Match A Interrupt Enable(0 << 0); // TOIE2. Overflow Interrupt EnableTIFR2 = 0;TCNT2 = 0; // Initialize Timer2 countersei(); // Allow interrupts}void timer_synchronize(void) {cli(); // Stop interruptsGTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timersTCNT0 = 0; // Initialize Timer0 counterTCNT2 = 0; // Initialize Timer2 counterGTCCR = 0; // release all timerssei(); // Allow interruptswhile ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);TCNT0 = TIMER0_PERIOD * TIMER0_PHASE_ADJUST; // Initialize Timer0 counter}// Timer0 interrupt handlerISR(TIMER0_COMPA_vect) {int16_t adc_value;if (adc_measuring == 1) {// ADC Start ConversionADCSRA |= (1 << ADSC);// Read last conversionadc_value = ADCW;// Accumulate values (10us)adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER;adc_acc_quadrature += (int32_t) adc_value * SIN_INTEGER[level_state_old + SAMPLES_PER_WAVE / 4];// Update next statelevel_state_old = level_state;level_state++;if (level_state >= SAMPLES_PER_WAVE)level_state = 0;adc_samples++;if (adc_samples > SAMPLES_PER_MEASURE) {adc_measuring = 0;}}}// Timer2 interrupt handlerISR(TIMER2_COMPA_vect) {}void debug_pin_pulse(void) {PORTD |= (1 << PIN_DEBUG_OUT);delayMicroseconds(4);PORTD &= ~(1 << PIN_DEBUG_OUT);}Schematic:AttachedRelated articles:https://circuitcellar.com/cc-blog/build-an-accurate-milliohm-meter/https://www.electronicsforu.com/electronics-projects/milliohm-meterhttps://www.eevblog.com/forum/projects/design-review-ac-milliohm-meter/

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 08:27:01 pm
content: Some Measurements (uncalibrated):Resistor of 1 Ohm:Code:SAMPLE_FREQUENCY = 8196.72 HzMEASURE_SIGNAL_FREQUENCY = 512.30 HzSAMPLE_TIME = 1.00 s1085.3mOhm R1.3mOhm Z_C1085.3mOhm R1.3mOhm Z_C1085.2mOhm R1.4mOhm Z_C1085.2mOhm R1.4mOhm Z_C1085.2mOhm R1.4mOhm Z_C1085.1mOhm R1.4mOhm Z_C1085.1mOhm R1.5mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.1mOhm R1.4mOhm Z_C1085.2mOhm R1.4mOhm Z_C1085.0mOhm R1.3mOhm Z_C1085.1mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.3mOhm Z_C1084.9mOhm R1.3mOhm Z_C1085.0mOhm R1.4mOhm Z_C1085.0mOhm R1.3mOhm Z_C1085.0mOhm R1.4mOhm Z_CCapacitor of 1000uF:Code:SAMPLE_FREQUENCY = 8196.72 HzMEASURE_SIGNAL_FREQUENCY = 512.30 HzSAMPLE_TIME = 1.00 s-0.5mOhm R0.8mOhm Z_C63.2mOhm R237.2mOhm Z_C1309.69uFarads96.0mOhm R362.8mOhm Z_C856.42uFarads95.7mOhm R362.3mOhm Z_C857.52uFarads95.8mOhm R362.4mOhm Z_C857.16uFarads95.9mOhm R362.5mOhm Z_C856.97uFarads95.8mOhm R362.6mOhm Z_C856.74uFarads95.9mOhm R362.7mOhm Z_C856.62uFarads95.8mOhm R362.7mOhm Z_C856.47uFarads95.9mOhm R362.5mOhm Z_C856.92uFarads95.9mOhm R362.7mOhm Z_C856.56uFarads95.8mOhm R362.7mOhm Z_C856.54uFarads95.8mOhm R362.6mOhm Z_C856.84uFarads95.8mOhm R362.7mOhm Z_C856.57uFarads95.9mOhm R362.7mOhm Z_C856.59uFarads95.9mOhm R362.7mOhm Z_C856.58uFarads95.9mOhm R362.6mOhm Z_C856.69uFarads95.9mOhm R362.7mOhm Z_C856.61uFarads95.8mOhm R362.6mOhm Z_C856.77uFarads95.8mOhm R362.7mOhm Z_C856.55uFarads95.9mOhm R362.7mOhm Z_C856.66uFarads95.9mOhm R362.7mOhm Z_C856.64uFarads95.8mOhm R362.7mOhm Z_C856.64uFarads95.9mOhm R362.7mOhm Z_C856.57uFaradsInductor of 68uH:Code:SAMPLE_FREQUENCY = 8196.72 HzMEASURE_SIGNAL_FREQUENCY = 512.30 HzSAMPLE_TIME = 1.00 s44.0mOhm R185.3mOhm Z_L57.56uHenrys44.1mOhm R185.6mOhm Z_L57.66uHenrys44.2mOhm R185.9mOhm Z_L57.75uHenrys44.1mOhm R186.0mOhm Z_L57.77uHenrys43.8mOhm R185.9mOhm Z_L57.76uHenrys43.9mOhm R186.0mOhm Z_L57.79uHenrys43.9mOhm R185.8mOhm Z_L57.71uHenrys44.1mOhm R185.7mOhm Z_L57.69uHenrys44.2mOhm R185.6mOhm Z_L57.65uHenrys44.1mOhm R185.4mOhm Z_L57.59uHenrys43.9mOhm R185.2mOhm Z_L57.54uHenrys44.0mOhm R185.1mOhm Z_L57.51uHenrys44.1mOhm R185.0mOhm Z_L57.47uHenrys44.2mOhm R185.0mOhm Z_L57.46uHenrys44.1mOhm R184.9mOhm Z_L57.44uHenrys44.0mOhm R184.7mOhm Z_L57.37uHenrys43.8mOhm R184.6mOhm Z_L57.36uHenrys43.8mOhm R184.6mOhm Z_L57.35uHenrys43.8mOhm R184.6mOhm Z_L57.34uHenrys43.8mOhm R184.6mOhm Z_L57.36uHenrys43.9mOhm R184.7mOhm Z_L57.38uHenrys43.9mOhm R184.7mOhm Z_L57.37uHenrys43.9mOhm R184.8mOhm Z_L57.40uHenrys43.9mOhm R184.8mOhm Z_L57.40uHenrys43.8mOhm R184.8mOhm Z_L57.40uHenrys

author: Picuino
author_rank: Super Contributor
timestamp: May 12, 2024, 08:33:46 pm
content: Breadboard of analog section (without Arduino Nano)

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 08:28:34 pm
content: Script of gf (https://www.eevblog.com/forum/profile/?u=176249) to calculate the multiplier for the least distorting discrete sine function.The discrete sine function is used to multiply by the ADC samples that read the signal at the impedance under test. When multiplying the sine function by the received signal, only the signal that has the exact same frequency as the sine function will be measured.Quote from: gf on May 13, 2024, 07:47:53 pmAttached: resultsPython script for create integer sines table:Code:SAMPLES_PER_WAVE = 16MULTIPLIER = 61import mathdots = [d/2 for d in range(1, SAMPLES_PER_WAVE*3 + 1, 2)]angles = [d*2*math.pi/SAMPLES_PER_WAVE for d in dots]sines = [round(MULTIPLIER * math.sin(angle)) for angle in angles]print("const int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {")for i in range(SAMPLES_PER_WAVE//4 + 1):row = [str(s) for s in sines[i*4:i*4+4]]print(" " + ", ".join(row) + ',')print("};")integer sine table with multiplier=61:Code:const int16_t SIN_INTEGER[SAMPLES_PER_WAVE + SAMPLES_PER_WAVE / 4] = {12, 34, 51, 60,60, 51, 34, 12,-12, -34, -51, -60,-60, -51, -34, -12,12, 34, 51, 60,};

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 09:11:41 pm
content: Quote from: Picuino on May 13, 2024, 08:28:34 pmNot exactly. Other frequencies can also pass through, but they are more or less attenuated. It acts like a filter and has a frequency response. Only a few frequencies that fall on the zeros of the frequency response are completely rejected.Attached is the frequency response of the equivalent bandpass filter, when 8192 Samples are multiplied with the complex sine wave and accumulated.The three spurs result from the integer quantization of the sine tables. They are undesired, but they are below -60 dBc. These spurs are located at odd harhomics of the carrier frequency, and therefore determine how well the filter rejects carrier harmonics.Figure2 is a zoom-in to the peak.

author: jbb
author_rank: Super Contributor
timestamp: May 13, 2024, 09:22:58 pm
content: I had a quick look at the schematic, and I have a question.U1 is an instrumentation amp, and applies quite some gain. It seems to run from a single +5V supply. It looks like the +ve input signal will be near ground. Will U1 work OK with such a low input common mode voltage?

author: gf
author_rank: Super Contributor
timestamp: May 13, 2024, 09:47:01 pm
content: The datasheet says it works down to -0.1V.

author: Picuino
author_rank: Super Contributor
timestamp: May 13, 2024, 09:54:49 pm
content: The maximum output voltage is 5V and the gain is 1050, as a result the minimum input voltage before saturating the amplifier will be -4.7 millivolts.In practice it works without problems.

author: Picuino
author_rank: Super Contributor
timestamp: May 14, 2024, 06:44:57 am
content: One problem with the meter I just found is that the input is not protected against charged capacitors. One improvement I can think of is to add two diodes in antiparallel to the input.

author: croma641
author_rank: Regular Contributor
timestamp: May 14, 2024, 10:58:46 am
content: I believe that this project represents the best design/program for implementing a diy micro-ohmmeter. It can be used to derive interesting insights for your own project.https://www.pittnerovi.com/jiri/hobby/electronics/milliohmmeter/index.html