Trouble with Subtraction after ADC

Thread Starter

SteveO

Joined May 15, 2008
33
Hi all,

Using PIC16F688. The idea of the program is to take two ADC inputs (AN6/AN7) and subtract the values. If AN6 > AN7 turn RA0 high, if not turn it low.

However my code does not perform as expected. It only sets RA0 high if the two voltages on AN6 & AN7 are very close to one another. When either is significantly higher than the other the pin is low.

I have checked and both conversions seem to working properly so I believe it is in my subtraction, end of code. If anyone has any ideas where I might be going wrong it would be greatly appreciated.

Thanks.

Code:

Rich (BB code):
#include <htc.h>
#include <math.h>
#include <stdio.h>
#define _XTAL_FREQ 4000000
//#define bitset(var,bitno) (var|=1<<bitno)
//#define bitclr(var,bitno) (var&=~(1<<bitno))



__CONFIG(INTIO & WDTDIS & PWRTEN & MCLRDIS & UNPROTECT & UNPROTECT & BORDIS);

void main(void){
	
	int x = 0;
	int IRone = 0;		//AN6/RC2
	int IRtwo = 0;		//AN7/RC3
	int IRdiff = 0;		//for difference in IR measurements
	PORTA = 0x00;		// PORTA low
	PORTC = 0x00;		// PORTC low
	CMCON0 = 0x07;		// turn off comparators
	ADCON0 = 0b10011001;		//turn on ADC AN6 selected - right justified
	ADCON1 = 0b00010000;		//ADC clock select FOSC/8 - 2us @ 4Mhz

	TRISA = 0x00;				//Port A outputs
	TRISC = 0b00001100;			//RC2 + RC3 input pins (AN6/AN7)
	ANSEL = 0b11000000;			//AN6/AN7 selected as analogue input pins

	ADIE = 1;		//enable ADC interrupt
	ADIF = 0; 		//ensure ADC interrupt flag is cleared
	
	while(1){

		ADCON0 = 0b10011001;		//ADC ON - AN6
		ADCON0 = 0b10011011;		//ADC GO - AN6		
			while(ADIF = 0){		//wait for ADC interrupt flag
				_delay(1);
			}
		ADIF = 0;					//clear ADC interrupt flag
		IRone = (ADRESH*256) + ADRESL;		//calc IRone - right justified 

		ADCON0 = 0b10011101;		//ADC ON - AN7
		ADCON0 = 0b10011111;		//ADC GO - AN7
			
			while(ADIF == 0){		//wait for ADC to finish
				_delay(1);
			}
		ADIF = 0;					//clear ADC interrupt flag
		IRtwo = (ADRESH*256) + ADRESL;		//calc IRtwo - right justified
		
		IRdiff = IRone - IRtwo;
		
		if(IRdiff >= 0){
			PORTA = 0b00000001;
				for(x = 0; x <=13; x++){
					__delay_ms(20);
				}
		}
		else{
			PORTA = 0b00000000;
		}
	}
}
 

kevins

Joined Nov 26, 2009
2
Looking at your calculation '(ADRESH*256) + ADRESL', I suspect that ADRESH is an unsigned char or other 8-bit value, which means the multiplication may overflow an 8-bit range. Try casting it to a integer for your calculations: -

e.g. IRone/IRtwo = (int)ADRESH * 256 + ADRESL;
 

Thread Starter

SteveO

Joined May 15, 2008
33
Thanks Kevins.

Haven't thought about that but makes good sense. I'll look into this when I get home this evening and let you know my results.

Thanks again.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Apparently not the problem, but definitely a good suggestion, thanks again though Kevin.

After I had some time to do some more trouble shooting, it is right after I change channels on the ADC to AN7 by the code below that things start to get flaky:
Rich (BB code):
		ADCON0 = 0b10011101;		//ADC ON - AN7
			__delay_ms(2);
		ADCON0 = 0b10011111;		//ADC GO - AN7
By flaky I mean flashing in and out when it should be in a certain stage, and just not behaving as expected. Going to play around with it a little more tonight, and let you know if I get anywhere.

Thanks.

Addition:
I have broken down my code to just set RA0 high if AN6 is > 1/2 the supply.
When I have the code to run the ADC for AN7 commented out I get good results. However when the code is in the program (if/else statement still only dependent on AN6) I get weird results. AN7 seems to be able to control the output on RA0 as well. When I connect AN7 to ground / supply the LED goes out / stays on and the voltage on AN6 does not affect RA0.
 
Last edited:

AlexR

Joined Jan 16, 2008
732
Try declaring IRone and IRtwo as "unsigned int". In C the type "int" defaults to a signed number and this could mess up your subtraction.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Thanks Alex. Makes sense and I have changed the code to reflect this. Didn't seem to have an impact but I will leave it this way.

Cheers.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Ok I think I'm going crazy... The code seems to be doing the opposite...

Here is the updated while loop.

Rich (BB code):
	while(1){

		ADCON0 = 0b10011001;		//ADC ON - AN6
			__delay_ms(2);
		ADCON0 = 0b10011011;		//ADC GO - AN6		
			while(ADIF = 0){		//wait for ADC interrupt flag
				_delay(1);
			}
		ADIF = 0;					//clear ADC interrupt flag
		IRone = (ADRESH*256) + ADRESL;		//calc IRone - AN6 


		ADCON0 = 0b10011101;		//ADC ON - AN7
			__delay_ms(2);
		ADCON0 = 0b10011111;		//ADC GO - AN7
			
			while(ADIF == 0){		//wait for ADC to finish
				_delay(1);
			}
		ADIF = 0;					//clear ADC interrupt flag

		IRtwo = (ADRESH*256) + ADRESL;		//calc IRtwo - AN7

		//IRdiff = IRtwo - IRone;
		
		if(IRone >= 512){
			RA0 = 1;
				for(x = 0; x <=13; x++){
					__delay_ms(20);
				}
		}
		else{
			RA0 = 0;
		}
	
		if(IRtwo >= 512){
			RA2 = 1;
				for(x = 0; x <=13; x++){
					__delay_ms(20);
				}
		}
		else{
			RA2 = 0;
		}	
	
	}
AN6 (IRone in code/Pin 8) is controlling RA2 (pin11) and AN7 (pin 7) is controlling RA0 (pin 13). This completely contradicts what I have written. Am I going crazy, because I'm sure there is a reasonable explination for this.

Thanks again.
 

eng1

Joined Oct 25, 2009
13
shooting, it is right after I change channels on the ADC to AN7 by the code below that things start to get flaky:
Rich (BB code):
        ADCON0 = 0b10011101;        //ADC ON - AN7
            __delay_ms(2);
        ADCON0 = 0b10011111;        //ADC GO - AN7
By flaky I mean flashing in and out when it should be in a certain stage, and just not behaving as expected.
I suppose that your delay is for the sample & hold capacitor to charge after changing the input channel. Is the source a low-impedance one?

If you have checked that the two readings are correct when using AN6 and AN7 separately, consider increasing the delay (this depends on the input inpedance).

As suggested, cast the A/D results to int:
Code:
IRone = ADRESH;
IRone = IRone << 8 + ADRESL;
 

Thread Starter

SteveO

Joined May 15, 2008
33
Thanks everyone I really appreciate the help.Turns out my first ADIF while statement needed to be changed to a == instead of a =. Really silly mistake that took alot of time to figure out. Thanks all for the help. Learnt alot from the comments here.

Cheers.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Rich (BB code):
while(ADIF == 0){		//wait for ADC to finish
_delay(1);
}
When I do this command the program just waits for the ADC to finish and the interrupt flag to be set. I'm wondering, is there is a better way to accomplish this so that when the interrupt flag is set it will jump to certain code? This way I can be doing other functions while waiting for the ADC to complete.
 

Markd77

Joined Sep 7, 2009
2,806
You could enable the interrupt, but another way would be to put a call to the other job that needs doing where your _delay(1) is.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Thanks Mark.

What exactly do you mean by enabling the interrupt? How would I then check if it has been flagged?

Thanks.
 

Markd77

Joined Sep 7, 2009
2,806
There is a fairly good bit in the datasheet, section 8.1.7 and have a look at 11.5 as well. They explain it better than I could.
If you only have one source of interrupt enabled then there is no need to check what caused it.
Using interrupts can be tricky, but worth it in the long run.
 

BMorse

Joined Sep 26, 2009
2,675
you have to clear the ADIF bit first before enabling the ADC.....

Configure A/D interrupt (if desired):
• Clear ADIF bit (PIR1<6>)
• Set ADIE bit (PIE1<6>)
You are setting the ADIE before you clear the ADIF

ADIE = 1; //enable ADC interrupt
ADIF = 0; //ensure ADC interrupt flag is cleared
 

Thread Starter

SteveO

Joined May 15, 2008
33
OK, so it looks like I need to also enable the global interrupt bit of INTCON register. I'm just a little confused with how I can be alerted that there is an interrupt without constantly checking for it.

Thanks.
 

BMorse

Joined Sep 26, 2009
2,675
A ISR (Interrupt handler routine) will take care of the tasks "behind" the scene if you have the right interrupts set, you will just have to write your code in the ISR to do what you want when this happens.... see this post here (#5) >> http://forum.allaboutcircuits.com/showthread.php?t=30957

they are using a F84 but same difference.
For a F688 to use the ADC with an Interrupt follow these guidelines:

These steps should be followed for an A/D conversion:
1. Configure the A/D module:
• Configure analog/digital I/O (ANSEL)
• Configure voltage reference (ADCON0)
• Select A/D input channel (ADCON0)
• Select A/D conversion clock (ADCON1)
• Turn on A/D module (ADCON0)
2. Configure A/D interrupt (if desired):
• Clear ADIF bit (PIR1<6>)
• Set ADIE bit (PIE1<6>)
• Set PEIE and GIE bits (INTCON<7:6>)
3. Wait the required acquisition time.
4. Start conversion:
• Set GO/DONE bit (ADCON0<0>)
5. Wait for A/D conversion to complete, by either:
• Polling for the GO/DONE bit to be cleared
(with interrupts disabled); OR
• Waiting for the A/D interrupt
6. Read A/D Result register pair
(ADRESH:ADRESL), clear bit ADIF if required.
7. For next conversion, go to step 1 or step 2 as
required. The A/D conversion time per bit is
defined as TAD. A minimum wait of 2 TAD is
required before the next acquisition starts.

If you have more than one interrupt enabled, you will have to determine which one caused the interrupt, if checking to see if it is the ADC that caused the interrupt, you can check to see if PIR1,ADIF bit is set if it is, this means an ADC interrupt occured.... then clear this bit before exiting the ISR (ex: bcf PIR1,ADIF)
 
Last edited:

Thread Starter

SteveO

Joined May 15, 2008
33
Thanks for the help. I think I'm missing something. How does this ISR get called when the interrupt occurs? Is it when the global interrupt flag is first enabled and then set by the program that your code jumps to a specific spot? Is it correct naming of the function or how is it possible to jump to this part in the code without having to consistently check the register? Sorry for the junior questions.

Thanks.
 

AlexR

Joined Jan 16, 2008
732
There is no standard for how an ISR (interrupt service routine) is defined and each C compiler has its own method. In the case of Hi-Tech C the interrupt handling function is flagged by the function qualifier "interrupt", so it might look like this: (other compilers use different syntax to define the ISR function)
Rich (BB code):
void interrupt my_isr(void)  
{
   // global interrupts are automatically disabled while program is in the interrupt routine
   // check interrupt flags to find source of interrupt
   // do whatever needs to be done
   // clear the interrupt flag
}
   
  // returning from interrupt automatically enables global interrupts
What actually happens at the machine code level is that the compiler places a goto instruction at address 004 pointing to the interrupt function. As soon as in interrupt occurs the current program counter value gets pushed onto the stack and the program counter gets loaded with the interrupt vector value (004) It is steered to the interrupt routine, does whatever it has to do, then when it returns from interrupt it pulls the program counter off the stack and continues along its merry way from where it was before the interrupt occurred.
 

Thread Starter

SteveO

Joined May 15, 2008
33
Awesome Alex. Thanks very much for your thorough explanation. I understand know that it knows where to go based on the interrupt function qualifier. Makes perfect sense, I will give this a shot.

Thanks.
 
Top