Copyright = Mcs Electronics
Copyright = based on work of Ben Zijstra and Heiko/Hkipnik
Www = Http ://Www.mcselec.com
Email = Avr@mcselec.com

Comment = ST7565R-SPI 2.1in 128x64 Graphic Display Library (LCD12864-06D)
Libversion = 001
Date = 2026/05/31

Statement = No Source Code From The Library May Be Distributed In Any Form
Statement = Of Course This Does Not Apply For The Compiled Code When You Have A Bascom -avr License
history = O-Family fix : Modified "glcdEADOGM128x3.lib" for AQM1248A. 2014/05/06
history =              : Underline mode added. Rotate 180 degrees option added. 2019/ 1/15
history =              : Modified for ST7567 SPI mode. 2025/12/21
history =              : Modified for ST7565R (LCD12864-06D) SPI mode. 2026/05/31

; ------- Write the BASIC source as follows -----------------------------------
Comment =
Comment = ST7565R 2.1in 128x64 Graphic LCD  SPI mode (LCD12864-06D)
Comment =
Comment = *Syntax*
Comment = Const St7565_rotate = 0                                  'Screen rotation. (Connector: top = [0], bottom = [1])
Comment = $lib "glcd-ST7565R_SPI_2.1in_128x64.lib"                    'Include the function added library for ST7565R SPI.
Comment = '  Set the LCD connection port. Cs1 = [CS], Rst = [RSE], A0 = [RS], Sclk = [SCL], Si = [SI]
Comment = Config Graphlcd = 128 * 64eadogm , Cs1 = Portd.3 , Rst = Portd.4 , A0 = Portd.5 , Sclk = Portd.6 , Si = Portd.7
Comment = Glcdcmd &H81 : Glcdcmd 30                                'Sets the LCD contrast. [0:Low - 63:High]

; -----------------------------------------------------------------------------

[_GLCD]
$EXTERNAL _LPMBYTE,WAITMS

_Init_lcd:
_LCD_INIT:
_SET_DISPLAY:

* SBI _GLCD_PORT_CS1,_glcd_cs1		; [CS] High
* SBI _GLCD_PORT_SCL,_glcd_scl		; [SCL] High

* CBI _GLCD_PORT_RST,_glcd_rst		; [RST] Low
  @genus(10)				; Wait 10uS
* SBI _GLCD_PORT_RST,_glcd_rst		; [RST] High
  @genus(10)				; Wait 10uS

  Ldi R24,&HE2                  	; (14) Reset
  Rcall _Gwrite_cmd			; Send command
  Ldi R24,10				; Wait time after reset (10ms)
  Clr R25
* Call _Waitms
;
* Ldi R30,low(_lcd_init_data * 2)	; Initializing the LCD
* Ldi R31,high(_lcd_init_data * 2)
*#IF _ROMSIZE>65536
* Call _SET_RAMPZ
*#ENDIF
;
  Ldi R21,14				; 13 bytes
_Lcd_init_lp:
  Call _lpmbyte                       	; Get double command first byte
  Mov R24,R0
  Rcall _Gwrite_cmd			; Sending Commands
  Dec R21
  Brne _Lcd_init_lp			; Has the initial setting data been sent?
  Rjmp _Clear_graph			; Clear all displays

;---------------------------------------  LCD initial setting data ------------
_lcd_init_data:
  .db &HAE				; ( 1) Display ON/OFF [Display OFF]
  .db &HA2				; (11) LCD bias set : Sets the LCD drive voltage bias ratio [1/65duty, 1/9 bias]
;
; Display rotation (connection cable is lower side = 0, upper side = 1)
#IF varexist("St7565_rotate")
  #IF St7565_rotate = 0
    .db &HA0				; ( 8) ADC Select (Segment Driver Direction Select) [normal]
    .db &HC8				; (15) Common output mode select [Reverse 1/65duty COM63->COM0]
  #ELSE
    .db &HA1				; ( 8) ADC Select (Segment Driver Direction Select) [Reverse]
    .db &HC0				; (15) Common output mode select [Normal 1/65duty COM0->COM63]
  #ENDIF
#ELSE
  .db &HA0				; ( 8) ADC Select (Segment Driver Direction Select) [normal]
  .db &HC8				; (15) Common output mode select [Reverse 1/65duty COM63->COM0]
#ENDIF
;
  .db &H26				; (17) V0 voltage regulator internal resistor ratio set [6.0]
  .db &H81 , 30	 			; (18) Electronic volume mode set , Electronic volume register set [_contrast = Contrast Value , 00-63]
  .db &H2C				; (16) Power control set [Booster circuit: ON]
  .db &H2E				; (16) Power control [Voltage regulator circuit: ON]
  .db &H2F				; (16) Power control [Voltage follower circuit: ON]
  .db &HA4                              ; (10) Display all points ON/OFF [normal display]
  .db &H40				; ( 2) Display start line set [00]
  .db &HA6				; ( 9) Display normal reverse [normal]
  .db &HAF                         	; ( 1) Display ON/OFF [Display ON]

;------------------------------------------------------------------------------ 

_gwrite_cmd:
  push r23
* cbi _GLCD_PORT_A0,_glcd_a0		; command
  Rjmp _Gwrite_DataMisc			; same code

_gwrite_data:
  push r23
* sbi _GLCD_PORT_A0,_glcd_a0		; data

_gwrite_datamisc:
* cbi _GLCD_PORT_CS1,_glcd_cs1		; enable chip
  ldi r25,8				; 8 bits
_gwrite_datamisc8:
  lsl r24				; bit into carry
  brcc _gwrite_datamisc_0		; bit was 0
* sbi  _GLCD_PORT_SI, _glcd_si		; set bit
  rjmp _gwrite_datamisc_next

_gwrite_datamisc_0:
* cbi  _GLCD_PORT_SI, _glcd_si          ; clear bit
  nop
_gwrite_datamisc_next:
* cbi _GLCD_PORT_SCL,_glcd_scl		; generate clock
  @genus(1)
* sbi _GLCD_PORT_SCL,_glcd_scl
  @genus(1)
  dec r25
  brne _gwrite_datamisc8		; do 8 bits

* sbi _GLCD_PORT_CS1,_glcd_cs1		; disable chip
  pop r23
  Ret

'==============================================================================

; There is no difference between clear text and clear graphics!

_Clear_graph:				; Clear all displays
_Clear_text:
  Ldi R31,&HB0	                  	; ( 3) Set Page Address 
_Clear_graph1:
  Mov R24,R31
  Rcall _Gwrite_cmd			; Send command
  Ldi R24,&H00                  	; ( 4) Set Column Address [LSB]
  Rcall _Gwrite_cmd			; Send command
  Ldi R24,&H10                  	; ( 4) Set Column Address [MSB]
  Rcall _Gwrite_cmd			; Send command
;
  Ldi R30,132				; Column length
_Clear_graph2:
  Clr R24				; Data for clear
  Rcall _Gwrite_data			; Send data
  Dec R30
  Brne _Clear_graph2			; Finished clearing the column?
;
  Inc R31
  Cpi R31,&HB8
  Brne _Clear_graph1			; Finished clearing the row?
  Ret


; Character in r24
; in order to draw multiple fontsizes the fontdata stores the
; number of bytes and the number of rows.
;  .db 1,8,8,0  , means 1 row, 8 bits width, block size total, last param is spacing for true type fonts

_gwrite_lcdchar:
$EXTERNAL _LPMBYTE, _MUL8, _GLOCATE
  Push r30				; save registers
  Push r31
  lds r21,{___lcdrow}			; the row
  push r21				; save it
  lds r7,{___lcdcol}
  Lds r30 ,{___fonttable}		; start of font data
  Lds r31 ,{___fonttable +1}
#IF _ROMSIZE>65536
* In R3,RAMPZ				; save RAMPZ page
* Lds r0,{___fonttable +2}		; load page number for the used font
* Out RampZ,r0				; set page number
#ENDIF
  Call _lpmbyte				; Get Y Bytes 1 For Example
  mov r18,r0				; save Y
  call _lpmbyte				; get X bytes , 8 for  an 8*8 font
  mov r19,r0				; save X
  call _lpmbyte				; get blocksize
  mov r16,r0				; in accu
  call _lpmbyte				; get blocksize
  mov r22,r0				; spacing, should be 1 or 2

  subi r24,32				; chars start at 32 (space)
  mov r20,r24
  call _mul8				; get offset

  Add r30,r20				; add to start of font data
  Adc r31,r21
#IF _ROMSIZE>65536
  rcall _glcd_rampcheck_inc 		; check boundery
#ENDIF

  ;--------optional true type option ----
  clr r12
  ;we need to determine the left space of the character so that we can skip these zeros
  ;we store the left in r15
  clr r1				; we need a reg with zero
  mov r14,r18				; Y bytes
  cpi r22,0				; test for 0
  breq _gwrite_lcdchar_rows		; it is 0 so skip this option
  clr r13
  dec r13				; put 255 into r13
  mov r12,r13				; and also in r12
_truetypey:
  clr r15
  mov r16,r19				; x bits
_trueTypeX:
  call _lpmbyte 			; get data in r0
  breq _trueType1			; it is zero so skip
  dec r16				; adjust
  Add r30,r16				; skip the rest of the bytes
  Adc r31,r1				; add zero
#IF _ROMSIZE>65536
  rcall _glcd_rampcheck_inc		; check boundery
#ENDIF
  rjmp  _truetype2			; skip code
_truetype1:
  inc r15				; inc zero bytes counter
  dec r16
brne _trueTypeX				; check the other x bytes
_truetype2:
  cp r15,r13				; check the previous with the new value
  brsh _truetype3			; if r15 same or higher then we skip
  mov r13,r15				;save the smallest in r13
_truetype3:
  dec r14				; next row of bytes
  brne _trueTypeY			; check again for Y
  ;now r13 contains the number of left bytes to skip
  ;we now check the same in reverse so we get the rgt space and the Z pointe is at the right pos
  mov r14,r18				; Y bytes
_truetypeyr:
  clr r15
  mov r16,r19				; x bits
_truetypexr:
  sbiw r30,1				; dec Z pointer
#IF _ROMSIZE>65536
  rcall _glcd_rampcheck_dec		; check boundery
#ENDIF
  lpm					; get byte
  tst r0				; test for zero
  breq _truetype1R
  dec r16
  sub r30,r16
  sbc r31,r1
#IF _ROMSIZE>65536
  rcall _glcd_rampcheck_dec		; check boundery
#ENDIF
  rjmp _truetype2R
_truetype1r:
  inc r15
  dec r16
  brne _truetypeXR
_truetype2r:
  cp r15,r12
  brsh _truetype3R
  mov r12,r15
_truetype3r:
  dec r14
  brne _truetypeYR
  ;now R12 contains right space
  cp r13,r19				; check if left is empty char
  brne  _gwrite_lcdchar_rows		; no so continue
  mov r13,r22				; use defined spaces since a space would be all zero
  clr r12				; clear right space otherwise it mess up
_gwrite_lcdchar_rows:
  sts {___lcdcol},r7 			; restore column
  mov r23,r19				; x bits 8 12 or 16
  sub r23,r12				; minus right space

_gwrite_lcdchar1:
  lds r20,{___lcdcol}
  lds r21,{___lcdrow}
  call _Glocate				; set right position
  cpi r22,0				; test for 0
  breq  _gwrite_lcdchar2		; normal font
  tst r13				; check spaces
  breq  _gwrite_lcdchar2		; if zero then skip
  mov r21,r13				; skips
_gwrite_lcdcharSkips:
  call _lpmbyte				; get byte
  dec r21
  brne _gwrite_lcdcharSkips
  sub r23,r13				; adjust x bits
_gwrite_lcdchar2:
  call _lpmbyte				; get byte
  mov r24,r0
  lds r21,{___LCDrev}			; reverse display?
  CPI R21,$01				; Optional number = Invert?  <<Edit underline mode>>
  BRNE _gwrite_lcdchar3
  com r24				; not 0 so complement
_gwrite_lcdchar3:
  RCALL _gwrite_underline		; write char data  <<Edit underline mode>>
  lds r21,{___lcdcol}			; increase column
  inc r21
  sts {___lcdcol},r21
  dec r23				; dec bits dones
  brne _gwrite_lcdchar2			; write all X bytes

  add r30,r12				; move z pointer with right space so we keep the proper pointer value
  adc r31,r1				; r1 was 0
#IF _ROMSIZE>65536
  rcall _glcd_rampcheck_inc		; check boundery
#ENDIF
  ;now add some space
  cpi r22,0				; is it true type?
  breq _gwrite_lcdchar5			; no so exit
  lds r21,{___LCDrev}			; reverse display?
  mov r23,r22				; right space columns
_gwrite_rightspace:
  clr r24
  CPI R21,$01				; Optional number = Invert?  <<Edit underline mode>> 
  BRNE _gwrite_rightspace2		; skip if normal
  com r24				; inverse
_gwrite_rightspace2:
  RCALL _gwrite_underline 		; write char data  <<Edit underline mode>>
  LDS R24,{___lcdcol}			; increase column  <<Edit underline mode>>
  INC R24
  STS {___lcdcol},R24
  dec r23
  brne _gwrite_rightspace
_gwrite_lcdchar5:
  lds r21,{___lcdrow}			; inc row
  inc r21
  sts {___lcdrow},r21
  dec r18				; dec rows
  breq _gwrite_lcdchar4			; all done so skip code
  rjmp _gwrite_lcdchar_rows		; for all rows

_gwrite_lcdchar4:
  pop r21				; get original row back
  sts {___lcdrow},r21			; restore

#IF _ROMSIZE>65536
* OUT RAMPZ, R3 			; restore RAMPZ for next character
#ENDIF

  Pop R31
  Pop R30
  Ret


_gwrite_underline:			; <<Underline check>> "option is 2:Solid , 3:dotted"
  CPI R18,$01
  BRNE _gwrite_underline1		; lowest row?
  LDS R21,{___LCDrev}
  CPI R21,$02
  BRCS _gwrite_underline1		; not Underline?
  BREQ _gwrite_underline2		; Solid Underline?
  LDS R21,{___lcdcol}
  SBRS R21,0				; dotted Underline
  RJMP _gwrite_underline1		; x position odd?
  ;
_gwrite_underline2:
  ORI R24,$80
_gwrite_underline1:
  CALL _GWrite_data			; write char data
  RET


#IF _ROMSIZE>65536
_glcd_rampcheck_inc:
  Brcc _glcd_rampcheck_inc1		; no boundery
  In R2,RAMPZ
  Inc R2
  Out RAMPZ,R2
_glcd_rampcheck_inc1:
  Ret

_glcd_rampcheck_dec:
  Brcc _glcd_rampcheck_dec1		; no boundery
  In R2,RAMPZ
  Dec R2
  Out RAMPZ,R2
_glcd_rampcheck_dec1:
  Ret
#ENDIF

[END]


[_GLOCATE]
; called with R21 row  R20 col
_glocate:
  Dec r20				; adjust
  Dec r21				; adjust
_setcol:
#IF varexist("ST7565_rotate")
  #IF ST7565_rotate = 1
    ldi r24,4 				; column starts at 4 >>> Added by MWS <<<
    add r20,r24				; >>> Added by MWS <<<
  #ENDIF
#ENDIF
  ldi r24,&B10110000
  add r24,r21
  rcall _GWrite_cmd 			; page address or row

  mov r24,r20
  swap r24
  andi r24,$F
  sbr r24,16
  rcall _GWrite_cmd			; column address
  mov r24,r20
  andi r24,$F
  rjmp _GWrite_cmd			; column address
[END]


; r22 - x2
; r20- x1
; r21 - y

[_CLS_LINE]
$EXTERNAL  _GLOCATE
_Cls_Line:
#IF varexist("ST7565_rotate")
  #IF ST7565_rotate = 1
    ldi r24,4				; column starts at 4 
    add r22,r24                  
  #ENDIF
#ENDIF
  rcall _glocate  ; select right location
_Cls_Line_loop:
  lds r24,{___LCDrev}			;read value to use from reverse
  rcall _gwrite_data			; clear
;  inc r21				>>> Edit by MWS <<<
;  cp r21,r22				>>> Edit by MWS <<<
  inc r20				;>>> Added by MWS <<<
  cp r20,r22				;>>> Added by MWS <<<
  brlo _Cls_Line_loop
  Ret
[END]


[_getbytefromrom_eprom]
$EXTERNAL _READEEPROM, _LPMBYTE
; get a byte from code ROM or from EEPROM
;you could write code to get the data from an external EEPROM
_getbytefromrom:
  Tst R23                          	; is it 0 ?
  Breq _GetByteFromROM1            	; yes get from flash
#IF _XMEGA
  loadadr _XMEGAREG,X
  call _ReadEEPROM1                
  Subi r31,&H10   
  Ret
#ELSE
  Clr R26                          	; point to R0
  Clr R27
  jmp _ReadEEPROM1                	; get data in r0
#ENDIF

_getbytefromrom1:
  jmp _LpmByte                    	; returns data in r0
[END]


[_showpicture]
$EXTERNAL _DIV8 , _MUL8 , _GETBYTEFROMROM_EPROM , _GLOCATE
; RLE encoded. When the data after the byte is AA, it means it is a repeating
; sequence and the byte after it hold the number of repeats
; AB, AC , AF,AA, EE means show AB,AC and AF EE times
; needs more code but needs less picture data!
; show a picture with ShowPic x,y , label
; y+2 holds X, y+1 holds Y , y+0 holds EPROM/ROM flag, Z points to picture data
; The calling code uses for example
; clr r20
; clr r21
; st -y,r20      this way it will be referred to with  y+4
; st -y,r21                   and                      y+3

; ldi zl,low(plaatje * 2)        to point to picture data
; ldi zh,high(plaatje * 2)
; in the SED data is stored column after column with 8 rows of data
_showpicture:
  CLR R1				; repeats
  CLR R2				; char to repeat
  clr r3				; temp register
  ld r23,y+				; 255 means get from EEPROM, 0 means get from flash
  Call _getbytefromrom			; Get Height Of Picture In Pixels
  Mov r16,r0
  Ldi r20,8
  Call _Div8				; divide by 8
  St -y,r16				; y+2  number of rows to do

  Call _getbytefromrom			; Get Width Of Picture In Pixels
  st -y,r0				; number of cols into  y+1
  st -y,r0				; number of cols into  y+0

  ld r16,y+3				; get Y pos
  Ldi R20 , 8
  Call _div8				; Correct
  st y+3,r16				; save row
_showpicture1:
  ld r20,y+4				; get X
mov r15,r20
  ld r21,y+3				; get Y
  Rcall _setcol				; set address
_showpicture2:
  ; *** New RLE encoding routine ***
  cp r1,r3				; is repeats 0 ?
  brne _ShowPicture8			; no
  Call _getbytefromrom			; Get Next Char
  mov r2,r0				; save char
  mov r24,r0
  cpi r24,&HAA				; test for repeat char must always be shown
  breq _ShowPicture9 			; we need to show AA
  Call _getbytefromrom			; Is It Rle print
  mov r24,r0
  cpi r24,&HAA				; is it a RLE encoded char?
  breq _ShowPicture10			; yes
  sbiw R30,1				; no readjust pointer to graphic data
  rjmp _ShowPicture11			; show it

_showpicture9:
  adiw r30,1  ; skip 0
  rjmp _ShowPicture11
_showpicture8:
  ;we are repeating
  dec r1				; adjust repeat counter
  breq _showpicture2
  rjmp _ShowPicture11			; show it
_showpicture10:
  Call _getbytefromrom			; Get Number Of Repeats
  Mov r24,r0
  Tst r24				; is it zero?
  Brne _showpicture15			; no a real sequenc
  Sbiw r30,2				; adjust pointer
  Rjmp _showpicture11			; skip somebytes
_showpicture15:
  mov r1,r0				; save number of repeats
_showpicture11:
  mov r24,r2
; *** end of RLE code ***

;--------------
  lds r25,{___LCDrev} 			; reverse display?
  tst r25
  breq _showpicture12
  com r24
_showpicture12:
;---------------

  push r24
  mov r20,r15
  ldd r21,y+3
  Rcall _setcol				; set address
  pop r24

  rcall _Gwrite_Data

  inc r15
  ld r24,y+1 				; get column counter
  dec r24
  st y+1,r24				; save col counter
  brne _ShowPicture2			; for all columns

  ld r24,y+3				; next page
  inc r24
  st y+3,r24

  ld r24,y+0 				; get num of cols
  st y+1,r24				; save for the new row

  ld r24,y+2				; get row counter
  dec r24
  st y+2,r24
  brne _showpicture1			; for all rows

  adiw r28,5				; adjust soft stack
  Ret

[END]
