I’ve been working with the A/D converter on the Atmel Xmega AVR processors, and it’s been quite the handful. At this point in time (Jan 2010) the parts have some very serious issues, and I’ve lost a lot of time trying to get them to work as advertised. This posting is to share what I’ve found, and the best solutions I’ve determined to get the Xmega A/D converters to work.

If you haven’t already done so, you need to read Atmel application note AVR1300, “Using the XMEGA ADC”, as well as use the source code provided with that app note.

You also need to read the errata for the Xmega. The errata is found towards the bottom of the Xmega datasheet. I should point out that the errata only gives you a hint of the problems with the part, but at least Atmel acknowledges there are some big issues. I’ve been working a lot with Atmel’s tech support, and I must say they’ve been helpful (to the extent they can be) and responsive. Supporting a broken part can’t be easy for them either. They have told me that Atmel is working on a silicon revision to fix these problems, but they haven’t been able to tell me when it might see the light of day.

OK, here we go. The code snippets below are from my code; you’ll need to edit them slightly to suit your needs. But they will show you which AVR1300 functions to use.

Singled-Ended Vs Differential

The Xmega is a differential 12-bit ADC that can be used in either singled-ended or differential mode. Initially I’d been using it in single-ended mode, just because it’s easier, and I’d been getting errors of magnitude well in excess of 30 counts (LSBs). Obviously that’s enormous. In fact the errata implies that errors of up to 60 LSBs are possible. That’s almost 6 bits! At that point it’s closer to a random number generator than an A/D converter…

The best thing to do here is use differential mode. I found the best results came from tying one of the external ADC pins to GND, and using that as the “negative” input for the converter. You want to do this with no gain. Using gain only adds more error. So your code would look something like this (change your pin names as appropriate):

ADC_Ch_InputMode_and_Gain_Config(&ADC_BK.CH0, ADC_CH_INPUTMODE_DIFF_gc, ADC_DRIVER_CH_GAIN_NONE); 	// differential mode, no gain
ADC_Ch_InputMux_Config(&ADC_BK.CH0, pin, ADC_CH_MUXNEG_PIN1_gc);			 // + & - inputs

It’s possible to use the internal GND reference, but my experiments seemed to favour using an external pin tied to GND.

Xmega ADC Reference Voltage

The part has an internal 1.00V reference voltage, that you’re supposed to be able to use as the reference voltage for the A/D. In practice this doesn’t work. The errata suggests this, and suggests the 1.00V reference is too low a voltage for the ADC to function properly.

I suspect the best solution is to provide a buffered external stable reference voltage of around 2V and feed it into the part via its external reference pin. In my case this wasn’t an option so I had to settle for something else. The next best option is to use the internal setting of VCC/1.6 as the ADC reference. In such a case your code will look something like this:

ADC_Reference_Config(&ADC_BK, ADC_REFSEL_VCC_gc); 		// use Vcc/1.6 as ADC reference

With a 3.3V VCC, this gives a reference of 2.06V. The problem here is that the reference for an A/D is assumed to be stable. Very stable. That’s hard to claim for a VCC line, which may have all kinds of noise on it. So this falls into the “do the best you can” camp, but it’s still better than using the internal 1.00V.

If you don’t know what VCC is (perhaps you need to measure it), how do you accomplish that when VCC/1.6 is your ADC reference? The only way I know is by getting the ADC to sample the internal bandgap voltage. Something like this:

ADC_Ch_InputMode_and_Gain_Config(&ADC_BK.CH0, ADC_CH_INPUTMODE_INTERNAL_gc, ADC_DRIVER_CH_GAIN_NONE);
ADC_Ch_InputMux_Config(&ADC_BK.CH0, ADC_CH_MUXINT_BANDGAP_gc, 0);

You’ll want to calibrate this yourself. The datasheet says the bandgap is 1.1V. I’ve found it’s a little less than that – more like 1.08V. There’s no way to put a multimeter on it directly, but you can get the ADC to measure it, and you can of course measure your VCC voltage, so that tells you what the bandgap actually is. Note that according to the errata, you cannot measure the bandgap if your VCC is below 2.7V, so in that case you’re probably out of luck, and you’d better look again at providing an external ADC reference voltage.

Note there is an error in the errata. (Yes, Atmel needs an errata for their errata….). Errata #4 talks about using the A/D to measure the 1.00V internal voltage reference. Atmel tech support has confirmed this is not possible – the errata is wrong. The only “reference” you can measure is the bandgap.

Signed vs Unsigned Mode

The Xmega has a 12-bit A/D. It can be run in either signed mode, or unsigned mode. If you’re measuring a simple single-ended input referenced to ground (which most folks do), the way to get the full 12-bits of resolution is to use unsigned mode. In unsigned mode, the “negative” input of the ADC is tied to an internally generated voltage which is at the approximate midpoint of the ADC reference voltage. The problem is this internally generated voltage is very unstable, which has the effect of producing widely varying ADC outputs for a stable input voltage.

The solution is to use signed mode. In signed mode, the “negative” ADC input is under your control, and should be routed to a external pin which is tied to GND (as per “Singled-Ended vs Differential” above). Signed mode is selected much like this:

ADC_ConvMode_and_Resolution_Config(&ADC_BK, ADC_ConvMode_Signed, ADC_RESOLUTION_12BIT_gc);

In signed mode, with a simple single-ended input referenced to ground, you effectively have an 11 bit converter, not a 12. Output values will typically be in the range of 0 – 2047 (0 – 0x7ff).

ADC Sampling Speed

For measuring internal signals you need to be running the ADC below 100 kHz. I’ve tried a bunch of different sampling speeds, and 62 kHz seems to be the sweet spot. If you’re using the internal 2 MHz CPU clock, this is an ADC prescaler of 32. Like so:

ADC_Prescaler_Config(&ADC_BK, ADC_PRESCALER_DIV32_gc);

ADC Offset

I’ve struggled with this one. The AVR1300 app note & example code shows a simple method of measuring the ADC’s DC offset, and subtracting it from every ADC measurement. I’ve found the DC offset to be relatively small: around -4 in my case. Yet when I try to “correct” my ADC measurements by subtracting it out, it makes my measurements less accurate, not more.

It almost makes me wonder if it’s not a real DC offset, but rather some artifact of the nonlinearity problems currently inherent to the ADC.

After much experimenting, I’ve decided not to use it. What I do is measure the ADC offset when the code starts up, and I print it out for informational purposes. But then I set the offset to zero, and leave it there. So I’m not using the offset (offset = 0 in my code) and this seems to produce better ADC results.

ADC Calibration Bytes

The AVR1300 code shows how to read the ADC calibration bytes into the appropriate ADC register. Like so:

ADC_CalibrationValues_Set(&ADC_BK);

I’ve found it doesn’t seem to make any real difference. I do indeed do it, but commenting this line out doesn’t appear to make a meaningful difference.

Averaging & Clipping

Averaging ADC measurements certainly helps reduce their sample-to-sample variability. To take an ADC measurement, I take 4 and average them. This is well worthwhile.

The other thing I do is clip them to zero. Because I’m measuring single-ended voltages, a negative voltage is not physically possible (or desired). So after my average of 4 ADC measurements, if the result is less than zero, I make the result zero. This helps in my code later, because I know I don’t need to deal with negative numbers. Otherwise, because my ADC appears to have a slight negative “offset”, measuring a grounded pin can result in an ADC measurement of -3 for example, which is not meaningful. So something like this gets clipped to zero.

Summary

To summarise a long post on the care & feeding of a currently rather broken Xmega ADC, here are my hard-earned suggestions:

  • Use Differential mode / differential inputs. Tie an external ADC pin to GND if necessary.
  • Use either VCC/1.6, or an external reference voltage, for the ADC reference. Not the internal 1.00V. Use the ADC to measure the bandgap if that helps.
  • Use signed mode.
  • Consider using a 62 kHz ADC sampling rate.
  • Consider assuming the ADC offset is zero.
  • Average multiple ADC measurements (and clip if it makes sense in your application)

Good luck! Let’s hope Atmel releases fixed silicon really soon.

Update: In late 2011 Atmel started shipping a revised line of Xmega parts called the “U” parts. The new partnumbers end with a U. For example ATXMEGA32A4U. Although I haven’t personally tried these new devices, apparently these U parts have the ADC problems fixed. I believe the non-U parts still have the ADC problems. If you’re going to be using an Xmega for something, I’d suggest looking for a U part.