Differential ADC

Started 25th July 2017 - a differential ADC library for Arduino

The ATmega 32u4 features a differential ADC (analogue to digital converter) as well as a programmable gain amplifier. It is used in the Arduino Leonardo and the Pro Micro. Unfortunately on the Pro Micro the relevant pins are not connected and the pin spacing on the chip is so small that soldering to them is nigh on impossible.

Differential ADC is also commonly available on the Arduino Mega 2560 which uses the ATMega 2560 chip. There are 1280 versions of both. There is also an ATMega1284 which has Arduino support and differential ADC.

Photo shows a Pro Mini and a Pro Micro.

Arduino Pro Micro, David Pilling

My efforts to write code came to grief through setting out using a Pro Micro and failing to grasp that ADC0 and ADC1 in the Atmel documentation mapped to A4 and A5 in Arduino-land, which do not exist on a Pro Mini. So my program was never going to work. The only example code I found is listed below [1][2].

The blog library [2] won't compile on modern Arduino and the blogger notes as supplied it does not work entirely correctly. This is because it has had some delay() lines commented out. But by fixing this code and using an Arduino Mega 2560 I actually got good results for differential ADC.

In Arduino-land, one uses identifiers like A0 to refer to an analog pin. Internally A0 is defined as:

static const uint8_t A0 = 18;

There is then a mapping between A0 and analog channels

if (pin >= 18) pin -= 18;

Which gets something like A0 down to the number after the A i.e. 0 - the Arduino channel. Finally there is a mapping between Arduino channels and AVR channels (analogPinToChannel(P)) taking 0 to ADC7 (this mapping only takes place for the 32u4).

const uint8_t PROGMEM analog_pin_to_channel_PGM[12] = {
	7,	// A0		PF7		ADC7
	6,	// A1		PF6		ADC6	
	5,	// A2		PF5		ADC5	
	4,	// A3		PF4		ADC4
	1,	// A4		PF1		ADC1	
	0,	// A5		PF0		ADC0	

taken from pins_arduino.txt

In the AVR data sheet there is a table "Table 24-4. Input Channel and Gain Selections" (ATMega 32u4) and "Table 26-4. Input Channel Selections" (ATMega 2560) which has 64 entries that map to various combinations of ADC channels and gains. The row index number of the entry provides the 6 bit MUX code; the low 5 bits go into the ADMUX register and the high bit goes into the MUX5 bit in the ADCSRB register.

The idea of the blog code is to input a "channel" number to the functions. At best this is a row index number from the data sheet table (in other words it has no independent meaning); for the 32u4 the Arduino to AVR channel mapping in the code means some values can not be obtained.

Below is a download of my differential ADC library and an example program. I have tested this on an Arduino Leonardo and an Arduino Mega 2560. I've supplied code for the ATMega 1284 but it is untested. The second download shows the ADC library being used with the ADC free running and returning results from interrupts. See also my design for a soldering iron controller which uses this library.


Presumably the job of software is to remove hardware dependence, so one should have a function that takes two input channels and the gain value and returns the result. However most of those combinations will not be valid and different processors offer different gains - some things will only work on a given processor.

The first function in the library is:
int analogGetCode(uint8_t pin1,uint8_t pin2,uint16_t gain)
Parameters are two Arduino analog identifers e.g. A0, A1 and the gain. It returns the MUX code for the combination.

int analogGetCodeAVR(uint8_t pin1,uint8_t pin2,uint16_t gain)
Parameters are two AVR ADC channel identifiers e.g. 0, 1 and the gain. It returns the MUX code for the combination.

void analogSetDiffCode(uint8_t code)
Writes the MUX code in the argument into the ADC registers.

int analogReadDiff()
Reads a value from the ADC using the current values in the ADC registers.

int analogReadDiffCode(uint8_t code)
Reads a value from the ADC using the MUX code in the argument.

int analogSignedValue(int value)
Takes the value from the ADC and turns it into a signed value.

int analogVoltageSetup(void)
Prepares for converting signed ADC values into voltages. This caches the number that will be used to do the conversion.

int analogVoltageValue(int value,int base,int gain)
Converts a signed ADC value into a voltage. Results are in mV multiplied by the 'base' parameter, so one could read in microvolts (base=1000). It is also necessary to input the gain used for the ADC reading.

void analogReferenceDiff(uint8_t mode)
The ADC can use different reference voltages. The default is the power voltage of the chip, Vcc. Usually there is an on-chip voltage reference that can be used too. If Vcc is used the analogVoltageSetup() function will attempt to read the value of Vcc using the on-chip reference.

Arduino provides DEFAULT, INTERNAL, INTERNAL1V1, INTERNAL2V56 and EXTERNAL. These are a number 0..3 which provides two bits to go into the REF0 and REF1 bits of the ADMUX register. Consult the processor data sheet.

With a 5 V reference, a gain of 200 and a range of +/- 511 the measurable voltage is +/- 25 mV with a resolution of 0.05 mV.


  1. Atmega32u4 differential input mode - Arduino Forum
  2. Arduino Differential ADC and Gain ADC - Muh Ahsan blog


Click to return to index

Page last modified on September 04, 2017, at 02:31 PM
Powered by PmWiki