🔋 Project: Digital Voltmeter with PIC16F877A bonkers image absolutely heres a

1. Introduction

In this project, you’ll build a digital voltmeter capable of measuring 0V to 5V DC using the built-in ADC (Analog-to-Digital Converter) of the PIC16F877A microcontroller. The measured voltage is displayed on a 16×2 LCD screen in real-time.

Why this project?
Understanding how to measure voltages with a microcontroller is fundamental to many applications like battery level monitoring, sensor data acquisition, and general diagnostics.


2. Learning Objectives

  • Interface analog inputs and use the ADC module in PIC16F877A.
  • Calculate and display voltage values on an LCD screen.
  • Understand ADC resolution and scaling.
  • Practice with 4-bit LCD communication protocol.

3. Required Tools and Components

  • PIC16F877A microcontroller
  • 16×2 LCD (HD44780 compatible)
  • Variable voltage source or potentiometer (for voltage testing)
  • 10K potentiometer (for LCD contrast control)
  • Jumper wires, breadboard, and power supply (5V regulated)
  • MPLAB X IDE with XC8 compiler
  • PIC programmer (e.g., PICkit 3 or 4)

4. Background Knowledge

ADC Basics
The ADC in PIC16F877A is 10-bit, meaning it can convert an analog voltage into a digital value between 0 and 1023. With a reference voltage of 5V, each step equals ~4.88 mV (5V / 1024). To calculate voltage from ADC value:

iniCopyEditVoltage = (ADC_Value * 5.0) / 1024

LCD Interfacing
The LCD operates in 4-bit mode to save I/O pins. It requires control pins (RS, E) and data pins (D4–D7) along with power and contrast control.


5. Wiring Instructions (Descriptive)

  • Connect analog voltage source (or potentiometer wiper) to RA0 (Pin 2).
  • Connect LCD pins RS, E, D4–D7 to RB0, RB1, RB2–RB5 respectively.
  • Use a 10K potentiometer across Vss–Vdd of LCD, with wiper to Vo for contrast.
  • Power both the PIC and LCD with 5V regulated.
  • Tie R/W of LCD to GND to keep it in write mode.

6. Full Source Code with Comments (XC8)

// Digital Voltmeter Code for PIC16F877A

cCopyEdit#include <xc.h>
#define _XTAL_FREQ 8000000  // 8 MHz crystal

// Configuration bits
#pragma config FOSC = HS
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config BOREN = ON
#pragma config LVP = OFF
#pragma config CPD = OFF, CP = OFF

// LCD Control Pins
#define RS RB0
#define E  RB1
#define D4 RB2
#define D5 RB3
#define D6 RB4
#define D7 RB5

// Function declarations
void ADC_Init();
unsigned int ADC_Read(unsigned char channel);
void LCD_Init();
void LCD_Command(unsigned char cmd);
void LCD_Char(unsigned char data);
void LCD_String(const char *str);
void LCD_SetCursor(unsigned char row, unsigned char column);
void LCD_Clear();

void main() {
    unsigned int adc_result;
    float voltage;
    char buffer[10];

    TRISA = 0xFF;  // Set PORTA as input
    TRISB = 0x00;  // Set PORTB as output for LCD

    ADC_Init();
    LCD_Init();
    LCD_Clear();
    LCD_SetCursor(1, 1);
    LCD_String("Voltage: ");

    while (1) {
        adc_result = ADC_Read(0);  // Read analog value from channel 0 (RA0)
        voltage = (adc_result * 5.0) / 1024.0;  // Convert to voltage

        sprintf(buffer, "%.2f V", voltage);
        LCD_SetCursor(2, 1);
        LCD_String("            ");  // Clear previous reading
        LCD_SetCursor(2, 1);
        LCD_String(buffer);

        __delay_ms(500);
    }
}

// Initialize ADC Module
void ADC_Init() {
    ADCON1 = 0x80;  // Right justified result, Fosc/32
    ADCON0 = 0x41;  // ADC ON, Channel 0 selected
}

// Read from ADC
unsigned int ADC_Read(unsigned char channel) {
    ADCON0 &= 0xC5;  // Clear existing channel selection
    ADCON0 |= (channel << 3);  // Select desired channel
    __delay_ms(2);  // Acquisition delay
    GO_nDONE = 1;  // Start conversion
    while (GO_nDONE);  // Wait until done
    return ((ADRESH << 8) + ADRESL);  // Combine 10-bit result
}

// LCD Functions
void LCD_Init() {
    LCD_Command(0x02); // Initialize 4-bit mode
    LCD_Command(0x28); // 2 line, 5x7 matrix
    LCD_Command(0x0C); // Display ON, cursor OFF
    LCD_Command(0x06); // Increment cursor
    LCD_Command(0x01); // Clear display
}

void LCD_Command(unsigned char cmd) {
    RS = 0;
    D4 = (cmd >> 4) & 1;
    D5 = (cmd >> 5) & 1;
    D6 = (cmd >> 6) & 1;
    D7 = (cmd >> 7) & 1;
    E = 1; __delay_ms(1); E = 0;

    D4 = cmd & 1;
    D5 = (cmd >> 1) & 1;
    D6 = (cmd >> 2) & 1;
    D7 = (cmd >> 3) & 1;
    E = 1; __delay_ms(1); E = 0;
    __delay_ms(2);
}

void LCD_Char(unsigned char data) {
    RS = 1;
    D4 = (data >> 4) & 1;
    D5 = (data >> 5) & 1;
    D6 = (data >> 6) & 1;
    D7 = (data >> 7) & 1;
    E = 1; __delay_ms(1); E = 0;

    D4 = data & 1;
    D5 = (data >> 1) & 1;
    D6 = (data >> 2) & 1;
    D7 = (data >> 3) & 1;
    E = 1; __delay_ms(1); E = 0;
    __delay_ms(2);
}

void LCD_String(const char *str) {
    while (*str) {
        LCD_Char(*str++);
    }
}

void LCD_SetCursor(unsigned char row, unsigned char column) {
    unsigned char position = (row == 1) ? 0x80 + (column - 1) : 0xC0 + (column - 1);
    LCD_Command(position);
}

void LCD_Clear() {
    LCD_Command(0x01);
}

7. Troubleshooting Tips

  • LCD not responding: Verify connections, adjust contrast potentiometer, and ensure correct delays are used in code.
  • Voltage readings incorrect: Double-check the voltage calculation formula, especially ADC scaling factors.
  • No ADC value change: Confirm voltage at RA0 using a multimeter; ensure analog source is within 0–5V.
  • Compile errors: Re-check configuration bits and ensure ADCON1/ADCON0 are correctly initialized.

8. Project Extensions

  • Expand measurement range using a voltage divider (e.g., for 0–12V).
  • Log voltage readings to EEPROM or send them over UART.
  • Add over-voltage warning with buzzer alert.
  • Display readings on a graphical LCD or OLED display.