Sunday, May 31, 2020

Interfacing a parallels in serial out shift register to PIC16F887 via digital pins

A digital IC, 74HC165 is a parallel in serial out shift registers. It has 8 parallel input and one serial output. It's commonly used for inputs expanding when the controller run out of digital I/O. It's communication interface's just like an SPI protocol. But without using the SPI module of the MCU, we can use software emulation to function an SPI-like protocol.


Interfacing a parallel in serial out shift register to PIC16F887 via digital pins
Pins diagram

Interfacing a parallel in serial out shift register to PIC16F887 via digital pins
Timing diagram of 74HC165

The load pin used for loading the 8-bit parallel inputs D0 to D7. The clock pin used for scanning all the inputs and shift them out serially via Serial Out pin.


Interfacing a parallel in serial out shift register to PIC16F887 via digital pins
The 74HC165 clock pin connects to RE0. The load pin connects to RE1.
And the serial out pin connects to RE2. PortD represents the digital data.

Source code:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
char serialRead(){
    char read=0,temp=0;
    /*Raise load pin*/
    RE1=1;
    for(int i=0;i<8;i++){
        /*Shift data left*/
        temp<<=1;
        /*if serial out high
         set read high*/
        read=((RE2==1)?1:0);
        /*clock pulse*/
        RE0=0;
        for(int k=0;k<100;k++);
        RE0=1;
        /*load data into variable*/
        temp|=read;      
    }
    /*Lower load pin*/
    RE1=0;
    return temp;
}
void main(){
    /*Port configuration*/
    PORTD=0x00;
    PORTE=0x00;
    TRISD=0x00;
    TRISE=0x04;
    /*Clear analog function
     on PortE*/
    ANSEL=0x00;
    while(1){
        /*Read the digital inputs*/
        PORTD=serialRead();
    }
}

Interfacing a parallel in serial out shift register to PIC16F887 via digital pins
A snap shot of running program, The 74HC165 loads 0x0F.


Interfacing the 74HC595 shift registers with PIC16F887 digital pins

The 74HC595 shift registers is a serial in parallel out shift register type. We just use three digital pins to send the data the register. Using these three wires could drive multiple of this IC.
For more detail about 74HC595, please see this post.

In this example I use two registers to show a timing of 60 seconds.

Interface the 74HC595 shift registers with PIC16F887 digital pins
Schematic diagram

Source code is here:

Interface the 74HC595 shift registers with PIC16F887 digital pins
A screen shot while the simulation shows 17 seconds count.


If you want a standard PCB for ATMega32 micro-controller, you can order my AVR Microcontroller project from PCBWay with a reasonable price. Click here to get a free $5 credit for new account.

Interfacing ATMega32 to 74HC595 shift register
ATMega16 ATMega32 Experiment Board PCB from PCBWay



Interfacing LDR and NTC thermistor to PIC16F887 ADC module

A Light dependent resistor (LDR) is a resistor whose resistance varies depend on the light source it absorbs. Its electrical resistance decrease up on the intensity of light. With this feature we can create an electric circuit to create a variable output voltage fed to the MCU ADC. It usually used for controlling the output lamp in house.

Interfacing LDR and NTC thermistor to PIC16F887 ADC module
Some LDR I posses.


A negative temperature coefficient thermistor (NTC) is a resistor whose resistance decrease up on the heat it absorbs. It usually used for controlling the output cooling fan speed.

Interfacing LDR and NTC thermistor to PIC16F887 ADC module
Some 10k NTC I use for my own workshop


In this example, I use an LDR for turning a lamp on and off. An NTC is used for turning the output fan on and off.

Interfacing LDR and NTC thermistor to PIC16F887 ADC module
LDR create a variable output analog voltage fed to AN12. Its output is a lamp controlled
by RD7. NTC fed an analog output voltage to AN11 with the corresponding RD0 DC fan
motor output.

The program use timer 0 to schedule the ADC reading every one second. ADC conversion doesn't use any ADC interrupt.

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000
void portSetup(void){
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTD=0x00;
    TRISB0=1;
    TRISB4=1;
    TRISD=0x00;
}
void adcSetup(void){
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS12=1;
    ANS11=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    GO=1;
    while(GO);
}
void interruptSetup(void){
    /*Select FOSC*/
    T0CS=0;
    /*Select timer 0 Prescaler*/
    PSA=0;
    /*Enable Timer 0 Overflow
     interrupt*/
    T0IE=1;
    /*Turn on Global interrupt
     Control*/
    GIE=1;
    /*Clear interrupt flag*/
    T0IF=0;
    /*Clear timer 0 register*/
    TMR0=0;
}
unsigned int adcResult;
unsigned char oneSecondTick=0;
void main(void){
    portSetup();
    adcSetup();
    interruptSetup();
    while(1){
        /*If it's one second*/
        if(oneSecondTick>=15){
        /*Select NTC*/
        ADCON0bits.CHS=0x0B;
        __delay_ms(10);
        GO=1;
        while(GO);
        adcResult=(ADRESH<<8)+ADRESL;
        RD0=(adcResult>512)?0:1;
        /*Select LDR*/
        ADCON0bits.CHS=0x0C;
        __delay_ms(10);
        GO=1;
        while(GO);
        adcResult=(ADRESH<<8)+ADRESL;
        RD7=(adcResult<512)?0:1;
        oneSecondTick=0;
        }
    }
}
void interrupt _ISR(void){
    /*If timer 0 Overflow*/
    if(T0IF){
        oneSecondTick+=1;
        T0IF=0;
    }
}

It is just a software simulation. In the physical hardware test we can find an appropriate value of ADC result to fit the application.

Interfacing LDR and NTC thermistor to PIC16F887 ADC module
A sample of the running program in software

Saturday, May 30, 2020

PIC16F887 reads the ADC using Interrupt

The ADC module could trigger an interrupt flag when the reading is completed. Using the interrupt makes the running tasks more effective since it doesn't need to wait for reading completion in the main program loop.

To use the ADC interrupt, we must enable the,
  • Global interrupt control bit (GIE)of the INTCON register
  •  ADC interrupt enable bit (ADIE) of the PIE1 register,
and clear the ADC complete reading interrupt flag (ADIF) of the PIR1 register. 

To read the ADC using interrupt method,
  1. set 'GO' of the ADCON0 to '1'
  2. test the ADIF flag 
  3. if ADIF = 1, we can get the ADC result
  4. clear ADIF
Programming the interrupt of ADC reading

Programming the interrupt of ADC reading

Programming the interrupt of ADC reading

In this example, I use ADC interrupt to return a completed ADC reading result. Timer 0 is used for initiating the ADC reading for every one second. Both ADC and timer use interrupt ISR.

Programming the interrupt of ADC reading
Analog voltage input fed to AN12. A multiplexed SSD display the ADC reading
result in decimal.

C source code's here:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(unsigned int result){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 //Digit 1 1000's
 PORTD=0x00;
 PORTC=ssd[result/1000];
 if(result>=1000)PORTD=0x01;
 __delay_ms(10);
 //Digit 2 100's
 PORTD=0x00;
 PORTC=ssd[(result%1000)/100];
 if(result>=100)
     PORTD=0x02;
 __delay_ms(10);
 //Digit 3 10's
 PORTD=0x00;
 PORTC=ssd[(result%100)/10];
 if(result>=10)
     PORTD=0x04;
 __delay_ms(10);
 //Digit 4 1's
 PORTD=0x00;
 PORTC=ssd[result%10];
 PORTD=0x08;
 __delay_ms(10);
}
unsigned int readADC(void){
    GO=1;
    while(GO);
    __delay_ms(10);
    return (ADRESH<<8)+ADRESL;
}
void portSetup(void){
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    TRISB=0x01;
    TRISC=0x00;
    TRISD=0x00;
}
void adcSetup(void){
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS12=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN12 RB0*/
    ADCON0bits.CHS=0b1100;
    
}
void interruptSetup(void){
    /*Select FOSC*/
    T0CS=0;
    /*Select timer 0 Prescaler*/
    PSA=0;
    /*Enable Timer 0 Overflow
     interrupt*/
    T0IE=1;
    /*Turn on Global interrupt
     Control*/
    GIE=1;
    /*Enable Peripheral Interrupt*/
    PEIE=1;
    /*Enable ADC complete reading
     interrupt*/
    ADIE=1;
    /*Clear ADC interrupt Flag*/
    ADIF=0;
    /*Clear interrupt flag*/
    T0IF=0;
    /*Clear timer 0 register*/
    TMR0=0;
}
unsigned int adcResult;
unsigned char oneSecondTick=0;
void main(void){
    portSetup();
    adcSetup();
    interruptSetup();    
    while(1){
        driveDisplays(adcResult);
    }
}
void interrupt _ISR(void){
    /*If timer 0 Overflow*/
    if(T0IF){
        oneSecondTick+=1;
        T0IF=0;
    }
    /*If it's one second*/
    if(oneSecondTick>=15){
        oneSecondTick=0;
        GO=1; 
    }
    /*If ADC reading completed*/   
      if(ADIF){
        adcResult=(ADRESH<<8)+ADRESL;
        ADIF=0;
    }
}

Programming the interrupt of ADC reading
The ADC reading acquires 921 digital value result in decimal. 

Back to main tutorials page.




Designing a positive and negative DC voltage meter using the 10-bit ADC module

The ADC module's resolution is up to 10-bit, yield an acceptable measuring accuracy with the voltage within 50 V DC.

In this example, I use this ADC module to measure a DC voltage within -25 V to +25 V. So the total voltage magnitude is 50 V.

Since the maximum reference voltage is 5 V in magnitude. I use a voltage divider circuit to scale the input voltage to the lower 5 V.


Designing a positive negative voltage meter using the 10-bit ADC module

From the voltage divider:

Designing a positive negative voltage meter using the 10-bit ADC module

The V(ADC) is at 5 V maximum. V(MEASURE) could be 50 V in magnitude. So we divide the V(MEASURE) by 11.
But in the program simulation I don't use 11. I use 9 because it cause a lot of error. 
In the real hardware test, you can try 11.

The voltmeter display the measuring voltage on a five-digit multiplexed SSD. The ADC reading is scheduled every one second due the timer 0 scheduling. The analog DC input voltage is fed from a -25 V and +25 V voltage terminals, giving a 50 V DC magnitude. 

Designing a positive and negative DC voltage meter using the 10-bit ADC module
Schematic diagram a digital two poles voltmeter. A POT used for adjusting the analog input
voltage, varies from -25 V to +25 V.

Source code:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(  int analogRead){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 float voltage;
 int _voltage;
 /*Voltage Calculation total reference is
  +5V with centered ground*/
 voltage=((5.0*((float)(analogRead)/1024))-2.5)/9.1;
 //voltage/=11;
 _voltage=(int)(voltage*10000);
 /*Check negative voltage*/
 if(_voltage&0x8000){
     _voltage=-_voltage;
     PORTD=0x00;
 PORTC=0x40;
 PORTD=0x01;
 __delay_ms(10);
 }
 /*Decimal Place*/
 PORTD=0x00;
 PORTC=ssd[_voltage/1000];
 if(_voltage>=1000)
     PORTD=0x02;
 __delay_ms(10);
 /*Floating point*/
 PORTD=0x00;
 PORTC=ssd[(_voltage%1000)/100]|0x80;
 PORTD=0x04;
 __delay_ms(10);
 PORTD=0x00;
 PORTC=ssd[(_voltage%100)/10];
 PORTD=0x08;
 __delay_ms(10);
 PORTD=0x00;
 PORTC=ssd[_voltage%10];
 PORTD=0x10;
 __delay_ms(10);
}
int readADC(void){
    GO=1;
    while(GO);
    return (ADRESH<<8)+ADRESL;
}
void portInit(void){
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    /*RB4 for analog input*/
    TRISB4=1;
    TRISC=0x00;
    TRISD=0x00;
}
void adcInit(void){
    /*Select external Voltage reference
     +2.5V to -2.5V*/
    VCFG1=1;
    VCFG0=1;
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS11=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN11 RB4*/
    ADCON0bits.CHS=0x0B;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
}
void timerInit(void){
     /*Select FOSC*/
    T0CS=0;
    /*Select timer 0 Prescaler*/
    PSA=0;
    /*Enable Timer 0 Overflow
     interrupt*/
    T0IE=1;
    /*Turn on Global interrupt
     Control*/
    GIE=1;
    /*Clear interrupt flage*/
    T0IF=0;
    /*Clear timer 0 register*/
    TMR0=0;
}
int adcResult;
int counter=0;
void main(void){
    portInit();
    adcInit();
    timerInit();
    
    while(1){
        driveDisplays(adcResult);
    }
}
void interrupt _ISR(void){
    if(T0IF){
        counter++;
        T0IF=0;
    }
    /* If it's one second*/
    if(counter>=15){
        adcResult=readADC();
        counter=0;
    }
}

Designing a positive and negative DC voltage meter using the 10-bit ADC module
A screen shot shows a positive voltage reading 24.84 V nearest to the positive terminal.

Designing a positive and negative DC voltage meter using the 10-bit ADC module
A screen shot shows a negative voltage reading -24.89 V nearest to the negative terminal.








Interfacing LM35 temperature sensor to the ADC module of PIC16F887

LM35 is an analog temperature sensor built in an integrated circuit. It could work in voltage range of -55 to +150 degree Celsius. It could be supply by a DC voltage between 4 to 30 V. It analog output is the temperature data. It is a linear output, 10 mV per degree Celsius.

Using the MCU ADC, it's easy to access rather using a complex protocol like one-wire or I2C bus.



Interfacing LM35 temperature sensor to the ADC module of PIC16F887
LM35 IC in TO-92 package and its pin out. 

To find the analog temperature data, we can find the digital ADC result per 10 mV. Or we can find the total voltage reading, multiplied by 100 to get the equivalent temperature in degree Celsius.

In this example, I use the ADC module to read the temperature data between -55 to 150 degree Celsius. The internal ADC module of PIC16F887 use -2.5 V to +2.5 V reference voltage, to make the device be able to read both negative and positive voltage.

I use timer 0 overflow interrupt to schedule the ADC reading for every one second.

The SSD is multiplexed with seven digits. Each digit turn on for every 5 milli seconds.


Interfacing LM35 temperature sensor to the ADC module of PIC16F887
Schematic diagram. The LM35 connects to RB4/AN11 to convert the analog
temperature data. The display is multiplexed with seven digits SSD.

Source code's here:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000
#define ssdRate 5
void driveDisplays(  int analogRead){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 float voltage;
 int _voltage;
 /*Voltage Calculation total reference is
  +5V with centered ground*/
 voltage=(5*((float)(analogRead)/1024))-2.5;
 voltage*=10;
 /*Now _voltage becomes temperature,
  multiply it by 100 to convert it
  to the SSD data*/
 _voltage=(voltage*100);
 /*Check negative voltage*/
 if(_voltage&0x8000){
     _voltage=-_voltage;
     PORTD=0x00;
     PORTC=0x40;
     if((_voltage<1000)|(_voltage>-1000))
         PORTD=0x02;
     else
         PORTD=0x01;
     __delay_ms(ssdRate);
 }
 /*Decimal Place*/
 if((_voltage>=1000)|(_voltage<=-1000)){
 PORTD=0x00;
 PORTC=ssd[_voltage/1000];
 PORTD=0x02;
 __delay_ms(ssdRate);
 }
 PORTD=0x00;
 PORTC=ssd[(_voltage%1000)/100];
 if(_voltage>=100)
     PORTD=0x04;
 __delay_ms(ssdRate);
 /*Floating point*/
 PORTD=0x00;
 PORTC=ssd[(_voltage%100)/10]|0x80;
 PORTD=0x08;
 __delay_ms(ssdRate);
 PORTD=0x00;
 PORTC=ssd[_voltage%10];
 PORTD=0x10;
 __delay_ms(ssdRate);
 PORTD=0x00;
 PORTC=0x63;
 PORTD=0x20;
 __delay_ms(ssdRate);
 PORTD=0x00;
 PORTC=0x39;
 PORTD=0x40;
 __delay_ms(ssdRate);
}
int readADC(void){
    GO=1;
    while(GO);
    /*Return a 10-bit result*/
    return (ADRESH<<8)+ADRESL;
}
void portSetup(void){
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    /*RB4 for analog input*/
    TRISB4=1;
    TRISC=0x00;
    TRISD=0x00;
}
void timer0Setup(void){
    /*Select FOSC*/
    T0CS=0;
    /*Select timer 0 Prescaler*/
    PSA=0;
    /*Enable Timer 0 Overflow
     interrupt*/
    T0IE=1;
    /*Turn on Global interrupt
     Control*/
    GIE=1;
    /*Clear interrupt flage*/
    T0IF=0;
    /*Clear timer 0 register*/
    TMR0=0;
}
void adcSetup(void){
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    /*RB4 for analog input*/
    TRISB4=1;
    TRISC=0x00;
    TRISD=0x00;
    /*Select external Voltage reference
     +2.5V to -2.5V*/
    VCFG1=1;
    VCFG0=1;
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS11=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN11 RB4*/
    ADCON0bits.CHS=0x0B;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
}
int adcResult;
int cnt=10;
void main(void){
    
    portSetup();
    adcSetup();
    timer0Setup();
    
    while(1){
        //adcResult=readADC();
        driveDisplays(adcResult);
    }
}
void interrupt _ISR(void){
    if(T0IF){
        cnt+=1;
        T0IF=0;
    }
    if(cnt>=15){
        adcResult=readADC();
        cnt=0;
    }
}


Interfacing LM35 temperature sensor to the ADC module of PIC16F887
A sample of simulation


Back to main tutorials page.



Friday, May 29, 2020

Working with ADC references voltage of PIC16F887

By default two references voltages of the ADC internally connected to VDD and VSS, supplying a +5 V reference voltage for the ADC module. We can change the reference voltage to any value in the total range of +5 V DC.

The VREF+ and VREF- pin could be programmed to externally connect to any voltage reference. By assigning the 'VCFG0' to '1' allows the VREF+ pin connect to external positive voltage. Similarly, by assigning the 'VCFG1' to '1' allows the VREF- pin connect to external negative voltage.

For this example, I connect the VREF+ pin to +2.5 V and the VREF- pin to -2.5 V. Hence, the total reference voltage for the ADC module is,

Working with ADC reference voltage of PIC16F887

The step voltage size is 0.00488 V. In this case the the lowest voltage of -2.5 V gives the ADC reading result 0. While the maximum voltage +2.5 V gives the ADC reading result 1023. The zero analog input voltage yields the ADC reading result to 512.


Working with ADC reference voltage of PIC16F887
Schematic diagram. RA3/AN3 is used for positive voltage reference connects to +2.5 V.
RA2/AN2 is used for negative voltage reference connects to -2.5 V. The voltage reading displays
between -2.5 V to +2.5 V.

Source code:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(  int analogRead){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 float voltage;
 int _voltage;
 /*Voltage Calculation total reference is
  +5V with centered ground*/
 voltage=(5*((float)(analogRead)/1024))-2.5;
 _voltage=(voltage*100);
 /*Check negative voltage*/
 if(_voltage&0x8000){
     _voltage=-_voltage;
     PORTD=0x00;
 PORTC=0x40;
 PORTD=0x01;
 __delay_ms(10);
 }
 /*Decimal Place*/
 PORTD=0x00;
 PORTC=ssd[_voltage/100]|0x80;
 PORTD=0x02;
 __delay_ms(10);
 /*Floating point*/
 PORTD=0x00;
 PORTC=ssd[(_voltage%100)/10];
 PORTD=0x04;
 __delay_ms(10);
 PORTD=0x00;
 PORTC=ssd[_voltage%10];
 PORTD=0x08;
 __delay_ms(10);
}
int readADC(void){
    GO=1;
    while(GO);
    __delay_ms(10);
    return (ADRESH<<8)+ADRESL;
}
void main(void){
      int adcResult;
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    /*RB4 for analog input*/
    TRISB4=1;
    TRISC=0x00;
    TRISD=0x00;
    /*Select external Voltage reference
     +2.5V to -2.5V*/
    VCFG1=1;
    VCFG0=1;
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS11=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN11 RB4*/
    ADCON0bits.CHS=0x0B;
ADON=1;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
    while(1){
        adcResult=readADC();
        driveDisplays(adcResult);
        
    }
}

Working with ADC reference voltage of PIC16F887
The screen-shot shows the analog voltage input of -1.49 V. 

Back to main tutorials page.


Reading multiple analog voltage inputs in the ADC module

As we can see in the previous post, the analog input channels could be selected by the CHS bits of the ADCON0 register.


In this example I use the ADC module to read four input analog pins RA0, RA1, RA2 and RA3. The selection is made by A 2 positions DIP switch. This DIP switch could make four values. This value make the selection of analog channel.


The overall process is 80 percents similar to the previous one.

Reading multiple analog voltage inputs
Schematic diagram. Four Potentiometers fed their analog input voltage to
RA0 to RA3. DSW1 is DIP switch make the analog channel selection
for the ADC module conversion. A four-digit multiplexed SSD displays
a decimal representation of each 10-bit analog resolution.

Source code is here.

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(unsigned int cnt){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 //Digit 4 1000's
 PORTD=0x00;
 PORTC=ssd[cnt/1000];
 PORTD=0x01;
 __delay_ms(10);
 //Digit 3 100's
 PORTD=0x00;
 PORTC=ssd[(cnt%1000)/100];
 PORTD=0x02;
 __delay_ms(10);
 //Digit 2 10's
 PORTD=0x00;
 PORTC=ssd[(cnt%100)/10];
 PORTD=0x04;
 __delay_ms(10);
 //Digit 1 1's
 PORTD=0x00;
 PORTC=ssd[cnt%10];
 PORTD=0x08;
 __delay_ms(10);
}
unsigned int readADC(void){
    ADCON0bits.CHS=PORTB;
    GO=1;
    while(GO);
    __delay_ms(10);
    return (ADRESH<<8)+ADRESL;
}
void main(void){
    unsigned int adcResult;
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    TRISB=0x03; 
    TRISC=0x00;
    TRISD=0x00;
    /*PORTB has no analog inputs*/
    ANSELH=0x00;
    /*Enable Weak Pull ups*/
    nRBPU=0;
    /*Turn on RB0 and RB1
     Weak Pull Up resistor*/
    WPUB=0x03;
    /*Result is right justify*/
    ADFM=1;
    /*Set some analog pins of PORTA*/
    ANSEL=0x0F;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*By default select AN0*/
    ADCON0bits.CHS=0b0000;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
    while(1){
        adcResult=readADC();
        driveDisplays(adcResult);
    }
}

Reading multiple analog voltage inputs
A simulation screen shot. The ADC module is reading the analog voltage
input fed from RA3. The result is 2.5 V which is 511 in digital representation. 



Using ADC module to read analog voltage between 0 to 5 volts DC

In this previous post, we introduced about using ADC module of PIC16F887. Generally, the voltage references VREF+ and VREF- are internally wired to VDD and VSS, respectively. PIC16F887 could be supplied in a range of 2.0 V to 5.5 V. Generally, the supply voltage is +5 V because most external devices work at +5 V.


Step size is an analog voltage per unit of the ADC resolution.
Using ADC module to read analog voltage between 0 to 5 volts DC
For VREF+ = +5 V (VDD) and the VREF- = 0 V (VSS), we get the step size of
Using ADC module to read analog voltage between 0 to 5 volts DC
For example if the ADC result equal to 512, the voltage is,
Using ADC module to read analog voltage between 0 to 5 volts DC
Similarly, we get find the digital result equivalent to the analog input voltage,
Using ADC module to read analog voltage between 0 to 5 volts DC
In this example, I use a POT to adjust the analog input voltage varies from 0 to +5 V. To find the voltage we can use the equations listed above. But we can use another short form as,
In the C program, we must use the floating point to measure the analog voltage.

Using ADC module to read analog voltage between 0 to 5 volts DC
Schematic diagram. RB0 is AN12 analog input, measuring the input voltage
from 0 to +5 V. PORTC and PORTD display the measuring voltage with
one floating point.

The C source code:

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(unsigned int analogRead){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 float voltage;
 int _voltage;
 /*Voltage Calculation*/
 voltage=5*((float)analogRead/1024);
 _voltage=voltage*10;
 
 /*Decimal Place*/
 PORTD=0x00;
 PORTC=ssd[_voltage/10]|0x80;
 PORTD=0x01;
 __delay_ms(10);
 /*Floating point*/
 PORTD=0x00;
 PORTC=ssd[_voltage%10];
 PORTD=0x02;
 __delay_ms(10);
}
unsigned int readADC(void){
    GO=1;
    while(GO);
    __delay_ms(10);
    return (ADRESH<<8)+ADRESL;
}
void main(void){
    unsigned int adcResult;
    /*Analog and digital Port
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    TRISB=0x01;
    TRISC=0x00;
    TRISD=0x00;
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS12=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN12 RB0*/
    ADCON0bits.CHS=0b1100;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
    while(1){
        adcResult=readADC();
        driveDisplays(adcResult);
        __delay_ms(20);
    }
}

Using ADC module to read analog voltage between 0 to 5 volts DC
Simulation screen shot. The measuring voltage displays 3.9 V DC.










Introduction to analog to digital converter module

Introduction

An analog to digital converter (ADC) module converts an analog voltage to a digital binary equivalence value. In PIC16F887 the built-in ADC module use a successive approximation type. I has up to 14 analog channels with reference voltage selection. The ADC resolution is 10-bit, made up of two distinct 8-bit registers.

Some pins of PortA and PortB could be used for analog inputs.

Introduction to analog to digital converter module
Analog input pins of PIC16F887


There are two option of ADC clocks, MCU clock and the ADC built-in RC oscillator (FRC). The FRC gives a frequency of 500 kHz maximum. Using the MCU clock with the maximum clocking frequency, the ADC could complete the conversion time at 100 nano seconds.

Introduction to analog to digital converter module
A simplified scheme of ADC module in PIC16F887.
An original detailed block diagram could be found
in PIC16F887 datasheet.

ADC module takes up to 12 cycles to complete the conversion to make the 10-bit result ready. Since the 10-bit result consists of two 8-bit registers. It could be arranged in two margins, left or right. 

Up on the completion, an interrupt flag relates to ADC is set. With this advantage, we can use interrupt routine to process the analog to digital conversion.

There are some registers to work with ADC module:
  • Input output port register (PORTx),
  • Port direction register (TRISx),
  • ANSEL and ANSELH registers,
  • ADCON0 and ADCON1 register,
  • related interrupt registers,
  • ADC result registers.
Some registers listed above have already discussed in previous posts. So we are listing some not-ready-listed registers.

Introduction to analog to digital converter module
This register set the analog inputs pins of some of PortA and PortE. Setting any bit to '1', assigning the corresponding pin to analog input. By default they are '1'.

Introduction to analog to digital converter module

ANSELH controls the analog input pin at PortB. It has only 6 analog input pins while the remaining two are digital. By default, they are set to '1'.

Introduction to analog to digital converter module

This is the first register to control the operation of ADC module.
  • ADC Conversion Clock Select bits ADCS[1:0], is a two-bit clock selection for ADC. The clock source for ADC could fed from MCU clock with the 2, 8 and 32 clock division. Another clock source could fed from internal ADC oscillator with the maximum frequency of 500 kHz. Setting ADCS to 0x03, the ADC clock is fed from it's internal oscillator.
  • Analog Channel Select bits CHS[3:0], could select between the 14 analog input channels. These bits could select the reference capacitor for analog comparator and the 0.6 V fixed voltage reference.
  • ADC Conversion Status bits GO/nDone, initiate a start of conversion when it's set to '1'. It will clear to '0' when the conversion completed. We can also use this bit to check the status of the conversion.
  • ADC Enable bit ADON, turn on the ADC module when set to '1'. Otherwise it's turned off.

Introduction to analog to digital converter module

ADCON1 configures additional setting of the ADC operation.
  • ADC Conversion Result format Select bit ADFM, arranges the margin of the ADC conversion result. It effect the ADRESL and ADRESH register. When it's '0' (default) the result is right justified. 
  • Negative voltage reference bit VCFG1, set the negative voltage reference for ADC. Setting it to '1', the negative voltage reference must supply to VREF- pin. By default, it's '0' and the negative voltage reference pin is internally wired to VSS of the MCU.
  • Positive voltage reference bit VCFG0, set the positive voltage reference for ADC. Setting it to '1', the positive voltage reference must supply to VREF+ pin. By default, it's '0' and the positive voltage reference pin is internally wired to VDD of the MCU.
Introduction to analog to digital converter module
The ADC Result High Register ADRESH, store the result of ADC conversion. In the picture above, it's assumed that the result format is right justified (ADFM=1). 

Introduction to analog to digital converter module
ADRESL is a pair register with ADRESH. Joining them together to make a 10-bit ADC resolution. In this picture we assume that the result format is right justified (ADFM=1).

Programming the ADC in XC8

To access to the ADC module, we must setting up the ADC module and work with the conversion process.
To set up the ADC module:
  1. set the related pin to input
  2. By default those related pins are analog inputs. But we can do it optionally, by setting any bits of the ANSEL and ANSELH register. These two registers contain the analog and digital pins selection as listed above.
  3. Select the ADC clock source by setting the ADCS bits of the ADCON0 register.
  4. Since there are 14 analog channels. Only one active channel is selected for reading the analog voltage value. So we must select one channel by setting the 4-bit CHS bits of the ADCON0 register. 
  5. Turn on the ADC module by setting the ADON bit of the ADCON0 register to '1'.
  6. Two resolution register ADRESL and ADRESH pair store the conversion result. We must set it to right or left justification as preferred by assigning the value to ADFM bit of the ADCON1 register.
  7. Select the voltage reference by setting any value to the VCFG1 and VCFG0 bits of the ADCON1 register.     
To start a conversion:
  1. If we are not yet select the analog channel, do it with the CHS bits.
  2. Wait for some period to make the capacitor properly work
  3. Set the GO/nDone bit to '1' to start the conversion
  4. Wait for the completion by checking the GO/nDone bit.
  5. Get the result from ADRESH and ADRESL register pair.

Programming Example

In this example I read an analog voltage value from AN12 input channel. The result is display in decimal (0 to 1023 for 10-bit value) on a multiplexed SSD.
The ADC clock is driven from its dedicated internal oscillator. The analog value is fed from a POT ranging from 0 to +5 V. The voltage references is internally wired to VSS and VDD of the MCU.

Introduction to analog to digital converter module
Schematic diagram. RB0 or AN12 is an analog input reading pin, reading an analog
value from the POT. The POT works with a passive low pass filter withe any
calculation. The result is displayed on a 4-digit multiplexed SSD ranging from
0 to 1023 in decimal value.

Source code is here.

#include<xc.h>
// PIC16F887 Configuration Bit Settings
// CONFIG1
#pragma config FOSC = XT
#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 = ON
#pragma config FCMEN = ON
#pragma config LVP = ON
// CONFIG2
#pragma config BOR4V = BOR40V
#pragma config WRT = OFF
/*_XTAL_FREQ use for __delay*/
#define _XTAL_FREQ 4000000

void driveDisplays(unsigned int cnt){
 unsigned char ssd[16]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,
 0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
 //Digit 4 1000's
 PORTD=0x00;
 PORTC=ssd[cnt/1000];
 PORTD=0x01;
 __delay_ms(10);
 //Digit 3 100's
 PORTD=0x00;
 PORTC=ssd[(cnt%1000)/100];
 PORTD=0x02;
 __delay_ms(10);
 //Digit 2 10's
 PORTD=0x00;
 PORTC=ssd[(cnt%100)/10];
 PORTD=0x04;
 __delay_ms(10);
 //Digit 1 1's
 PORTD=0x00;
 PORTC=ssd[cnt%10];
 PORTD=0x08;
 __delay_ms(10);
}
unsigned int readADC(void){
    GO=1;
    while(GO);
    __delay_ms(10);
    return (ADRESH<<8)+ADRESL;
}
void main(void){
    unsigned int adcResult;
    /*Analog and digital Port 
     Configuration*/
    PORTB=0x00;
    PORTC=0x00;
    PORTD=0x00;
    TRISB=0x01;
    TRISC=0x00;
    TRISD=0x00;
    /*Result is right justify*/
    ADFM=1;
    /*By default is analog,
     but again set it to analog*/
    ANS12=1;
    /*Select FRC Clock of ADC module*/
    ADCON0bits.ADCS=0x03;
    /*Turn on ADC Module*/
    ADON=1;
    /*Select AN12 RB0*/
    ADCON0bits.CHS=0b1100;
    /*initiate a conversion*/
    GO=1;
    /*Wait until GO=0 "done"*/
    while(GO);
    while(1){
        adcResult=readADC();
        driveDisplays(adcResult);
    }
}

Introduction to analog to digital converter module
A simulation screen shot. The analog reading is 921 in decimal.















Search This Blog

Labels

25AA010A (1) 8051 (7) 93AA46B (1) ADC (30) Analog Comparator (1) Arduino (15) ARM (6) AT89C52 (7) ATMega32 (54) AVR (57) CCS PICC (28) DAC (1) DHT11 (2) Display (105) Distance Sensor (3) DS18B20 (3) dsPIC (2) dsPIC30F1010 (2) EEPROM (5) Environment Sensor (4) esp8266 (1) I2C (29) Input/Output (67) Interrupt (19) Keil (5) Keypad (10) LCD (46) Master/Slave (1) MAX7221 (1) MCP23017 (5) MCP23S17 (4) Meter (3) MikroC (2) Motor (15) MPLABX (66) Nokia 5110 LCD (3) OLED (2) One-Wire (6) Oscillator (8) PCB (6) PCD8544 (3) PCF8574 (5) PIC (107) PIC12F (2) PIC16F628A (2) PIC16F630 (1) PIC16F716 (3) PIC16F818 (10) PIC16F818/819 (2) PIC16F84A (15) PIC16F876A (1) PIC16F877A (9) PIC16F88 (1) PIC16F887 (60) PIC18 (19) PIC18F1220 (4) PIC18F2550 (3) PIC18F4550 (12) PWM (11) RTC (8) Sensor (10) SH1106 (1) Shift Register (11) Shift Registers (2) SPI (24) STM32 (6) STM32 Blue Pill (6) STM32CubeIDE (6) STM32F103C8T6 (6) SysTick (3) temperature sensor (11) Thermometer (21) Timer/Counter (30) TM1637 (2) UART (7) Ultrasonic (4) Voltmeter (7) WDT (1) XC16 (2) XC8 (94)