소스 검색

driver for ST7735/89 & LED PWM

Philippe G 4 년 전
부모
커밋
3c76f6fcb5

+ 4 - 8
components/display/SSD1351.c

@@ -73,6 +73,7 @@ static void Update16( struct GDS_Device* Device ) {
 		LastCol = LastCol * 2 + 1;
 		SetRowAddress( Device, FirstRow, LastRow );
 		SetColumnAddress( Device, FirstCol, LastCol );
+		Device->WriteCommand( Device, ENABLE_WRITE );
 			
 		int ChunkSize = (LastCol - FirstCol + 1) * 2;
 			
@@ -83,12 +84,10 @@ static void Update16( struct GDS_Device* Device ) {
 				memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
 				optr += ChunkSize;
 				if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
-				Device->WriteCommand( Device, ENABLE_WRITE );
 				Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
 				optr = Private->iRAM;
 			}
 		} else for (int i = FirstRow; i <= LastRow; i++) {
-			Device->WriteCommand( Device, ENABLE_WRITE );
 			Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
 		}	
 
@@ -103,13 +102,12 @@ static void Update16( struct GDS_Device* Device ) {
 		int Height = min(Private->PageSize, Device->Height - r);
 		
 		SetRowAddress( Device, r, r + Height - 1 );
+		Device->WriteCommand(Device, ENABLE_WRITE);		
 		
 		if (Private->iRAM) {
 			memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
-			Device->WriteCommand(Device, ENABLE_WRITE);
 			Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
 		} else	{
-			Device->WriteCommand(Device, ENABLE_WRITE);
 			Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
 		}	
 	}	
@@ -142,6 +140,7 @@ static void Update24( struct GDS_Device* Device ) {
 		LastCol = (LastCol * 2 + 1) / 3; 
 		SetRowAddress( Device, FirstRow, LastRow );
 		SetColumnAddress( Device, FirstCol, LastCol );
+		Device->WriteCommand( Device, ENABLE_WRITE );
 			
 		int ChunkSize = (LastCol - FirstCol + 1) * 3;
 					
@@ -152,12 +151,10 @@ static void Update24( struct GDS_Device* Device ) {
 				memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
 				optr += ChunkSize;
 				if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
-				Device->WriteCommand( Device, ENABLE_WRITE );
 				Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
 				optr = Private->iRAM;
 			}	
 		} else for (int i = FirstRow; i <= LastRow; i++) {
-			Device->WriteCommand( Device, ENABLE_WRITE );
 			Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize );
 		}	
 
@@ -167,15 +164,14 @@ static void Update24( struct GDS_Device* Device ) {
 #else
 	// always update by full lines
 	SetColumnAddress( Device, 0, Device->Width - 1);
+	Device->WriteCommand(Device, ENABLE_WRITE);
 	
 	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 * 3, Private->PageSize * Device->Width * 3 );
-			Device->WriteCommand(Device, ENABLE_WRITE);
 			Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width * 3 );
 		} else	{
-			Device->WriteCommand(Device, ENABLE_WRITE);
 			Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Private->PageSize * Device->Width * 3 );
 		}	
 	}	

+ 280 - 0
components/display/ST77xx.c

@@ -0,0 +1,280 @@
+/**
+ * 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		2048
+#define ENABLE_WRITE	0x2c
+
+#define min(a,b) (((a) < (b)) ? (a) : (b))
+
+static char TAG[] = "ST77xx";
+
+enum { ST7735, ST7789 };
+
+struct PrivateSpace {
+	uint8_t *iRAM, *Shadowbuffer;
+	uint8_t MADCtl, PageSize;
+	uint8_t Model;
+};
+
+// Functions are not declared to minimize # of lines
+
+static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
+	Device->WriteData( Device, &Data, 1 );
+}
+
+static void SetColumnAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
+	uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
+	Device->WriteCommand( Device, 0x2A );
+	Device->WriteData( Device, (uint8_t*) &Addr, 4 );
+}
+
+static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
+	uint32_t Addr = __builtin_bswap16(Start) | (__builtin_bswap16(End) << 16);
+	Device->WriteCommand( Device, 0x2B );
+	Device->WriteData( Device, (uint8_t*) &Addr, 4 );
+}
+
+static void Update16( struct GDS_Device* Device ) {
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+		
+#ifdef SHADOW_BUFFER
+	uint32_t *optr = (uint32_t*) Private->Shadowbuffer, *iptr = (uint32_t*) Device->Framebuffer;
+	int FirstCol = Device->Width / 2, LastCol = 0, FirstRow = -1, LastRow = 0;  
+	
+	for (int r = 0; r < Device->Height; r++) {
+		// look for change and update shadow (cheap optimization = width is always a multiple of 2)
+		for (int c = 0; c < Device->Width / 2; c++, iptr++, optr++) {
+			if (*optr != *iptr) {
+				*optr = *iptr;
+				if (c < FirstCol) FirstCol = c;	
+				if (c > LastCol) LastCol = c;
+				if (FirstRow < 0) FirstRow = r;
+				LastRow = r;
+			}
+		}
+
+		// wait for a large enough window - careful that window size might increase by more than a line at once !
+		if (FirstRow < 0 || ((LastCol - FirstCol + 1) * (r - FirstRow + 1) * 4 < PAGE_BLOCK && r != Device->Height - 1)) continue;
+		
+		FirstCol *= 2;
+		LastCol = LastCol * 2 + 1;
+		SetRowAddress( Device, FirstRow, LastRow );
+		SetColumnAddress( Device, FirstCol, LastCol );
+		Device->WriteCommand( Device, ENABLE_WRITE );
+			
+		int ChunkSize = (LastCol - FirstCol + 1) * 2;
+			
+		// own use of IRAM has not proven to be much better than letting SPI do its copy
+		if (Private->iRAM) {
+			uint8_t *optr = Private->iRAM;
+			for (int i = FirstRow; i <= LastRow; i++) {
+				memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize);
+				optr += ChunkSize;
+				if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
+				Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
+				optr = Private->iRAM;
+			}
+		} else for (int i = FirstRow; i <= LastRow; i++) {
+			Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 2, ChunkSize );
+		}	
+
+		FirstCol = Device->Width / 2; LastCol = 0;
+		FirstRow = -1;
+	}	
+#else
+	// always update by full lines
+	SetColumnAddress( Device, 0, Device->Width - 1);
+	
+	for (int r = 0; r < Device->Height; r += min(Private->PageSize, Device->Height - r)) {
+		int Height = min(Private->PageSize, Device->Height - r);
+		
+		SetRowAddress( Device, r, r + Height - 1 );
+		Device->WriteCommand(Device, ENABLE_WRITE);
+		
+		if (Private->iRAM) {
+			memcpy(Private->iRAM, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
+			Device->WriteData( Device, Private->iRAM, Height * Device->Width * 2 );
+		} else	{
+			Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 2, Height * Device->Width * 2 );
+		}	
+	}	
+#endif	
+}
+
+static void Update24( struct GDS_Device* Device ) {
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+		
+#ifdef SHADOW_BUFFER
+	uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer;
+	int FirstCol = (Device->Width * 3) / 2, LastCol = 0, FirstRow = -1, LastRow = 0;  
+	
+	for (int r = 0; r < Device->Height; r++) {
+		// look for change and update shadow (cheap optimization = width always / by 2)
+		for (int c = 0; c < (Device->Width * 3) / 2; c++, optr++, iptr++) {
+			if (*optr != *iptr) {
+				*optr = *iptr;
+				if (c < FirstCol) FirstCol = c;	
+				if (c > LastCol) LastCol = c;
+				if (FirstRow < 0) FirstRow = r;
+				LastRow = r;
+			}
+		}
+		
+		// do we have enough to send (cols are divided by 3/2)
+		if (FirstRow < 0 || ((((LastCol - FirstCol + 1) * 2 + 3 - 1) / 3) * (r - FirstRow + 1) * 3 < PAGE_BLOCK && r != Device->Height - 1)) continue;
+		
+		FirstCol = (FirstCol * 2) / 3;
+		LastCol = (LastCol * 2 + 1) / 3; 
+		SetRowAddress( Device, FirstRow, LastRow );
+		SetColumnAddress( Device, FirstCol, LastCol );
+		Device->WriteCommand( Device, ENABLE_WRITE );
+			
+		int ChunkSize = (LastCol - FirstCol + 1) * 3;
+					
+		// own use of IRAM has not proven to be much better than letting SPI do its copy
+		if (Private->iRAM) {
+			uint8_t *optr = Private->iRAM;
+			for (int i = FirstRow; i <= LastRow; i++) {
+				memcpy(optr, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize);
+				optr += ChunkSize;
+				if (optr - Private->iRAM < PAGE_BLOCK && i < LastRow) continue;
+				Device->WriteData(Device, Private->iRAM, optr - Private->iRAM);
+				optr = Private->iRAM;
+			}	
+		} else for (int i = FirstRow; i <= LastRow; i++) {
+			Device->WriteData( Device, Private->Shadowbuffer + (i * Device->Width + FirstCol) * 3, ChunkSize );
+		}	
+
+		FirstCol = (Device->Width * 3) / 2; LastCol = 0;
+		FirstRow = -1;
+	}	
+#else
+	// always update by full lines
+	SetColumnAddress( Device, 0, Device->Width - 1);
+	Device->WriteCommand(Device, ENABLE_WRITE);
+	
+	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 * 3, Private->PageSize * Device->Width * 3 );
+			Device->WriteData( Device, Private->iRAM, Private->PageSize * Device->Width * 3 );
+		} else	{
+			Device->WriteData( Device, Device->Framebuffer + r * Device->Width * 3, Private->PageSize * Device->Width * 3 );
+		}	
+	}	
+#endif	
+}
+
+static void SetHFlip( struct GDS_Device* Device, bool On ) { 
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	Private->MADCtl = On ? (Private->MADCtl & ~(1 << 7)) : (Private->MADCtl | (1 << 7));
+	Device->WriteCommand( Device, 0x36 );
+	WriteByte( Device, Private->MADCtl );
+}	
+
+static void SetVFlip( struct GDS_Device *Device, bool On ) { 
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	Private->MADCtl = On ? (Private->MADCtl | (1 << 6)) : (Private->MADCtl & ~(1 << 6));
+	Device->WriteCommand( Device, 0x36 );
+	WriteByte( Device, Private->MADCtl );
+}	
+	
+static void DisplayOn( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x29 ); }
+static void DisplayOff( struct GDS_Device* Device ) { Device->WriteCommand( Device, 0x28 ); }
+
+static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) {
+	Device->WriteCommand( Device, 0x51 );
+	WriteByte( Device, Contrast );
+}
+
+static bool Init( struct GDS_Device* Device ) {
+	struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
+	int Depth = (Device->Depth + 8 - 1) / 8;
+	
+	Private->PageSize = min(8, PAGE_BLOCK / (Device->Width * Depth));
+	
+#ifdef SHADOW_BUFFER	
+	Private->Shadowbuffer = malloc( Device->FramebufferSize );	
+	memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize);
+#endif
+#ifdef USE_IRAM
+	Private->iRAM = heap_caps_malloc( (Private->PageSize + 1) * Device->Width * Depth, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA );
+#endif
+
+	ESP_LOGI(TAG, "ST77xx with bit depth %u, page %u, iRAM %p", Device->Depth, Private->PageSize, Private->iRAM);
+	
+	// Sleepout + Booster
+	Device->WriteCommand( Device, 0x11 );
+		
+	// need BGR & Address Mode
+	Private->MADCtl = (1 << 3) | ((Device->Width > Device->Height ? 1 : 0) << 5);
+	Device->WriteCommand( Device, 0x36 );
+	WriteByte( Device, Private->MADCtl );		
+		
+	// set flip modes & contrast
+	GDS_SetContrast( Device, 0x7F );
+	Device->SetVFlip( Device, false );
+	Device->SetHFlip( Device, false );
+	
+	// set screen depth (16/18)
+	Device->WriteCommand( Device, 0x3A );
+	WriteByte( Device, Device->Depth == 24 ? 0x06 : 0x05 );
+	
+	// no Display Inversion
+    Device->WriteCommand( Device, 0x20 );	
+		
+	// gone with the wind
+	Device->DisplayOn( Device );
+	Device->Update( Device );
+
+	return true;
+}	
+
+static const struct GDS_Device ST77xx = {
+	.DisplayOn = DisplayOn, .DisplayOff = DisplayOff,
+	.SetVFlip = SetVFlip, .SetHFlip = SetHFlip,
+	.Update = Update16, .Init = Init,
+	.Mode = GDS_RGB565, .Depth = 16,
+};	
+
+struct GDS_Device* ST77xx_Detect(char *Driver, struct GDS_Device* Device) {
+	uint8_t Model;
+	int Depth;
+	
+	if (strcasestr(Driver, "ST7735")) Model = ST7735;
+	else if (strcasestr(Driver, "ST7789")) Model = ST7789;
+	else return NULL;
+		
+	if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
+		
+	*Device = ST77xx;	
+	((struct PrivateSpace*) Device->Private)->Model = Model;
+	sscanf(Driver, "%*[^:]:%u", &Depth);
+	
+	if (Depth == 18) {
+		Device->Mode = GDS_RGB666;
+		Device->Depth = 24;
+		Device->Update = Update24;
+	} 	
+	
+	if (Model == ST7789) Device->SetContrast = SetContrast;
+	
+	return Device;
+}

+ 42 - 3
components/display/core/gds.c

@@ -9,22 +9,37 @@
 #include <string.h>
 #include <ctype.h>
 #include <stdint.h>
+#include <math.h>
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "driver/gpio.h"
+#include "driver/ledc.h"
 #include "esp_log.h"
 
 #include "gds.h"
 #include "gds_private.h"
 
 static struct GDS_Device Display;
+static struct GDS_BacklightPWM PWMConfig;
 
 static char TAG[] = "gds";
 
-struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] ) {
+struct GDS_Device* GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM* PWM ) {
+	if (!Driver) return NULL;
+	if (PWM) PWMConfig = *PWM;
+	
 	for (int i = 0; DetectFunc[i]; i++) {
 		if (DetectFunc[i](Driver, &Display)) {
-			ESP_LOGD(TAG, "Detected driver %p", &Display);
+			if (PWM && PWM->Init) {
+				ledc_timer_config_t PWMTimer = {
+						.duty_resolution = LEDC_TIMER_13_BIT,
+						.freq_hz = 5000,                   
+						.speed_mode = LEDC_HIGH_SPEED_MODE,
+						.timer_num = PWMConfig.Timer,
+					};
+				ledc_timer_config(&PWMTimer);
+			}	
+			ESP_LOGD(TAG, "Detected driver %p with PWM %d", &Display, PWM ? PWM->Init : 0);			
 			return &Display;
 		}	
 	}
@@ -165,6 +180,22 @@ bool GDS_Init( struct GDS_Device* Device ) {
 		NullCheck( Device->Framebuffer, return false );
 	}	
 	
+	if (Device->Backlight.Pin >= 0) {
+		Device->Backlight.Channel = PWMConfig.Channel++;
+		Device->Backlight.PWM = PWMConfig.Max - 1;
+
+		ledc_channel_config_t PWMChannel = {
+            .channel    = Device->Backlight.Channel,
+            .duty       = Device->Backlight.PWM,
+            .gpio_num   = Device->Backlight.Pin,
+            .speed_mode = LEDC_HIGH_SPEED_MODE,
+            .hpoint     = 0,
+            .timer_sel  = PWMConfig.Timer,
+        };
+		
+		ledc_channel_config(&PWMChannel);
+	}
+	
 	bool Res = Device->Init( Device );
 	if (!Res && Device->Framebuffer) free(Device->Framebuffer);
 	return Res;
@@ -196,7 +227,15 @@ int GDS_GrayMap( struct GDS_Device* Device, uint8_t Level) {
 	return -1;
 }
 
-void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { if (Device->SetContrast) Device->SetContrast( Device, Contrast); }
+void GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { 
+	if (Device->SetContrast) Device->SetContrast( Device, Contrast ); 
+	else if (Device->Backlight.Pin >= 0) {
+		Device->Backlight.PWM = PWMConfig.Max * powf(Contrast / 255.0, 3);
+		ledc_set_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel, Device->Backlight.PWM );
+		ledc_update_duty( LEDC_HIGH_SPEED_MODE, Device->Backlight.Channel );		
+	}
+}
+	
 void GDS_SetHFlip( struct GDS_Device* Device, bool On ) { if (Device->SetHFlip) Device->SetHFlip( Device, On ); }
 void GDS_SetVFlip( struct GDS_Device* Device, bool On ) { if (Device->SetVFlip) Device->SetVFlip( Device, On ); }
 void GDS_SetDirty( struct GDS_Device* Device ) { Device->Dirty = true; }

+ 5 - 1
components/display/core/gds.h

@@ -22,10 +22,14 @@ enum { GDS_MONO = 0, GDS_GRAYSCALE, GDS_RGB332, GDS_RGB444, GDS_RGB555, GDS_RGB5
 
 struct GDS_Device;
 struct GDS_FontDef;
+struct GDS_BacklightPWM { 
+	int Channel, Timer, Max;
+	bool Init;
+};
 
 typedef struct GDS_Device* GDS_DetectFunc(char *Driver, struct GDS_Device *Device);
 
-struct GDS_Device*	GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[] );
+struct GDS_Device*	GDS_AutoDetect( char *Driver, GDS_DetectFunc* DetectFunc[], struct GDS_BacklightPWM *PWM );
 
 void 	GDS_SetContrast( struct GDS_Device* Device, uint8_t Contrast );
 void 	GDS_DisplayOn( struct GDS_Device* Device );

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

@@ -8,10 +8,10 @@ extern "C" {
 struct GDS_Device;
 
 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_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin );
 
 bool GDS_SPIInit( int SPI, int DC );
-bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed );
+bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed, int BacklightPin );
 
 #ifdef __cplusplus
 }

+ 6 - 2
components/display/core/gds_private.h

@@ -72,6 +72,11 @@ typedef struct spi_device_t* spi_device_handle_t;
 
 struct GDS_Device {
 	uint8_t IF;
+	int8_t RSTPin;
+	struct {
+		int8_t Pin, Channel;
+		int PWM;	
+	} Backlight;
 	union {
 		// I2C Specific
 		struct {
@@ -80,11 +85,10 @@ struct GDS_Device {
 		// SPI specific
 		struct {
 			spi_device_handle_t SPIHandle;
-			int8_t RSTPin;
 			int8_t CSPin;
 		};
 	};	
-
+	
     // cooked text mode
 	struct {
 		int16_t Y, Space;

+ 2 - 5
components/display/core/ifaces/default_if_i2c.c

@@ -21,7 +21,6 @@ static int I2CWait;
 
 static const int GDS_I2C_COMMAND_MODE = 0x80;
 static const int GDS_I2C_DATA_MODE = 0x40;
-static const int i2c_timeout_value=2000;
 
 static bool I2CDefaultWriteBytes( int Address, bool IsCommand, const uint8_t* Data, size_t DataLength );
 static bool I2CDefaultWriteCommand( struct GDS_Device* Device, uint8_t Command );
@@ -50,9 +49,6 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
 
 		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 );
-		printf("Setting timeout value to %d",i2c_timeout_value);
-		i2c_set_timeout(I2CPortNumber,  (I2C_APB_CLK_FREQ /(2500000))* i2c_timeout_value);
-
 	}	
 
     return true;
@@ -70,13 +66,14 @@ bool GDS_I2CInit( int PortNumber, int SDA, int SCL, int Speed ) {
  * 
  * Returns true on successful init of display.
  */
-bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin ) {
+bool GDS_I2CAttachDevice( struct GDS_Device* Device, int Width, int Height, int I2CAddress, int RSTPin, int BacklightPin ) {
     NullCheck( Device, return false );
 
     Device->WriteCommand = I2CDefaultWriteCommand;
     Device->WriteData = I2CDefaultWriteData;
     Device->Address = I2CAddress;
     Device->RSTPin = RSTPin;
+	Device->Backlight.Pin = BacklightPin;	
 	Device->IF = GDS_IF_I2C;
 	Device->Width = Width;
 	Device->Height = Height;

+ 3 - 2
components/display/core/ifaces/default_if_spi.c

@@ -34,7 +34,7 @@ bool GDS_SPIInit( int SPI, int DC ) {
     return true;
 }
 
-bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int Speed ) {
+bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int CSPin, int RSTPin, int BackLightPin, int Speed ) {
     spi_device_interface_config_t SPIDeviceConfig;
     spi_device_handle_t SPIDevice;
 
@@ -44,7 +44,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
 		ESP_ERROR_CHECK_NONFATAL( gpio_set_direction( CSPin, GPIO_MODE_OUTPUT ), return false );
 		ESP_ERROR_CHECK_NONFATAL( gpio_set_level( CSPin, 0 ), return false );
 	}	
-
+	
     memset( &SPIDeviceConfig, 0, sizeof( spi_device_interface_config_t ) );
 
     SPIDeviceConfig.clock_speed_hz = Speed > 0 ? Speed : SPI_MASTER_FREQ_8M;
@@ -59,6 +59,7 @@ bool GDS_SPIAttachDevice( struct GDS_Device* Device, int Width, int Height, int
     Device->SPIHandle = SPIDevice;
     Device->RSTPin = RSTPin;
     Device->CSPin = CSPin;
+	Device->Backlight.Pin = BackLightPin;	
 	Device->IF = GDS_IF_SPI;
 	Device->Width = Width;
 	Device->Height = Height;

+ 17 - 10
components/display/display.c

@@ -52,14 +52,15 @@ static const char *known_drivers[] = {"SH1106",
 		"SSD1327",
 		"SSD1675",
 		"SSD1351",
+		"ST77xx",
 		"ILI9341",
 		NULL
 	};
 static void displayer_task(void *args);
 
 struct GDS_Device *display;   
-extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ILI9341_Detect;
-GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ILI9341_Detect, NULL };
+extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect;
+GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, ILI9341_Detect, NULL };
 
 /****************************************************************************************
  * 
@@ -73,16 +74,22 @@ void display_init(char *welcome) {
 		return;
 	}	
 	
-	int width = -1, height = -1;
+	int width = -1, height = -1, backlight_pin = -1;
 	char *p, *drivername = strstr(config, "driver");
+
+	if ((p = strcasestr(config, "width")) != NULL) width = atoi(strchr(p, '=') + 1);
+	if ((p = strcasestr(config, "height")) != NULL) height = atoi(strchr(p, '=') + 1);
+	if ((p = strcasestr(config, "back")) != NULL) backlight_pin = atoi(strchr(p, '=') + 1);	
 		
 	// query drivers to see if we have a match
 	ESP_LOGI(TAG, "Trying to configure display with %s", config);
-	display = GDS_AutoDetect(drivername ? drivername : "SSD1306", drivers);
+	if (backlight_pin >= 0) {
+		struct GDS_BacklightPWM PWMConfig = { .Channel = pwm_system.base_channel++, .Timer = pwm_system.timer, .Max = pwm_system.max, .Init = false	};
+		display = GDS_AutoDetect(drivername, drivers, &PWMConfig);
+	} else {
+		display = GDS_AutoDetect(drivername, drivers, NULL);
+	}	
 		
-	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) {
 		int RST_pin = -1;
@@ -96,7 +103,7 @@ void display_init(char *welcome) {
 				
 			init = true;
 			GDS_I2CInit( i2c_system_port, -1, -1, i2c_system_speed ) ;
-			GDS_I2CAttachDevice( display, width, height, address, RST_pin );
+			GDS_I2CAttachDevice( display, width, height, address, RST_pin, backlight_pin );
 		
 			ESP_LOGI(TAG, "Display is I2C on port %u", address);
 		} else if (strstr(config, "SPI") && spi_system_host != -1) {
@@ -107,7 +114,7 @@ void display_init(char *welcome) {
 		
 			init = true;
 			GDS_SPIInit( spi_system_host, spi_system_dc_gpio );
-			GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, speed );
+			GDS_SPIAttachDevice( display, width, height, CS_pin, RST_pin, backlight_pin, speed );
 				
 			ESP_LOGI(TAG, "Display is SPI host %u with cs:%d", spi_system_host, CS_pin);
 		} else {
@@ -213,7 +220,7 @@ static void displayer_task(void *args) {
 		scroll_sleep -= sleep;
 		vTaskDelay(sleep / portTICK_PERIOD_MS);
 	}
-}
+}	
 
 /****************************************************************************************
  * 

+ 6 - 2
components/services/globdefs.h

@@ -10,14 +10,18 @@
  
 #pragma once
 
-#define I2C_SYSTEM_PORT	1
-#define SPI_SYSTEM_HOST	SPI2_HOST
+#define I2C_SYSTEM_PORT		1
+#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;
+typedef struct {
+	int timer, base_channel, max;
+} pwm_system_t;
+extern pwm_system_t pwm_system;
 
 #ifdef CONFIG_SQUEEZEAMP
 #define ADAC dac_tas57xx

+ 91 - 35
components/services/led.c

@@ -10,13 +10,16 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <math.h>
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "freertos/timers.h"
 #include "esp_system.h"
 #include "esp_log.h"
 #include "driver/gpio.h"
+#include "driver/ledc.h"
 #include "led.h"
+#include "globdefs.h"
 #include "accessors.h"
 
 #define MAX_LED	8
@@ -29,6 +32,8 @@ static struct led_s {
 	bool on;
 	int onstate;
 	int ontime, offtime;
+	int pwm;
+	int channel;
 	int pushedon, pushedoff;
 	bool pushed;
 	TimerHandle_t timer;
@@ -37,8 +42,22 @@ static struct led_s {
 static struct {
 	int gpio;
 	int active;
-} green = { CONFIG_LED_GREEN_GPIO, 0 },
-  red = { CONFIG_LED_RED_GPIO, 0 };
+	int pwm;
+} green = { .gpio = CONFIG_LED_GREEN_GPIO, .active = 0, .pwm = -1 },
+  red = { .gpio = CONFIG_LED_RED_GPIO, .active = 0, .pwm = -1 };
+  
+static int led_max = 2;
+
+/****************************************************************************************
+ * 
+ */
+static void set_level(struct led_s *led, bool on) {
+	if (led->pwm < 0) gpio_set_level(led->gpio, on ? led->onstate : !led->onstate);
+	else {
+		ledc_set_duty(LEDC_HIGH_SPEED_MODE, led->channel, on ? led->pwm : (led->onstate ? 0 : pwm_system.max));
+		ledc_update_duty(LEDC_HIGH_SPEED_MODE, led->channel);
+	}		
+}
 
 /****************************************************************************************
  * 
@@ -49,9 +68,9 @@ static void vCallbackFunction( TimerHandle_t xTimer ) {
 	if (!led->timer) return;
 	
 	led->on = !led->on;
-	ESP_LOGD(TAG,"led vCallbackFunction setting gpio %d level", led->gpio);
-	gpio_set_level(led->gpio, led->on ? led->onstate : !led->onstate);
-	
+	ESP_EARLY_LOGD(TAG,"led vCallbackFunction setting gpio %d level %d (pwm:%d)", led->gpio, led->on, led->pwm);
+	set_level(led, led->on);
+		
 	// was just on for a while
 	if (!led->on && led->offtime == -1) return;
 	
@@ -63,7 +82,7 @@ static void vCallbackFunction( TimerHandle_t xTimer ) {
  * 
  */
 bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
-	if (!leds[idx].gpio || leds[idx].gpio<0 ) return false;
+	if (!leds[idx].gpio || leds[idx].gpio < 0 ) return false;
 	
 	ESP_LOGD(TAG,"led_blink_core");
 	if (leds[idx].timer) {
@@ -89,25 +108,40 @@ bool led_blink_core(int idx, int ontime, int offtime, bool pushed) {
 			
 	if (ontime == 0) {
 		ESP_LOGD(TAG,"led %d, setting reverse level", idx);
-		gpio_set_level(leds[idx].gpio, !leds[idx].onstate);
+		set_level(leds + idx, false);
 	} else if (offtime == 0) {
 		ESP_LOGD(TAG,"led %d, setting level", idx);
-		gpio_set_level(leds[idx].gpio, leds[idx].onstate);
+		set_level(leds + idx, true);
 	} else {
 		if (!leds[idx].timer) {
 			ESP_LOGD(TAG,"led %d, Creating timer", idx);
 			leds[idx].timer = xTimerCreate("ledTimer", ontime / portTICK_RATE_MS, pdFALSE, (void *)&leds[idx], vCallbackFunction);
 		}
         leds[idx].on = true;
-        ESP_LOGD(TAG,"led %d, Setting gpio %d", idx, leds[idx].gpio);
-		gpio_set_level(leds[idx].gpio, leds[idx].onstate);
-		ESP_LOGD(TAG,"led %d, Starting timer.", idx);
+		set_level(leds + idx, true);
+
+        ESP_LOGD(TAG,"led %d, Setting gpio %d and starting timer", idx, leds[idx].gpio);
 		if (xTimerStart(leds[idx].timer, BLOCKTIME) == pdFAIL) return false;
 	}
-	ESP_LOGD(TAG,"led %d, led_blink_core_done", idx);
+	
+	
 	return true;
 } 
 
+/****************************************************************************************
+ * 
+ */
+bool led_brightness(int idx, int pwm) {
+	if (pwm > 100) pwm = 100;
+	leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
+	if (!leds[idx].onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
+	
+	ledc_set_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel, leds[idx].pwm);
+	ledc_update_duty(LEDC_HIGH_SPEED_MODE, leds[idx].channel);
+	
+	return true;
+}
+
 /****************************************************************************************
  * 
  */
@@ -123,34 +157,50 @@ bool led_unpush(int idx) {
 /****************************************************************************************
  * 
  */
-bool led_config(int idx, gpio_num_t gpio, int onstate) {
-	if(gpio<0){
+int led_allocate(void) {
+	 if (led_max < MAX_LED) return led_max++;
+	 return -1;
+}
+
+/****************************************************************************************
+ * 
+ */
+bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm) {
+	if (gpio < 0) {
 		ESP_LOGW(TAG,"LED GPIO not configured");
 		return false;
 	}
+	
 	ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s", idx, gpio, onstate>0?"On":"Off");
 	if (idx >= MAX_LED) return false;
+	
 	leds[idx].gpio = gpio;
 	leds[idx].onstate = onstate;
-	ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Selecting GPIO pad", idx, gpio, onstate>0?"On":"Off");
-	gpio_pad_select_gpio(gpio);
-	ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Setting direction to OUTPUT", idx, gpio, onstate>0?"On":"Off");
-	gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
-	ESP_LOGD(TAG,"Index %d, GPIO %d, on state %s. Setting State to %d", idx, gpio, onstate>0?"On":"Off", onstate);
-	gpio_set_level(gpio, !onstate);
-	ESP_LOGD(TAG,"Done configuring the led");
-	return true;
-}
+	leds[idx].pwm = -1;
 
-/****************************************************************************************
- * 
- */
-bool led_unconfig(int idx) {
-	if (idx >= MAX_LED) return false;	
-	
-	if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
-	leds[idx].timer = NULL;
+	if (pwm < 0) {	
+		gpio_pad_select_gpio(gpio);
+		gpio_set_direction(gpio, GPIO_MODE_OUTPUT);
+	} else {	
+		leds[idx].channel = pwm_system.base_channel++;
+		leds[idx].pwm = pwm_system.max * powf(pwm / 100.0, 3);
+		if (!onstate) leds[idx].pwm = pwm_system.max - leds[idx].pwm;
+		
+		ledc_channel_config_t ledc_channel = {
+            .channel    = leds[idx].channel,
+            .duty       = leds[idx].pwm,
+            .gpio_num   = gpio,
+            .speed_mode = LEDC_HIGH_SPEED_MODE,
+            .hpoint     = 0,
+            .timer_sel  = pwm_system.timer,
+        };
+		
+		ledc_channel_config(&ledc_channel);
+	}
 	
+	set_level(leds + idx, false);
+	ESP_LOGD(TAG,"PWM Index %d, GPIO %d, on state %s, pwm %d%%", idx, gpio, onstate > 0 ? "On" : "Off", pwm);		
+
 	return true;
 }
 
@@ -170,7 +220,6 @@ void set_led_gpio(int gpio, char *value) {
 }
 
 void led_svc_init(void) {
-	
 #ifdef CONFIG_LED_GREEN_GPIO_LEVEL
 	green.active = CONFIG_LED_GREEN_GPIO_LEVEL;
 #endif
@@ -181,8 +230,15 @@ void led_svc_init(void) {
 #ifndef CONFIG_LED_LOCKED
 	parse_set_GPIO(set_led_gpio);
 #endif
-	ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d), red:%d (active:%d)", green.gpio, green.active, red.gpio, red.active);
+	ESP_LOGI(TAG,"Configuring LEDs green:%d (active:%d %d%%), red:%d (active:%d %d%%)", green.gpio, green.active, green.pwm, red.gpio, red.active, red.pwm);
+	
+	char *nvs_item = config_alloc_get(NVS_TYPE_STR, "led_brightness"), *p; 
+	if (nvs_item) {
+		if ((p = strcasestr(nvs_item, "green")) != NULL) green.pwm = atoi(strchr(p, '=') + 1);
+		if ((p = strcasestr(nvs_item, "red")) != NULL) red.pwm = atoi(strchr(p, '=') + 1);
+		free(nvs_item);
+	}
 
-	led_config(LED_GREEN, green.gpio, green.active);
-	led_config(LED_RED, red.gpio, red.active);
+	led_config(LED_GREEN, green.gpio, green.active, green.pwm);
+	led_config(LED_RED, red.gpio, red.active, red.pwm);
 }

+ 3 - 2
components/services/led.h

@@ -20,9 +20,10 @@ enum { LED_GREEN = 0, LED_RED };
 #define led_blink(idx, on, off)			led_blink_core(idx, on, off, false)
 #define led_blink_pushed(idx, on, off)	led_blink_core(idx, on, off, true)
 
-bool led_config(int idx, gpio_num_t gpio, int onstate);
-bool led_unconfig(int idx);
+bool led_config(int idx, gpio_num_t gpio, int onstate, int pwm);	
+bool led_brightness(int idx, int percent); 
 bool led_blink_core(int idx, int ontime, int offtime, bool push);
 bool led_unpush(int idx);
+int  led_allocate(void);
 
 #endif

+ 17 - 1
components/services/services.c

@@ -9,7 +9,8 @@
 #include <stdio.h>
 #include "esp_log.h"
 #include "driver/gpio.h"
-#include <driver/i2c.h>
+#include "driver/ledc.h"
+#include "driver/i2c.h"
 #include "platform_config.h"
 #include "battery.h"
 #include "led.h"
@@ -26,6 +27,11 @@ 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;
+pwm_system_t pwm_system = { 
+		.timer = LEDC_TIMER_0,
+		.base_channel = LEDC_CHANNEL_0,
+		.max = (1 << LEDC_TIMER_13_BIT),
+	};		
 
 static const char *TAG = "services";
 
@@ -93,6 +99,16 @@ void services_init(void) {
 		spi_system_host = -1;
 		ESP_LOGW(TAG, "no SPI configured");
 	}	
+	
+	// system-wide PWM timer configuration
+	ledc_timer_config_t pwm_timer = {
+		.duty_resolution = LEDC_TIMER_13_BIT, 
+		.freq_hz = 5000,                     
+		.speed_mode = LEDC_HIGH_SPEED_MODE,  
+		.timer_num = pwm_system.timer,
+	};
+	
+	ledc_timer_config(&pwm_timer);
 
 	led_svc_init();
 	battery_svc_init();

+ 3 - 0
main/esp_app_main.c

@@ -350,6 +350,9 @@ void register_default_nvs(){
 	ESP_LOGD(TAG,"Registering default value for key %s, value %s", "set_GPIO", CONFIG_SET_GPIO);
 	config_set_default(NVS_TYPE_STR, "set_GPIO", CONFIG_SET_GPIO, 0);
 	
+	ESP_LOGD(TAG,"Registering default value for key %s", "led_brightness");
+	config_set_default(NVS_TYPE_STR, "led_brightness", "", 0);
+	
 	ESP_LOGD(TAG,"Registering default value for key %s", "spdif_config");
 	config_set_default(NVS_TYPE_STR, "spdif_config", "", 0);