.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 ; 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 MaskedWrite lcd::LCD_IO+IO::DDRA, (lcd::RS | lcd::RW | lcd::E), lcd::RA_MASK ; lda #(lcd::RS | lcd::RW | lcd::E) ; RA 5-7 output ; 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 @lcd_wait_nbusy_loop: ; read the busy flag ; TODO use update_with_mask lda #lcd::RW sta lcd::LCD_IO + IO::RA lda #(lcd::RW | lcd::E) sta lcd::LCD_IO + IO::RA lda lcd::LCD_IO + IO::RB bmi @lcd_wait_nbusy_loop ; msb set ; and #%10000000 ; and updates zero flag, if not set retry ; bne @lcd_wait_nbusy_loop lda #%00000000 ; TODO dont overwrite 0-4 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 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 so that text linebreaks after 16 chars ;;******************************************************************************** .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