ADC calibration and averaging lib?

Hello everyone!

I'm working on battery analyzer project that samples lots of analog data. The data is passed over various voltage dividers, op-amps, etc stuff, so it's virtually impossible to predict and match the analog values in code, and also values differ a little for different sensor PCB due to non-equal components.

The thing I noticed experimenting with my trusted data and sampled ADC values (in Excel), is that you can simply catch 2 ADC values (one at min signal range, and other at max signal range), take corresponding analog values, and use a simple multiply/shift formula to get real sensor value from raw ADC data. The accuracy for my hardware is perfect, about 0.1% to 0.5%.

So I started with IDE and actually already coded some code for acquiring this cal data, and routines to do the right math for me.
I can get the awesome analog values now, but I'm a little bored coding all that corresponding stuff like storing cal data in EEPROM, catching exceptions (lower-than-low ADC, and higher-than-high ADC), optimizing code/RAM usage, averaging value from 1000 ADC reads, etc.

So am I reinventing the bicycle and just missing some great Arduino library for that tasks?

Welcome!,

I think it is part of the job to do some things over again. Are there parts that are "trouble"? You can post the code and the forum can help

Well, nobody actually interfaces LCD or SD card each time writing the code from scratch, that's why lazy people invented libraries :slight_smile:

Getting the right values from ADC and calibrating the data seems to turn out in some dozens line of C-code, that's why I was wondering if some good library already exists for this, just to free the head for higher-level tasks than just coding that down :slight_smile:

Ok, I'll finish and fine-tune my code and upload it here in a while if someone is interested :slight_smile:

Always interested in code :wink:

and this for averaging - Arduino Playground - Statistics -

or this - Arduino Playground - HomePage -

Thank you for reference.
With my love to huge code that simple lib already exceeds 200 lines, no EEPROM yet :slight_smile:
Actually, it turned out that I needed "Analog" to PWM-driven DAC conversion and calibration also, so now the lib works both ways:
getting analog sensor value from raw ADC data and putting right PWM output for desired analog value. I'm surprised with precision once carefully calibrated.
I think it will take some time for me to clean the code and make this universal and reusable.

Just to keep topic alive, I'd like to share my code. It works pretty ok (looks a little ugly however), so I don't feel that I would change anything.
Attached is main routine include file, ADCCal.h.

The library is very simple and only does 2-point linear calibration, so corrects for shift and gain. It does not include any nonlinear or table lookup routines. Library outputs the data even beyond that points. So if you calibrate "min" value as 0.5V, it will give you 0.1V, but will set the flag that the value is outside the calibration boundaries and unreliable.

How to use:

  1. Declare everything. In my case there is an array of 2 calibrations, one for input voltage (ADC), second for output current (DAC):
#define CALDATA_VOLTAGE 0
#define CALDATA_DAC_CURRENT 1

ADC_DAC_CALIBRATION ADCcalData[2];
  1. Load/Save from eeprom (thanks to robtillaart). Here with unnecessary Serial output of loaded parameters:
	for (int i=0;i<1;i++)
	{
	EEPROM_readAnything(i*sizeof(ADC_DAC_CALIBRATION)+1, ADCcalData[i]); //don't save to "0" position
		Serial.print(F("READ CAL DATA:"));
		Serial.println(i);

		Serial.print(F("MIN: Analog:"));
		Serial.print(ADCcalData[i].m_iMinAnalogVal);

		Serial.print(F("MIN: Digital:"));
		Serial.println(ADCcalData[i].m_iMinDigitalVal);

		Serial.print(F("MAX: Analog:"));
		Serial.print(ADCcalData[i].m_iMaxAnalogVal);

		Serial.print(F("MAX: Digital:"));
		Serial.println(ADCcalData[i].m_iMaxDigitalVal);
	}
	Serial.println(F("DONE LOADING EEPROM"));
  1. You need some calibration routine to get the data. That's the most weird code. In my device, it's made by interacting with user:
	while(!keyboard.available()){};	keyboard.read();

	Serial.println("CONNECT 5.0V PSU, set 5.00V, and press any key");
	while(!keyboard.available()) {};	keyboard.read();
					
	unsigned long ulADCData=0;
	for (int i=0;i<1000;i++)
	{
		ulADCData+=analogRead(VOLTAGE_ADC_PIN); //Reading voltage for 5 secs.
		delay(5);
	}
	ADCcalData[CALDATA_VOLTAGE].m_iMaxAnalogVal= 5000; //5000mV is 5.00V 
	ADCcalData[CALDATA_VOLTAGE].m_iMaxDigitalVal = ulADCData/1000; //in ADC reads, that's what we've read.

The same calibration should be done for "low" ranges.
So, to do the full calibration, user should fill in 4 variables:

m_iMaxAnalogVal = 5000;  //5.00V is the max what we sense. Struct is in millivolts, so 5000mV.
m_iMaxDigitalVal = ADCData; //Whatever ADC read when 5.00V was connected. 
m_iMinAnalogVal= 500; //0.5V is our min range that we sense. 500mV is 0.5V.
m_iMinDigitalVal = ADCData; //Whatever ADC read when 0.5V was connected.

For the DAC data, all is pretty the same: you output some digital (PWM,DAC,...) signal by Arduino, and measure it with scope, ruler or DMM.
Both for "High" range of signal and "Low" range of signal. Then you fill in MaxDigital and MinDigital with known output data, and fill in MaxAnalog and MinAnalog with what you've measured.

Saving to EEPROM is straightforward:

	Serial.println(F("SAVING CAL DATA TO EEPROM"));
	for (int i=0;i<1;i++)
	{
		EEPROM_writeAnything(i*sizeof(ADC_DAC_CALIBRATION)+1, ADCcalData[i]); //don't save to "0" position
	}
	Serial.println(F("DONE"));

And now the usage of lib. Simple and easy.

For ADC:

unsigned int iADC0=analogRead(VOLTAGE_ADC_PIN); //Read Analog voltage.
Serial.println(ADCcalData[CALDATA_VOLTAGE].GetAnalog(iADC0));

For DAC:

unsigned int iDischargeCurrent = 10000; //it's 10A analog value
DACOutput(ADCcalData[CALDATA_DAC_CURRENT].GetDigital(iDischargeCurrent));

I would be happy if this code would be useful for someone, and appreciate if someone makes a candy from that :slight_smile:

ADCCal.h (5.21 KB)