Creating a serial LCD can be very useful for quick debugging; as well as projects requiring LCD support with minimal interface. The circuit presented here may not satisfy all needs, but it should serve as a good starting point for further work.
How does serial LCD interface work?
It is quite simple really. Using a microcontroller with a built-in HW UART, the information form the HW UART is received and passed on to the LCD using 4-bit control. The LCD is a standard 2x16 character based display compatible with a HD44780 controller. The microcontroller is a 16F688 with built-in UART. A baud-select function is included to allow for the selection of two different input baud rates. The following figure shows how the circuit used.
Code Description:
The code for the simple serial LCD was created using Oshonsoft Basic. This language is not as complete or provides as much functionality as others (e.g. CCS C), but it can be readily obtained and used.
The code can be summarized as follows:
Oshonsoft Code:
CCS Code:
How does serial LCD interface work?
It is quite simple really. Using a microcontroller with a built-in HW UART, the information form the HW UART is received and passed on to the LCD using 4-bit control. The LCD is a standard 2x16 character based display compatible with a HD44780 controller. The microcontroller is a 16F688 with built-in UART. A baud-select function is included to allow for the selection of two different input baud rates. The following figure shows how the circuit used.
Code Description:
The code for the simple serial LCD was created using Oshonsoft Basic. This language is not as complete or provides as much functionality as others (e.g. CCS C), but it can be readily obtained and used.
The code can be summarized as follows:
- On power-up read the baud-select input (select between 9600bps and 1200bps),
- Continuously monitor the HW UART and parse any received characters,
- a null character is ignored,
- a carriage return starts the second line of the LCD,
- an escape character clears the LCD,
- everything else is passed on to the LCD.
Oshonsoft Code:
Code:
'* PIC16F688 Serial LCD Test Bed *'
'Title: Simple Serial LCD Test Program
'Description: This program uses PIC16F688 to drive a 2x16 LCD from an RS-232 input (9600bps)
'Author: languer (2010)
'Pin Allocation:
'PIN# Main_Fn
'RA0 -> PGD - Programming data
'RA1 -> PGC - Programming clock
'RA2 -> not used
'RA3 -> not used
'RA4 -> LCD EBIT
'RA5 -> LCD RSBIT
'RC0 -> LCD DB4
'RC1 -> LCD DB5
'RC2 -> LCD DB6
'RC3 -> LCD DB7
'RC4 -> BAUD SELECT
'RC5 -> RS232 RX
'Usage Information:
'BAUD Select
'low -> 1200
'high -> 9600
'RS232 Input
'0x00 -> ignore
'0x0D -> LCD line 2, clear line 2
'0x1B -> LCD line 1, clear LCD
'General Device Configuration
Define CONF_WORD = 0x33c4
OSCCON = 0x70 'define internal 8MHz clock
Define CLOCK_FREQUENCY = 8
'HW UART Setup
Define ALLOW_MULTIPLE_HSEROPEN = 1
'LCD Configuration
Define LCD_BITS = 4 'allowed values are 4 and 8 - the number of data interface lines
Define LCD_DREG = PORTC
Define LCD_DBIT = 0 '0 or 4 for 4-bit interface, ignored for 8-bit interface
Define LCD_RSREG = PORTA
Define LCD_RSBIT = 5
Define LCD_EREG = PORTA
Define LCD_EBIT = 4
Define LCD_RWREG = 0 'set to 0 if not used, 0 is default
Define LCD_RWBIT = 0 'set to 0 if not used, 0 is default
Define LCD_COMMANDUS = 2000 'delay after LCDCMDOUT, default value is 5000
Define LCD_DATAUS = 50 'delay after LCDOUT, default value is 100
Define LCD_INITMS = 10 'delay used by LCDINIT, default value is 100
'Variable Declarations
Const trisa1 = %11001111
Const trisc1 = %11010000
Symbol lcd_db4 = PORTC.0
Symbol lcd_db5 = PORTC.1
Symbol lcd_db6 = PORTC.2
Symbol lcd_db7 = PORTC.3
Symbol lcd_rs = PORTA.4
Symbol lcd_e = PORTA.5
Symbol baud_select = PORTC.4
Symbol rs232_rx = PORTC.5
Dim _true As Bit
Dim _false As Bit
Dim char As Byte
_true = True
_false = False
'Main Program
main:
Call init()
WaitMs 1000
While _true
Call get_rs232_char()
Call send_lcd_char()
Wend
End
Proc init()
AllDigital 'set all ports to digital
TRISA = trisa1
TRISC = trisc1
WaitMs 1
Lcdinit 1 'initialize LCD module; cursor is blinking
Lcdcmdout LcdClear 'clear LCD display
Lcdout "LCD Display" 'line 1
Lcdcmdout LcdLine2Home 'set cursor at the beginning of line 2
Lcdout "LR@2010" 'line 2
WaitMs 1
If baud_select = 1 Then
Hseropen 9600
Else
Hseropen 1200
Endif
End Proc
Proc get_rs232_char()
Hserin char
End Proc
Proc send_lcd_char()
Select Case char
Case 0
'do nothing
Case 13
Lcdcmdout LcdLine2Home
Lcdcmdout LcdLine2Clear
Case 27
Lcdcmdout LcdHome
Lcdcmdout LcdClear
Case Else
Lcdout char
EndSelect
End Proc
CCS Code:
Code:
/*** PREPROCESSOR ***/
#include <16F688.h>
#device ADC=10
#fuses INTRC_IO,NOPROTECT,NOBROWNOUT,NOMCLR,NOCPD,NOWDT,PUT,NOIESO,NOFCMEN
#use delay(clock=8000000)
/*** INCLUDES ***/
/*** BUILD INFORMATION ***/
#ORG 0x01F0,0x01FF
const char build_rev[] = {"Rev 1.0"};
/*** DEFINITIONS ***/
#byte PORTA = 0x05
#byte PORTC = 0x07
#byte INTCON = 0x0B
#byte PIR1 = 0x0C
#byte CMCON = 0x19
#byte ADCON0 = 0x1F
#byte OPTION_REG = 0x81
#byte TRISA = 0x85
#byte TRISC = 0x87
#define LINE_16_1 0x00
#define LINE_16_2 0x40
#define LINE_16_3 0x10
#define LINE_16_4 0x50
// These defines are specifically for a 4x20 line display.
#define LINE_20_1 0x00
#define LINE_20_2 0x40
#define LINE_20_3 0x14
#define LINE_20_4 0x54
// Another define for use in LCD_PutCmd
#define CLEAR_DISP 0x01
struct lcd_pin_map
{
int8 empty1: 4; // RA0-RA3 unused
int1 en : 1; // RA5
int1 rs : 1; // RA4
int1 na1: 1; // RA6 - not implemented
int1 na2: 1; // RA7 - not implemented
int8 empty2: 8; // not implemented
int8 data: 4; // RC0-RC3
int8 na3: 4; // RC4-7 - not implemented
} lcd;
#byte LCD = PORTA //PORT A & C
/*** VARIABLES / CONSTANTS ***/
#define _TRISA 0b11001111
#define _TRISC 0b11110000
#define scrollDelay 100
#define ioCommTx PIN_A0
#define ioHwUartRx PIN_C5
#define ioHwUartTx PIN_C4
#define ioBaudSelect PIN_C4
#define bufferSize 35 //includes 32bytes to cover 2x16 display, plus STX and ETX, plus 1
#define STX 0x81
#define ETX 0x18
int8 commMsg[bufferSize];
int8 msgCnt;
int1 baudSelect;
/*** FUNCTION PROTOTYPES ***/
void init();
void getMsg1200();
void getMsg9600();
void displayMsg();
void LCD_SetPosition( char cX );
void LCD_PutChar( char cX );
void LCD_ScrollChar( char cX );
void LCD_PutCmd( char cX );
void LCD_Init( void );
void LCD_pulseEN( void );
#use rs232 (baud=1200,xmit=ioCommTx,parity=N,bits=8,stream=DEBUG)
#use rs232 (baud=1200,xmit=ioHwUartTx,rcv=ioHwUartRx,parity=N,bits=8,stream=HWUART1200,ERRORS)
#use rs232 (baud=9600,xmit=ioHwUartTx,rcv=ioHwUartRx,parity=N,bits=8,stream=HWUART9600,ERRORS)
/*** FUNCTION - Main ***/
void main()
{
init(); // initializes pic
LCD_Init ();
LCD_PutCmd ( CLEAR_DISP );
LCD_SetPosition ( LINE_16_1 );
printf ( LCD_PutChar, "SERIAL DISPLAY" );
LCD_SetPosition ( LINE_16_2 );
printf ( LCD_PutChar, "LR@2013 " );
printf ( LCD_PutChar, build_rev );
baudSelect = input(ioBaudSelect);
delay_ms(2500);
if (baudSelect == 0)
{
LCD_PutCmd ( CLEAR_DISP );
LCD_SetPosition ( LINE_16_1 );
printf ( LCD_PutChar, "1200 BAUD" );
}
else
{
LCD_PutCmd ( CLEAR_DISP );
LCD_SetPosition ( LINE_16_1 );
printf ( LCD_PutChar, "9600 BAUD" );
}
while( true )
{
if (baudSelect == 0)
{
getMsg1200();
}
else
{
getMsg9600();
}
displayMsg();
}
}
/*** FUNCTION - MCU Intialization ***/
void init()
{
set_tris_A( _TRISA );
set_tris_C( _TRISC );
setup_adc( ADC_OFF );
setup_adc_ports( NO_ANALOGS );
setup_comparator( NC_NC_NC_NC );
}
/*** FUNCTION - Get UART Message (1200baud) ***/
void getMsg1200()
{
int8 commChar = 0;
int8 msgState = 0;
int1 msgComplete = False;
while ( ~msgComplete )
{
if ( kbhit(HWUART1200) )
{
commChar = fgetc(HWUART1200); // receive character
switch (msgState)
{
case 0:
{
if (commChar == STX)
{
msgState++;
msgCnt = 0;
}
else
{
msgState = 0;
msgCnt = 0;
}
break;
}
case 1:
{
if (commChar == ETX)
{
msgState = 0;
msgComplete = True;
}
else
{
if (msgCnt >= bufferSize)
{
msgState = 0;
msgCnt = 0;
}
else
{
commMsg[msgCnt] = commChar;
msgCnt++;
}
}
break;
}
default:
{
msgState = 0;
msgCnt = 0;
msgComplete = False;
break;
}
}
}
}
}
/*** FUNCTION - Get UART Message (9600baud) ***/
void getMsg9600()
{
int8 commChar = 0;
int8 msgState = 0;
int1 msgComplete = False;
while ( ~msgComplete )
{
if ( kbhit(HWUART9600) )
{
commChar = fgetc(HWUART9600); // receive character
switch (msgState)
{
case 0:
{
if (commChar == STX)
{
msgState++;
msgCnt = 0;
}
else
{
msgState = 0;
msgCnt = 0;
}
break;
}
case 1:
{
if (commChar == ETX)
{
msgState = 0;
msgComplete = True;
}
else
{
if (msgCnt >= bufferSize)
{
msgState = 0;
msgCnt = 0;
}
else
{
commMsg[msgCnt] = commChar;
msgCnt++;
}
}
break;
}
default:
{
msgState = 0;
msgCnt = 0;
msgComplete = False;
break;
}
}
}
}
}
/*** FUNCTION - Display Message On LCD ***/
void displayMsg()
{
int8 cnt;
for (cnt=0;cnt<msgCnt;cnt++)
{
LCD_PutChar(commMsg[cnt]);
}
}
/*** FUNCTION - Initialize LCD ***/
void LCD_Init ( void )
{
// Initializing into 4-bit mode is a very exact sequence
// as defined in the datasheets for these displays.
delay_ms( 200 ); /* wait enough time after Vdd rise */
lcd.rs = 0;
lcd.data = 0x00;
lcd.data = 0x03; /* init with specific nibbles to start 4-bit mode */
LCD_pulseEN();
LCD_pulseEN();
LCD_pulseEN();
lcd.data = 0x02; /* set 4-bit interface */
LCD_pulseEN(); /* send dual nibbles hereafter, MSN first */
LCD_PutCmd( 0x2C ); /* function set (2 lines, 5x7 characters) */
LCD_PutCmd( 0x0C ); /* display ON, cursor OFF, blink OFF */
// LCD_PutCmd( 0x0E ); /* display ON, cursor ON, blink OFF */
// LCD_PutCmd( 0x0F ); /* display ON, cursor ON, blink ON */
// LCD_PutCmd( 0x01 ); /* clear display */
LCD_PutCmd( 0x01 ); /* clear display */
LCD_PutCmd( 0x06 ); /* entry mode set, increment */
}
/*** FUNCTION - Set LCD X,Y Position ***/
void LCD_SetPosition( cX )
{
/*
This subroutine works specifically for 4-bit Port A.
Value received cX will place cursor at a particular line and offset.
16-char display 20-char display
=============== ===============
Line 1 is hex: 00 - 0F 00 - 13
Line 2 is hex: 40 - 4F 40 - 53
Line 3 is hex: 10 - 1F 14 - 27
Line 4 is hex: 50 - 5F 54 - 67
*/
lcd.data = swap( cX ) | 0x08;
LCD_pulseEN();
lcd.data = swap( cX );
LCD_pulseEN();
}
/*** FUNCTION - Write Character To LCD ***/
void LCD_PutChar( cX )
{
/* this subroutine works specifically for 4-bit Port A */
switch ( cX )
{
case '\f':
{
LCD_PutCmd( CLEAR_DISP );
break;
}
case '\n':
{
LCD_SetPosition ( LINE_16_2);
break;
}
case '\r':
{
LCD_SetPosition ( LINE_16_2);
break;
}
default:
{
lcd.rs = 1;
lcd.data = swap( cX ); // send high nibble
LCD_pulseEN();
lcd.data = swap( cX ); // send low nibble
LCD_pulseEN();
lcd.rs = 0;
break;
}
}
}
/*** FUNCTION - Scroll Character On LCD ***/
void LCD_ScrollChar( cX )
{
/* this subroutine works specifically for 4-bit Port A */
lcd.rs = 1;
lcd.data = swap( cX ); /* send high nibble */
LCD_pulseEN();
lcd.data = swap( cX ); /* send low nibble */
LCD_pulseEN();
lcd.rs = 0;
delay_ms(scrollDelay);
}
/*** FUNCTION - Write Command To LCD ***/
void LCD_PutCmd( cX )
{
/* this subroutine works specifically for 4-bit Port A */
lcd.data = swap( cX ); /* send high nibble */
LCD_pulseEN();
lcd.data = swap( cX ); /* send low nibble */
LCD_pulseEN();
}
/*** FUNCTION - Toggle Enable Pin On Lcd To Load Command Or Character Into LCD ***/
void LCD_pulseEN( void )
{
lcd.en = 1;
delay_us( 10 );
lcd.en = 0;
delay_ms( 5 );
}