; Datei: lcd-routines-bib.asm
; enthält die Routinen zum Steuern einer LCD-Einheit 
; an den unteren (LCD_data = lower_port)  oder oberen Porthälfte (LCD_data = upper_port)
; Die Steuerleitungen sind frei wählbar und werden auch über die Headerdatei 
; lcd-routines-bib_header.asm eingestellt
; ============================================================================
; =============================================================================
; interface
; ==============================================================================
;
;
; lcd_data (temp1) - ein Zeichen in Temp1 übergeben zur Ausgabe
; lcd_command (temp1) - einen Befehlscode in temp1 übergeben
; lcd_enable - Übernahmebefehl (strobe)
; lcd_enable2 - bei 4-zeiligem Display die unteren 2 Zeilen
; delay50us - 50 us Sekunden Pause machen
; delay5ms - 5 ms warten
; lcd_init - Display initialisieren (4Bit Betrieb)
; Powerupwait - Warten zum Betriebsstart, bis Spannung steht
; lcd_clear - Display löschen, Cursor home
; lcd_home - Cursor in Homeposition
; lcd_position - Cursor an Position Spalte (temp1) Zeile (temp2), 
; lcd_cursor - Display-Cursor-Blinken an/aus
;		Bit2 -> Display; Bit1 -> cursor; Bit0 -> Blinken
; lcd_flash_string - einen String aus dem Programmspeicher (Flash) ausgeben
;       der String ist 0-terminiert, das z-Register enthält die Textadresse (mal 2!)
;       ldi zl, low(textadresse*2)
;       ldi zh, high(textadresse*2)
; lcd_number (temp1) - Zahl in Register temp1 dezimal ausgeben
; lcd_number_hex (temp1) - Zahl in Register temp1 als Hexadezimalzahl ausgeben.
; lcd_binout - Binärzahl in akku b ausgeben, Binär-Stellenzahl in temp2
;		mit bin2asc nach buffer wandeln, Ziffern-Stellenzahl + 1 in temp2
; 		der bufferzeiger z steht auf der Null des String-Endes
; ==============================================================================
; interface ende
; ==============================================================================
 .cseg
 lcd_data:
 		   push  temp1
           push  temp2
           push  temp3
		   in 	 temp3, sreg			 ; Statusregister sichern
		   push  temp3					 ; 
           mov   temp2, temp1            ; "Sicherungskopie" für
                                         ; die Übertragung des 2.Nibbles
.if lc_data == upper_port
           andi  temp1, maskdata	     ; unteres Nibble auf Null setzen
		   sbi   rs_port, PIN_RS         ; RS-Leitung für Datentransfer auf 1
           in    temp3, LCD_PORT		 ; Portbyte einlesen
		   andi  temp3, maskport		 ; oberen Bereich nullen
           or    temp1, temp3			 ; Datennibble setzen
           out   LCD_PORT, temp1         ; ausgeben
		   rcall lcd_enable				 ; übernehmen
           swap  temp2                   ; Vertauschen
           andi  temp2, maskdata         ; untere Hälfte auf Null setzen 
           sbi   rs_port, PIN_RS         ; RS-Leitung für Datentransfer auf 1
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp2, temp3
           out   LCD_PORT, temp2         ; ausgeben
		   rcall lcd_enable
			                             ; 2. Nibble, kein swap da es schon
                                         ; an der richtigen stelle ist
										 ; Enable-Routine aufrufen
           rcall delay50us               ; Delay-Routine aufrufen
.endif ; upper_port

.if lc_data == lower_port
		   swap  temp1					 ; oberes Nibbel kommt zuerst -> tauschen
           andi  temp1, maskdata	     ; oberes Nibble auf Null setzen
		   sbi   rs_port, PIN_RS         ; RS-Leitung für Datentransfer auf 1
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp1, temp3			 ; Datennibble einoderieren
           out   LCD_PORT, temp1         ; ausgeben
		   rcall lcd_enable				 ; übernehmen
           sbi   rs_port, PIN_RS         ; RS-Leitung für Datentransfer auf 1
           andi  temp2, maskdata         ; obere Hälfte auf Null setzen 
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp2, temp3			 ;
           out   LCD_PORT, temp2         ; ausgeben
		   rcall lcd_enable
			                             ; 2. Nibble, kein swap da es schon
                                         ; an der richtigen stelle ist
										 ; Enable-Routine aufrufen
           rcall delay50us               ; Delay-Routine aufrufen
.endif ; lower_port

           pop   temp3
		   out   sreg,  temp3
		   pop   temp3
           pop   temp2
		   pop   temp1
           ret                           ; zurück zum Hauptprogramm
; +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     ;
lcd_command:                            ; wie lcd_data, nur mit RS = 0
           push  temp1					; nicht für die Befehle clear und home (1,67 ms!)
		   push  temp2					; Wartezeit am Schluss: 50 µs
           push  temp3
		   in temp3, sreg
		   push temp3
           mov   temp2, temp1            ; "Sicherungskopie" für
                                         ; die Übertragung des 2.Nibbles
.if lc_data == upper_port
           andi  temp1, maskdata	     ; unteres Nibble auf Null setzen
		   cbi   rs_port, PIN_RS        ; 
           in    temp3, LCD_PORT		 ; Portbyte einlesen
		   andi  temp3, maskport		 ; oberen Bereich nullen
           or    temp1, temp3			 ; Datennibble setzen
           out   LCD_PORT, temp1         ; ausgeben
		   rcall lcd_enable				 ; übernehmen
           swap  temp2                   ; Vertauschen
           andi  temp2, maskdata         ; untere Hälfte auf Null setzen 
		   cbi   rs_port, PIN_RS        ; 
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp2, temp3
           out   LCD_PORT, temp2         ; ausgeben
		   rcall lcd_enable
			                             ; 2. Nibble, kein swap da es schon
                                         ; an der richtigen stelle ist
										 ; Enable-Routine aufrufen
           rcall delay50us               ; Delay-Routine aufrufen
.endif ; upper_port

.if lc_data == lower_port
		   swap  temp1					 ; oberes Nibbel kommt zuerst -> tauschen
           andi  temp1, maskdata	     ; oberes Nibble auf Null setzen
		   cbi   rs_port, PIN_RS        ; 
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp1, temp3			 ; Datennibble einoderieren
           out   LCD_PORT, temp1         ; ausgeben
		   rcall lcd_enable				 ; übernehmen
		   cbi   rs_port, PIN_RS        ; 
           andi  temp2, maskdata         ; obere Hälfte auf Null setzen 
           in    temp3, LCD_PORT		 ; Datenport einlesen
		   andi  temp3, maskport		 ; lcd-Datenteil nullen
           or    temp2, temp3			 ;
           out   LCD_PORT, temp2         ; ausgeben
		   rcall lcd_enable
			                             ; 2. Nibble, kein swap da es schon
                                         ; an der richtigen stelle ist
										 ; Enable-Routine aufrufen
           rcall delay50us               ; Delay-Routine aufrufen
.endif ; lower_port


 		   pop temp3
		   out sreg, temp3
           pop   temp3
           pop   temp2
		   pop   temp1
           ret
 

  ; erzeugt den Enable-Puls
lcd_enable:
           sbi   e_PORT, PIN_E          ; Enable high
           nop                          ; 3 Taktzyklen warten
           nop
           nop
		;  rcall taste
           cbi   e_PORT, PIN_E          ; Enable wieder low
           ret  
		                           ; Und wieder zurück                     
		                   
; Pause nach jeder Übertragung
delay50us:                              ; 50us Pause
		   push temp1
		   in temp1, sreg
		   push temp1

           ldi   temp1, t50us
delay50us_:
		   nop
		   nop
           dec   temp1
           brne  delay50us_
		   pop temp1
		   out sreg, temp1
		   pop temp1
           ret                          ; wieder zurück
 
 ; Längere Pause für manche Befehle
delay5ms:                               ; 5ms Pause
		   push temp1
		   in temp1, sreg
		   push temp1
		   push temp2
           ldi  temp1, t5ms
		   rjmp wgloop0

delay2ms:  		   
		   push temp1
		   in temp1, sreg
		   push temp1
		   push temp2
           ldi  temp1, t5ms/3     		 ; ergibt 1,67 ms

WGLOOP0:   ldi  temp2, $C9
WGLOOP1:   dec  temp2
           brne WGLOOP1
           dec  temp1
           brne WGLOOP0

		   pop temp2
		   pop temp1
		   out sreg, temp1
		   pop temp1
           ret                          ; wieder zurück
 
 ; Initialisierung: muss ganz am Anfang des Programms aufgerufen werden
lcd_init:
           push  temp1
		   push  temp2
		   in    temp2, sreg
		   push  temp3

           in    temp1, LCD_DDR
           ori   temp1, maskdata		; Ausgangspins gemäß Datenmaske setzen
           out   LCD_DDR, temp1			; Datenleitungen auf Ausgang
		   sbi	 rs_ddr, pin_rs			; RS-Leitung aus Ausgang
		   sbi   e_ddr, pin_e			; E-Leitung auf Ausgang
           ldi   temp3,6				; 6 mal 5 ms = 30 ms warten vorbereiten
		   cbi   rs_port, PIN_RS        ; Es folgen Befehle
		   cbi	 e_port, pin_e 			; Leitung E auf 0
powerupwait:
           rcall delay5ms				; 30 ms warten
           dec   temp3
           brne  powerupwait

.if lc_data == lower_port		   			; 
           ldi   temp1,    0b00000011   ; muss 3mal hintereinander gesendet
.endif

.if lc_data == upper_port
		   ldi   temp1,    0b00110000
.endif
		   in    temp3, lcd_port
		   andi  temp3, maskport
		   or    temp1, temp3
           out   LCD_PORT, temp1        ; werden zur Initialisierung

           rcall lcd_enable             ; 1
           rcall delay5ms
           rcall lcd_enable             ; 2
           rcall delay5ms
           rcall lcd_enable             ; und 3mal!
           rcall delay5ms
.if lc_data == lower_port		   			; 
           ldi   temp1,    0b00000010   
.endif

.if lc_data == upper_port
		   ldi   temp1,    0b00100000
.endif
		   in    temp3, lcd_port
		   andi  temp3, maskport
		   or    temp1, temp3
           out   LCD_PORT, temp1        ; werden zur Initialisierung
           rcall lcd_enable
           rcall delay5ms				; 4bit-Modus einstellen
           ldi   temp1,    0b00101100   ; 4 Bit, 2 Zeilen, 5x11
           rcall lcd_command
           ldi   temp1,    0b00001111   ; Display on, Cursor on, Cursor blinkt
           rcall lcd_command
           ldi   temp1,    0b00000100   ; endlich fertig
           rcall lcd_command
		
		   pop   temp3
		   out   sreg, temp2
		   pop   temp2
           pop   temp1
           ret
 

 ; Sendet den Befehl zur Löschung des Displays
lcd_clear:
           push  temp1
           ldi   temp1,    0b00000001   ; Display löschen
           rcall lcd_command
           rcall delay2ms
           pop   temp1
           ret

 ; Cursor Home
lcd_home:
           push  temp1
           ldi   temp1,    0b00000010   ; Cursor Home
           rcall lcd_command
           rcall delay2ms
           pop   temp1
           ret

 ; Cursor an/aus
 ; Bit2 -> Display; Bit1 -> cursor; Bit0 -> Blinken
lcd_cursor:
		push 	temp1
		andi 	temp1, 0x07
		ori 	temp1, onoff_control
		rcall 	lcd_command
		rcall 	delay50us
		pop 	temp1
		ret

 ; Cursor an Position Spalte (temp1) Zeile (temp2), 
lcd_position:
		push 	temp1
		push 	temp2
		andi 	temp1, 0b00011111
		andi 	temp2, 0x01
		sbrc 	temp2, 0
		ldi 	temp2, (1<<zeile)
		or  	temp1, temp2
		sbr 	temp1, 0b10000000
		rcall 	lcd_command
		pop 	temp2
		pop 	temp1
		ret

 ; Einen konstanten Text aus dem Flash Speicher
 ; ausgeben. Der Text wird mit einer 0 beendet
lcd_flash_string:
           push  temp1
		   in 	 temp1,sreg
		   push  temp1					; Register sichern

lcd_flash_string_1:
           lpm   temp1, Z+				; Byte von der Position, die in ZL:ZH adressiertist
		   cpi   temp1, 0				; nach temp1 einlesen und auf Null-Byte vergleichen
           breq  lcd_flash_string_2		; Falls Nullbyte, dann fertig
           rcall lcd_data				; sonst auf LCD ausgeben
           rjmp  lcd_flash_string_1		; und noch einmal

lcd_flash_string_2:
		   pop   temp1					; Register restaurieren
		   out 	 sreg, temp1
           pop   temp1
           ret


.ifdef arithmetik_asm
; eine Zahl aus dem Register temp1 hexadezimal ausgeben
lcd_number_hex:
        push  temp1			; sichern
		push  temp2
		rcall ascbyte
		rcall lcd_data
		mov temp1, temp2
		rcall lcd_data
		pop   temp2
		pop   temp1
		ret
.endif
		

.ifndef lcd_number
	; ein Byte als Dezimalzahl auf LCD oder via RS232 ausgeben
	; Datenbyte: temp1 
lcd_number:
           push  temp1
           push  temp2
           push  temp3
		   in    temp3, sreg
		   push  temp3

		   ldi 	 temp2, low(lcd_data) 	  ; Adresse von Befehl "lcd_data" als
		   mov   zl, temp2 				  ; Sprungadresse für indirekten SUB-Call
		   ldi   temp2, high(lcd_data)	  ; in Z-Register ablegen
		   mov   zh, temp2
		   rjmp  v24_number_0

v24_number:
           push  temp1
           push  temp2
           push  temp3
		   in    temp3, sreg
		   push  temp3

		   ldi 	 temp2, low(serout) 	  ; Adresse von Befehl "lcd_data" als
		   mov   zl, temp2 				  ; Sprungadresse für indirekten SUB-Call
		   ldi   temp2, high(serout)	  ; in Z-Register ablegen
		   mov   zh, temp2

v24_number_0:
           mov   temp2, temp1
                                  		  ; abzählen wieviele Hunderter
                                          ; in der Zahl enthalten sind
;		   rjmp  v24_number_2a
           ldi   temp1, '0'
v24_number_1:
           subi  temp2, 100
           brcs  v24_number_2
           inc   temp1
           rjmp  v24_number_1
                                          ;
                                          ; die Hunderterstelle ausgeben
v24_number_2:
		   mov   temp3, temp1			  ; wert von temp1 merken
;		   cpi   temp1, '0'
;		   breq  v24_number_2a			  ; führende Null nicht ausgeben
           icall 
v24_number_2a:
           subi  temp2, -100              ; 100 wieder dazuzählen, da die
                                          ; vorherhgehende Schleife 100 zuviel
                                          ; abgezogen hat

                                          ; abzählen wieviele Zehner in
                            			  ; der Zahl enthalten sind
           ldi   temp1, '0'
v24_number_3:
           subi  temp2, 10
           brcs  v24_number_4
           inc   temp1
           rjmp  v24_number_3

                                          ; die Zehnerstelle ausgeben
v24_number_4:
           icall 
           subi  temp2, -10               ; 10 wieder dazuzählen, da die
                                          ; vorhergehende Schleife 10 zuviel
                                          ; abgezogen hat

                                          ; die übrig gebliebenen Einer
                                          ; noch ausgeben
           ldi   temp1, '0'
           add   temp1, temp2
           icall 
		   
		   pop   temp3
		   out   sreg, temp3
           pop   temp3
           pop   temp2
           pop   temp1
           ret
.endif


.ifdef arithmetik_asm
	; Binärzahl in akku b ausgeben, Binär-Stellenzahl in temp2
	; mit bin2asc nach buffer wandeln, Ziffern-Stellenzahl + 1 in temp2
	; der bufferzeiger z steht auf der Null des String-Endes
lcd_binout:
	ldi temp2, 24
	rcall bin2asc
lcd_bin2asc_0:
	ld temp1, -z
	rcall lcd_data
	dec temp2
	brne lcd_bin2asc_0
	ret
.endif	

