e-Paper / i-Ink Display
Für Anwendungen wo es auf sehr guten Kontrast und/oder sehr niedrigen Stromverbrauch ankommt sind sog. e-Paper Displays eine gute Wahl.
Zum Experimentieren habe ich die TypeGDEH0154D27 mit 200x200 Pixeln von Good Display benutzt.
Display plus Hilfsplatine sind z.B. bei Eckstein
zu bekommen.
Infos und Beispielprogramme zum Display findet man auf der Webseite von
Waveshare.
Der Hersteller des Displays scheint die Firma
Good Display zu sein.
Von dem Display gab es scheinbar auch eine ältere Version mit der Bezeichnung GDEP015OC1,
die soll SW-kompatibel sein.
Links: GDEP015OC1, rechts: GDEH0154D27
Das GDEH0154D27 hat eine Auflösung von 200x200 Pixel und 1-Bit Farbe (schwarz / weiss). Das Treiber-IC hat die Bezeichnung
IL3829. Es scheint sich dabei um den SSD1607 von Solomon Systech zu
handeln. Das Datenblatt ist eine 1:1 Kopie und es findet sich auch ein Bild von Solomon dort drin.
Nur das "nackte" Display zu benutzen kann ich überhaupt nicht empfehlen. Für den Betrieb benötigt es einen Spannungswandler und
diverses "Hühnerfutter". Am schwierigsten ist aber das Löten des Flex-Kabels mit seinen nur 0.3mm breiten Pads..
HW-Infos zum Treiber/Adapterboard gibt es hier:
https://www.waveshare.com/w/upload/8/87/E-Paper-Driver-HAT-Schematic.pdf
Weitere Details zur Funktionsweise von ePapers sind z.B. hier zu finden:
http://benkrasnow.blogspot.com/2017/10/fast-partial-refresh-on-42-e-paper.html
oder hier:
https://mcuoneclipse.com/2017/11/04/fascinating-details-of-waveshare-e-paper-displays/
Die beiden Quellen sollte man vorab studieren um die Funktionsweise besser zu verstehen. Daraus wird auch deutlich weshalb die Ansteuerung
so kompliziert ist.
UPDATE
Mittlerweile hat Waveshare das Modul durch eine neue Version V2 ersetzt die NICHT SW-kompatibel zur vorherigen ist. Die Dokumentation
ist leider etwas chaotisch und ich konnte die Unterschiede noch nicht herausfiltern.
Das verwendete Display ist vermutlich ein
GDEH0154D67
mit einem SSD1681-Controller.
Neben dem geänderten Display-Controller wurde auch die Beschaltung (positiv) erweitert. Sie ist nun universell für 3V und 5V geeignet.
Eine Übersicht der 1.54 inch e-Paper Displays von GoodDisplay:
(http://www.good-display.com/news/82.html)
Part Number | Color | Refresh Time | Resolution | IC Driver | Feature |
---|---|---|---|---|---|
GDEH0154D67 | BW | 2S (full refresh) | 200x200 | SSD1681 | Partial refresh, High Resolution |
GDEW0154M10 | BW | 4S (full refresh) | 152x152 | UC8151D | DES Technology, Wide Working Temperature Range, Can Be Used In Sunlight |
GDEW0154I9F | BW | 3S (full refresh) | 152x152 | UC8151C | Flexible,4 Grayscale |
GDEM0154E97LT | BW | 3S (full refresh) | 152x152 | SSD1675B | Low temperature |
GDEW0154T8 | BW | 0.82S (fast refresh) | 152x152 | UC8151C | Partial refresh,4 Grayscale |
GDEW0154M09 | BW | 0.82S (fast refresh) | 200x200 | JD79653 | Fast refresh,High Resolution |
GDEH0154Z90 | BWR | 15S (full refresh) | 200x200 | SSD1681 | Three colors,High Resolution |
GDEW0154Z17 | BWR | 16S (full refresh) | 152x152 | UC8151C | Three colors |
GDEM0154C90 | BWY | 30S (full refresh) | 152x152 | SSD1680 | Three colors |
Besonderheiten
Eine wichtige Eigenschaft direkt vorweg: ePaper-Displays sind extrem langsam! Ein vollständiger Bildwechsel
dauert eine gute Sekunde. Man kann das etwas beschleunigen, aber ein LCD ist immer wesentlich schneller. Display-Varianten mit 3 Farben
(z.B. weiss, schwarz und rot) sind nochmals deutlich langsamer.
Der grosse Vorteil ist der extrem hohe Kontrast der ein Ablesen auch bei direkter Beleuchtung erlaubt.
Eine weitere Besonderheit ist der Aufbau des Displayspeichers. Er ist
doppelt vorhanden. Dies liegt an der speziellen Methode der Pixel-Ansteuerung die das zuletzt angezeigte Pixelmuster berücksichtigt.
SW-Treiber
Ein gute erste Basis für eigene SW findet man auf der Seite von Waveshare:
https://www.waveshare.com/wiki/File:1.54inch_e-Paper_Module_code.7z
Desweiteren gibt es eine sehr umfangreiche Grafik-Library für Arduino:
https://www.arduinolibraries.info/libraries/u8g2
Der Controller SSD1607 ist der passende. Die Bibliothek enthält noch viele andere Typen.
SW-Aufbau
Zu Beginn wird die HW zurückgesetzt durch ein LOW auf Reset. Danach folgt eine Wartezeit damit sich alles stabilisieren kann:
void Display_HW_Reset_EPD(void){
P_data= 0;
P_Data = 0;
P_ChipSelect = 0;
P_Reset = 0;
Delay(20000);
P_Reset = 1;
}
Danach folgt die Init-Sequenz. Eine Besonderheit sind dabei die LUTs (LookUpTables)
für den Pixel-Refresh. Das Display unterstützt Partial- und
Full-Refresh, jede hat eine eigene LUT.
Full-Refresh "räumt gründlich auf", jedes einzelne Pixel ist danach korrekt gesetzt. Das Display "blinzelt" dabei 2 mal, das ganze
dauert länger als 1 Sekunde.
PartialRefresh kümmert sichnur um Pixel die ihre Farbe geändert haben und ist deutlich schneller (~0.3s). Als Nachteil bleiben ganz
leichte Schatten zurück und der Kontrast ist geringer.
Am besten mischt man beide Refresh-Arten, je nach Situation. Man muss dazu aber vor dem Display-Refresh die jeweilige LUT laden und den passenden
Parameter für das Update-Command verwenden.
Init-Sequenz
Die Init-Teil ist weitgehend aus den Beispielen übernommen. Leider existiert vom eigentlichen Display kein Datenblatt, einige Parameter
wurden daher experimentell ermittelt (z.B. der Wert für VCOM):
void InitDisplay(void) {
// Display_WaitUntilIdle();
// Diese Funktion überwacht den Busy-Pin (Ausgang) des Displays und wartet solange er HIGH ist.
// Einige Befehle brauchen etwas Zeit, folgende Befehle dürfen erst gesendet werden wenn der Pegel
// wieder auf LOW geht. Besonders wichtig während des Display-Refresh. Zur
// Absicherung wird dieser Befehl an allen wichtigen Stellen eingestreut.
Display_SendCommand(DRIVER_OUTPUT_CONTROL); // 0x01
Display_SendData((EPD_HEIGHT - 1) & 0xFF); // 0xC7 = 199; POR 0x12B = dez299 (9 Bit)
Display_SendData(((EPD_HEIGHT - 1) >> 8) & 0xFF); // 0xC7 = 199; POR 0x12B = dez299 (9 Bit)
Display_SendData(0x00); // GD = 0; SM = 0; TB = 0; POR = 0x00
Display_SendCommand(BOOSTER_SOFT_START_CONTROL); // 0x0C
Display_SendData(0xD7); // POR 0x87
Display_SendData(0xD6); // POR 0x86
Display_SendData(0x9D); // POR 0x85
Display_SendCommand(WRITE_VCOM_REGISTER); // 0x2C
// Display_SendData(0xA8); // VCOM 0xA8 ??
Display_SendData(0x9B); // VCOM 0x9B ??
Display_SendCommand(SET_DUMMY_LINE_PERIOD); // 0x3A
Display_SendData(0x1A); // POR 0x16, 4 dummy lines per gate??
Display_SendCommand(SET_GATE_TIME); // 0x3B
Display_SendData(0x08); // POR 0x08, 2us per line??
Display_SendCommand(TEMPERATURE_SENSOR_CONTROL); // 0x1A, 12 Bit Data, POR 0x7FF = 127°C, MSB 0x7F=0b01111111 + LSB 0xF0=0b11110000
Display_SendData(0x19); // 0x19, 0x190 = 25°C
Display_SendData(0x00); // 0x00
Display_SendCommand(DATA_ENTRY_MODE_SETTING); // 0x11
Display_SendData(DATA_ENTRY_MODE_SETTING_VALUE); // X increment; Y increment
Display_SendCommand(WRITE_LUT_REGISTER); // 0x32, the length of look-up table is 30 bytes
for (int i = 0; i < 30; i++) {
if (mode == 0){ // Full update
Display_SendData(lut_full_update[i]);
} else { // Partial update
Display_SendData(lut_partial_update[i]);
}
}
Display_WaitUntilIdle(); // Check Buys-Line, wait until previous command is completed
}
LUT-Tables
Die LUT-Tabellen beschreiben die Sequenz für den Pixel-Refresh und das Verhalten für die verschiedenen s/w Pixelübergänge. Hier wird gerne
etwas experimentiert um einen schnelleren Refresh zu erhalten. Das hat allerdings auch einige Nachteile
und kann die Lebensdauer verkürzen.
const unsigned char lut_full_update[] =
{
0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69, 0x69, 0x59, 0x58, 0x99, 0x99,
0x88, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00
// Source: c:\Daten\Bauteile\Display\ePaper\GxEPD-master\src\GxGDEP015OC1\GxGDEP015OC1.cpp
// 0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const unsigned char lut_partial_update[] =
{
0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
// Source: c:\Daten\Bauteile\Display\ePaper\GxEPD-master\src\GxGDEP015OC1\GxGDEP015OC1.cpp
// 0x10, 0x18, 0x18, 0x08, 0x18, 0x18, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x14, 0x44, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Source: u8x8_d_ssd1607_200x200.c, Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
// according to the command table, the lut has 240 bits (=30 bytes * 8 bits)
// Waveform part of the LUT (20 bytes)
// bit 7/6: 1 - 1 transition
// bit 5/4: 1 - 0 transition
// bit 3/2: 0 - 1 transition
// bit 1/0: 0 - 0 transition
// 00 VSS
// 01 VSH
// 10 VSL
// 11 NA
// Timing part of the LUT, 20 Phases with 4 bit each: 10 bytes
Weiteres folgt..