;****************************************************************************
;* *
;* LCD Controller für Dualscan 640x480 LCD *
;* *
;* Paralleler Datenbus *
;* *
;* Version 0.2 *
;* *
;****************************************************************************
;* *
;* Einfacher LCD Controller für ein SW 640x480 LCD, wie es in vielen alten *
;* Notebooks verbaut ist. *
;* Außer einem mega8515, einem 74HC32, dem 74x573 Adresslatch im Datenbus, *
;* und einem 64kB SRAM (mit 55ns oder schneller) werden keine weiteren *
;* Bauteile benötig. Neue Daten werden über ein paralleles Interface mit *
;* bis zu 100kByte (mit einem einfachen QBasic Programm schaffe ich rund *
;* 50kByte/s) in den Speicher geladen. *
;* Aufgrund der hohen Datenrate des Parallelports und der LCD Ausgang, *
;* läuft der AVR mit 18,432MHz und steuert das LCD mit rund 60Hz an. *
;* Betreibt man den AVR mit 22,1184MHz kann man auf 70-75Hz erreichen. *
;* *
;* Der Datentransfer über den Parallelport behindert die Datenausgabe auf *
;* dem LCD nicht, man sieht also kein Flackern oder ähnliches. *
;* *
;* Um den Datentransfer zwischen LCD-Speicer und Ansteuerung-Speicher zu *
;* steuern gbt es 3 Möglichkeiten: *
;* a) Ansteuerung hat Priorität (z.B. bi SED1330). Sobals CS\ Low wird, *
;* wird der Datentransfer zum LCD unterbrochen und die Pixel dunkel *
;* geschaltet. Dies hat den Nachteil, dass das LCD bei vielen Daten *
;* deutlich sichtbar flackert, aber den Vorteil, dass man ohne ein Busy *
;* zu überprüfen, Daten senden kann. *
;* b) Ansteuerung des LCD hat Priorität (z.B. T6963). Hier muss man vor *
;* jedem Datentransfer das Busy Flag überprüfen, was die Übertragung *
;* ausbremst, aber eine saubere Anzeige liefert. *
;* c) Einen ausreichend schneller Speicher verwenden, und den LCD *
;* Controller mit dem Bustakt synchronisieren (z.B. S1D13704/5). *
;* Bei meiner Ansteuerung habe ich einen Mittelweg gewählt. An sich hat *
;* die Ansteuerung Priorität gegenüber der LCD Ausgabe, die quasi im *
;* Hintergrund läuft, und immer wieder durch die Ansteuerung unterbrochen *
;* wird. Allerdings wird ab und zu der Busy Pin auch während der *
;* Datenübertragung des Controllers zum LCD gesetzt. Sollte man trotzdem *
;* Daten senden, ist es nicht weiter schlimm, solange der Controller noch *
;* genügend Zeit übrig hat, um die Daten ans LCD auszugeben. *
;* Sendet man dagegen dicht hintereinander zwei Datenwerte, noch ehe das *
;* das Busy vom ersten Wert gelöscht ist, geht der zweite warscheinlich *
;* verloren. *
;* Das ganze klingt im ersten Moment etwas merkwürdig, hat aber seine *
;* Gründe: Überprüft man das Busy Bit, stellt fest es ist gelöscht und *
;* wird, ehe man das Byte senden kann von einem Interrupt unterbrochen, *
;* dann kann es durchaus passieren, dass der Controller inziwschen Busy *
;* gesetzt hat, weil er Daten ans LCD ausgibt. Daher ist es nicht schlimm, *
;* wenn trotzdem Daten gesendet werden. *
;* *
;* Die Datenübertragung läuft über 3 Leitungen und den 8bit Datenbus: *
;* Write, Command/Data und Busy dienen zur Steuerung des Datenbusses *
;* Da das Interface Softwaremäßig gelöst wurde, muss man ein paar Details *
;* beachten: *
;* Bei der fallenden Flanke von Write werden nach etwa 0,3-1,5us die Daten *
;* gespeichert. Command/Data und die Daten müssen also solange stabil *
;* bleiben, bis diese Zeit vorüber ist, oder Busy auf High Pegel geht. *
;* Der Writeimpuls selber wird Hardwaremäßig abgefragt und muss mindestens *
;* 100ns lang sein. Alle Write Impulse die während der Ausführung eines *
;* Befehls ankommen, werden ignoriert (es ginge auch anderst, aber dies *
;* dient zur "Entprellung", da beim Betrieb am LPT sonst Störungen *
;* auftreten. Mit einen 1,5m Kabel habe ich keine Störungen, bei 2m ist es *
;* nahezu unbrauchbar. Auf jedenfall benötigen Write und Command/Data *
;* PullUps gegen +5V. Diese sollten etwa 400-1000 Ohm haben. *
;* *
;* Befehle: *
;* *
;* 001 Set Cursor. Gefolgt von zwei Parametern, nämlich der Zeilen *
;* und Spaltenadresse. Die Zeilenadresse liegt im Bereich 0-239, *
;* und die Spaltenadresse im Bereich 0-79 (plus 128, falls die *
;* Position in der unteren Displayhälfte liegt.) *
;* *
;* 002 Set PWM. Stellt das Tastverhältnis des PWM Ausgangs OC1B ein. *
;* Das Tastverhältnis folgt auf diesen Befehl als Parameter. *
;* *
;* 010 Clear Line. Dieser Befehl benötigt keinen Parameter und löscht *
;* die aktuelle Zeile. Danach wird der Cursor auf den Beginn der *
;* nächsten Zeile gesetzt. So kann man 480 mal diesen Befehl *
;* senden, um das ganze LCD zu löschen. Allerdings ist dieser *
;* Befehl sehr komplex, und benötigt daher einiges an Zeit. Sendet *
;* man also diesen Befehl öfters ohne Pause, dann kommt es zu *
;* einem kurzen Flackern des LCDs. *
;* *
;* 011 Clear LCD. Dieser Befehl löscht das LCD in extrem kurzer Zeit. *
;* Aber wie auch schon beim Clear Line Befehl, benötigt er einiges *
;* an Zeit um den kompletten Speicher zu löschen. Das LCD Timing *
;* wird zwar nicht ganz eingehalten, aber man merkt nichts davon, *
;* da ab und zu dummy Daten ans LCD gesendet werden. *
;* *
;* 192 Display Character. Mit diesem Befehl wird ein ASCII Zeichen *
;* mittels des interenen Character Generator dargestellt. Das *
;* gewünschte Zeichen wird als Parameter übergeben. Die *
;* Darstellung erfolgt als 8x8 Zeichen. Die Schriftart entspricht *
;* der auf einem PC im 640x480 Grafikmodus. *
;* *
;* Daten: Nach dem Senden eines Datenwertes wird dieser in den Speicher *
;* geschrieben, und der Adresszähler anschließend erhöht. *
;* Jedes Byte enthält 8 nebeneinanderliegende Pixel. Im SRAM *
;* werden dagegen 4 nebeneinander liegende Pixel aus der oberen, *
;* und 4 nebeneinander liegende Pixel aus der unteren Hälfte *
;* gespeichert. Das mach das Laden von Daten etwas komplizierter, *
;* vereinfacht und beschleunigt die Ausgabe aber extrem. *
;* *
;* IO Pins: *
;* *
;* PA0-7 Adr/Dat Daten/Low Adresse für den SRAM *
;* PB0-7 Daten 8 Bit Befehle/Daten für den Controller *
;* PC0-7 Adresse High Adresse für den SRAM *
;* PD0 C/D Command/Data: Auswahl Befehl (high) und Daten (low) *
;* PD1 Busy Status des Display Controllers: Busy=high, Ready=low *
;* PD2 WR\ Write Eingang. Ein Low Impuls läd die Daten *
;* PD3 EXSCL Aktivierung für XSCL. Dieser Pin aktiviert über ein *
;* 74HC02 den Schiebetakt für die Daten *
;* PD4 LP Latchpuls. Der Impuls läd die Daten aus dem Schiebe- *
;* register ins Display. Dient als HSync. *
;* PD5 FLM VSync für das LCD. Das LCD springt in die erste Zeile. *
;* PD6 VWR\ Write Enable für den SRAM. *
;* PD7 VRD\ Output Enable für den SRAM und XSCL über den 74HC32 *
;* für das LCD. Bei jedem Lesevorgang werden die Daten ins *
;* LCD geladen *
;* PE0 EnLCD Schaltet über einen PNP und einen NPN Transistor die *
;* LCD Spannung, damit im Falle eines Fehlers (z.B. Ausfall*
;* der +5V) das LCD nicht zerstört wird. *
;* PE1 ALE Adress Latch für Low Adressen des SRAMs *
;* PE2 PWM PWM Ausgang, z.B. für Displaykontrast (geht noch nicht) *
;* *
;****************************************************************************
.include "m8515def.inc" ;@18,432MHz
.def null = r0
.def eins = r1
.def voll = r2
.def regsave = r3
.def tempm = r4
.def L_COL = r21
.def H_COL = r22
.def COL = r23
.def TmpCount = r24
.def temp = r16
.def temp2 = r17
.def temp3 = r18
.def NextOp = r19 ;Zähler für Parameter eines Befehls
.def Flags = r20
.def XAdresseLow=r26 ;Writepointer Spalte
.def ZeileL =r27 ;Writepointer Zeile LSB
.def ReadAddressL=r28 ;Readpointer Spalte
.def ReadAddressH=r29 ;Readpointer Zeile
;PortB
; 8bit IO Data
;PortD
.equ CD =0 ;Command/Data
.equ Busy =1 ;Controller Busy
.equ WR =2 ;Write Data: min. 1us Low
.equ XSCL =3 ;Enable XSCL
.equ LP =4 ;HSync
.equ FLM =5 ;VSync
;PortE
.equ EnLCD =0 ;LCD Spannung ein/ausschalten
;Flags
.equ Frame =0 ;Warten bis 1/(60fps * 242 Zeilen) Sekunden vorbei ist, ehe die nächste Zeile ausgegeben wird.
.equ ZeileH =7 ;Writepointer Zeile MSB, entscheidet zwischen obere und untere Displayhälte, also zwischen High und Low Nibble eines RAM Bytes
.equ ZStart =4 ;High Adresses des Bildanfangs des externen SRAMs (um internen SRAM auszublenden)
;NextOPs
.equ SetCurL =1
.equ SetCurH =2
.equ SetPWMV =3
.equ CGChar =4
.equ YPos =100 ;Position des Logos
.equ XPos =100 ;Position des Logos
.org 0000
rjmp reset
.org INT0addr ;Interruptvektor für Daten-Empfang
in temp, pinb ;Daten von PORTB lesen
sbi portd, Busy ;Busy Pin setzen
in temp2, pind ;C/D von PORTD lesen
rjmp Write
.org OVF0addr ;Interruptvektor für Beginn von neuer Zeile (Display Timing)
sbrc Flags, Frame ;Wurde letze Zeile überhaupt ausgegeben, oder hat Datentransfer alles blockiert ?
sbi portd, Busy ;Wenn ja, dann Busy Pin setzen (ist zwar nicht unbedingt notwndig, verbesser aber das Display Timing=weniger Flimmern)
out Tcnt0, tempm ;Timerwert laden
sbr Flags, (1< Max ?
brne normal
clr XAdresseLow ;Spalten Adresse = 0
inc ZeileL ;Zeilen Adresse ++
cpi ZeileL, 120+ZStart ;Zeilen Adresse > Max ?
brne normal
ldi ZeileL, ZStart ;Zeile = Beginn XRAM
sbr Flags, (1< Max ?
brne normal
clr XAdresseLow ;Spalten Adresse = 0
inc ZeileL
cpi ZeileL, 120+ZStart ;Zeilen Adresse > Max ?
brne normal
ldi ZeileL, ZStart ;Zeile = Beginn XRAM
cbr Flags, (1< Max ?
brne normal2
clr XAdresseLow ;Spalten Adresse = 0
subi ZeileL, -8 ;Zeilen Adresse ++
cpi ZeileL, 120+ZStart ;Zeilen Adresse > Max ?
brlo normal2
ldi ZeileL, ZStart ;Zeile = Beginn XRAM
ldi temp, (1<Bit3 ? Dann im nächsten Byte lesen
inc XAdresseLow
andi temp2, 3 ;zu löschendes Bit (Bit 0-39
clr temp3
clc
inc temp2
ser temp3
pow:
rol temp3
dec temp2
brne pow ;temp3 enthält nun (1<