Using ADC of PIC Microcontroller – MPLAB XC8

Using ADC of PIC Microcontroller – MPLAB XC8

In this tutorial we will learn, how to use the ADC module of a PIC Microcontroller using MPLAB XC8 compiler. For demonstration we will use the commonly available PIC 16F877A microcontroller.

Every physical quantity found in nature like temperature, humidity, pressure, force is analog. We need to convert these analog quantities to digital to process it using a digital computer or a microcontroller. This is done by using Analog to Digital Converters. An Analog to Digital Converter or ADC is a device which converts continuous analog quantity (here : voltage) to corresponding discrete digital values.

PIC 16F877A microcontroller has 8 ADC inputs and it will convert analog inputs to a corresponding 10 bit digital number. For the sake of explanation take ADC Lower Reference as 0V and Higher Reference as 5V.

  • Vref- = 0V
  • Vref+ = 5V
  • n = 10 bits
  • Resolution = (Vref+ – Vref-)/(2n – 1) = 5/1023 = 0.004887V

So ADC resolution is 0.00487V, which is the minimum required voltage to change a bit. See the examples below.


Analog Input Digital Output
Binary Hex Decimal
0 0b0000000000 0x000 0
0.004887V 0b0000000001 0x001 1
0.009774V 0b0000000010 0x002 2
0.014661V 0b0000000011 0x003 3
4.999401V 0b1111111111 0x3FF 1023

The ADC module of PIC 16F877A has 4 registers.

  • ADRESH – A/D Result High Register
  • ADRESL – A/D Result Low Register
  • ADCON0 – A/D Control Register 0
  • ADCON1 – A/D Control Register 1

ADC Block Diagram

ADC Module Block Diagram - PIC16F877A
ADC Module Block Diagram – PIC16F877A

From this block diagram you can easily understand the working of ADC channel selection and reference voltage selection.

ADCON0 – A/D Control Register 0

ADCON0 Register - PIC 16F877A
ADCON0 Register – PIC 16F877A
  • Bit 7 ~ 6 : ADCS1 & ADCS0, A/D Conversion clock selection bits. These bits are used in combination with ADCS2 of ADCON1 register. (See the ADC Clock Selection Table)
  • Bit 5 ~ 3 : CH2, CH1 & CH0 are analog channel selection bits which will select a particular channel out of 8.
  • Bit 2 : Go_nDone is the A/D conversion status bit. Setting this bit initiates the A/D conversion and it will be automatically cleared when the conversion completes.
  • Bit 1 : Unimplemented, Read as 0.
  • Bit 0 : ADON, A/D Module On bit. Setting this bit will turn on the ADC module.

ADCON1 – A/D Control Register 1

ADCON1 Register - PIC 16F877A
ADCON1 Register – PIC 16F877A
  • Bit 7 : ADFM, A/D Result Format bit. Bits of result will be right justified if this bit is set otherwise it will be left justified.
  • Bit 6 : ADCS2, A/D Conversion clock selection bit. This bit is used in combination with ADCS0 and ADCS1 of ADCON0 register. (See the ADC Clock Selection Table)
  • Bit 5 ~ 4 : Unimplemented. Read as 0.
  • Bit 3 ~ 0 : PCFG3 ~ PCFG0, A/D Port Configuration Bits. Status of these bits determine whether the pin is Analog or Digital as per the table below.
PIC ADC Port Configuration Bits
PIC ADC Port Configuration Bits

Note : Some ICs use special register ANSEL for setting each pin as analog or digital.

A/D Acquisition Time

Holding capacitor (CHOLD) must be charged to the input voltage to meet the accuracy specified by the datasheet. So we must provide a delay greater than the minimum required acquisition time to charge the capacitor. 19.72μs is the minimum time specified in the datasheet. Please refer the datasheet for more details.

A/D Clock Selection

ADC Clock Conversion - PIC 16F877A
ADC Clock Conversion – PIC 16F877A

A/D conversion clock must be selected to ensure minimum TAD. TAD is the conversion time per bit, which is 1.6μs. Please refer the datasheet for more details.

ADC Clock Selection Table - PIC 16F877A
ADC Clock Selection Table – PIC 16F877A

Circuit Diagram

Using Internal ADC Module of PIC Microcontroller
Using Internal ADC Module of PIC Microcontroller

You can easily understand the circuit if you already go through our first tutorials, PIC Microcontroller MPLAB XC8 Tutorials. The reference voltages for A/D conversion is set to VDD (5V) and VSS (GND) in the software (see the code below). Analog input to Channel 0 is provided using a potentiometer such that we can vary the input voltage from 0 ~ 5V. A/D conversion will generate a 10 bit digital value (0 ~ 1023) corresponding to the analog input. This digital value is displayed using 10 LEDs connected PORTB and PORTC of the microcontroller.

MPLAB XC8 Code

// CONFIG
#pragma config FOSC = HS      // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF     // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF    // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF    // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF      // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF      // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF      // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF       // Flash Program Memory Code Protection bit (Code protection off)

#include <xc.h>
#include <pic16f877a.h>

#define _XTAL_FREQ 8000000

void ADC_Init()
{
  ADCON0 = 0x81;               //Turn ON ADC and Clock Selection
  ADCON1 = 0x00;               //All pins as Analog Input and setting Reference Voltages
}

unsigned int ADC_Read(unsigned char channel)
{
  if(channel > 7)              //Channel range is 0 ~ 7
    return 0;

  ADCON0 &= 0xC5;              //Clearing channel selection bits
  ADCON0 |= channel<<3;        //Setting channel selection bits
  __delay_ms(2);               //Acquisition time to charge hold capacitor
  GO_nDONE = 1;                //Initializes A/D conversion
  while(GO_nDONE);             //Waiting for conversion to complete
  return ((ADRESH<<8)+ADRESL); //Return result
}

void main()
{
  unsigned int a;
  TRISA = 0xFF;                 //Analog pins as Input
  TRISB = 0x00;                 //Port B as Output
  TRISC = 0x00;                 //Port C as Output
  ADC_Init();                   //Initialize ADC

  do
  {
    a = ADC_Read(0);            //Read Analog Channel 0
    PORTB = a;                  //Write Lower bits to PORTB
    PORTC = a>>8;               //Write Higher 2 bits to PORTC
    __delay_ms(100);            //Delay
  }while(1);                    //Infinite Loop
}

I hope that the program is self explanatory as it is well commented.

Download Here

You can download the complete zipped project folder here.

I hope that you understand everything what I explained. Please do a comment if you have any doubt, which will help me to improve this tutorial.

Share this post

  • 1. ADC_Read() function is a general function. This just an example program. You may use other ADC channels if required. Other channels are not used in this program.
    2. This is done just to make ADC_Read() a general function.
    3. It is not for changing justification. One register in this microcontroller holds only 8bits. I guess this MCU is having 10bit ADC. So it just combines ADC result registers to a single value.

  • I have a few queries.
    1. channel is always going to be 0. Therefore what is the necessity to check if channel >7 in the ADC_read() ?
    2. Subsequently, what is the function of resetting the channel bits and setting them again in the next two lines. Can we not configure this permanently in the ADCON1 and ADCON0 registers?
    3. Also, is my understanding accurate as regarding to the 8 right shifts you do when returning the result of ADC_read() is to change justification of the ADC result? Can this not be achieved by setting ADFM ( ADCON1.7) =1 [which means right justified]

    Thank you for the wonderful content. This is Very helpful.

  • Thanks a lot respected Mr George for well commented program. It is very useful.
    With Regards,
    S S RANA

  • ADCON0 &= 0xC5; is equivalent to
    ADCON0 = ADCON0 & 0xC5; (ADCON0 & 1011 0101)=>( 1000 0001 & 1011 0101)

    ADCON0 |= channel<<3; is equivalent to
    ADCON0= ADCON0 | (channel< ADCON0 | (0000 0000< (1000 0001 | 0000 0000)
    {i.e if channel is 1 then => ADCON0 | {0000 0001< (1000 0001 | 0000 1000) }
    Hope it helps

  • Hi! I have a little problem. I write a loop to read the ADC channel 0 every 500ms, but the ADC change the result sometimes. I put the AN0 input on 0V and the ADC continue doing this. The great problem is the result, it’s many bits of error. Could you help me to know what happen?

  • what’s different between this pic (16f877) and 18f45k22? I traied to use your code with some little changes for my pic but it doesn’t work…Could you help me?

  • For anyone wondering why their code only showing zeroes for the lower 6 bits, it’s because you are copy and pasting the code on this page which is left justified. Simply change ADCON1 to 0x80 to fix this.

  • (ADRESH<<8)+ADRESL)?
    Please help me understand, i can see ADFM=0 and how the result is left justified.
    Shouldnt the result be (ADRESH<>6)?
    *(ADRESH<>6) = moving the lowest from bits 7-6 to 1-0 so they add onto the gaps made by ADRESH<<2.

    Thanks btw this was very helpful.

  • I cant understand these two lines.
    ADCON0 &= 0xC5; //Clearing the Channel Selection Bits
    ADCON0 |= channel<<3; //Setting the required Bits

  • that is only a 1 bit problem. That might be caused due to some voltage issue. A difference of 0.004887V can cause it. Nothing to worry about it.

  • Hey, i have a problem with RB0 bit. My hardware and software are exactly the same, but RB0 is always inverted result, when analogue input is 5V, all leds light up except RB0, at the other end when input is 0, only RB0 is HIGH, any idea whats happening? Already tried with disabling interupt register INTCONbits.INTE = 0;

  • thanks for your reply……

    bro actually i planned to build temperature sensor using mplab x and lm35…….how to convert the lm35 reading to actual value….i tried with above one by using adc= ((ADC_value*5)/1024); but it’s not working help me bro

  • thanks for explanation but one small doubt

    ADCON0 &= 0xC5; //Clearing channel selection bits
    ADCON0 |= channel<<3; //Setting channel selection bits
    __delay_ms(2); //Acquisition time to charge hold capacitor
    GO_nDONE = 1; //Initializes A/D conversion
    while(GO_nDONE); //Waiting for conversion to complete
    return ((ADRESH<<8)+ADRESL); //Return result

    why u used & AND symbol and OR symbol in ADCON0 and return statement also?i am poor in coding will u help me pls
    and also displaying value of a into high and low bit?

  • Thank you very much for a great explanation. This is the first time that I’ve seen an ADC function carried out using the __delay function, which seems a great idea. However, I’ve not been able to understand why you delay 2ms. Would it not be more efficient to delay, say 22us? Or have I misunderstood the A/D Acquisition time element?


  • >