TACHYON [~ HEX
( <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 )
\ 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 ; } |
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
;
{
\ 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
#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
\ 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
;
}
{ <--- comment out here to enable option
autorun W@ == oldboot
pri LCDBOOT oldboot ?DUP IF CALL THEN LCDSTART ;
AUTORUN LCDBOOT
}
]~
END