浏览代码

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

Sebastien 5 年之前
父节点
当前提交
d1e46104ae

+ 5 - 3
README.md

@@ -30,7 +30,7 @@ Use the `squeezelite-esp32-I2S-4MFlash-sdkconfig.defaults` configuration file.
 To access NVS, in the webUI, go to credits and select "shows nvs editor". Go into the NVS editor tab to change NFS parameters. In syntax description below \<\> means a value while \[\] describe optional parameters. 
 
 ### I2C
-The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000. Syntax is
+The NVS parameter "i2c_config" set the i2c's gpio used for generic purpose (e.g. display). Leave it blank to disable I2C usage. Note that on SqueezeAMP, port must be 1. Default speed is 400000 but some display can do up to 800000 or more. Syntax is
 ```
 sda=<gpio>,scl=<gpio>[,port=0|1][,speed=<speed>]
 ```
@@ -62,16 +62,18 @@ I2C,width=<pixels>,height=<pixels>[address=<i2c_address>][,HFlip][,VFlip][driver
 SPI,width=<pixels>,height=<pixels>,cs=<gpio>[,speed=<speed>][,HFlip][,VFlip][driver=SSD1306|SSD1326|SH1106]
 ```
 - VFlip and HFlip are optional can be used to change display orientation
-- Default speed is 8000000 (8MHz)
+- Default speed is 8000000 (8MHz) but SPI can work up to 26MHz or even 40MHz
 
 Currently 128x32/64 I2C display like [this](https://www.buydisplay.com/i2c-blue-0-91-inch-oled-display-module-128x32-arduino-raspberry-pi) and [this](https://www.waveshare.com/wiki/1.3inch_OLED_HAT) are supported
 
 The NVS parameter "metadata_config" sets how metadata is displayed for AirPlay and Bluetooth. Syntax is
 ```
-[format=<display_content>][,speed=<speed>]
+[format=<display_content>][,speed=<speed>][,pause=<pause>]
 ```
 - 'speed' is the scrolling speed in ms (default is 33ms)
 
+- 'pause' is the pause time between scrolls in ms (default is 3600ms)
+
 - 'format' can contain free text and any of the 3 keywords %artist%, %album%, %title%. Using that format string, the keywords are replaced by their value to build the string to be displayed. Note that the plain text following a keyword that happens to be empty during playback of a track will be removed. For example, if you have set format=%artist% - %title% and there is no artist in the metadata then only <title> will be displayed not " - <title>".
 
 ### Set GPIO

+ 20 - 0
components/display/SSD132x.c

@@ -165,6 +165,25 @@ static void IRAM_ATTR DrawPixel1Fast( struct GDS_Device* Device, int X, int Y, i
     }
 }
 
+static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
+	uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff;
+	uint8_t Width = Device->Width >> 3;
+	uint8_t *optr = Device->Framebuffer;
+
+	for (int r = y1; r <= y2; r++) {
+		int c = x1;
+		// for a row that is not on a boundary, not column opt can be done, so handle all columns on that line
+		while (c & 0x07 && c <= x2) DrawPixel1Fast( Device, c++, r, Color );
+		// at this point we are aligned on column boundary
+		int chunk = (x2 - c + 1) >> 3;
+		memset(optr + Width * r + (c >> 3), _Color, chunk );
+		c += chunk * 8;
+		while (c <= x2) DrawPixel1Fast( Device, c++, r, Color );
+	}
+	
+	Device->Dirty = true;
+}
+
 static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
 	uint8_t *optr = Device->Framebuffer;
 	
@@ -312,6 +331,7 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) {
 		Device->Update = Update1;
 		Device->DrawPixelFast = DrawPixel1Fast;
 		Device->DrawBitmapCBR = DrawBitmapCBR;
+		Device->ClearWindow = ClearWindow;
 	} else {
 		Device->Depth = 4;
 	}	

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

@@ -69,21 +69,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
 			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;
+			uint8_t Width = Device->Width >> 3;
+			uint8_t *optr = Device->Framebuffer;
 			// 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);
+					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);
+				if (r + 8 <= y2) {
+					memset(optr + Width * r + x1, _Color, x2 - x1 + 1);
 					r += 8;
 				} else while (r <= y2) {
-					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color);
+					for (c = x1; c <= x2; c++) GDS_DrawPixelFast( Device, c, r, Color );
 					r++;
 				}
 			}
@@ -95,12 +96,13 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2,
 		} else {
 			uint8_t _Color = Color | (Color << 4);
 			uint8_t Width = Device->Width;
+			uint8_t *optr = Device->Framebuffer;
 			// 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);
+				memset(optr + ((r * Width + c)  >> 1), _Color, chunk);
 				if (c + chunk <= x2) GDS_DrawPixelFast( Device, x2, r, Color);
 			}
 		}	

+ 1 - 1
components/display/core/gds_default_if.h

@@ -7,7 +7,7 @@ extern "C" {
 
 struct GDS_Device;
 
-bool GDS_I2CInit( int PortNumber, int SDA, int SCL );
+bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int speed );
 bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin );
 
 bool GDS_SPIInit( int SPI, int DC );

+ 1 - 43
components/display/core/gds_draw.c

@@ -268,46 +268,4 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int
 	}
 	
 	Device->Dirty = true;
-}
-
-/****************************************************************************************
- * 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) )
- */
-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);
-				}	
-			}	
-			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;				
-		}
-	}	 
-}	
+}

+ 0 - 5
components/display/core/gds_draw.h

@@ -9,10 +9,6 @@
 extern "C" {
 #endif
 
-struct GDS_Device;
-
-enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
-
 #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 );
@@ -21,7 +17,6 @@ void GDS_DrawHLine( struct GDS_Device* Device, int x, int y, int Width, int Colo
 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);

+ 228 - 0
components/display/core/gds_image.c

@@ -0,0 +1,228 @@
+#include <string.h>
+#include "math.h"
+#include "esp32/rom/tjpgd.h"
+#include "esp_log.h"
+
+#include "gds.h"
+#include "gds_private.h"
+#include "gds_image.h"
+
+const char TAG[] = "ImageDec";
+
+#define SCRATCH_SIZE	3100
+
+//Data that is passed from the decoder function to the infunc/outfunc functions.
+typedef struct {
+    const unsigned char *InData;	// Pointer to jpeg data
+    int InPos;						// Current position in jpeg data
+	int Width, Height;	
+	union {
+		uint16_t *OutData;				// Decompress
+		struct {						// DirectDraw
+			struct GDS_Device * Device;
+			int XOfs, YOfs;
+			int Depth;
+		};	
+	};	
+} JpegCtx;
+
+static unsigned InHandler(JDEC *Decoder, uint8_t *Buf, unsigned Len) {
+    JpegCtx *Context = (JpegCtx*) Decoder->device;
+    if (Buf) memcpy(Buf, Context->InData +  Context->InPos, Len);
+    Context->InPos += Len;
+    return Len;
+}
+
+static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
+	JpegCtx *Context = (JpegCtx*) Decoder->device;
+    uint8_t *Pixels = (uint8_t*) Bitmap;
+	
+    for (int y = Frame->top; y <= Frame->bottom; y++) {
+		if (y < Context->YOfs) continue;
+        for (int x = Frame->left; x <= Frame->right; x++) {
+			if (x < Context->XOfs) continue;
+            // Convert the 888 to RGB565
+            uint16_t Value = (*Pixels++ & ~0x07) << 8;
+            Value |= (*Pixels++ & ~0x03) << 3;
+            Value |= *Pixels++ >> 3;
+            Context->OutData[Context->Width * y + x] = Value;
+        }
+    }
+    return 1;
+}
+
+static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) {
+	JpegCtx *Context = (JpegCtx*) Decoder->device;
+    uint8_t *Pixels = (uint8_t*) Bitmap;
+	int Shift = 8 - Context->Depth;
+	
+    for (int y = Frame->top; y <= Frame->bottom; y++) {
+        for (int x = Frame->left; x <= Frame->right; x++) {
+            // Convert the 888 to RGB565
+            int Value = ((Pixels[0]*11 + Pixels[1]*59 + Pixels[2]*30) / 100) >> Shift;
+			Pixels += 3;
+			// used DrawPixel and not "fast" version as X,Y may be beyond screen
+			GDS_DrawPixel( Context->Device, Context->XOfs + x, Context->YOfs + y, Value);
+        }
+    }
+    return 1;
+}
+
+//Decode the embedded image into pixel lines that can be used with the rest of the logic.
+static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale, bool SizeOnly) {
+    JDEC Decoder;
+    JpegCtx Context;
+	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	
+    if (!Scratch) {
+        ESP_LOGE(TAG, "Cannot allocate workspace");
+        return NULL;
+    }
+
+	Context.OutData = NULL;
+    Context.InData = Source;
+    Context.InPos = 0;
+	        
+    //Prepare and decode the jpeg.
+    int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
+	if (Width) *Width = Decoder.width;
+	if (Height) *Height = Decoder.height;
+	Decoder.scale = Scale;
+
+    if (Res == JDR_OK && !SizeOnly) {
+		// find the scaling factor
+		Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t));
+		uint8_t N = 0, ScaleInt =  ceil(1.0 / Scale);
+		ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++;
+		while (ScaleInt >>= 1) N++;
+		
+		// ready to decode		
+		if (Context.OutData) {
+			Context.Width = Decoder.width / (1 << N);
+			Context.Height = Decoder.height / (1 << N);
+			if (Width) *Width = Context.Width;
+			if (Height) *Height = Context.Height;
+			Res = jd_decomp(&Decoder, OutHandler, N > 3 ? 3 : N);
+			if (Res != JDR_OK) {
+				ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
+			}	
+		} else {
+			ESP_LOGE(TAG, "Can't allocate bitmap %dx%d", Decoder.width, Decoder.height);			
+		}	
+	} else if (!SizeOnly) {
+        ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
+    }    
+      
+	// free scratch area
+    if (Scratch) free(Scratch);
+    return Context.OutData;
+}
+
+uint16_t* GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale) {
+	return DecodeJPEG(Source, Width, Height, Scale, false);
+}	
+
+void GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height) {
+	DecodeJPEG(Source, Width, Height, 1, true);
+}	
+
+/****************************************************************************************
+ * 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) )
+ */
+void GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode ) {
+	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[Width*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[Width*r + c];
+					pixel = ((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f)  * 59 + (pixel >> 10) * 30) / 100;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> Scale);
+				}	
+			}	
+			break;
+		case GDS_RGB444:
+			for (int c = 0; c < Width; c++) {
+				for (int r = 0; r < Height; r++) {
+					int pixel = Image[Width*r + c];
+					pixel = (pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f)  * 59 + (pixel >> 8) * 30;
+					GDS_DrawPixel( Device, c + x, r + y, pixel >> (Scale - 1));
+				}	
+			}	
+			break;				
+		}
+	}	 
+}	
+
+//Decode the embedded image into pixel lines that can be used with the rest of the logic.
+bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit) {
+    JDEC Decoder;
+    JpegCtx Context;
+	bool Ret = false;
+	char *Scratch = calloc(SCRATCH_SIZE, 1);
+	
+    if (!Scratch) {
+        ESP_LOGE(TAG, "Cannot allocate workspace");
+        return NULL;
+    }
+
+    // Populate fields of the JpegCtx struct.
+    Context.InData = Source;
+    Context.InPos = 0;
+	Context.XOfs = x;
+	Context.YOfs = y;
+	Context.Device = Device;
+	Context.Depth = Device->Depth;
+        
+    //Prepare and decode the jpeg.
+    int Res = jd_prepare(&Decoder, InHandler, Scratch, SCRATCH_SIZE, (void*) &Context);
+	Context.Width = Decoder.width;
+	Context.Height = Decoder.height;
+	
+    if (Res == JDR_OK) {
+		uint8_t N = 0;
+		
+		// do we need to fit the image
+		if (Fit & GDS_IMAGE_FIT) {
+			float XRatio = (Device->Width - x) / (float) Decoder.width, YRatio = (Device->Height - y) / (float) Decoder.height;
+			uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio);
+			Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++;
+			while (Ratio >>= 1) N++;
+			Context.Width /= 1 << N;
+			Context.Height /= 1 << N;
+		} 
+		
+		// then place it
+		if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = x + ((Device->Width - x) - Context.Width) / 2;
+		if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = y + ((Device->Height - y) - Context.Height) / 2;
+		
+		// do decompress & draw
+		Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N);
+		if (Res == JDR_OK) {
+			Ret = true;
+		} else {	
+			ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res);
+		}	
+	} else {	
+        ESP_LOGE(TAG, "Image decoder: jd_prepare failed (%d)", Res);
+    }    
+      
+	// free scratch area
+    if (Scratch) free(Scratch);
+	return Ret;
+}
+

+ 23 - 0
components/display/core/gds_image.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <stdint.h>
+#include "esp_err.h"
+
+// no progressive JPEG handling
+
+struct GDS_Device;
+
+enum { GDS_RGB565, GDS_RGB555, GDS_RGB444 };
+
+#define GDS_IMAGE_TOP		0x00
+#define GDS_IMAGE_CENTER_X	0x01
+#define GDS_IMAGE_CENTER_Y	0x02
+#define GDS_IMAGE_CENTER	(GDS_IMAGE_CENTER_X | GDS_IMAGE_CENTER_Y)
+#define GDS_IMAGE_FIT		0x10
+
+// Width and Height can be NULL if you already know them (actual scaling is closest ^2)
+uint16_t* 	GDS_DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scale);
+void	 	GDS_GetJPEGSize(uint8_t *Source, int *Width, int *Height);
+void 		GDS_DrawRGB16( struct GDS_Device* Device, uint16_t *Image, int x, int y, int Width, int Height, int RGB_Mode );
+// set DisplayWidth and DisplayHeight to non-zero if you want autoscale to closest factor ^2 from 0..3
+bool 		GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int Fit);

+ 1 - 1
components/display/core/gds_private.h

@@ -105,7 +105,7 @@ struct GDS_Device {
 	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 (*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	

+ 6 - 3
components/display/core/ifaces/default_if_i2c.c

@@ -17,6 +17,7 @@
 #include "gds_default_if.h"
 
 static int I2CPortNumber;
+static int I2CWait;
 
 static const int GDS_I2C_COMMAND_MODE = 0x80;
 static const int GDS_I2C_DATA_MODE = 0x40;
@@ -31,9 +32,11 @@ static bool I2CDefaultWriteData( struct GDS_Device* Device, const uint8_t* Data,
  * 
  * Returns true on successful init of the i2c bus.
  */
-bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
+bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
 	I2CPortNumber = PortNumber;
 	
+	I2CWait = pdMS_TO_TICKS( Speed ? Speed / 4000 : 100 );
+	
 	if (SDA != -1 && SCL != -1) {
 		i2c_config_t Config = { 0 };
 
@@ -42,7 +45,7 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL ) {
 		Config.sda_pullup_en = GPIO_PULLUP_ENABLE;
 		Config.scl_io_num = SCL;
 		Config.scl_pullup_en = GPIO_PULLUP_ENABLE;
-		Config.master.clk_speed = 250000;
+		Config.master.clk_speed = Speed ? Speed : 400000;
 
 		ESP_ERROR_CHECK_NONFATAL( i2c_param_config( I2CPortNumber, &Config ), return false );
 		ESP_ERROR_CHECK_NONFATAL( i2c_driver_install( I2CPortNumber, Config.mode, 0, 0, 0 ), return false );
@@ -98,7 +101,7 @@ static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Da
         ESP_ERROR_CHECK_NONFATAL( i2c_master_write( CommandHandle, ( uint8_t* ) Data, DataLength, true ), goto error );
         ESP_ERROR_CHECK_NONFATAL( i2c_master_stop( CommandHandle ), goto error );
 
-        ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, pdMS_TO_TICKS( 1000 ) ), goto error );
+        ESP_ERROR_CHECK_NONFATAL( i2c_master_cmd_begin( I2CPortNumber, CommandHandle, I2CWait ), goto error );
         i2c_cmd_link_delete( CommandHandle );
     }
 

+ 3 - 2
components/display/display.c

@@ -93,7 +93,7 @@ void display_init(char *welcome) {
 			if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1);
 		
 			init = true;
-			GDS_I2CInit( i2c_system_port, -1, -1 ) ;
+			GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
 			GDS_I2CAttachDevice( display, width, height, address, -1 );
 		
 			ESP_LOGI(TAG, "Display is I2C on port %u", address);
@@ -274,8 +274,9 @@ void displayer_metadata(char *artist, char *album, char *title) {
 		strncpy(string, title ? title : "", SCROLLABLE_SIZE);
 	}
 	
-	// get optional scroll speed
+	// get optional scroll speed & pause
 	if ((p = strcasestr(displayer.metadata_config, "speed")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.speed);
+	if ((p = strcasestr(displayer.metadata_config, "pause")) != NULL) sscanf(p, "%*[^=]=%d", &displayer.pause);
 	
 	displayer.offset = 0;	
 	utf8_decode(displayer.string);

+ 3 - 1
components/services/accessors.c

@@ -45,9 +45,11 @@ const i2c_config_t * config_i2c_get(int * i2c_port) {
 		.sda_pullup_en = GPIO_PULLUP_ENABLE,
 		.scl_io_num = -1,
 		.scl_pullup_en = GPIO_PULLUP_ENABLE,
-		.master.clk_speed = 400000,
+		.master.clk_speed = 0,
 	};
 
+	i2c.master.clk_speed = i2c_system_speed;
+	
 	nvs_item = config_alloc_get(NVS_TYPE_STR, "i2c_config");
 	if (nvs_item) {
 		if ((p = strcasestr(nvs_item, "scl")) != NULL) i2c.scl_io_num = atoi(strchr(p, '=') + 1);

+ 1 - 0
components/services/globdefs.h

@@ -24,6 +24,7 @@
 #define SPI_SYSTEM_HOST	SPI2_HOST
 
 extern int i2c_system_port;
+extern int i2c_system_speed;
 extern int spi_system_host;
 extern int spi_system_dc_gpio;
 extern bool gpio36_39_used;

+ 1 - 0
components/services/services.c

@@ -23,6 +23,7 @@ extern void monitor_svc_init(void);
 extern void led_svc_init(void);
 
 int i2c_system_port = I2C_SYSTEM_PORT;
+int i2c_system_speed = 400000;
 int spi_system_host = SPI_SYSTEM_HOST;
 int spi_system_dc_gpio = -1;