PIC16F877A; having trouble with ADC module (more detail below)

Thread Starter

Marcus314

Joined Jun 28, 2017
7
Let me begin slowly and clear: recently I started with PIC microcontrollers; having done several basic projects for newscomers to PIC microcontrollers such as flashing an LED on/off, interfacing a 7-segment display, and even interfacing a LCD display. To be honest; most of the things I've learned about PIC microcontrollers have been thanks to YouTube channel Student Companion; as well as webpage electroSome. I had no previous experience with PIC microcontrollers (and microcontrollers in general tho...). However my problem begins when I want to use the ADC module of a PIC16F877A. I'm using MPLAB X IDE v3.55 with XC8 compiler. Now I know that for the ADC I have to know how to use 4 registers that control the behavior of the ADC which are:
  1. ADCON0
  2. ADCON1
  3. ADRESH
  4. ADRESL

The first two ones are for configuring the behavior of the ADC; whereas the last two are just registers on where the ADC conversion value will be stored.
Now I'm going to explain my circuit with this figure:
What I want to do is to control five LEDs using a potentiometer through a PIC16F877A. This is my line of code I wrote in MPLAB X:

I want to declare that I actually don't know how to "read" the value of the potentiometer; so the part of the function read_ADC(){} was actually taken from the electroSome site. For more details check the page at: https://electrosome.com/adc-pic-microcontroller-mplab-xc8/
However I kind of know how to initialize my ADC module and this is done declaring the bits of the ADC module registers ADCON0 and ADCON1.
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)

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

#include <xc.h>
#include <stdlib.h>
#include <stdio.h>
#define _XTAL_FREQ 4000000

void init_ADC(void){
    //ADCON0 register bits
    ADCON0=0b01000000;
    //ADCON1 register bits
    ADCON1=0b00001110;
    return;
}
unsigned int read_ADC(unsigned int channel){
    if(channel>7){ //ADC channels are through AN0-AN7
        return 0;
    }else{
        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(void){
    TRISAbits.TRISA1=0;
    TRISAbits.TRISA2=0;
    TRISAbits.TRISA3=0;
    TRISAbits.TRISA4=0;  
    TRISAbits.TRISA5=0;  
    init_ADC();
    int adc;
    while(1){
        adc=read_ADC(0);
        if(adc>300&&adc<500){
            PORTAbits.RA1=1;
            PORTAbits.RA2=1;
            PORTAbits.RA3=1;
            PORTAbits.RA4=1;
            PORTAbits.RA5=1;
        }else{
            PORTAbits.RA1=0;
            PORTAbits.RA2=0;
            PORTAbits.RA3=0;
            PORTAbits.RA4=0;
            PORTAbits.RA5=0;
        }
    }
    return;
}
Now as you can see in the schematic the only bit of PORTA that I want to use as analog input is RA0/AN0; and I want to use the rest of PORTA as digital I/O which is where I'm going to connect my five LEDs. I could have connected my LEDs to either PORTB, PORTC, or PORTD, however for the sake of saving pins I will just use one port: PORTA so I can use the other unused ports for other tasks (like interfacing a LCD display, a keypad, or any other thing for the like).

The description so is that I want to use FOSC/2; only 1 analog channel and the rest as digital I/O with reference voltages in Vss and Vdd, and the channel to be read is AN0; with my result to be left justified. Therefore with all these descriptions and since I only want to initialize my ADC then the init_ADC(){} function is:
Code:
void init_ADC(void){
    //ADCON0 register bits
    ADCON0=0b01000000;
    //ADCON1 register bits
    ADCON1=0b00001110;
    return;
}
There is no syntax error in my code since when I built it MPLAB terminal said BUILD SUCCEDEED. I then had put the resulting hex file in the PIC16F877A microcontroller from the simulation in Proteus and setting the processor clock frequency to 4MHz. I know that the value to be read from the ADC is from 0-1023 (there are 1024 values that can be stored in 10 bits after the conversion which are from 0000000000 to 1111111111) in integer form so when the potentiometer analog input is between 300 and 500 then ALL LEDs must turn on. However when I ran the circuit in Proteus this never happens; and pins RA1-RA5 are always in logic 0.

I don't know where the problem is because I previously did the electroSome circuit in the link I posted in blue (the circuit with the 10 LEDs); however the LEDs were in a different PORT that has no analog inputs and they declared ALL pins of PORTA as analog inputs (PCFG=0000) yet they only use ONE pin; which I think is a waste of pins that could be used for other tasks. I don't know if it is actually possible to do what I want in this circuit and if it is I would appreciate your answers on where my code is wrong and how I could improve it so I can correctly use the ADC module of the PIC16F877A.

Moderator edit: added code tags
 

be80be

Joined Jul 5, 2008
2,072
if(adc>300&&adc<500) that would never happen
&& is the logical AND operator.
if(adc>300&adc<500)
& is the bitwise AND operator
 

be80be

Joined Jul 5, 2008
2,072
Your copying what someone did and thats part of the problem too.

Code:
unsigned int read_ADC(unsigned int channel){
    if(channel>7){ //ADC channels are through AN0-AN7
        return 0;
    }else{
        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
    }
look at that and see what you think is going on
 

Thread Starter

Marcus314

Joined Jun 28, 2017
7
I don't know if you could explain me a series of steps that could be followed for properly using the ADC of PIC16F877A. There are like two pages more that also use that code you said I copied yet they still don't give any good explanation on what it actually means...
Your copying what someone did and thats part of the problem too.

Code:
unsigned int read_ADC(unsigned int channel){
    if(channel>7){ //ADC channels are through AN0-AN7
        return 0;
    }else{
        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
    }
look at that and see what you think is going on
 

Thread Starter

Marcus314

Joined Jun 28, 2017
7
I think the problem has its roots on the fact that even if I initiate my ADC module; I still don't know how to read an analog channel properly.
 

be80be

Joined Jul 5, 2008
2,072
This maybe a better way to see how it works i didn't test the changes.
It's a sample using AN0
Code:
//ADC example program written for PIC programming tutorial.
//Target Device PIC16F877A
//Compiler: XC8
//Author  : Anil C S
//Website : www.technoburst.net
//Email   : anil@technoburst.net , technoburst@gmail.com
// Changed to use leds on PORTA
#include <stdio.h>
#include <stdlib.h>

#define _XTAL_FREQ 20000000     //defining crystal frequency to 20 MHz

// PIC16F877A Configuration Bit Settings

#include <xc.h>

// 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 = ON       // Brown-out Reset Enable bit (BOR enabled)
#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)


void main()
{
    unsigned short ADCResult;

    TRISA = 0b00000001;     //Setting PORTA as output port
    PORTA = 0b00000000;     //Initializing PORTA

    ADCON0bits.ADCS  = 0;   //Selecting the clk division factor = FOSC/2
    ADCON1bits.ADCS2 = 0;   //Selecting the clk division factor = FOSC/2

    ADCON1bits.ADFM = 1;    //Result right justified

    ADCON1bits.PCFG = 0b1110;    //Setting AN0  analog port as Analog input

    ADCON0bits.ADON = 1;    //Turns on ADC module

    ADCON0bits.CHS = 0;     //Selects channel 0 ( AN0 )
  
    while(1)
    {
    __delay_us(25); //Waits for the acquisition to complete

    ADCON0bits.GO = 1;   //Starts ADC conversion

    while (ADCON0bits.nDONE) continue;   //wait till ADC conversion is over

    ADCResult = (ADRESH<<8) + ADRESL ;   //Merging the MSB and LSB

   
 
 if(ADCResult<512)
    {
            PORTAbits.RA1=1;
            PORTAbits.RA2=1;
            PORTAbits.RA3=1;
            PORTAbits.RA4=1;
            PORTAbits.RA5=1;
    }
    else
    {
            PORTAbits.RA1=0;
            PORTAbits.RA2=0;
            PORTAbits.RA3=0;
            PORTAbits.RA4=0;
            PORTAbits.RA5=0;
    }
    }
}
 
Last edited:

jjw

Joined Dec 24, 2013
823
In your original code the ADC result should be right justified.
Change ADCON1= 0b10001110
Or use the example of be80be
 
Last edited:
Top