PIC 18F Text on a KS0108 Graphic LCD

by Joe Colquitt

Copied with permission from http://home.clear.net.nz/pages/joecolquitt/text2lcd.html


This page is a companion to LCD BMP at PICList and uses the same board and schematic

NOTE : - the routines here are not examples of compact code. They are intended to show general methods and help the user understand how these graphic screens work to develop their own s/w for line drawing, animations etc. For instance, routines could be optimised as general modules which can receive a variety of values, such as the result of an ADC conversion, x,y positions, scrolling etc, rather than these hard-coded examples.

In these example routines, character data is stored in PIC program memory and fetched with the table commands. It could be stored in other memory, like an external EEPROM, and fetched with eg I2C or SPI. This may suit some applications or micros better.

The examples given here are based on containing data within discrete pages. However, with a little manipulation, data can be split so that eg an 8-bit character straddles two pages.

For example, the top half of a character can be in the lower half of Page 2, and the lower half of the character goes into the top half of Page 3



Using Boolean operators (eg AND) and shifts of a character's shape data, it can be placed anywhere and also merged with another for animations etc and line drawings which, for example, cross.

The display memory can be read, enabling one character to 'pass through' another by using logic such as inclusive-ORing (IOR) and AND-ing

The scrolling register, line_base ; would be unchanged for a static display. However, putting line_base in a 0-64 loop will demonstrate what it does. Any text on the screen moves upwards as line_base is increased (and conversely will move down as line_base is decreased)

It will wrap-around and re-appear at the bottom of the screen, stopping at its original position when line_base = 64 (actually 0 again, as the argument of line_base won't reach into bits 6 and 7)

As well as the text/picture moving, the pages also move. In this routine, the right-hand side of the screen scrolls upwards 8 lines. Page 0 rolls out of the top of the screen and is now at the bottom of the screen, with Page 7 above it. The small box drawn in Page 0 is therefore at the bottom of the screen, immediately to the right of centre.

         bcf     cs1             ;enable right-hand controller
	 usec
	 bsf     cs2
	 usec

         clrf    temp0           ;scroll counter

scroll   movfw   temp0
         addlw   line_base
         call    write_c

         call    ms100           ;100ms delay, so scroll can be seen

         incf    temp0           ;increment, test for > 8
         movlw   .8
         cpfsgt  temp0           ;yes
         bra     scroll          ;no, loop

;print position for box

         movlw  y_base+.0        ;column 0
         call   write_c
 
         movlw   page_base+.0    ;Page 0
         call    write_c

         movlw   b'11100011'     ;box with a ding in it
         call    write_d
         movlw   b'10000001'
         call    write_d
         movlw   b'10011001'
         call    write_d
         movlw   b'10000001'
         call    write_d
         movlw   b'10000001'
         call    write_d
         movlw   b'11111111'
         call    write_d

By introducing new display data, the 128x64 area can appear to scroll up/down through a much longer screen. Perhaps showing more menu options, or scrolling to a new screen as an alternative to clearing and printing one.

Note that this screen has two controllers, and is really two 64x64 screens that can be scrolled independently in either direction, so more than whole-screen effects are possible.

------------------------------------------------------------------------------

As explained in the companion page, these LCDs display a data byte as a vertical column.

Here's the shape (taken from the HD44780 datasheet) of a lower-case 't' which is drawn rotated right, and its 5 bytes of data compiled in that orientation, to become 5 columns.



------------------------------------------------------------------------------

;PIC (18F) set-up

#define  do_alt  flags,0       ;= 0 standard, = 1 descenders

;per KS0108 datasheet
;page_base  = b'10111000'      ;b8 + 0 - 7 (Page number)
;y_base     = b'01000000'      ;40 + address 0 - 63 (Column number)
;line_base  = b'11000000'      ;c0 + line 0 - 63 (Scrolling)

page_base = 0xb8
y_base    = 0x40
line_base = 0xc0

rom_ram  macro   var           ;macro to copy string from ROM to RAM
         movlw   upper(var)    ;load TBLPTR with string address
         movwf   tblptru
         movlw   high(var)
         movwf   tblptrh
         movlw   low(var)
         movwf   tblptrl
         tblrd*+
         call    get_txt
         endm

dispw    macro                ;display WREG
         call    write_d
         endm

;two arrays

         cblock  0x0040

         str_temp:32          ;RAM copy of string in ROM

         endc

         cblock  0x0060

         chars:32             ;descender/not descender

         endc

;Arbitrary data storage addresses in PIC program memory

strings   = 0x1000

ch16      = 0x1200

asc5x7    = ch16+0x800

         movlw   0x00         ;initialise scroll register
	 addlw   line_base
	 call    write_c

;----------------

Here are two examples of text. The top line is standard 5 x 7, as per a typical alphanumeric (eg 16 x 2) LCD. The lower line shows the same with alternate descender characters used instead



The routine below is in two sections, Pass 1 and Pass 2

The method I've chosen, because on this PIC I think it's as good as any other, is (if the do_alt flag is set) to note during Pass 1 which characters will be replaced with alternates.

Assuming do_alt is set -

After the rom_ram jazz macro has executed, a copy of the string is in a RAM array at 0x0040

Before a character is printed in Pass 1, it's tested to see if it's a descender character that can be replaced. If it is, then its standard ASCII number is substituted by the alternate drawing. The top half of the character is drawn in Pass 1 and the alternate ASCII is recorded in a separate array (at 0x0060) for printing the lower half of the character in Pass 2. If it isn't a replaceable descender character, then a space (ie blank character) is recorded

So, after Pass 1 j z z , g y p have been found, replaced, top halves drawn, and the array created for Pass 2 drawing.



a E and t do not have any descender data, spaces are recorded for their lower halves

;Display standard 16x2 characters, with optional alternate descender
;characters using extra data and a page lower
;
;If displaying descenders (or characters more than 8 pixels high)
;
;Test character to be displayed. If not a descender character
;then store 0x20 (space) at its position in an arrary. If it is a
;descender character, then store the alternate character number
;at its position in the array

alt_ch   lfsr    fsr0,chars        ;for Pass 2 array, 0x20's and 128+

         lfsr    fsr1,str_temp     ;RAM copy of ROM string

         rom_ram jazz              ;copy "jazz,Egypt" string from ROM to RAM
         clrf    postinc1          ;add trailing 0

         lfsr    fsr1,str_temp     ;reset RAM string pointer

;----------------

;Pass 1, display character (optionally the top half of a tall character)

pass1    movlw   low(asc5x7)       ;5x7 character data base address
         movwf   tblptrl
         movlw   high(asc5x7)
         movwf   tblptrh
         clrf    tblptru

         movfw   postinc1          ;get character from string in RAM
         skpnz                     ;exit if 0x00, ie end of string
         goto    pass2             ;Pass 1 complete

;----------------

;Optional, note any descender characters for Pass 2

         call    test_alt          ;test if a descender character

         movff   alt_char,postinc0 ;store result in array for second pass

;----------------

         addlw   -0x20             ;subtract 0x20 from W
         mullw   .10               ;x 5 x 2 = offset from base address
                                   ;ie 5 words per character

         movfw   prodl             ;add offset to base address
         addwf   tblptrl
         movfw   prodh
         addwfc  tblptrh

         movlw   .5                ;number of data bytes to retrieve
         movwf   temp0

         movlw   0x00              ;display a blank leading column
         call    write_d

p1_data  tblrd*+                   ;fetch low byte of data word
         movff   tablat,wreg
         call    write_d           ;display it

         tblrd*+                   ;fetch high byte of data word
                                   ;but ignore data for Pass 1

         decfsz  temp0
         bra     p1_data           ;loop for 5 bytes

         bra     pass1             ;loop until end of string

;----------------

;Pass 2. Reset display position, display space or more data

pass2    btfss   do_alt            ;Pass 2 if doing descenders
         return                    ;or exit for standard characters

         movfw   column
         addlw   y_base            ;reset to column 10
         call    write_c

         movfw   page_copy         ;next page down from Pass 1
         call    write_c

         lfsr    fsr0,chars        ;array, spaces and descender characters

next_p2  movlw   low(asc5x7)       ;5x7 character data base address
         movwf   tblptrl
         movlw   high(asc5x7)
         movwf   tblptrh
         clrf    tblptru

         movfw   postinc0          ;get character from string in RAM
         skpnz                     ;exit if 0x00, ie end of string
         return

         addlw   -0x20             ;offset
         mullw   .10

         movfw   prodl             ;add offset to base address
         addwf   tblptrl
         movfw   prodh
         addwfc  tblptrh

         movlw   .5                ;number of data bytes to retrieve
         movwf   temp0

         movlw   0x00              ;display a blank leading column
         call    write_d

p2_data  tblrd*+                   ;fetch low byte of data word
                                   ;ignore it for Pass 2

         tblrd*+                   ;fetch high byte
         movff   tablat,wreg
         call    write_d           ;display it

         decfsz  temp0
         bra     p2_data           ;loop for 5 bytes

         bra     next_p2           ;next character

;----------------

;test character for substitution by one with a true descender

test_alt btfss   do_alt            ;exit if do_alt = 0
         return                    ;ie do not detect and replace

         movwf   temp0             ;temp store character
         movlw   .128              ;alternate character base code
         movwf   alt_char

;test for , g j p q y or z

         movfw   temp0
         xorlw   ","
         bz      ch128             ;is ,
         xorlw   ","^"g"
         bz      ch129             ;is g
         xorlw   "g"^"j"
         bz      ch130             ;is j
         xorlw   "j"^"p"
         bz      ch131             ;is p
         xorlw   "p"^"q"
         bz      ch132             ;is q
         xorlw   "q"^"y"
         bz      ch133             ;is y
         xorlw   "y"^"z"
         bz      ch134             ;is z

         movlw   0x20              ;ASCII space
         movwf   alt_char          ;not descender, store lower-half space

         movfw   temp0             ;and restore character to W
         return

;increment alt_char to match character's ASCII

ch134    incf    alt_char          ;ASCII code for alternate character
ch133    incf    alt_char
ch132    incf    alt_char
ch131    incf    alt_char
ch130    incf    alt_char
ch129    incf    alt_char
ch128    nop
         movfw   alt_char          ;replace standard with alternate
         return

;================================================
;        Copy a text string
;================================================

get_txt  movfw   tablat       ;get characters until
         btfsc   wreg,7       ;W > 0x7f (ie FF terminator)
         return
         movwf   postinc1     ;store in RAM
         tblrd*+
         bra     get_txt

;================================================
;        Data
;================================================

         org strings

jazz     db "jazz,Egypt",0xff

v_str    db "Voltage",0xff

;------------

         org    ch16

;Standard LCD numbers, twice size, drawn rotated right
;(the sharp-eyed will notice the data repetition)



 dw 0x0ffc,0x0ffc,0x3303,0x3303,0x30c3,0x30c3,0x3033,0x3033,0x0ffc,0x0ffc ;0
 dw 0x0000,0x0000,0x300c,0x300c,0x3fff,0x3fff,0x3000,0x3000,0x0000,0x0000 ;1
 dw 0x300c,0x300c,0x3c03,0x3c03,0x3303,0x3303,0x30c3,0x30c3,0x303c,0x303c ;2
 dw 0x0c03,0x0c03,0x3003,0x3003,0x3033,0x3033,0x30cf,0x30cf,0x0f03,0x0f03 ;3
 dw 0x03c0,0x03c0,0x0330,0x0330,0x030c,0x030c,0x3fff,0x3fff,0x0300,0x0300 ;4
 dw 0x0c3f,0x0c3f,0x3033,0x3033,0x3033,0x3033,0x3033,0x3033,0x0fc3,0x0fc3 ;5
 dw 0x0ff0,0x0ff0,0x30cc,0x30cc,0x30c3,0x30c3,0x30c3,0x30c3,0x0f00,0x0f00 ;6
 dw 0x0003,0x0003,0x3f03,0x3f03,0x00c3,0x00c3,0x0033,0x0033,0x000f,0x000f ;7
 dw 0x0f3c,0x0f3c,0x30c3,0x30c3,0x30c3,0x30c3,0x30c3,0x30c3,0x0f3c,0x0f3c ;8
 dw 0x003c,0x003c,0x30c3,0x30c3,0x30c3,0x30c3,0x0cc3,0x0cc3,0x03fc,0x03fc ;9

;------------

         org    asc5x7                  ;5x7 ASCII set without descenders

;Standard LCD characters, normal size, drawn rotated right

8-bit x 5 '0'



16-bit x 5 '0'



16-bit x 5 'g', including descender in upper byte



;16-bit data, can ignore leading 00 if descenders not needed
;
;ASCII 0x20 - 0x2f, d32 - d47

 dw 0x0000,0x0000,0x0000,0x0000,0x0000  ;space
 dw 0x0000,0x0000,0x005f,0x0000,0x0000  ;!
 dw 0x0000,0x0007,0x0000,0x0007,0x0000  ;"
 dw 0x0014,0x007f,0x0014,0x007f,0x0014  ;#
 dw 0x0024,0x002a,0x007f,0x002a,0x0012  ;$
 dw 0x0023,0x0013,0x0008,0x0064,0x0062  ;%
 dw 0x0036,0x0049,0x0055,0x0022,0x0050  ;&
 dw 0x0000,0x0005,0x0003,0x0000,0x0000  ;'
 dw 0x0000,0x001c,0x0022,0x0041,0x0000  ;(
 dw 0x0000,0x0041,0x0022,0x001c,0x0000  ;)
 dw 0x0014,0x0008,0x003e,0x0008,0x0014  ;*
 dw 0x0008,0x0008,0x003e,0x0008,0x0008  ;+
 dw 0x0000,0x0050,0x0030,0x0000,0x0000  ;,
 dw 0x0008,0x0008,0x0008,0x0008,0x0008  ;-
 dw 0x0000,0x0060,0x0060,0x0000,0x0000  ;.
 dw 0x0020,0x0010,0x0008,0x0004,0x0002  ;/

;ASCII 30 - 3f, 48 - 63

 dw 0x003e,0x0051,0x0049,0x0045,0x003e  ;0
 dw 0x0000,0x0042,0x007f,0x0040,0x0000  ;1
 dw 0x0042,0x0061,0x0051,0x0049,0x0046  ;2
 dw 0x0021,0x0041,0x0045,0x004b,0x0031  ;3
 dw 0x0018,0x0014,0x0012,0x007f,0x0010  ;4
 dw 0x0027,0x0045,0x0045,0x0045,0x0039  ;5
 dw 0x003c,0x004a,0x0049,0x0049,0x0030  ;6
 dw 0x0001,0x0079,0x0005,0x0003,0x0001  ;7
 dw 0x0036,0x0049,0x0049,0x0049,0x0036  ;8
 dw 0x0006,0x0049,0x0049,0x0029,0x001e  ;9
 dw 0x0000,0x0036,0x0036,0x0000,0x0000  ;:
 dw 0x0000,0x0056,0x0036,0x0000,0x0000  ;;
 dw 0x0008,0x0014,0x0022,0x0041,0x0000  ;<
 dw 0x0014,0x0014,0x0014,0x0014,0x0014  ;=
 dw 0x0000,0x0041,0x0022,0x0014,0x0008  ;>
 dw 0x0002,0x0001,0x0051,0x0009,0x0006  ;?

;ASCII 40 - 4f, 64 - 79

 dw 0x0032,0x0049,0x0079,0x0041,0x003e  ;@
 dw 0x007e,0x0011,0x0011,0x0011,0x007e  ;A
 dw 0x007f,0x0049,0x0049,0x0049,0x0036  ;B
 dw 0x003e,0x0041,0x0041,0x0041,0x0022  ;C
 dw 0x007f,0x0041,0x0041,0x0022,0x001c  ;D
 dw 0x007f,0x0049,0x0049,0x0049,0x0041  ;E
 dw 0x007f,0x0009,0x0009,0x0009,0x0001  ;F
 dw 0x003e,0x0041,0x0049,0x0049,0x007a  ;G
 dw 0x007f,0x0008,0x0008,0x0008,0x007f  ;H
 dw 0x0000,0x0041,0x007f,0x0041,0x0000  ;I
 dw 0x0020,0x0041,0x0041,0x003f,0x0001  ;J
 dw 0x007f,0x0008,0x0014,0x0022,0x0041  ;K
 dw 0x007f,0x0040,0x0040,0x0040,0x0040  ;L
 dw 0x007f,0x0002,0x00c0,0x0002,0x007f  ;M
 dw 0x007f,0x0004,0x0008,0x0010,0x007f  ;N
 dw 0x007e,0x0041,0x0041,0x0041,0x007e  ;O

;ASCII 50 - 5f, 80 - 95

 dw 0x007f,0x0009,0x0009,0x0009,0x0006  ;P
 dw 0x003e,0x0041,0x0051,0x0021,0x005e  ;Q
 dw 0x007f,0x0009,0x0019,0x0029,0x0046  ;R
 dw 0x0026,0x0049,0x0049,0x0049,0x0032  ;S
 dw 0x0001,0x0001,0x007f,0x0001,0x0001  ;T
 dw 0x003f,0x0040,0x0040,0x0040,0x003f  ;U
 dw 0x001f,0x0020,0x0040,0x0020,0x001f  ;V
 dw 0x003f,0x0040,0x0030,0x0040,0x003f  ;W
 dw 0x0063,0x0014,0x0008,0x0014,0x0063  ;X
 dw 0x0007,0x0008,0x0070,0x0008,0x0007  ;Y
 dw 0x0061,0x0051,0x0049,0x0045,0x0043  ;Z
 dw 0x0000,0x007f,0x0041,0x0041,0x0000  ;[
 dw 0x0000,0x0000,0x0000,0x0000,0x0000  ;spare (Yen)
 dw 0x0000,0x0041,0x0041,0x007f,0x0000  ;]
 dw 0x0004,0x0002,0x0001,0x0002,0x0004  ;^
 dw 0x0001,0x0001,0x0001,0x0001,0x0001  ;_

;ASCII 60 - 6f, 96 - 111

 dw 0x0000,0x0001,0x0002,0x0004,0x0000  ;'
 dw 0x0020,0x0054,0x0054,0x0054,0x0078  ;a
 dw 0x007f,0x0048,0x0044,0x0044,0x0038  ;b
 dw 0x0038,0x0044,0x0044,0x0044,0x0020  ;c
 dw 0x0038,0x0044,0x0044,0x0048,0x007f  ;d
 dw 0x0038,0x0054,0x0054,0x0054,0x0018  ;e
 dw 0x0008,0x007e,0x0009,0x0001,0x0002  ;f
 dw 0x000c,0x0052,0x0052,0x0052,0x003e  ;g
 dw 0x007f,0x0008,0x0004,0x0004,0x0078  ;h
 dw 0x0000,0x0044,0x007d,0x0040,0x0000  ;i
 dw 0x0020,0x0040,0x0040,0x003d,0x0000  ;j
 dw 0x007f,0x0010,0x0028,0x0044,0x0000  ;k
 dw 0x0000,0x0041,0x007f,0x0040,0x0000  ;l
 dw 0x007c,0x0004,0x0018,0x0004,0x0078  ;m
 dw 0x007c,0x0008,0x0004,0x0004,0x0078  ;n
 dw 0x0038,0x0044,0x0044,0x0044,0x0038  ;o

;ASCII 70 - 7f, 112 - 127

 dw 0x007c,0x0014,0x0014,0x0014,0x0008  ;p
 dw 0x0008,0x0014,0x0014,0x0018,0x007c  ;q
 dw 0x007c,0x0008,0x0004,0x0004,0x0008  ;r
 dw 0x0048,0x0054,0x0054,0x0054,0x0020  ;s
 dw 0x0004,0x003f,0x0044,0x0040,0x0020  ;t
 dw 0x003c,0x0040,0x0040,0x0020,0x007c  ;u
 dw 0x001c,0x0020,0x0040,0x0020,0x001c  ;v
 dw 0x003c,0x0040,0x0030,0x0040,0x003c  ;w
 dw 0x0044,0x0028,0x0001,0x0028,0x0044  ;x
 dw 0x000c,0x0050,0x0050,0x0050,0x003c  ;y
 dw 0x0044,0x0064,0x0054,0x004c,0x0044  ;z
 dw 0x0000,0x0008,0x0036,0x0041,0x0000  ;{
 dw 0x0000,0x0000,0x007f,0x0000,0x0000  ;|
 dw 0x0000,0x0041,0x0036,0x0008,0x0000  ;}
 dw 0x0008,0x0008,0x002a,0x001c,0x0008  ;->
 dw 0x0008,0x001c,0x002a,0x0008,0x0008  ;<-

;alternates with descenders, ASCII 80 - 85, 128 - 134

 dw 0x0000,0x0140,0x00c0,0x0000,0x0000  ;, ASCII 80, 128
 dw 0x0038,0x0244,0x0244,0x0244,0x01f8  ;g ASCII 81, 129
 dw 0x0100,0x0200,0x0204,0x01fd,0x0000  ;j ASCII 82, 130
 dw 0x03f8,0x0044,0x0044,0x0044,0x0038  ;p ASCII 83, 131
 dw 0x0038,0x0044,0x0144,0x02c4,0x0238  ;q ASCII 84, 132
 dw 0x003c,0x0240,0x0240,0x0240,0x01fc  ;y ASCII 85, 133
 dw 0x0108,0x0244,0x0244,0x0224,0x01d8  ;z ASCII 86, 134

  end

----------------------------------------------------------------------------------

;Display '278 in large numbers across mid-screen boundary
;with 'Voltage' label underneath



Once again, the 4-part code here shows just one way to do this

;Start display at a known (x,y) co-ordinate 48,16

;1st digit is contained entirely within controller 1's side

         bsf     cs1               ;select left-hand controller
         usec
         bcf     cs2
         usec

d278     movlw   .48               ;48th column
         movwf   column
         addlw   y_base
         call    write_c

         movlw   .2                ;3rd page (starts @ line 16)
         addlw   page_base
         call    write_c

         movlw   low(ch16)         ;data base address (ie character "0")
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

;----------------

         movlw   .2                ;offset to first digit "2"
         mullw   .20               ;at 20 bytes (10 words) of data per character

         movfw   prodl             ;add character offset to base address
         addwf   tblptrl
         movfw   prodh
         addwfc  tblptrh

;----------------

;top half of character

         movlw   .10               ;data counter
         movwf   temp0

top_c1   tblrd*+                   ;fetch low byte
         movfw   tablat
         call    write_d           ;display it

         tblrd*+                   ;fetch, ignore, high byte

         decfsz  temp0             ;loop
         bra     top_c1

;bottom half of character

         movfw   column            ;reset to coumn 48
         addlw   y_base
         call    write_c

         movlw   .3
         addlw   page_base         ;next page, page 4 (starts @ line 24)
         call    write_c

;fetch same data words as before but use the high bytes

         movlw   low(ch16)         ;data base address (ie character 0)
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

         movlw   .2                ;first digit
         mullw   .20               ;20 bytes of data per character

         movfw   prodl             ;add character offset
         addwf   tblptrl           ;to base address
         movfw   prodh
         addwfc  tblptrh

         movlw   .10               ;data counter
         movwf   temp0

bot_c1   tblrd*+                   ;fetch low byte, ignore

         tblrd*+
         movfw   tablat
         call    write_d           ;display it

         decfsz  temp0             ;loop
         bra     bot_c1

;----------------

         movlw   0x00              ;two-pixel space before next digit
         call    write_d           ;column = 58
         call    write_d           ;       = 59

;column for start of second character = 60

;go back up a page for top half of next character

         movlw   .2                ;3rd page (starts @ line 16)
         addlw   page_base
         call    write_c

         movlw   low(ch16)         ;data base address (ie character 0)
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

;----------------

         movlw   .7                ;offset to data for second digit "7"
         mullw   .20               ;at 20 bytes (10 words) of data per character

         movfw   prodl             ;add character offset to base address
         addwf   tblptrl
         movfw   prodh
         addwfc  tblptrh

;----------------

;top half of character

         clrf    temp0             ;data counter

top_c2   tblrd*+                   ;fetch low byte
         movfw   tablat
         call    write_d           ;display it

         tblrd*+                   ;fetch, ignore, high byte

         incf    temp0             ;if temp0 = 10, top is finished
         movlw   .10
         cpfslt  temp0             ;else not 10, check for boundary
         bra     c2_low

;if column is < 64 then set cs1
;if column is = 64 then set cs2

         movlw   .4
         cpfseq  temp0             ;start column (60) + 4 data = 64
         bra     top_c2

         bcf     cs1               ;switch to right-hand controller
         usec
         bsf     cs2
         usec

         movlw   .0                ;and column 0
         addlw   y_base
         call    write_c

         movlw   .2
         addlw   page_base         ;maintain page number
         call    write_c

         bra     top_c2            ;loop

;bottom half of character

c2_low   bsf     cs1               ;back to left-hand controller
         usec
         bcf     cs2
         usec

         movlw   .60               ;reset to column 60
         addlw   y_base
         call    write_c

         movlw   .3
         addlw   page_base         ;down a page (starts @ line 24)
         call    write_c

;fetch same data words as before but use the high bytes

c2_datl  movlw   low(ch16)         ;data base address (ie character 0)
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

         movlw   .7                ;second digit, "7"
         mullw   .20               ;20 bytes of data per character

         movfw   prodl             ;add character offset
         addwf   tblptrl           ;to base address
         movfw   prodh
         addwfc  tblptrh

         clrf    temp0             ;data counter

bot_c2   tblrd*+                   ;fetch low byte, ignore

         tblrd*+                   ;fetch high byte
         movfw   tablat
         call    write_d           ;display it

         incf    temp0             ;if temp0 = 10, bottom is finished
         movlw   .10
         cpfslt  temp0             ;not 10, check for boundary
         bra     digit3            ;else finished

;if column is < 64 then set cs1
;if column is > 63 then set cs2

         movlw   .4
         cpfseq  temp0
         bra     bot_c2

         bcf     cs1               ;switch to right-hand controller
         usec
         bsf     cs2
         usec

         movlw   .0                ;and column 0
         addlw   y_base
         call    write_c

         movlw   .3
         addlw   page_base
         call    write_c

         bra     bot_c2

;----------------

;3rd digit is contained entirely within controller 2's side

digit3   movlw   0x00              ;two-pixel space before next digit
         call    write_d
         call    write_d

         movlw   .2                ;3rd page (starts @ line 16)
         addlw   page_base
         call    write_c

         movlw   low(ch16)         ;data base address (ie character 0)
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

;----------------

         movlw   .8                ;offset to third digit "8"
         mullw   .20               ;at 20 bytes (10 words) of data per character

         movfw   prodl             ;add character offset to base address
         addwf   tblptrl
         movfw   prodh
         addwfc  tblptrh

;----------------

;top half of character

         movlw   .10               ;data counter
         movwf   temp0

top_c3   tblrd*+                   ;fetch low byte
         movfw   tablat
         call    write_d           ;display it

         tblrd*+                   ;fetch, ignore, high byte

         decfsz  temp0             ;loop
         bra     top_c3

;bottom half of character

         movlw   .8                ;reset to column 8
         addlw   y_base
         call    write_c

         movlw   .3
         addlw   page_base         ;next page (starts @ line 24)
         call    write_c

;fetch same data words as before but use the high bytes

         movlw   low(ch16)         ;data base address (ie character 0)
         movwf   tblptrl
         movlw   high(ch16)
         movwf   tblptrh
         clrf    tblptru

         movlw   .8                ;third digit
         mullw   .20               ;20 bytes of data per character

         movfw   prodl             ;add character offset
         addwf   tblptrl           ;to base address
         movfw   prodh
         addwfc  tblptrh

         movlw   .10               ;data counter
         movwf   temp0

bot_c3   tblrd*+                   ;fetch low byte, ignore

         tblrd*+
         movfw   tablat
         call    write_d           ;display it

         decfsz  temp0             ;loop
         bra     bot_c3

         call    v_label           ;the routine where you get to figure
                                   ;out how to break a character across
				   ;the mid-screen boundary