Browse Source

Merge remote-tracking branch 'origin/master' into httpd

Sebastien 5 years ago
parent
commit
1bbd6c8225

+ 19 - 5
components/display/SH1106.c

@@ -17,6 +17,7 @@
 #include "gds_private.h"
 
 #define SHADOW_BUFFER
+#define USE_IRAM
 
 static char TAG[] = "SH1106";
 
@@ -79,14 +80,25 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
 
 static bool Init( struct GDS_Device* Device ) {
 	Device->FramebufferSize = ( Device->Width * Device->Height ) / 8;	
-	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
-    NullCheck( Device->Framebuffer, return false );
 	
+// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy		
 #ifdef SHADOW_BUFFER	
-	if (Device->IF == IF_I2C) Device->Shadowbuffer = malloc( Device->FramebufferSize );
-	else Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
+    NullCheck( Device->Framebuffer, return false );
+#ifdef USE_IRAM
+	if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	else 
+#endif
+	Device->Shadowbuffer = malloc( Device->FramebufferSize );	
 	NullCheck( Device->Shadowbuffer, return false );
 	memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize);
+#else	// not SHADOW_BUFFER
+#ifdef USE_IRAM
+	// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy
+	if (Device->IF == IF_SPI) Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	else 
+#endif
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
 #endif	
 		
 	// need to be off and disable display RAM
@@ -130,8 +142,9 @@ static bool Init( struct GDS_Device* Device ) {
 static const struct GDS_Device SH1106 = {
 	.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
 	.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
-	.DrawPixelFast = GDS_DrawPixelFast,
 	.Update = Update, .Init = Init,
+	//.DrawPixelFast = GDS_DrawPixelFast,
+	//.ClearWindow = ClearWindow,
 };	
 
 struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
@@ -139,6 +152,7 @@ struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device) {
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	*Device = SH1106;	
+	Device->Depth = 1;
 	ESP_LOGI(TAG, "SH1106 driver");
 	
 	return Device;

+ 20 - 6
components/display/SSD1306.c

@@ -17,6 +17,7 @@
 #include "gds_private.h"
 
 #define SHADOW_BUFFER
+#define USE_IRAM
 
 static char TAG[] = "SSD1306";
 
@@ -77,14 +78,25 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
 
 static bool Init( struct GDS_Device* Device ) {
 	Device->FramebufferSize = ( Device->Width * Device->Height ) / 8;	
-	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
-    NullCheck( Device->Framebuffer, return false );
 	
+// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy		
 #ifdef SHADOW_BUFFER	
-	if (Device->IF == IF_I2C) Device->Shadowbuffer = malloc( Device->FramebufferSize );
-	else Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
+    NullCheck( Device->Framebuffer, return false );
+#ifdef USE_IRAM
+	if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	else 
+#endif
+	Device->Shadowbuffer = malloc( Device->FramebufferSize );	
 	NullCheck( Device->Shadowbuffer, return false );
 	memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize);
+#else	// not SHADOW_BUFFER
+#ifdef USE_IRAM
+	// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy
+	if (Device->IF == IF_SPI) Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+	else 
+#endif
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
 #endif	
 		
 	// need to be off and disable display RAM
@@ -95,7 +107,7 @@ static bool Init( struct GDS_Device* Device ) {
 	Device->WriteCommand( Device, 0x8D );
 	Device->WriteCommand( Device, 0x14 ); 
 			
-	// COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something difference
+	// COM pins HW config (alternative:EN if 64, DIS if 32, remap:DIS) - some display might need something different
 	Device->WriteCommand( Device, 0xDA );
 	Device->WriteCommand( Device, ((Device->Height == 64 ? 1 : 0) << 4) | (0 < 5) );
 		
@@ -131,8 +143,9 @@ static bool Init( struct GDS_Device* Device ) {
 static const struct GDS_Device SSD1306 = {
 	.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
 	.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
-	.DrawPixelFast = GDS_DrawPixelFast,
 	.Update = Update, .Init = Init,
+	//.DrawPixelFast = GDS_DrawPixelFast,
+	//.ClearWindow = ClearWindow,
 };	
 
 struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
@@ -140,6 +153,7 @@ struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device) {
 	
 	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
 	*Device = SSD1306;	
+	Device->Depth = 1;
 	ESP_LOGI(TAG, "SSD1306 driver");
 	
 	return Device;

+ 320 - 0
components/display/SSD132x.c

@@ -0,0 +1,320 @@
+/**
+ * Copyright (c) 2017-2018 Tara Keeling
+ *				 2020 Philippe G.
+ * 
+ * This software is released under the MIT License.
+ * https://opensource.org/licenses/MIT
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <esp_heap_caps.h>
+#include <esp_log.h>
+
+#include "gds.h"
+#include "gds_private.h"
+
+#define SHADOW_BUFFER
+#define USE_IRAM
+#define PAGE_BLOCK	1024
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+
+static char TAG[] = "SSD132x";
+
+enum { SSD1326, SSD1327 };
+
+struct SSD132x_Private {
+	uint8_t *iRAM;
+	uint8_t ReMap, PageSize;
+	uint8_t Model;
+};
+
+static const unsigned char BitReverseTable256[] = 
+{
+  0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 
+  0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, 
+  0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, 
+  0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, 
+  0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 
+  0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
+  0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 
+  0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
+  0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
+  0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 
+  0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
+  0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
+  0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, 
+  0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
+  0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 
+  0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
+};
+
+// Functions are not declared to minimize # of lines
+
+static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
+	Device->WriteCommand( Device, 0x15 );
+	Device->WriteCommand( Device, Start );
+	Device->WriteCommand( Device, End );
+}
+static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
+	// can be by row, not by page (see Update Optimization)
+	Device->WriteCommand( Device, 0x75 );
+	Device->WriteCommand( Device, Start );
+	Device->WriteCommand( Device, End );
+}
+
+static void Update4( struct GDS_Device* Device ) {
+	struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private;
+		
+	// always update by full lines
+	SetColumnAddress( Device, 0, Device->Width / 2 - 1);
+	
+#ifdef SHADOW_BUFFER
+	uint16_t *optr = (uint16_t*) Device->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
+	bool dirty = false;
+	
+	for (int r = 0, page = 0; r < Device->Height; r++) {
+		// look for change and update shadow (cheap optimization = width always / by 2)
+		for (int c = Device->Width / 2 / 2; --c >= 0;) {
+			if (*optr != *iptr) {
+				dirty = true;
+				*optr = *iptr;
+			}
+			iptr++; optr++;
+		}
+		
+		// one line done, check for page boundary
+		if (++page == Private->PageSize) {
+			if (dirty) {
+				SetRowAddress( Device, r - page + 1, r );
+				// own use of IRAM has not proven to be much better than letting SPI do its copy
+				if (Private->iRAM) {
+					memcpy(Private->iRAM, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );
+					Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 );
+				} else	{
+					Device->WriteData( Device, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 );					
+				}	
+				dirty = false;
+			}	
+			page = 0;
+		}	
+	}	
+#else
+	for (int r = 0; r < Device->Height; r += Private->PageSize) {
+		SetRowAddress( Device, r, r + Private->PageSize - 1 );
+		if (Private->iRAM) {
+			memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
+			Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width / 2 );
+		} else	{
+			Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Private->PageSize * Device->Width / 2 );
+		}	
+	}	
+#endif	
+}
+
+/* 
+ We have to make a choice here: either we go by row one by one and send lots of 
+ small packets on the serial interface or we do a page of N rows once at least
+ a change has been detected. So far, choice is to go one-by-one
+*/ 
+static void Update1( struct GDS_Device* Device ) {
+#ifdef SHADOW_BUFFER
+	// not sure the compiler does not have to redo all calculation in for loops, so local it is
+	int width = Device->Width / 8, rows = Device->Height;
+	uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer;
+	
+	// by row, find first and last columns that have been updated
+	for (int r = 0; r < rows; r++) {
+		uint8_t first = 0, last;	
+		for (int c = 0; c < width; c++) {
+			if (*iptr != *optr) {
+				if (!first) first = c + 1;
+				last = c ;
+			}	
+			*optr++ = *iptr++;
+		}
+		
+		// now update the display by "byte rows"
+		if (first--) {
+			SetColumnAddress( Device, first, last );
+			SetRowAddress( Device, r, r);
+			Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1);
+		}
+	}	
+#else	
+	// automatic counter and end Row/Column
+	SetColumnAddress( Device, 0, Device->Width / 8 - 1);
+	SetRowAddress( Device, 0, Device->Height - 1);
+	Device->WriteData( Device, Device->Framebuffer, Device->FramebufferSize );
+#endif	
+}
+
+// in 1 bit mode, SSD1326 has a different memory map than SSD1306 and SH1106
+static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
+    uint32_t XBit = ( X & 0x07 );
+    uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width + X ) >> 3 );
+
+    if ( Color == GDS_COLOR_XOR ) {
+        *FBOffset ^= BIT( 7 - XBit );
+    } else {
+		// we might be able to save the 7-Xbit using BitRemap (A0 bit 2)
+        *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( 7 - XBit ) : *FBOffset & ~BIT( 7 - XBit );
+    }
+}
+
+static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
+	uint8_t *optr = Device->Framebuffer;
+	
+	if (!Height) Height = Device->Height;
+	if (!Width) Width = Device->Width;
+	
+	// just do bitreverse and if BitRemap works, there will be even nothing to do	
+	for (int i = Height * Width >> 3; --i >= 0;) *optr++ = BitReverseTable256[*Data++];
+	
+	// Dirty is set for us
+}
+
+static void SetHFlip( struct GDS_Device* Device, bool On ) { 
+	struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private;
+	if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 2))) : (Private->ReMap & ~((1 << 0) | (1 << 2)));
+	else Private->ReMap = On ? (Private->ReMap | ((1 << 0) | (1 << 1))) : (Private->ReMap & ~((1 << 0) | (1 << 1)));
+	Device->WriteCommand( Device, 0xA0 );
+	Device->WriteCommand( Device, Private->ReMap );
+}	
+
+static void SetVFlip( struct GDS_Device *Device, bool On ) { 
+	struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private;
+	if (Private->Model == SSD1326) Private->ReMap = On ? (Private->ReMap | (1 << 1)) : (Private->ReMap & ~(1 << 1));
+	else Private->ReMap = On ? (Private->ReMap | (1 << 4)) : (Private->ReMap & ~(1 << 4));
+	Device->WriteCommand( Device, 0xA0 );
+	Device->WriteCommand( Device, Private->ReMap );
+}	
+	
+static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAF ); }
+static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0xAE ); }
+
+static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
+    Device->WriteCommand( Device, 0x81 );
+    Device->WriteCommand( Device, Contrast );
+}
+
+static bool Init( struct GDS_Device* Device ) {
+	struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private;
+	
+	// find a page size that is not too small is an integer of height
+	Private->PageSize = min(8, PAGE_BLOCK / (Device->Width / 2));
+	Private->PageSize = Device->Height / (Device->Height / Private->PageSize) ;	
+	
+#ifdef USE_IRAM	
+	// let SPI driver allocate memory, it has not proven to be more efficient
+	if (Device->IF == IF_SPI) Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+#endif	
+	Device->FramebufferSize = ( Device->Width * Device->Height ) / 2;	
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
+    NullCheck( Device->Framebuffer, return false );
+	
+// benchmarks showed little gain to have SPI memory already in IRAM vs letting driver copy		
+#ifdef SHADOW_BUFFER	
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
+    NullCheck( Device->Framebuffer, return false );
+#ifdef USE_IRAM
+	if (Device->IF == IF_SPI) {
+		if (Device->Depth == 1) {
+			Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+		} else {
+			Device->Shadowbuffer = malloc( Device->FramebufferSize );	
+			Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+		}	
+	} else
+#endif
+	Device->Shadowbuffer = malloc( Device->FramebufferSize );	
+	memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize);
+#else	// not SHADOW_BUFFER
+#ifdef USE_IRAM
+	if (Device->IF == IF_SPI) {
+		if (Device->Depth == 1) {
+			Device->Framebuffer = heap_caps_calloc( 1, Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+		} else  {
+			Device->Framebuffer = calloc( 1, Device->FramebufferSize );	
+			Private->iRAM = heap_caps_malloc( Private->PageSize * Device->Width / 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+		}
+	} else 
+#endif	
+	Device->Framebuffer = calloc( 1, Device->FramebufferSize );
+#endif	
+
+	ESP_LOGI(TAG, "SSD1326/7 with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
+			
+	// need to be off and disable display RAM
+	Device->DisplayOff( Device );
+    Device->WriteCommand( Device, 0xA5 );
+	
+	// need COM split (6)
+	Private->ReMap = 1 << 6;
+	// MUX Ratio
+    Device->WriteCommand( Device, 0xA8 );
+    Device->WriteCommand( Device, Device->Height - 1);
+	// Display Offset
+    Device->WriteCommand( Device, 0xA2 );
+    Device->WriteCommand( Device, 0 );
+	// Display Start Line
+    Device->WriteCommand( Device, 0xA1 );
+	Device->WriteCommand( Device, 0x00 );
+	Device->SetContrast( Device, 0x7F );
+	// set flip modes
+	Device->SetVFlip( Device, false );
+	Device->SetHFlip( Device, false );
+	// no Display Inversion
+    Device->WriteCommand( Device, 0xA6 );
+	// set Clocks
+    Device->WriteCommand( Device, 0xB3 );
+    Device->WriteCommand( Device, ( 0x08 << 4 ) | 0x00 );
+	// set Adressing Mode Horizontal
+	Private->ReMap |= (0 << 2);
+	// set monotchrome mode if required
+	if (Device->Depth == 1) Private->ReMap |= (1 << 4);
+	// write ReMap byte
+	Device->WriteCommand( Device, 0xA0 );
+	Device->WriteCommand( Device, Private->ReMap );		
+		
+	// gone with the wind
+	Device->WriteCommand( Device, 0xA4 );
+	Device->DisplayOn( Device );
+	Device->Update( Device );
+	
+	return true;
+}	
+
+static const struct GDS_Device SSD132x = {
+	.DisplayOn = DisplayOn, .DisplayOff = DisplayOff, .SetContrast = SetContrast,
+	.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
+	.Update = Update4, .Init = Init,
+};	
+
+struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
+	uint8_t Model;
+	
+	if (!strcasestr(Driver, "SSD1326")) Model = SSD1326;
+	else if (!strcasestr(Driver, "SSD1327")) Model = SSD1327;
+	else return NULL;
+	
+	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
+	
+	*Device = SSD132x;	
+	((struct SSD132x_Private*) Device->Private)->Model = Model;
+	
+	sscanf(Driver, "%*[^:]:%c", &Device->Depth);
+	
+	if (Model == SSD1326 && Device->Depth == 1) {
+		Device->Update = Update1;
+		Device->DrawPixelFast = DrawPixel1Fast;
+		Device->DrawBitmapCBR = DrawBitmapCBR;
+	} else {
+		Device->Depth = 4;
+	}	
+		
+	return Device;
+}

+ 55 - 8
components/display/core/gds.c

@@ -21,7 +21,7 @@ static struct GDS_Device Display;
 
 static char TAG[] = "gds";
 
-struct GDS_Device*	GDS_AutoDetect( char *Driver, GDS_DetectFunc DetectFunc[] ) {
+struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] ) {
 	for (int i = 0; DetectFunc[i]; i++) {
 		if (DetectFunc[i](Driver, &Display)) {
 			ESP_LOGD(TAG, "Detected driver %p", &Display);
@@ -53,20 +53,67 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) {
 }	
 
 void GDS_Clear( struct GDS_Device* Device, int Color ) {
+	if (Device->Depth == 1) Color = Color == GDS_COLOR_BLACK ? 0 : 0xff;
+	else if (Device->Depth == 4) Color = Color | (Color << 4);
     memset( Device->Framebuffer, Color, Device->FramebufferSize );
+	Device->Dirty = true;
 }
 
 void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
-	// cheap optimization on boundaries
-	if (x1 == 0 && x2 == Device->Width - 1 && y1 % 8 == 0 && (y2 + 1) % 8 == 0) {
-		memset( Device->Framebuffer + (y1 / 8) * Device->Width, 0, (y2 - y1 + 1) / 8 * Device->Width );
+	// driver can provide own optimized clear window
+	if (Device->ClearWindow) {
+		Device->ClearWindow( Device, x1, y1, x2, y2, Color );
+	} else if (Device->Depth == 1) {
+		// single shot if we erase all screen
+		if (x2 - x1 == Device->Width - 1 && y2 - y1 == Device->Height - 1) {
+			memset( Device->Framebuffer, Color == GDS_COLOR_BLACK ? 0 : 0xff, Device->FramebufferSize );
+		} else {
+			uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
+			uint8_t Width = Device->Width;
+			// try to do byte processing as much as possible
+			for (int r = y1; r <= y2;) {
+				int c = x1;
+				// for a row that is not on a boundary, no optimization possible
+				while (r & 0x07 && r <= y2) {
+					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color);
+					r++;
+				}
+				// go fast if we have more than 8 lines to write
+				if (r + 8 < y2) {
+					memset(Device->Framebuffer + Width * r / 8 + x1, _Color, x2 - x1 + 1);
+					r += 8;
+				} else while (r <= y2) {
+					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color);
+					r++;
+				}
+			}
+		}
+	} if (Device->Depth == 4) {
+		if (x2 - x1 == Device->Width - 1 && y2 - y1 == Device->Height - 1) {
+			// we assume color is 0..15
+			memset( Device->Framebuffer, Color | (Color << 4), Device->FramebufferSize );
+		} else {
+			uint8_t _Color = Color | (Color << 4);
+			uint8_t Width = Device->Width;
+			// try to do byte processing as much as possible
+			for (int r = y1; r <= y2; r++) {
+				int c = x1;
+				if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color);
+				int chunk = (x2 - c + 1) >> 1;
+				memset(Device->Framebuffer + ((r * Width + c)  >> 1), _Color, chunk);
+				if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
+			}
+		}	
 	} else {
 		for (int y = y1; y <= y2; y++) {
 			for (int x = x1; x <= x2; x++) {
-				Device->DrawPixelFast( Device, x, y, Color);
-			}		
-		}	
-	}	
+				GDS_DrawPixelFast( Device, x, y, Color);
+			}
+		}
+	}
+
+	// make sure diplay will do update
+	Device->Dirty = true;
 }
 
 void GDS_Update( struct GDS_Device* Device ) {

+ 17 - 4
components/display/core/gds.h

@@ -4,16 +4,29 @@
 #include <stdint.h>
 #include <stdbool.h>
 
-#define GDS_COLOR_BLACK 0
-#define GDS_COLOR_WHITE 1
+/* NOTE for drivers:
+ The build-in DrawPixel(Fast), DrawCBR and ClearWindow are optimized for 1 bit 
+ and 4 bits screen depth. For any other type of screen, DrawCBR and ClearWindow
+ default to use DrawPixel, which is very sub-optimal. For such other depth, you 
+ must supply the DrawPixelFast. The built-in 1 bit depth function are only for 
+ screen with vertical framing (1 byte = 8 lines). For example SSD1326 in 
+ monochrome mode is not such type of screen, SH1106 and SSD1306 are
+*/ 
+
+enum { 	GDS_COLOR_L0 = 0, GDS_COLOR_L1, GDS_COLOR_L2, GDS_COLOR_L3, GDS_COLOR_L4, GDS_COLOR_L5, GDS_COLOR_L6, GDS_COLOR_L7, 
+		GDS_COLOR_L8, GDS_COLOR_L9, GDS_COLOR_L10, GDS_COLOR_L11, GDS_COLOR_L12, GDS_COLOR_L13, GDS_COLOR_L14, GDS_COLOR_L15,
+};
+		
+#define GDS_COLOR_BLACK GDS_COLOR_L0
+#define GDS_COLOR_WHITE GDS_COLOR_L15
 #define GDS_COLOR_XOR 2
 
 struct GDS_Device;
 struct GDS_FontDef;
 
-typedef struct GDS_Device* (*GDS_DetectFunc)(char *Driver, struct GDS_Device *Device);
+typedef struct GDS_Device* GDS_DetectFunc(char *Driver, struct GDS_Device *Device);
 
-struct GDS_Device*	GDS_AutoDetect( char *Driver, GDS_DetectFunc[] );
+struct GDS_Device*	GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] );
 
 void 	GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
 void 	GDS_DisplayOn( struct GDS_Device* Device );

+ 126 - 105
components/display/core/gds_draw.c

@@ -14,8 +14,8 @@
 #include <math.h>
 #include <esp_attr.h>
 
-#include "gds.h"
 #include "gds_private.h"
+#include "gds.h"
 #include "gds_draw.h"
 
 static const unsigned char BitReverseTable256[] = 
@@ -45,49 +45,11 @@ __attribute__( ( always_inline ) ) static inline void SwapInt( int* a, int* b )
     *a = Temp;
 }
 
-inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
-    uint32_t YBit = ( Y & 0x07 );
-    uint8_t* FBOffset = NULL;
-
-    /* 
-     * We only need to modify the Y coordinate since the pitch
-     * of the screen is the same as the width.
-     * Dividing Y by 8 gives us which row the pixel is in but not
-     * the bit position.
-     */
-    Y>>= 3;
-
-    FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
-
-    if ( Color == GDS_COLOR_XOR ) {
-        *FBOffset ^= BIT( YBit );
-    } else {
-        *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( YBit ) : *FBOffset & ~BIT( YBit );
-    }
-}
-
-inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
-    uint32_t YBit = ( Y & 0x07 );
-    uint8_t* FBOffset = NULL;
-
-    /* 
-     * We only need to modify the Y coordinate since the pitch
-     * of the screen is the same as the width.
-     * Dividing Y by 8 gives us which row the pixel is in but not
-     * the bit position.
-     */
-    Y>>= 3;
+// un-comment if need to be instanciated for external callers
+extern inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
+extern inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
 
-    FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
-
-    if ( Color == GDS_COLOR_XOR ) {
-        *FBOffset ^= BIT( YBit );
-    } else {
-        *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( YBit ) : *FBOffset & ~BIT( YBit );
-    }
-}
-
-void IRAM_ATTR GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
+void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color ) {
     int XEnd = x + Width;
 
 	Device->Dirty = true;
@@ -98,30 +60,24 @@ void IRAM_ATTR GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width
 	if (y < 0) y = 0;
 	else if (y >= Device->Height) x = Device->Height - 1;
 
-    for ( ; x < XEnd; x++ ) {
-        if ( IsPixelVisible( Device, x, y ) == true ) {
-            Device->DrawPixelFast( Device, x, y, Color );
-        } else {
-            break;
-        }
-    }
+    for ( ; x < XEnd; x++ ) GDS_DrawPixelFast( Device, x, y, Color );
 }
 
-void IRAM_ATTR GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
+void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color ) {
     int YEnd = y + Height;
 
 	Device->Dirty = true;
+	
+	if (x < 0) x = 0;
+	if (x >= Device->Width) x = Device->Width - 1;
+	
+	if (y < 0) y = 0;
+	else if (YEnd >= Device->Height) YEnd = Device->Height - 1;
 
-    for ( ; y < YEnd; y++ ) {
-        if ( IsPixelVisible( Device, x, y ) == true ) {
-            GDS_DrawPixel( Device, x, y, Color );
-        } else {
-            break;
-        }
-    }
+    for ( ; y < YEnd; y++ ) GDS_DrawPixel( Device, x, y, Color );
 }
 
-static inline void IRAM_ATTR DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
+static inline void DrawWideLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
     int dx = ( x1 - x0 );
     int dy = ( y1 - y0 );
     int Error = 0;
@@ -138,7 +94,7 @@ static inline void IRAM_ATTR DrawWideLine( struct GDS_Device* Device, int x0, in
 
     for ( ; x < x1; x++ ) {
         if ( IsPixelVisible( Device, x, y ) == true ) {
-            Device->DrawPixelFast( Device, x, y, Color );
+            GDS_DrawPixelFast( Device, x, y, Color );
         }
 
         if ( Error > 0 ) {
@@ -150,7 +106,7 @@ static inline void IRAM_ATTR DrawWideLine( struct GDS_Device* Device, int x0, in
     }
 }
 
-static inline void IRAM_ATTR DrawTallLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
+static inline void DrawTallLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
     int dx = ( x1 - x0 );
     int dy = ( y1 - y0 );
     int Error = 0;
@@ -167,7 +123,7 @@ static inline void IRAM_ATTR DrawTallLine( struct GDS_Device* Device, int x0, in
 
     for ( ; y < y1; y++ ) {
         if ( IsPixelVisible( Device, x, y ) == true ) {
-            Device->DrawPixelFast( Device, x, y, Color );
+            GDS_DrawPixelFast( Device, x, y, Color );
         }
 
         if ( Error > 0 ) {
@@ -179,7 +135,7 @@ static inline void IRAM_ATTR DrawTallLine( struct GDS_Device* Device, int x0, in
     }
 }
 
-void IRAM_ATTR GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
+void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color ) {
     if ( x0 == x1 ) {
         GDS_DrawVLine( Device, x0, y0, ( y1 - y0 ), Color );
     } else if ( y0 == y1 ) {
@@ -206,7 +162,7 @@ void IRAM_ATTR GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1,
     }
 }
 
-void IRAM_ATTR GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill ) {
+void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill ) {
     int Width = ( x2 - x1 );
     int Height = ( y2 - y1 );
 
@@ -236,57 +192,122 @@ void IRAM_ATTR GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, i
 /****************************************************************************************
  * Process graphic display data from column-oriented data (MSbit first)
  */
-void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height) {
+void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
 	if (!Height) Height = Device->Height;
 	if (!Width) Width = Device->Width;
-
-	// need to do row/col swap and bit-reverse
-	int Rows = Height / 8;
-	for (int r = 0; r < Rows; r++) {
-		uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
-		for (int c = Width; --c >= 0;) {
-			*optr++ = BitReverseTable256[*iptr];;
-			iptr += Rows;
-		}	
+	Height >>= 3;
+		
+	if (Device->DrawBitmapCBR) {
+		Device->DrawBitmapCBR( Device, Data, Width, Height, Color );
+	} else if (Device->Depth == 1) {
+		// need to do row/col swap and bit-reverse
+		for (int r = 0; r < Height; r++) {
+			uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
+			for (int c = Width; --c >= 0;) {
+				*optr++ = BitReverseTable256[*iptr];
+				iptr += Height;
+			}	
+		}
+	} else if (Device->Depth == 4)	{
+		uint8_t *optr = Device->Framebuffer;
+		int LineLen = Device->Width >> 1;
+		for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
+			uint8_t Byte = BitReverseTable256[*Data++];
+			// we need to linearize code to let compiler better optimize
+			if (c & 0x01) {
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0x0f) | (((Byte & 0x01)*Color)<<4); optr += LineLen; Byte >>= 1;
+			} else {
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+				*optr = (*optr & 0xf0) | (((Byte & 0x01)*Color)); optr += LineLen; Byte >>= 1;
+			}	
+			// end of a column, move to next one
+			if (++r == Height) { c++; r = 0; optr = Device->Framebuffer + (c >> 1); }		
+		}
+	} else {
+		// don't know bitdepth, use brute-force solution
+		for (int i = Width * Height, r = 0, c = 0; --i >= 0;) {
+			uint8_t Byte = *Data++;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 7, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 6, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 5, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 4, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 3, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 2, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 1, (Byte & 0x01) * Color ); Byte >>= 1;
+			GDS_DrawPixelFast( Device, c, (r << 3) + 0, (Byte & 0x01) * Color ); 
+			if (++r == Height) { c++; r = 0; }			
+		}
+		/* for better understanding, here is the mundane version 
+		for (int x = 0; x < Width; x++) {
+			for (int y = 0; y < Height; y++) {
+				uint8_t Byte = *Data++;
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 0, ((Byte >> 7) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 1, ((Byte >> 6) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 2, ((Byte >> 5) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 3, ((Byte >> 4) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 4, ((Byte >> 3) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 5, ((Byte >> 2) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 6, ((Byte >> 1) & 0x01) * Color );
+				GDS_DrawPixel4Fast( Device, x, y * 8 + 7, ((Byte >> 0) & 0x01) * Color );
+			}
+		}
+		*/
 	}
 	
 	Device->Dirty = true;
 }
 
 /****************************************************************************************
- * Process graphic display data MSBit first
- * WARNING: this has not been tested yet
+ * Simply draw a RGB565 image
+ * monoschrome (0.2125 * color.r) + (0.7154 * color.g) + (0.0721 * color.b)
+ * grayscale (0.3 * R) + (0.59 * G) + (0.11 * B) )
  */
- /*
-static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data) {
-	// default end point to display size
-	if (x2 == -1) x2 = Display.Width - 1;
-	if (y2 == -1) y2 = Display.Height - 1;
-	
-	display->dirty = true;
-	
-	//	not a boundary draw
-	// same comment about bit depth
-	if (y1 % 8 || y2 % 8 || x1 % 8 | x2 % 8) {
-		ESP_LOGW(TAG, "can't write on non cols/rows boundaries for now");
-	} else {	
-		// set addressing mode to match data
-		if (by_column) {
-			// copy the window and do row/col exchange
-			for (int r = y1/8; r <=  y2/8; r++) {
-				uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r;
-				for (int c = x1; c <= x2; c++) {
-					*optr++ = MSb ? BitReverseTable256[*iptr] : *iptr;
-					iptr += (y2-y1)/8 + 1;
+void GDS_DrawRGB16( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image ) {
+	if (Device->DrawRGB16) {
+		Device->DrawRGB16( Device, x, y, Width, Height, RGB_Mode, Image );
+	} else {
+		int Scale = Device->Depth < 5 ? 5 - Device->Depth : 0;
+		switch(RGB_Mode) {
+		case GDS_RGB565:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[r][c];
+					pixel = ((pixel & 0x1f) * 11 + ((((pixel >> 5) & 0x3f)  * 59) >> 1) + (pixel >> 11) * 30) / 100;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
+				}	
 			}	
+			break;
+		case GDS_RGB555:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[r][c];
+					pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f)  * 59 + (pixel >> 10) * 30) / 100;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
+				}	
 			}	
-		} else {
-			// just copy the window inside the frame buffer
-			for (int r = y1/8; r <= y2/8; r++) {
-				uint8_t *optr = Display.Framebuffer + r*Display.Width + x1, *iptr = data + r*(x2-x1+1);
-				for (int c = x1; c <= x2; c++) *optr++ = *iptr++;
+			break;
+		case GDS_RGB444:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[r][c];
+					pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f)  * 59 + (pixel >> 8) * 30;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1));
+				}	
 			}	
+			break;				
 		}
-	}	
-}
-*/
+	}	 
+}	

+ 13 - 6
components/display/core/gds_draw.h

@@ -11,13 +11,20 @@ extern "C" {
 
 struct GDS_Device;
 
-void IRAM_ATTR GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
-void IRAM_ATTR GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
-void IRAM_ATTR GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
-void IRAM_ATTR GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
+enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
 
-// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0
-void IRAM_ATTR GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height);
+#ifndef _GDS_PRIVATE_H_
+void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
+void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int X, int Y, int Color );
+#endif
+void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Color );
+void GDS_DrawVLine( struct GDS_Device* Device, int x, int y, int Height, int Color );
+void GDS_DrawLine( struct GDS_Device* Device, int x0, int y0, int x1, int y1, int Color );
+void GDS_DrawBox( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color, bool Fill );
+void GDS_DrawRGB16( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image );
+
+// draw a bitmap with source 1-bit depth organized in column and col0 = bit7 of byte 0 
+void GDS_DrawBitmapCBR( struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color);
 
 #ifdef __cplusplus
 }

+ 2 - 2
components/display/core/gds_font.c

@@ -9,10 +9,10 @@
 #include <string.h>
 #include <stdint.h>
 #include <math.h>
+#include "gds_private.h"
 #include "gds.h"
-#include "gds_draw.h"
 #include "gds_font.h"
-#include "gds_private.h"
+#include "gds_draw.h"
 #include "gds_err.h"
 
 static int RoundUpFontHeight( const struct GDS_FontDef* Font ) {

+ 49 - 17
components/display/core/gds_private.h

@@ -1,4 +1,5 @@
-#pragma once
+#ifndef _GDS_PRIVATE_H_
+#define _GDS_PRIVATE_H_
 
 #include <stdint.h>
 #include <stdbool.h>
@@ -10,8 +11,6 @@
 #define GDS_CLIPDEBUG_WARNING 1
 #define GDS_CLIPDEBUG_ERROR 2
 
-#define SHADOW_BUFFER
-
 #if CONFIG_GDS_CLIPDEBUG == GDS_CLIPDEBUG_NONE
     /*
      * Clip silently with no console output.
@@ -41,7 +40,7 @@
 #define MAX_LINES	8
 
 #if ! defined BIT
-#define BIT( n ) ( 1 << n )
+#define BIT( n ) ( 1 << ( n ) )
 #endif
 
 struct GDS_Device;
@@ -94,25 +93,31 @@ struct GDS_Device {
     bool FontForceMonospace;
 
 	// various driver-specific method
+	// must always provide 
 	bool (*Init)( struct GDS_Device* Device);
 	void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast );
 	void (*DisplayOn)( struct GDS_Device* Device );
 	void (*DisplayOff)( struct GDS_Device* Device );
-	void (*Update)( struct GDS_Device* Device );
-	void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color );
 	void (*SetHFlip)( struct GDS_Device* Device, bool On );
 	void (*SetVFlip)( struct GDS_Device* Device, bool On );
-	    
+	void (*Update)( struct GDS_Device* Device );
+	// must provide for depth other than 1 (vertical) and 4 (may provide for optimization)
+	void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color );
+	void (*DrawBitmapCBR)(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color );
+	// may provide for optimization
+	void (*DrawRGB16)( struct GDS_Device* Device, int x, int y, int Width, int Height, int RGB_Mode, uint16_t **Image );
+	void (*ClearWindow)( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color );
+		    
 	// interface-specific methods	
     WriteCommandProc WriteCommand;
     WriteDataProc WriteData;
+
+	// 16 bytes for whatever the driver wants (should be aligned as it's 32 bits)	
+	uint32_t Private[4];
 };
 
 bool GDS_Reset( struct GDS_Device* Device );
 
-void IRAM_ATTR 	GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color );
-void IRAM_ATTR 	GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color );
-
 inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y )  {
     bool Result = (
         ( x >= 0 ) &&
@@ -130,17 +135,44 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y )  {
     return Result;
 }
 
-inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
-    if ( IsPixelVisible( Device, x, y ) == true ) {
-        Device->DrawPixelFast( Device, x, y, Color );
+inline void IRAM_ATTR GDS_DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
+    uint32_t YBit = ( Y & 0x07 );
+    uint8_t* FBOffset = NULL;
+
+    /* 
+     * We only need to modify the Y coordinate since the pitch
+     * of the screen is the same as the width.
+     * Dividing Y by 8 gives us which row the pixel is in but not
+     * the bit position.
+     */
+    Y>>= 3;
+
+    FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
+
+    if ( Color == GDS_COLOR_XOR ) {
+        *FBOffset ^= BIT( YBit );
+    } else {
+        *FBOffset = ( Color == GDS_COLOR_WHITE ) ? *FBOffset | BIT( YBit ) : *FBOffset & ~BIT( YBit );
     }
 }
 
-inline void IRAM_ATTR GDS_DrawPixel4( struct GDS_Device* Device, int x, int y, int Color ) {
-    if ( IsPixelVisible( Device, x, y ) == true ) {
-        Device->DrawPixelFast( Device, x, y, Color );
-    }
+inline void IRAM_ATTR GDS_DrawPixel4Fast( struct GDS_Device* Device, int X, int Y, int Color ) {
+	uint8_t* FBOffset;
+
+    FBOffset = Device->Framebuffer + ( (Y * Device->Width >> 1) + (X >> 1));
+	*FBOffset = X & 0x01 ? (*FBOffset & 0x0f) | (Color << 4) : ((*FBOffset & 0xf0) | Color);
 }
 
+inline void IRAM_ATTR GDS_DrawPixelFast( struct GDS_Device* Device, int X, int Y, int Color ) {
+    if (Device->DrawPixelFast) Device->DrawPixelFast( Device, X, Y, Color );
+	else if (Device->Depth == 4) GDS_DrawPixel4Fast( Device, X, Y, Color);
+	else if (Device->Depth == 1) GDS_DrawPixel1Fast( Device, X, Y, Color);
+}	
 
+inline void IRAM_ATTR GDS_DrawPixel( struct GDS_Device* Device, int x, int y, int Color ) {
+    if ( IsPixelVisible( Device, x, y ) == true ) {
+        GDS_DrawPixelFast( Device, x, y, Color );
+    }
+}
 
+#endif

+ 3 - 2
components/display/core/gds_text.c

@@ -22,10 +22,11 @@
 #include <stdint.h>
 #include <arpa/inet.h>
 #include "esp_log.h"
+
+#include "gds_private.h"
 #include "gds.h"
 #include "gds_draw.h"
 #include "gds_text.h"
-#include "gds_private.h"
 
 #define max(a,b) (((a) > (b)) ? (a) : (b))
 
@@ -109,7 +110,7 @@ bool GDS_TextLine(struct GDS_Device* Device, int N, int Pos, int Attr, char *Tex
 		int Y_min = max(0, Device->Lines[N].Y), Y_max = max(0, Device->Lines[N].Y + Device->Lines[N].Font->Height);
 		for (int c = (Attr & GDS_TEXT_CLEAR_EOL) ? X : 0; c < Device->Width; c++) 
 			for (int y = Y_min; y < Y_max; y++)
-				Device->DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
+				GDS_DrawPixelFast( Device, c, y, GDS_COLOR_BLACK );
 	}
 		
 	GDS_FontDrawString( Device, X, Device->Lines[N].Y, Text, GDS_COLOR_WHITE );

+ 4 - 5
components/display/display.c

@@ -58,10 +58,9 @@ static EXT_RAM_ATTR struct {
 
 static void displayer_task(void *args);
 
-struct GDS_Device *display;
-struct GDS_Device* SSD1306_Detect(char *Driver, struct GDS_Device* Device);
-struct GDS_Device* SH1106_Detect(char *Driver, struct GDS_Device* Device);
-GDS_DetectFunc drivers[] = { SH1106_Detect, SSD1306_Detect, NULL };
+struct GDS_Device *display;   
+extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect;
+GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, NULL };
 
 /****************************************************************************************
  * 
@@ -84,7 +83,7 @@ void display_init(char *welcome) {
 		
 	if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
 	if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
-		
+			
 	// so far so good
 	if (display && width > 0 && height > 0) {
 		// Detect driver interface

+ 2 - 2
components/services/accessors.c

@@ -89,7 +89,7 @@ const spi_bus_config_t * config_spi_get(spi_host_device_t * spi_host) {
  * 
  */
 void parse_set_GPIO(void (*cb)(int gpio, char *value)) {
-	char *nvs_item, *p, type[4];
+	char *nvs_item, *p, type[16];
 	int gpio;
 	
 	if ((nvs_item = config_alloc_get(NVS_TYPE_STR, "set_GPIO")) == NULL) return;
@@ -97,7 +97,7 @@ void parse_set_GPIO(void (*cb)(int gpio, char *value)) {
 	p = nvs_item;
 	
 	do {
-		if (sscanf(p, "%d=%3[^,]", &gpio, type) > 0) cb(gpio, type);
+		if (sscanf(p, "%d=%15[^,]", &gpio, type) > 0) cb(gpio, type);
 		p = strchr(p, ',');
 	} while (p++);
 	

+ 1 - 1
components/services/led.c

@@ -164,7 +164,7 @@ void set_led_gpio(int gpio, char *value) {
 		green.gpio = gpio;
 		if ((p = strchr(value, ':')) != NULL) green.active = atoi(p + 1);
 	} else 	if (strcasestr(value, "red")) {
-		red.active = gpio;
+		red.gpio = gpio;
 		if ((p = strchr(value, ':')) != NULL) red.active = atoi(p + 1);
 	}	
 }

+ 5 - 5
components/squeezelite/a1s/ac101.c

@@ -71,7 +71,7 @@ static int i2c_port;
  * init
  */
 static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {	 
-	esp_err_t res;
+	esp_err_t res = ESP_OK;
 	
 	i2c_port = i2c_port_num;
 
@@ -144,8 +144,8 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
 	i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { 	.bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO, 
 															.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = CONFIG_I2S_DI_IO
 								};
-	i2s_driver_install(i2s_num, i2s_config, 0, NULL);
-	i2s_set_pin(i2s_num, &i2s_pin_config);
+	res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL);
+	res |= i2s_set_pin(i2s_num, &i2s_pin_config);
 	
 	// enable earphone & speaker
 	i2c_write_reg(SPKOUT_CTRL, 0x0220);
@@ -156,9 +156,9 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config) {
 	ac101_set_spk_volume(100);
 	ac101_set_earph_volume(100);
 	
-	ESP_LOGI(TAG, "DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
+	ESP_LOGI(TAG, "DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
 
-	return true;
+	return (res == ESP_OK);
 }	
 
 /****************************************************************************************

+ 23 - 13
components/squeezelite/display.c

@@ -233,8 +233,8 @@ bool sb_display_init(void) {
 	displayer.mutex = xSemaphoreCreateMutex();
 	displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer);
 	
-	// size scroller
-	scroller.scroll.max = (displayer.width * displayer.height / 8) * 10;
+	// size scroller (width + current screen)
+	scroller.scroll.max = (displayer.width * displayer.height / 8) * (10 + 1);
 	scroller.scroll.frame = malloc(scroller.scroll.max);
 	scroller.back.frame = malloc(displayer.width * displayer.height / 8);
 	scroller.frame = malloc(displayer.width * displayer.height / 8);
@@ -500,8 +500,9 @@ static void grfe_handler( u8_t *data, int len) {
 			displayer.dirty = false;
 		}	
 	
-		// draw new frame
-		GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), displayer.width, displayer.height);
+		// draw new frame, it might be less than full screen (small visu)
+		int width = ((len - sizeof(struct grfe_packet)) * 8) / displayer.height;
+		GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), width, displayer.height, GDS_COLOR_WHITE);
 		GDS_Update(display);
 	}	
 	
@@ -561,7 +562,7 @@ static void grfs_handler(u8_t *data, int len) {
 		
 		// background excludes space taken by visu (if any)
 		scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0);
-
+		
 		// set scroller steps & beginning
 		if (pkt->direction == 1) {
 			scroller.scrolled = 0;
@@ -580,7 +581,8 @@ static void grfs_handler(u8_t *data, int len) {
 		scroller.scroll.size = offset + size;
 		LOG_INFO("scroller current size %u", scroller.scroll.size);
 	} else {
-		LOG_INFO("scroller too larger %u/%u", scroller.scroll.size + size, scroller.scroll.max);
+		LOG_INFO("scroller too larger %u/%u/%u", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width);
+		scroller.scroll.width = scroller.scroll.size / (displayer.height / 8);
 	}	
 }
 
@@ -598,13 +600,13 @@ static void grfg_handler(u8_t *data, int len) {
 	scroller.width = htons(pkt->width);
 	memcpy(scroller.back.frame, data + sizeof(struct grfg_packet), len - sizeof(struct grfg_packet));
 		
-	// update display asynchronously (frames are oganized by columns)
+	// update display asynchronously (frames are organized by columns)
 	memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
 	for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
 	
 	// can only write if we really own display
 	if (displayer.owned) {
-		GDS_DrawBitmapCBR(display, scroller.frame, scroller.back.width, displayer.height);
+		GDS_DrawBitmapCBR(display, scroller.frame, scroller.back.width, displayer.height, GDS_COLOR_WHITE);
 		GDS_Update(display);
 	}	
 		
@@ -636,7 +638,7 @@ static void visu_update(void) {
 	// reset bars for all cases first	
 	for (int i = visu.n; --i >= 0;) visu.bars[i].current = 0;
 	
-	if (visu_export.running && visu_export.running) {
+	if (visu_export.running) {
 					
 		if (visu.mode == VISU_VUMETER) {
 			s16_t *iptr = visu_export.buffer;
@@ -704,15 +706,20 @@ static void visu_update(void) {
 	visu_export.level = 0;
 	pthread_mutex_unlock(&visu_export.mutex);
 
-	GDS_ClearExt(display, false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
-	
+	// don't refresh screen if all max are 0 (we were are somewhat idle)
+	int clear = 0;
+	for (int i = visu.n; --i >= 0;) clear = max(clear, visu.bars[i].max);
+	if (clear) GDS_ClearExt(display, false, false, visu.col, visu.row, visu.col + visu.width - 1, visu.row + visu.height - 1);
+
+	// there is much more optimization to be done here, like not redrawing bars unless needed
 	for (int i = visu.n; --i >= 0;) {
 		int x1 = visu.col + visu.border + visu.bar_border + i*(visu.bar_width + visu.bar_gap);
 		int y1 = visu.row + visu.height - 1;
 			
 		if (visu.bars[i].current > visu.bars[i].max) visu.bars[i].max = visu.bars[i].current;
 		else if (visu.bars[i].max) visu.bars[i].max--;
-			
+		else if (!clear) continue;
+		
 		for (int j = 0; j <= visu.bars[i].current; j += 2) 
 			GDS_DrawLine(display, x1, y1 - j, x1 + visu.bar_width - 1, y1 - j, GDS_COLOR_WHITE);
 			
@@ -773,6 +780,9 @@ static void visu_handler( u8_t *data, int len) {
 			visu.border =  htonl(pkt->border);
 			bars = htonl(pkt->bars);
 			visu.spectrum_scale = htonl(pkt->spectrum_scale) / 100.;
+			
+			// might have a race condition with scroller message, so update width in case
+			if (scroller.active) scroller.back.width = displayer.width - visu.width;
 		} else {
 			// full screen visu, try to use bottom screen if available
 			visu.width = displayer.width;
@@ -855,7 +865,7 @@ static void displayer_task(void *args) {
 				memcpy(scroller.frame, scroller.back.frame, scroller.back.width * displayer.height / 8);
 				for (int i = 0; i < scroller.width * displayer.height / 8; i++) scroller.frame[i] |= scroller.scroll.frame[scroller.scrolled * displayer.height / 8 + i];
 				scroller.scrolled += scroller.by;
-				if (displayer.owned) GDS_DrawBitmapCBR(display, scroller.frame, scroller.width, displayer.height);	
+				if (displayer.owned) GDS_DrawBitmapCBR(display, scroller.frame, scroller.width, displayer.height, GDS_COLOR_WHITE);	
 				
 				// short sleep & don't need background update
 				scroller.wake = scroller.speed;

+ 3 - 3
components/squeezelite/output_i2s.c

@@ -252,9 +252,9 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
 		// finally let DAC driver initialize I2C and I2S
 		if (dac_tas57xx.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) adac = &dac_tas57xx;
 		else if (dac_a1s.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) adac = &dac_a1s;
-		else {
-			dac_external.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config);
-			adac = &dac_external;
+		else if (!dac_external.init(I2C_PORT, CONFIG_I2S_NUM, &i2s_config)) {
+			LOG_WARN("DAC not configured and SPDIF not enabled, I2S will not continue");
+			return;
 		}
 	}	
 

+ 16 - 15
components/squeezelite/tas57xx/dac_57xx.c

@@ -27,7 +27,9 @@
 #include "driver/gpio.h"
 #include "adac.h"
 
-#define VOLUME_GPIO	14
+// this is the only hard-wired thing
+#define VOLUME_GPIO	14	
+
 #define TAS575x 0x98
 #define TAS578x	0x90
 
@@ -79,7 +81,7 @@ static int tas57_detect(void);
  */
 static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config)	{	 
 	i2c_port = i2c_port_num;
-	
+		
 	// configure i2c
 	i2c_config_t i2c_config = {
 			.mode = I2C_MODE_MASTER,
@@ -104,11 +106,6 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config)	{
 	
 	LOG_INFO("TAS57xx DAC using I2C sda:%u, scl:%u", i2c_config.sda_io_num, i2c_config.scl_io_num);
 	
-	// init volume & mute
-	gpio_pad_select_gpio(VOLUME_GPIO);
-	gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
-	gpio_set_level(VOLUME_GPIO, 0);
-
 	i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
 	
 	for (int i = 0; tas57xx_init_sequence[i].reg != 0xff; i++) {
@@ -121,22 +118,26 @@ static bool init(int i2c_port_num, int i2s_num, i2s_config_t *i2s_config)	{
 	}
 
 	i2c_master_stop(i2c_cmd);	
-	esp_err_t ret = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
+	esp_err_t res = i2c_master_cmd_begin(i2c_port, i2c_cmd, 500 / portTICK_RATE_MS);
     i2c_cmd_link_delete(i2c_cmd);
 
 	// configure I2S pins & install driver	
 	i2s_pin_config_t i2s_pin_config = (i2s_pin_config_t) { 	.bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO, 
 														.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = CONFIG_I2S_DI_IO,
 								};
-	i2s_driver_install(i2s_num, i2s_config, 0, NULL);
-	i2s_set_pin(i2s_num, &i2s_pin_config);
-	LOG_INFO("DAC using I2S bck:%u, ws:%u, do:%u", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
+	res |= i2s_driver_install(i2s_num, i2s_config, 0, NULL);
+	res |= i2s_set_pin(i2s_num, &i2s_pin_config);
+	LOG_INFO("DAC using I2S bck:%d, ws:%d, do:%d", i2s_pin_config.bck_io_num, i2s_pin_config.ws_io_num, i2s_pin_config.data_out_num);
 	
-	if (ret != ESP_OK) {
-		LOG_ERROR("could not intialize TAS57xx %d", ret);
-		return false;
-	} else {
+	if (res == ESP_OK) {
+		// init volume & mute
+		gpio_pad_select_gpio(VOLUME_GPIO);
+		gpio_set_direction(VOLUME_GPIO, GPIO_MODE_OUTPUT);
+		gpio_set_level(VOLUME_GPIO, 0);
 		return true;
+	} else {
+		LOG_ERROR("could not intialize TAS57xx %d", res);
+		return false;
 	}	
 }