;LCD-Routinen für Text-Punktmatrix-LCD ;mit Controller HD44780. 2 Zeilen je 16 bis 40 Zeichen ;Version mit frei definierbaren Steuerleitungen. ;Diese Routinensammlung baut auf Beispielen von Andreas Schwarz ;und T.S. auf und ist von Hannes Lux zusammengestellt worden. ; hannes.lux@gmx.de / www.hanneslux.de ;============================================================================ ;Diese Sammlung enthält folgende Routinen: ;---------------------------------------------------------------------------- ; lcd_init ;Initialisiert und löscht das LCD ;---------------------------------------------------------------------------- ; lcd_clear ;Löscht das LCD, aber nicht den Bildschirmspeicher ;(wird nur bei lcd_init gebraucht) ;---------------------------------------------------------------------------- ; lcd_command ;Übergibt das Byte im Register "tmp" als Kommando ;an das LCD (wird nur intern gebraucht) ;---------------------------------------------------------------------------- ; lcd_data ;Übergibt das Byte (ASCII-Zeichen) im Register "tmp" ;zur Textausgabe. Dabei sind folgende Steuerzeichen ;möglich: ;1: Blinken ausschalten ;2: akt. Ausgabe-Position als Blinkanfang setzen ;3: akt. Ausgabe-Position als Blinkende setzen ;4: Bildschirm löschen ;8: Cursor home (ohne Löschen) ;13: CR, Zeilenwechsel, Rest der Zeile wird gelöscht ;---------------------------------------------------------------------------- ; lcd_update: ;Sendet ein Zeichen aus dem Bildschirmspeicher an das ;LCD. Muss von Haupschleife alle 1..10ms aufgerufen ;werden ;---------------------------------------------------------------------------- ; locate ;Macro zum Einstellen der Ausgabeposition. ;Aufruf: locate Zeile,Spalte ;Zeile: 0-1, Spalte: 0-39 ;---------------------------------------------------------------------------- ; blink Zeile,Spalte,Anzahl ;Markiert die Blinkposition im VLCD auf ;Zeile (0...3), Spalte (0...23) und Anzahl blinkender ;Zeichen. ;----------------------------------------------------------------------------- ; print konst8 ;Gibt 1 Zeichen auf dem LCD aus. Das Zeichen wird als ;Konstante (Byte) angegeben. ;============================================================================ ;Die Datei "print.inc" stellt einige Ausgaberoutinen zur Verfügung, die ;die Ausgabe von Texten und Zahlen erleichtert. Zusammen mit "locate" wird ;die Ausgabe von Menütexten, Zahlen usw. ähnlich einfach wie in BASIC. ;============================================================================ ;Im Hauptprogramm müssen folgende Speicherbereiche, Register und Konstanten ;definiert werden: ;Konstanten: clock ;Controllertakt ; zln ;Anzahl Zeilen des LCDs (2) ; spn ;Anzahl Spalten des LCDs (16, 20, 24, 40) ;SRAM: vlcd: .byte zln*spn ;Bildschirmspeicher ; rpos: .byte 1 ;Leseposition VLCD ; wpos: .byte 1 ;Schreibposition VLCD ; blinka: .byte 1 ;Blinkbereich Anfang ; blinke: .byte 1 ;Blinkbereich Ende ;Register: bliz (u) ;Blinkzähler ; tmp (o) ;Datenübergabe, wird nicht gesichert! ; vflags (o) ;Flags für LCD, Bits werden hier definiert ; wh (o) ;working h, temporär, wird gesichert ; zh:zl (o) ;temporär, werden gesichert ;Die eizelnen Bits in 'vflags' werden hier deklariert: .equ zeiwe=0 ;Flag für Zeilenwechsel .equ update=1 ;Flag für Update LCD .equ upsync=2 ;Synchronisationsflag vom Timer-Interrupt ; ;============================================================================ ;Anschlussbelegung: ;Die 4 Datenleitungen sollten in einem Nibble liegen. ;Dies kann ein unteres oder oberes Nibble sein. ;Die Auswahl geschieht mit "lcddatmsk" ;Die Steuerleitungen des LCD können über alle ;verfügbaren Ports frei gewählt werden, was diesen ;Treiber sehr flexibel macht. ;---------------------------------------------------------------------------- .equ lcdport=portd ;Port, an dem die 4 Datenleitungen des LCD ;angeschlossen sind. .equ lcddatmsk=$0f ;Maske auf LCD-Datenpins. ;$0f - LCD ist an Bit 0...3 des Ports angeschlossen. ;$f0 - LCD ist an Bit 4...7 des Ports angeschlossen. ;---------------------------------------------------------------------------- .equ rsport=portd ;Port, an dem RS angeschlossen ist .equ rs=4 ;Pin, an dem RS angeschlossen ist ;---------------------------------------------------------------------------- .equ enableport=portd ;Port Anschluss Enable des ersten Controllers. .equ enable=5 ;Pin Anschluss Enable des ersten Controllers. ;---------------------------------------------------------------------------- ; hier nicht genutzt... ;.equ rwport=portd ;Port, an dem R/W angeschlossen ist ;.equ rw=2 ;Pin, an dem R/W angeschlossen ist ;============================================================================ ;============================================================================= ; locate Zeile,Spalte ;Setzt die Ausgabeposition im VLCD auf ;Zeile (0...3) und Spalte (0...26). ;----------------------------------------------------------------------------- .macro locate ;Zeile (0...3), Spalte (0...39) ;Positionierung der Ausgabeposition ldi tmp,((@0 & 1)*spn)+(@1 & 63)+low(vlcd) ;Position im VLCD errechnen sts wpos,tmp ;und einstellen .endmacro ;---------------------------------------------------------------------------- ; blink Zeile,Spalte,Anzahl ;Markiert die Blinkposition im VLCD auf ;Zeile (0...3), Spalte (0...23) und Anzahl blinkender ;Zeichen. ;----------------------------------------------------------------------------- .macro blink ;Zeile (0...3), Spalte (0...39), Anzahl Blinkzeichen ;Positionierung der Blinkposition ldi tmp,((@0 & 1)*spn)+(@1 & 63)+low(vlcd) ;Startposition im VLCD errechnen sts blinka,tmp ;und einstellen subi tmp,-@2 ;Endposition im VLCD sts blinke,tmp ;einstellen .endmacro ;----------------------------------------------------------------------------- ; print konst8 ;Gibt 1 Zeichen auf dem LCD aus. Das Zeichen wird als ;Konstante (Byte) angegeben. ;----------------------------------------------------------------------------- .macro print ;Konstante als Byte ;Gibt ein Zeichen (Konstante) an LCD aus ldi tmp,@0 ;Das Zeichen als Konstante rcall lcd_data ;an LCD ausgeben .endmacro ;============================================================================ ; Aufrufbare Routine, initialisiert und löscht das LCD ;---------------------------------------------------------------------------- lcd_init: ;LCD-Initialisierung push zl push zh ldi zl,low(vlcd) ;Z-Pointer ldi zh,high(vlcd) ;auf VLCD sts rpos,zl ;VLCD auf Anfang des sts wpos,zl ;Bildschirmspeichers setzen sts blinka,zl ;Blinkpointer sts blinke,zl ;auch ldi tmp,' ' ;Leerzeichen lcd_init1: st z+,tmp ;ins VLCD cpi zl,low(vlcd+zln*spn) ;Ende VLCD erreicht? brne lcd_init1 ;nein, nochmal... in tmp,lcdport-1 ;Datenrichtung LCD-Datenport holen sbr tmp,lcddatmsk ;benutzte Datenpins als Ausgang out lcdport-1,tmp ;definieren sbi rsport-1,rs ;RS-Leitung als Ausgang sbi enableport-1,enable ;Enable1-Leitung als Ausgang ; ldi zl,50 ;>15ms warten, nachdem Vcc > 4,5V ist ldi zl,20 ;>15ms warten, nachdem Vcc > 4,5V ist powerupwait: ;hier 250ms warten, dafür keine Spannungsprüfung rcall delay5ms dec zl brne powerupwait cbi rsport,rs ;L-Pegel legen ;Startsequenz in tmp,lcdport ;ganzen Port einlesen cbr tmp,lcddatmsk ;Platz für Datenbits löschen ldi wh,0b00110011 ;benötigte Datenbits in beide Nibbles andi wh,lcddatmsk ;nur das genutzte Nibble or tmp,wh ;übernehmen out lcdport,tmp ;und wieder ausgeben ;dreimal ausgeben rcall lcd_impuls ;1. Ausgabe (von 3) rcall delay5ms ;>4,1ms warten rcall lcd_impuls ;2 rcall delay5ms ;>0,1ms warten rcall lcd_impuls ;und 3! rcall delay5ms ;Ab hier BUSY prüfen ;4-Bit-Modus aktivieren in tmp,lcdport ;ganzen Port einlesen cbr tmp,lcddatmsk ;Platz für Datenbits löschen ldi wh,0b00100010 ;benötigte Datenbits in beide Nibbles andi wh,lcddatmsk ;nur das genutzte Nibble or tmp,wh ;übernehmen out lcdport,tmp ;und wieder ausgeben rcall lcd_impuls rcall delay5ms ;ab jetzt 4-Bit-Zugriff mit Busy-Abfrage ldi tmp,0b00101000 ;4bit-Modus einstellen rcall lcd_command rcall delay5ms ldi tmp,0b00001100 ;Display ein, Cursor aus rcall lcd_command rcall delay5ms ldi tmp,0b00000100 ;Entry-Mode rcall lcd_command rcall delay5ms pop zh pop zl ;(Code geht weiter...) ;============================================================================ ; Aufrufbare Routine, löscht das LCD ;---------------------------------------------------------------------------- lcd_clear: ;Sendet den Befehl zur Löschung des Displays ldi tmp,0b00000001 ;Display löschen rcall lcd_command rcall delay5ms ret ;============================================================================ ; Aufrufbare Routine, sendet den Befehl in tmp an das LCD ;---------------------------------------------------------------------------- lcd_command: ;sendet den Befehl in tmp an das LCD push wh ;für Nibble-Wahl ldi wh,lcddatmsk ;Referenz für Nibblewahl push tmp ;Commandobyte sichern sbrc wh,0 ;wenn LCD am unteren Nibble angeschlossen ist swap tmp ;dann oberes Nibble nach unten cbi rsport,rs ;RS löschen rcall lcd_nibble ;erstes Nibble ausgeben... pop tmp ;Kopie vom Commandobyte holen sbrc wh,4 ;wenn LCD am oberen Nibble angeschlossen ist swap tmp ;dann unteres Nibble nach oben pop wh ;wieder frei cbi rsport,rs ;RS löschen rjmp lcd_nibble ;zweites Nibble ausgeben und zurück... ;============================================================================ ; Aufrufbare Sub-Routine, legt das Zeichen in tmp in das VLCD ;---------------------------------------------------------------------------- lcd_data: ;UP, schreibt Zeichen in tmp in virtuellen Bildschirm push zh push zl ldi zh,high(vlcd) ;Z-Pointer auf lds zl,wpos ;Ausgabeposition cpi tmp,32 ;Zeichen unter Chr$(32)? brmi lcd_stz ;ja, als Steuerzeichen behandeln... st z+,tmp ;nein, Zeichen ins VLCD schreiben cpi zl,low(vlcd+zln*spn) ;letztes Zeichen erreicht? brne lcd_data1 ;nein... lcd_data0: ldi zl,low(vlcd) ;ja, Pointer auf Anfang des Bildschirmspeichers lcd_data1: sts wpos,zl ;Pointer wieder sichern rjmp lcd_data_end ;fertig... lcd_stz: ;in tmp ist ein Steuerzeichen cpi tmp,13 ;Zeilenumbruch? brne lcd_stz1 ;nein... ldi tmp,' ' ;Leerzeichen lcd_stz0: ;Loop CR st z+,tmp ;Leerzeichen in Bildschirmspeicher schreiben cpi zl,low(vlcd+spn*1) ;letztes Zeichen der Zeile erreicht? breq lcd_data1 ;ja, Pointer sichern und fertig... cpi zl,low(vlcd+spn*2) ;letztes Zeichen der Zeile erreicht? breq lcd_data0 ;ja, Pointer korregieren, sichern und fertig... rjmp lcd_stz0 ;nochmal... lcd_stz1: ;kein Zeilenumbruch cpi tmp,4 ;CLS? brne lcd_stz_cls ;nein... ldi zl,low(vlcd) ;Pointer auf ldi zh,high(vlcd) ;Beginn Bildschirmspeicher sts wpos,zl ;Ausgabeposition, sts blinka,zl ;Blinkanfang, sts blinke,zl ;und Blinkende sichern ldi tmp,32 ;Leerzeichen lcd_stz_cls0: ;Loop CLS st z+,tmp ;Leerzeichen in Bildschirmspeicher schreiben cpi zl,low(vlcd+spn*zln) ;letztes Zeichen der letzten Zeile erreicht? brne lcd_stz_cls0 ;nein, nochmal... rjmp lcd_data_end ;fertig... lcd_stz_cls: ;Ende CLS cpi tmp,1 ;Blinken aus? brne lcd_stz_bls ;nein... ldi zl,low(vlcd) ;ja, Beginn Bildschirmspeicher sts blinka,zl ;als Blinkanfang sts blinke,zl ;und Blinkende sichern lcd_stz_bls: ;Ende Blinken aus cpi tmp,2 ;Blinkanfang? brne lcd_stz_bla ;nein... lds zl,wpos ;ja, aktuelle Ausgabeposition dec zl ; sts blinka,zl ;als Blinkanfang sichern lcd_stz_bla: ;Ende Blinkanfang setzen cpi tmp,3 ;Blinkende? brne lcd_stz_ble ;nein... lds zl,wpos ;ja, aktuelle Ausgabeposition dec zl ; sts blinke,zl ;als Blinkende sichern lcd_stz_ble: ;Ende Blinkende setzen cpi tmp,8 ;Cursor Home? brne lcd_stz_home ;nein... ldi zl,low(vlcd) ;ja, Anfang Bildschirmspeicher sts wpos,zl ;Ausgabeposition auf Anfang lcd_stz_home: ;Ende Cursor home lcd_stz_stz2: ;noch keine weiteren Kommandos implementiert lcd_data_end: sbr vflags,1<