Christians Webseite        << zurück        vor >>

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

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
ePaper

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