Published using Google Docs
( CHARLCD.fth )
Updated automatically every 5 minutes

TACHYON [~ HEX

--- CHARACTER LCD ---

( <WEBPAGE VERSION> - refresh for latest  updates - copy all and paste into serial terminal )

( Introduction to Tachyon Forth for downloads and links )

FORGET CHARLCD.fth

pub CHARLCD.fth                ." Character LCD - 8-bit parallel interface  V1.1 14115.1200 " ;

{

Interfaces a standard (H44780 compatible) 20 character by 4 line LCD in 8-bit mode write-only to the Propeller. The 8-bit data bus can also be used for keypad scanning and other uses that are not incompatible with the manner in which the bus is used.

The LCD module is interfaced in write-only mode so there is no need to worry about 5V to 3.3V protection plus all LCD inputs have TTL thresholds which means that anything 2V or more will be treated as a high (as opposed to 0.7Vdd). Historically the busy bit was read to check when the LCD was ready however that information is well known and published and except for a native HOME or CLEAR instruction, the LCD will always be ready for another character within a few microseconds and yet the software write speed is always slower. Therefore nothing special needs to be done but a delay is added for the special initialization instructions and this simplifies the hardware interface at the same time. IMO this is the recommended interface for any character LCD.

The backlight brightness and LCD contrast are controlled under software, there are no need for pots to adjust the contrast or the brightness. Audio tones are implemented with the A counter and the contrast with the B counter.

The software interface is designed primarily as a stream interface as if it were a serial display as this means application software can talk to it the same as it would to a serial terminal using control codes and standard formatting to effect operations.

*Note: 4-bit transfers although popular can be "unreliable" as noise could disrupt the nibble synchronization since there is no mechanism in the interface to differentiate between high and low nibbles, once one has been missed or added by glitches then the information will continue to be garbled until reset.

SAMPLE DISPLAY

LCD SCHEMATIC

CONTROL CODES

HEX

CTL

NAME

2nd

DESCRIPTION

00

^@

NULL

(ignore)

01

^A

HOME

Move to the top left home position (does not clear the screen)

02

^B

BIG

Big digit mode - draw following digits over 4 lines (max 4 digits)

03

^C

NORMAL

Normal digit mode

04

^D

HIDE CURSOR

Hide the cursor (normal)

05

^E

UNDERLINE CURSOR

Show an underline as the cursor

06

^F

BLINK CURSOR

Show the cursor as a blinking block

07

^G

BELL

Generate a short tone on the bell output

08

^H

BACKSPACE

Non-destructively backspace (stops at left margin)

09

^I

TAB

Write blanks until the next 8th column position

0A

^J

LF

Move the cursor down one line in the same X position (wraps-around)

0B

^K

UP

Move the cursor up one line

0C

^L

CLD

Clear the display

0D

^M

CR

Return the cursor to the left hand side (or margin)

0E

^N

BACKLIGHT ON

Turn the backlight on using the last selected brightness

0F

^O

BACKLIGHT OFF

Turn off the backlight

10

^P

CURSOR

POS

Position the cursor - absolute position encoded as +64

11

^Q

(ignored)

12

^R

RIGHT

13

^S

(ignored)

14

^T

BRIGHTNESS

VAL

15

^U

DISPLAY ON

16

^V

DISPLAY OFF

17

^W

SET CONTRAST

VAL

18

^X

(ignored)

19

^Y

SET MARGIN

SIZE

Set the left hand margin (0..max)

1A

^Z

RESET

Reset the LCD display and driver settings

1B

^[

ESCAPE

(ignored)

NAMES:  $6A3B...7492 for 2,647 bytes (+881)

CODE:   $0924...53DB for 19,127 bytes (+1,267)

Changelog:

141115  Added I/O task control to allow multiple cogs to access LCD

130328  Added keypad encoder which sits off the LCD data bus

130326  Added tables and diagrams and other formatting

130325  Added big digit modes for 4 line displays

130225  Fixed bug in WriteLCD if data pins were not the default

Also added LCDPINS to allow pins to be redefined.easily

Fixed bug in LCDCTRLS vectors as NOP only compiles as a single bytecode

Optimized code

Started adding enhanced serial command functions

}


[PRIVATE

( CONSTANTS )

--- Pin definitions ---

\ The default pins shown here suit my product but they are easy to change

\ To force to a different pin just replace the constant value like this: ' #lite 1+ !

\ {

\ I/O PORT ASSIGNMENTS (P1144)

#P0

== #lcdpins

\ LCD data pins

#lcdpins

8 MASKS

== #lcdbus

\ LCD data bus mask P0..P7

#P14

== #vlcd

\ RC network connected to Vlcd for electronic contrast

#P15

MASK

== #rs

\ Register select

#P16

MASK

== #rw

\ Not required for character LCDs. Tie R/W low on LCD

#P17

MASK

== #lcdce

\ LCD enable line

#P18

MASK

== #lite

\ backlight control via an NPN

#P19

== #buzz

\ Piezo transducer - externally excited

}

{

\ I/O PORT ASSIGNMENTS (P1145)

#P8

== #lcdpins

\ LCD data pins

#lcdpins

8 MASKS

== #lcdbus

\ LCD data bus mask P0..P7

#P4

== #vlcd

\ RC network connected to Vlcd for electronic contrast

#P6

MASK

== #rs

\ Register select

0

== #rw

\ Not required for character LCDs. Tie R/W low on LCD

#P7

MASK

== #lcdce

\ LCD enable line

#P5

MASK

== #lite

\ backlight control via an NPN

0

== #buzz

\ Piezo transducer - externally excited

}              

( *** LCD INFORMATION *** )

4                       == #lines

#20                     == #chars

\ lcdmode flags

1                       == #big         \ big digit mode flag


( *** VARIABLES *** )

WORD lcdvec

\ dpy = %1000 + dpy + cur + blink

BYTE         xpos,ypos,lcdlm,dpy,lch,lcdmode,bigptr


( *** CODE *** )

--- Optional Piezo transducer sounds ---

{

pub CLICK

      #buzz DUP PINSET #100 us PINCLR

      ;

pub BEEP

        #2250 #150

pub TONE ( tone dur -- )

        SWAP A #buzz APIN HZ ms

        MUTE

        ;

pub BEEPS ( cnt -- )

     FOR BEEP #50 ms NEXT

     ;

pub PIP

        #3000 #80 TONE

        ;

pub WARBLE ( hz ms -- )        

         3 FOR

         2DUP TONE

         OVER #100 + OVER TONE

         NEXT

        2DROP

        ;

pub RING

        #500 #40 WARBLE

        #200 ms

        #500 #40 WARBLE

        ;

pub RINGS ( rings -- )

        FOR RING #1000 ms NEXT

        ;

}

--- LCD DEFINITIONS ---

pub LCDPINS ( data rs ce -- )

        MASK ' #lcdce 1+ !                    --- Setup LCD CE constant

        MASK ' #rs 1+ !                       --- Setup LCD RS (or A0) constant

        DUP ' #lcdpins 1+ !                   --- Setup LCD data pins constant

        8 MASKS ' #lcdbus 1+ !                --- and it's mask

        ;

pub LCDSIZE ( chars lines -- )

        ' #lines 1+ !

        ' #chars 1+ !

        ;

pri CONTRAST ( byte -- \ Adjust the LCD contrast - use counter B in duty DAC mode )

        B #vlcd DAC!

        ;

\ This routine could control the backlight brightness but in this version simply switches it        

pri LITE ( on/off -- )

        0<> #lite OUT

        ;

pri LCDDAT ( ch -- \ Output a byte to the LCD as a character )

        1

pri WriteLCD ( data rs -- )

        #rs OUT                             \ select register

        #lcdbus OUTCLR                      \ Prep data bus as outputs (all low)

        #lcdpins SHL OUTSET                 \ and write data (set after clear)

        #lcdce DUP OUTSET OUTCLR            \ 1us pulse chip enable

\        5 us                                \ small delay (redundant)

        #lcdbus INPUTS                      \ float the data lines (used by keypad encoder)

        ;         

pri LCDCMD ( cmd -- \ Output a byte to the LCD as an instruction )

        0 WriteLCD

        ;

{ Cursor translation for 20x4 LCD

00..19 -->        00..19

20..39 -->        64..83

40..59 -->        20..39

60..79 -->        84..103

}

pri XLTCUR ( abs -- xlt )

        DUP #20 < ?EXIT

         DUP #59 > IF #24 + EXIT THEN

         DUP #39 > IF #20 - EXIT THEN

         #44 +

         ;

pri LCDCR                                  --- Carriage return

         lcdlm C@ xpos C!                --- Return to X position zero and update LCD's cursor

pri XYCUR ( -- <cur> )                            --- update the LCD's internal cursor pointer                

         ypos C@ #chars *                --- CUR = CHARS/LINE*Y + X

         xpos C@ +

pri SETCUR ( c -- )

         #79 MIN                         --- Limit cursor to 80 characters

        XLTCUR                          --- convert absolute position to charlcd position  

        $80 OR LCDCMD                   --- and instruct LCD to set cursor position

        ;

pri LCDCLS                            --- Clear the LCD screen

         1 LCDCMD                        --- Raw LCD command to clear it's screen

         1 ms                            --- The only command that you really need to wait for

pri LCDHOME

         ypos C~                         --- clear y line position

         xpos C~                         --- clear x character position

         2 LCDCMD                        --- Raw home command for LCD

pri LCDNOP                            --- a dummy entry for a null character - a 1ms might be handy

         1 ms                            --- Just give the LCD time to execute it

         ;

pri LCDLF                             --- Line feed

         ypos C++

         ypos C@ #lines MOD ypos C!         --- Wrap-around to the top of the display?

         XYCUR                           --- Update the LCD's internal cursor

          ;

{

\ CG chunky character codes - used to define the "chunky pixels" for the big digits

0        == TLC

1        == TRC

2        == BLC

3        == BRC

4        == LB        \ lower block

5        == UB        \ upper block

6        == FB        \ full block

}

HEX TABLE cgshapes        PRIVATE \ build big digits using "chunky pixels" (5x7 CG characters)

        00 | 00 | 00 | 01 | 03 | 07 | 0F | 1F |        --- TLC: Left-right up-ramp shape.

        00 | 00 | 00 | 10 | 18 | 1C | 1E | 1F |        --- TRC: Right-left "      

        1F | 0F | 07 | 03 | 01 | 00 | 00 | 00 |        --- BLC: Left-right down ramp.

        1F | 1E | 1C | 18 | 10 | 00 | 00 | 00 |        --- BRC: Right-left "    "

        00 | 00 | 00 | 00 | 1F | 1F | 1F | 1F |        --- LB: Lower block.

        1F | 1F | 1F | 1F | 00 | 00 | 00 | 00 |        --- UB: Upper block.

        1F | 1F | 1F | 1F | 1F | 1F | 1F | 1F |        --- FB: Full block.

        00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |        --- Full blank (not used)

        

\ Init CG Characters -

pri !CGCHARS ( addr -- )

         $40 LCDCMD $40 ADO I C@ 1 ms LCDDAT LOOP

         ;

\ Chunky "Pixel" Characters for digits 0..9

TABLE bigpixels        

( 0 )

       0 |  5 |  5 | 1 |

       6 |  BL |  BL |  6 |

       6 |  BL |  BL |  6 |

       2 |  4 |  4 | 3 |

( 1 )

        BL | 0 |  6 |  BL |

        BL |  BL |  6 |  BL |

        BL |  BL |  6 |  BL |

        BL |  4 |  6 |  4 |

( 2 )

       0 |  5 |  5 | 1 |

       BL |  BL |  4 | 3 |

       0 |  5 |  BL |  BL |

       6 |  4 |  4 |  4 |

( 3 )

       0 |  5 |  5 | 1 |

        BL |  BL |  4 | 3 |

        BL |  BL |  5 | 1 |

       2 |  4 |  4 | 3 |

( 4 )

       BL | 0 |  6 |  BL |

       0 | 3 |  6 |  BL |

       5 |  5 |  6 |  5 |

       BL |  BL |  6 |  BL |

( 5 )

       0 |  5 |  5 |  5 |

        6 |  4 |  4 | 1 |

        BL |  BL |  BL |  6 |

        4 |  4 |  4 | 3 |

( 6 )

       0 |  5 |  5 | 1 |

       6 |  BL |  BL |  BL |

       6 |  5 |  5 | 1 |

       2 |  4 |  4 | 3 |

( 7 )

        5 |  5 |  5 | 1 |

        BL |  BL | 0 | 3 |

        BL | 0 | 3 |  BL |

       0 | 3 |  BL |  BL |

( 8 )

       0 |  5 |  5 | 1 |

       2 |  4 |  4 | 3 |

       6 |  BL |  BL |  6 |

       2 |  4 |  4 | 3 |

( 9 )

       0 |  5 |  5 | 1 |

        6 |  BL |  BL |  6 |

       2 |  5 |  5 |  6 |

        BL |  4 |  4 | 3 |

pri BIGPIXEL ( -- pixels )

        bigptr C@ bigpixels + C@ 1 bigptr C+!        

        ;

pri DRAWROWS

        XYCUR                         \ update the LCD's cursor

        4 FOR BIGPIXEL LCDDAT NEXT    \ draw 4 horizontal chunky pixels

        BL LCDDAT                     \ provide a blank "pixel" on the right hand side

        ;

\ Assume the char is a decimal digit and draw it using chunky pixels

pri DRAWBIG ( char -- )

        "0" - 4 SHL bigptr C!         \ Point to the chunky pixel table for this digit

         ypos C~                       \ Always draw it from the top of the display

        4 FOR DRAWROWS ypos C++ NEXT  \ draw 4 rows

        5 xpos C+!                    \ update horizontal position as 4 pixels + blank

        ;

pri LCDchar ( char -- )

         lch C@                        \ Restore character and pass as a character to the LCD

pri LCDCHAR ( char -- )             \ Treat character as data

        #big lcdmode SET?             \ Displays this as a big digit?

         OVER "0" "9" WITHIN AND       \ but only if it is a digit

          IF DRAWBIG

          ELSE

            LCDDAT

           xpos C++                    \ increment character position

           xpos C@ #chars =>           \ and wrap to next line if necessary

             IF LCDCR LCDLF THEN

          THEN

        ;

( CONTROL CODE SUBROUTINES )

\ Tab to the next column of 8 - advance at least one space

pri LCDTAB        BEGIN BL LCDCHAR xpos C@ 7 AND 0= UNTIL ;

pri LCDBS         xpos C@ IF xpos C-- THEN XYCUR ;

pri (LCDLITE)     lcdvec W~ $40 - LITE ;

pri LCDLITE       ' (LCDLITE) lcdvec W! ;

pri LCDRIGHT      ;

pri LCDON         4 dpy SET

 pri LCDDM         dpy C@ LCDCMD ;

pri LCDOFF        4 dpy CLR LCDDM ;        

pri LCDHIDE       3 dpy CLR LCDDM ;

pri LCDUL         1 dpy CLR 2 dpy SET LCDDM ;

pri LCDBLINK      3 dpy SET LCDDM ;

pri LCDBL         $28 LITE ;

pri LCDNBL        0 LITE ;

pri (LCDCONT)     lcdvec W~ $3F AND CONTRAST ;

pri LCDCONT       ' (LCDCONT) lcdvec W! ;

pri LCDBIG        #big lcdmode SET ;

pri LCDSMALL      #big lcdmode CLR ;

pri LCDESC        ;

pri (LCDCUR)      $40 - 0 MAX SETCUR lcdvec W~ ;

pri LCDCUR        ' (LCDCUR) lcdvec W! ;

pri (LCDLM)       lcdlm C! lcdvec W~ ;

pri LCDLM         ' (LCDLM) lcdvec W! ;

IFNDEF BEEP

pri BEEP                ;

}

pri LCDINIT

         #lcdce OUTCLR

          #rw OUTCLR                            --- Force R/W low (not needed for standard LCDs)

         #rs OUTCLR

        8 CONTRAST                            --- Set a default contrast level

         $28 LITE                              --- Turn on the backlight and set the brightness (if enabled)

          $0F dpy C!                            --- set display mode variable

         xpos C~ ypos C~                       --- clear variables

         lcdlm C~ lcdmode C~

         lcdvec W~                             --- clear LCD control code vector

        2 ms

         $38 LCDCMD 2 ms                       --- Force LCD to 8-bit mode and init

         $38 LCDCMD 1 ms                        

         $40 LCDCMD $0C LCDCMD $06 LCDCMD

         $01 LCDCMD 1 ms

         $02 LCDCMD 5 ms

         cgshapes !CGCHARS                     --- write my chunky graphics into the LCD

        ;

\ Primary stream entry - process controls, sequences and characters (accessed via EMIT when redirected)

pri LCDEMIT

        >B                                                --- limit to 8-bit character

        lcdvec W@ ?DUP IF JUMP THEN         --- Bypass processing and execute control code sequence if active

        DUP lch C!                          --- backup the character

pri LCDCTRLS ( ctrl -- )                          --- Look-up and execute the code that corresponds to the ctrl character    

         BL LOOKUP LCDchar                          --- Treat as a character if above table limit

( 00 )

LCDNOP

LCDHOME

LCDBIG

LCDSMALL

( 04 )

LCDHIDE

LCDUL

LCDBLINK

BEEP

( 08 )

LCDBS

LCDTAB

LCDLF

LCDchar

( 0C )

LCDCLS

LCDCR

LCDBL

LCDNBL

( 10 )

LCDCUR

LCDNOP

LCDRIGHT

LCDNOP

( 14 )

LCDLITE

LCDON

LCDOFF

LCDCONT

( 18 )

LCDNOP

LCDLM

LCDINIT

LCDESC

( 1C )

LCDNOP

LCDNOP

LCDNOP

LCDNOP

         ;

PRIVATE]

--- Redirect all output to the LCD

pub LCD

         ' LCDEMIT uemit W!

         ;

WORD lcdch

BYTE lcdtask

pub (HUBLCD)

         BEGIN lcdch W@ 0= UNTIL $100 OR lcdch W!

         ;

pub LCD

         lcdtask C@ IF ' (HUBLCD) ELSE ' LCDEMIT THEN uemit W!

         ;

8 LONGS lcdstk

pub LCD.TASK

         !RP lcdstk SP! !SP

        lcdtask C~~

         BEGIN

           lcdch W@ ?DUP IF LCDEMIT lcdch W~ THEN

         AGAIN

         ;

{

--- LCD DEMO ---

\ Initialization and splash screen demo

pub LCDSTART

        lcdvec W~

        LCD ^Z EMIT                                                --- Init the LCD

      \ Splash screen

        BELL

         ^B EMIT ." 23" ^C EMIT                                --- 2 big digits

        ^A EMIT ^Y EMIT #10 EMIT <CR>             --- go home and set margin for 10 characters

        ." .7`C  Auto"

        ."   Cooling2"

        ."    01:30pm"

        ."   Set:21.0"

         CON                                                        --- Return character I/O back to console

         ;

}

{ <--- comment out here to enable option

--- Optional Serial LCD task ---

#P21 |<                == #lcdrxd

pri (SERLCD)

        #9600 SERBAUD

          BEGIN #lcdrxd SERIN LCDEMIT AGAIN

        ;

pub SERLCD                ' (SERLCD) TASK? RUN ;        

\ Optional word that displays the 80 characters or symbols

pub SHOWLCD ( from -- )  LCD 0C EMIT #80 ADO I EMIT LOOP CON ;  ok

}

{ <--- comment out here to enable option

--- Optional KEYPAD encoder ---

\ KEYPAD I/O DEFINITIONS

#lcdpins

== #keypad

\ 4 rows and then 4 columns

#keypad

4 MASKS

== #keyrows

\ keypad input rows - optional pullups

#keypad

4 +

== #keycol

#keycol

4 MASKS

== #keycols

\ keypad columns

BYTE keypad

\ ASCII codes which correspond to the raw keypad code indexed from 0

TABLE keycodes

  "*" | "7" | "4" | "1" |

  "0" | "8" | "5" | "2" |

  "#" | "9" | "6" | "3" |

  "D" | "C" | "B" | "A" |

   00 |

\ Test the keypad for a keypress and if so then scan and return with a valid ASCII code

\ The key has to be released or another detected

\ If there is no key or the key is still pressed than it returns with a null (zero).

\ To prevent the keypad from interfering with the LCD just use some ~220R to 1K series resistors in the rows

\ Execution time if no key is pressed: 75us

pub KEYPAD@ ( --- key )

  #keypad 8 MASKS OUTSET            \ Precharge the row (& col) lines in case we don't have pullups

  #keyrows INPUTS                   \ Release the row inputs

  #keycols OUTCLR                   \ now activate all the columns to check for any key pressed

  #keypad 4 PINS@                   \ read the rows

  $0F <>                            \ ..any low?

    IF                              \ a key is pressed, but which one?

    $10                             \ initial illegal index replaced with column (hopefully)

      0 4 ADO                       \ scan through 4 columns from 0

      #keycols INPUTS               \ float the columns

      I MASK #keycol SHL OUTCLR     \ pull a column down low

      5 us                          \ account for series resistance before row pin discharges

      #keypad 4 PINS@               \ read the rows

      $0F <> IF DROP I LEAVE THEN   \ found the column, now replace the index and leave the loop

      LOOP

                                    \ ( col ) found the column, now find the row

    #keypad 4 PINS@ $0F XOR >|      \ convert the row to an index ( col row )

    2 SHL OR                        \ merge with the column as ( rrcc )

    keycodes + C@                   \ and return with translated key code

    DUP keypad C@ =                 \ prevent repeat readings

      IF DROP FALSE

      ELSE DUP keypad C!            \ update keypad variable with latest key

      1 ms                          \ a little minimum debounce period for a confirmed key

      THEN

    ELSE

    FALSE                           \ otherwise there isn't a keypress

    keypad C~                                \ clear latest key

    THEN

  ;  

       

       

 

}

--- Optional autoboot ---

{ <--- comment out here to enable option

autorun W@         == oldboot

pri LCDBOOT   oldboot ?DUP IF CALL THEN LCDSTART ;

AUTORUN LCDBOOT

}

]~        

END