Ein solches Modul können wir direkt an die Buchsenleiste unserer Attiny-Platine anschließen (vgl. Abb.). Das Modul steht dann zwar auf dem Kopf, aber es lässt sich so initialisieren, dass die Bildausgabe ein aufrechtes Bild erzeugt. VCC verbinden wir mit dem 3,3-V-Anschluss neben dem USB-COM-Wandler, GND schließen wir an Masse an, SCL und SDA werden mit PortB.7 bzw. PortB.5 verbunden. Auf dem Modul befinden sich schon passende Pull-Up-Widerstände; deswegen können wir auf die I2C-Pull-Up-Jumper bei unserer Attiny-Platine verzichten.
Auf diesem OLED-Modul werden wir unser Spannungsdiagramm anzeigen. Zusätzlich benötigen wir noch einen AD-Wandler; dazu soll ein PCF8591-Modul zum Einsatz kommen, wie es hier schon beschrieben worden ist.
Nun zur Programmierung: Das SSD1306-Modul ist recht komplex; das zugehörige Manual umfasst 65 Seiten. Für den arduino gibt es passende Libraries, ebenso für den BASCOM-Compiler. Leider funktioniert die BASCOM-Library "glcdSSD1306-I2C.lib" nicht mit der frei erhältlichen Demo-Version von BASCOM. Außerdem belegen diese Libraries mehr Speicherplatz, als der Attiny2313 zur Verfügung hat. Deswegen habe ich für mein Mini-Oszi-Programm auf die grundlegenden I2C-Befehle zurückgegriffen. Diese findet man im Manual auf den Seiten 28ff.
Wie üblich muss der Baustein zunächst adressiert werden; die Schreibadresse lautet bei meinem Modul: &H78. Danach erwartet der Baustein Kontroll-Bytes oder Daten-Bytes; erstere stellen Befehle und zugehörige Parameter dar, letztere sind die eigentlichen Bilddaten und werden direkt in das Graphik-RAM geschrieben. Wie kann das Modul nun diese beiden Typen unterscheiden? Dazu wertet es die ersten beiden Bits (von links aus gezählt) aus, vgl. Manual S. 20:
Das erste Bit (MSB) ist das sogenannte Co-Bit; hat dieses continuation-Bit den Wert 1, so erwartet das Modul nur noch ein einziges Byte. Hat das Bit den Wert 0, so erwartet es einen ganzen Datenstrom.
Das nächste Bit (MSB-1) ist das sogenannte D/C#-Bit; hat dieses data/command-select-Bit den Wert 1, wird / werden das nächste Byte / die nächsten Bytes als Daten-Bytes gedeutet, ansonsten als Kontroll-Bytes.
Beispiele:
Der Befehl &H 00 = &B 0000 0000 zeigt dem SSD1306, dass ein Strom von Kontroll-Bytes folgt.
Der Befehl &H 81 = &B 1000 0001 (Einstellung des Kontrastes) hat das Co-Bit 1 und das D/C#-Bit 0; das Modul erwartet demnach genau ein weiteres Byte, und zwar ein Kontroll-Byte. In diesem Fall ist es der Kontrast-Wert.
Der Befehl &H 40 = &B 0100 0000 zeigt dem SSD1306, dass es sich bei folgenden Bytes um Daten-Bytes handelt, die in den Grafik-RAM geschrieben werden sollen.
Byte-Ströme werden durch einen I2c-Stop-Befehl beendet.
Zu Beginn muss das Display initialisiert werden. Glücklicherweise findet man auf der vorletzten Seite des Manuals (S. 64) eine Beispiel-Sequenz für die Initialisierung.
Diese Sequenz musste ich nur in einigen wenigen Punkten modifizieren, u. A. um das Bild vertikal zu spiegeln. Im Quellcode (s. u.) findet man die einzelnen Kommandos auch stichwortartig kommentiert. Diese Kommentare stammen zum Teil aus Programmen von I. Dobson und Sonal Pinto.
Wie spricht man nun die einzelnen Pixel des Displays an? Dazu greift man auf das Grafik-RAM des Displays zurück. Dieses ist Byte-weise strukturiert: Jeweils 1 Byte entsprechen 8 senkrecht untereinander angeordneten Pixel. Der Pixelwert 1 entsprecht einem leuchtenden Punkt auf dem Display. In der folgenden Abbildung sind die Pixel eines solchen Bytes hell braun gefärbt. Die Position eines solchen Sprites ist horizontal durch den Segment-Wert gekennzeichnet und vertikal durch den Page-Wert. Ein einziges Pixel wird damit durch die Angabe dreier Werte bestimmt: den Segment-Wert, den Page-Wert und die Bit-Nummer. Soll das 2-Pixel-Muster aus der Abbildung angezeigt werden, dann muss das Byte &B 0100 0100 = &H 44 in die Page 3 des Segments 14 geschrieben werden. Dazu übermittelt man dem Modul zunächst Page-Wert und Segmentwert für das Sprite und dann dessen Wert (vgl. Source-Code).
Will man z. B. das Display löschen, muss das komplette Grafik-RAM mit Nullen gefüllt werden. In diesem Fall muss dem Modul lediglich zu Beginn die Startposition (Segemnet0, Page0) übertragen werden; das Modul inkrementiert den Sprite-Zeiger automatisch, jedesmal wenn ein Sprite-Wert übertragen worden ist.
Code: Alles auswählen
' Datei für Attiny-Platine von E. Eube, G. Heinrichs und U. Ihlefeldt
' Über die zu zugehörige Konfigurationsdatei werden automatisch Voreinstellungen
' für die Kompilierung übernommen; diese betreffen z. B. die Taktfrequenz, die
' Baudrate, die Anschlüsse von LCD, I2C- und SPI-Modulen.
' Programm nach I. Dobson und Sonal Pinto
' OLED display 128x64 using SSD1306 0.96"
' PCF8591-Modul Typ Y0027
' SCL -> B.7
' SDA -> B.5
' Ta0: Messintervall kleiner, d. h. Aufzeichnung schneller;
' Maximale Schnelligkeit: ca. 1 Durchlauf (128 Messsungen) in ca. 210 ms
' Ta1: Messintervall größer
' Ta0 und Ta1 gleichzeitig betätigt: Messung stoppen
' Reset-Taster: Neu-Start
'----------------------------------------------------------------------------
$regfile = "attiny2313.dat" 'Dadurch wird auch das Pin Layout bestimmt
'**********************************************************
'******************* Deklarationen ************************
Declare Sub Zeichne_punkt
Declare Sub Init_oled
Declare Sub Loeschen_oled
Declare Function Analogwert_von_pcf_modul() As Byte
Dim X As Byte
Dim X0 As Byte
Dim X1 As Byte
Dim Y As Byte
Dim Y0 As Byte
Dim Y1 As Byte
Dim Punkt As Byte
Dim I As Word
Dim Kontrast As Byte
Dim Send As Byte
Dim Write_adresse_pcf As Byte
Dim Read_adresse_pcf As Byte
Dim K As Byte
Dim Messwert As Byte
Dim Messintervall As Byte
Dim Ende As Byte
Dim Write_adresse_oled As Byte
'**********************************************************
'****************** Initialisierung ***********************
Ddrb = &B11111111 'Port B als Ausgangsport
Ddrd = &B01110000 'D4, D5, D6 als Ausgang; Rest als Eingang
Portd = &B10001111 'Eingänge auf high legen
Config I2cdelay = 1 'I2C-Taktung möglichst rasch
Enable Int0 'vgl. BASCOM-Hilfe...
Config Int0 = Falling
Enable Int1 'Achtung: T1 ist im Gegensatz zu T0 nicht mit einem Kondensator entprellt
Config Int1 = Falling
Enable Interrupts
On Int0 Schneller
On Int1 Langsamer
Kontrast = 200 'hoher Kontrast
Write_adresse_pcf = 144 'PCF8591
Read_adresse_pcf = 145
Write_adresse_oled = &H78 'OLED SSD1306
Messintervall = 20 'Wartezeit nach jeder Messung (in ms)
Waitms 50 'warte bis Kondensator bei Ta0 geladen
'**********************************************************
'******************** Hauptprogramm ***********************
Call Init_oled
Call Loeschen_oled
Do
For X = 0 To 127
Disable Interrupts 'Ausführen der Interruptroutine nicht während einer Messung
Y = Analogwert_von_pcf_modul()
Y = Y / 4 'Analogwert [0..255] -> Anzeigewert [0..63]
Call Zeichne_punkt
Waitms Messintervall
Ende = Pind.2 + Pind.3 'Ende = 0, wenn Ta0 und Ta1 gleichzeitig betätigt werden
If Ende = 0 Then X = 127 'und dann ggf. Schleifenabbruch
Enable Interrupts
Next X
If Ende > 0 Then Call Loeschen_oled
Loop Until Ende = 0
End
'**********************************************************
'******************** Unterprogramme **********************
Sub Init_oled
'(
I2cstart 'OLED da?
I2cwbyte Write_adresse_oled
If Err = 0 Then Portd.6 = 1 'LED bei D.6 an, wenn OLED gefunden
I2cstop
Waitms 200
Portd.6 = 0
')
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H00 'Command Stream nötig
I2cwbyte &HAE 'DISPLAYOFF
I2cwbyte &HD5 'SETDISPLAYCLOCKDIV
I2cwbyte &H80 'ratio 0x80
I2cwbyte &HA8 'Set MUX
I2cwbyte &H3F '&H1F for 128x32; &H3F for 128x64
I2cwbyte &HD3 'SETDISPLAYOFFSET
I2cwbyte &H00
I2cwbyte &H40 'SETSTARTLINE
I2cwbyte &H8D 'CHARGEPUMP
I2cwbyte &H14 'vccstate 14 for chargepump
I2cwbyte &H20 'MEMORYMODE
I2cwbyte &H00 'horizontal addr mode
I2cwbyte &HA0 'SEGREMAP links-rechts A0/A1
I2cwbyte &HC8 'COMSCANDEC (&HC0 vert. gespieg.)
I2cwbyte &HDA 'SETCOMPINS
I2cwbyte &H12 '&H12 for 64 rows
I2cwbyte &H81 'SETCONTRAST
I2cwbyte Kontrast 'value 1-->256
I2cwbyte &HD9 'SETPRECHARGE
I2cwbyte &HF1 'vccstate F1
I2cwbyte &HDB 'SETVCOMDETECT
I2cwbyte &H30 '&H30 -> 0.83*VCC;
I2cwbyte &HA4 'DISPLAYALLON_RESUME
I2cwbyte &HA6 'NORMALDISPLAY
I2cwbyte &HAF 'Display on
I2cstop
End Sub
Sub Zeichne_punkt
' x zwischen 0 und 127
' y zwischen 0 und 63
Y0 = Y Mod 8
Y1 = Y / 8
Y1 = Y1 + &HB0
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H80 'Single Command
I2cwbyte Y1 'Page (Y) &HB0 = Col0 entspricht Zeile
I2cstop
X0 = X Mod 16
X1 = X / 16
X1 = X1 + &H10
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H00 'command stream
I2cwbyte X0 'Spalte x Low Nibble
I2cwbyte X1 'Spalte x High Nibble
I2cstop
Punkt = 0
Punkt.y0 = 1
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &HC0 '1 Datum
I2cwbyte Punkt 'Sprite aus 7 inaktiven und einem aktiven Pixel: vertikal z. B. 00000100
I2cstop
End Sub
Sub Loeschen_oled
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H80 'Single Command
I2cwbyte &HB0 'Page (Y) &HB0 = Col0
I2cstop
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H00 'command stream
I2cwbyte &H00 'Select start (X) column 0
I2cwbyte &H10
I2cstop
I2cstart
I2cwbyte Write_adresse_oled
I2cwbyte &H40 'Datenstrom für das RAM
For I = 0 To 1023
I2cwbyte 0
Next I
I2cstop
End Sub
Function Analogwert_von_pcf_modul() As Byte
' knr=0 -> ldr, knr=3 -> poti, knr=1 -> temperatur (Modul-Typ YL-04))
' knr 1 -> poti, knr 3 -> ldr, knr 2 -> temperatur (Modul-Typ Mini: Y0027; Aktoren u. Sensoren in einer Reihe; große LED)
K = 3 'LDR bei Y0027
I2cstart
I2cwbyte Write_adresse_pcf
I2cwbyte K
Waitus 10
I2cstop
Waitus 10
' Messung
I2cstart
I2cwbyte Read_adresse_pcf
I2crbyte Messwert , Nack
I2cstop
Waitus 10
Analogwert_von_pcf_modul = Messwert
End Function
'**********************************************************
'******************Interruptroutinen***********************
Schneller: 'Sprung-Marke, kein Subroutine-Name
If Messintervall > 1 Then Messintervall = Messintervall - 2 'Messintervall nicht unter 0
Return
Langsamer:
If Messintervall < 250 Then Messintervall = Messintervall + 10
' Waitms 10
Return
'**********************************************************
' Bemerkung: Da T1 nicht entprellt ist, wird durch eine einzige Betätigung von
' T1 ggf. mehrfach ein int1-Interrupt ausgelöst. Durch wenige Tastendrücke bei T1
' ist das Lauflicht auf Minimaltempo gebracht.
' Dem Prellen kann z. B. durch eine Pause (ca. 10 ms) in der int1-Interrupt-Routine
' begegnet werden.
.