LCD display shows decimal and binary counting with PIC16F57.
View Full Size | Visit Hobby Page

PIC16F628A: Displaying Decimal Numbers on LCD

by Lewis Loflin

Note: while the above uses the PIC16F57 the LCD code is identical.

This project demonstrates how to display a 16-bit binary number in decimal format on a standard HD44780-compatible LCD using the PIC16F628A microcontroller. Building on the earlier "Hello World!" LCD example, this version introduces binary-to-decimal conversion with leading zero suppression and a modular 4-bit LCD interface. The setup efficiently uses just six I/O pins and provides a foundation for real-world applications such as voltmeters, counters, and sensor readouts. Though demonstrated on the PIC16F628A, the code is compatible with the PIC16F57 and similar devices.

This demo builds on the earlier "Hello World!" LCD example by adding real number processing. It converts a 16-bit binary value to a human-readable decimal string and displays it using a 4-bit interface LCD. This showcases several important microcontroller programming concepts in C.

1. Binary to Decimal Conversion

Instead of just printing fixed strings, the program now processes a 16-bit unsigned int and converts it to a 5-digit ASCII decimal number for display:

void to_decimal_ascii(unsigned int value, char buf[5]) {
    buf[0] = (value / 10000) % 10 + '0';
    buf[1] = (value / 1000) % 10 + '0';
    buf[2] = (value / 100) % 10 + '0';
    buf[3] = (value / 10) % 10 + '0';
    buf[4] = (value % 10) + '0';
}

This is a clean and readable way to extract each decimal digit from a binary value using division and modulus.

2. Leading Zero Suppression

The function lcd_print_trimmed() is used to skip over leading zeros, which would otherwise clutter the display. This makes the number easier to read:

void lcd_print_trimmed(const char *buf, unsigned char len) {
    unsigned char i = 0;
    while (i < len - 1 && buf[i] == '0') i++;
    while (i < len) {
        lcd_send_byte(buf[i++], 1);
    }
}

3. Modular LCD Interface

The LCD functions are cleanly organized:

This modular approach makes it easy to reuse code across future projects.

4. Efficient Use of I/O Pins

The LCD is driven in 4-bit mode, using only six pins total (RA0, RA1, and RB0-RB3). This leaves more pins available for sensors, buttons, or other peripherals - a necessity for small devices like the PIC16F628A.

5. Real Embedded Use Case

This example now forms the basis for practical applications like:

It bridges the gap between beginner-level string printing and real data display from hardware.

This example demonstrates using the PIC16F628A to:

6. Hardware Configuration

7. How It Works

The LCD functions are cleanly organized:

XC8 Source Code

#include <xc.h>

#pragma config FOSC = XT // use 4 mHz crystal
#pragma config WDTE = OFF
#pragma config PWRTE = ON
#pragma config MCLRE = ON
#pragma config BOREN = OFF
#pragma config LVP = OFF
#pragma config CPD = OFF
#pragma config CP = OFF

#define _XTAL_FREQ 4000000

#define LCD_RS RA1
#define LCD_E  RA0
#define LCD_D4 RB0
#define LCD_D5 RB1
#define LCD_D6 RB2
#define LCD_D7 RB3

void pulse_enable() {
    LCD_E = 1;
    __delay_us(1);
    LCD_E = 0;
    __delay_us(50);
}

void lcd_nibble(unsigned char nibble) {
    LCD_D4 = (nibble >> 0) & 1;
    LCD_D5 = (nibble >> 1) & 1;
    LCD_D6 = (nibble >> 2) & 1;
    LCD_D7 = (nibble >> 3) & 1;
    pulse_enable();
}

void lcd_send_byte(unsigned char byte, unsigned char is_data) {
    LCD_RS = is_data;
    lcd_nibble(byte >> 4);     // High nibble
    lcd_nibble(byte & 0x0F);   // Low nibble
    __delay_ms(2);
}

void lcd_init() {
    TRISAbits.TRISA0 = 0;
    TRISAbits.TRISA1 = 0;
    TRISB &= 0xF0;

    __delay_ms(20);
    LCD_RS = 0;

    lcd_nibble(0x03); __delay_ms(5);
    lcd_nibble(0x03); __delay_us(150);
    lcd_nibble(0x03);
    lcd_nibble(0x02); // 4-bit mode

    lcd_send_byte(0x28, 0); // 2 lines, 5x8 font
    lcd_send_byte(0x0C, 0); // Display ON
    lcd_send_byte(0x06, 0); // Entry mode
    lcd_send_byte(0x01, 0); // Clear
    __delay_ms(2);
}

void lcd_print(const char *str) {
    while (*str) {
        lcd_send_byte(*str++, 1);
    }
}

// Converts 16-bit unsigned int to 5-digit ASCII in buf[]
void to_decimal_ascii(unsigned int value, char buf[5]) {
    buf[0] = (value / 10000) % 10 + '0';
    buf[1] = (value / 1000) % 10 + '0';
    buf[2] = (value / 100) % 10 + '0';
    buf[3] = (value / 10) % 10 + '0';
    buf[4] = (value % 10) + '0';
}

void lcd_print_trimmed(const char *buf, unsigned char len) {
    unsigned char i = 0;
    while (i < len - 1 && buf[i] == '0') i++;
    while (i < len) {
        lcd_send_byte(buf[i++], 1);
    }
}

void main() {
    CMCON = 0x07;  // comparators off
    lcd_init();
    lcd_print("Hello!");  // Line 1

    __delay_ms(1000);
    lcd_send_byte(0xC0, 0);  // Line 2

    unsigned int value = 0xFF;  // decimal 255
    char bcd_ascii[6];         // 5 digits + null
    to_decimal_ascii(value, bcd_ascii);
    bcd_ascii[5] = '\0';       // safety null
    lcd_print_trimmed(bcd_ascii, 5);
    
    while (1) { /* idle */ }
}

Conclusion

This is more than just an upgrade to "Hello World!" - it's a working microcontroller display system with value conversion, display formatting, and modular design. It demonstrates how to get real numeric data onto a limited-resource LCD using clean and efficient C code.

PIC16F628A wiring setup with external 4MHz crystal and test hardware.
PIC16F628A test setup uses internal oscillator in these projects.
View Full Size | Visit Hobby Page

PIC16F628A Hardware Notes

The next three pages focuses on Timer0 and interrupts in the PIC16F84A


PIC16F57 projects.

Electronics and Science

Six Parts:


The following has information on ferrite materials, SCRs, Neon sign colors.