Displays gibt es ja wirklich viele. Doch dieses mal betrachten wir kein gewöhnliches Display sondern ein OLED-Display. Das winzige Display, das ich euch heute vorstellen möchte ist gerade mal 0,96′ groß und eignet sich somit sehr gut für sehr kleine Schaltungen die Informationen über eine HMI (Human Machine Interface) bereitstellen müssen / möchten. Die Platine inklusive vormontiertem OLED-Display hat die Maße von 23 x 27 mm. Angesprochen wird das Display via I2C-Bus und kann dabei auf der Rückseite über umzulötenden SMD-Widerstand von Slave-Adresse 0x78 (default) auf 0x7A geändert werden um einen eventuell vorhandenen Adressenkonflikt zu lösen. Wenn man ein klein wenig Geduld mitbringt, dass ist das Display schon für unter 5€ bei ebay zu haben. Das nachfolgende Foto zeigt das winzige Display, dass wirklich hervorragend ablesbar ist. Auf dem Foto kommt es zwar nicht so gut rüber, doch die Schrift ist weiß auf schwarzem Hintergrund (bei ebay gibt es unter anderem auch Varianten mit blauer Schrift).
Obwohl das Display so winzig ist verfügt es über eine beachtliche Auflösung von 128×64 Pixel. Dank der Ansteuerung via I2C-Schnittstelle benötigt das Display lediglich vier Anschlussleitungen. Der Betriebsspannunsgsbereich des Displays geht von 3,3V bis hoch zu zu 5V. Wer sich ein solches Display kaufen möchte findet diese am besten mit dem Suchbegriff “SSD1306” direkt bei ebay. Auf dem Foto oben seht ihr zwei Font-Größen (8 / 16 Pixel in der Höhe). Die Version mit 16 Pixeln in der Höhe ist zum Ablesen aus etwas größerer Entfernung ideal, während die kleine Minimalvariante mit 8 Pixeln in der Höhe lediglich aus unmittelbarer Nähe (etwa ab 30 cm) gut zu lesen ist.
Byteorientierung
Mit Hilfe der nachfolgenden Grafik solltet ihr die Orientierung der Bytes auf dem Display bzw. im Inneren des Speichers des Displaycontrollers etwas besser verstehen können. Somit wisst ihr genau in welcher Art und Weise Bytes in den Framebuffer geschrieben werden müssen um am Ende das gewünschte Ergebnis auf dem OLED-Display zu sehen 🙂 Die 64 Pixel in der Höhe werden hierbei auf acht Bytes aufgeteilt wovon sich jeweils das LSB oben und das MSB unten befindet. Anhand des Beispiels auf der Abbildung mit dem “A” würde das Byte ganz oben links in der Ecke also den Wert 0x7E
haben. Die nächsten Bytes (in fortlaufender X-Richtung) haben dann entsprechend die Werte: 0x11
, 0x11
, 0x11
, 0x7E
, 0x00
, 0x00
, 0x00
, 0x7F
, 0x49
…
Bezüglich der Adressen in X und Y Richtung ist zu sagen, dass sich der Ursprung, wie in der Abbildung dargestellt, in der oberen linken Ecke befindet – Adresse (0,0). Von diesem Punkt aus werden die Koordinaten aus hochgezählt. Während sich die X-Koordinate im Bereich von 0 … 127 im gültigen Bereich befindet, reicht die Y-Koordinate lediglich von 0 … 7. Das liegt in der Page-Aufteilung des Speichers. Je acht Pixel in der Höhe sind zu einer Page zusammengefasst und über entsprechenden Byte Zugriff ansprechbar.
Nutzung der Routinen
Die Displayroutinen arbeiten mit einem lokalen Framebuffer auf dem Controller. Sämtliche Aktionen wie das Setzen eines Pixels, das Zeichnen einer Linie … werden lokal im Framebuffer durchgeführt. Mit dem Aufruf der Funktion fb_show kann der Inhalt des Framebuffers dann auf dem Display dargestellt werden. Der Famebuffer ist eindimensional aufgebaut und hat somit eine Länge von 128 x 64. Somit werden für das Display intern im Controller 8.192 Bit benötigt um den Framebuffer abzubilden.
void main (void) { initPIC(); initI2C(); lcd_init(); /*Shut OFF the LCD-Backlight*/ LATCbits.LC2 = 1; /*Etwas Text in den Framebuffer schreiben*/ fb_draw_string_big(44,0,"OLED"); fb_draw_string_big(0,2,"Typ: SSD1306"); fb_draw_string_big(0,4,"Size: 128x64"); fb_draw_string(15,7,"http://pic-projekte.de/"); /*Den Framebuffer zur Anzeige bringen*/ fb_show(); /*Endlosschleife*/ while(1) { } }
Darstellung von Bitmaps
Jetzt möchte ich euch noch zeigen, wie einfach es sein kann eine Grafik / ein Bitmap auf dem OLED-Display zu zeichnen. Zunächst schauen wir uns die folgenden Fotos an auf denen je ein Bitmap auf dem Display dargestellt wurde. Auf dem ersten Foto ist neben dem Logo meiner Webseite zusätzlich der Schriftzug “OLED Display” und “SSD1306” zu sehen. Das zweite Foto zeigt das Logo von WordPress zentriert über die gesamte Höhe von 64 Pixeln des Displays. Und wir schauen uns jetzt an, wie einfach es ist ein beliebiges Bild zu erstellen und im Anschluss auf dem Display darzustellen.
Ich möchte an dieser Stelle lediglich eine sehr einfache Methode zur Implementierung der Bitmap-Funktionalität vorstellen. Diese Vorgehensweise ist keinesfalls Speicheroptimiert. Aber das ist auch gar nicht gewollt. Ich möchte vielmehr in einfachen Schritten zeigen, wie man innerhalb weniger Minute eine Grafik / ein Bild auf das OLED-Display zaubern kann:
Bitmap vorbereiten
Zunächst einmal sucht ihr euch ein Bitmap aus, dass ihr auf dem Display anzeigen möchtet. Dieses sollte idealerweise die Maße 128 x 64 Pixel (Breite x Höhe) oder kleiner haben. Ich habe zum Schreiben dieser Anleitung das folgende Bild benutzt:
Wenn ihr ein Bild habt, dass kleiner als 128 x 64 Pixel ist, solltet ihr es mit Paint so erweitern (nicht einfach skalieren, da dann die Schärfe leidet), dass ihr entsprechend auf 128 x 64 Pixel kommt. Wenn das Bitmap entsprechend vorbereitet ist, müssen wir dieses irgendwie in C-Code umwandeln. Der User Rüder S. aus dem Mikrocontroller.net Forum hat hierzu ein kleines Tool geschrieben, was genau das bewerkstelligt. Einfach downloaden und starten. Über den Button Bild laden...
importiert ihr nun eure Bitmap-Datei und klickt auf Code erstellen
, siehe:
Den generierten Source-Code könnt ihr dann Copy Clpbrd
mit in die Zwischenablage kopieren. Da wir hier lediglich eine Quick & Dirty Variante betrachten entfernen wir noch die ersten beiden Bytes aus dem Array (siehe am Beispiel 0x80
und 0x40
) sowie das PROGMEM
. Diese beiden Bytes geben die Größe des Arrays an (0x80 = 128
und 0x40 = 64
). Wir möchten diese Infos nicht nutzen sondern gehen stattdessen immer davon aus, dass das Bild das gesamte Display füllen wird. Wenn die beiden Bytes (sowie das PROGMEM
) entfernt wurden, kann das Ergebnis in eurer Programm kopiert werden (z.B. in eine neue Header-Datei bmp.h
).
Bitmap darstellen
Wir haben nun ein Bitmap in C-Code umgewandelt, im Anschluss (für uns aktuell) überflüssige Größeninfos entfernt und den Code entsprechend in unser Programm eingefügt (siehe bmp.h
). Nun können wir die Funktion fb_show_bmp()
nutzen um das Bitmap auf dem Display darzustellen, siehe:
fb_show_bmp(bmp_running_men);
Nun solltet ihr das Bitmap auf dem Display sehen, wie auch in den beiden Beispielfotos (siehe weiter oben) 🙂
Download
Da die Routinen für das Display wirklich nicht kompliziert sind, möchte ich auf eine detaillierte Analyse verzichten und lediglich den Download anbieten. Die Routinen sind mit dem für den XC8-Compiler von Microchip in C geschrieben. Der Abstraktionsgrad der Sprache C macht die Routinen jedoch sehr einfach portierbar und somit für viele Plattformen nutzbar. Der Download beinhaltet neben den Routinen auch einen dazugehörigen Font sowie ein Minimalbeispiel zur Anwendung der Routinen. Bitte lade dir die Routinen von meinem GitHub-Repository herunter.
Hi cooles Tutorial!
Aber wo findet ich den Schaltplan? Damit ich weiß welche Pins ich mit dem OLED Display verbinden muss
Danke!
Hi Tobi,
den braucht es auch nicht.
Einfach per I2C anschließen und los geht’s.
Gruß Nico
the code is perfect, thank you
Hi Nico!
Vielen Dank erstmal für die Lib und die Beschreibungen. Läuft alles super 🙂
Ich versuche gerade, eine Schrift in doppelter grösse zur “big” zu erstellen, also 4Byte hoch. Nur wie kann ich das font – array generieren? Kennt jemand ein Tool dafür?
Vielen dank im voraus und schöne Feiertage 🙂
Marc
Hi Marc,
die wurden mit dem GLCD Font Creator 2 erstellt, einfach mal nach googlen.
Gruß Nico
Hey Nico,
Danke für die Antwort und noch alles Gute für das neue Jahr.
LG Marc
Hallo Nico,
ich hoffe, ich störe das Blog hier nicht mit meinen “Problemaufwürfen”. Was ich bislang mitbekommen habe, da auch andere (zB > tinusaur) dasselbe Problem hatten, ist, daß va die Initialisierungssequenz anzupassen ist, um die OLEDs (gleich ob SSD1306 oder SSD1315-getrieben) zum Funktionieren zu bringen. Da wie immer kaum etwas vernünftig dokumentiert ist, bleibt leider häufig nur das Vergleichen und Probieren. Tinusaur verwendet jedoch keine Hardware-I2C, sondern ein “Software-Bit-Bang-I2C” (für die AVR geschrieben) – ist jedoch leicht anzupassen. Jedenfalls verwende ich Deine Treiber seit einiger Zeit mit voller Zufriedenheit und ich denke, daß die Integration der SSD1315-China-Clone keine wirkliche Herausforderung darstellen.
Viele Grüße
Alfred
Noch etwas, worüber ich gestolpert bin: seit einiger Zeit werden in den 0.96″-OLEDs (128×64), welche etwas kleiner (0.96 != 0.96 ?? ) sind als die älteren, SSD1315-Chips verbaut, welche leider nicht mit SSD1306-Code ansprechbar sind. Da ich mich erst selber damit beschäftigen muß, kann ich leider keine genauen Angaben zu den Unterschieden machen.
Hallo,
interessant, einen SSD1315 habe ich noch nicht in Verwundung gehabt bisher.
Gruß Nico
Schönen Abend an alle,
die Treibersoftware von Nico Pannwitz funktioniert einwandfrei. Sollte es doch einmal Probleme geben, dann hier ein (hoffentlich hilfreicher) Tip: nicht alles, was unter SSD1306 verkauft wird, hat auch einen solchen auf der Platine. Gelegentlich ist’s ein SH1106, welcher nicht so völlig kompatibel mit dem SSD1306 ist. Ungut ist nur, daß durchaus etwas angezeigt wird und man recht rasch geneigt ist zu glauben, etwas stimme mit dem OLED nicht.
Der wesentliche Unterschied zwischen SSD1306 und SH1106 ist, daß der SH1106 ein internes RAM von 132×64 Pixel hat – im Gegensatz zum SSD1306 mit 128×64 Pixel.
Die Frage (128×64 OLED mit einem SH1106) ist nun: wie sind die 128×64 im 132×64 RAM abgebildet?
Offensichtlich ist das 128×64 OLED innerhalb des 132×64 Bereichs zentriert, was bedeutet, daß Pixel (2,0) im RAM als Pixel (0,0) verschoben auf dem Display erscheint.
Wenn dem so ist, daß ein SH1106 anstelle eines SSD1306 verbaut ist, dann muß man eben die Softwaretreiber entsprechend anpassen.
Danke für dein Feedback!
Gruß Nico
Hallo Nico,
Erstmals: TOP! danke! als Anfänger ist das für mich Gold! ich lerne total viel. Eine Frage habe ich noch (jap…totaller Anfänger). Ich Programmiere mit MPLAB IDE und benutze einen PIC16F887 und konnte den ganze Code so umsetzen, dass der MPLAB kompiliert und das Display angesteuert wird… ABER: mein Buffer darf nur 64 bit groß sein (eigentlich sollte der 256 lang sein können aber kompiliert nicht)… uns somit zeigt das Display natürlich Quatsch… gibt es für so etwas einen Umweg?
Vielen Dank!
Gruß,
Yoyo
Hallo Nico, danke für den Code und die Erklärungen!
Ich benutze einen OLED mit SSD1306 mit 128 x 32 Pixel. Nun meine Frage: Hat mein Framebuffer auch eine länge von 128×64? Oder gibt es Aenderungen zu beachten im Uebertragen von deinem Code?
Vielen Dank
Hallo,
dann würde ich sagen, dass auch nur die Menge an Daten (Pixeln) übertragen werden muss. Probier mal aus, ob es bereits ausreicht das Define in `lcd.h` anzupassen, siehe:
“`c
#define SSD1306_HEIGHT 64
“`
Zusätzlich kann die Puffergröße auf `256` reduziert werden (auch in `lcd.c`):
“`c
extern uint8_t buffer[1024];
“`
Und die Funktion `lcd_sendFramebuffer()` angepasst werden. Versuch es mal selber, wenn du Probleme bekommst, meld dich gerne im Forum.
Gruß Nico
Danke für die rasche Antwort!
Hatte diese Aenderungen schon vorgenommen gehabt.
Ich denke du meinst die Puffergrösse kann auf 512 Bits reduziert werden?! (32×128/8)
Da liegt iergendwo vermutlich auch mein Verständnisproblem… Bei mir wird alles iergendwie durch 2 geteilt.
Wenn ich eine vertikale Gerade von 4 Pixel oben links Zeichnen will und schreibe buffer[0] = 0xFF, bekomme ich lediglich eine vertikale Gerade von 2 Pixel ganz oben links. Wenn ich dieselbe Gerade auf der zweiten Linie des OLEDs zeichnen will und schreibe buffer[128] = 0xFF bekomme ich wieder eine Gerade von 2 Pixel aber im unteren Teil der ersten Linie.
Eröffne am besten mal einen Beitrag im Forum und beschreibe, was du bereits gemacht hast. Füg bitte auch deinen aktuellen Code als Anhang mit an. Dann wird es einfacher als hier in den Kommentaren zum Artikel.
Gruß Nico
thanks a lot for your explanation and the code. Very helpful.
Hallo Nico,
Ich hatte wahnsinnige Probleme, I²C mit den XC8 Libraries zur Mitarbeit zu bewegen. Mit ein paar kleineren Aenderungen (ANSELCbits) hat Deine peri.c Geschichte endlich die erwarteten Ergebnisse im Logic-Analyser gezeigt! Und der positive Nebeneffekt war natuerlich, dass das OLED auch funktioniert 🙂
Besten Dank fuer den Code und diesen Blog! Ich bin relativ neu in der Welt der Microcontroller und hab hier ne ganze Menge gelernt!
Moin, freut mich, dass es klappt 🙂
Servus Nico, danke für den Code!
Bei mir hat er leider nicht funktioniert, erst als ich `ANSELD = 0x00;` hinzugefügt habe.
LG Clément
Moin Clément,
danke für die Rückmeldung 🙂
Viele Grüße
I think your nibble ordering is backwards.
E7 should be 7E
F7 should be 7F
94 should be 49.
Thanks for sharing your project and code on github 🙂
Hi Steve, yes you are right 🙂 Thank you for the hint. I’ve corrected the Bytes.
How do you created the fonts, can you please describe?
The Fonts are created with the GLCD Font Creator 2 from MikroElectronika.
eine Länge von 128 x 64. Somit werden für das Display intern im Controller 8.192 Byte benötigt um den Framebuffer abzubilden.
8192 Bit nicht byte
Hab’s korrigiert, danke 🙂