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 1mA
Full scale: 2000 milli Ohms approx
Resolution: 0.1 milli Ohms
Kelvin 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:/*
Version 5.0 (12/05/2024)
Copyright 2024 Picuino
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN 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 milliohms
const float BOARD_PHASE_ADJUST = -0.079; // Radians adjust
const float BOARD_ADDED_RESISTOR = 0.0; // Board probes resistor in milliohms. Sistematic Error
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,
};
#endif
volatile 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 pin
pinMode(PIN_SIGNAL_OUT, OUTPUT);
pinMode(PIN_DEBUG_OUT, OUTPUT);
// Print initial info
print_info();
// Set up peripherals
timer0_setup();
timer2_setup();
timer_synchronize();
adc_setup();
// Inits measure
measure_init();
while (adc_measuring == 1);
measure_init();
}
void loop() {
// Main Loop
while (1) {
if (adc_measuring == 0) {
read_float_values();
phase_adjust();
print_values();
measure_init();
}
}
}
void read_float_values(void) {
// Read accumulator values
impedance_inphase = -adc_acc_inphase;
impedance_quadrature = -adc_acc_quadrature;
// Rescale values
impedance_inphase *= BOARD_CALIBRATION;
impedance_quadrature *= BOARD_CALIBRATION;
}
void phase_adjust(void) {
// Get impedance sign
if (impedance_quadrature > 0) {
impedance_sign = 1;
}
else {
impedance_sign = -1;
}
impedance_quadrature = abs(impedance_quadrature);
// Phase adjust
float 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 sign
if (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 interrupts
ADMUX = (1 << 6) |
(0 << ADLAR) |
(PIN_ANALOG_MUX << 0);
ADCSRA = (1 << ADEN) |
(0 << ADSC) |
(0 << ADATE) |
(0 << ADIE) |
(ADC_PRESCALER); // Division factor
ADCSRB = 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 register
TCCR0A = (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 Enable
TIFR0 = 0;
TCNT0 = 0; // Initialize Timer0 counter
sei(); // Allow interrupts
}
void timer2_setup(void) {
cli(); // Stop interrupts
TCCR2A = (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 Enable
TIFR2 = 0;
TCNT2 = 0; // Initialize Timer2 counter
sei(); // Allow interrupts
}
void timer_synchronize(void) {
cli(); // Stop interrupts
GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); // halt all timers
TCNT0 = 0; // Initialize Timer0 counter
TCNT2 = 0; // Initialize Timer2 counter
GTCCR = 0; // release all timers
sei(); // Allow interrupts
while ((PIND & (1 << PIN_SIGNAL_OUT)) != 0);
while ((PIND & (1 << PIN_SIGNAL_OUT)) == 0);
TCNT0 = TIMER0_PERIOD * TIMER0_PHASE_ADJUST; // Initialize Timer0 counter
}
// Timer0 interrupt handler
ISR(TIMER0_COMPA_vect) {
int16_t adc_value;
if (adc_measuring == 1) {
// ADC Start Conversion
ADCSRA |= (1 << ADSC);
// Read last conversion
adc_value = ADCW;
// Accumulate values (10us)
adc_acc_inphase += (int32_t) adc_value * SIN_INTEGER[level_state_old];
adc_acc_quadrature += (int32_t) adc_value * SIN_INTEGER[level_state_old + SAMPLES_PER_WAVE / 4];
// Update next state
level_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 handler
ISR(TIMER2_COMPA_vect) {
}
void debug_pin_pulse(void) {
PORTD |= (1 << PIN_DEBUG_OUT);
delayMicroseconds(4);
PORTD &= ~(1 << PIN_DEBUG_OUT);
}
Schematic: Attached
Related 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/