
๐ Project: Temperature Monitoring System Using LM35 and PIC16F877A
โ 1. Introduction
This project demonstrates how to use the PIC16F877A microcontroller to read temperature from an LM35 sensor and display it on a 16×2 LCD. The analog signal from the sensor is processed using the microcontroller’s built-in ADC (Analog-to-Digital Converter).
This is a practical starting point for understanding data acquisition and display systems.
๐ฏ 2. Learning Objectives
- Learn how to configure and use the ADC of the PIC16F877A.
- Interface and read data from an analog temperature sensor (LM35).
- Display processed data on a 16×2 LCD in 4-bit mode.
- Understand analog-to-digital voltage scaling and conversion.
- Gain experience in debugging embedded hardware and code.
๐งฐ 3. Tools and Components
Hardware Required
- PIC16F877A microcontroller
- LM35 analog temperature sensor
- 16×2 LCD display (HD44780-compatible)
- 10kฮฉ potentiometer (for LCD contrast)
- Breadboard and jumper wires
- 5V regulated power supply
- 20 MHz crystal oscillator (or 4 MHz alternative)
- Two 22pF capacitors
- MPLAB-compatible PIC programmer (e.g., PICkit 3 or 4)
Software Required
- MPLAB X IDE
- MPLAB XC8 compiler
- Proteus (for simulation, optional)
๐ 4. Background Concepts
ADC (Analog-to-Digital Converter): A module that converts an analog voltage into a binary number. The PIC16F877A provides a 10-bit ADC, meaning the analog range (0โ5V) is converted into a digital value between 0 and 1023.
LM35 Temperature Sensor: Outputs 10 mV per ยฐC. So, 250 mV = 25ยฐC.
LCD 16×2 (HD44780): Displays characters in two lines. We use 4-bit mode to save microcontroller I/O pins.
๐ผ๏ธ 5. Circuit Wiring Guide
LM35 Sensor
- VCC โ +5V
- GND โ GND
- Vout โ RA0 (AN0) of PIC16F877A
LCD Display Connections (4-bit Mode)
- RS โ RC0
- RW โ GND (write only)
- E โ RC1
- D4 โ RC2
- D5 โ RC3
- D6 โ RC4
- D7 โ RC5
- VSS โ GND
- VDD โ +5V
- VEE โ Middle pin of 10k potentiometer (contrast control)
Oscillator and Power
- 20 MHz crystal connected to OSC1/OSC2 with two 22pF capacitors to GND
- All unused pins set as digital outputs or inputs as needed
๐งพ 6. Source Code (XC8) โ Fully Commented
#include <xc.h>
#define _XTAL_FREQ 20000000 // Define clock frequency for delays
// 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
#pragma config WRT = OFF
#pragma config CP = OFF
// Function Declarations
void ADC_Init();
unsigned int ADC_Read(unsigned char);
void LCD_Init();
void LCD_Command(unsigned char);
void LCD_Char(unsigned char);
void LCD_String(const char *);
void LCD_Clear();
void LCD_Set_Cursor(unsigned char, unsigned char);
// Main function
void main(void) {
unsigned int temp_adc;
float temperature;
char tempStr[16];
ADC_Init(); // Initialize ADC
LCD_Init(); // Initialize LCD
LCD_String("Temp Monitor");
while(1) {
temp_adc = ADC_Read(0); // Read ADC from channel 0 (RA0)
temperature = (temp_adc * 4.88); // Convert ADC value to millivolts
temperature = temperature / 10.0; // Convert mV to ยฐC (LM35)
LCD_Set_Cursor(2, 1);
LCD_String("Temp: ");
sprintf(tempStr, "%.2f C", temperature);
LCD_String(tempStr);
__delay_ms(1000);
}
}
// ADC Initialization
void ADC_Init() {
ADCON0 = 0x41; // Turn on ADC, select channel 0
ADCON1 = 0x80; // Right justify result, Vref = VDD
TRISA0 = 1; // Set RA0 as input
}
// ADC Read Function
unsigned int ADC_Read(unsigned char channel) {
if(channel > 7) return 0;
ADCON0 &= 0xC5; // Clear channel selection bits
ADCON0 |= (channel << 3); // Select desired channel
__delay_ms(2); // Acquisition time
GO_nDONE = 1; // Start conversion
while(GO_nDONE); // Wait until done
return ((ADRESH << 8) + ADRESL); // Combine 10-bit result
}
// LCD Initialization
void LCD_Init() {
TRISC = 0x00; // Configure PORTC as output
__delay_ms(20);
LCD_Command(0x02); // Initialize in 4-bit mode
LCD_Command(0x28); // 2-line, 5x7 font
LCD_Command(0x0C); // Display ON, cursor OFF
LCD_Command(0x06); // Auto increment cursor
LCD_Command(0x01); // Clear screen
}
// LCD Send Command
void LCD_Command(unsigned char cmd) {
PORTC = (PORTC & 0x0F) | (cmd & 0xF0);
RC0 = 0; RC1 = 1; __delay_ms(1); RC1 = 0;
PORTC = (PORTC & 0x0F) | (cmd << 4);
RC0 = 0; RC1 = 1; __delay_ms(1); RC1 = 0;
__delay_ms(2);
}
// LCD Print Character
void LCD_Char(unsigned char data) {
PORTC = (PORTC & 0x0F) | (data & 0xF0);
RC0 = 1; RC1 = 1; __delay_ms(1); RC1 = 0;
PORTC = (PORTC & 0x0F) | (data << 4);
RC0 = 1; RC1 = 1; __delay_ms(1); RC1 = 0;
__delay_ms(2);
}
// LCD Print String
void LCD_String(const char *str) {
while(*str) {
LCD_Char(*str++);
}
}
// LCD Set Cursor Position
void LCD_Set_Cursor(unsigned char row, unsigned char column) {
unsigned char pos = (row == 1) ? 0x80 + column - 1 : 0xC0 + column - 1;
LCD_Command(pos);
}
// Clear LCD
void LCD_Clear() {
LCD_Command(0x01);
__delay_ms(2);
}
๐ง 7. Troubleshooting and Debugging
LCD not displaying characters Check:
- LCD contrast via potentiometer
- Wiring of data and control lines
- Delay timing and initialization steps
Incorrect temperature readings Check:
- LM35 placement and output voltage using a multimeter
- Conversion formula (ADC value ร 4.88 mV, then รท 10)
No ADC conversion Check:
- ADCON0 and ADCON1 settings
- RA0 must be input
- Ensure channel select bits are correct
Garbage characters on LCD Check:
- 4-bit initialization sequence
- Correct delays between nibbles
- Proper pull-down or pull-up configurations if required
๐ 8. Extensions and Improvements
- Add buzzer alert: Use another pin (e.g., RD0) to drive a buzzer when temperature exceeds a threshold.
- UART output: Send temperature readings to a PC or IoT server using UART.
- Data logging: Store values to EEPROM with timestamps using a real-time clock (DS1307).
- Graphical Interface: Use a Nokia 5110 or OLED display for better visuals.