I want to read a DC voltage from a battery with 0.1mV resolution or better and send the value to a PLC based in Arduino Mega. I selected an ADS1115 (datasheet here) which has enough resolution to do that. I already created a question in the past (this one), which was very helpful as I improve the reading a lot by doing 2 things: connecting the ground to the negative pole of both the battery/ADC input (to assure common ground for differential mode), and isolating the digital part (to avoid digital currents getting to the ADC input by ground loops). However, I still have some ocassionaly oscilations in the reading as I will explain in detail. The final set-up after the answers from the previous question looks like this:
ADS1115 is from Adafruit which break out board is mounted on a breadboard. ADUM1250 is used in its evaluation board and connected to the breadboard. Last but not least, the BTS is a battery test system connected to the battery which has two functions:
- Battery cycling. It injects a constant current (100 mA) in the following way: it starts injecting +100 mA to charge the battery, and after some minutes it injects -100mA to discharge the battery (and the process repeats several times). This is done by OUT connection, using both positive and negative cables to the battery poles respectively.
- DC voltage measurement. The BTS also provides an stable and correct measurement with no oscillations (using IN connection, also using positive and negative), which is used as a reference to compare with my ADC measurement. I also did the measurements with a multimeter which matches perfectly the BTS value. So it is clear that the problem is in the ADC reading.
My feeling is that there is some voltage drift entering the ADC. To give more tips about the oscillation that maybe can help to identify the issue, the following table shows an example of the it compared with the BTS/multimeter reading. ADC datarate is set to the maximum value (860 samples per second) and then the PLC stores the last value in a variable and prints it every 2 seconds to a file:
These values are during a discharge cycle (voltage always decreases along time). As the time goes on (epochs every 2 seconds), BTS always reads an smaller value (-0.0004V or -0.0003V compared with the previous one). However, the ADC occasionally reads +0.0001V (epoch marked in red color), which is a larger value than the previous one, and this mess up completely my further computations. I need a clear signal, it is not a solution for me to filter by software.
What could be the cause for this ocassional oscillations? They always happen in the opposite direction of the cycling: if the battery is being discharge, voltage goes up, but in the battery is being charge, the voltage goes down. Besides, there is an offset around 60mV on the ADC readings compared with the reference readings which I cannot explain. This offset does not affect my computations but maybe gives some clue of the problem.
I tried to replace the ADS1115 by an ADS1219 but the results were the same so it seemts that something in the ADC inputs or outputs is still affecting.
Find also the code related with the ADC reading and value printing. One timer interrupt is used to do an average of 4 values (FIR filter) and other time interrupt to print the value every 2 seconds:
#include <TimerOne.h>
#include <TimerThree.h>
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
const int AVGNUMELEM = 4;
float ADCValue ;
float arrayAvg[AVGNUMELEM];
float avg = 0;
int counteravg = 0;
const float scalefactor = 0.0001250;//ref 4V
const int PCSERIALSPEED = 9600;
const int MILLISINSEC = 1000;
//2 seconds
const long TIMER1TIME = 2000000;
//0.5 seconds
const long TIMER3TIME = 500000;
// Pin output of the PLC
int DIGITALOUTPUT = 42;
Adafruit_ADS1115 ads1115_battery;
// Timer called every 0.5 seconds
void ISR_Store4values() {
if(counteravg < AVGNUMELEM){
arrayAvg[counteravg] = ADCValue;
counteravg ++;
}
else
{
for(int i = 0; i < (AVGNUMELEM-1); i++){
arrayAvg[i] = arrayAvg[i + 1];
}
arrayAvg[(AVGNUMELEM-1)] = ADCValue;
}
}
// Timer called to compute the average every 2 seconds
void ISR_PrintEvery2s() {
avg = 0;
for(int i = 0; i < AVGNUMELEM; i++){
avg = avg + arrayAvg[i];
}
avg = avg / AVGNUMELEM;
Serial.print(avg,4);
}
void setup() {
Timer1.initialize(TIMER1TIME);
Timer1.attachInterrupt(ISR_PrintEvery2s);
Timer3.initialize(TIMER3TIME);
Timer3.attachInterrupt(ISR_Store4values);
pinMode(DIGITALOUTPUT,OUTPUT);
Serial.begin (PCSERIALSPEED);
ads1115_battery.begin();
if (!ads1115_battery.begin()) {
Serial.println (F("Error when init ADC"));
while (1);
}
ads1115_battery.setGain(GAIN_ONE);
// Data rate 860 sps https://github.com/RobTillaart/ADS1X15
ads1115_battery.setDataRate(7);
}
void loop() {
ADCValue = scalefactor * ads1115_battery.readADC_Differential_0_1();
}