Option Explicit

'LCD-Ansteuerung von Christof Rueß, www.hobby-elektronik.de.vu
'Die Verwendung dieses Moduls ist kostenfrei, solange diese Zeilen erhalten bleiben.
'Fragen, Kritik und Anregungen an: hobbyelektronik@gmx.net

'Deklaration für die Portansteuerung (auch für Win2k & WinXP)
Public Declare Sub PortOut32 Lib "inpout32.dll" Alias "Out32" (ByVal PortAddress As Integer, ByVal Value As Integer)
Public Declare Function PortIn32 Lib "inpout32.dll" Alias "Inp32" (ByVal PortAddress As Integer) As Integer

'Hier können die Werte für die verschiedenen Zustände der Ausgänge eingetragen werden.
'Falls das Display direkt, also ohne die invertierende Treiberstufe betrieben wird,
'müssen SCLlo mit SCLhi und SDAlo mit SDAhi getauscht werden.
Const SCLlo = 1 'Ausgabe am Port für SCL wenn low
Const SCLhi = 0 'Ausgabe am Port für SCL wenn high
Const SDAlo = 2 'Ausgabe am Port für SDA wenn low
Const SDAhi = 0 'Ausgabe am Port für SDA wenn high
Const StaticOut = 0 'Statische Ausgabe am Port (evtl. für die Stromversorgung des LCDs oder Steuerung LEDs - _
                     kann durch eine Variable ersetzt werden.)

Private LcdPort As Long 'Variable für den Parallelport

'Enums für die Sonderzeichen am oberen Displayrand
Enum EnSpecialCharA
  scL2 = 1 'zweiter Strich hinter "FM"
  scRDS = 2 'RDS-Zeichen
  scL3 = 4 ' dritter Strich hinter "FM"
  stDP = 16 'Punkt hinter dem 6. Zeichen unten
  stTP = 32 'TP-Zeichen (Traffic Program)
  stTA = 64 'TA-Zeichen (Traffic Announcement)
  scST = 128 'Stereo-Zeichen
End Enum

Enum EnSpecialCharB
  sc1 = 1 '1 oben links
  sc3 = 2 '3 oben links
  sc2 = 4 '2 oben links
  sc4 = 8 '4 oben links
  scL1 = 16 'erster Strich hinter "FM"
  sc6 = 32 '1 oben links
  scFM = 64 'FM-Zeichen
  sc5 = 128 '5 oben links
End Enum

'Type für die Blinkfrequenz
Enum EnBlinkFrequ
  bOff = 0 'kein blinken
  b2Hz = 1 '2 Hz
  b1Hz = 2 '1 Hz
  bhHz = 3 '0.5 Hz
End Enum

'Hiermit wird der Port, an dem das LCD angschlossen ist festgelegt
Property Let Port(ByVal NewPort As Long)
LcdPort = NewPort
End Property

'Damit der Port auch gelesen werden kann, ist dies hier notwendig:
Property Get Port() As Long
Port = LcdPort
End Property

'binären String in dezimalen Long umwandeln
Function BintoDez(ByVal Bin As String) As Long
  Dim X&, y&
    Bin = Bin & String$(8 - Len(Bin), "0")
    For X = 1 To Len(Bin)
      If Mid$(Bin, X, 1) = "1" Then
        y = y + 2 ^ (8 - X)
      End If
    Next X
    BintoDez = y
End Function

'dezimal -> binär
Function DeztoBin(ByVal Dez As Long) As String
  Dim X%
   If Dez >= 2 ^ 32 Then
     Call MsgBox("Zahl ist größer als 32 Bit")
     Exit Function
   End If
   Do
     If (Dez And 2 ^ X) Then
       DeztoBin = "1" & DeztoBin
     Else
       DeztoBin = "0" & DeztoBin
     End If
     X = X + 1
   Loop Until 2 ^ X > Dez
   DeztoBin = Format(DeztoBin, "00000000")
End Function

'Startbit senden
Private Function I2C_Startbit()
PortOut32 LcdPort, SCLhi + SDAhi + StaticOut
PortOut32 LcdPort, SCLhi + SDAlo + StaticOut
PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
End Function

'Stoppbit senden
Private Function I2C_Stoppbit()
PortOut32 LcdPort, SCLhi + SDAlo + StaticOut
PortOut32 LcdPort, SCLhi + SDAlo + StaticOut
PortOut32 LcdPort, SCLhi + SDAhi + StaticOut
End Function

'Ein Byte an das Display senden
Function I2C_WriteByte(ByVal I2C_Data As Long)
Dim BinData As String
Dim X

BinData = DeztoBin(I2C_Data) 'dezimale Daten in binäre umwandeln

For X = 1 To 8
  If Mid(BinData, X, 1) = "1" Then 'Jedes Bit abfragen
    PortOut32 LcdPort, SCLlo + SDAhi + StaticOut
    PortOut32 LcdPort, SCLhi + SDAhi + StaticOut
    PortOut32 LcdPort, SCLlo + SDAhi + StaticOut
  Else
    PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
    PortOut32 LcdPort, SCLhi + SDAlo + StaticOut
    PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
  End If
Next

'Byte abschließen
PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
PortOut32 LcdPort, SCLhi + SDAlo + StaticOut
PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
'Noch ein ACK rausjagen
PortOut32 LcdPort, SCLlo + SDAlo + StaticOut
End Function

'Display initialisieren
Function Init(Optional ByVal HighContrast As Boolean = False)
Dim X
I2C_Startbit 'Startbit ausgeben
I2C_WriteByte 112 ' Adresse des LCDs
I2C_WriteByte 224 '11100000
  'Device select (weitere Befehle folgen, Device 0)
I2C_WriteByte 200 + IIf(HighContrast = True, 4, 0) '11001000
  'Mode set (weitere Befehle folgen, Normaler Modus, Lcd Enabled, 1/3 bias, 1:4 MUX)
I2C_WriteByte 240 '11110000 'blinken abstellen

I2C_WriteByte 0 'Cursor auf Null setzen

'gesamten Displayinhalt löschen
For X = 0 To 18
  I2C_WriteByte 0
Next

I2C_Stoppbit 'Stoppbit ausgeben
End Function

'Sonderzeichen in der oberen Zeile des Displays an- und abschalten
Function SpecialChars(Optional ByVal SPchar1 As EnSpecialCharA, Optional ByVal SPchar2 As EnSpecialCharB)
I2C_Startbit 'Startbit ausgeben
I2C_WriteByte 112 ' Adresse des LCDs
I2C_WriteByte 224 '11100000
  'Device select (weitere Befehle folgen, Device 0)

I2C_WriteByte 32 'Cursor auf "obere Zeile" setzen - Position * 4
I2C_WriteByte SPchar1 'ersten Teil schreiben
I2C_WriteByte SPchar2 'zweiten Teil schreiben

I2C_Stoppbit
End Function

'Display blinken lassen
Function Blink(ByVal BlinkFrequ As EnBlinkFrequ)
I2C_Startbit 'Startbit ausgeben
I2C_WriteByte 112 ' Adresse des LCDs
I2C_WriteByte 224 '11100000
  'Device select (weitere Befehle folgen, Device 0)
I2C_WriteByte 240 + BlinkFrequ 'blinken einstellen
I2C_Stoppbit
End Function

'Text an das Display ausgeben
Function WriteText(ByVal Text As String)
Dim X
Dim ByteA As Long
Dim ByteB As Long
I2C_Startbit
I2C_WriteByte 112 ' Adresse des LCDs
I2C_WriteByte 224
  'Device select (weitere Befehle folgen, Device 0)
I2C_WriteByte 0 'Cursor auf erstes Zeichen setzen
Text = Left(Text, 8) 'Murx!
Text = Text & Space(8 - Len(Text))
For X = 1 To 8 'Text parsen
  Select Case UCase(Mid(Text, X, 1))
    'die Zusammensetzung der Zeichen ist etwas eigenartig:
    'Der linke Teil des Zeichens wird in ByteA gespeichert, der rechte in ByteB.
    'Mehr Informationen dazu auf der oben genannten Homepage.
    Case " ": ByteA = 0: ByteB = 0
    Case "A": ByteA = 184: ByteB = 141
    Case "B": ByteA = 13: ByteB = 189
    Case "C": ByteA = 153: ByteB = 144
    Case "D": ByteA = 13: ByteB = 185
    Case "E": ByteA = 185: ByteB = 144
    Case "F": ByteA = 184: ByteB = 128
    Case "G": ByteA = 153: ByteB = 149
    Case "H": ByteA = 176: ByteB = 13
    Case "I": ByteA = 13: ByteB = 176
    Case "J": ByteA = 17: ByteB = 25
    Case "K": ByteA = 176: ByteB = 7
    Case "L": ByteA = 145: ByteB = 16
    Case "M": ByteA = 146: ByteB = 11
    Case "N": ByteA = 146: ByteB = 73
    Case "O": ByteA = 153: ByteB = 153
    Case "P": ByteA = 184: ByteB = 140
    Case "Q": ByteA = 153: ByteB = 217
    Case "R": ByteA = 184: ByteB = 204
    Case "S": ByteA = 169: ByteB = 149
    Case "T": ByteA = 12: ByteB = 160
    Case "U": ByteA = 145: ByteB = 25
    Case "V": ByteA = 208: ByteB = 2
    Case "W": ByteA = 208: ByteB = 73
    Case "X": ByteA = 66: ByteB = 66
    Case "Y": ByteA = 164: ByteB = 12
    Case "Z": ByteA = 73: ByteB = 146
    Case "0": ByteA = 217: ByteB = 155
    Case "1": ByteA = 0: ByteB = 11
    Case "2": ByteA = 57: ByteB = 156
    Case "3": ByteA = 9: ByteB = 157
    Case "4": ByteA = 160: ByteB = 13
    Case "5": ByteA = 169: ByteB = 208
    Case "6": ByteA = 185: ByteB = 149
    Case "7": ByteA = 8: ByteB = 137
    Case "8": ByteA = 185: ByteB = 157
    Case "9": ByteA = 169: ByteB = 157
    Case "°": ByteA = 168: ByteB = 140
    Case "!": ByteA = 11: ByteB = 146
    Case """": ByteA = 128: ByteB = 32
    Case "$": ByteA = 173: ByteB = 181
    Case "%": ByteA = 192: ByteB = 3
    Case "&": ByteA = 185: ByteB = 212
    Case "/": ByteA = 64: ByteB = 2
    Case "\": ByteA = 2: ByteB = 64
    Case "(": ByteA = 153: ByteB = 0
    Case ")": ByteA = 0: ByteB = 153
    Case "=": ByteA = 33: ByteB = 20
    Case "?": ByteA = 72: ByteB = 156
    Case "@": ByteA = 57: ByteB = 157
    Case "Ü": ByteA = 25: ByteB = 145
    Case "Ö": ByteA = 57: ByteB = 149
    Case "Ä": ByteA = 56: ByteB = 133
    Case "*": ByteA = 102: ByteB = 102
    Case "+": ByteA = 36: ByteB = 36
    Case "-": ByteA = 32: ByteB = 4
    Case "~": ByteA = 136: ByteB = 44
    Case "_": ByteA = 1: ByteB = 16
    Case ".": ByteA = 1: ByteB = 0
    Case ",": ByteA = 96: ByteB = 0
    Case "µ": ByteA = 176: ByteB = 12
    Case "<": ByteA = 0: ByteB = 66
    Case ">": ByteA = 66: ByteB = 0
    Case "|": ByteA = 4: ByteB = 32
    Case "^": ByteA = 130: ByteB = 0
    Case "}": ByteA = 13: ByteB = 36
    Case "{": ByteA = 36: ByteB = 176
    Case ":": ByteA = 33: ByteB = 0
    Case ";": ByteA = 104: ByteB = 0
    Case "§": ByteA = 255: ByteB = 255
      'Für den Paragraph gibt es kein sinnvolles Zeichen, deshalb wird er als Testzeichen missbraucht.
    Case Else: ByteA = 0: ByteB = 0
  End Select
  I2C_WriteByte ByteA 'erstes Byte ausgeben
  I2C_WriteByte ByteB 'zweites Byte ausgeben
Next
I2C_Stoppbit
End Function