NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Strange ADC errors

February 20, 2013
by r010159
r010159's Avatar

Hello!

I am using the ADC of the uC in a familiar way, to measure the output voltage of a temperature sensor. When used as described in the nerdkit documentation, the reading and display of the correct temperature works, as reasonably can be expected. I then implimented the power save features of the uC, using a timer overflow interrupt to update the LCD. This was done using "How to make your battery last longer" note in the nerdkits "How to" library. Here is where the first problem surfaces. The display is updated about every ten seconds, instead of the approximate 60 times a second. Using a blinking LED, I narrowed the problem down to the ISR for the ADC. Does it look like the clock for the ADC is affected by the scaling of the clock the of the counter being used (counter2)?

When I use a different prescale factor for the clock of the ADC, the display updates much more frequently (prescale of 4 instead of 128). This is where the second problem surfaces. The smaller prescale factor I use, the more innacurate the displayed temperature becomes. The displayed temperature starts out as being "accurate". Then over time the value increases to about 20 degrees over room temperatre. This does not happen with a larger prescaling factor for the ADC. And there is nothing warm to the touch on the breadboard. When I use the smallest prescale of 2 for the ADC clock, the temperature reads over 400 degrees! At first I thought it was some thermal noise causing the problem, but the original nerdkit program did not have this problem. And to get a result over 400 degrees is surprising.

Any thoughts on what is causing this problem?

Bob Graham

February 20, 2013
by scootergarrett
scootergarrett's Avatar

You didn’t turn off the ADC when you put it in power save mode did you? Simple stuff first

February 20, 2013
by r010159
r010159's Avatar

No, I did not turn ADC of when in power save mode. I made sure the ADC remained "on". And I forgot to mention that I have onr ISR for obtaining temperature readings from the ADC, and the other ISR keyed off of the internal counter to display the results.

Thank you!

Bob Graham

February 21, 2013
by r010159
r010159's Avatar

I have done some additional testing. It looks like the displayed temperature does go up over time with any prescaling factor used. And this also applies to the Counter2 clock, besides the scaling of the ADC clock. I did not notice this before because initially the temperature updates were so infrequent. What is interesting is that I had a version of this program working without the rising temperature. The only difference is that I moved the code to display the temperature from the main routine to an ISR that is invoked on Counter2 overflow.

Bob Graham

February 21, 2013
by Ralphxyz
Ralphxyz's Avatar

I moved the code to display the temperature from the main routine to an ISR that is invoked on Counter2 overflow

Not that this will help, but you should never have your LCD routine in the ISR.

You want the ISR to take the lest time possible.

Ralph

February 21, 2013
by r010159
r010159's Avatar

Well, I narrowed the problem down to sleep mode. The two ISRs work perfectly together. Ralphxyz makes a good point on not performing time labored operations in an ISR. However, this is the only way I can utilize power save mode to see how it works as an expirament. When the uC is put to sleep, both ISR routines slow way down. And innacurate temperature readings occur with the paculiarity of rising as time goes by.

I do not see where there is a problem with my code. And I have implemented power save mode as documented in the "How to" document, with the exception of leaving the ADC peripheral on and functioning. All of this iappears to be simple to implement. I will next try out the different power save modes. I daresay there may be a problem on the 328P with its power save mode. This is unlikely, but it has me thinking.

Bob Graham

February 21, 2013
by Noter
Noter's Avatar

More likely there is still a problem in your code because events are not happening in the sequence you expect. Interrupts can be difficult to understand at times.

A handy way to get slow stuff like your display code out of the ISR is to simply set a state flag in the interrupt routine and then check/process/clear it in you main loop.

February 21, 2013
by r010159
r010159's Avatar

Thanks for the suggestion in implementing a state flag in the ISR. Then all I would need to do is wake up the CPU to have the main routine print the temperature info, followed by another SLEEP instruction.

I did find out something important. According to the table documenting the power down modes, the ADC is not supported in power save mode. The ADC is only supported in the Idle and ADC Noise Reduction modes. This is backed up by further documentation descrbing each power down mode. So even though in power save mode the ADC appears to function, it actually is not supported in the power save mode that I had selected. It may cause the events to not operate in the sequence that I expected.

So now I power down the peripherals except for ADC using the Power Reduction Register. I then implement ADC noise reduction mode, which is listed as one of the power down modes. The side effect is I may get better temperature readings by the ADC. And it is now working properly.

Bob Graham

February 21, 2013
by Noter
Noter's Avatar

I am using sleep to extend battery life in a project now that uses the UART and Timer1 interrupts. Before my main loop I insert these statements:

// power reduction
PRR = _BV(PRTWI)            // TWI
    | _BV(PRTIM2)           // Timer2
//  | _BV(PRTIM1)           // Timer1
    | _BV(PRTIM0)           // Timer0
    | _BV(PRSPI)            // SPI
    | _BV(PRADC)            // ADC
//  | _BV(PRUSART0)     // USART
    ;

// disable digital inputs
DIDR0 = _BV(ADC0D)
    | _BV(ADC1D)
    | _BV(ADC2D)
    | _BV(ADC3D)
    | _BV(ADC4D)
    | _BV(ADC5D)
    ;
//  DIDR0 = _BV(AIN1D)
//  | _BV(AIN0D)
//              ;

//  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
//  set_sleep_mode(SLEEP_MODE_PWR_SAVE);
    set_sleep_mode(SLEEP_MODE_IDLE);

I keep them all there and just comment out the ones I am using. Then at the bottom of my main loop is where I sleep -

while(true){
// ... whatever your program does here
// ... in my case read a character from the uart if ready
// ... and process accordingly. Then check/process/clear flags
// ... set by timer1. Then sleep again.
  sleep_enable();
  sleep_cpu();
  sleep_disable();
}

It's easiest to get your program working using the interrupts and then add the power saving settings and sleep to it. The sleep modes and power saving settings are all documented very well in the 328P spec. If you haven't reviewed those chapters yet look them over too.

February 21, 2013
by Noter
Noter's Avatar

By the way, I've tried all sorts of thing to get more accurate readings from the ADC and none really make much of a difference except it will get a lot worse with more samples/sec. What I found worked best was a combination of oversampling and simple averaging. Then the results were quite amazing.

February 22, 2013
by r010159
r010159's Avatar

Thanks for all of the information. You,as usual, have been very helpful. I did get the ISRs working before implementing sleep mode as a way of debugging the problem. I should have done this while putting it together in the first place. However, I did not disable the digital inputs. I saw those functiond, like sleep_cpu declared in the "sleep" header file. I see where it provides more control over the process then just using a call to sleep_mode. Interesting that the temperature readings become worse at a higher samples/sec.

Bob Graham

April 08, 2013
by mark_p
mark_p's Avatar

I know this is a little bit of an older thread, but I was playing with the ADC recently and have some advice. You continue to say that you have more inaccurate readings at higher ADC clock frequencies. In the ATmega168 datasheet under the ADC section it says:

"By default, the successive approximation circuitry requires an input clock frequency between 50 kHz and 200 kHz to get maximum resolution. If a lower resolution than 10 bits is needed, the input clock frequency to the ADC can be higher than 200 kHz to get a higher sample rate." Pg. 248 of 376 in my PDF copy.

Essentially what you are doing by dropping the prescale number is raising the ADC clock past something that it can accurately use. With the standard nerdkits configuration (14.74 MHz clock), you need the pre-scale number to be 128 for the most accurate reading. Anything above 200kHz is going to give you less than 10 bits of accuracy. And with temperature gauge, and without reading your datasheet, I would imagine you need to be as accurate as possible.

So change the ADC pre-scale factor back to 128;

ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

And you will have your accurate reading returned.

April 08, 2013
by Ralphxyz
Ralphxyz's Avatar

Here is a very interesting discussion about ADC jitters.

bretm and Paul (Noter) were actually doing statistical analysis of my data flow.

Ralph

April 08, 2013
by Noter
Noter's Avatar

Here's a fun project, a Homemade DAC A/D Converter.

June 04, 2013
by BobaMosfet
BobaMosfet's Avatar

The ADC is very accurate. In fact, it is not necessary to poll it so many times. It requires proper warm up, a poll, and voila you have a value. In most cases, if you're getting wildly inaccurate readings, it's because you need to disable the JTAG interface (fuses).

You also need to understand how to scale the value. In order to know what voltage is coming in, you need to work the standard ADC equation the other way:

Vin = ADC / (1024 / AVref)

BM

Post a Reply

Please log in to post a reply.

Did you know that hobby servos are controlled through a particular kind of PWM (Pulse Width Modulation) signal? Learn more...