Oversampling to enhance ADC resolution
Apr
3
Written by:
4/3/2012 11:24 PM
The method implemented in the application note is called Oversampling and Decimation, and while is a complex theorem, the usage on this scale can be managed and implemented quite easily, so that’s good news! Lets take a quick look at the basic parts and then apply it to some code after learning about the process a bit.
Sampling Frequency:
According to the Nyquist theorem in order to reconstruct a signal properly we must sample it at least twice as fast. When applied to the 10bit ADC in the AVR family, we get an effective range of 50kHz-200kHz. Putting our upper bandwidth threshold of the signal at ~8kHz. This is an important consideration to keep in mind(Although AVR ADC can go up to 1MHz, it comes at a much lower ENOB). Luckily for our signal this doesn’t matter much, the signal we will interface with is a nice and slow one!
Oversampling:
Now that we understand the limits in place due to sampling frequency and hardware limitations, we can form a better picture of this technique. In short we need to take a large number of samples and add them together, the number of oversamples determines the increased bit resolution. For ever desired bit of resolution the signal must be sampled 4 times. As you can see this can put a strain on our frequency limits very quickly, and therefore must be used wisely. For just 2 more bits of resolution we must sample the signal an extra 16 times.
Noise:
In order for this method of increased resolution to work properly there needs to be some noise in the signal. The signal itself must also be stable during conversion. On the surface this looks strange since we want noise, but also stable signal, the whole idea is to get the LSBs to twitch a bit, and allows us to lock onto variations that avoid reading in the same value (turning this into normal averaging) over and over. An added benefit to this method is spreading the noise across a larger binary number, effectively increasing our Signal to Noise ratio as well as our ENOB.
Averaging:
Now that we have a given sample set, at a set frequency and a noise base that allows this method to work, how do we turn this into a meaningful number that we can use? This is where the Decimation or Interpolation portion comes into play. Unlike the normal averaging method where x samples are added then divided by x, decimation allows for an increase of resolution, by bit shifting to the right to scale accordingly (1 shift right is like dividing by a factor of 2). This handy trick gives us the power of the Moving average as well as an increase in resolution. When 16 10bit numbers are added together the result is a 14bit number with the last 2 bits expected to hold null data, to get “back” to 12bits we need to scale this number by a factor(SF=2^n, n=desired bit increase). In our 12bit case we would need to shift twice(first shift is a power of 2 and second would be 4, 2^2 =4).
So by taking 16 10bit samples in a desired sampling frequency range, adding these numbers together and bit shifting to the right twice we have created a 12 bit number and spread our noise across a 14bit. This is an effective way to increase the resolution as shown above, now lets apply it to some code and take a 12bit reading from a 10bit ADC!
#define BUFFER_SIZE 16 // For 12 Bit ADC data
volatile uint32_t result[BUFFER_SIZE];
volatile int i = 0;
volatile uint32_t sum=0;
void setupADC(uint8_t channel, int frequency)
{
cli();
ADMUX = channel | _BV(REFS0);
ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADATE) | _BV(ADIE);
ADCSRB |= _BV(ADTS2) | _BV(ADTS0); //Compare Match B on counter 1
TCCR1A = _BV(COM1B1);
TCCR1B = _BV(CS11)| _BV(CS10) | _BV(WGM12);
uint32_t clock = 250000;
uint16_t counts = clock/(BUFFER_SIZE*frequency);
OCR1B = counts;
TIMSK1 = _BV(OCIE1B);
ADCSRA |= _BV(ADEN) | _BV(ADSC);
sei();
}
ISR(ADC_vect)
{
result[i] = ADC;
i=++i&(BUFFER_SIZE-1);
for(int j=0;j
{
sum+=result[j];
}
if(i==0)
{
/****DEAL WITH DATA HERE*****/
sum = sum>>2;
//Serial.println(sum,DEC);
pHRaw = sum;
}
sum=0;
TCNT1=0;
}
ISR(TIMER1_COMPB_vect)
{
}
Not only are we oversampling but this code creates a Timer triggered ADC port that gets us an accurate sampling frequency and allows us some space in our main loop to do some work with our results! There really inst much to this in terms of the oversampling, We fill a 16 sample wide buffer, once full we add the the results together and bit shift the sum 2 places to get us scaled back to 12bits, Pretty neat! For more detail about interrupts, ISRs and timer triggered ADCs check out the Arduino and AVR forums!
As a last step lets plug this code into one of our pH units and see how it performs, Instead of 1024 steps our new result will be 4096steps and we’ll expect numbers like 2048 instead of 512!
1 comment(s) so far...
Re: Oversampling to enhance ADC resolution
Sir, could you please explain how or why you assign clock= 250000 and is the timer set to give a constant noise to AREF pin as described in the avr121 application note?? And can i implement this program with minor changes to over sample to 16 bit resolution on a atmega32 chip??
By S Gowri Shankar on
1/19/2013 9:40 AM
|