Analog to digital Conversion is an important feature of a microcontroller. Though previously, microcontrollers like 8051 did not have an internal ADC module, newer microcontrollers do possess this feature.
Signals like voltage, current as well as the output produced by different sensors are analog in nature. Sensors and transducers measure physical quantities and produce output in terms of varying voltages. These signals must be converted into binary data for the microcontroller to take appropriate action. This is where an ADC comes into the picture.

 

ADC in PIC16F887 microcontroller

PIC16F887 uses a 10-bit ADC module to convert an analog signal into binary form. So the maximum count that can be obtained is 2^10 which gives 1024. It consists of 14 Analog channels(AN0-AN13). The ADC voltage reference is software selectable which is generated internally or can be provided externally through Vref+ and Vref- pins.The ADC interrupt can be enabled or disabled. When enabled, interrupt occurs on completion of 1 full conversion.

There are a few registers associated with ADC in PIC16F887. Let us understand the role of each register.

Registers associated with ADC
  • TRIS
  • ANSEL-ANSELH
  • ADCON0
  • ADCON1
  • ADRESH-ADRESL
TRIS register (TRISA, TRISB, TRISE)

TRIS registers are data direction registers which are used to configure a port pin either as an input or as an output. It must be set to 0 for using it as an output and must be set to 1 for using it as an input. For a pin to be used as analog, it must be set as an input. In pic16f887, there are in all 14 Analog channel with 5 on PORTA, 3 on PORTE and remaining channels on PORTB. So if a pin with an analog function on PORTA is to be used, then the respective bit must be set as an input using TRISA register.

 

ANSEL-ANSELH registers

ANSEL

ANS7 ANS6 ANS5 ANS4 ANS3 ANS2 ANS1 ANS0

ANSELH

ANS13 ANS12 ANS11 ANS10 ANS9 ANS8

These are a set of 2 registers. In the above two registers, you’ll find that the number of bits in these 2 registers is equal to the number of ADC channels, which is 14. If you look at the datasheet, the analog channels are labeled as AN0, AN1, AN2… AN13.
Bit ANS0 is the input mode bit for AN0. Similarly, bit ANS1 is the input mode bit for AN1, ANS2 for AN2, and it goes on till AN13.
ANSEL and ANSELH are used to set the input mode of a pin. While using an ADC channel, the associated ANSEL bit must be set to 1, and the rest must be cleared.

1- Analog input. Pin is assigned as analog input.
0- Digital I/O

 

ADCON Register: A/D control register 0
ADCS1 ADCS0 CHS3 CHS2 CHS1 CHS0 GO/D͞O͞N͞E ADON

ADCS1: ADCS0 – A/D Conversion Clock source
These bits are used to select the clock source for A/D conversion.
0:0 = Fosc/2
0:1 = Fosc/8
1:0 = Fosc/32
1:1 = Frc (Dedicated internal oscillator)

CHS3: CHS0 – ADC channel selection bit
ADC channel selection can be done using channel selection bits.
0000 = AN0
0001 = AN1
0010 = AN2
0011 = AN3
..
..
..
1100 = AN12
1101 = AN13
1110 = Cvref
1111 = Fixed Reference( 0.6V Fixed voltage reference)

GO/D͞O͞N͞E – A/D conversion bit
This bit is set by the programmer to begin Analog to digital conversion. This bit is automatically cleared when the conversion is completed. ADIF flag is set after one complete conversion. ADC interrupt will occur if enabled.

ADON – ADC enable bit
ADON must be set to enable ADC. ADC is disabled when ADON is cleared.

 

ADCON1 Register: A/D control register 1
ADFM VCFG1 VCFG0

ADFM – A/D result format select bit
1 = Right justified (See ADRESH-ADRESL para)
0 = Left justified

VCFG1: VCFG0 – These bits are used to set the reference voltage for ADC. The reference voltage can be provided externally through Vref+ and Vref- pin or can be set to VDD and VSS.

VCFG1 – Voltage reference bit
1 = Vref- pin
0 = Vss

VCFG0 – Voltage reference bit
1 = Vref+ pin
0 = Vdd

 

ADRESH – ADRESL registers

ADRESH and ADRESL are 2, 8-bit registers which are used to store the ADC conversion result. Since pic16f887 uses a 10 bit ADC, the value obtained after conversion would be a binary number containing 10-bits. This result is automatically stored in the ADRESH-ADRESL register pair. The way this result is stored depends on the bit ADFM in ADCON1 register.

When ADFM = 0, the higher 8 bits of the 10-bit result is stored in ADRESH and the remaining 2 bits are stored in ADRESL register.
When ADFM = 1, the higher 2 bits of the 10-bit result is stored in ADRESH register and the remaining 8 bits are stored in ADRESL register.

Following image show result formatting for ADC

ADC result formatting. Left justified and right justified

 

Steps involved in ADC programming
  • Set the pin mode as input using the data direction registers (TRIS register).
  • Select the input mode as analog by setting the appropriate ANSEL bit.
  • Choose the ADC channel and select the ADC clock source using the ADCON0 register.
  • Select result formatting and ADC reference voltage using the ADCON1 register.
  • Wait for acquisition time.
    Acquisition time is the time required for the holding capacitor in the sample and hold circuit to charge after each ADC conversion.
  • Set Go/D͞O͞N͞E bit to begin conversion.
  • ADIF flag in PIR1 register is set and Go/D͞O͞N͞E bit is automatically cleared when the conversion is completed.
  • Read ADC counts from ADRESH-ADRESL register

ADC program for PIC16f887 and the required circuit connections.

Following circuit shows circuit connections for ADC

Circuit connection for displaying ADC value on LCD

MPLAB X program for displaying ADC counts on LCD using  PIC16F887 Microcontroller.

In the following program, no LCD libraries have been used. Everything is simply coded from top to bottom. If you’re not sure about how LCD is programmed, then I would recommend you to look at my previous tutorial Displaying message on character LCD.


#pragma config FOSC = INTRC_NOCLKOUT
#pragma config WDTE = OFF   
#pragma config PWRTE = OFF  
#pragma config MCLRE = ON   
#pragma config CP = OFF     
#pragma config CPD = OFF    
#pragma config BOREN = ON   
#pragma config IESO = OFF   
#pragma config FCMEN = OFF      
#pragma config LVP = OFF        

#pragma config BOR4V = BOR40V  
#pragma config WRT = OFF        

#include <xc.h>
#include<pic.h>
#include<pic16f887.h>
#define _XTAL_FREQ 4000000

//LCD FUNCTIONS
void lcd_cmd(unsigned char c);
void lcd_data(unsigned char d);
void lcd_init(void);
void enable(void);
char str[] = "ADC counts";
void lcd_print(char *ptr);

// ADC FUNCTIONS AND VARIABLE DECLERATIONS
int ADC_read(void);
void init_ADC(void);
unsigned int get_count(void);
unsigned int value;

int arr_dec[17]  ={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; // DECIMAL ARRAY
int arr_hex[3] = {}; // ARRAY TO HOLD INDIVIDUAL HEX VALUE
char decimal_data[3] = {}; // ARRAY TO STORE INDIVIDUAL DECIMAL VALUE
int power(int, int);    // FUNCTION TO CALCULATE POWER ("RAISE TO")

int main()
{
  unsigned int count;
  int temp;
    
    TRISD = 0x00;
    TRISC = 0x00;
    TRISA = 0x01;
    PORTD = 0x00;
    PORTC = 0x00;
    PORTA = 0x00;
    ANSEL = 0b00000001;
        
    lcd_init();
    lcd_cmd(0x80);
    lcd_print(str);  // PRINT "ADC COUNTS"
    
    init_ADC();
    
       while(1)
    {  
        lcd_cmd(0xc0);
        value = ADC_read(); // READ ADC FROM CHANNEL 1; VALUE WILL BE 
                            // STORED IN HEXDECIMAL FORM
    
     for(int i=0; i<3; i++)
      {                   arr_hex[i] = (value%16);    //SEPERATE INDIVIDUAL HEX BITS 
      temp = arr_hex[i];
      value /= 16;
      decimal_data[i] = arr_dec[temp]; // COMPARE HEX BITS WITH DECIMAL BIT ARRAY
                                       // TO STORE DECIMAL VALUES IN DECIMAL_DATA 
      }

          count = get_count();      //FUNCTION TO CONVERT HEXADECIMAL VALUE TO DECIMAL   
          
          lcd_data((count/1000)+48); // SEPERATE BITS AND PRINT
          count = count%1000;     
          lcd_data((count/100)+48);
          count = count%100;
          lcd_data((count/10)+48);
          count = count%10;
          lcd_data((count)+48);
       
  __delay_ms(300);
}
}


void init_ADC(void)
{
    ADCON0 = 0x41;  // Selecting ADC clock as Fosc/8; Channel 0; Enabling ADC module
    ADCON1 = 0x80;  // Setting voltage reference to VSS and VDD: Right justified
}


    
int ADC_read(void)
{
     ADCON0 = 0x41;
    GO_nDONE = 1;
    while(!GO_nDONE);
    return ((ADRESH<<8)+ADRESL);
  
}

void lcd_init(void)
{
    // LCD initial initialization 
    
    __delay_ms(15);
    lcd_cmd(0x03);
    __delay_ms(5);
    lcd_cmd(0x03);
    __delay_ms(5);
    lcd_cmd(0x03);
    __delay_ms(5); 
    lcd_cmd(0x02);
    __delay_ms(5);
    
    // LCD command setting initialization
    
    lcd_cmd(0x28);       // 4 bit interface length. 2 rows enabled.
    __delay_ms(5);
    
    lcd_cmd(0x10);       // Move cursor or shift display
    __delay_ms(5);
    
    lcd_cmd(0x0c);       // Enable display. Cursor off.
    __delay_ms(5);
    
    lcd_cmd(0x06);      // Increment cursor position after each byte 
    __delay_ms(5);
    
    lcd_cmd(0x01);      // clear display
    __delay_ms(5);
    
}

void lcd_cmd(unsigned char c)
{
    RD7 = 0;
    RD6 = 0;
    PORTC = (PORTC&0x0f)|(0xf0&c);
    enable();
    PORTC = ((PORTC&0x0f)|(0x0f&c)<<4);
    enable();
}

void lcd_data(unsigned char d)
{
    RD7 = 1;
    RD6 = 0;
    PORTC = (PORTC&0x0f)|(0xf0&d);
    enable();
    PORTC = ((PORTC&0x0f)|(0x0f&d)<<4);
    enable();
    
}

void enable(void)
{
    RD5 = 1;
    __delay_ms(5);
    RD5 = 0;
}

void lcd_print(char *ptr)
{
    while(*ptr != '\0')
    {
        lcd_data(*ptr);
        ptr++;
    }
}

unsigned int get_count(void)  // FUNCTION TO CONVERT HEX NUMBER IN DECIMAL
{
    int i;
    unsigned int count=0;
    for(i=2; i>=0; i--)
    {
        count += (decimal_data[i]*power(16,i));  // FORMULA FOR CONVERSION
    }
    
    return count;
}

int power(int x, int y)  // FUNCTION TO CALCULATE POWER
{
    unsigned int result = 1;
    while(y!=0)
    {
        result = result*x;
        y--;
    }
    return result;
}


Following images shows ADC counts on character lcd.

Displaying ADC counts on LCD.

Following image shows adc counts on LCD.

ADC counts on LCD when pot is set to minimum.


Moiz

Electronics engineer graduated from M.H. Saboo Siddik college of engineering. Currently working as Jr. Innovative engineer. Skilled in 8051, PIC and ARM microcontrollers. Circuit analyzation and Debugging. Constantly looking to acquire more skills which would help myself to become more proficient in embedded domain. Founder and blogger at techetrx.com LinkedIn profile: https://www.linkedin.com/in/moiz-shaikh-305294137

3 Comments

Vinay S · August 27, 2019 at 11:08 am

Hi, thanks for the detailed article. I have a small question though.. How do i connect analog input directly to pin AN1? Say, if i have a transformer which gives secondary output as 3V, how do it connect it to pin RA0 here?
Also, if you could please help with the formula to calculate the voltage and display it in LCD, it would be nice. Thanks for your effort so far.

    Moiz · September 7, 2019 at 7:09 pm

    Glad you liked it.

    Considering your output is properly rectified, connect a 4.9V Zener diode and 0.1uF ceramic cap in parallel with your ADC pin.
    As for the formula, since the ADC is 12-bit, maximum count i.e 1023 corresponds to 5V.
    So if you intend to display the voltage, you can use x = (5*ADC_Count)/1023. Where ADC_count is the obtained ADC value and x is the voltage you want to display.

sudhir · February 28, 2020 at 12:18 pm

This article is really helpful. Thanks alot.

Leave a Reply

Your email address will not be published. Required fields are marked *