it is time to do some cool projects using PIC12F683. Now we can have a nice LCD display with PIC12F683. This project shows how to make a digital voltmeter of range 0-20V using PIC12F683. Enjoy!
Background
You cannot feed 20V directly to a PIC I/O pin, you need a resistor divider network that converts 0-20V range into 0-5V. The figure below shows how it will be achieved.
At any instant, the voltage Va will be 1/4th of the input voltage, Vin. So, for maximum input voltage of 20V, the Va will be 5V. A 5.1V Zener diode in the figure is to prevent Va to rise above 5.1V if the input voltage goes much above 20V. This will protect the microcontroller port. The analog voltage Va is read through AN0 port and is converted to 10-bit digital number (0-1023) by PIC12F683.
ADC Math
0-5V Analog I/P ---> 0-1023 Digital Count
=> Resolution = (5-0)/(1023-0) = 0.00489 V/Count
=> I/P voltage = 4*Va = 4* Digital Count * 0.00489 = 0.01956 * Digital Count
To avoid floating point, use I/P voltage = 196*Digital Count. This number is a long integer.
Example, suppose
Vin = 13.6V. Then,
Va = 0.25*Vin = 3.4V
=> Digital Count = 3.4/0.00489 = 695
=> Calculated I/P Voltage = 196*695 = 136220 = 13.6V (First 3 digits of 6 digit product)
Sources of error
The above math looks pretty easy but when you implement it, you will not errors in output measurements because the above calculations are based on following ideal conditions:
So, let's revise the math above with real figures. I measured the supply voltage to PIC and it is 5.02V. R1 and R2 are measured to be 1267 and 3890 Ohms. So this gives,
/* Digital Voltmeter and
3-wire Serial LCD using 74HC595
Rajendra Bhatt, Oct 3, 2010
*/
sbit Data_Pin at GP5_bit;
sbit Clk_Pin at GP1_bit;
sbit Enable_Pin at GP2_bit;
// Always mention this definition statement
unsigned short Low_Nibble, High_Nibble, p, q, Mask, N,t, RS, Flag, temp;
void Delay_50ms(){
Delay_ms(50);
}
void Write_LCD_Nibble(unsigned short N){
Enable_Pin = 0;
// ****** Write RS *********
Clk_Pin = 0;
Data_Pin = RS;
Clk_Pin = 1;
Clk_Pin = 0;
// ****** End RS Write
// Shift in 4 bits
Mask = 8;
for (t=0; t<4; t++){
Flag = N & Mask;
if(Flag==0) Data_Pin = 0;
else Data_Pin = 1;
Clk_Pin = 1;
Clk_Pin = 0;
Mask = Mask >> 1;
}
// One more clock because SC and ST clks are tied
Clk_Pin = 1;
Clk_Pin = 0;
Data_Pin = 0;
Enable_Pin = 1;
Enable_Pin = 0;
}
// ******* Write Nibble Ends
void Write_LCD_Data(unsigned short D){
RS = 1; // It is Data, not command
Low_Nibble = D & 15;
High_Nibble = D/16;
Write_LCD_Nibble(High_Nibble);
Write_LCD_Nibble(Low_Nibble);
}
void Write_LCD_Cmd(unsigned short C){
RS = 0; // It is command, not data
Low_Nibble = C & 15;
High_Nibble = C/16;
Write_LCD_Nibble(High_Nibble);
Write_LCD_Nibble(Low_Nibble);
}
void Initialize_LCD(){
Delay_50ms();
Write_LCD_Cmd(0x20); // Wake-Up Sequence
Delay_50ms();
Write_LCD_Cmd(0x20);
Delay_50ms();
Write_LCD_Cmd(0x20);
Delay_50ms();
Write_LCD_Cmd(0x28); // 4-bits, 2 lines, 5x7 font
Delay_50ms();
Write_LCD_Cmd(0x0C); // Display ON, No cursors
Delay_50ms();
Write_LCD_Cmd(0x06); // Entry mode- Auto-increment, No Display shifting
Delay_50ms();
Write_LCD_Cmd(0x01);
Delay_50ms();
}
void Position_LCD(unsigned short x, unsigned short y){
temp = 127 + y;
if (x == 2) temp = temp + 64;
Write_LCD_Cmd(temp);
}
void Write_LCD_Text(char *StrData){
q = strlen(StrData);
for (p = 0; p < q; p++){
temp = StrData[p];
Write_LCD_Data(temp);
}
}
char Message1[] = "DVM Project";
unsigned int ADC_Value, DisplayVolt;
char *volt = "00.00";
void main() {
CMCON0 = 7; // Disable Comparators
TRISIO = 0b00001001; // All Outputs, except GP0 and GP3
ANSEL = 0x01; // GP0 analog i/p
Initialize_LCD();
Position_LCD(1,3);
Write_LCD_Text(Message1);
Position_LCD(2,10);
Write_LCD_Data('V');
do {
ADC_Value = ADC_Read(0);
DisplayVolt = ADC_Value * 2;
volt[0] = DisplayVolt/1000 + 48;
volt[1] = (DisplayVolt/100)%10 + 48;
volt[3] = (DisplayVolt/10)%10 + 48;
volt[4] = DisplayVolt%10 + 48;
Position_LCD(2,4);
Write_LCD_Text(volt);
delay_ms(100);
} while(1);
}
Background
You cannot feed 20V directly to a PIC I/O pin, you need a resistor divider network that converts 0-20V range into 0-5V. The figure below shows how it will be achieved.
At any instant, the voltage Va will be 1/4th of the input voltage, Vin. So, for maximum input voltage of 20V, the Va will be 5V. A 5.1V Zener diode in the figure is to prevent Va to rise above 5.1V if the input voltage goes much above 20V. This will protect the microcontroller port. The analog voltage Va is read through AN0 port and is converted to 10-bit digital number (0-1023) by PIC12F683.
ADC Math
0-5V Analog I/P ---> 0-1023 Digital Count
=> Resolution = (5-0)/(1023-0) = 0.00489 V/Count
=> I/P voltage = 4*Va = 4* Digital Count * 0.00489 = 0.01956 * Digital Count
To avoid floating point, use I/P voltage = 196*Digital Count. This number is a long integer.
Example, suppose
Vin = 13.6V. Then,
Va = 0.25*Vin = 3.4V
=> Digital Count = 3.4/0.00489 = 695
=> Calculated I/P Voltage = 196*695 = 136220 = 13.6V (First 3 digits of 6 digit product)
Sources of error
The above math looks pretty easy but when you implement it, you will not errors in output measurements because the above calculations are based on following ideal conditions:
- Vcc supply voltage to PIC12F683 is exactly 5V,
- R1 and R2 are exact 1.3K and 3.9K respectively
So, let's revise the math above with real figures. I measured the supply voltage to PIC and it is 5.02V. R1 and R2 are measured to be 1267 and 3890 Ohms. So this gives,
0 - 5.02 V Analog I/P ---> 0-1023 Digital Count
=> Resolution = (5.02-0)/(1023-0) = 0.004907 V/Count
Va = 1267*Vin/(1267+3890) = 0.2457*Vin
=> I/P voltage = 4.07*Va = 4.07* Digital Count * 0.004907
= 0.01997 * Digital Count
= 0.02*Digital Count (Approx.)
To avoid floating point, use I/P voltage = 2*Digital Count. No need for Long integer.
Example, suppose
Vin = 4.6V. Then,
Va = 0.2457*Vin = 1.13V
=> Digital Count = 1.13/0.004907 = 230
=> Calculated I/P Voltage = 2*230 = 0460 = 04.6V (First 3 digits of 4 digit product)
Circuit Setup
Connect the Va terminal in the above resistor network to AN0 (GP0) input of PIC12F683. Also connect the serial LCD the same way as we did in our 3-wire serial LCD project. Just maintain the same setup for display as shown below.
Software
/* Digital Voltmeter and
3-wire Serial LCD using 74HC595
Rajendra Bhatt, Oct 3, 2010
*/
sbit Data_Pin at GP5_bit;
sbit Clk_Pin at GP1_bit;
sbit Enable_Pin at GP2_bit;
// Always mention this definition statement
unsigned short Low_Nibble, High_Nibble, p, q, Mask, N,t, RS, Flag, temp;
void Delay_50ms(){
Delay_ms(50);
}
void Write_LCD_Nibble(unsigned short N){
Enable_Pin = 0;
// ****** Write RS *********
Clk_Pin = 0;
Data_Pin = RS;
Clk_Pin = 1;
Clk_Pin = 0;
// ****** End RS Write
// Shift in 4 bits
Mask = 8;
for (t=0; t<4; t++){
Flag = N & Mask;
if(Flag==0) Data_Pin = 0;
else Data_Pin = 1;
Clk_Pin = 1;
Clk_Pin = 0;
Mask = Mask >> 1;
}
// One more clock because SC and ST clks are tied
Clk_Pin = 1;
Clk_Pin = 0;
Data_Pin = 0;
Enable_Pin = 1;
Enable_Pin = 0;
}
// ******* Write Nibble Ends
void Write_LCD_Data(unsigned short D){
RS = 1; // It is Data, not command
Low_Nibble = D & 15;
High_Nibble = D/16;
Write_LCD_Nibble(High_Nibble);
Write_LCD_Nibble(Low_Nibble);
}
void Write_LCD_Cmd(unsigned short C){
RS = 0; // It is command, not data
Low_Nibble = C & 15;
High_Nibble = C/16;
Write_LCD_Nibble(High_Nibble);
Write_LCD_Nibble(Low_Nibble);
}
void Initialize_LCD(){
Delay_50ms();
Write_LCD_Cmd(0x20); // Wake-Up Sequence
Delay_50ms();
Write_LCD_Cmd(0x20);
Delay_50ms();
Write_LCD_Cmd(0x20);
Delay_50ms();
Write_LCD_Cmd(0x28); // 4-bits, 2 lines, 5x7 font
Delay_50ms();
Write_LCD_Cmd(0x0C); // Display ON, No cursors
Delay_50ms();
Write_LCD_Cmd(0x06); // Entry mode- Auto-increment, No Display shifting
Delay_50ms();
Write_LCD_Cmd(0x01);
Delay_50ms();
}
void Position_LCD(unsigned short x, unsigned short y){
temp = 127 + y;
if (x == 2) temp = temp + 64;
Write_LCD_Cmd(temp);
}
void Write_LCD_Text(char *StrData){
q = strlen(StrData);
for (p = 0; p < q; p++){
temp = StrData[p];
Write_LCD_Data(temp);
}
}
char Message1[] = "DVM Project";
unsigned int ADC_Value, DisplayVolt;
char *volt = "00.00";
void main() {
CMCON0 = 7; // Disable Comparators
TRISIO = 0b00001001; // All Outputs, except GP0 and GP3
ANSEL = 0x01; // GP0 analog i/p
Initialize_LCD();
Position_LCD(1,3);
Write_LCD_Text(Message1);
Position_LCD(2,10);
Write_LCD_Data('V');
do {
ADC_Value = ADC_Read(0);
DisplayVolt = ADC_Value * 2;
volt[0] = DisplayVolt/1000 + 48;
volt[1] = (DisplayVolt/100)%10 + 48;
volt[3] = (DisplayVolt/10)%10 + 48;
volt[4] = DisplayVolt%10 + 48;
Position_LCD(2,4);
Write_LCD_Text(volt);
delay_ms(100);
} while(1);
}
Testing
I tested my DVM with my variable power supply and also verified with another digital multimeter.
I hope you liked this project.