With the current test current of some 1 mA 0,1 mOhm is 100 nV at the DUT. So the observed noise is not that bad.
It depends on the avearging time if the observed noise is about what one can expect from the AD8220. With a lock-in amplifier one can trade speed for noise. With relatively short integration one may still get some effect of mains hum.
What are the aimed specs? What range of resistance, with what accuracy, and at what resolution should the instrument measure?
At risk of totally derailing the proceedings, I have an alternative suggestion: the Texas Instruments ADS1235. It has a 24 bit ADC, Programmable Gain Amplifier (PGA) and control logic for AC excitation. Seems like it can apply positive excitation, sample the ADC, apply negative excitation and sample the ADC again for you.
Back in the 90's I used an AD630 to extract a battery impedance measurement from industrial noisy live battery systems using a portable tool that applied about 0.1Arms sinewave into a battery (cell or string) and kelvin probe extracted a signal that was amplified and presented to the AD630. An ICL8038 sine generator provided reasonable amplitude stability, muting, and a sync signal for the AD630. The test frequency was aligned to nominal zero-phase shift response of target batteries, and not aligned to any mains related processes. At that time only a 12-bit ADC was practical, with software flipping the sensed signal polarity and averaging, to achieve a max error tolerance of <0.3% along with temperature errors mainly due to the 8038 and current sense resistor, for FS reading of 50 milliohm and resolution down to single micro-ohm level. The AD630 was not the main concern for performance, but was a pricey part.
21.499 mOhm
21.502 mOhm
21.496 mOhm
21.502 mOhm
21.504 mOhm
21.499 mOhm
21.508 mOhm
21.502 mOhm
21.500 mOhm
21.503 mOhm
21.508 mOhm
21.500 mOhm
21.505 mOhm
21.504 mOhm
21.507 mOhm
21.500 mOhm
21.512 mOhm
21.499 mOhm
21.505 mOhm
21.504 mOhm
21.505 mOhm
21.493 mOhm
21.496 mOhm
21.502 mOhm
21.499 mOhm
21.501 mOhm
21.502 mOhm
21.495 mOhm
21.492 mOhm
21.499 mOhm
21.486 mOhm
21.488 mOhm
21.499 mOhm
21.505 mOhm
21.494 mOhm
21.509 mOhm
21.501 mOhm
21.490 mOhm
21.501 mOhm
21.485 mOhm
21.492 mOhm
21.495 mOhm
21.493 mOhm
21.497 mOhm
21.492 mOhm
21.497 mOhm
21.501 mOhm
/*
Version 1.1 (06/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.
*/
const int PIN_PWM_OUT = 3;
const int PIN_ANALOG = A6;
const int PIN_ANALOG_MUX = 6;
#define CLK_BOARD 16000000
#define UART_BAUDS 115200
#define TIMER0_PRESET 27 // Must be >= 27
#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64)) // 9250
const float MEASURE_TIME = 1; // Seconds
const long SAMPLES_PER_LEVEL = 40; // ADC samples per output level
const long LEVELS_PER_MEASURE = 2 * (long)((MEASURE_TIME * TIMER0_FREQ) / (SAMPLES_PER_LEVEL * 2));
const float BOARD_CALIBRATION = 8.0; // Converts measure to milliohms
volatile long adc_sum;
volatile unsigned int adc_enable;
volatile unsigned int adc_value;
volatile unsigned long adc_samples;
volatile unsigned int level_state;
volatile unsigned char output_level;
volatile unsigned char output_level_old;
volatile unsigned char adc_measure_end;
char buff[50];
float resistance;
void setup() {
Serial.begin(UART_BAUDS);
Serial.print("MEASURE_TIME = "); Serial.println(1.0 * SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE / TIMER0_FREQ);
Serial.print("SAMPLES_PER_LEVEL = "); Serial.println(SAMPLES_PER_LEVEL);
Serial.print("LEVELS_PER_MEASURE = "); Serial.println(LEVELS_PER_MEASURE);
// Set up output reference signal pin
pinMode(PIN_PWM_OUT, OUTPUT);
// Set up peripherals
timer0_setup();
adc_setup();
// Main Loop
adc_init();
for (;;) {
if (adc_measure_end == 1) {
resistance = adc_sum;
resistance *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));
Serial.print(resistance, 2);
Serial.println("\tmOhm");
adc_init();
}
}
}
void loop() {}
void adc_init(void) {
cli();
PORTD |= (1 << PIN_PWM_OUT);
output_level = 0;
level_state = 0;
delayMicroseconds(100);
adc_sum = 0;
adc_samples = 0;
output_level = 0;
adc_measure_end = 0;
adc_enable = 1;
sei();
}
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) | (0b111);
ADCSRB = 0x00;
sei(); //allow interrupts
}
void timer0_setup(void) {
cli(); //stop interrupts
//set timer0 interrupt at 2kHz
TCCR0A = 0; // set entire TCCR2A register to 0
TCCR0B = 0; // same for TCCR2B
TCNT0 = 0; //initialize counter value to 0
// set compare match register
OCR0A = TIMER0_PRESET;
// turn on CTC mode
TCCR0A |= (1 << WGM01);
// Set CS01 and CS00 bits for 64 prescaler
TCCR0B |= (1 << CS01) | (1 << CS00);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
sei(); //allow interrupts
}
ISR(TIMER0_COMPA_vect) {
if (adc_enable) {
// ADC Start Conversion
ADCSRA |= (1 << ADSC);
delayMicroseconds(12); // Wait for Sample and Hold
// Read and accumulate old ADC value
if (adc_samples) {
// Read ADC old measure
adc_value = ADCW;
// Add measure to accumulator
if (output_level_old == 0) {
adc_sum -= adc_value;
}
if (output_level_old == 1) {
adc_sum += adc_value;
}
}
adc_samples++;
// Update next state
level_state++;
if (level_state >= (2 * SAMPLES_PER_LEVEL)) {
level_state = 0;
}
// Update output reference signal
output_level_old = output_level;
if (level_state < SAMPLES_PER_LEVEL) {
PORTD |= (1 << PIN_PWM_OUT);
output_level = 0;
}
else {
PORTD &= ~(1 << PIN_PWM_OUT);
output_level = 1;
}
// Take one measure after several samples
if (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE) {
PORTD |= (1 << PIN_PWM_OUT);
output_level = 0;
adc_measure_end = 1;
adc_enable = 0;
}
}
}
MEASURE_TIME = 0.10
SAMPLES_PER_LEVEL = 40
LEVELS_PER_MEASURE = 22
22.04 mOhm
21.55 mOhm
21.41 mOhm
21.90 mOhm
21.52 mOhm
21.76 mOhm
22.08 mOhm
21.52 mOhm
21.89 mOhm
21.86 mOhm
21.71 mOhm
21.98 mOhm
21.60 mOhm
21.79 mOhm
22.03 mOhm
21.50 mOhm
21.98 mOhm
21.65 mOhm
21.57 mOhm
22.15 mOhm
21.48 mOhm
21.95 mOhm
21.99 mOhm
21.65 mOhm
21.74 mOhm
21.59 mOhm
21.81 mOhm
21.76 mOhm
22.13 mOhm
21.75 mOhm
21.70 mOhm
22.00 mOhm
21.54 mOhm
21.89 mOhm
21.90 mOhm
21.43 mOhm
22.26 mOhm
21.35 mOhm
21.89 mOhm
21.80 mOhm
21.38 mOhm
21.95 mOhm
21.79 mOhm
21.50 mOhm
21.99 mOhm
21.90 mOhm
21.33 mOhm
22.08 mOhm
21.33 mOhm
21.75 mOhm
21.58 mOhm
21.57 mOhm
21.78 mOhm
21.28 mOhm
21.67 mOhm
22.05 mOhm
21.76 mOhm
21.28 mOhm
21.86 mOhm
22.06 mOhm
21.40 mOhm
21.72 mOhm
21.83 mOhm
21.46 mOhm
22.15 mOhm
21.25 mOhm
21.77 mOhm
MEASURE_TIME = 0.99
SAMPLES_PER_LEVEL = 40
LEVELS_PER_MEASURE = 222
21.62 mOhm
21.68 mOhm
21.73 mOhm
21.62 mOhm
21.67 mOhm
21.64 mOhm
21.71 mOhm
21.66 mOhm
21.69 mOhm
21.68 mOhm
21.67 mOhm
21.69 mOhm
21.71 mOhm
21.74 mOhm
21.78 mOhm
21.64 mOhm
21.71 mOhm
21.72 mOhm
21.66 mOhm
21.59 mOhm
21.70 mOhm
21.71 mOhm
21.60 mOhm
21.68 mOhm
21.69 mOhm
21.68 mOhm
21.57 mOhm
21.63 mOhm
21.66 mOhm
21.64 mOhm
21.71 mOhm
21.74 mOhm
21.63 mOhm
21.76 mOhm
21.74 mOhm
21.74 mOhm
21.76 mOhm
21.70 mOhm
21.68 mOhm
21.65 mOhm
21.64 mOhm
21.67 mOhm
21.61 mOhm
21.52 mOhm
21.65 mOhm
21.64 mOhm
21.64 mOhm
21.67 mOhm
21.70 mOhm
21.53 mOhm
21.54 mOhm
MEASURE_TIME = 10.00
SAMPLES_PER_LEVEL = 40
LEVELS_PER_MEASURE = 2232
21.62 mOhm
21.59 mOhm
21.63 mOhm
21.60 mOhm
21.63 mOhm
21.67 mOhm
21.68 mOhm
21.72 mOhm
21.77 mOhm
21.72 mOhm
21.76 mOhm
21.75 mOhm
21.74 mOhm
21.61 mOhm
21.59 mOhm
Nice!
Is this with the internal 10bit ADC of ATmega328, or with the 18bits ADC?
MEASURE_TIME = 0.99
SAMPLES_PER_LEVEL = 40
LEVELS_PER_MEASURE = 222
21.09 mOhm
21.10 mOhm
21.06 mOhm
20.98 mOhm
20.99 mOhm
20.98 mOhm
21.11 mOhm
21.00 mOhm
21.01 mOhm
21.00 mOhm
21.03 mOhm
21.04 mOhm
21.06 mOhm
20.99 mOhm
21.04 mOhm
20.98 mOhm
21.00 mOhm
21.11 mOhm
21.06 mOhm
21.04 mOhm
20.99 mOhm
21.02 mOhm
21.06 mOhm
21.07 mOhm
21.04 mOhm
21.10 mOhm
21.08 mOhm
21.10 mOhm
20.98 mOhm
21.12 mOhm
21.02 mOhm
21.08 mOhm
/*
Version 2.0 (07/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.
*/
const int PIN_PWM_OUT = 3;
const int PIN_ANALOG = A6;
const int PIN_ANALOG_MUX = 6;
#define CLK_BOARD 16000000
#define UART_BAUDS 115200
#define TIMER0_PRESET 27 // Must be >= 27
#define TIMER0_FREQ (CLK_BOARD / (((TIMER0_PRESET) + 1) * 64)) // 9250
const float MEASURE_TIME = 1; // Seconds
const long SAMPLES_PER_LEVEL = 6; // ADC samples per output level. must be an even number.
const long LEVELS_PER_MEASURE = 2 * (long)(((MEASURE_TIME) * (TIMER0_FREQ)) / (2 * SAMPLES_PER_LEVEL));
const float BOARD_CALIBRATION = 8.0; // Converts measure to milliohms
volatile long adc_acc_inphase;
volatile long adc_acc_quadrature;
volatile unsigned char adc_enable;
volatile unsigned char adc_measure_end;
volatile unsigned int adc_value;
volatile unsigned long adc_samples;
volatile unsigned int level_state;
volatile unsigned char level_state_old;
float resistance_inphase;
float resistance_quadrature;
void setup() {
Serial.begin(UART_BAUDS);
Serial.print("MEASURE_TIME = ");
Serial.print(1.0 * (SAMPLES_PER_LEVEL) * (LEVELS_PER_MEASURE) / (TIMER0_FREQ));
Serial.println(" s");
Serial.print("MEASURE_FREQUECY = ");
Serial.print((TIMER0_FREQ) / (2.0 * SAMPLES_PER_LEVEL));
Serial.println(" Hz");
Serial.print("SAMPLES_PER_LEVEL = ");
Serial.print(SAMPLES_PER_LEVEL);
Serial.println(" Samples");
// Set up output reference signal pin
pinMode(PIN_PWM_OUT, OUTPUT);
// Set up peripherals
timer0_setup();
adc_setup();
// Main Loop
adc_init();
for (;;) {
if (adc_measure_end == 1) {
resistance_inphase = adc_acc_inphase;
resistance_quadrature = adc_acc_quadrature;
resistance_inphase *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));
resistance_quadrature *= (BOARD_CALIBRATION / (SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE));
Serial.print(resistance_inphase, 2);
Serial.print("\tmOhm R \t");
Serial.print(resistance_quadrature, 2);
Serial.println("\tmOhm Zc");
adc_init();
}
}
}
void loop() {}
void adc_init(void) {
cli();
PORTD |= (1 << PIN_PWM_OUT);
level_state = 0;
delayMicroseconds(100);
adc_acc_inphase = 0;
adc_acc_quadrature = 0;
adc_samples = 0;
adc_measure_end = 0;
adc_enable = 1;
sei();
}
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) | (0b111);
ADCSRB = 0x00;
sei(); //allow interrupts
}
void timer0_setup(void) {
cli(); //stop interrupts
//set timer0 interrupt at 2kHz
TCCR0A = 0; // set entire TCCR2A register to 0
TCCR0B = 0; // same for TCCR2B
TCNT0 = 0; //initialize counter value to 0
// set compare match register
OCR0A = TIMER0_PRESET;
// turn on CTC mode
TCCR0A |= (1 << WGM01);
// Set CS01 and CS00 bits for 64 prescaler
TCCR0B |= (1 << CS01) | (1 << CS00);
// enable timer compare interrupt
TIMSK0 |= (1 << OCIE0A);
sei(); //allow interrupts
}
ISR(TIMER0_COMPA_vect) {
if (adc_enable) {
// ADC Start Conversion
ADCSRA |= (1 << ADSC);
delayMicroseconds(12); // Wait for Sample and Hold
// Read and accumulate old ADC value
if (adc_samples) {
// Read ADC old measure
adc_value = ADCW;
// Add measure to accumulator
if (level_state_old < SAMPLES_PER_LEVEL) {
adc_acc_inphase -= adc_value;
}
else {
adc_acc_inphase += adc_value;
}
if ((level_state_old >= 0.5 * SAMPLES_PER_LEVEL) && (level_state_old < 1.5 * SAMPLES_PER_LEVEL)) {
adc_acc_quadrature -= adc_value;
}
else {
adc_acc_quadrature += adc_value;
}
}
adc_samples++;
// Update next state
level_state_old = level_state;
level_state++;
if (level_state >= (2 * SAMPLES_PER_LEVEL)) {
level_state = 0;
}
// Update output reference signal
if (level_state < SAMPLES_PER_LEVEL) {
PORTD |= (1 << PIN_PWM_OUT);
}
else {
PORTD &= ~(1 << PIN_PWM_OUT);
}
// Take one measure after several samples
if (adc_samples > SAMPLES_PER_LEVEL * LEVELS_PER_MEASURE) {
adc_measure_end = 1;
adc_enable = 0;
}
}
}
MEASURE_TIME = 1.00 s
MEASURE_FREQUECY = 744.00 Hz
SAMPLES_PER_LEVEL = 6 Samples
485.72 mOhm R -0.18 mOhm Zc
485.68 mOhm R -0.26 mOhm Zc
485.81 mOhm R -0.21 mOhm Zc
486.26 mOhm R -0.21 mOhm Zc
486.30 mOhm R -0.29 mOhm Zc
486.78 mOhm R -0.19 mOhm Zc
487.34 mOhm R -0.22 mOhm Zc
487.63 mOhm R -0.28 mOhm Zc
487.70 mOhm R -0.27 mOhm Zc
487.77 mOhm R -0.31 mOhm Zc
487.68 mOhm R -0.25 mOhm Zc
487.91 mOhm R -0.29 mOhm Zc
487.96 mOhm R -0.28 mOhm Zc
488.01 mOhm R -0.24 mOhm Zc
487.99 mOhm R -0.24 mOhm Zc
487.98 mOhm R -0.25 mOhm Zc
488.02 mOhm R -0.20 mOhm Zc
488.25 mOhm R -0.25 mOhm Zc
488.23 mOhm R -0.24 mOhm Zc
488.44 mOhm R -0.31 mOhm Zc
488.39 mOhm R -0.28 mOhm Zc
488.41 mOhm R -0.26 mOhm Zc
488.35 mOhm R -0.32 mOhm Zc
MEASURE_TIME = 1.00 s
MEASURE_FREQUECY = 744.00 Hz
SAMPLES_PER_LEVEL = 6 Samples
88.13 mOhm R 130.24 mOhm Zc
108.88 mOhm R 161.26 mOhm Zc
108.79 mOhm R 161.40 mOhm Zc
108.70 mOhm R 161.41 mOhm Zc
108.73 mOhm R 161.39 mOhm Zc
108.71 mOhm R 161.38 mOhm Zc
108.87 mOhm R 161.48 mOhm Zc
108.74 mOhm R 161.47 mOhm Zc
108.74 mOhm R 161.47 mOhm Zc
108.75 mOhm R 161.36 mOhm Zc
108.76 mOhm R 161.42 mOhm Zc
108.75 mOhm R 161.52 mOhm Zc
108.78 mOhm R 161.48 mOhm Zc
108.83 mOhm R 161.53 mOhm Zc
108.76 mOhm R 161.51 mOhm Zc
108.80 mOhm R 161.44 mOhm Zc
108.83 mOhm R 161.44 mOhm Zc
108.69 mOhm R 161.44 mOhm Zc
108.88 mOhm R 161.45 mOhm Zc
108.81 mOhm R 161.49 mOhm Zc
108.91 mOhm R 161.40 mOhm Zc
108.92 mOhm R 161.49 mOhm Zc
108.89 mOhm R 161.64 mOhm Zc
108.92 mOhm R 161.53 mOhm Zc
109.02 mOhm R 161.53 mOhm Zc
108.91 mOhm R 161.44 mOhm Zc
108.93 mOhm R 161.55 mOhm Zc
108.95 mOhm R 161.55 mOhm Zc
108.94 mOhm R 161.56 mOhm Zc
108.99 mOhm R 161.63 mOhm Zc
65534
65533
65532
65533
65534
65532
65533
65533
65533
65532
65534
65532
65532
65533
65534
65532
65533
65533
65533
65533
65533
65533
65534
65532
65533
I am only getting the first 16 bits, discarding the last two bits.