Saturday, November 12, 2016

Digital Voltmeter Using PIC Micro-Controller

Digital Voltmeters are most common devices for Embedded and Hardware Engineers and are used on daily purpose for measuring voltage values in their circuits. In this post i will show you how to make digital voltmeter using Microchip PIC micro-controller and then display voltage values on multiplexed seven segment display (I hope you all are aware of the seven segments and if not then you can click here for that).
Digital Voltmeter
In this project, i will just use the Proteus Simulation environment, and we will measure the DC voltage (0-100V) using PIC Analog to Digital Converter. One thing is important, as PIC is a 5V device and directly connecting such a high voltage (100V) on the micro-controller, will definitely (i am not using probably word, as it will definitely blow the device) damage the micro-controller. So to overcome this situation we have to scale down the Maximum Voltage value which is 100V to the Maximum Voltage value which the micro-controller can sense, in this case it is 5V.
For scaling down the voltage we will use the voltage divider circuit, which is shown below.
Voltage Divider
Now a question arises that, how i selected these values of resistor, for that see the following calculations.
Calculations
So i think it is clear now, how to choose the resistor values. But there is point, you will not find 19k resistor in market, but as i am working in simulation so i can tune the resistor value to any value, so in practical use, fine tune the values, with the equation above and with the resistors you have.
Now lets build the circuit using PIC16F676 micro-controller, the schematic used is given below, as our maximum voltage (100V) value is of 3-digits, so we need only three seven segments but in the schematic, four seven segments are used, because in the Proteus i didn't find the multiplexed seven segments having only three seven segments, so i used the one with four seven segments but as you can see in the schematic the fourth one is disabled.
Schematic Diagram
The program is as follow:
 #include "config.h"  
 #define REFRESH_INTERVAL 50ul  
 #define DIGIT_0_Indx   0x40  
 #define DIGIT_1_Indx   0x79  
 #define DIGIT_2_Indx   0x24  
 #define DIGIT_3_Indx   0x30  
 #define DIGIT_4_Indx   0x19  
 #define DIGIT_5_Indx   0x12  
 #define DIGIT_6_Indx   0x02  
 #define DIGIT_7_Indx   0x78  
 #define DIGIT_8_Indx   0x00  
 #define DIGIT_9_Indx   0x10  
 #define BLANK_Indx    0x7F  
 uint8_t SevenSegment[] = { DIGIT_0_Indx, DIGIT_1_Indx, DIGIT_2_Indx,   
               DIGIT_3_Indx, DIGIT_4_Indx, DIGIT_5_Indx,   
               DIGIT_6_Indx, DIGIT_7_Indx, DIGIT_8_Indx,   
               DIGIT_9_Indx };  
 void Initialize_Display( void );  
 void Display_Value( uint8_t code );  
 void main()  
 {  
  CMCON = 0x07;  
  ANSEL = 0x08;  
  TRISA |= 0x10; // RA4/AN3 as Input  
  ADCON0 = 0x8D; // Right Justified, VDD, AN3 Selected, A/D Power-Up  
  ADCON1 = 0x10;  
  InitTimer0 ();  
  Initialize_Display ();  
  while(1)  
  {  
   uint16_t adc_data = 0;  
   ADCON0 = 0x8D;  
   ADCON0bits.GO_DONE = 1;  // Start Conversion  
   while( ADCON0bits.GO_DONE ); // Wait Here  
   adc_data = ADRESH<<8;  
   adc_data |= ADRESL;  
   adc_data = adc_data & 0x3FF;  
   adc_data = adc_data/10;  
   uint8_t d1, d2, d3;  
   d1 = adc_data % 10;  
   d2 = (adc_data/10) % 10;  
   d3 = (adc_data/100) % 10;  
   PORTA &= 0xF8;  
   PORTA |= 0x04;  
   Display_Value (SevenSegment[d1]);  
   __delay_ms(10);  
   PORTA &= 0xF8;  
   PORTA |= 0x02;  
   Display_Value (SevenSegment[d2]);  
   __delay_ms(10);  
   PORTA &= 0xF8;  
   PORTA |= 0x01;  
   Display_Value (SevenSegment[d3]);  
   __delay_ms(10);  
  }  
 }  
 void Initialize_Display( void )  
 {  
  TRISA &= 0xD8; // RA0,RA1,RA2, RA5 as Output  
  TRISC = 0x00;  
  PORTA &= 0xD8;  
  PORTC = 0x00;  
 }  
 void Display_Value( uint8_t code )  
 {  
  /*  
   * a -> RC0 (bit-0)  
   * b -> RC3 (bit-1)  
   * c -> RC5 (bit-2)  
   * d -> RC4 (bit-3)  
   * e -> RC2 (bit-4)  
   * f -> RC1 (bit-5)  
   * g -> RA5 (bit-6)  
   */  
  PORTC = 0;  
  PORTA &= 0xDF;  
  if ( code == BLANK_Indx )  
  {  
   PORTC = 0xFF;  
   PORTA |= 0x20;  
  }  
  else  
  {  
   // RC0  
   if( code & 0x01 )  
    PORTC |= 0x01;  
   // RC3  
   if( code & 0x02 )  
    PORTC |= 0x08;  
   // RC5  
   if( code & 0x04 )  
    PORTC |= 0x20;  
   // RC4  
   if( code & 0x08 )  
    PORTC |= 0x10;  
   // RC2  
   if( code & 0x10 )  
    PORTC |= 0x04;  
   // RC1  
   if( code & 0x20 )  
    PORTC |= 0x02;  
   // RA5  
   if( code & 0x40 )  
    PORTA |= 0x20;  
  }  
 }  

The above program is written in MPLAB-X 3.40 with XC8 v1.38 Compiler.
Calculation Logic Explanation:
The Voltage which needs to be measured is first scaled down by 20 times with the help of voltage divider circuit, so whatever voltage PIC micro-controller's analog to digital converter will measure, have to be multiplied by 20 times to get back the exact value.
But if you see the code given below, all the above calculation is done in a single line i.e. by dividing the adc value by 10.
Calculation part in Source Code
Now i will explain, why i just divided the adc value by 10 instead of doing what i have written in the first line of this section. So lets understand all this, step by step:
  • We get digital value of the analog signal and let's say it is stored in ADC_DATA variable.
  • Next step is to convert this digital value in voltage.
  • Analog Value Sensed by PIC,
    ANALOG_VALUE_PIC = (Reference Voltage*ADC_DATA)/(2^10)
  • ANALOG_VALUE_PIC = (5*ADC_DATA)/(1024)
    Reference Voltage in our case is 5V and we are using 10-bit ADC of PIC micro-controller, that's why 2^10 = 1024 is used.
  • ANALOG_VALUE = ANALOG_VALUE_PIC * 20
    We multiplied by 20 because, we scale down the voltage by 20 as explained above.
  • ANALOG_VALUE = (5*20/1024)*ADC_DATA
  • So it's clear from the above expression, that we have (5*10/1024) as constant value, which is equivalent to = 0.098 and i rounded off this to 0.01 or (1/10), to make the calculation simple, with some error in the system.
  • Final Expression, ANALOG_VALUE = ADC_DATA/10 
So, it depends on you which method you want to use, the method i used will save the huge space but is not accurate, and the another will consume space but is accurate. So choice is yours.
Sample-1

Sample-2
Have a look at this video for the complete project demo:


Download Source From GitHub

11 comments:

  1. Thanks for your great work.... Seven Segment Common Cathode or common anode??

    ReplyDelete
    Replies
    1. Thanks for appreciation.
      Its Common Anode.
      You can download the code, which also has Proteus Design file, after opening the design you can see that this part has -CA written at last, which means Common Anode, similarly if -CC is written then that seven segment is Common Cathode.

      Delete
  2. I want to further use equation:
    (dB) = 20xLog{to the base 10} x (V in/V out)
    & change the range to -16 to 16 (dBm)
    How far it is possible?
    Please let me know fast

    ReplyDelete
    Replies
    1. It looks a simple formula to me, but I doubt that small micro will be able to handle this.
      What is the problem you are facing in applying this formula, as it is not very clear from your comment.

      Delete
    2. I have analog input from 0V to 1.3V. I want to convert it into dBm using (dB) = 20xLog{to the base 10} x (V in/V out) equation. Can you please suggest me what changes i have to do it in the program ?

      Delete
    3. Hi,
      Thanks for the tutorial.
      What changes I have to make it to work with common cathode display

      Delete
  3. Sir, please guide me how convert this meter upto 300v.

    ReplyDelete
  4. hi,
    Thanks for the tutorial.
    What changes I have to make inorder to work it with Common Cathode in program.

    ReplyDelete
  5. Hi send this project dineshbdinesh@gmail.com

    ReplyDelete
    Replies
    1. Everything is already there on this page. What else is needed?

      Delete
  6. Hi sir
    iam a new bee in programming while compiling i got the following errors
    make -f nbproject/Makefile-default.mk SUBPROJECTS= .build-conf
    make[1]: Entering directory 'C:/Users/Manoj/Documents/psmcu676.X'
    make -f nbproject/Makefile-default.mk dist/default/production/psmcu676.X.production.hex
    make[2]: Entering directory 'C:/Users/Manoj/Documents/psmcu676.X'
    "C:\Program Files (x86)\Microchip\xc8\v2.05\bin\xc8-cc.exe" -mcpu=16F676 -c -fno-short-double -fno-short-float -O0 -fasmfile -maddrqual=ignore -xassembler-with-cpp -Wa,-a -DXPRJ_default=default -msummary=-psect,-class,+mem,-hex,-file -ginhx032 -Wl,--data-init -mno-keep-startup -mno-osccal -mno-resetbits -mno-save-resetbits -mno-download -mno-stackcall -std=c99 -gdwarf-3 -mstack=compiled:auto:auto -o build/default/production/psmcu676.p1 psmcu676.c
    ::: advisory: (2049) C99 compliant libraries are currently not available for baseline or mid-range devices, or for enhanced mid-range devices using a reentrant stack; using C90 libraries
    psmcu676.c:26:2: error: unknown type name 'uint8_t'
    uint8_t SevenSegment[] = { DIGIT_0_Indx, DIGIT_1_Indx, DIGIT_2_Indx,
    ^
    psmcu676.c:31:22: error: unknown type name 'uint8_t'
    void Display_Value( uint8_t code );
    ^
    psmcu676.c:39:3: warning: implicit declaration of function 'InitTimer0' is invalid in C99 [-Wimplicit-function-declaration]
    InitTimer0 ();

    ReplyDelete