Monday, September 19, 2016

PIC16F877a ADC Reading

CIRCUIT DIAGRAM






SOURCE CODE



#include <xc.h>  // this file contains the SFR information of the chip
#include <delays.h> // header file for using C18 delay functionss
//----------------------------------------------------------------------------------
#define _XTAL_FREQ     4000000L
//Function prototypes
void port_initialise(void);
void init_variables (void);
void DelayMs(unsigned int msCount);
void lcd_ini(void);
void lcd_writeDAT(unsigned char ch);
void lcd_writeCMD(unsigned char ch);
void lcd_writeDAT_Run(unsigned char ch);
void lcd_strob(void);
void setposition(unsigned char pos);
void lcdstringptr(const char *ptr, unsigned char lineNo);
void LCDchk_busy (void);
void lcdFrameUpdate(void);
void bin2dec(unsigned int intforBCD);
void ADCtempread(void);
void adc_init(void);
void copyb2dresultcutoff (void);
void copyb2dresultcurrent (void);

//--------------------------------------------------------------------------
//These are required if you are using this hex file with a device programmer or Debugger.
//When using with bootloader this will be ignored
//These config settings will start 4550 system and USB engine in 48Mhz System and USB Clock
//But this example code after bootup switches clock to 4Mhz internal oscillator.
#pragma config WDTE = OFF    
    #pragma config FOSC = HS
    #pragma config PWRTE = ON
    #pragma config BOREN = ON
    #pragma config LVP = OFF
    #pragma config CPD = OFF
    #pragma config WRT = OFF
    #pragma config DEBUG = ON
    #pragma config CP = OFF
//--------------------------------------------------------------------------
//ROM based look uptables and constants
// This array holds the seven segment pattern to be illuminated for each digit.

const char * tempis   = {" Temparature is "};
const char * degCel   = {"        Deg.Cel "};
const char * cuttoffval  = {"CutOff   .   Cel"};
//--------------------------------------------------------------------------
//#pragma udata access myaccess
near volatile  struct  // This is a structure to create logic flags.
{ // FLAGAbits
   unsigned FLAG0:1; //
   unsigned FLAG1:1; //
   unsigned FLAG2:1; //
   unsigned FLAG3:1; //
   unsigned FLAG4:1; //
   unsigned FLAG5:1; //
   unsigned FLAG6:1;
unsigned FLAG7:1;
} FLAGAbits;
//-------------------------------------------------------------------------
union
{ //This is a union to hold the ADC reading for ADC converter. This ADC result
  unsigned char ADCbytes[2];// we are getting as bytes from ADRSL and ADRSH. But it is 
  unsigned int  Temp16Bit ; // a 10 bit number. So for calculation purpose
}LM35Temp; //we need to process it as a two byte number- so  union
//---------------------------------------------------------------------------
//#pragma udata
// Variable dclarations(Global)
unsigned char Digit1,Digit2; // digit1 and Digit2 holds the values to be discplayed
unsigned char DebounceCnt; // keyboard debounce time variable
unsigned char FrameWait,ADCwait,buzCnt;// wait counts to control the speed of operations
unsigned int ADCcurrentValue,ADCvalueCutoff = 62;
unsigned char b2d_result[5],b2d_resultcutoff[5],b2d_resultcurrent[5]; // arrays for storing bcd results
//-------------------------------------------------------------------------
// Defines

#define  buzzer  PORTBbits.RB5 //  buzzer
#define  LEDenable  PORTCbits.RC3 // led array enable
#define Digit1_En   PORTBbits.RB0
#define Digit2_En   PORTBbits.RB1 // digits enable transistor control
#define  SwUp     PORTEbits.RE0 // up counter
#define  SwDown     PORTEbits.RE1 // Downcounter
#define LCD_EN PORTAbits.RA2
#define LCD_RS PORTDbits.RD5
#define relay  PORTBbits.RB6

#define FlagIncri FLAGAbits.FLAG0 // indicates incriment key pressed
#define FlagDecri FLAGAbits.FLAG1 // decriment
#define FlagDebounce FLAGAbits.FLAG2 // key debounce
#define LCDiniFlag  FLAGAbits.FLAG3
#define LCDFrameFlag  FLAGAbits.FLAG4
#define cutoffDispFlag FLAGAbits.FLAG5
#define cutoffState FLAGAbits.FLAG6
//----------------------------------------------------------------------------
//The USBMicroMaster Kit uses a bootloader which is residing below 0x1000 of programe memmory
//Here follows the reset and interrupt Re-mapping to suit the Bootloader used.
//aditional directives are added to avoid malfunction even if used with a device programmer .
//-------------------------Here we are re directing the reset vector-------------------------------
//--------------This portion is to re-direct code execution if not used along with bootloader------
void interrupt InterruptArea(void)
{
}
//----------------------------------------------------------------------------
void main (void)
{ // main loop starts-------------
unsigned char count;
unsigned char n; // Controller starts with 48Mhx clock from 20Mhz Xtal and PLL for USB bootloader
  port_initialise(); // setting up I/O lines as input and output
  init_variables (); // variables are to be initialised
adc_init(); // initialises ther ADC module
lcd_ini(); // lcd initialise
LCDiniFlag=1; // indicating initialisation is over to use busy flag checking in LCD
//---------------------------------------------------------------------------------------------------
while(1) // Normally, every MCU programe is a never ending loop. here starts it
{ //while 1 loop starts----------
ADCwait++; // ADC need not be reated on every loop
if(ADCwait>10)
{
ADCwait=0;
ADCtempread();
}
lcdFrameUpdate(); // here lcd messages change

} //while 1 loop ends------------
} // main loop ends--------------
///*********************************************************************************************************
//#pragma code functions

void ADCtempread(void)
{
ADCON0 &= 0b11000111; //select channel 1 and set the GO/DONE bit
ADCON0bits.GO=1;
while(ADCON0bits.GO);     // if conversion over
LM35Temp.ADCbytes[0]=ADRESL;
LM35Temp.ADCbytes[1]=ADRESH; // load ADC result bytes
ADCcurrentValue=LM35Temp.Temp16Bit; // the 16bit number stord to disaply
LM35Temp.Temp16Bit= 48*(LM35Temp.Temp16Bit); //multiply by 48
bin2dec(LM35Temp.Temp16Bit); //now result is a 16 bit number, we need to extract it as digits
copyb2dresultcurrent(); // bcd conversion done and digits copied to a array for dispaly
Digit1=(b2d_resultcurrent[1]-0x30); Digit2=(b2d_resultcurrent[2]-0x30);// digits are in ASCII for LCD
} //30h is substracted to extract the value only .
//-------------------------------------------------------------------------------------------------------
void adc_init(void) // three analogue channels enables ,module on. but ch0 selected
{
ADCON0 = 0b01000001 ; //selecting channel 1 & making ADC module on .
ADCON1 = 0b10000100 ;//making channels AN0 & AN1 and AN2 as analog inputs .
TRISA |= 0b00001011;// three PORTA pins as inputs 
ADRESH = 0x00;
ADRESL = 0x00;
}
//------------------------------------------------------------------------------------------------------
//This function converts a 16 bit number to ascii
void bin2dec(unsigned int intforBCD)
{
 unsigned char count;
   count=5;
   do
count--; // to extract the bcd digits modulas of 10 is arrived
     b2d_result[count] = '0' + (intforBCD % 10); //add '0' that is 30h for ASCII
     intforBCD /= 10; // divide by 10 and repeat the same thing 4 times
} while(count);
}
//------------------------------------------------------------------------------------------------------
void lcdFrameUpdate(void)
{// this function arranges different messages to appear in LCD sequentially
FrameWait++;
if(FrameWait>100) // this value determions the speed at which messages change in lcd
{
if(LCDFrameFlag)
{
lcdstringptr(USBMicro,1);
if(cutoffDispFlag)
{
LM35Temp.Temp16Bit=ADCvalueCutoff; // here cut off value is converted to digits and shown in lcd
LM35Temp.Temp16Bit= 48*(LM35Temp.Temp16Bit);
bin2dec(LM35Temp.Temp16Bit);
copyb2dresultcutoff();
lcdstringptr(cuttoffval,2);
lcd_writeCMD(0xC7);
lcd_writeDAT(b2d_resultcutoff[1]);
lcd_writeDAT(b2d_resultcutoff[2]);
lcd_writeDAT('.');
lcd_writeDAT(b2d_resultcutoff[3]);
lcd_writeDAT(b2d_resultcutoff[4]);
lcd_writeCMD(0xF0);// fix cursor out side dispaly address to avoid cursor
cutoffDispFlag=0;
}
else
{
lcdstringptr(EmbCkit,2);
cutoffDispFlag=1;
}
LCDFrameFlag=0;
}
else
{
lcdstringptr(tempis,1); // here normal tempararure is shown
lcdstringptr(degCel,2);
lcd_writeCMD(0xC1);
lcd_writeDAT(b2d_resultcurrent[1]);
lcd_writeDAT(b2d_resultcurrent[2]);
lcd_writeDAT('.');
lcd_writeDAT(b2d_resultcurrent[3]);
lcd_writeDAT(b2d_resultcurrent[4]);
lcd_writeCMD(0xF0);// fix cursor out side dispaly address to avoid cursor
LCDFrameFlag=1;
}
FrameWait=0;
}
}
//****************************************************************************************
void lcdstringptr( const char *ptr, unsigned char lineNo)
{
unsigned char loc; // this function displays a string stored in the code memmory
unsigned char locCount;

if(lineNo==2)
{
setposition(16); //if second line is to be updated 16 is the second line start
}
else if(lineNo==1)
{
setposition(0); //(0 to 15 is first line)
}
for(locCount=0;locCount<=15;locCount++)// 16 times load local variable with value at pointer
{
loc=*ptr;
if(!loc) //check weather value 0 ( id\f end of string ,it will be 0.
break; // compiler by default null terminates every string
lcd_writeDAT(loc); // send each data to LCD
*ptr++;
}
}
//--------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------
void port_initialise (void)
{
TRISD = 0X00; // LED port is made as output
PORTD = 0X00; // and cleared
ADCON1 = 0b10000100; // A/D module not ued.All pins digital
TRISA =  0b00001011; // LED port is made as output
PORTA = 0X00; // and cleared
TRISC &= 0b11111110; // LCD enable pin output
TRISE = 0B00000111;
PORTE = 0X00; // digit enable transistors in PORTE- hold them low 
TRISB = 0b00000000;// switch pins as input- all else output
PORTB = 0X00 ; // LED array and buzzer pins made output and kept low.
LEDenable=0; // led array enable transistor off
Digit1_En=0; // digits to be kept low
Digit2_En=0;
}
//*******************************************************************************************************
void init_variables (void)
{
Digit1=0;Digit2=0;DebounceCnt=0;
FlagIncri=FlagDecri=FlagDebounce=0;
LCDiniFlag=0;
LCDFrameFlag=0;cutoffDispFlag=1;
//_asm nop _endasm  // you can add any varibale initialisation here if required
}
//**************************************************************************
void DelayMs(unsigned int msCount)
{
unsigned int locCnt;
for(locCnt=0;locCnt<=msCount;locCnt++)
{
__delay_ms(1); // This C18 function creates a delay of 1000 instructions= 1000uSec= 1 millisecond
// looping this msCount times will give that much milliseconds delay
}
//******************************************************************************
void lcd_ini(void) //Initialisation Of LCD
{

    LCD_EN=0; //Disable LCD
    LCD_RS=0; // write to command register
DelayMs(20); // Carefull! when changing Target increase delay .

PORTD = (PORTD & 0xF0) | 0x03; //Function set
DelayMs(12);
lcd_strob();     // 1 Strobe for 8 bit pushed in!
DelayMs(12);
lcd_strob();      // 2 Strobe for 8 bit  pushed in!
DelayMs(12);
lcd_strob();     // 3 Srobe for 8 bit pushed in!
DelayMs(12);

PORTD &=  0b11111110;        // 4 bit interface command setting!
lcd_strob();     // 4 bit  first strobe
DelayMs(2); // Time to sink command


lcd_writeCMD(0b00101000); // (28h)  set interface data length, number of lines-
DelayMs(2); // Charector Font
lcd_writeCMD(0b00001000); // (08h) dispaly off all cursor all off
DelayMs(2);
lcd_writeCMD(0b00000110);  // (06h) position incriment decriment and
DelayMs(2); // display shift
lcd_writeCMD(0b00001111); // (0F)LCD on again after adjustments , cursor on and blink on!
DelayMs(2);
lcd_writeCMD(0b00000001); // (01)Clear  the entire display
DelayMs(2);
}
//********************************************************************************************
// write a byte to the LCD in 4 bit mode
void lcd_writeDAT(unsigned char ch)
{
if(!LCDiniFlag) // check weather in the initialisation stage
{
NOP();
}
else
{
//LCDchk_busy(); //else check busy flag so that process will become fast
NOP();
}
    LCD_RS=1; //LCD to data mode
PORTD = (PORTD & 0xF0) |  (ch >> 4); //Hi nibble first.
lcd_strob();
PORTD= (PORTD & 0xF0) |  (ch & 0x0F); //Low nibble next
lcd_strob();
}
//********************************************************************************************
//FunctionFor Writing Commands To LCD
void lcd_writeCMD(unsigned char ch)
{
if(!LCDiniFlag) // check weather in the initialisation stage
{
DelayMs(1); // if so use delay
NOP();
}
else
{
//LCDchk_busy(); //else check busy flag so that process will become fast
NOP();
}
    LCD_RS=0;             // LCD in Command Mode
PORTD = (PORTD & 0xF0) |  (ch >> 4); // Hi nibble first.
lcd_strob();
PORTD = (PORTD & 0xF0) |  (ch & 0x0F); // Low nibble next
lcd_strob();
}
//*********************************************************************************************

void lcd_strob(void) //Function For toggling Enable pin of LCD to push data in
{
LCD_EN=1; //Enable LCD
NOP();
LCD_EN=0; //Disable LCD
}
//***********************************************************************************************
void setposition(unsigned char pos) //LCD position
{
unsigned char startloc;
if((pos>=0)&(pos<=15)) // positions 0,1,2,3,...14,15
{ //          16,17,.....30,31
startloc=(0x80+pos); // position should be ored with 80h to set left most bit
  } // for specifying position.see LCD datasheet
if((pos<=31) & (pos>=16))
{
startloc=(0xC0+(pos-16));  // second line
}

lcd_writeCMD(startloc); // fix cursor to required position.....
}
//*************************************************************************************************
//LCD needs some time to update display as it is a slow device. So before sending data to-
//LCD sequentially we must make sure LCD has finished work on previous data- this function checks it
//If LCD busy 7th bit of data read from LCD will be high. See LCD datasheet
void LCDchk_busy (void) // this function checks and hold on if LCD is Busy
{
unsigned char busybyte,temp;
TRISD |=  0b00001111; // busy pin as input

do
{
//LCD_RD = 1;                     // Set the control bits for read
    LCD_RS = 0;
    LCD_EN=1;
temp=PORTD;
LCD_EN=0;
temp=((temp&0b00001111)<<4);
busybyte=temp;
LCD_EN=1;
temp=PORTD;
LCD_EN=0;
temp=(temp&0b00001111);
busybyte=(busybyte|temp);
//LCD_RD = 0;
//_asm nop _endasm
}while(busybyte&0x80); // returns only if LCD not busy

TRISD &=  0b11000000; // Data and command lines for LCD back to output mode
}
//***************************************************************************************************
void cutofftempsetting(void) // This function polls the keys and incriments or deciments
{ // the cut off value
if(FlagIncri) // if incriment flag is set by switch Up
{
ADCvalueCutoff++;
FlagIncri=0;
if(ADCvalueCutoff>312) // LM35 has a maximon temp of 150 Degree celcious is 1500mVolts
ADCvalueCutoff=0; // (1500/4.8) ~ 312 . So 312 is the maximum limit
} // incriment end
//----------------------------------------------------------------------------------------------------
if(FlagDecri) // if decriment flag is set by down switch
{
ADCvalueCutoff--;
FlagDecri=0;
if(ADCvalueCutoff<1)
ADCvalueCutoff=312;
}
//-----------------------------------------------------------------------------------------------------
if(!FlagDebounce) // if debounce period is over then only poll keys
{
if(!SwUp)
{
FlagIncri=1; // if key presed indicate flag ,set debounce flag and on buzzer
FlagDebounce=1;
buzzer=1;
}
if(!SwDown)
{
FlagDecri=1;
FlagDebounce=1;
buzzer=1;
}
}
//------------------------------------------------------------------------------------------------------
// for a debounce time avoid polling keys to elliminate multiple keypress on a single stroke.
if(FlagDebounce)
{
DebounceCnt++;
if(DebounceCnt==4) // The buzzer needs a  small beep only.
buzzer=0; // So clear it before debounce over.
if(DebounceCnt>8)
{
FlagDebounce=0; // clear debounce flag and counter
DebounceCnt=0;
}
}
}
//******************************************************************************************************
void copyb2dresultcutoff (void)// These functions copies result array to another one for
{ // We can use pointers to direct results to different arrays-
unsigned char count; //-Here avoided to make things simple
for(count=0;count<5;count++)
{
b2d_resultcutoff[count]=b2d_result[count];
}
}
//-----------------------------------------------------------------------------------------------------
void copyb2dresultcurrent (void)
{
unsigned char count;
for(count=0;count<5;count++)
{
b2d_resultcurrent[count]=b2d_result[count];
}
}
//------------------------------------------------------------------------------------------------------

EXPLANATION

This program basically reads a analogue temperature sensor and displays the temperature in LCD. The sensor used is easily available and low cost temperature sensor LM35. It is a internally calibrated integrated sensor which will give a voltage of 10 mille Volt/Degree Celsius. That means if the current temperature ‘felt’ by LM35 is 0 Degree Celsius, its output pin will give 0 Volts. From then for every degree Celsius rise, the voltage will go up by 10 mille volt.
For example if the Ambient temperature is 30 Degree Celsius, LM35 output pin will give 30*10milli Volt = 300 mille Volt which is .3 Volt. So to get the temperature data from this sensor all we have to do is measure the voltage using an analogue to digital converter which is built in PIC and then divide the result in mill volts by 10 which will give the real temperature in Degree Celsius.
Analog to Digital Converter is a peripheral (like Timers) available in PIC forconverting Analogue data (Most of the real world data are analog, btw!) to digital form so that Microcontroller can process, display or operate upon them. 16F877A is having 10 bit ADC which means, as the analogue voltage varies from its low limit to high limit (Decided by the reference of ADC), in our case 0V to +5V, The digital result will vary from 0 to 1023.
Just like timers ADC’s are designed with a wide variety of users in mind, so we need to configure them before using in our application. ADC peripheral is being initialized in the adc_ini function. Details are available in the data sheet. We need to specify the pins where Sensor is connected and all. Once we command the module to start conversion, we can check a particular bit to see weather conversion is over. Once it is over, we have a result which can be processed by MCU which are available in ADC result registers.
As already discussed, 10 bit ADC is present in this chip. So Maximum value is 1023 and minimum is 0. We set reference for ADC as 5 Volts. So 1 ADC result equals 4.8mV, i.e. (5000/1024) Mille Volts. (ADC result*4.8) will give voltage in mill volts. Also LM35 has a output of 10mV/Degree Celsius. LM35 output m Volts/10 will give Degree Celsius. So ((ADC result)*4.8)/10 = (ADC result)*.48 = Temperature in Degree Celsius. To avoid fraction calculation we multiply (ADC result) by 48 instead of .48 and induce a decimal point on the 100th digit position to effect a divide by 100. To put it straightly, we need to read the ADC in 10 bit mode, multiply by 48 and introduce two decimal places while displaying the value. We will get a resolution of almost .5 degree Celsius by this calculation which is acceptable when considering what is assured by LM35 specifications
The ADC result is a 10 bit number and on multiplying it with 48 we will be getting a number which is 16 bits or lower width. LM35 is not supposed to withstand such high temperatures and it is not expected in our case anyway. Still in principle we need to process a 16 bit result, expecting the worst case scenarios.
Let us consider a possible situation - current temperature is 30° C. Obviously LM35 output pin will be having 300 mill Volts (10mV/° Celsius). So when the ADC reads this we will get a result of 62 or 63 (62.5 is the exact but only whole numbers are possible).Now we will multiply this by 48. 63*48 = 3024. Now if we introduce two decimal places while display the result can be 30.24° Celsius. But what we have in PIC’s internal Registers is a 16 bit number 3024, which needs to be split up into 3, 0, 2, and 4. Then only it can be shown in our display. Now just as we do in elementary mathematics, we need to locate how many thousands in it – 3. Then in the remaining part how many hundreds- nil. Then how many 10s are left -2. And how many is the ultimate 1s? –it is 4!. Yeah!! What we have just done is BCD conversion! We need to do it in software to derive the digits.
Now we have derived means to show temperature in LCD. Our remaining task is to ON/OFF the relay in board when the temperature rises above a certain limit. We have specified a cut off temperature which is showing in LCD in alternate frames, which can be adjusted up or down by our UP/DOWN switches. Our code should check whether the ADC result or rather temperature has exceeded that limit. We have created a flag to indicate whether the relay is in cut off mode or not and the value comparison is done on separate routes. The more simple method is just provide an ‘if’ statement. If ADC greater, relay ON, else relay OFF. But that will cause the relay to chatter when the ADC result will be jogging around our cut off value. So always better to provide a hysteresis ‘window’ in such switching decision loops. We have provided a margin value also here which can be adjusted if your sensor gives more jittery values by any reason,
if(ADCcurrentValue<(ADCvalueCutoff-1)) //check weather current
//value gone below. This -1can be -2 if needed.
In Data Memmory area we can see some new declarations.
union
{
unsigned char ADCbytes[2];// As from registers
unsignedint Temp16Bit ; // for calculation purpose
}LM35Temp;
This a union declaration in C. Even though we say ADC result is a 10 bit number, our controller’s Data memory is only 8 bit wide (That is why it is called 8 bit controller). So the 10 bit number will be available through two 8 bit registers. i.e ADC result will be present in ADRSL and ADRSH registers. So they are physically in two registers but for calculation we need to handle them as single 16 bit number by attaching together- there comes unions. Our LM35Temp Union is a variable which we can access as two bytes and a single 16 bit number. For loading ADC we approach this variable as bytes and for calculation we will tackle them a single 16 bit number.
unsigned char b2d_result[5], b2d_resultcutoff[5], b2d_resultcurrent[5];// arrays for storing bcd results
These are arrays implemented in PIC’s data memory to hold the result digits of calculations so that we can load them to display at the proper time to update display in a round robin fashion. As the LCD needs each numeral to be in ASCII format to display properly , we are storing these digits in ASCII format in these arrays.
We are using a variable ADCwaitTo slow down the ADC conversion frequency. Temperature is not a very fast changing parameter, at least in our test conditions. So we don’t have to convert at very high sampling rates. But to slow down the sampling rate without affecting the other code segments we are using this variable as a ‘reducer’.


Like in the previous examples we are handling switch and buzzer with de bounce to adjust the cut–off temperature. That cut off value will be displayed in LCD also

No comments:

Post a Comment