|
- ; lcd
- ;
- ; Implement PutC on TI-84+ (for now)'s LCD screen.
- ;
- ; The screen is 96x64 pixels. The 64 rows are addressed directly with CMD_ROW
- ; but columns are addressed in chunks of 6 or 8 bits (there are two modes).
- ;
- ; In 6-bit mode, there are 16 visible columns. In 8-bit mode, there are 12.
- ;
- ; Note that "X-increment" and "Y-increment" work in the opposite way than what
- ; most people expect. Y moves left and right, X moves up and down.
- ;
- ; *** Z-Offset ***
- ;
- ; This LCD has a "Z-Offset" parameter, allowing to offset rows on the
- ; screen however we wish. This is handy because it allows us to scroll more
- ; efficiently. Instead of having to copy the LCD ram around at each linefeed
- ; (or instead of having to maintain an in-memory buffer), we can use this
- ; feature.
- ;
- ; The Z-Offet goes upwards, with wrapping. For example, if we have an 8 pixels
- ; high line at row 0 and if our offset is 8, that line will go up 8 pixels,
- ; wrapping itself to the bottom of the screen.
- ;
- ; The principle is this: The active line is always the bottom one. Therefore,
- ; when active row is 0, Z is FNT_HEIGHT+1, when row is 1, Z is (FNT_HEIGHT+1)*2,
- ; When row is 8, Z is 0.
- ;
- ; *** 6/8 bit columns and smaller fonts ***
- ;
- ; If your glyphs, including padding, are 6 or 8 pixels wide, you're in luck
- ; because pushing them to the LCD can be done in a very efficient manner.
- ; Unfortunately, this makes the LCD unsuitable for a Collapse OS shell: 6
- ; pixels per glyph gives us only 16 characters per line, which is hardly
- ; usable.
- ;
- ; This is why we have this buffering system. How it works is that we're always
- ; in 8-bit mode and we hold the whole area (8 pixels wide by FNT_HEIGHT high)
- ; in memory. When we want to put a glyph to screen, we first read the contents
- ; of that area, then add our new glyph, offsetted and masked, to that buffer,
- ; then push the buffer back to the LCD. If the glyph is split, move to the next
- ; area and finish the job.
- ;
- ; That being said, it's important to define clearly what CURX and CURY variable
- ; mean. Those variable keep track of the current position *in pixels*, in both
- ; axes.
- ;
- ; *** Requirements ***
- ; fnt/mgm
- ;
- ; *** Constants ***
- .equ LCD_PORT_CMD 0x10
- .equ LCD_PORT_DATA 0x11
-
- .equ LCD_CMD_6BIT 0x00
- .equ LCD_CMD_8BIT 0x01
- .equ LCD_CMD_DISABLE 0x02
- .equ LCD_CMD_ENABLE 0x03
- .equ LCD_CMD_XDEC 0x04
- .equ LCD_CMD_XINC 0x05
- .equ LCD_CMD_YDEC 0x06
- .equ LCD_CMD_YINC 0x07
- .equ LCD_CMD_COL 0x20
- .equ LCD_CMD_ZOFFSET 0x40
- .equ LCD_CMD_ROW 0x80
- .equ LCD_CMD_CONTRAST 0xc0
-
- ; *** Variables ***
- ; Current Y position on the LCD, that is, where re're going to spit our next
- ; glyph.
- .equ LCD_CURY LCD_RAMSTART
- ; Current X position
- .equ LCD_CURX @+1
- ; two pixel buffers that are 8 pixels wide (1b) by FNT_HEIGHT pixels high.
- ; This is where we compose our resulting pixels blocks when spitting a glyph.
- .equ LCD_BUF @+1
- .equ LCD_RAMEND @+FNT_HEIGHT*2
-
- ; *** Code ***
- lcdInit:
- ; Initialize variables
- xor a
- ld (LCD_CURY), a
- ld (LCD_CURX), a
-
- ; Clear screen
- call lcdClrScr
-
- ; We begin with a Z offset of FNT_HEIGHT+1
- ld a, LCD_CMD_ZOFFSET+FNT_HEIGHT+1
- call lcdCmd
-
- ; Enable the LCD
- ld a, LCD_CMD_ENABLE
- call lcdCmd
-
- ; Hack to get LCD to work. According to WikiTI, we're not sure why TIOS
- ; sends these, but it sends it, and it is required to make the LCD
- ; work. So...
- ld a, 0x17
- call lcdCmd
- ld a, 0x0b
- call lcdCmd
-
- ; Set some usable contrast
- ld a, LCD_CMD_CONTRAST+0x34
- call lcdCmd
-
- ; Enable 8-bit mode.
- ld a, LCD_CMD_8BIT
- call lcdCmd
-
- ret
-
- ; Wait until the lcd is ready to receive a command
- lcdWait:
- push af
- .loop:
- in a, (LCD_PORT_CMD)
- ; When 7th bit is cleared, we can send a new command
- rla
- jr c, .loop
- pop af
- ret
-
- ; Send cmd A to LCD
- lcdCmd:
- out (LCD_PORT_CMD), a
- jr lcdWait
-
- ; Send data A to LCD
- lcdDataSet:
- out (LCD_PORT_DATA), a
- jr lcdWait
-
- ; Get data from LCD into A
- lcdDataGet:
- in a, (LCD_PORT_DATA)
- jr lcdWait
-
- ; Turn LCD off
- lcdOff:
- push af
- ld a, LCD_CMD_DISABLE
- call lcdCmd
- out (LCD_PORT_CMD), a
- pop af
- ret
-
- ; Set LCD's current column to A
- lcdSetCol:
- push af
- ; The col index specified in A is compounded with LCD_CMD_COL
- add a, LCD_CMD_COL
- call lcdCmd
- pop af
- ret
-
- ; Set LCD's current row to A
- lcdSetRow:
- push af
- ; The col index specified in A is compounded with LCD_CMD_COL
- add a, LCD_CMD_ROW
- call lcdCmd
- pop af
- ret
-
- ; Send the glyph that HL points to to the LCD, at its current position.
- ; After having called this, the LCD's position will have advanced by one
- ; position
- lcdSendGlyph:
- push af
- push bc
- push hl
- push ix
-
- ld a, (LCD_CURY)
- call lcdSetRow
- ld a, (LCD_CURX)
- srl a \ srl a \ srl a ; div by 8
- call lcdSetCol
-
- ; First operation: read the LCD memory for the "left" side of the
- ; buffer. We assume the right side to always be empty, so we don't
- ; read it. After having read each line, compose it with glyph line at
- ; HL
-
- ; Before we start, what is our bit offset?
- ld a, (LCD_CURX)
- and 0b111
- ; that's our offset, store it in C
- ld c, a
-
- ld a, LCD_CMD_XINC
- call lcdCmd
- ld ix, LCD_BUF
- ld b, FNT_HEIGHT
- ; A dummy read is needed after a movement.
- call lcdDataGet
- .loop1:
- ; let's go get that glyph data
- ld a, (hl)
- ld (ix), a
- call .shiftIX
- ; now let's go get existing pixel on LCD
- call lcdDataGet
- ; and now let's do some compositing!
- or (ix)
- ld (ix), a
- inc hl
- inc ix
- djnz .loop1
-
- ; Buffer set! now let's send it.
- ld a, (LCD_CURY)
- call lcdSetRow
-
- ld hl, LCD_BUF
- ld b, FNT_HEIGHT
- .loop2:
- ld a, (hl)
- call lcdDataSet
- inc hl
- djnz .loop2
-
- ; And finally, let's send the "right side" of the buffer
- ld a, (LCD_CURY)
- call lcdSetRow
- ld a, (LCD_CURX)
- srl a \ srl a \ srl a ; div by 8
- inc a
- call lcdSetCol
-
- ld hl, LCD_BUF+FNT_HEIGHT
- ld b, FNT_HEIGHT
- .loop3:
- ld a, (hl)
- call lcdDataSet
- inc hl
- djnz .loop3
-
- ; Increase column and wrap if necessary
- ld a, (LCD_CURX)
- add a, FNT_WIDTH+1
- ld (LCD_CURX), a
- cp 96-FNT_WIDTH
- jr c, .skip ; A < 96-FNT_WIDTH
- call lcdLinefeed
- .skip:
- pop ix
- pop hl
- pop bc
- pop af
- ret
- ; Shift glyph in (IX) to the right C times, sending carry into (IX+FNT_HEIGHT)
- .shiftIX:
- dec c \ inc c
- ret z ; zero? nothing to do
- push bc ; --> lvl 1
- xor a
- ld (ix+FNT_HEIGHT), a
- .shiftLoop:
- srl (ix)
- rr (ix+FNT_HEIGHT)
- dec c
- jr nz, .shiftLoop
- pop bc ; <-- lvl 1
- ret
-
- ; Changes the current line and go back to leftmost column
- lcdLinefeed:
- push af
- ld a, (LCD_CURY)
- call .addFntH
- ld (LCD_CURY), a
- call lcdClrLn
- ; Now, lets set Z offset which is CURROW+FNT_HEIGHT+1
- call .addFntH
- add a, LCD_CMD_ZOFFSET
- call lcdCmd
- xor a
- ld (LCD_CURX), a
- pop af
- ret
- .addFntH:
- add a, FNT_HEIGHT+1
- cp 64
- ret c ; A < 64? no wrap
- ; we have to wrap around
- xor a
- ret
-
- ; Clears B rows starting at row A
- ; B is not preserved by this routine
- lcdClrX:
- push af
- call lcdSetRow
- .outer:
- push bc ; --> lvl 1
- ld b, 11
- ld a, LCD_CMD_YINC
- call lcdCmd
- xor a
- call lcdSetCol
- .inner:
- call lcdDataSet
- djnz .inner
- ld a, LCD_CMD_XINC
- call lcdCmd
- xor a
- call lcdDataSet
- pop bc ; <-- lvl 1
- djnz .outer
- pop af
- ret
-
- lcdClrLn:
- push bc
- ld b, FNT_HEIGHT+1
- call lcdClrX
- pop bc
- ret
-
- lcdClrScr:
- push bc
- ld b, 64
- call lcdClrX
- pop bc
- ret
-
- lcdPutC:
- cp LF
- jp z, lcdLinefeed
- cp BS
- jr z, .bs
- push hl
- call fntGet
- jr nz, .end
- call lcdSendGlyph
- .end:
- pop hl
- ret
- .bs:
- ld a, (LCD_CURX)
- or a
- ret z ; going back one line is too complicated.
- ; not implemented yet
- sub FNT_WIDTH+1
- ld (LCD_CURX), a
- ret
|