.include "system/lcd.h65" .ifndef lcd::LCD_IO .fatal "lcd::LCD_IO is not defined: set it to the base address of the IO chip of the LCD" .endif Export lcd,init,clear,print,print_char,set_position,set_custom_char Export lcd,_cmd,_wait_nbusy,_write_ram,_read_ram,_set_dd_ram_addr_from_charcount Export lcd,_charcount ; RAM VARIABLES .bss _charcount: .res 1 ; @TODO when clockspeeds are more than 1MHz, _cmd, _write_ram and _read_ram might need to be adjusted .code ;;******************************************************************************** ;; @function Initialize the lcd module ;; @details call before doing anything else ;; @modifies A ;;******************************************************************************** .proc init ; init IO lda #$ff ; RB 0-7 output sta lcd::LCD_IO+IO::DDRB lda lcd::LCD_IO+IO::DDRA ora #(lcd::RS | lcd::RW | lcd::E) sta lcd::LCD_IO+IO::DDRA ; init lcd lda #lcd::CMD::FUNCTION_SET jsr _cmd lda #lcd::CMD::DISPLAY_ON jsr _cmd lda #lcd::CMD::CLEAR jsr _cmd lda #lcd::CMD::ENTRY_MODE jsr _cmd stz _charcount rts .endproc ;;******************************************************************************** ;; @function Set the cursor to a position ;; @param A: cursor position: `(lcd::LINEX + offset)`, where offset € [$0, $f] ;; @details: ;; If the position is too large, it will be set to char 4 in line 2 ;; @returns A: the cursor position ;;******************************************************************************** .proc set_position pha cmp #$60 bge @invalid cmp #$50 bge @line4 cmp #$40 bge @line2 cmp #$20 bge @invalid cmp #$10 bge @line3 bra @set ; @line1: ; starts at $00, _charcount at $00 @line2: ; starts at $40, _charcount at $10 sbc #$30 ; carry is already set bra @set @line3: ; starts at $10, _charcount at $20 add #$10 bra @set @line4: ; starts at $50, _charcount at $30 sbc #$20 @set: sta _charcount pla pha ora #lcd::CMD::SET_ADDRESS jsr _cmd ; and #(<~lcd::CMD::SET_ADDRESS) ; return original argument pla rts @invalid: pla ; otherwise stack corruption lda #$13 sta _charcount lda #(lcd::CMD::SET_ADDRESS | (lcd::LINE2 + 3)) jsr _cmd lda #(lcd::LINE2 + 3) rts .endproc ;******************************************************************************** ; PRINTING TO LCD ;******************************************************************************** ;;******************************************************************************** ;; @function Clear the display ;; @see lcd_print ;; @modifies A ;;******************************************************************************** .proc clear stz _charcount lda #lcd::CMD::CLEAR jsr _cmd rts .endproc ;;******************************************************************************** ;; @function Print a null-terminated string ;; @param ARG0-1 Address of the string to print ;; @modifies: A,Y ;; @returns Y: Length of the string ;;******************************************************************************** .proc print ldy #$00 @lcd_print_loop: lda (ARG0),y beq @lcd_print_end jsr print_char iny bra @lcd_print_loop @lcd_print_end: rts .endproc ;;******************************************************************************** ;; @function Print a single character ;; @param A: Character to print ;;******************************************************************************** .proc print_char pha jsr _write_ram inc _charcount jsr _break_line pla ; put char back in a .endproc ;;******************************************************************************** ;; @function Write a custom character to the lcds CharacterGenerator (CG) RAM ;; @param A: The ASCII code: 0-7 ;; @param ARG0-1: Start address of the 8 bytes describing the character ;; @returns: C: 0 => success, 1 => invalid argument ;; @modifies: A,Y ;;******************************************************************************** .proc set_custom_char cmp #8 bcs @rts ; >= 8 rol ; address is bytes 3-5 rol rol ora #lcd::CMD::SET_CG_ADDRESS jsr lcd::_cmd ldy #0 @loop: lda (ARG0),y jsr lcd::_write_ram iny cpy #8 bne @loop clc @rts: rts .endproc ;;******************************************************************************** ;; Internal LCD Commands ;;******************************************************************************** ;; @function Wait until the lcd is no longer busy ;; @details ;; Reads from the address until the busy flag is no longer set. ;; After reading, the VIA will be set to output mode again ;;******************************************************************************** .proc _wait_nbusy pha stz lcd::LCD_IO + IO::DDRB ; set IO1-IO + IO::RB to input lda lcd::LCD_IO + IO::RA and #<~lcd::RA_MASK ora #lcd::RW sta lcd::LCD_IO + IO::RA @lcd_wait_nbusy_loop: ; read the busy flag ; set E low, then high lda lcd::LCD_IO + IO::RA and #<~lcd::E sta lcd::LCD_IO + IO::RA ora #lcd::E sta lcd::LCD_IO + IO::RA lda lcd::LCD_IO + IO::RB bmi @lcd_wait_nbusy_loop ; msb set lda lcd::LCD_IO + IO::RA and #<~lcd::RA_MASK sta lcd::LCD_IO + IO::RA lda #%11111111 ; set IO1-IO + IO::RB to output sta lcd::LCD_IO + IO::DDRB pla rts .endproc ;;******************************************************************************** ;; @function Send a @ref lcd::CMD "command" to the lcd ;; @param A: command ;; @modifies A ;;******************************************************************************** .proc _cmd jsr _wait_nbusy sta lcd::LCD_IO + IO::RB ; while preserve bits 0-4: unset E, set RS ; unset E, RW and RS lda lcd::LCD_IO + IO::RA and #(<~lcd::RA_MASK) sta lcd::LCD_IO + IO::RA ; set E ora #lcd::E sta lcd::LCD_IO + IO::RA ; unset E eor #lcd::E sta lcd::LCD_IO + IO::RA rts .endproc ;;******************************************************************************** ;; @function Write a byte to the lcd ;; @details ;; Set the CG or DD address first ;; @param A: data ;; @modifies A ;;******************************************************************************** .proc _write_ram jsr _wait_nbusy sta lcd::LCD_IO + IO::RB ; while preserve bits 0-4: unset E and RW, set RS lda lcd::LCD_IO + IO::RA and #(<~lcd::RA_MASK) ora #lcd::RS sta lcd::LCD_IO + IO::RA ; set E ora #lcd::E sta lcd::LCD_IO + IO::RA ; unsert E eor #lcd::E sta lcd::LCD_IO + IO::RA rts .endproc ;;******************************************************************************** ;; @function Read a byte from the lcd ;; @details ;; Set the CG or DD address first ;; @returns A: byte ;;******************************************************************************** .proc _read_ram jsr _wait_nbusy ; set IO1-IO + IO::RB to input stz lcd::LCD_IO + IO::DDRB ; while preserve bits 0-4: unset E, set RW and RS lda lcd::LCD_IO + IO::RA and #(<~lcd::RA_MASK) ora #(lcd::RW | lcd::RS) sta lcd::LCD_IO + IO::RA ; set E ora #lcd::E sta lcd::LCD_IO + IO::RA ; load the byte lda lcd::LCD_IO + IO::RB pha ; unset E lda lcd::LCD_IO + IO::RA eor #lcd::E sta lcd::LCD_IO + IO::RA ; set IO1-IO + IO::RB to output lda #$ff sta lcd::LCD_IO + IO::DDRB pla rts .endproc ;;******************************************************************************** ;; @function Set the LCD DD-RAM Address from the character count ;; @details ;; Sets the DD-RAM Address so that a line break occurs after 16 characters ;; If _charcount is more than $40, the position will be set to _charcount % $40 ;;******************************************************************************** .proc _set_dd_ram_addr_from_charcount cmp #$40 ; set to line1 when full bcs @wrap_to_line1 ; a € [$40, $ff] cmp #$10 bcc @line1 ; a € [$00, $0f] cmp #$20 bcc @line2 ; a € [$10, $1f] cmp #$30 bcc @line3 ; a € [$20, $2f] ; bra @line4; a € [$30, $3f] @line4: clc adc #$20 ; _charcount $30-$3f -> $50 - $5f bra @set_address @wrap_to_line1: and #%00111111 ; now every _charcount is mapped between 0 and $3f bra @set_address @line2: adc #$30 ; _charcount $10-$1f -> $40 - $4f bra @set_address @line3: sec sbc #$10 ; _charcount $20-$2f -> $10 - $1f bra @set_address @line1: ; _charcount $00-$1f -> $00 - $0f - nothing to do @set_address: ora #lcd::CMD::SET_ADDRESS jsr _cmd rts .endproc ;;******************************************************************************** ;; @function Set the LCD DD-RAM Address to te next line if the end of the line was reached ;; @details ;; If the cursor is past the end of a line, the DD-RAM Address is set to the next line. ;; If _charcount is more than $40, the position will be set to line 1. ;; This function is intended to be used with to set the correct address when ;; auto-shift is enabled ;;******************************************************************************** .proc _break_line ; check if checks are necessary lda _charcount beq @line1 bit #%10001111 ; ($10 | $20 | $30 | $40) = %01110000 beq @check rts @check: ; checks necessary cmp #$10 beq @line2 cmp #$20 beq @line3 cmp #$30 beq @line4 cmp #$40 ; set to line1 when full bge @line1 rts @line1: stz _charcount lda #(lcd::CMD::SET_ADDRESS | lcd::LINE1) jsr _cmd rts @line2: lda #(lcd::CMD::SET_ADDRESS | lcd::LINE2) jsr _cmd rts @line3: lda #(lcd::CMD::SET_ADDRESS | lcd::LINE3) jsr _cmd rts @line4: lda #(lcd::CMD::SET_ADDRESS | lcd::LINE4) jsr _cmd rts .endproc