[AVR] ADC oversampling function - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 08:20:21 pm
content: I have written the following little function to oversampe an AVR ADC. Anyone see any obvious flaws ?Code:int16_t ADC_os_read (uint8_t bits) //return the current ADC value with a resolution increase of x "bits", the function will wait until the a conversion has ended.{uint16_t samples = 4^bits;uint32_t adc_sum = 0;uint16_t i;for (i = samples; i > 0; --i){bit_s(ADCSRA, ADSC);//start a conversionwhile( ADCSRA & (1<<ADSC)); // Wait until ADC conversion is complete..adc_sum = adc_sum + ADC; //}adc_sum >> bits;return adc_sum;}

author: bktemp
author_rank: Super Contributor
timestamp: February 14, 2017, 08:32:16 pm
content: 4^bits does not do what you expect it to do: ^ is the C operator for binary XOR, but you want a left shift, so 4<<bits is the correct expression.

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 08:37:08 pm
content: I think I meant it to be 4 "to the power of", how do I express an exponent in C ?

author: Avacee
author_rank: Unknown
timestamp: February 14, 2017, 08:40:08 pm
content: adc_sum is 32bits but you return a 16bitno check if someone passes in a large value for bits - eg 255 - if you only want to allow 4 bit oversampling then prevent values > 5^ is XOR.. use pow(x,y)why declare samples when you just assign the value to i - can just do "for (i = pow(x,y)) .. save a few clock cycles allocating samples (unless the compiler is dumb and evaluates this every time in the for loop, it should do it once)

author: senso
author_rank: Frequent Contributor
timestamp: February 14, 2017, 08:49:28 pm
content: Or just << pow will use a floating point routine and add more than 2K of code.

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 08:50:28 pm
content: Ah yes, I changed the sum value to 32 bits as it could need that many bits in the calculations but of course the function "header" needs updating. I should really recast it as a 16 bit as I'm not expecting more than 16 bits from an oversampled 10 bit ADC

author: Avacee
author_rank: Unknown
timestamp: February 14, 2017, 08:50:55 pm
content: Quote from: senso on February 14, 2017, 08:49:28 pmYep - got me there .. I blame doing too much C# recently, makes one lazy<< ftw

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 08:52:33 pm
content: Quote from: senso on February 14, 2017, 08:49:28 pmI don't quite understand

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 09:04:46 pm
content: Code:pow(4,bits)has been accepted by the compiler

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 09:24:59 pm
content: New version, I could not find a way of recasting from one type to another so brute force it by using a 16 bit and a 32 bit variable and transfer to the 16 bit variable before returning. Do I assume correctly that the downsize will be from the right so any extra on the left will "fall off" ?Code:int16_t ADC_os_read (uint8_t bits) //return the current ADC value with a resolution increase of x "bits", the function will wait until the a conversion has ended.{uint16_t samples = pow(4,bits);uint32_t adc_sum32 = 0;uint16_t i;uint16_t adc_sum16 = 0;for (i = samples; i > 0; --i){bit_s(ADCSRA, ADSC);//start a conversionwhile( ADCSRA & (1<<ADSC)); // Wait until ADC conversion is complete..adc_sum32 = adc_sum32 + ADC; //}adc_sum32 >> bits;adc_sum16 = adc_sum32;return adc_sum16;}

author: senso
author_rank: Frequent Contributor
timestamp: February 14, 2017, 09:25:43 pm
content: http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__math_1ga41b41c307b8f96760e9c0c17180b241b.htmldouble pow(double __x,double __y)In its core its a floating point function, it adds 2K of floating point libs to your code when a simple left shift will achieve the same result using just a couple instructions.

author: Andy Watson
author_rank: Super Contributor
timestamp: February 14, 2017, 09:37:44 pm
content: adc_sum32=>> bits;

author: helius
author_rank: Super Contributor
timestamp: February 14, 2017, 09:41:08 pm
content: Quote from: senso on February 14, 2017, 08:49:28 pmarithmetic shift is a multiplication by a power of2. please don't tell me you thought4<<9raises 4 to the power 9However, \$ 4^n=2^{2n} \$, so you can correctly simplify the codepow(4,bits)to1<<bits*2.

author: Simon
author_rank: Unknown
timestamp: February 14, 2017, 09:57:23 pm
content: So we now have:Code:int16_t ADC_os_read (uint8_t bits) //return the current ADC value with a resolution increase of x "bits", the function will wait until the a conversion has ended.{uint16_t samples = 1<<2*bits;uint32_t adc_sum32 = 0;uint16_t i;uint16_t adc_sum16 = 0;for (i = samples; i > 0; --i){bit_s(ADCSRA, ADSC);//start a conversionwhile( ADCSRA & (1<<ADSC)); // Wait until ADC conversion is complete..adc_sum32 = adc_sum32 + ADC; //}adc_sum32 >> bits;adc_sum16 = adc_sum32;return adc_sum16;}

author: Simon
author_rank: Unknown
timestamp: February 15, 2017, 07:36:02 am
content: Quote from: Andy Watson on February 14, 2017, 09:37:44 pmthanks, I missed thatIt actually needs to be:Code:adc_sum32 = adc_sum32 >> bits;else the compiler does not like it.

author: nctnico
author_rank: Super Contributor
timestamp: February 15, 2017, 08:31:05 am
content: I think you should re-read the appnote on oversampling. Don't forget you will need noise for oversampling to actually work (it might not). You may get an extra bit using 4x oversampling IF the ADC in the uC is decent but beyond that you will be better off using an (external) ADC with more bits.

author: Brutte
author_rank: Frequent Contributor
timestamp: February 15, 2017, 09:55:00 am
content: You are trying to do a FIR "kinda" filtering with decimation.bn*y = a1*x+ a2*x+... + an*xSo bn = n and a1=a2=a3=...=an=1 and all previous samples before x are ignored: a0=0First problem is that this routine is not sampling on regular time base as you are triggering adc S/H by software quite randomly, if irqs are on. You get jitter and instead a=[0 0 0 1 1 1 1 1 1 1 1 ] you get a=[1 1 1 0 1 1 1 1 0 1 0].Either trigger ADC synchronously or put assert(irqs disabled) in the code.Second thing, with a fixed point FIR, you were supposed to divide the accumulated result by bn and not chopping off log2(bn) bits, methinks.Chopping off adds random noise to the result.For n=8, if x = [7 7 7 7 7 7 7 6] then y = 55.0/8.0=6.875 which after rounding should give fixed point 7. By chopping off, the result is a fixed point 6 and that does not look like an average to me.Also on the last but one line you are doing "brave cast", different signedness, different sizes, without even testing if that is doable.And there is a question of dithering but that is an analog part of the play so it does not applear in the code. You need some decent variance in the signal to increase ADC resolution.

author: Andy Watson
author_rank: Super Contributor
timestamp: February 15, 2017, 12:23:16 pm
content: Quote from: Simon on February 15, 2017, 07:36:02 amThat's because I'm an idiot and should have typed:Code:adc_sum32 >>= bits;

author: mikerj
author_rank: Super Contributor
timestamp: February 15, 2017, 12:33:20 pm
content: Quote from: Brutte on February 15, 2017, 09:55:00 amn=8 is not a valid number of samples for this function. The integer value passed in is the extra number of bits of resolution required, and the number of samples is then calculated as 4 raised to the power of n i.e. 1 extra bit requires 4 samples, two extra bits requires 16 samples etc.Also you are performing a simple averaging function above which is not what is required. Dividing the accumulated sum by the number of samples (as an integer operation) throws away the extra resolution which you are trying to get, though it will reduce noise. If you accumulate four samples, you divide the result by 2, and you get an extra bit of resolution (maybe). For 16 samples you'd divide the result by 4, netting two additional bits in the result.

author: Simon
author_rank: Unknown
timestamp: February 15, 2017, 12:44:24 pm
content: Quote from: Brutte on February 15, 2017, 09:55:00 amFrom what AVR app note 121 says table 3-1 on page 8, the result is right shifted by as many bits as you are adding to the existing resolution of the ADC.My idea for this peice of code is as much about filtering as it is about gaining a few extra bits. I have seen gittery results on an ADC before and thought it was time to at least filter it to get a more stable reading. If I do it as an oversample I'll either get some trailing "0's" or maybe a bit more resolution. Ultimatey 10 bits is enough for what I want.No i have not studied the analogue side of it and no there is no timing in the readings (how would I sync the readings to atificial noise ?). I am just relying on natural noise but with a 1m cable I'm sure I'll pick some crap up, it may help the oversampling or it may just be noise that I filter out.Yes I know I'm being a bit brave with my type casting. I would never expect this to work anywhere near 16 bits in terms of increasing resolution and providing I don't try to oversample to more than 16 bits my result to return should never be more than 16 bits.

author: Brutte
author_rank: Frequent Contributor
timestamp: February 15, 2017, 01:30:27 pm
content: Quote from: mikerj on February 15, 2017, 12:33:20 pmI am not referring to the number of samples but to the chopping as such. It does not matter how many samples the algorithm takes.So for n=4 there is: b4=2, a1=a2=a3=a4=1If you feed [7 7 7 6] you get y=27.0/2.0=13.5 and fixed point result after chopping off least significant bit becomes 13. So actually we get:Code:float noise, y;....return (int)(y+noise); //with no rounding on castwhere noise is always positively biassed as you chop either 0.0 or 0.5 each time and if those two appear with equal probability, the average chop is +0.25 methinks.Or I do not get the deeper sense of that chopping.

author: hamster_nz
author_rank: Super Contributor
timestamp: February 16, 2017, 12:08:19 am
content: I've been thinking a little on this, and come the the conclusion that unless you tell us what signal you are filtering and what you want to achieve it is very difficult to offer any advice outside of the basic implementation code.Maybe the input signal has only very low frequency components (e.g. monitoring a battery voltage), in that case taking four samples very quickly and averaging them is a valid way to remove random conversion noise between samples. If the random noise has the correct statistical properties it might even be able to add a bit or two of effective resolution.However, if the ADC's input signal contains any high frequency signals (relative to output the sample rate), then averaging three out of four samples is bad. You don't see the difference between (50, 50, 50, 50) or (100, 0, 100, 0), (100, 100, 0, 0), (67,67,66,0) or (200, 0, 0, 0) as they all average out to 50. Because of this it really depends on signal conditioning (analogue filtering) before it goes into the ADC.One easy test would be to print out a dozen samples - if the ADC reading is stable in the LSB then there there is no more information that can be gained through averaging it.

author: BrianHG
author_rank: Super Contributor
timestamp: February 16, 2017, 01:47:53 am
content: Yes, for the over sampling trick to work, your 4 samples needs to be significantly faster than the final desired total result sample rate and you source signal needs to be low in bandwidth as well. If you don't care about the HF response of your sampler, like using the result to measure a DC voltage of load cell or temperature gauge, you code should work fine unless the CPU's ADC has 0 noise on the LSB when it samples. You can always cheat this be using a noisy opamp to feed your ADC input.

author: Simon
author_rank: Unknown
timestamp: February 16, 2017, 07:54:34 am
content: Oh it's just a temperature signal from a thermistor so no massive changes in a hurry. My last device that used a temperature to generate a pwm had the pwm jumping all over the place (but would have been filtered down stream) so clearly there is an amount of noise to be removed and potential resolution to be harvested, at worse I'll get a cleaner reading

author: Andreas
author_rank: Super Contributor
timestamp: February 16, 2017, 07:26:12 pm
content: Hello,when doing the wiring right (ground star in the analog section) there is no noise that you can average at least with a 10 Bit ADC.When your PWM-signal influences the measurement you are probably sampling at the wrong time.I would sample at the end of the passive PWM phase.The only case where averaging might be useful is when you have a sensor sensitive to line frequency hum.But in this case you would have to sample at e.g. 16-20 equidistant time points within the PLC (power line cycle).In the attached picture I have averaged around 350 measurement values over 1 minute for 1 measurement pointfrom a NTC with a 10 Bit ADC (5V).(X-Axis: number of samples, Y-axis voltage in mV).You can clearly see the 5 mV steps of the ADC.Only when the temperature is exactly between 2 steps there is a small range where intermediate averages can be seen.With best regardsAndreas

author: Simon
author_rank: Unknown
timestamp: February 17, 2017, 09:03:18 pm
content: As my thermistor is connected from ground to an ADC input and there is a pull up resistor (470 ohm) from the ADC input to 5V. My PWM frequency is around 240Hz and with a 12-13 bit oversample and 62.5-125KHz it would take at least a PWM cycle to take the measurements, so if I put a high value resistor of 10's to 100's of KOhm from the PWM output pin to the ADC input it would shake it about enough to drag the input up and down around the 1 bit level (4.8mV)

author: Andy Watson
author_rank: Super Contributor
timestamp: February 17, 2017, 09:32:14 pm
content: Quote from: Simon on February 16, 2017, 07:54:34 amSo this is a proportional rather than bang-bang controller? If so, you ought to look at fixing the sampling/update rate because \$\Delta t\$ affects the gain of the loop and hence the stability.

author: Simon
author_rank: Unknown
timestamp: February 17, 2017, 09:44:52 pm
content: Quote from: Andy Watson on February 17, 2017, 09:32:14 pmI don't know what you mean by gain in a proportional system. No I'm not doing bang bang. It's a case of take target temp +/- some degrees and ramping the fan from min to max from min to max temp. Yes there is no guarantee of an exact temperature but that is the idea of having a range. No gain to worry about. I could of course do a more precise mapping to create a bit of a curve to skew the response, for example I can have a sharper response around the temperature I'm hoping to acheive and have less of the PWM range in the highest or lowest range if I want more guarantee of hitting a centre target, or if I am trying to maximise efficiency and power consumption and don't mind not hitting the target too closely have a less steep reaction around the target and then hit it hard in the extremes hoping to never have to use it.

author: Simon
author_rank: Unknown
timestamp: February 17, 2017, 09:47:51 pm
content: if I want to be a bit more accurate on the target I could have a proportion response but do something likeif ("new reading" > "last reading")add some extra speedelsejust follow the straight lineJust to make it respond a bit more to a suddend increase of decrease in temperature.

author: Avacee
author_rank: Unknown
timestamp: February 17, 2017, 10:10:45 pm
content: Unless you are trying to maintain a set temperature to a very precise value just how quickly do you need the fan to respond to a change in temperature?If you don't need the fan to have an instantaneous effect on the temperature then controlling the fan speed every fraction of a second is overkill.Imho, a fan speed that changes frequently is rather annoying, even if it's only changing by a few RPM .. OCD FTW! :pHave you considered slow,medium,fast fan speeds and set temperature points with some hystersis?This could remove the need for the extra resolution as you are averaging the values over a longer timeframe so have more samples to average.With regard to noise - you could store N samples then discard the X highest and lowest values and average the remaining values.

author: Simon
author_rank: Unknown
timestamp: February 17, 2017, 10:22:19 pm
content: The response time of the mechanical/thermal system is an unknown which is why I am looking at a fairly dumb linear response / look up table. So no I don't need to respond fast. As I said before if anything what I need most from this little function is a filtered value. If I gain resolution then that is nice but I'm not relying on it. As I said before the last system I did had its output PWM jumping all over the place as I was relying on a single measurement at a point in time no averaging, this was not a big problem as the input to the fan had its own lowpass filter anyway. So obviously I should do some averaging and the fan I am using now is cleverer and has a distal input rather than a low pass filtered input and it also does checks on the PWM input and only response to a change in speed if it sees a certain amount of identical PWM pulses.A low medium and high fan speed is probably not appropriate. There won't be a problem with the fan ramping up and down quickly as I believe the thermal mass is quite high and should not be sharp responses in the system. If the fan needs to run hard it needs to run hard, if it does not need to it does not need to. If anything because I am using as many steps as possible in resolution speed change would never be rapid and hardly noticeable. If I were to jump from a low to medium speed and back again you would notice the fan ramp up and down as the fan has its own control system whereby it does not respond immediately. If I include more steps then the increase will be gradual.

author: Avacee
author_rank: Unknown
timestamp: February 17, 2017, 11:14:55 pm
content: Nod - understand now about the fan ramping up/down gently.

author: Andreas
author_rank: Super Contributor
timestamp: February 18, 2017, 06:44:22 am
content: Quote from: Simon on February 17, 2017, 09:47:51 pmHello,I think that is the root of your problems.You are adding some kind of D-Part which is not adjusted to the thermal mass control loop.With best regardsAndreas

author: Simon
author_rank: Unknown
timestamp: February 18, 2017, 08:22:15 am
content: yes which is why I want to avoid doing that, if they want a stupid amount of nailing of the target then I probably need to put 80% of the fan performance into t+50% of max deviation, like an exponential curve. Obviously if you push that too hard you end up with bang bang control anyway which almost defeats the object. As things stand I'll probably work in T +/-1C because they are a bit clueless and in the clouds with how real life works. If this was over a wider range like 10C I'd have more mapping to play with to get a balance of power versus effectiveness of the cooling to their expectations.

author: Simon
author_rank: Unknown
timestamp: February 24, 2017, 09:30:06 pm
content: Well I just tried out my new peice of code and I am very pleased with it. Some discussion of the analogue side of things ended up here:https://www.eevblog.com/forum/beginners/time-constant-involving-a-resistor-divider/25/. Essentially with a 470 ohm pull up resistor and a thermistor with a resistance of not a lot less at my intended temperature I have a 470nF capacitor in parallel with the thermistor and used a PWM output to make some noise through a 1Mohm resistor. I set my function for 3 bits of oversampling and the result is quite pleasant. I would of had 10 b/C of resolution but the theoretical resolution in oversampling is not about 80b/C, infact the output PWM steps happen in about 0.5% increments instead of the 4% increments I'd be getting with the natural 10b resolution.

author: BrianHG
author_rank: Super Contributor
timestamp: February 25, 2017, 05:53:46 am
content: Can you specify the temperature accuracy you need over what range?I achieved a 0.1 degree Celsius resolution with a 10 bit ADC from -20 to +120 degrees Celsius by only adding a transistor and a second resistor load for the thermosistor so when the light load on the thermosistor read too low a value on the ADC, I turned on the transistor, just like changing the range the range on an auto ranging ohm meter, to get that extra precision at lower temperatures.Well, combined with oversampling, you now can get 0.025 degree precision. I think your thermosistor wouldn't be that precise anyways..

author: Simon
author_rank: Unknown
timestamp: February 25, 2017, 08:56:20 am
content: Well accuracy and resolution are two different things. My sensor is actually 5% but I was looking for as many control steps as I could muster. This experiment was setup over 2C which means I'd have to pull the extra resolution out to see such small step changes in PWM which verifies that my oversampling is working. As the board I tried this on is already designed oversampling was the only option. If I designed a board again I'd use an instrumentation amplifier to extract the amount of range I require and amplify it to get more resolution.

author: 0xPIT
author_rank: Regular Contributor
timestamp: February 25, 2017, 01:38:02 pm
content: I've implemented the oversampling and calibration concepts from the AVR datasheets here:https://github.com/0xPIT/adcUsage example inhttps://github.com/0xPIT/UltiDMMAfter calibration, this works very well.- pit

author: Simon
author_rank: Unknown
timestamp: February 25, 2017, 07:02:30 pm
content: My C foo is not good enough yet to understand all of it (I'm just at the point where I can recognise what my own code does an hour after writing it). How does the calibration work ?