ADC unrealiable in ATmega32? - Page 1
Forum section: electronics / beginners

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 02:44:42 pm
content: Hello, I've been following the EEV blog for quite a while, but I just decided to join this community.I'm an EE student, just finished my first year, and I have run into a problem.I'm designing a simple PSU, and I want a LCD displaying the output voltage and current, at the moment I'm on the voltage part of it, but this is where the problem occurs.When I'm in the low ranges (about 1-1.5V) it shoots about 80mV under my fluke, and in the high end (about 26V), it shoots about 350mV above!I could understand it if it was linear mistake, but it's fairly accurate at around 7-13V, but then starts to over- and undershoot. Is the ADC in an ATmega32 unreliable? (I'm taking the average of 3000 samples at the moment, to debug), or is my fluke out of spec? Or what is happening here? Should I get an external 12bit ADC?Thanks for any answers in advance.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: May 25, 2012, 02:57:02 pm
content: Not sure as the info you provided is sparse. But have you by any chance fried the chip?Edit: Where are my manners? Welcome to the forums.

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 03:03:50 pm
content: Quote from: Ed.Kloonk on May 25, 2012, 02:57:02 pmThank you.The chip is brand-spaking new, so I don't hope that it's flawed from the beginning.What's basically happening is that my PSU goes from 0-~30V, I'm running my ADC from a voltage-divider between 100k and 20k, so 1/6*(<30V) = <5V, and then I'm oversampling quite a lot at the moment (I'm taking the average of 3000 samples and divide that number with 1023, then multiplies that with my max voltage).And again, a linear offset would make sense in my head, but since it first undershoots, and then overshoots doesn't make sense to me.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: May 25, 2012, 03:10:09 pm
content: OK. That's good info. So now my problem I have is I have very limited experience with this type of device though I've made many a arduino smoke. A bigger problem is I have consumed an entire bottle of wine.Perhaps we should wait for some one who knows much more about this stuff shows up to help you. Can you hang on for a bit?

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 03:13:47 pm
content: Quote from: Ed.Kloonk on May 25, 2012, 03:10:09 pmOf course.

author: AlfBaz
author_rank: Super Contributor
timestamp: May 25, 2012, 04:23:45 pm
content: Just some quick thoughts.Is the adc sample and hold? The voltage divider may be exceeding source impedance specs of the adc. You may also not be allowing enough time for the internal sampling cap to charge.Another thing to check is the stability of your adc's voltage reference, ie how much does it change over your measurement,

author: fmaimon
author_rank: Unknown
timestamp: May 25, 2012, 06:36:14 pm
content: Try using a capacitor in parallel with the 20k resistor. 100nF is a good start. Or try using an opamp in a voltage follower configuration to lower the impedance. It's higher than the datasheet limit (10k).

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 06:42:48 pm
content: Quote from: AlfBaz on May 25, 2012, 04:23:45 pmQuote from: fmaimon on May 25, 2012, 06:36:14 pmI found in the datasheet that the impedance should be below 10k, so I changed the voltage divider to a 5k-1k.There is already a 10uF capacitor in parallel with the 1k resistor.The problem still persists, with a undershoot in the lower voltages and a overshoot in the high voltages.The voltage reference is a lm7805, and I've now tried to multiply my averaged ADC output divided by 1023 with my Vref*6.It is still most accurate in the 5-10V'ish area, undershoots below that, and overshoots above that. I'm really clueless at this point.

author: TerminalJack505
author_rank: Super Contributor
timestamp: May 25, 2012, 08:28:36 pm
content: That sounds pretty far off. Especially the 350mV. That's nearly 4 LSBs of error.There might be a problem with your sampling code. I seem to remember Atmel having an app note regarding oversampling. It might be worth trying to find it just to see how they do it.You might also post a schematic and your code as that will help a lot as well.

author: free_electron
author_rank: Super Contributor
timestamp: May 25, 2012, 09:19:36 pm
content: a/d are nonlinear at the extents of the scale. ( close to 0 and close to vref ) but you are pretty far off ...source impeance is one aspect. If the a/d is of the sampling type the sampling can cause spike currents while they chrage the sampler capacitor.but a unity gain opamp before the a/d to solve this.oversampling 3000 times is not needed . still your sampling mechanism may be wrong...Best mechanism is the sliding window system.in code :Code:dim x(16) as integerdim ptr as integerdim total as integerfunction sample() as integertotal = total - x(ptr) ' substract the 'old' sample valuex(ptr) = read_adc ' store a new sample in the slottotal = total + x(ptr) ' add new acquired sample to totalif ptr = 15 then ptr=0 else ptr++return (total >>4)end subThere you go. Sliding window integrator with 16x oversampling. if you go to 32x oversampling the last becomes total>>5 .. etc.i use integer arithmetic to speed up things.I basically store a table of the last 16 samples. And i keep the total .The table always holds 'old' samples. So i throw out the oldest ( the one currently pointed at by ptr ) by substracting it from total. take a reading ,add that to total and stor in the table. increment pointer ( and roll over if needed ). by doing the arithmetic in this way i avoid having to go over the full table every time to get the total count. i keep a 'running count'.The only problem is after a system reset. As total will be zero and the table empty you need to initialize the system by calling this function 16 times to seed the table. but that can be done during system startup.

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 09:54:23 pm
content: This is my code, sorry for amateur and messy code, it's my first real try with micro controllers.Code:#include <avr/io.h>#include <stdlib.h>#include <avr/interrupt.h>#include <util/delay.h>#include "myLCD.h"char adcResult;int main(void){InitializeLCD();ADCSRA |= 1<<ADPS2;ADMUX |= 1<<ADLAR;ADMUX |= 1<<REFS0;ADCSRA |= 1<<ADIE;ADCSRA |= 1<<ADEN;sei();ADCSRA |= 1<<ADSC;while(1){}}ISR(ADC_vect){double tempADC = 0;for (int i = 0; i <500; i++){ // adding 500 samples togethertempADC = tempADC + (ADCH << 2 | ADCL >> 6);}double tempADCresult = tempADC/500; // getting the averageuint8_t theLow = ADCL; // These two lines were used originally, but now the LCD doesn't update if they're not there.uint16_t tenBitvalue = ADCH << 2 | theLow >> 6;double ADCH_value2 = (tempADCresult/1023)*(4.92*6); // ADCvalue * (Vref * voltagedivision)dtostrf(ADCH_value2,7,2,adcResult);GotoThisPosition(1,1); // LCD placementSend_String("Voltage:");Send_String((adcResult));Send_String("V ");ADCSRA |= 1<<ADSC;}

author: free_electron
author_rank: Super Contributor
timestamp: May 25, 2012, 10:13:28 pm
content: tempADC = tempADC + (ADCH << 2 | ADCL >> 6);why are you mucking about with the individual bytes ? just read ADC ...ADCSRA|=(1<<ADSC); // start conversionwhile(!(ADCSRA & (1<<ADIF))); // wait for completeADCSRA|=(1<<ADIF); // clear adif flagother problems :by using 500 and dividing you are introducing jitter in your output: There is a post-division remainder error introduced each time. Simply increase your number of samples to 512 and use an integer division by shifting to the right. That only eliminates the 'noise' and does not intoduce a random shift.you still have a discontinuity between results. lates say you take the average of 500 samples that are all 'high' and the next run you take 500 samples all 'low' you can still have large sample to sample steps. the sliding window mechanism eliminates that. ( besides oversampling 500x times is absolutley absurd ... if that is the only way to get the noise out of you rsystem you have serious hardware problems .... ) there is no way you need to filter for 9 bits in a 10 bit a/d .... ( 2^9 = 512 ) and the a/d is only 2^10 ...come one... oversampling to get 2 or 3 more effective bits yes... beyond that : you have a serious problem in your system.i don't know how you r adc is configured ( free-run or sample-on-command) but another potential problem is reading 'high' from an old sample and 'low' from a new ( or vice versa) the ADc should always be under control. tell it to do a conversion , go do something else , and just prior to reading the output : check the 'complete flag' .

author: Raxios
author_rank: Contributor
timestamp: May 25, 2012, 10:30:15 pm
content: I've changed it to sample 32 times, it's not jittery, but I just figures that since I wasn't doing anything else with it, I might as well pick a high number.The problem with the undershooting and the overshooting still persists though, is the code suggestion to make the program smaller/more efficient, or will it actually improve the precision? Since I'm only going to use this micro for 2 ADC channels, I don't need to make it super efficient, not before I make it work properly at least.The code I've linked is all there is on it btw. I'm truly a noob when it comes to micro controllers, so I believe that every time it senses a change, it will run the ISR(ADC_vect), I might be very wrong though.I believe that "(ADCH << 2 | ADCL >> 6)" is the 10bit result?

author: free_electron
author_rank: Super Contributor
timestamp: May 25, 2012, 10:54:03 pm
content: QuoteIt is an attempt to show you proper coding and signal processing techniques. Right now your code can be sublimated in 1 word : crap. You appear to have no clue what you are doing and are just flailing around...Go read the datasheet pages 250 and following. it is all explained in great detail , including noise reduction by using the sleep mode of the cpu ( so the conversion only happens when the cpu is idle ) , how not to be fidgeting around with adjacent channels during a conversion and much more.Quoteright now you have ZERO precision... because the whole program logic is flawed.I am not an AVR expert but this piece of code : "(ADCH << 2 | ADCL >> 6) for sure does NOT work !The adc result is stored on two consecutive locations ( 0x78 and 0x79 according to page 265 ), so to get this as a 16 bit number ( if there is no instruction to read this directly as 16 bit , which would amaze me .. )Code:int adc_result;adc_result = (adchigh << 8) | adclow;note that my 'c' is rusty so there may be typo's in the above code ...Take adchigh which contains only 2 effective bits (aligned right ) : shift those up 8 position and logically or the 8 bits form adclow. done. 16 bit number.( i would actually have coded that even more differently but i don't know the correct 'c' syntax for the avr compiler. stuff like this i create overlayed variables... )int adc_result#pragma @adcresultbyte adlow#pragma @adcresult +1byte adhighthen i can simply do :adhigh = adc_highadlow = adc_lowand read adc_resultno need for shift / or and other constructions that take processing time.i wouldn't even do the above.. i would just define 'adc_result to 'live' at base address 0x78 and be done with it.-edit- there is actually a little paragraph that explains you to read adcl first because reading adch will cause the registers to updat. so , in order to prevent data corruption due to samples overwriting each other you need to do this :Code:int adc_result;byte temp;temp = adclow;adc_result = adchigh;adc_result = (adc_result << 8) | temp;the fact that they have such a mechanism leads me to believe there is actually a read16 instruction in that cpu's core... that way the register is accessed with the correct endianness and integrity is preserved.

author: westfw
author_rank: Super Contributor
timestamp: May 26, 2012, 01:14:05 am
content: >> I am not an AVR expert but this piece of code : "(ADCH << 2 | ADCL >> 6) for sure does NOT work !The AVR ADC actually has a "left justified" mode (which IS being used, because ADLAR = 1) where the above code fragment is pretty close to correct. The high 8 bits are in ADCH, and the low two bits are left justified in ADCL. The possibility that the code is doing byte shifts instead of word shifts is quite worrying, but I don't think I can say for sure that it is wrong without looking at the code produced. "((uint16_t)ADCH) << 2 | (ADCL >>6))" would make me less nervous. I wouldn't normally USE that mode, because it is a bit jarring, but it's not necessarily WRONG.The rest of the code is not comforting. What jumps out as me is that you're doing a lot of stuff in the ISR that should NOT be done in an ISR.Code:for (int i = 0; i <500; i++){ // adding 500 samples togethertempADC = tempADC + (ADCH << 2 | ADCL >> 6);}You're not doing 500 A-D conversions here, you're just reading the register 500 times (if it's in some free-running mode, you might get more than one reading, or you might not.) You need to wait for a conversion to be complete.

author: Psi
author_rank: Super Contributor
timestamp: May 26, 2012, 01:14:48 am
content: yep, there isADCH - High regsiterADCL - Low registerADCW - Full word (Note i'm not sure if this is a library function or an instruction so i don't know if it works the same with left adjust mode enabled)The reason you might want left adjust mode is if you only need 8 bit resolution and want to read ADCH only.But since you want full 10bit you should leave it right adjusted.With the adc in default (right adjusted) you could combine the values like thisuint16_t adc_result;adc_result = ADCL | (ADCH<< 8 );NOTE: you MUST read ADCL firstReading ADCL locks the ADCH byte so it cannot change until you've read it.This ensures you don't get an ADC update between reading the low byte and reading the high byte.(As it would result in data from two conversions).When doing ADCL | (ADCH<< 8 ) you want to move the high register 8 shifts to the left.This moves the two high bits (256 and 512) from____0000-0011TO...0011-0000-0000"OR" that with the low byte and you get the full data.0011-0000-0000OR0000-1111-1111=0011-1111-1111But there is no reason to do it that way and worry about the order when you can just do it in one operation.uint16_t adc_result16;adc_result16 = ADCW;

author: westfw
author_rank: Super Contributor
timestamp: May 26, 2012, 05:32:50 am
content: Code:adc_result = ADCL | (ADCH<< 8 );NOTE: you MUST read ADCL firstI don't think that there is anything about that C expression that guarantees that ADCL will be read first...Using ADCW should work correctly.

author: Psi
author_rank: Super Contributor
timestamp: May 26, 2012, 07:39:17 am
content: Quote from: westfw on May 26, 2012, 05:32:50 amhm.. i assumed the C compiler would generate the ASM in the written order.The same as when you use an IF and have a two-part condition statement, like...1) Check if an object is of a specific typeAND2) Cast it to that type and checks a property of the object.If it didn't evaluate in written order it might cast and access an object of the wrong type.

author: Raxios
author_rank: Contributor
timestamp: May 26, 2012, 12:32:23 pm
content: Thank you all so much for your input.I will try throughout the day to understand all of it, and hopefully make it work!

author: free_electron
author_rank: Super Contributor
timestamp: May 26, 2012, 02:16:54 pm
content: Quote from: Psi on May 26, 2012, 07:39:17 amnever assume anything with compilers ! It depends on the implementation.. And if an optimizer is in effect.. All bets are off !I guessed there was something odd with that a/d when i wrote 'im not an expert'. Why on earth would they make such a wonky mode ? Ok if you need only 8 bit, but then you set it in that mode and use only 8 bit. Setting it in 8 bit mode and then kludging around to retrieve the full 10 bit is just ... Boneheaded...Simplest solution : just read the bloody adcw and done.And use a sliding window integrator for you oversampling. It will be spot on.

author: aluck
author_rank: Regular Contributor
timestamp: May 26, 2012, 04:31:50 pm
content: Once I was told that it is not possible to get whole 10-bit resolution from ATmega's ADC. That one could get 8 bits and the rest 2 bits are garbage.At a first glance, it seemed true. The last two bits were erratic and didn't make any sense.Then I stumbled upon Atmel's Appnote AVR121 and learned about decimation. Using the technique describe I got quite stable 10 bits resolution (although much lesser sampling rate - but it was not much of an issue).

author: free_electron
author_rank: Super Contributor
timestamp: May 26, 2012, 05:10:21 pm
content: Thats the technique i described above : sliding window integration.However, i dont agree with what is said in that appnote. They give the example where they pull 16 bits out of an 10 bit converter. They run on the 'noise 'The problem is that noise is not a flat spread..... It is unpredictable. The dnl of such a conversion is garbage. If you need 16 bit use an 16 bit and apply sliding window integration.

author: baljemmett
author_rank: Unknown
timestamp: May 26, 2012, 05:14:41 pm
content: Quote from: Psi on May 26, 2012, 07:39:17 amNope, there's no sequence point within the quoted expression, so the compiler is free to evaluate ADCL and ( ADCH << 8 ) in whichever order it feels like today. It might be as-written, or it might not -- in fact, depending on the underlying instruction set it might do something like "load ADCH into register x, shift register x left by eight, OR ADCL into register x" instead of requiring a temporary to hold the ADCH<<8 term. (I'm not familiar with AVRs at all, so that probably doesn't even make sense for the architecture, but in general terms it's possible.)QuoteAh, now, that's subtly different -- when you're doing that you use the short-circuiting boolean operators && and ||, which are explicitly defined to evaluate the left operand first and only evaluate the right operand if required to compute the final result. (In other words, && and ||, along with the :? and comma operators, establish additional sequence points within the expression.) If you used the bitwise & or | instead, both sides would be evaluated (in an undefined order) because both operands will be required to compute the final result (except for the trivial cases where one is 0 or ~0 as appropriate, but that's nitpicking).The standard, as usual, has all the gory details -- or there's thecomp.lang.c FAQ's take on it-- but the long and the short of it is you need to do that as two statements if you want to guarantee order of evaluation. Or just read the 16-bit variable instead!

author: PA0PBZ
author_rank: Super Contributor
timestamp: May 26, 2012, 07:47:02 pm
content: Quote from: Psi on May 26, 2012, 07:39:17 amJust tested this with PIC C and guess what?(0xFC3 = ADRESL, 0xFC4 = ADRESH)dataPacket._byte = ADRESL | (ADRESH << 3);50C4 MOVF 0xfc4, W, ACCESS0D08 MULLW 0x850F3 MOVF 0xff3, W, ACCESS10C3 IORWF 0xfc3, W, ACCESS0100 MOVLB 06F66 MOVWF 0x66, BANKEDIt starts by reading the high value

author: aluck
author_rank: Regular Contributor
timestamp: May 26, 2012, 10:03:28 pm
content: Quote from: free_electron on May 26, 2012, 05:10:21 pmNo, it's not. Do read again. Do try it. Compare results. Repeat.

author: Psi
author_rank: Super Contributor
timestamp: May 26, 2012, 11:08:43 pm
content: Quote from: baljemmett on May 26, 2012, 05:14:41 pmQuote from: PA0PBZ on May 26, 2012, 07:47:02 pmThanks guys, learn something every daySafe way to combine low and high is going to beCode:uint16_t adc_value;adc_value = ADCL;adc_value = adcvalue | (ADCH << 8) ;Or just use ADCW

author: free_electron
author_rank: Super Contributor
timestamp: May 27, 2012, 12:24:19 am
content: Quote from: aluck on May 26, 2012, 10:03:28 pmTheir difference is that they shift down only 3 positions where i do 4. So they claim to get 'additional bits, which is nonsense. You can't get 16 bit out of a 10 bit converter no matter how hard you try.The second trick they employ is injecting noise in the input. They are essentially making a sigma delta on top of their a/d. But this again is nonsense... I wouldn't trust that. First of all your 'random' generator is not going to be random at all. It'll be based on a prbs polynomal. If you try to convert a perfectly stable static input it will still bounce all over the place..

author: aluck
author_rank: Regular Contributor
timestamp: May 27, 2012, 07:18:17 am
content: Quote from: free_electron on May 27, 2012, 12:24:19 amYou missed 2 points:1. It is not just "a noise". If you superimpose 10Vpp noise over 250 mV signal - it wouldn't make any sense. You need artificially produced noise "with a ripple peak-to-peak value of a few LSB" (page 9).2. It is not designed to replace 16-bit ADC with el-cheapo 8-bit. You are losing sampling rate at exponentional progression. For each additional 1 bit you have to double the number of measurements, so for getting additional 8 bit you will need 2^8 oversampling.So far, you are just theorizing. Just give it a try and let us know of your results. If you still not satisfied - let's summon Atmel people here and question them a little bit Torquemada style.