Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

Modalities Of Using The ADC Module Of PIC 16F877A - Part 5

SEQUENTIAL STEPS IN ANALOGUE TO DIGITAL CONVERSION USING THE INTERNAL ADC

So, here are the steps to configure the ADC and carry out the conversion:
• Decide which pins need to be analogue, which pins need to be digital and which (if any) need to be configured as reference pins.
• Select the appropriate settings for PCFG[3:0] in ADCON1.
• Select ADC module conversion clock by setting ADCS[2:0] in ADCON1 and ADCON0.
• Select justification method – left-justification or right-justification by setting/clearing ADFM in ADCON1.
• Turn on ADC module by setting ADON to 1 in ADCON0.
• Select which channel is to be sampled/measured and converted by setting CHS[2:0] in ADCON0.
• Wait the required acquisition time.
• Start conversion by setting GO/DONE to 1 in ADCON0.
• Check if conversion is over by checking if GO/DONE has been cleared to 0.
• If conversion is over, read the results from ADRESH:ADRESL.

A simple code example will help clear this.

__________________________________________________ __________________________________________________ ________________

CODE EXAMPLES

Example:

We will sample AN0 and AN1. We'll use VDD as VREF+ and VSS as VREF-. We'll use right-justification and display the larger of the two results on PORTD. The PIC 16F877A is to be run off a 16MHz crystal.

Solution:

So, we'll need a setting that allows for AN0 and AN1 to be analogue. We do not need any external reference as VDD and VSS will be used for VREF+ and VREF-. PCFG<3:0> setting of 0100 will give us that (we'll have another analogue channel, but there's no setting for only 2 channels with no reference).

Next, let's calculate the settings for TAD.
FOSC = 16MHz
TOSC = (1/16MHz) = 0.0625&#956;s
1.6&#956;s/0.0625&#956;s = 25.6
So, we should choose 32 TOSC. Let's check. 32 TOSC = 32(0.0625&#956;s) = 2&#956;s. This is marginally larger than the minimum 1.6&#956;s. So, we can choose 64 TOSC which will give a little bit more time. That will give us 64(0.0625&#956;s) = 4&#956;s. This is good and is also significantly larger than the minimum 1.6&#956;s. So this should be chosen unless ADC conversion needs to be very quick, in which case, we'll want to use 32 TOSC, where TAD = 2&#956;s. For 64 TOSC, ADCS2 = 1, ADCS1 = 1, ADCS0 = 0.

We've decided on using right-justification.

As mentioned before, we'll use the arbitrary 50&#956;s acquisition delay, which is larger than the minimum mentioned 19.72&#956;s
Instead of individually selecting the PCFG, ADCS2 and ADFM bits, we can set it all together in one step by writing to the ADCON1 register. Same with ADCON0. Instead of writing to the bits individually, we can write to ADCON0 in one step.

So, for ADCON0, ADCS0 = 0, ADCS1 = 1. We'll first sample channel 0, then channel 1. So, initially, CHS2 = CHS1 = CHS0 = 0. We'll have to turn the ADC on, so ADON = 1. Therefore, bits 7 and 1 are to be set to 1, the rest cleared to 0. This means, ADCON0 must be loaded with the binary value 1000 0001. Represented as hexadecimal, this is 0x81.

Let's move on to ADCON1. ADFM = 1 for right-justification. ADCS2 = 1. Required PCFG<3:0> setting has been found to be 0100. So, bits 7, 6 and 2 are to be set to 1, the rest cleared to 0. This means, ADCON1 must be loaded with the binary value 1100 0100. Represented as hexadecimal, this is 0xC4.

Before going into sampling multiple inputs, let's look at a simple code to read off AN0 (channel 0):

I'm using mikroC PRO for PIC for this. While you can apply this same concept to any compiler, the exact syntax is probably not going to work on another compiler.

Code:
void main(void){ 
	unsigned char l_byte, h_byte; 
	unsigned int ADR; 
	CMCON = 7; //Disable comparator
 
	PORTD = 0; //Clear initial value of PORTD 
	TRISD = 0; //PORTD to be output 
	ADCON0 = 0x81; //Check in description above 
	ADCON1 = 0xC4; //Check in description above 
	while (1){ 
		delay_us(50); //Acquisition Delay 
		GO_DONE_bit = 1; //Set GO_DONE bit to start conversion 
		while (GO_DONE_bit == 1); //Wait for bit to be cleared 
		//If bit is cleared, this means conversion is over 
		l_byte = ADRESL; 
		h_byte = ADRESH; 
		ADR = (h_byte<<8)|l_byte; 
	} 
}

The result is stored in ADR.
The logic in this program is pretty simple and should be easy to understand if you've read through thoroughly.

Now let's make a simple function for the ADC reading.
Code:
unsigned int ADCRead(unsigned char channel){ 
	unsigned char l_byte, h_byte; 
	unsigned int ADR;
 	ADCON0 = 0x81 | (channel << 3); //Change channel
	delay_us(50); //Acquisition Delay 
	GO_DONE_bit = 1; //Set GO_DONE bit to start conversion 
	while (GO_DONE_bit == 1); //Wait for bit to be cleared 
	//If bit is cleared, this means conversion is over 
	l_byte = ADRESL; 
	h_byte = ADRESH; 
	ADR = (h_byte<<8)|l_byte; 
	return ADR; 
}

The value of bits 2, 3 and 4 – CHS – dictates which channel is to be read. Therefore, the value of the channel to be read is shifted to the left thrice, so that CHS = value of channel to be read.

The line:
Code:
ADR = (h_byte<<8)|l_byte;
is used to combine the two 8-bit registers into one 16-bit register. By shifting the 8-bit register h_byte 8 places to the left, it has been made the upper (more significant) 8-bits of the 16-bit register. Thus the lower 8 bits all have a value of 0 and when OR-ed with l_byte, take the value of l_byte.


Now, your task is to find out the result of this line:
Code:
ADCON0 = 0x81 | (channel << 3); //Change channel

For values of channel from 0 to 7, find the corresponding values of ADCON0 and then you will become clear as to how the channel changing is so easily done with just one line.

Now, let's read the two channels AN0 and AN1.
Code:
unsigned int ADCRead(unsigned char channel){ 
	unsigned char l_byte, h_byte; 
	unsigned int ADR; 
	ADCON0 = 0x81 | (channel << 3); //Change channel 
	delay_us(50); //Acquisition Delay 
	GO_DONE_bit = 1; //Set GO_DONE bit to start conversion 
	while (GO_DONE_bit == 1); //Wait for bit to be cleared 
	//If bit is cleared, this means conversion is over 
	l_byte = ADRESL; 
	h_byte = ADRESH; 
	ADR = (h_byte<<8)|l_byte; 
	return ADR; 
} 

 void main(void){ 
	unsigned int Ch0, Ch1;

	CMCON = 7; //Disable ADC

	PORTD = 0; 
	TRISD = 0; 
	ADCON0 = 0x81; 
	ADCON1 = 0xC4; 
	while (1){ 
		Ch0 = ADCRead(0); //Gets reading from channel 0

		Ch1 = ADCRead(1); //Gets reading from channel 1 
		if (Ch1 > Ch0){ //If voltage at AN1 > voltage at AN0 
			PORTD = Ch1>>2; 
		//Display 8-bit ADC result of AN1 on PORTD 
		} 
		else{  
			PORTD = Ch0>>2; 
		//Display 8-bit ADC result of AN0 on PORTD 
		} 	
       }
}

AN0 and AN1 are read and the 8-bit equivalent of the 10-bit ADC result of the greater of the two inputs is displayed on PORTD.

__________________________________________________ __________________________________________________ ________________

Example:

We have a temperature sensor LM35 connected to AN0. We’re going to make a kind of temperature checker. We have a fan connected at PORTD0 and a heater connected at PORTD1. If temperature is less than 25°C, we’ll start the heat. If temperature is greater than 30°C, we’ll start the cooler. For anything in between, they’ll both be off.

Now, if you don’t already know this, the LM35 is a temperature sensor that outputs an analogue voltage corresponding to the temperature. For each °C, the output is 0.01V, ie 10mV. So, if the temperature is 25°C, the output from the LM35 is 0.25V. For a temperature of 35°C, the output from the LM35 is 0.35V. That’s simple enough.

The LM35 is rated for a maximum temperature of 150°C. At 150°C, the LM35 will output 1.5V. But in our application, let’s take maximum temperature of 50°C. That corresponds to an output voltage of 0.5V. We can use the reference voltage as 5V for sake of simplicity or we can take an external reference for improved accuracy.

Let’s take it as 5V for simplicity as we won’t have to connect an external reference.

We’ll use a 16MHz oscillator and the settings are as before.

With 5V reference, 0.25V (for 25°C) gives an ADC conversion result of:
(0.25 / 5) * 1023 = 51

0.35V (for 35°C) gives an ADC conversion result of:
(0.35 / 5) * 1023 = 71

Code:
unsigned int ADCRead(unsigned char channel){ 
	unsigned char l_byte, h_byte; 
	unsigned int ADR; 
	ADCON0 = 0x81 | (channel << 3); //Change channel 
	delay_us(50); //Acquisition Delay 
	GO_DONE_bit = 1; //Set GO_DONE bit to start conversion 
	while (GO_DONE_bit == 1); //Wait for bit to be cleared 
	//If bit is cleared, this means conversion is over 
	l_byte = ADRESL; 
	h_byte = ADRESH; 
	ADR = (h_byte<<8)|l_byte; 
	return ADR; 
} 

 void main(void){ 
	#define Temp35 71

	#define Temp25 51

	unsigned int Ch0; 

	CMCON = 7; //Disable comparator

	PORTD = 0; 
	TRISD = 0; 
	ADCON0 = 0x81; 
	ADCON1 = 0xC4; 
	while (1){ 
		Ch0 = ADCRead(0); //Gets reading from channel 0 
		if (Ch0 > Temp35){
			RD0_bit  = 1;  //Turn on heater
		}
		else{
			RD0_bit = 0; //Turn off heater
		}

		if(Ch0 < Temp25){
			RD1_bit = 1; //Turn on cooler
		}
		else{
			RD1_bit = 0; //Turn off cooler
		}
	} 
}

That should be quite simple to understand. Now, your task should be to write a code with reference voltage 2.5V. You should be able to do that now.

__________________________________________________ __________________________________________________ ________________

Continued in part 6............

Comments

There are no comments to display.

Part and Inventory Search

Blog entry information

Author
Tahmid
Read time
7 min read
Views
1,001
Last update

More entries in Uncategorized

More entries from Tahmid

Share this entry

Back
Top