Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Reducing fluctuating adc-results

Status
Not open for further replies.

king.oslo

New Member
Hello there,

I frequently wire up sensors in a voltage divider and connect to PICs.

When I make adc requests, the ADC-results can vary a little with time. This may be that the voltage varies a little, but I am not sure.

What I mean, is that I can have a circuit that I dont modify, then make, say 10 ad conversions within 30 sec. These results may vary by a a few points. Maybe as much as 10 points in some cases.

I would really like to know about all the potential causes for this, and how to minimize this fluctuation.

Thanks for your input.

Kind regards,
Marius
 
Last edited:
hi,
What are the values of the potential divider resistors.??
 
Depends on the setup. I have fluctuating results in all my AD conversions. But in one situation, i may have R2: 2400ohm and R1: 2300ohm.M
 
Last edited:
Depends on the setup. I have fluctuating results in all my AD conversions. But in one situation, i may have R2: 2400ohm and R1: 2300ohm.M
The reason for asking about the resistors is that recommended source impedance for the adc is approx 5K, higher than 10K can cause a adc reading error.

In your PIC program how long is the adc acquisition delay set for.??
 
I've tried all sorts of delays. In this case I delay 100 cycles.

Code:
int adcCall(unsigned char channel, int iterations) { /* "channel" refers to 
                                        the ADC-channel being examined, "iterations" refers to the
                                        number of sequential conversions to be made*"
 	int i;
 	int result;
 	unsigned int total = 0;
  	SetChanADC(channel);
  	for (i = 0; i < iterations; i++) {
   		Delay10TCYx(10);  /* 100 cycles delay. It seems the longer 
                the delay, the more fluctuating the results may be. But I assume longer 
                delays will give me a better average? */
    	ConvertADC(); 
    	while( BusyADC() ); 
		result = ReadADC();
    	total = total + result;
  	}
	return total/iterations; // returns the average of all adc requests.
}
 
Last edited:
If its a 5Vref, 10 points is in the order of 50mV.

Have you tried using a known fixed reference voltage.?

Is the PIC's supply stable, as you may know the adc is ratio-metric, so if the PIC's +Vs is being used as the +Vref and you are measuring a voltage derived from another source this could cause a problem.

Can you explain how its set up in hardware.?
 
Power supply:
12V car battery --> Voltage regulator 5VDC max 1.5A --> capacitator: 10uF -> all devices on the PCB (including PIC and 5VDC R1 is paralleled from this supply.

Setup of the voltage divider:
5VDC -> R1 (2300ohm) -> Vout (to PIC) -> R2 (2400ohm) -> GND

I dont know how stable the supply is. I havn't got a scope to measure.

What are you thinking?M
 
hi,
If you dont have a Vref IC, say 2.50V, I would try an unloaded low voltage battery, say 1.5V thru 3.5V via 1K resistor into the adc pin.
[without your divider]
or
Add a small decoupling cap from the junction of your divider to 0V, say 10nF, in case there is noise on the supply to the top of the divider.
 
Last edited:
If you are having hardware or configuration issues, then this is a bandaid on a broken arm but you could try using a rolling average of the ADC readings.
 
can u explan rolling average of the ADC readings

Ya.
I normally use the FSR if I'm going to do that.
What I do is decide what RAM location I want to use as the start of the block of readings. You add a counter variable to that. You then use the FSR to point to the RAM location based on the result of that addition. You must also have a check at the end so that you don't send the FSR past where you want your block of ADC samples to reside in RAM. You then bring the FSR back to the value unadulterated FSR pointer variable and sum all of the readings in the block and divide by the number of readings.

Here's an idea of what the code would look like (though check me on the names and what not)

Code:
;EXAMPLE FOR ROLLING AVERAGE OF TEN 8 BIT ADC READINGS

cblock  00h
FSRL_Counter
endc

ADC_RAM_BLOCK       ;subroutine for loading the block of ram with 10 adc readings
MOVLW   D'90             ;90 decimal is the FSRL location of the ram block in this example
ADDWF    FSRL_Counter,w
MOVWF   FSRL
MOVF     ADRESL,W
MOVWF  INDF
INCF      FSRL_Counter
MOVLW   '9           ;set up w reg to perform subtraction with the counter variable
SUBWF    FSRL_Counter
BTFSC     STATUS,Z   ;skip next instruction if FSRL_Coutner <> 9
CLRF       FSRL_Counter
RETURN

From there you read the FSR starting at the same position and going to the end of your block of samples, adding each one. Once you get to the end you divide by the number of samples in the block (10 in this example).
 
Last edited:
Hey wannaBinventor, are you insinuating that I have hardware or configuration issues? What do you think they may be, now that you have seen my hardware setup and some if my code?

Ericgibbs, I will try the 10nF capacitator tomorrow. Do you think my capacitor at the power supply is big enough?

Thanks.M
 
I'd say that you do. Eric probably knows much more of this kind of thing than I do, but the 10uF capacitor is too big in my opinion. What PIC are you using? Does it have an FVR built in? The 10nf should help I would think, however.
 
I'd say that you do. Eric probably knows much more of this kind of thing than I do, but the 10uF capacitor is too big in my opinion. What PIC are you using? Does it have an FVR built in? The 10nf should help I would think, however.

hi,
I think the OP's 10uF is on the +5V power rail, not the adc input, I have suggested he tries a 10nF at the junction of the divider.
 
I agree with the 10nF on the divider junction, but I think the 10uF at the power supply is too big. I don't think the 10uF can filter out high freq variations that he may be experiencing. I'd go probably .1uF or 1uF there, but then again I'm no expert.
 
I agree with the 10nF on the divider junction, but I think the 10uF at the power supply is too big. I don't think the 10uF can filter out high freq variations that he may be experiencing. I'd go probably .1uF or 1uF there, but then again I'm no expert.

I would agree that an electrolytic cap , say 10uF thru 47uF and a 100nF decoupling cap is a good idea.
 
Well, the power decoupling depends on what's on the board, so more details are needed.

I'm using the 1 or 10 nF to GND capacitor on all analog inputs that go through a voltage divider, this filters out noise and allows use of a higher divider resistance than what the datasheet recommends. When the ADC takes its sample, it charges its internal hold capacitor with the input voltage, so if the filtering cap is much larger than the hold capacitor, the voltage change can be negligible (<1 LSB) even with a 100k voltage divider...
 
Hello again,

I am back.

I have altered my setup:

Now I have 10nF on each voltage divider. and Vref+ and Vref- are VDD and VSS. I have 10uF from 5V to 0V after voltage regulator. I still have results fluctuating about 10-15 points.

What should I check next?

Thanks.M
 
ADC sampling & Avging

I am using a 16f886 in a project atm for displaying a usable moving avg of the vehicle's air/fuel ratio, both Narrow & wideband. I use the MSB of the 10bit adc and I still get a little variation (in bit 0) on the benchtop. Probabaly due to the % error in my LDO 5Vreg, which is the MCU Vref. If u are using the LSB of the 10bit ADC you will get larger kinds of fluctuations. Have you left aligned the 10 bit data into the 8 bit ADRESH register, and used that as your ADC sample?

Anyway here is my moving avg code...which sums 16 samples on the fly and uses asm math to divide efficiently.

Code:
; first roll old data down a byte (17 byte stack of data);0x1aa - start of Oxydataroll
	movlw (Oxydataroll+.15)          ;  decimal 15, last addy of (0-15) ,16 samples, 1 extra for 'overflow sample' => 17 bytes of sequential GPR.
	movwf FSR                             ; setup indirect addr
	;bsf STATUS,IRP                     ; bankswitch (if required) for FSR to banks 2&3
Rolldata
	movf INDF,w           ; get upper byte
	                             ; sum data on the fly.
	addwf TempL,f
	btfsc STATUS,C
	incf TempH,f
	                             ; now continue data roll
	incf FSR,f               ; point to lower byte
	movwf INDF            ; move upper byte here.
	decf FSR,f              ; back to orig. upper byte
	movlw Oxydataroll   ; start of Oxydataroll
	subwf FSR,w
	btfsc STATUS,Z       ; test if we have done all the 16 bytes.
	goto Rolldone
	decf FSR,f              ; point to higher upper byte.
	goto Rolldata          ; keep going
Rolldone                         ; now push latest oxydata sample into top of oxy data roll stack.
	banksel Oxydata
	movf Oxydata,w       ; current adc sample
	movwf INDF             ; top byte of stack
	;bcf STATUS,IRP      ; bankswitch (if req'd) for FSR to banks 0,1
	movlw .16               ; # of bytes summed in TempL, TempH => total cannot exceed 12 bits of a 16bit #.

;Do a 12 bit divide by .16.
	movlw b'11110000'
	andwf TempL,f        ; clr low nybble.
	swapf TempL,f        ; swap nybbles
	swapf TempH,w      ;swap nybbles 
	andlw b'11110000'   ; clr the current lo nybble (was hi nybble b4 swap)
	iorwf TempL,w        ; unite nybbles to make = div by 16 result in wreg.

; Result of 16 element moving avg in wreg, recalc'd upon every new single sample for smooth averaging.
 
Last edited:
Make sure that the voltage reference has a large capacitor on it. Some PICs take a surge of current from the voltage reference when the ADC measures the voltage.

I would also suggest 100 nF in parallel with the ADC input. That will charge the sampling capacitor on its own without needing any current from the potential divider, with less than one count dip. That my not be needed, but in many applications 100 nF isn't a problem, so you might as well use that.

For averaging, I use a routine that replicates an RC network with a time constant that can be very long. I have an accumulator, usually 24 bits. The ADC samples at regular intervals, maybe 10 times a second. The ADC reading is added into the accumulator. Then the accumulator is divided by a constant, K. To keep the code simple, I often use 256, and I always use a power of 2. The divided result is subtracted from the accumulator.

If the ADC value is constant, the accumulator value eventually becomes K times as large as the ADC value. The time constant is K times the sample rate.

Code:
;First the ADC result is added into the accumulator.	
	mov		ADRESL, w
	addwf	ACC_L, f		;low byte
	mov		ADRESH, w
	btfsc	STATUS, C
	incfsz	ADRESH, w
	addwf	ACC_M, f		;mid byte
	btfsc	STATUS, C
	incf	ACC_H, f		;add carry into high byte.
	
;Now dividing by 256 is done by just taking the high byte as the mid byte, and the mid byte as the low byte.
;So mid is subtracted from low, and high is subtracted from mid.
	
	mov		ACC_M, w
	subwf	ACC_L, f		;low byte
	mov		ACC_H, w
	btfss   STATUS,C
    incfsz  ACC_H, w
	subwf	ACC_M, f		;mid byte
	btfss	STATUS, C
	decf	ACC_H, f		;subtract carry into high byte.
	
;That will leave ACC_H, ACC_M as a damped version of ADRESH, ADRESL. The time constant is 256 times the speed at which this code is run.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top