Just want to share my first efforts to write a "driver" for the T6963c/RA6963 graphic LCD controller. I am still using a Winstar WG12864D display (126x64).
(This driver is for my 65816 board, so I mixed in some 65816 methods for printing debug strings using the Supermon816 monitor)
You can find the
"reference design" hereParts to be done/corrected:
- Proper "goto X,Y" routine (by calculating from address pointer);
- Proper position tracking (relates to bullet above);
- Graphics support;
- Optimize code for size/speed?;
If someone has some more ideas/corrections/etc....
Code:
;==============================================================================
; L I L I T H S B C v0.2
;
; Graphic LCD Routines
; For T6963c / RA6963 based controllers
;
; Version history:
; 2020-09-30 - 0.1.0 Initial development version
;
; NOTE: This driver assumes the '816' GALS on Lilith to be installed
;
; Driver will (only?) include basic routines
;==============================================================================
; LICENSE INFO
; ************
;
; This code is freeware. Use it, modify it, ridicule it in public, or whatever,
; so long as authorship credit (blame?) is given somewhere to me for the
; base program/driver/etc
;
; Xander Maas <xjmaas@gmail.com>
;==============================================================================
; (Additional assembler/linker directives
.listbytes 255
.p816
.org $4000
;------------------------------------------------------------------------------
; This entry points/routines
;
; Hardware entries:
; glcd_status
; glcd_data
;
; "Public" routines:
; glcd_init - Initialize the controller
; glcd_posxy - Sets cursors position (incomplete, have to find out
; how to do the proper calculations for current address
; pointer reference)
; glcd_putcha - Prints character to LCD (incomplete, missing proper
; <LF> and <CR> handling.
; glcd_home - Clears screen and moves cursor to top left position
;
; "Test/Demo" routines:
; glcd_demo - Prints "Lilith Bios version info" and a "real long"
; string.
;
; Support routines:
; glcd_cmd
; glcd_2byte
; glcd_1byte
; glcd_wait
; glcd_autowrite
; glcd_autoread
;------------------------------------------------------------------------------
; Only needed during initial development
s_byte = 1
s_word = 2
s_xword = 3
s_dword = 4
;---------------------------------------
; Local macros
;---------------------------------------
.macro gLCD_Version
.byte '0' ; Major
.byte '.'
.byte '1' ; Minor
.byte '.'
.byte '0' ; Revision
.endmacro
;---------------------------------------
; Zero Page/Direct Page usage
;---------------------------------------
waitSTA = glcd_zeropage ; STA mask and compare value
ptrCursor = waitSTA + s_byte ; Cursor pointer (LSB - MSB)
regOffset = ptrCursor + s_word ; Offset register
ptrAddress = regOffset + s_byte ; Address pointer (LSB - MSB)
homeAddrTxt = ptrAddress + s_word ; Text home address (LSB - MSB)
areaTxt = homeAddrTxt + s_word ; Text area
homeAddrGrph = areaTxt + s_byte ; Graphics home address (LSB - MSB)
areaGrph = homeAddrGrph + s_word ; Graphic area
CH = areaGrph + s_byte ; New horizontal position
CV = CH + s_byte ; New vertical position
;---------------------------------------
; Constants
;---------------------------------------
glcd_zeropage = $30 ; Base address of Zero Page usage
glcd_data = $c300 ; Currently hardcoded?
glcd_control = $c301
n_columns = 16 ; Number of columns
n_rows = 4 ; Number or rows
BELL = $07 ; <Bell>
LF = $0a ; <LF>
CR = $0d ; <CR>
SPACE = $20 ; <Space>
;---------------------------------------
; T6963c/RA6963 commands
;---------------------------------------
; Register settings
setCursor = $21 ; Set cursor pointer
setOffset = $22 ; Set offset register
setAddrPtr = $24 ; Set address pointer
; Control Word
txtHome = $40 ; Set text home address
txtArea = $41 ; Set text area
grphHome = $42 ; Set graphic home address
grphArea = $43 ; Set graphic area
; Mode Set
setOR = $80
setXOR = $81
setAND = $83
setTxtAttr = $84 ; Text attribute mode
intCGROM = $00 ; Use internal Char generator
extCGRAM = $08 ; Use external Char generator
; Display Mode
modeDispOff = $90 ; Turn display off
modeCur0Blnk0 = $91 ; **UNSUPPORTED/NON_EXISTANT? Cursor off
modeCur1Blnk0 = $92 ; Cursor on, blink off
modeCur1Blnk1 = $93 ; Cursor on, blink on
modeTxt1Grph0 = $94 ; Text on, graphic off
modeTxt0Grph1 = $98 ; Text off, graphic on
modeTxt1Grph1 = $9c ; Text on, graphic on
; Cursor Pattern
cur1 = $a0 ; 1-line cursor
cur2 = $a1 ; 2-line cursor
cur3 = $a2 ; 3-line cursor
cur4 = $a3 ; 4-line cursor
cur5 = $a4 ; 5-line cursor
cur6 = $a5 ; 6-line cursor
cur7 = $a6 ; 7-line cursor
cur8 = $a7 ; 8-line cursor
; Data Auto Read/Write
dataAutoWrite = $b0 ; Set data auto write
dataAutoRead = $b1 ; Set data auto read
dataAutoReset = $b2 ; Reset data auto read/write
; Data Read/Write
writeIncAddrPtr = $c0 ; Write data - incr address pointer
readIncAddrPtr = $c1 ; Read data - incr address pointer
writeDecAddrPtr = $c2 ; Write data - decr address pointer
readDecAddrPtr = $c3 ; Read data - decr address pointer
writeNonAddrPtr = $c4 ; Write data - leave address pointer
readNonAddrPtr = $c5 ; Read data - leave address pointer
; Screen Peek
scrPeek = $e0 ;
; Screen Copy
scrCopy = $e8 ; Copy 1 single line to grpahic area
; Bit Set/Reset
bitreSet = $f0 ; Reset bit
bitSet = $f8 ; Set bit
bit0 = $00 ;=======================
bit1 = $01 ; Use these by adding this to ...
bit2 = $02 ; ... bitSet or bitReset.
bit3 = $03 ; example to set bit #5:
bit4 = $04 ; lda #bitSet+bit5
bit5 = $05
bit6 = $06
bit7 = $07
;---------------------------------------
; Additional RA6963 commands
;---------------------------------------
inverse = $d0 ; Enable/disable reverse screen
; (Data bit #0 = enable/disable)
setBlink = $50 ; Sets blink time
setAutoMove = $60 ; Set cursor auto move
; (Data 1, bit #0 = enable/disable)
fontSelect = $70 ; Change the CG ROM font
;---------------------------------------
; BIOS/Monitor routines (Lilith Supermon816 impementation)
;
; Change this for your own system
;---------------------------------------
DPYHEX = $e75a ; Print byte as hex value
NEWLINE = $e771 ; Print <LF> & <CR>
SPRINT = $e786 ; Print C-string
PUTCHA = $fedf ; Print a character
;=======================================
; Public routines
;---------------------------------------
; glcd_init
;
; Clear the screen, set the base addresses for text and graphics. Also
; clears the RAM (write $00s).
;
; Returns with gLCD in text mode
;
; TODO: (useful for gLCD detection?)
; Carry clear => error/no controller found (based on timeout?)
; Carry set => OK
;---------------------------------------
glcd_init:
pea mm_dispoff
jsr SPRINT
lda #%00000011 ; As we use STA0 and STA1 the most
sta waitSTA ; during commands, set this as standard
lda #modeDispOff ; While init, turn off display
jsr glcd_cmd
; Set mode (OR-mode, Internal CG ROM)
pea mm_setMode
jsr SPRINT
lda #(setOR | intCGROM)
jsr glcd_cmd
; Clear Text RAM area
pea mm_clrTxt
jsr SPRINT
lda #$00 ; Set Address pointer
ldx #$00 ; Clear from $0000 to $03ff
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
lda #dataAutoWrite ; Turn on auto (bulk) write
jsr glcd_cmd
pea mm_startAWrite
jsr SPRINT
lda #%00001000 ; Starting Auto Read/Write implies
sta waitSTA ; setting STA2/3 bit
ldy #$00 ; Blank 4 RAM pages
@pageTxt:
phy
pea mm_clearPage
jsr SPRINT
ply
ldx #$00 ;
@byteTxt:
lda #$00 ; Value to write - $00
jsr glcd_autowrite ; Perform auto (bulk) write
inx
cpx #$00
bne @byteTxt ; If .X not yet < $00, next page
iny
cpy #$04
bne @pageTxt ; If .Y not yet < $00, next page
; Clear graphic area with $00
; 1024 bytes: 8 pixels per byte, (128 / 8) * 64 lines
pea mm_clrGrph
jsr SPRINT
lda #$00
ldx #$10
sta homeAddrGrph ; Save LSB
stx homeAddrGrph+ 1 ; Save MSB
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
ldy #$00
@pageGR:
phy
pea mm_clearPage
jsr SPRINT
ply
ldx #$00
@byteGR:
lda #$00
jsr glcd_autowrite
inx
cpx #$00
bne @byteGR
iny
cpy #$04 ; 4 pages = 1kB
bne @pageGR
; When done with auto (bulk) write
lda #dataAutoReset ; Reset auto (bulk) function(s)
jsr glcd_cmd
lda #%00000011 ; Restore "Normal wait" => STA0 | STA1
sta waitSTA
; We set text home address to $0100, so we might be able to implement some
; scroll effects later?
pea mm_setTxtHome
jsr SPRINT
lda #$00 ; LSB - $00
ldx #$01 ; MSB - $01
sta homeAddrTxt ; Save for reference
stx homeAddrTxt + 1
jsr glcd_2byte
lda #txtHome
jsr glcd_cmd
; We set graphic home address to $1000
pea mm_setGrphHome
jsr SPRINT
lda #$00 ; LSB - $00
ldx #$10 ; MSB - $10
sta homeAddrGrph
stx homeAddrGrph + 1
jsr glcd_2byte
lda #grphHome
jsr glcd_cmd
; Set text/graphic area
pea mm_setTxtArea
jsr SPRINT
lda #n_columns ; Number of columns as defined above
ldx #$00 ; Always $00
jsr glcd_2byte
lda #txtArea
jsr glcd_cmd
lda #grphArea ; As the arguments are the same
jsr glcd_cmd
; Turn on Text mode,
pea mm_txtOn
jsr SPRINT
lda #(modeTxt1Grph0 | modeCur1Blnk1)
jsr glcd_cmd
pea mm_initDone
jsr SPRINT
; Set initial Cursor position (0,0)
lda #$00
ldx #$00
sta ptrCursor ; Store current cursor position
stx ptrCursor + 1 ; in shadow register(s) in ZP/DP
jsr glcd_2byte
lda #setCursor
jsr glcd_cmd
rts ; Done
;---------------------------------------
; glcd_posxy
;
; Sets cursor, uses two ZP/DP addresses
;
; Prerequisites:
; CH - Horizontal position
; CV - Vertical position
;
; NOTE: We do not save the corrected values to CH/CV, maybe we may need
; them somewhere else?
;---------------------------------------
glcd_posxy:
pea mm_posxy
jsr SPRINT
lda CH ; Check for valid value, otherwise
pha
jsr DPYHEX
pla
cmp #n_columns ; calculate correct value
beq @check_rows ; No recalculation needed...
sec
@modColumns:
sbc #n_columns ; Subtract n_columns until ...
bcs @modColumns ; ... we borrow
adc #n_columns ; Add n_columns to get CH % n_columns
@check_rows:
sta ptrCursor ; Save new horizontal position
lda CV ; Check for valid value, otherwise
jsr DPYHEX
cmp #n_rows ; calculate correct value
beq @set ; No recalculation needed
sec
@modRows:
sbc #n_rows ; Subtract n_rows until ...
bcs @modRows ; ... we borrow
adc #n_rows ; Add n_rows to get CV % n_rows
@set:
sta ptrCursor + 1 ; Save new vertical position
tax ; Vertical position moved to .X
lda ptrCursor ; Reload new horizontal position
jsr glcd_2byte
lda #setCursor
jsr glcd_cmd
clc
rts
;---------------------------------------
; glcd_putcha
;
; Sends an ASCII character to the controller by subtracting $20
; Converts non printable characters to <Space> with the folowing
; exceptions:
; <Bell> - [NOT IMPLEMENTED YET!] Will "Flash" screen
; <LF> - Will move one line down (in future even scroll?)
; <CR> - Will move cursor to left most position
; When printing a value, it will also increment ZP ptrCursor and wrap?
;
; Prerequisites:
; .A - Contains ASCII value to print
;---------------------------------------
glcd_putcha:
pha
pea mm_putcha
jsr SPRINT
pla
cmp #BELL
bne @chkLF
pea mm_putchaBell
jsr SPRINT
bra @done
@chkLF:
cmp #LF
bne @chkCR
pea mm_putchaLF
jsr SPRINT
rts
@chkCR:
cmp #CR
bne @char
pea mm_putchaCR
jsr SPRINT
rts
@char:
cmp #SPACE ; Is it a <Space> or higher?
bmi @non ; No, so we replace it
; Would a BIT to skip to bytes be faster?
bra @print ; Seems we can print it safely?
@non:
lda #'.' ; We got a non-printable character,
; so we print a ., for now...
@print:
pha ; Debug
pea mm_putcha_char ; Print when entering part
jsr SPRINT ; followed by ...
pla
jsr PUTCHA ; ... the character to print ...
; Prepare byte for LCD Char generator table (.A - $20)
sec
sbc #$20
jsr glcd_1byte
lda #writeIncAddrPtr
jsr glcd_cmd
; Track current "cursor" position
clc
lda ptrAddress
adc #$01
sta ptrAddress
bcc @cursor
lda ptrAddress + 1
adc #$00
sta ptrAddress + 1
@cursor:
clc
lda ptrCursor
adc #$01
sta ptrCursor
bcc @done
lda ptrCursor + 1
adc #$01
sta ptrCursor + 1
jsr glcd_setcursor
@done:
rts
;---------------------------------------
; glcd_home
;
; Clears the screen and moves the cursor to the top left
; position
;---------------------------------------
glcd_home:
; Clear Text RAM area
pea mm_clrTxt
jsr SPRINT
lda #$00 ; Set Address pointer
ldx #$00 ; Clear from $0000 to $03ff
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
lda #dataAutoWrite ; Turn on auto (bulk) write
jsr glcd_cmd
pea mm_startAWrite
jsr SPRINT
lda #%00001000 ; Starting Auto Write implies read STA3
sta waitSTA
ldy #$00 ; Blank 4 RAM pages
@pageTxt:
phy
pea mm_clearPage
jsr SPRINT
ply
ldx #$00 ;
@byteTxt:
lda #$00 ; Value to write - $00
jsr glcd_autowrite ; Perform auto (bulk) write
inx
cpx #$00
bne @byteTxt ; If .X not yet < $00, next page
iny
cpy #$04
bne @pageTxt ; If .Y not yet < $00, next page
; Clear graphic area with $00
; 1024 bytes: 8 pixels per byte, (128 / 8) * 64 lines
pea mm_clrGrph
jsr SPRINT
lda #$00
ldx #$10
sta homeAddrGrph ; Save LSB
stx homeAddrGrph+ 1 ; Save MSB
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
ldy #$00
@pageGR:
phy
pea mm_clearPage
jsr SPRINT
ply
ldx #$00
@byteGR:
lda #$00
jsr glcd_autowrite
inx
cpx #$00
bne @byteGR
iny
cpy #$04 ; 4 pages = 1kB
bne @pageGR
; When done with auto (bulk) write
lda #dataAutoReset ; Reset auto (bulk) function(s)
jsr glcd_cmd
lda #%00000011 ; Restore "Normal" STA0 | STA1
sta waitSTA
stz CH ; Set X, Y to 0
stz CV
jsr glcd_posxy
; Reset text home address to $0100
pea mm_setTxtHome
jsr SPRINT
lda #$00 ; LSB - $00
ldx #$01 ; MSB - $01
sta homeAddrTxt ; Save for reference
stx homeAddrTxt + 1
jsr glcd_2byte
lda #txtHome
jsr glcd_cmd
; Reset graphic home address to $1000
pea mm_setGrphHome
jsr SPRINT
lda #$00 ; LSB - $00
ldx #$10 ; MSB - $10
sta homeAddrGrph
stx homeAddrGrph + 1
jsr glcd_2byte
lda #grphHome
jsr glcd_cmd
; Reset text/graphic area
pea mm_setTxtArea
jsr SPRINT
lda #n_columns ; Number of columns as defined above
ldx #$00 ; Always $00
jsr glcd_2byte
lda #txtArea
jsr glcd_cmd
lda #grphArea ; As the arguments are the same
jsr glcd_cmd
; Set Text Address Pointer to known value
lda homeAddrTxt
ldx homeAddrTxt + 1
sta ptrAddress
stx ptrAddress + 1
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
; Set cursor position to known position (0,0)
stz ptrCursor
stz ptrCursor + 1
jsr glcd_setcursor
rts
;=======================================
; Demo routine
;---------------------------------------
glcd_demo:
lda #$00
ldx #$01
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
ldx #$00
@next:
lda mm_lilithBios, X
beq @doneFirst
phx
jsr glcd_putcha
plx
inx
bra @next
@doneFirst:
jsr glcd_home
lda #$00
ldx #$01
jsr glcd_2byte
lda #setAddrPtr
jsr glcd_cmd
ldx #$00
@next1:
lda mm_testLong, X
beq @done
phx
jsr glcd_putcha
plx
inx
bra @next1
@done:
rts
;=======================================
; Graphic LCD Support routines
;---------------------------------------
; glcd_cmd
;
; Send a command to the controller
;---------------------------------------
glcd_cmd:
jsr glcd_wait ; Wait until controller is ready
sta glcd_control ; Send command to controller
rts
;---------------------------------------
; glcd_2byte
;
; Send two bytes of data to the controller
;
; Registers:
; .A => must contain byte #1
; .X => must contain byte #2
;---------------------------------------
glcd_2byte:
jsr glcd_wait ; Wait until the controller is ready
sta glcd_data ; Send byte
txa ; .X ==> .A
; Flow into
; vvvvvvvvvvvvv
;---------------------------------------
; glcd_1byte
;
; Send one byte of data to the controller
;
; Registers:
; .A => must contain byte #1
;---------------------------------------
glcd_1byte:
jsr glcd_wait ; Wait until the controller is ready
sta glcd_data ; Send byte
rts
;---------------------------------------
; glcd_wait
;
; Wait for the controller to process a previous data or command transfer
;
; Prerequites:
; Use ZP/DP address for mask, e.g. for STA0 and STA1
; lda #%00000011 ; $03
; sta waitSTA ;
; Registers:
; .A - preserved
;---------------------------------------
glcd_wait:
pha ; Save .A
; pea mm_waitGLCD
; jsr SPRINT
@wait:
lda glcd_control
and waitSTA ; Mask of bits based on waitSTA
cmp waitSTA ; Are these bits set?
bne @wait ; No, we have to wait...
pla ; Restore .A
rts
;---------------------------------------
; glcd_autowrite
;
; Routine used for "bulk" data transport to controller
;
; Prerequisites:
; .A - data (byte) to send to controller
;---------------------------------------
glcd_autowrite:
; pea mm_autoWrite
; jsr SPRINT
jsr glcd_wait ; Wait for controller to become ready
sta glcd_data ; Send data
rts
;---------------------------------------
; glcd_autoread
;
; Routine used for "bulk" data transport from controller
;
; Registers:
; .A - Will contain byte read from controller
; .X - Saved to stack and restored
;---------------------------------------
glcd_autoread:
jsr glcd_wait ; Wait for controller to become ready
lda glcd_data ; Send data
rts
;---------------------------------------
; glcd_setcursor
;
; Set the (text) cursor to the position as indicated by ZP registers
; ptrCursor - X-position
; ptrCursor + 1 - Y-position
;---------------------------------------
glcd_setcursor:
lda ptrCursor
ldx ptrCursor + 1
jsr glcd_2byte
lda #setCursor
jsr glcd_cmd
rts
;
; Static info, strings
versionInfo:
gLCD_Version
mm_lilithBios:
.byte $0a, $0d, "Lilith BIOS", $0a, $0d
gLCD_Version
.byte $00
; For debug - ZP Usage
ZPUSAGE:
.byte glcd_zeropage
.byte waitSTA
.byte ptrCursor
.byte regOffset
.byte ptrAddress
.byte homeAddrTxt
.byte areaTxt
.byte homeAddrGrph
.byte areaGrph
.byte CH
.byte CV
mm_dispoff:
.byte $0a, $0d, "LCD - Init", $00
mm_setMode:
.byte $0a, $0d, "LCD --- Set mode", $00
mm_clrTxt:
.byte $0a, $0d, "LCD --- Clear Text RAM Area", $00
mm_clrGrph:
.byte $0a, $0d, "LCD --- Clear Graphic RAM area", $00
mm_setTxtHome:
.byte $0a, $0d, "LCD --- Set Text Home address", $00
mm_setGrphHome:
.byte $0a, $0d, "LCD --- Set Graphic Home address", $00
mm_setTxtArea:
.byte $0a, $0d, "LCD --- Set Text area", $00
mm_setGrphArea:
.byte $0a, $0d, "LCD --- Set Graphic area", $00
mm_txtOn:
.byte $0a, $0d, "LCD --- Turn on Text mode", $00
mm_initDone:
.byte $0a, $0d, "LCD - Init Done...", $00
mm_waitGLCD:
.byte $0a, $0d, "LCD --- Wait for controller", $00
mm_startAWrite:
.byte $0a, $0d, "LCD --- Enable Auto Write", $00
mm_autoWrite:
.byte $0a, $0d, "LCD --- Running Auto Write", $00
mm_resetAuto:
.byte $0a, $0d, "LCD --- Reset Auto Read/Write", $00
mm_clearPage:
.byte $0a, $0d, "LCD ------ Clearing memory page", $00
mm_putcha:
.byte $0a, $0d, "LCD - putcha", $00
mm_putchaLF:
.byte $0a, $0d, "LCD --- <LF> detected", $00
mm_putchaCR:
.byte $0a, $0d, "LCD --- <CR> detected", $00
mm_putchaBell:
.byte $0a, $0d, "LCD --- <Bell> detected", $00
mm_putcha_char:
.byte $0a, $0d, "LCD --- Normal character detected: ", $00
mm_posxy:
.byte $0a, $0d, "LCD - posxy", $00
mm_testLong:
.byte $0a, $0d, "LCD -- THIS IS A VERY LONG STRING -- LCD", $00