Mini-Oszi mit OLED SSD1306 und PCF8591-Modul

Hier können Sie ihre eigenen Projekte vorstellen
Antworten
Heinrichs
Beiträge: 185
Registriert: Do 21. Okt 2010, 18:31

Mini-Oszi mit OLED SSD1306 und PCF8591-Modul

Beitrag von Heinrichs » Do 1. Dez 2016, 14:14

Für ca. 4 bis 10 Euro kann man OLED-Displays mit einer Diagonalen von ca. 1 Zoll und 128*64 Pixeln erwerben. Sie benötigen eine Spannungsversorgung von 3,3 V - 5,0 V und werden über I2C, manchmal auch zusätzlich über SPI, angesteuert.

foto_oled.jpg
OLED SSD1306
foto_oled.jpg (13.99 KiB) 30729 mal betrachtet

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.

initialisierung_flussdiagramm.jpg
Beispielsequenz für die Initialisierung
initialisierung_flussdiagramm.jpg (28.67 KiB) 30729 mal betrachtet

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).

grafik_ram_klein.jpg
Grafik-RAM
grafik_ram_klein.jpg (22.55 KiB) 30716 mal betrachtet

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.
Das Programm erfasst die Spannungswerte an einem der Eingänge des PCF-Module (hier Kanal 3) und stellt sie grafisch auf dem Display an. Die Zeitbasis kann dabei mir den Tastern Ta0 und Ta1 verändert werden. Drückt man beide Taster gleichzeitig, wird die Messung angehalten. Mit dem Reset-Taster kann dann eine neue Messung eingeleitet werden. Im Anhang findet man ein Video dazu.

foto_oszi1.jpg
Mini-Oszi
foto_oszi1.jpg (32.83 KiB) 30729 mal betrachtet

Attiny_Oszi.wmv
Video vom Mini-Oszi
(2.75 MiB) 3071-mal heruntergeladen
.
Zuletzt geändert von Heinrichs am Mi 11. Dez 2019, 10:39, insgesamt 11-mal geändert.

Heinrichs
Beiträge: 185
Registriert: Do 21. Okt 2010, 18:31

Re: Mini-Oszi mit OLED SSD1306 und PCF8591-Modul

Beitrag von Heinrichs » Di 20. Dez 2016, 12:54

Neben den OLEDs mit dem Treiber SSD1306 werden auch OLEDs angeboten, welche äußerlich von den SSD1306-OLEDs nicht zu unterscheiden sind (zumal da das eigentliche Display dasselbe ist), aber einen anderen Treiber besitzen, nämlich den Treiber SH1106. Dieser Treiber arbeitet ganz ähnlich wie der SSD1306, aber er besitzt ein RAM mit einer 132x64-Struktur. Das bedeutet, dass die Spaltenanzahl des Treibers nicht mit der Spaltenanzahl des Displays übereinstimmt! Will man die Spalte x des Displays ansteuern, muss man beim SH1106 den Index x+2 benutzen.

Auch bei der Autoinkrementierung gibt es einen Unterschied: Diese funktioniert beim SSD1306 PAGE-übergreifend, beim SH1106 aber nicht; hier muss jede PAGE neu adressiert werden. Am Beispiel des Löschens soll dies verdeutlicht werden:

Code: Alles auswählen

' ############################# oled_SSD1306 #########################################

Sub Loeschen_oled_SSD1306
  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

' ############################# oled_SH1106 #########################################

Sub Loeschen_oled_SSH1106
 For X0 = 0 To 7
  X1 = &HB0 + X0
  I2cstart
  I2cwbyte Write_adresse_oled
  I2cwbyte &H80                                             'Single Command
  I2cwbyte X1                                               'Page (Y) &HB0 = Col0
  I2cstop

  I2cstart
  I2cwbyte Write_adresse_oled
  I2cwbyte &H00                                             'command stream
  I2cwbyte &H02                                             'Select start column 0; offset 2
  I2cwbyte &H10
  I2cstop

  I2cstart
  I2cwbyte Write_adresse_oled
  I2cwbyte &H40                                             'Datenstrom für das RAM
  For X = 0 To 127
    I2cwbyte 0
  Next X
  I2cstop
 Next X0
End Sub
Im Handling ist das SH1106-OLED sicherlich etwas sperriger; dafür ist es aber - insbesondere in der 1.3"-Variante - deutlich preisgünstiger.

Antworten