Friday, October 15, 2004

Control a 16x2 Character LCD

Continued From: PIC16F84

It's pretty simple. Your display probably has the standard 14pin or 16pin connector. Pins 15 and 16 are for the backlight, so they're not important. I think that most LCD's have a standard controller like the Hitachi HD44780, so there is just a simple protocol for talking to them. This example uses four bit mode, meaning we send a byte in two four bit chunks. This just saves I/O pins on your PIC. Anyway, first you initialize the LCD by sending some control codes, with appropriate delays, then you just give it bytes and it displays them at it's current position.

Here is some basic information about your LCD: Control Codes.

The Delay routines are simple. The last two LCD routines are for custom characters. Your LCD probably has 8 chunks of CGRAM where you can specify custom characters for display.

Custom LCD Characters

I wanted to draw this logo, so I specified the CGRAM accordingly in cd_load_custom_characters(). Then lcd_draw_logo() sends them to the appropriate positions on the LCD. The LED in this project simply flashes on and off with the button presses. It is there only as a debugging tool.

The main program prints "Hello World!" and then waits for a press and release on the push button. It then prints "Hello Again!" on the second line of the display and waits for a press and release on the push button. Next it draws the logo.

main.c

/*                     PIC16F84
 *                   .-----------.
 *                  -|RA2     RA1|- PUSH BUTTON
 *                  -|RA3     RA0|-
 *                  -|RA4    OSC1|- XT CLOCK pin1, 27pF to GND
 *      PUSH BUTTON -|MCLR   OSC2|- XT CLOCK pin2, 27pF to GND
 *              GND -|Vss     Vdd|- +5v
 *           LCD EN -|RB0     RB7|- LCD D7
 *           LCD RS -|RB1     RB6|- LCD D6
 *              LED -|RB2     RB5|- LCD D5
 *                  -|RB3     RB4|- LCD D4
 *                   '-----------'       
 *
 *  PUSH BUTTON = PIN - BUTTON - GND
 *                PIN - 10k - +5v
 *
 *  LED = PIN - LED - 100 ohm - GND     
 */

#include <pic.h>
#include "lcd.c"

__CONFIG(WDTDIS & XT & UNPROTECT);

main(void) 
{
  unsigned int i = 1;
  unsigned int j = 1;

  TRISA = 0b00000010; 
  TRISB = 0b00000000; 

  lcd_initialize(); 
  lcd_overwrite_string("Hello World!");

  for (;;) 
  {
    if (RA1 == 0)
    {
      RB2 = 1;
      j = 0;
    }
    else
    {
      RB2 = 0;
      if (j==0)
      {
        j=1;
        i++;
        if (i==2)
        {
          lcd_write_string_at_second_line("Hello Again!");
        }
        else if (i==3)
        {
          lcd_draw_logo();
        }
        else
        {
          lcd_overwrite_string("Clear Screen.");
        }
      }
    }
  }
}

lcd.h

extern void lcd_clear_and_home(void);

extern void lcd_goto(unsigned char pos);

extern void lcd_write_byte(unsigned char);

extern void lcd_write_char(char);

extern void lcd_write_string(const char * s);

extern void lcd_overwrite_string(const char * s);

extern void lcd_write_string_at_second_line(const char * s);

extern void lcd_initialize(void);

extern void lcd_load_custom_characters(void);

extern void lcd_draw_logo(void);

lcd.c

/*     16x2 character LCD
 *     .----------------.
 *     |  pin15         |- unused
 *     |  pin16         |- unused
 *     |                |
 *     |  pin01      GND|- GND 
 *     |  pin02      +5v|- +5v 
 *     |  pin03 Contrast|- 4.7k pot 
 *     |  pin04       RS|- PORTB1
 *     |  pin05       RW|- GND 
 *     |  pin06       EN|- PORTB0
 *     |  pin07    Data0|- GND 
 *     |  pin08    Data1|- GND
 *     |  pin09    Data2|- GND
 *     |  pin10    Data3|- GND
 *     |  pin11    Data4|- PORTB4
 *     |  pin12    Data5|- PORTB5
 *     |  pin13    Data6|- PORTB6
 *     |  pin14    Data7|- PORTB7
 *     '-----------------'   
 *
 *  LCD interface.
 *  This code will interface to a standard LCD controller like the Hitachi HD44780.
 *  This code is for the standard 14 pin connector, and 4-bit mode is assumed.
 */

#include  <pic.h>
#include  "lcd.h"
#include  "delay.c"

static bit LCD_RS  @ ((unsigned)&PORTB*8+1);  // Register select
static bit LCD_EN  @ ((unsigned)&PORTB*8+0);  // Enable
#define LCD_STROBE  ((LCD_EN = 1),(LCD_EN=0))

/* Clear and home the LCD. 
 */
void lcd_clear_and_home(void)
{
  LCD_RS = 0;
  lcd_write_byte(0x01);
  delay_ms(2);
}

/* Go to the specified position.
 */
void lcd_goto(unsigned char pos)
{
  LCD_RS = 0;
  lcd_write_byte(0x80+pos);
}

/* Write one byte to the LCD.
 */
void lcd_write_byte(unsigned char c)
{
  PORTB = (PORTB & 0x0F) | (c & 0xF0);
  LCD_STROBE;
  PORTB = (PORTB & 0x0F) | (c << 4);
  LCD_STROBE;
  delay_us(40);
}

/* Write one char to the LCD.
 */
void lcd_write_char(char c)
{
  LCD_RS = 1;  // characters
  PORTB = (PORTB & 0x0F) | (c & 0xF0);
  LCD_STROBE;
  PORTB = (PORTB & 0x0F) | (c << 4);
  LCD_STROBE;
  delay_us(40);
}

/* Write a string of chars to the LCD.
 */
void lcd_write_string(const char * s)
{
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/* Clear and home the LCD,
 * then write a string of chars to the LCD.
 */
void lcd_overwrite_string(const char * s)
{
  lcd_clear_and_home();
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/* Write a string of chars to the LCD 
 * starting at the beginning of the second line of the display.
 */
void lcd_write_string_at_second_line(const char * s)
{
  lcd_goto(64);
  LCD_RS = 1;  // characters
  while(*s)
  { lcd_write_byte(*s++); }
}

/* Initialise the LCD.
 */
void lcd_initialize(void)
{
  LCD_RS = 0;   // write control bytes
  delay_ms(15);  // power on delay
  PORTB = 0x30; // attention!
  LCD_STROBE;
  delay_ms(5);
  LCD_STROBE;
  delay_us(100);
  LCD_STROBE;
  delay_ms(5);
  PORTB = 0x20; // set 4 bit mode
  LCD_STROBE;
  delay_us(40);
  lcd_write_byte(0x28);  // 4 bit mode, 1/16 duty (2 lines), 5x8 dots
  lcd_write_byte(0x0C);  // display on, blink off, cursor off
//lcd_write_byte(0x0F);  // display on, blink on, cursor on
  lcd_write_byte(0x06);  // entry mode

  lcd_load_custom_characters();
}

/* Load the custom characters into cgram.
 * Also performs Clear and Home.
 */
void lcd_load_custom_characters(void)
{
  LCD_RS = 0;
  lcd_write_byte(0b01000000);  // cgram address #0
  LCD_RS = 1;

  // pattern is xxxbbbbb

  // arrow right
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000010);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00000010);
  lcd_write_byte(0b00000100);

  // arrow left
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001000);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00001000);
  lcd_write_byte(0b00000100);

  // arrow up
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010101);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001100);
  lcd_write_byte(0b00011000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // node
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00001110);

  // marked node
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00001110);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00010101);
  lcd_write_byte(0b00010001);
  lcd_write_byte(0b00001110);

  // line
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00011111);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // corner bottom left
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000110);
  lcd_write_byte(0b00000011);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  // corner bottom right
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00000100);
  lcd_write_byte(0b00001100);
  lcd_write_byte(0b00011000);
  lcd_write_byte(0b00000000);
  lcd_write_byte(0b00000000);

  lcd_clear_and_home();
}

/* Display a pattern of custom characters from cgram. 
 * Also performs Clear and Home.
 */
void lcd_draw_logo(void)
{
   // 0 = arrow right
   // 1 = arrow left
   // 2 = arrow up
   // 3 = node
   // 4 = marked node
   // 5 = line
   // 6 = corner bottom left
   // 7 = corner bottom right

   lcd_clear_and_home();

   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(5);
   lcd_write_char(3);

   lcd_goto(64);

   lcd_write_char(' ');
   lcd_write_char(6);
   lcd_write_char(5);
   lcd_write_char(0);
   lcd_write_char(3);
   lcd_write_char(5);
   lcd_write_char(2);
   lcd_write_char(' ');
   lcd_write_char(6);
   lcd_write_char(0);
   lcd_write_char(4);
   lcd_write_char(1);
   lcd_write_char(7);
   lcd_write_char(4);
   lcd_write_char(1);
   lcd_write_char(7);
}

delay.h

#define delay_us(x) { unsigned char cnt; cnt = (x)/(3)|1; while(--cnt != 0) continue; }

extern void delay_ms(unsigned char);

extern void delay_s(unsigned char);

delay.c

/* These routines assume a 4MHz clock.
 */

#include "delay.h"

/* Delay for the specified number of miliseconds.
 */
void delay_ms(unsigned char cnt)
{
  unsigned char i;
  do 
  {
    i = 4;
    do { delay_us(250); } while(--i);
  } 
  while(--cnt);
}

/* Delay for the specified number of seconds.
 */
void delay_s(unsigned char cnt)
{
  unsigned char i;
  do 
  {
    i = 4;
    do { delay_ms(250); } while(--i);
  } 
  while(--cnt);
}

MAIN.HEX

:10000000830100308A0004288301F82A83129600B5
:10001000971B112817088A001608960A0319970AD1
:1000200082008313171883171608960A84000008A5
:10003000080043346C3465346134723420345334F2
:1000400063347234653465346E342E34003448348D
:1000500065346C346C346F3420344134673461342B
:1000600069346E3421340034483465346C346C3473
:100070006F34203457346F3472346C346434213428
:0200800000344A
:10047C00831292008610803E8312A12A831286106A
:10048C0001308312A1220230572A83128C000430CF
:10049C008D00FA30831257228D0B4F2A8C0B4D2A6C
:1004AC000800831292000430930053309400940B94
:1004BC005D2A930B5B2A920B592A0800831286142F
:1004CC00702A0D0897000C088C0A03198D0A062057
:1004DC008312A1220D0897000C080620003803197E
:1004EC000800672A831244228614842A1108970074
:1004FC001008900A0319910A0620A12211089700EE
:10050C00100806200038031908007C2A403083129A
:10051C003E228614992A110897001008900A031994
:10052C00910A0620A1221108970010080620003815
:10053C0003190800912A83129300F03995000608DC
:10054C000F391504860006140610130EF0399500A9
:10055C0006080F3915048600061406100D30940099
:10056C00940BB62A0800831290008614F03995007B
:10057C0006080F391504860006140610100EF03903
:10058C00950006080F3915048600061406100D3068
:10059C009100910BCF2A0800831286100F30831222
:1005AC0057223030860006140610053057220614E8
:1005BC00061021309000900BE12A0614061005302D
:1005CC008312572220308600061406100D3090003E
:1005DC00900BEE2A28308312A1220C30A122063077
:1005EC00A122792B83128E018E0A8F018C018C0A29
:1005FC008D0102308316850086018312D22234309D
:10060C0090000030910078228518102B06158C0173
:10061C008D010A2B06110D080C04031D0A2B8C01ED
:10062C008C0A8D018E0A03198F0A0E08023A0F04E8
:10063C00031D272B273090000030910083128C2251
:10064C000A2B0E08033A0F04031D2F2B831235239C
:10065C000A2B19309000003091008312092B831261
:10066C0044220030B9220330B9220530B9220530BA
:10067C00B9220530B9220030B9220330B92200303A
:10068C00B9220330B9220530B9220530B922003025
:10069C00B9220330B9220530B9220530B922033012
:1006AC00B92240303E222030B9220630B922053022
:1006BC00B9220030B9220330B9220530B9220230F8
:1006CC00B9222030B9220630B9220030B9220430C8
:1006DC00B9220130B9220730B9220430B9220130D5
:1006EC00B9220730B92A8312861040308312A12216
:1006FC0086140030A1220030A1220030A122043047
:10070C00A1220230A1221F30A1220230A1220430EA
:10071C00A1220030A1220030A1220030A1220430FD
:10072C00A1220830A1221F30A1220830A1220430BE
:10073C00A1220430A1220E30A1221530A1220430B6
:10074C00A1220C30A1221830A1220030A1220030AD
:10075C00A1220030A1220030A1220030A1220E30B3
:10076C00A1221130A1221130A1221130A1220E3070
:10077C00A1220030A1220030A1220030A1220E3093
:10078C00A1221130A1221530A1221130A1220E304C
:10079C00A1220030A1220030A1220030A122003081
:1007AC00A1220030A1221F30A1220030A122003052
:1007BC00A1220430A1220430A1220430A122043051
:1007CC00A1220630A1220330A1220030A122003048
:1007DC00A1220430A1220430A1220430A122043031
:1007EC00A1220C30A1221830A1220030A12200300D
:0407FC00A122442AC8
:02400E00F93F78
:00000001FF
{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "", "page": { "blogIds": [ 228 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }