; ***************************************************************************
; copyright   : (C) 2023 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************



; Some code in this module is based on C-code in Tiny4kOLED (https://github.com/datacute/Tiny4kOLED/tree/master/src) 
; by Stephen Denne (MIT license), rewritten in AVR assembler
;
; - page 0 is top line, page 7 ist bottom line
; - column 0 is the leftmost colum, column 127 is the rightmost column
; - a page has a height of 8 (->8 bits)
; - when writing a byte to a position (e.g. page 0, column 0)
; - writing to chip RAM is done in columns
;
; text_xpos=(xpos*8), meaning max 16 chars per text line
;
; Page 0:    col 0 col 1 ... col 127
; Row 0(LSB) 0/0   1/0   ... 127/0
; Row 1      0/1   1/1   ... 127/1
; Row 2      0/2   1/2   ... 127/2
; Row 3      0/3   1/3   ... 127/3
; ...
; Row 7(MSB) 0/7   1/7   ... 127/7
; and so one


; ***************************************************************************
; defines

.equ LCD_WIDTH               = 128
.equ LCD_HEIGHT              = 64

.equ LCD_PAGE_COUNT          = 8

.equ LCD_MODE_MULTICMD       = 0x00
.equ LCD_MODE_SINGLECMD      = 0x80
.equ LCD_MODE_DATA           = 0x40
.equ LCD_MODE_SINGLEDATA     = 0xc0



; ***************************************************************************
; data

.dseg

lcdDataBegin:
lcdDataEnd:



; ***************************************************************************
; code

.cseg


LCD_BEGIN:



; ---------------------------------------------------------------------------
; @routine LCD_Init @global
;
; @return CFLAG set if okay, cleared on error

LCD_Init:
  ldi zl, LOW(lcdInitCommandsBegin)
  ldi zh, HIGH(lcdInitCommandsBegin)
  ldi r16, 28
  rcall lcdWriteCommandsFromFlash

  ldi r16, 0
  rcall LCD_Fill

  ; set cursor to next line here, sometimes this doesn't work correctly
  ; when called later. TODO: find out what goes wrong here...
  ldi r18, 0
  ldi r19, 0
  rcall LCD_SetCursor

  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine LCD_Fini @global
;

LCD_Fini:
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine LCD_Sleep @global
;
; @return nothing
; @param r16 1=sleep, 0=awake
; @clobbers r15, r16, r19 (R17, R18, R22)
;

LCD_Sleep:
  ldi r16, 0xae ; set sleep
  tst r19
  brne LCD_Sleep_send
  ldi r16, 0xaf ; set wake
LCD_Sleep_send:
  rjmp lcdOneByteCommand
; @end



; ---------------------------------------------------------------------------
; @routine LCD_SetContrast @global
;
; @return nothing
; @param r16 1=sleep, 0=awake
; @clobbers r15, r16, r19 (R17, R18, R22)
;

LCD_SetContrast:
  ldi r20, 0x81
  mov r21, r16
  rjmp lcdTwoByteCommand
; @end



; ---------------------------------------------------------------------------
; @routine lcdOneByteCommand
; Send a one byte command
;
; @return CFLAG set if okay, cleared on error
; @param r20 first byte of command
; @clobbers r15, r16, r19 (R17, R18, R22)

lcdOneByteCommand:
  in r15, SREG
  push r15
    cli

    rcall lcdBeginMultiCommand   ; (r16, R17, R18, R22)
    brcc lcdOneByteCommand_error

    mov r16, r20
    rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
    brcc lcdOneByteCommand_error

    rcall twiStop                ; (R22)
  pop r15
  out SREG, r15
  sec
  ret
  
lcdOneByteCommand_error:
  rjmp lcdTwiStopPopR15ClcRet    ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine lcdTwoByteCommand
; Send a two byte command
;
; @return CFLAG set if okay, cleared on error
; @param r20 first byte of command
; @param r21 second byte of command
; @clobbers r15, r16, r19 (R17, R18, R22)
;

lcdTwoByteCommand:
  in r15, SREG
  push r15
    cli

    rcall lcdBeginMultiCommand   ; (r16, R17, R18, R22)
    brcc lcdTwoByteCommand_error

    mov r16, r20
    rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
    brcc lcdTwoByteCommand_error

    mov r16, r21
    rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
    brcc lcdTwoByteCommand_error

    rcall twiStop                ; (R22)
  pop r15
  out SREG, r15
  sec
  ret
  
lcdTwoByteCommand_error:
  rjmp lcdTwiStopPopR15ClcRet    ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine lcdBeginMultiCommand
;
; Set TWI start condition, send address and LCD_MODE_MULTICMD
;
; @return CFLAG set if okay, cleared on error
; @clobbers r16, (R17, R18, R22)

lcdBeginMultiCommand:
  rcall twiStart             ; (R22)
  ldi r16, (LCD_TWI_ADDRESS*2)
  rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
  brcc lcdTwoByteCommand_error

  ldi r16, LCD_MODE_MULTICMD
  rjmp twiSendByteExpectAck ; (R16, R17, R18, R22)
; @end



lcdTwiStopPopR15ClcRet:
    rcall twiStop              ; (R22)
  pop r15
  out SREG, r15
  clc
  ret



; ---------------------------------------------------------------------------
; @routine LCD_SetCursor @global
;
; Set cursor. X is specified in pixels, Y is specified in character rows (8 pixels).
; Left upper corner is 0/0 (x/y), right lower corner is 127/7
;
; @return CFLAG set if okay, cleared on error
; @param R18 X
; @param R19 Y
; @clobbers r1, r2, r15, r16 (R16, R17, R18, R22)

LCD_SetCursor:
  in r15, SREG
  push r15
    cli
    mov r1, r18
    mov r2, r19
    rcall twiStart             ; (R22)
    ldi r16, (LCD_TWI_ADDRESS*2)
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error
  
    ldi r16, LCD_MODE_MULTICMD
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error
    mov r16, r2                ; Y
    andi r16, 0x07
    ori r16, 0xb0              ; set page start address for page addressing mode
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error
    
    ldi r16, 0x21              ; set column start
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error

    mov r16, r1                ; X
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error

    ldi r16, LCD_WIDTH-1
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_SetCursor_error

    rcall twiStop              ; (R22)
  pop r15
  out SREG, r15
  sec
  ret
LCD_SetCursor_error:
  rjmp lcdTwiStopPopR15ClcRet     ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine LCD_Fill @global
;
; Fill display RAM with the given value.
;
; @return CFLAG set if okay, cleared on error
; @param  R16 data to write
; @clobbers r15, r16, r18, r19, r20, r21 (r17, r22)

LCD_Fill:
  in r15, SREG
  push r15
    cli
    mov r20, r16

    ldi r21, 0 ; Y
LCD_Fill_loopY:
      clr r18
      mov r19, r21
      rcall LCD_SetCursor
      brcc LCD_Fill_error
  
      rcall twiStart
      ldi r16, (LCD_TWI_ADDRESS*2)
      rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
      brcc LCD_Fill_error

      ldi r16, LCD_MODE_DATA
      rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
      brcc LCD_Fill_error
    
      ldi r19, LCD_WIDTH           ; count backwards to save one instruction
LCD_Fill_loopX:
        mov r16, r20
        rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
        rcall twiSendByte
        brcc LCD_Fill_error
        dec r19
      brne LCD_Fill_loopX
      rcall twiStop
      inc r21
      cpi r21, LCD_PAGE_COUNT
    brcs LCD_Fill_loopY
  pop r15
  out SREG, r15
  sec
  ret
LCD_Fill_error:
  rjmp lcdTwiStopPopR15ClcRet     ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine LCD_PrintFromFlash @global
;
; Print a string from flash at the current position.
;
; @return CFLAG set if okay, cleared on error
; @param Z position of string to print
; @clobbers r15, r16, Z (R17, R18, R22)

LCD_PrintFromFlash:
  in r15, SREG
  push r15
    cli
    lsl zl
    rol zh
    rcall twiStart
    ldi r16, (LCD_TWI_ADDRESS*2)
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintFromFlash_error
    ldi r16, LCD_MODE_DATA
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintFromFlash_error

LCD_PrintFromFlash_loop:
      lpm r16, z+
      tst r16
      breq LCD_PrintFromFlash_end
      push zh
        push zl
          rcall lcdPrintOneChar
        pop zl
      pop zh
      brcc LCD_PrintFromFlash_error
    rjmp LCD_PrintFromFlash_loop
LCD_PrintFromFlash_end:
    rcall twiStop
  pop r15
  out SREG, r15
  sec
  ret
  
LCD_PrintFromFlash_error:
  rjmp lcdTwiStopPopR15ClcRet     ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine LCD_PrintChar @global
;
; Print a character at the current position.
;
; @return CFLAG set if okay, cleared on error
; @param  R16 char
; @clobbers r15, r16, r17, Z

LCD_PrintChar:
  in r15, SREG
  push r15
    cli
    rcall twiStart
    ldi r16, (LCD_TWI_ADDRESS*2)
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintChar_error
    ldi r16, LCD_MODE_DATA
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintChar_error
  
    rcall lcdPrintOneChar
    brcc LCD_PrintChar_error
    rcall twiStop
  pop r15
  out SREG, r15
  sec
  ret
LCD_PrintChar_error:
  rjmp lcdTwiStopPopR15ClcRet     ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine LCD_PrintHexByte @global
;
; Convert a given byte into HEX and write it to the current position.
;
; @return CFLAG set if okay, cleared on error
; @param  R16 byte to convert to hex
; @clobbers r15, r16, r17, r20, Z

LCD_PrintHexByte:
  in r15, SREG
  push r15
    cli
    mov r20, r16
    rcall twiStart
    ldi r16, (LCD_TWI_ADDRESS*2)
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintHexByte_error
    ldi r16, LCD_MODE_DATA
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc LCD_PrintHexByte_error
    mov r16, r20
    rcall lcdPrintHexByte
    brcc LCD_PrintHexByte_error
    rcall twiStop
  pop r15
  out SREG, r15
  sec
  ret
LCD_PrintHexByte_error:
  rjmp lcdTwiStopPopR15ClcRet     ; (R22)
; @end



; ---------------------------------------------------------------------------
; @routine LCD_PrintHexWord @global
;
; Convert a give word into HEX and write it to the current position.
;
; @return CFLAG set if okay, cleared on error
; @param  r18 low byte of the word
; @param  r19 high byte of the word
; @clobbers r15, r16, r17, r22, Z

LCD_PrintHexWord:
  in r15, SREG
  push r15
    cli
    push r18
      push r19
        rcall twiStart
        ldi r16, (LCD_TWI_ADDRESS*2)
        rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
        brcc LCD_PrintHexWord_errorPop
        ldi r16, LCD_MODE_DATA
        rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
        brcc LCD_PrintHexWord_errorPop
      pop r19
    pop r18
      
    rcall lcdPrintHexWord
    brcc LCD_PrintHexWord_errorNoPop
    rcall twiStop
  pop r15
  out SREG, r15
  sec
  ret
LCD_PrintHexWord_errorPop:
        rcall twiStop
      pop r19
    pop r18
LCD_PrintHexWord_errorNoPop:
  pop r15
  out SREG, r15
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdPrintHexWord
;
; Convert a given word into HEX and write it to the current position.
;
; @return CFLAG set if okay, cleared on error
; @param  r18 low byte of the word
; @param  r19 high byte of the word
; @clobbers r16, (r17, r20, Z)

lcdPrintHexWord:
  mov r16, r19
  push r18
    rcall lcdPrintHexByte    ; (r16, r17, r20, Z)
  pop r18
  brcc lcdPrintHexWord_error
  mov r16, r18
  rcall lcdPrintHexByte      ; (r16, r17, r20, Z)
  brcc lcdPrintHexWord_error
  sec
  ret
lcdPrintHexWord_error:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdPrintHexByte
;
; Convert a give byte into HEX and write it to the current position.
;
; @return CFLAG set if okay, cleared on error
; @param  R16 byte to convert to hex
; @clobbers r16, r20 (r16, r17, Z)

lcdPrintHexByte:
  mov r20, r16
  swap r16
  rcall lcdNibbleToAscii ; write high nibble (r16, r17)
  rcall lcdPrintOneChar  ; (r16, r17, Z)
  brcc lcdPrintHexByte_error
  mov r16, r20
  rcall lcdNibbleToAscii ; write low nibble
  rcall lcdPrintOneChar
  brcc lcdPrintHexByte_error
  sec
  ret
lcdPrintHexByte_error:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdNibbleToAscii
;
; Convert a nibble to an ASCII char.
; @return R16 ASCII representation of that nibble (e.g. '0' for 0)
; @param  R16 byte (in bits 0-3)
; @clobbers r16, r17

lcdNibbleToAscii:
  andi r16, 0xf
  cpi r16, 10
  brcs lcdNibbleToAscii_l1
  ldi r17, 7
  add r16, r17
lcdNibbleToAscii_l1:
  ldi r17, '0'
  add r16, r17
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdWriteCommandsFromFlash
;
; @return CFLAG set if okay, cleared on error
; @param  R16 number of bytes to send
; @param  Z   pointer to data
; @clobbers r16, r20, Z (r17, r18, r22)

lcdWriteCommandsFromFlash:
  rcall twiStart
  lsl zl
  rol zh
  mov r20, r16                            ; number of bytes
  ldi r16, (LCD_TWI_ADDRESS*2)
  rcall twiSendByteExpectAck              ; (R16, R17, R18, R22)
  brcc lcdWriteCommandsFromFlash_error
  
  ldi r16,LCD_MODE_MULTICMD
  rcall twiSendByteExpectAck              ; (R16, R17, R18, R22)
  brcc lcdWriteCommandsFromFlash_error
  
lcdWriteCommandsFromFlash_loop:
    lpm r16, z+
    rcall twiSendByteExpectAck            ; (R16, R17, R18, R22)
    brcc lcdWriteCommandsFromFlash_error
    dec r20
  brne lcdWriteCommandsFromFlash_loop
  rcall twiStop                           ; (R22)
  sec
  ret

lcdWriteCommandsFromFlash_error:
  rcall twiStop
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdPrintOneChar
;
; Print a character at the current position.
;
; @param R16 char
; @clobbers r16, r19, Z (R17, R18, R22)

lcdPrintOneChar:
  rcall lcdGetCharMatrix       ; (r16, r17, Z)
  ldi r16, 0                   ; spacing between chars
  rcall twiSendByteExpectAck   ; (R16, R17, R18, R22)
  brcc lcdPrintOneChar_error
  ldi r19, 6
lcdPrintOneChar_loop:
    lpm r16, z+
    rcall twiSendByteExpectAck ; (R16, R17, R18, R22)
    brcc lcdPrintOneChar_error
    dec r19
  brne lcdPrintOneChar_loop
  sec
  ret
  
lcdPrintOneChar_error:
  clc
  ret
; @end



; ---------------------------------------------------------------------------
; @routine lcdGetCharMatrix
;
; Get pointer to matrix of given char.
;
; @return Z   pos of character matrix (ready for LPM)
; @param  R16 char to lookup
; @clobbers r16, r17, Z

lcdGetCharMatrix:
#ifdef LCD_MINIMAL_FONT
  rcall lcdUpcase
  cpi r16, 65+32
  brcc lcdGetCharMatrix_l1
  cpi r16, 32
  brcc lcdGetCharMatrix_l2
lcdGetCharMatrix_l1:
  ldi r16, 32
lcdGetCharMatrix_l2:
  ldi r17, 32
  sub r16, r17
  mov zl, r16
  clr zh
  clr r17
  lsl zl      ; *2
  rol zh
  add zl, r16 ; *3
  adc zh, r17
  lsl zl      ; *6
  rol zh
  ldi r16, LOW(lcdFont6x8Minimal*2)
  ldi r17, HIGH(lcdFont6x8Minimal*2)
  add zl, r16
  adc zh, r17
  ret
#else
  cpi r16, 95+32
  brcc lcdGetCharMatrix_l1
  cpi r16, 32
  brcc lcdGetCharMatrix_l2
lcdGetCharMatrix_l1:
  ldi r16, 32
lcdGetCharMatrix_l2:
  ldi r17, 32
  sub r16, r17
  mov zl, r16
  clr zh
  clr r17
  lsl zl      ; *2
  rol zh
  add zl, r16 ; *3
  adc zh, r17
  lsl zl      ; *6
  rol zh
  ldi r16, LOW(lcdFont6x8*2)
  ldi r17, HIGH(lcdFont6x8*2)
  add zl, r16
  adc zh, r17
  ret
#endif
; @end



#ifdef LCD_MINIMAL_FONT
; ---------------------------------------------------------------------------
; @routine Uppercase the given character if it is lower case.
;
; @return R16 char
; @param  R16 char
; @clobbers r16, r17

lcdUpcase:
  cpi r16, 'a'
  brcs lcdUpcase_done
  cpi r16, 'z'+1
  brcc lcdUpcase_done
  ldi r17, 32
  sub r16, r17
lcdUpcase_done:
  ret
#endif
; @end



lcdInitCommandsBegin:  ; 28 bytes
   .db 0xae, 0x20, 0x00, 0xb0, 0xc8, 0x00, 0x10, 0x40
   .db 0x81, 0x3f, 0xa1, 0xa6, 0xa8, LCD_HEIGHT-1, 0xa4, 0xd3
   .db 0x00, 0xd5, 0xf0, 0xd9, 0x22, 0xda, 0x12, 0xdb
   .db 0x20, 0x8d, 0x14, 0xaf
lcdInitCommandsEnd:


; font taken from Tiny4kOLED (https://github.com/datacute/Tiny4kOLED/tree/master/src) by Stephen Denne (MIT license),
; original by Neven Boyanov
#ifdef LCD_MINIMAL_FONT
lcdFont6x8Minimal:
  .db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ;   0 
  .db 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 ; ! 1 
  .db 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 ; " 2 
  .db 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 ; # 3 
  .db 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 ; $ 4 
  .db 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 ; % 5 
  .db 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 ; & 6 
  .db 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 ; ' 7 
  .db 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 ; ( 8 
  .db 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 ; ) 9 
  .db 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 ; * 10
  .db 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 ; + 11
  .db 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 ; , 12
  .db 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 ; - 13
  .db 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 ; . 14
  .db 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 ; / 15
  .db 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E ; 0 16
  .db 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 ; 1 17
  .db 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 ; 2 18
  .db 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 ; 3 19
  .db 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 ; 4 20
  .db 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 ; 5 21
  .db 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 ; 6 22
  .db 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 ; 7 23
  .db 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 ; 8 24
  .db 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E ; 9 25
  .db 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 ; : 26
  .db 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 ; ; 27
  .db 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 ; < 28
  .db 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 ; = 29
  .db 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 ; > 30
  .db 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 ; ? 31
  .db 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E ; @ 32
  .db 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C ; A 33
  .db 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 ; B 34
  .db 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 ; C 35
  .db 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C ; D 36
  .db 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 ; E 37
  .db 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 ; F 38
  .db 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A ; G 39
  .db 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F ; H 40
  .db 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 ; I 41
  .db 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 ; J 42
  .db 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 ; K 43
  .db 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 ; L 44
  .db 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F ; M 45
  .db 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F ; N 46
  .db 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E ; O 47
  .db 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 ; P 48
  .db 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E ; Q 49
  .db 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 ; R 50
  .db 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 ; S 51
  .db 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 ; T 52
  .db 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F ; U 53
  .db 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F ; V 54
  .db 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F ; W 55
  .db 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 ; X 56
  .db 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 ; Y 57
  .db 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 ; Z 58
  .db 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 ; [ 59
  .db 0x00, 0x02, 0x04, 0x08, 0x10, 0x20 ; \ 60
  .db 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 ; ] 61
  .db 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 ; ^ 62
  .db 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 ; _ 63
  .db 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 ; ' 64
#else
lcdFont6x8:
  .db 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ;   0 
  .db 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 ; ! 1 
  .db 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 ; " 2 
  .db 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 ; # 3 
  .db 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 ; $ 4 
  .db 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 ; % 5 
  .db 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 ; & 6 
  .db 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 ; ' 7 
  .db 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 ; ( 8 
  .db 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 ; ) 9 
  .db 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 ; * 10
  .db 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 ; + 11
  .db 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 ; , 12
  .db 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 ; - 13
  .db 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 ; . 14
  .db 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 ; / 15
  .db 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E ; 0 16
  .db 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 ; 1 17
  .db 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 ; 2 18
  .db 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 ; 3 19
  .db 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 ; 4 20
  .db 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 ; 5 21
  .db 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 ; 6 22
  .db 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 ; 7 23
  .db 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 ; 8 24
  .db 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E ; 9 25
  .db 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 ; : 26
  .db 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 ; ; 27
  .db 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 ; < 28
  .db 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 ; = 29
  .db 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 ; > 30
  .db 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 ; ? 31
  .db 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E ; @ 32
  .db 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C ; A 33
  .db 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 ; B 34
  .db 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 ; C 35
  .db 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C ; D 36
  .db 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 ; E 37
  .db 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 ; F 38
  .db 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A ; G 39
  .db 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F ; H 40
  .db 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 ; I 41
  .db 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 ; J 42
  .db 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 ; K 43
  .db 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 ; L 44
  .db 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F ; M 45
  .db 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F ; N 46
  .db 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E ; O 47
  .db 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 ; P 48
  .db 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E ; Q 49
  .db 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 ; R 50
  .db 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 ; S 51
  .db 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 ; T 52
  .db 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F ; U 53
  .db 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F ; V 54
  .db 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F ; W 55
  .db 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 ; X 56
  .db 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 ; Y 57
  .db 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 ; Z 58
  .db 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 ; [ 59
  .db 0x00, 0x02, 0x04, 0x08, 0x10, 0x20 ; \ 60
  .db 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 ; ] 61
  .db 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 ; ^ 62
  .db 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 ; _ 63
  .db 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 ; ' 64
  .db 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 ; a 65
  .db 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 ; b 66
  .db 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 ; c 67
  .db 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F ; d 68
  .db 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 ; e 69
  .db 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 ; f 70
  .db 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C ; g 71
  .db 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 ; h 72
  .db 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 ; i 73
  .db 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 ; j 74
  .db 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 ; k 75
  .db 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 ; l 76
  .db 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 ; m 77
  .db 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 ; n 78
  .db 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 ; o 79
  .db 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 ; p 80
  .db 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC ; q 81
  .db 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 ; r 82
  .db 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 ; s 83
  .db 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 ; t 84
  .db 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C ; u 85
  .db 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C ; v 86
  .db 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C ; w 87
  .db 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 ; x 88
  .db 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C ; y 89
  .db 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 ; z 90
  .db 0x00, 0x08, 0x36, 0x41, 0x41, 0x00 ; { 91
  .db 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00 ; | 92
  .db 0x00, 0x00, 0x41, 0x41, 0x36, 0x08 ; } 93
  .db 0x00, 0x08, 0x04, 0x08, 0x10, 0x08 ; ~ 94
#endif


LCD_END:
.equ MODULE_SIZE_LCD = LCD_END-LCD_BEGIN

