diff --git a/components/display/SSD1351.c b/components/display/SSD1351.c index b5231cdd..a48c7d6c 100644 --- a/components/display/SSD1351.c +++ b/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 ); } } diff --git a/components/display/ST77xx.c b/components/display/ST77xx.c new file mode 100644 index 00000000..0a4a79a4 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +#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; +} \ No newline at end of file diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 60de62db..e2bccfa0 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -9,22 +9,37 @@ #include #include #include +#include #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; } diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 29f3e8c7..952b6609 100644 --- a/components/display/core/gds.h +++ b/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 ); diff --git a/components/display/core/gds_default_if.h b/components/display/core/gds_default_if.h index aac616e0..cb6324b3 100644 --- a/components/display/core/gds_default_if.h +++ b/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 } diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index ca672b4c..09292655 100644 --- a/components/display/core/gds_private.h +++ b/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; diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/display/core/ifaces/default_if_i2c.c index a7545b45..d88ff679 100644 --- a/components/display/core/ifaces/default_if_i2c.c +++ b/components/display/core/ifaces/default_if_i2c.c @@ -66,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; diff --git a/components/display/core/ifaces/default_if_spi.c b/components/display/core/ifaces/default_if_spi.c index 14e2829c..ce0cd948 100644 --- a/components/display/core/ifaces/default_if_spi.c +++ b/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; diff --git a/components/display/display.c b/components/display/display.c index e98cc812..575adc04 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -48,8 +48,8 @@ static EXT_RAM_ATTR struct { 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; -GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, NULL }; +extern GDS_DetectFunc SSD1306_Detect, SSD132x_Detect, SH1106_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect; +GDS_DetectFunc *drivers[] = { SH1106_Detect, SSD1306_Detect, SSD132x_Detect, SSD1675_Detect, SSD1322_Detect, SSD1351_Detect, ST77xx_Detect, NULL }; /**************************************************************************************** * @@ -63,16 +63,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; @@ -86,7 +92,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) { @@ -97,7 +103,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 { diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 3d1217aa..377d3019 100644 --- a/components/services/globdefs.h +++ b/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 diff --git a/components/services/led.c b/components/services/led.c index 970b17a2..2b8dac50 100644 --- a/components/services/led.c +++ b/components/services/led.c @@ -10,14 +10,18 @@ #include #include #include +#include #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" +#include "config.h" #define MAX_LED 8 #define BLOCKTIME 10 // up to portMAX_DELAY @@ -29,6 +33,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 +43,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 +69,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 +83,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 +109,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 +158,50 @@ bool led_unpush(int idx) { /**************************************************************************************** * */ -bool led_config(int idx, gpio_num_t gpio, int onstate) { - 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; +int led_allocate(void) { + if (led_max < MAX_LED) return led_max++; + return -1; } /**************************************************************************************** * */ -bool led_unconfig(int idx) { - if (idx >= MAX_LED) return false; +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; + } - if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME); - leds[idx].timer = NULL; + 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; + leds[idx].pwm = -1; + + 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 +221,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 +231,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); } diff --git a/components/services/led.h b/components/services/led.h index 89d06cc1..1f3e92b9 100644 --- a/components/services/led.h +++ b/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 diff --git a/components/services/services.c b/components/services/services.c index 6538f128..4c996dd4 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -9,7 +9,8 @@ #include #include "esp_log.h" #include "driver/gpio.h" -#include +#include "driver/ledc.h" +#include "driver/i2c.h" #include "config.h" #include "battery.h" #include "led.h" @@ -25,6 +26,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"; @@ -91,6 +97,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(); diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index e1bfaa48..ee4c8ac1 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -414,7 +414,7 @@ static void server(in_addr_t ip, u16_t hport, u16_t cport) { displayer.dirty = true; xSemaphoreGive(displayer.mutex); - + // inform new LMS server of our capabilities sendSETD(displayer.width, GDS_GetHeight(display)); @@ -1122,7 +1122,7 @@ static void visu_handler( u8_t *data, int len) { */ static void displayer_task(void *args) { int sleep; - + while (1) { xSemaphoreTake(displayer.mutex, portMAX_DELAY); diff --git a/main/esp_app_main.c b/main/esp_app_main.c index abc89f3a..27905c58 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -321,6 +321,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);