In this tutorial, we will design a digital voltmeter using a 7-segment display and pic microcontroller. Usually, we use LCDs to display sensor data. In the last tutorial, we designed a DC digital voltmeter with a 16×2 Liquid crystal display. But in this post, instead of using LCD, we will use a four-digit 7-segment display to print voltage value. We will use the ADC module of PIC16F877a microcontroller to measure DC voltage.
Components Required
- PIC16F877A Microcontroller
- 4-digital Seven Segment Display
- Resistors
- Crystal Oscillator
- Capacitors
- NPN transistors
Digital Voltmeter with 7-segment display circuit diagram
A picture below shows the circuit diagram of DC voltmeter with four digit seven-segment display and voltage divider circuits interfacing with PIC16F877A microcontroller. But you can use any other pic microcontroller also.
How to measure DC voltage?
Firstly, lets discuss do we need to use a voltage divider circuit. The simple and basic reason to use voltage divider circuit is that pic microcontroller operating voltage is between 0-5 volts. Hence, built-in ADC module of PIC16F877A microcontroller can read analog signal in the range 0 to 5 volts only. Therefore, in order to measure dc voltages higher than this level, we need come up with a solution to step down input voltage. This is the reason a voltage divider circuit is required in this project.
A voltage divider circuit as its name suggests divides the voltage between across two resistors. As you can see from the above circuit of dc voltmeter, we used two resistors
R9 = 180K
R7 =20k
The formula of voltage divider is very simple that is
VOUT = (R7/(R7+R9) x VIN)
For example, if input voltage is 19 volts, the output voltage will be:
VOUT = (20k/(20k+180k) x 19) = 1.9 volts
1.9 volts is less than 5 volts. Therefore, we can measure this voltage with ADC of PIC16F877A microcontroller. If you see from the voltage divider formula, the voltage step down or reduction factor is 20/200 = 1/10
Step down factor = 1/10
We can use this reduction factor in our programming to get actual voltage by measuring only the output voltage of voltage divider. We will measure step -down voltage that is the output of voltage divider circuit. But, inside our program, we will multiply measured voltage value with the reduction factor to get actual value of input voltage.
We used analog channel AN0/RA0 of PIC16F877A microcontroller. We have already post a guide on how to use analog digital module of Pic Microcontroller, you can read this post:
Connection with 4-digital seven-segment device
As you know that there are two types of 7-segment displays: namely common anode and common cathode type. We will use a common cathode type 4-digit device to display measure voltage value.
Connect PORTD with A-g pins of 4-digital seven-segment display using 330ohm current limiting resistors. Also connect RB0-RB3 pins of PORTB with control pins 1-4 of display device. Same data lines are used to send binary pattern to each digit of display device, but control lines select that at which place (7-segment) we want to display a digit.
To explore further on 7-segment displays interfacing with pic microcontrollers, go through these in-depth guides:
- Seven-Segment displays interfacing with pic microcontroller
- How to print ADC value on 7-segment using pic microcontroller
Digital voltmeter with 7-segment display Simulation
This circuit is simulated with proteus. As you can see, we connect a 50 volts DC source with a voltage divider circuit through a variable resistor. We use this variable resistor to apply a variable voltage to the circuit.
We can observe the same voltage on a 4-digit seven-segment device according to the input voltage variation.
MikroC Code
This code is written using MikroC for Pic compiler. Create a new project with MikroC compiler by selecting PIC16F877A microcontroller and set frequency to 8MHz. If you don’t know how create new project in mikroC, we suggest you read this post:
After creating a new project with MikroC, set configuration bits to these values:
CONFIG : $2007 : 0x2F4A
After that copy this code and compile it with MikroC for Pic compiler.
// define name to each control signal for 7-segment
sbit digit1 at PORTB.B0;
sbit digit2 at PORTB.B1;
sbit digit3 at PORTB.B2;
sbit digit4 at PORTB.B3;
// This array stores binary bit pattern that will be send to PORTD
unsigned char binary_pattern[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; // without Dp turn
unsigned char display1[10]= {0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xE7}; // with dp turn on
// variables to store digits, digital value and output voltage
unsigned int a1,a2,a3,a4; // temporary variables to store data of adc
int adc_value; //store output value from Analog Read functoion
unsigned int number;
long tlong;
unsigned int voltage;
// this function retrive each digita that will displayed on device
void get_digits()
{
a1 = voltage / 1000u; // holds 1000's digit
a2 = ((voltage/100u)%10u); // holds 100's digit
a3 = ((voltage/10u)%10u); // holds 10th digit
a4 = (voltage%10u); // holds unit digit value
}
// this function displays measured voltage on seven-segments
void display_voltage()
{
PORTD = binary_pattern[a2]; // send 1000's place data to fourth digit
digit1 = 0; // turn on forth display unit
delay_ms(3);
digit1 = 1; // turn off forth display unit
PORTD = display1[a3]; // send 100's place data to 3rd digit
digit2 = 0; // turn on 3rd display unit
delay_ms(3);
digit2 = 1; // turn off 3rd display unit
PORTD = binary_pattern[a4]; // send 10th place data to 2nd digit
digit3 = 0; // turn on 2nd display unit
delay_ms(3);
digit3 = 1; // turn off 2nd display unit
PORTD=binary_pattern[a1]; // send unit place data to 1st digit
digit4 = 0; // turn on 1st display unit
delay_ms(3);
digit4 = 1; // turn off 1st display unit
}
void interrupt()
{
get_digits(); // call function to split data
display_voltage(); //call display_data() function to output value to seven segment
T0IF_bit = 0; // clear source of timer0 interrupt
}
void main(void)
{
TMR0 = 0; // timer0 reset bit
OPTION_REG = 0x83; // select prescalar value 1:16 for timer0
INTCON = 0xA0; // turn on global interrupt and timer0 overflow interrupt
TRISD = 0x00; //define PORTD as a output pin
PORTD=0x00; // initialize PORTD pins to active low
TRISB=0x00;// Set PORTB as a output port
// set control pins pins initially active high
digit1 = 1;
digit2 = 1;
digit3 = 1;
digit4 = 1;
while(1)
{
adc_value = ADC_Read(0); // read data from channel 0
tlong = (float)adc_value*0.488768555;
voltage = tlong;
//delay_ms(100); // wait 100 milliseconds
}
}
MPAB XC8 Code
This code is for MPLAB XC8 Compiler. If you don’t know how to use MPLAB and XC8 compiler, you can read this complete in-depth guide:
After creating a new project, set configuration bits by generating configuration bit file with MPLAB XC8. While generating this file, select the HS crystal option and leave the remaining setting as to default settings.
#include <xc.h>
#define _XTAL_FREQ 20000000 //define crystal frequency to 20MHz
#define digit1 PORTBbits.RB0
#define digit2 PORTBbits.RB1
#define digit3 PORTBbits.RB2
#define digit4 PORTBbits.RB3
// This array stores binary bit pattern that will be send to PORTD
unsigned char binary_pattern[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
unsigned char display1[10]= {0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xE7}; // with dp turn on
unsigned int a1,a2,a3,a4;
unsigned int counter = 0;
int adc_value; //store output value from Analog Read functoion
unsigned int number;
long tlong;
unsigned int voltage;
void Analog_setting(){
ADCON0 = 0x81;
ADCON1 = 0x02;
}
unsigned int Analog_read(unsigned char channel){
int aadc,bbdc, ccdc;
if(channel>7)return 0;
ADCON0 = ADCON0 & 0xC5;
ADCON0 = ADCON0 | (channel << 3);
__delay_ms(2);
ADCON0bits.GO_DONE = 1;
while(ADCON0bits.GO_DONE);
aadc = ADRESH;
aadc = aadc<<2;
bbdc = ADRESL;
bbdc = bbdc >>6;
ccdc = aadc|bbdc;
return ccdc;
}
void main(void)
{
Analog_setting();
TRISD = 0x00; //define PORTD as a output pin
PORTD=0X00; // initialize PORTD pins to active low
TRISB=0X00;
digit1 = 1;
digit2 = 1;
digit3 = 1;
digit4 = 1;
while(1)
{
adc_value = Analog_read(0); // read data from channel 0
tlong = (float)adc_value*0.488768555;
voltage = tlong;
a1 = voltage / 1000; // holds 1000's digit
a2 = ((voltage/100)%10); // holds 100's digit
a3 = ((voltage/10)%10); // holds 10th digit
a4 = (voltage%10); // holds unit digit value
PORTD=binary_pattern[a2]; // send 1000's place data to fourth digit
digit1=0; // turn on forth display unit
__delay_ms(3);
digit1=1; // turn off forth display unit
PORTD=display1[a3]; // send 100's place data to 3rd digit
digit2=0; // turn on 3rd display unit
__delay_ms(3);
digit2=1; // turn off 3rd display unit
PORTD=binary_pattern[a4]; // send 10th place data to 2nd digit
digit3 = 0; // turn on 2nd display unit
__delay_ms(3);
digit3 = 1; // turn off 2nd display unit
PORTD=binary_pattern[a1]; // send unit place data to 1st digit
digit4 = 0; // turn on 1st display unit
__delay_ms(3);
digit4 = 1; // turn off 1st display unit
}
return ;
}
Hi. Just very quickly sifted through this, but found it somewhat uninteresting since it has “only” 4 digits.
Is there any tip of any micro processor that has higher resolution, as a build I mean, so that it might have, 5 or 6 digits instead?
I think many would find such a build very interesting, but it’d, of course, had to be reliable too.
I’d like to have one that can measure more finite than these cheap-o-meter out there, but I don’t feel like paying multiple digits, in cash that is.
I noticed that the input is very rudimentary on this build, and I can only imagine that a real one would have, besides low tolerances on the components, also have more sophisticated setup of components.
Could you put together a such build it’d be really super.
Thanks.
Hi
It is just for a demo purpose. To make better and practical design, we can use a shift register like 74HC595 and I2C communication to display data. By using a shift register like 74HC595 we can achieve same result with microcontroller three pins only.
Yeah, I know that, but I was more interested in the micro controller and input-circuitry. I don’t know how high resolution this particular controller has, but I’d imagine 10 bit as most has.
Sure you could use an external ADC with high resolution like, I dunno, 16-24 bit, but this is a bit over my knowledge when it comes to hooking the stuff up (Seem to always be some weird connections by some strange components, some filtering-caps and whatnot) as well as programming, I’m sort of semi-lost.
I haven’t even used any PIC yet, only some small-time projects with ATMEGA, apart from my washing machine (re)build which incorporates a 2560 with tons of sensors and output to 3-phase motor-controller and lots of other stuff.
Haven’t started programming that yet, but it’ll be an hell of a challenge!
Yes if we need a higher resolution like anything below 1mV, we can use higher resolution ADC like this one:
http://microcontrollerslab.com/ads1115-external-adc-with-esp32/
Pic microcontroller is also very easy to use similar to ATMEGA microcontrollers.
Hi I just did what you show in the video and in the code MikroC. It does not work, can you help me?
Open library manager on your compiler. Make sure that ADC library is being used.
I soooooooolved the problem!!!!!!
Hi Good to know
Might be useful for others if you told what problem you had and how you fixed it…
Absolutely You are on right!!!! I just used the wrong display, instead to use the common cathode I used the common anode!!!!!
Hi may i know for the mikroc code, can it be use for the pic16f887 ? If there any changes that are needed could you tell me.
This code will work fine for PIC16F887 in MikroC compiler.
Why my 7 segment cant display value 10V and above? After 9.90V it suddenly 0.90 and then others number, is there any thing wrong? (PIC16F887)
You need to insert this command under void main
ANSEL=ANSELH=0
hi.
why my 7 sengment display until 8.00 then jumps to 80.00??
i use pic16f887
Why do you always use timer0 to read AN0 ..? only to delay between readings …? or do you have another objective ..?