From f008229acd006de623caaefdf25b10fa6c5ff4be Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 24 Feb 2020 21:54:51 -0800 Subject: [PATCH 01/18] more display refactoring, led bug correction --- components/display/SH1106.c | 16 +- components/display/SSD1306.c | 18 +- components/display/SSD132x.c | 235 ++++++++++++++++++++++ components/display/core/gds.c | 10 +- components/display/core/gds.h | 12 +- components/display/core/gds_draw.c | 217 +++++++++++++------- components/display/core/gds_draw.h | 19 +- components/display/core/gds_font.c | 6 +- components/display/core/gds_private.h | 66 ++++-- components/display/core/gds_text.c | 5 +- components/display/display.c | 7 +- components/services/accessors.c | 4 +- components/services/led.c | 2 +- components/squeezelite/a1s/ac101.c | 10 +- components/squeezelite/display.c | 15 +- components/squeezelite/output_i2s.c | 6 +- components/squeezelite/tas57xx/dac_57xx.c | 31 +-- 17 files changed, 523 insertions(+), 156 deletions(-) create mode 100644 components/display/SSD132x.c diff --git a/components/display/SH1106.c b/components/display/SH1106.c index 2bc5ae6f..baba9c63 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -17,6 +17,7 @@ #include "gds_private.h" #define SHADOW_BUFFER +#define USE_IRAM static char TAG[] = "SH1106"; @@ -81,10 +82,15 @@ 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 ); - + #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 ); +#ifdef USE_IRAM + // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy + if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + else +#else + Device->Shadowbuffer = malloc( Device->FramebufferSize ); +#endif NullCheck( Device->Shadowbuffer, return false ); memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); #endif @@ -130,8 +136,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 +146,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; diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index e102c7ba..f0a1b095 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -17,6 +17,7 @@ #include "gds_private.h" #define SHADOW_BUFFER +#define USE_IRAM static char TAG[] = "SSD1306"; @@ -79,10 +80,15 @@ 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 ); - + #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 ); +#ifdef USE_IRAM + // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy + if (Device->IF == IF_SPI) Device->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); + else +#else + Device->Shadowbuffer = malloc( Device->FramebufferSize ); +#endif NullCheck( Device->Shadowbuffer, return false ); memset(Device->Shadowbuffer, 0xFF, Device->FramebufferSize); #endif @@ -95,7 +101,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 +137,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 +147,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; diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c new file mode 100644 index 00000000..d93c0018 --- /dev/null +++ b/components/display/SSD132x.c @@ -0,0 +1,235 @@ +/** + * 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 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; +}; + +// Functions are not deckared 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; + int r; + + // 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; + int page; + + for (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, Device->Width * page / 2 ); + Device->WriteData( Device, Private->iRAM, Device->Width * page / 2 ); + } else { + Device->WriteData( Device, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, Device->Width * page / 2 ); + } + dirty = false; + } + page = 0; + } + } +#else + for (r = 0; r < Device->Height; r += Private->PageSize) { + SetRowAddress( Device, r, r + Private->PageSize - 1 ); + Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Device->Width * Private->PageSize / 2 ); + } +#endif +} + +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, rows = Device->Height / 8; + 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 Page/Column + SetColumnAddress( Device, 0, Device->Width - 1); + SetPageAddress( Device, 0, Device->Height / 8 - 1); + Device->WriteData( Device, Device->Framebuffer, Device->FramebufferSize ); +#endif +} + +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 ); + +#ifdef SHADOW_BUFFER + Device->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Device->Shadowbuffer, return false ); + memset(Device->Shadowbuffer, 0xFF, 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; + else Device->Depth = 4; + + return Device; +} \ No newline at end of file diff --git a/components/display/core/gds.c b/components/display/core/gds.c index db738d6a..c80f18d8 100644 --- a/components/display/core/gds.c +++ b/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); @@ -54,19 +54,25 @@ void GDS_ClearExt(struct GDS_Device* Device, bool full, ...) { void GDS_Clear( struct GDS_Device* Device, int Color ) { 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 ) { + // driver can provide onw optimized clear window + if (Device->ClearWindow) { + Device->ClearWindow( Device, x1, y1, x2, y2, 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 ); } 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); } } } + + Device->Dirty = true; } void GDS_Update( struct GDS_Device* Device ) { diff --git a/components/display/core/gds.h b/components/display/core/gds.h index fed8455f..88156279 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -4,16 +4,20 @@ #include #include -#define GDS_COLOR_BLACK 0 -#define GDS_COLOR_WHITE 1 +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 ); diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index 5e464993..6b2f5c3e 100644 --- a/components/display/core/gds_draw.c +++ b/components/display/core/gds_draw.c @@ -14,8 +14,8 @@ #include #include -#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; +// 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 ); - /* - * 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; - - 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,29 +192,140 @@ 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->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; } +/**************************************************************************************** + * 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 if (Device->Depth == 4) { + for (int c = 0; c < Width; c++) { + for (int r = 0; r < Height; r++) { + int pixel = Image[r][c]; + switch(RGB_Mode) { + case GDS_RGB565: + pixel = (((pixel & 0x1f) * 11 + (((pixel >> 5) & 0x3f) * 59) / 2 + (pixel >> 11) * 30) / 100) >> 1; + break; + case GDS_RGB555: + pixel = (((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100) >> 1; + break; + case GDS_RGB444: + pixel = ((pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30) / 100; + break; + case GDS_RGB8_GRAY: + pixel = Image[r][c] >> 4; + break; + } + GDS_DrawPixel( Device, c + x, r + y, pixel ); + } + } + } else if (Device->Depth == 1) { + for (int c = 0; c < Width; c++) { + for (int r = 0; r < Height; r++) { + int pixel = Image[r][c]; + switch(RGB_Mode) { + case GDS_RGB565: + pixel = (((pixel & 0x1f) * 21 + (((pixel >> 5) & 0x3f) * 71) / 2+ (pixel >> 11) * 7) / 100) >> 4; + break; + case GDS_RGB555: + pixel = (((pixel & 0x1f) * 21 + ((pixel >> 5) & 0x1f) * 71 + (pixel >> 10) * 7) / 100) >> 4; + break; + case GDS_RGB444: + pixel = (((pixel & 0x0f) * 21 + ((pixel >> 4) & 0x0f) * 71 + (pixel >> 8) * 7) / 100) >> 3; + break; + case GDS_RGB8_GRAY: + pixel = Image[r][c] >> 7; + } + GDS_DrawPixel( Device, c + x, r + y, pixel); + } + } + } +} + /**************************************************************************************** * Process graphic display data MSBit first * WARNING: this has not been tested yet */ /* -static void draw_raw(int x1, int y1, int x2, int y2, bool by_column, bool MSb, u8_t *data) { +static void DrawBitmap(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; diff --git a/components/display/core/gds_draw.h b/components/display/core/gds_draw.h index 10bce8b8..048fa354 100644 --- a/components/display/core/gds_draw.h +++ b/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, GDS_RGB8_GRAY }; -// 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 } diff --git a/components/display/core/gds_font.c b/components/display/core/gds_font.c index e0d2981f..f1d9e513 100644 --- a/components/display/core/gds_font.c +++ b/components/display/core/gds_font.c @@ -9,10 +9,10 @@ #include #include #include -#include "gds.h" -#include "gds_draw.h" -#include "gds_font.h" #include "gds_private.h" +#include "gds.h" +#include "gds_font.h" +#include "gds_draw.h" #include "gds_err.h" static int RoundUpFontHeight( const struct GDS_FontDef* Font ) { diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 518f4283..806f5a90 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef _GDS_PRIVATE_H_ +#define _GDS_PRIVATE_H_ #include #include @@ -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. @@ -94,25 +93,29 @@ struct GDS_Device { bool FontForceMonospace; // various driver-specific method + // must 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 ); + // may provide for optimization + void (*DrawPixelFast)( struct GDS_Device* Device, int X, int Y, int Color ); + 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 +133,44 @@ inline bool IsPixelVisible( struct GDS_Device* Device, int x, int y ) { return Result; } +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_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 ) { - Device->DrawPixelFast( Device, x, y, Color ); + GDS_DrawPixelFast( Device, x, y, Color ); } } -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 ); - } -} - - - +#endif \ No newline at end of file diff --git a/components/display/core/gds_text.c b/components/display/core/gds_text.c index a72613e5..34e07d23 100644 --- a/components/display/core/gds_text.c +++ b/components/display/core/gds_text.c @@ -22,10 +22,11 @@ #include #include #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 ); diff --git a/components/display/display.c b/components/display/display.c index f8f00f38..f626cbd9 100644 --- a/components/display/display.c +++ b/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 }; /**************************************************************************************** * diff --git a/components/services/accessors.c b/components/services/accessors.c index 72360452..72543de8 100644 --- a/components/services/accessors.c +++ b/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++); diff --git a/components/services/led.c b/components/services/led.c index de1be4d7..970b17a2 100644 --- a/components/services/led.c +++ b/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); } } diff --git a/components/squeezelite/a1s/ac101.c b/components/squeezelite/a1s/ac101.c index 17a02c47..420d2d78 100644 --- a/components/squeezelite/a1s/ac101.c +++ b/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); } /**************************************************************************************** diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index 3f18cd56..d33b8b09 100644 --- a/components/squeezelite/display.c +++ b/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); @@ -501,7 +501,7 @@ static void grfe_handler( u8_t *data, int len) { } // draw new frame - GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), displayer.width, displayer.height); + GDS_DrawBitmapCBR(display, data + sizeof(struct grfe_packet), displayer.width, displayer.height, GDS_COLOR_WHITE); GDS_Update(display); } @@ -580,7 +580,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 +599,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); } @@ -855,7 +856,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; diff --git a/components/squeezelite/output_i2s.c b/components/squeezelite/output_i2s.c index 3a6db561..1a05a498 100644 --- a/components/squeezelite/output_i2s.c +++ b/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; } } diff --git a/components/squeezelite/tas57xx/dac_57xx.c b/components/squeezelite/tas57xx/dac_57xx.c index 6a60dcb9..7d30af94 100644 --- a/components/squeezelite/tas57xx/dac_57xx.c +++ b/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; } } From 9a5d8dac4a40a8ee28849fb4902a8fdc3e5bb53a Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 24 Feb 2020 23:43:03 -0800 Subject: [PATCH 02/18] add ClearWindow optimization --- components/display/core/gds.c | 19 ++++++++++++++++--- components/display/core/gds.h | 9 +++++++++ components/display/core/gds_private.h | 5 +++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/components/display/core/gds.c b/components/display/core/gds.c index c80f18d8..6cec45d2 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -61,9 +61,22 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, // driver can provide onw optimized clear window if (Device->ClearWindow) { Device->ClearWindow( Device, x1, y1, x2, y2, 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 ); + } 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, 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 c = x1; c <= x2; c++) { + int r = y1; + while (r & 0x07 && r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); + for (; (r >> 3) < (y2 >> 3); r++) Device->Framebuffer[(r >> 3) * Width + c] = _Color; + // memset(Device->Framebuffer + (r >> 3) * Width, _Color, (y2 >> 3) - (r >> 3)); + while (r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); + } + } } else { for (int y = y1; y <= y2; y++) { for (int x = x1; x <= x2; x++) { diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 88156279..2c3a9e26 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -4,6 +4,15 @@ #include #include +/* 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, }; diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 806f5a90..3e2676ff 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -93,7 +93,7 @@ struct GDS_Device { bool FontForceMonospace; // various driver-specific method - // must provide + // must always provide bool (*Init)( struct GDS_Device* Device); void (*SetContrast)( struct GDS_Device* Device, uint8_t Contrast ); void (*DisplayOn)( struct GDS_Device* Device ); @@ -101,8 +101,9 @@ struct GDS_Device { void (*SetHFlip)( struct GDS_Device* Device, bool On ); void (*SetVFlip)( struct GDS_Device* Device, bool On ); void (*Update)( struct GDS_Device* Device ); - // may provide for optimization + // 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 ); + // 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 ); From 510987790902a17a2c4d5f33215d8e46b10eddd3 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Feb 2020 19:38:08 -0800 Subject: [PATCH 03/18] more display update (still need to fix ClearWindow) --- components/display/SH1106.c | 16 ++-- components/display/SSD1306.c | 16 ++-- components/display/SSD132x.c | 121 ++++++++++++++++++++++---- components/display/core/gds.c | 49 ++++++++--- components/display/core/gds_draw.c | 108 +++++++---------------- components/display/core/gds_draw.h | 2 +- components/display/core/gds_private.h | 3 +- components/display/display.c | 2 +- components/squeezelite/display.c | 13 ++- 9 files changed, 205 insertions(+), 125 deletions(-) diff --git a/components/display/SH1106.c b/components/display/SH1106.c index baba9c63..f6c1dc2f 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -80,19 +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; + +// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy +#ifdef SHADOW_BUFFER Device->Framebuffer = calloc( 1, Device->FramebufferSize ); NullCheck( Device->Framebuffer, return false ); - -#ifdef 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->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else -#else - Device->Shadowbuffer = malloc( Device->FramebufferSize ); #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 diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index f0a1b095..044aca24 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -78,19 +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; + +// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy +#ifdef SHADOW_BUFFER Device->Framebuffer = calloc( 1, Device->FramebufferSize ); NullCheck( Device->Framebuffer, return false ); - -#ifdef 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->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); else -#else - Device->Shadowbuffer = malloc( Device->FramebufferSize ); #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 diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index d93c0018..0508554c 100644 --- a/components/display/SSD132x.c +++ b/components/display/SSD132x.c @@ -32,7 +32,27 @@ struct SSD132x_Private { uint8_t Model; }; -// Functions are not deckared to minimize # of lines +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 ); @@ -48,17 +68,15 @@ static void SetRowAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End static void Update4( struct GDS_Device* Device ) { struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; - int r; - + // 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; - int page; - for (r = 0, page = 0; r < Device->Height; r++) { + 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) { @@ -74,10 +92,10 @@ static void Update4( struct GDS_Device* Device ) { 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, Device->Width * page / 2 ); + 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, Device->Width * page / 2 ); + Device->WriteData( Device, Device->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); } dirty = false; } @@ -85,17 +103,27 @@ static void Update4( struct GDS_Device* Device ) { } } #else - for (r = 0; r < Device->Height; r += Private->PageSize) { + for (int r = 0; r < Device->Height; r += Private->PageSize) { SetRowAddress( Device, r, r + Private->PageSize - 1 ); - Device->WriteData( Device, Device->Framebuffer + r * Device->Width / 2, Device->Width * Private->PageSize / 2 ); + 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, rows = Device->Height / 8; + 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 @@ -117,13 +145,38 @@ static void Update1( struct GDS_Device* Device ) { } } #else - // automatic counter and end Page/Column - SetColumnAddress( Device, 0, Device->Width - 1); - SetPageAddress( Device, 0, Device->Height / 8 - 1); + // 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))); @@ -163,14 +216,38 @@ static bool Init( struct GDS_Device* Device ) { 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->Shadowbuffer = malloc( Device->FramebufferSize ); - NullCheck( Device->Shadowbuffer, return false ); + 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 ); @@ -225,11 +302,19 @@ struct GDS_Device* SSD132x_Detect(char *Driver, struct GDS_Device* Device) { 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; - else Device->Depth = 4; + + if (Model == SSD1326 && Device->Depth == 1) { + Device->Update = Update1; + Device->DrawPixelFast = DrawPixel1Fast; + Device->DrawBitmapCBR = DrawBitmapCBR; + } else { + Device->Depth = 4; + } return Device; } \ No newline at end of file diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 6cec45d2..9b7a1c9a 100644 --- a/components/display/core/gds.c +++ b/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,38 +53,61 @@ 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 ) { - // driver can provide onw optimized clear window + // 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, Device->FramebufferSize ); + 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 c = x1; c <= x2; c++) { + int c; + for (c = x1; c <= x2; c++) { int r = y1; while (r & 0x07 && r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); - for (; (r >> 3) < (y2 >> 3); r++) Device->Framebuffer[(r >> 3) * Width + c] = _Color; - // memset(Device->Framebuffer + (r >> 3) * Width, _Color, (y2 >> 3) - (r >> 3)); + //for (; (r >> 3) < (y2 >> 3); r++) Device->Framebuffer[(r >> 3) * Width + c] = _Color; + memset(Device->Framebuffer + (r >> 3) * Width + c, _Color, (y2 - r) >> 3); while (r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); } - } + } + } 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 + int r; + for (r = y1; r <= y2; r++) { + int c = x1; + if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color); + //for (; (c >> 1) < (x2 >> 1); c++) Device->Framebuffer[(r * Width + c) >> 1] = _Color; + memset(Device->Framebuffer + ((r * Width +c) >> 1), _Color, (x2 - c) >> 1); + if (c < x2) GDS_DrawPixelFast( Device, c, r, Color); + } + } } else { - for (int y = y1; y <= y2; y++) { - for (int x = x1; x <= x2; x++) { + int y; + for (y = y1; y <= y2; y++) { + int x; + for (x = x1; x <= x2; x++) { GDS_DrawPixelFast( Device, x, y, Color); - } - } - } - + } + } + } + + // make sure diplay will do update Device->Dirty = true; } diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index 6b2f5c3e..f22d1d00 100644 --- a/components/display/core/gds_draw.c +++ b/components/display/core/gds_draw.c @@ -197,7 +197,9 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int if (!Width) Width = Device->Width; Height >>= 3; - if (Device->Depth == 1) { + 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; @@ -276,84 +278,36 @@ void GDS_DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int 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 if (Device->Depth == 4) { - for (int c = 0; c < Width; c++) { - for (int r = 0; r < Height; r++) { - int pixel = Image[r][c]; - switch(RGB_Mode) { - case GDS_RGB565: - pixel = (((pixel & 0x1f) * 11 + (((pixel >> 5) & 0x3f) * 59) / 2 + (pixel >> 11) * 30) / 100) >> 1; - break; - case GDS_RGB555: - pixel = (((pixel & 0x1f) * 11 + ((pixel >> 5) & 0x1f) * 59 + (pixel >> 10) * 30) / 100) >> 1; - break; - case GDS_RGB444: - pixel = ((pixel & 0x0f) * 11 + ((pixel >> 4) & 0x0f) * 59 + (pixel >> 8) * 30) / 100; - break; - case GDS_RGB8_GRAY: - pixel = Image[r][c] >> 4; - break; - } - GDS_DrawPixel( Device, c + x, r + y, pixel ); + } 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); + } } - } - } else if (Device->Depth == 1) { - for (int c = 0; c < Width; c++) { - for (int r = 0; r < Height; r++) { - int pixel = Image[r][c]; - switch(RGB_Mode) { - case GDS_RGB565: - pixel = (((pixel & 0x1f) * 21 + (((pixel >> 5) & 0x3f) * 71) / 2+ (pixel >> 11) * 7) / 100) >> 4; - break; - case GDS_RGB555: - pixel = (((pixel & 0x1f) * 21 + ((pixel >> 5) & 0x1f) * 71 + (pixel >> 10) * 7) / 100) >> 4; - break; - case GDS_RGB444: - pixel = (((pixel & 0x0f) * 21 + ((pixel >> 4) & 0x0f) * 71 + (pixel >> 8) * 7) / 100) >> 3; - break; - case GDS_RGB8_GRAY: - pixel = Image[r][c] >> 7; - } - GDS_DrawPixel( Device, c + x, r + y, pixel); + 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; } } } - -/**************************************************************************************** - * Process graphic display data MSBit first - * WARNING: this has not been tested yet - */ - /* -static void DrawBitmap(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; - } - } - } 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++; - } - } - } -} -*/ diff --git a/components/display/core/gds_draw.h b/components/display/core/gds_draw.h index 048fa354..9daf654a 100644 --- a/components/display/core/gds_draw.h +++ b/components/display/core/gds_draw.h @@ -11,7 +11,7 @@ extern "C" { struct GDS_Device; -enum { GDS_RGB565, GDS_RGB555, GDS_RGB444, GDS_RGB8_GRAY }; +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 ); diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 3e2676ff..bc883009 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -40,7 +40,7 @@ #define MAX_LINES 8 #if ! defined BIT -#define BIT( n ) ( 1 << n ) +#define BIT( n ) ( 1 << ( n ) ) #endif struct GDS_Device; @@ -103,6 +103,7 @@ struct GDS_Device { 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 ); diff --git a/components/display/display.c b/components/display/display.c index f626cbd9..e5e28a70 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -83,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 diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index d33b8b09..c79a6bfb 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -637,7 +637,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; @@ -705,15 +705,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); From a792a8cd5beb884fa72dbcb85b5bb132bd2ab6d3 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Feb 2020 20:10:16 -0800 Subject: [PATCH 04/18] only slow clear for now --- components/display/core/gds.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 9b7a1c9a..0ffbaa92 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -60,6 +60,14 @@ void GDS_Clear( struct GDS_Device* Device, int Color ) { } void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) { + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + GDS_DrawPixelFast( Device, x, y, Color); + } + } + return; + // driver can provide own optimized clear window if (Device->ClearWindow) { Device->ClearWindow( Device, x1, y1, x2, y2, Color ); From a9f124096568864a5d4ca3d0ee84a3da7dc79be0 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Tue, 25 Feb 2020 23:23:08 -0800 Subject: [PATCH 05/18] Fixed ClearWindow & few scroller tweaks systematic verification of ClearWindow done ... --- components/display/core/gds.c | 47 +++++++++++++++----------------- components/squeezelite/display.c | 10 +++++-- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 0ffbaa92..353dc414 100644 --- a/components/display/core/gds.c +++ b/components/display/core/gds.c @@ -60,14 +60,6 @@ void GDS_Clear( struct GDS_Device* Device, int Color ) { } void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) { - - for (int y = y1; y <= y2; y++) { - for (int x = x1; x <= x2; x++) { - GDS_DrawPixelFast( Device, x, y, Color); - } - } - return; - // driver can provide own optimized clear window if (Device->ClearWindow) { Device->ClearWindow( Device, x1, y1, x2, y2, Color ); @@ -79,13 +71,21 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, uint8_t _Color = Color == GDS_COLOR_BLACK ? 0: 0xff; uint8_t Width = Device->Width; // try to do byte processing as much as possible - int c; - for (c = x1; c <= x2; c++) { - int r = y1; - while (r & 0x07 && r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); - //for (; (r >> 3) < (y2 >> 3); r++) Device->Framebuffer[(r >> 3) * Width + c] = _Color; - memset(Device->Framebuffer + (r >> 3) * Width + c, _Color, (y2 - r) >> 3); - while (r <= y2) GDS_DrawPixelFast( Device, c, r++, Color); + 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) { @@ -96,20 +96,17 @@ void GDS_ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, uint8_t _Color = Color | (Color << 4); uint8_t Width = Device->Width; // try to do byte processing as much as possible - int r; - for (r = y1; r <= y2; r++) { + for (int r = y1; r <= y2; r++) { int c = x1; if (c & 0x01) GDS_DrawPixelFast( Device, c++, r, Color); - //for (; (c >> 1) < (x2 >> 1); c++) Device->Framebuffer[(r * Width + c) >> 1] = _Color; - memset(Device->Framebuffer + ((r * Width +c) >> 1), _Color, (x2 - c) >> 1); - if (c < x2) 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 { - int y; - for (y = y1; y <= y2; y++) { - int x; - for (x = x1; x <= x2; x++) { + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { GDS_DrawPixelFast( Device, x, y, Color); } } diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index c79a6bfb..f7c9b385 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -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, GDS_COLOR_WHITE); + // 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; @@ -779,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; From 5fcf08e4c51e8b8fc98bffc9212cb5e75562d293 Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 26 Feb 2020 15:56:15 -0500 Subject: [PATCH 06/18] httpd ready for some testing - release --- components/telnet/telnet.c | 30 +++++++++++++++++++----------- main/esp_app_main.c | 23 ++++++++++------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/components/telnet/telnet.c b/components/telnet/telnet.c index b9b94f06..0f603ae4 100644 --- a/components/telnet/telnet.c +++ b/components/telnet/telnet.c @@ -47,7 +47,7 @@ #define TELNET_STACK_SIZE 8048 #define TELNET_RX_BUF 1024 -const static char tag[] = "telnet"; +const static char TAG[] = "telnet"; static int uart_fd=0; RingbufHandle_t buf_handle; //static SemaphoreHandle_t xSemaphore = NULL; @@ -56,6 +56,7 @@ static size_t log_buf_size=2000; //32-bit aligned size static bool bIsEnabled=false; static int partnerSocket=0; static telnet_t *tnHandle; +extern bool bypass_wifi_manager; /************************************ * Forward declarations @@ -77,16 +78,23 @@ struct telnetUserData { }; bool is_serial_suppressed(){ - return !bIsEnabled || !bMirrorToUART ; + return bIsEnabled?!bMirrorToUART:false ; } void init_telnet(){ char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable"); if (!val || strlen(val) == 0 || !strcasestr("YXD",val) ) { - ESP_LOGI(tag,"Telnet support disabled"); + ESP_LOGI(TAG,"Telnet support disabled"); if(val) free(val); return; } - bMirrorToUART = strcasestr("D",val)!=NULL; + // if wifi manager is bypassed, there will possibly be no wifi available + // + bMirrorToUART = (strcasestr("D",val)!=NULL); + if(!bMirrorToUART && bypass_wifi_manager){ + // This isn't supposed to happen, as telnet won't start if wifi manager isn't + // started. So this is a safeguard only. + ESP_LOGW(TAG,"Wifi manager is not active. Forcing console on Serial output."); + } FREE_AND_NULL(val); val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block"); @@ -110,11 +118,11 @@ void init_telnet(){ uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT ); buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct); if (buf_handle == NULL) { - ESP_LOGE(tag,"Failed to create ring buffer for telnet!"); + ESP_LOGE(TAG,"Failed to create ring buffer for telnet!"); return; } - ESP_LOGI(tag, "***Redirecting log output to telnet"); + ESP_LOGI(TAG, "***Redirecting log output to telnet"); const esp_vfs_t vfs = { .flags = ESP_VFS_FLAG_DEFAULT, .write = &stdout_write, @@ -152,14 +160,14 @@ static void telnet_task(void *data) { int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); if (rc < 0) { - ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "bind: %d (%s)", errno, strerror(errno)); close(serverSocket); return; } rc = listen(serverSocket, 5); if (rc < 0) { - ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "listen: %d (%s)", errno, strerror(errno)); close(serverSocket); return; } @@ -168,14 +176,14 @@ static void telnet_task(void *data) { socklen_t len = sizeof(serverAddr); rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len); if (rc < 0 ){ - ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno)); + ESP_LOGE(TAG, "accept: %d (%s)", errno, strerror(errno)); return; } else { partnerSocket = rc; - ESP_LOGD(tag, "We have a new client connection!"); + ESP_LOGD(TAG, "We have a new client connection!"); handle_telnet_conn(); - ESP_LOGD(tag, "Telnet connection terminated"); + ESP_LOGD(TAG, "Telnet connection terminated"); } } close(serverSocket); diff --git a/main/esp_app_main.c b/main/esp_app_main.c index 1cdaa920..10234c45 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -154,7 +154,7 @@ esp_err_t update_certificates(){ if ( (esp_err= nvs_get_str(handle, certs_version, NULL, &len)) == ESP_OK) { str=(char *)malloc(len); if ( (esp_err = nvs_get_str(handle, certs_version, str, &len)) == ESP_OK) { - printf("String associated with key '%s' is %s \n", certs_version, str); + ESP_LOGI(TAG,"String associated with key '%s' is %s", certs_version, str); } } if(str!=NULL){ @@ -341,10 +341,7 @@ void register_default_nvs(){ ESP_LOGD(TAG,"Done setting default values in nvs."); } -void displayInitCallback(TimerHandle_t pxTimer){ - ESP_LOGD(TAG,"Initializing display"); - display_init("SqueezeESP32"); -} + void app_main() { char * fwurl = NULL; @@ -360,17 +357,15 @@ void app_main() wifi_event_group = xEventGroupCreate(); ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group"); xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); - ESP_LOGI(TAG,"Registering default values"); register_default_nvs(); - ESP_LOGD(TAG,"Configuring services"); + ESP_LOGI(TAG,"Configuring services"); services_init(); - // initialize display in a timer thread to prevent locking up - // the main init sequence - TimerHandle_t display_init = xTimerCreate( "DisplInit", 100,pdFALSE,NULL,displayInitCallback); - xTimerStart(display_init, portMAX_DELAY); + + ESP_LOGI(TAG,"Initializing display"); + display_init("SqueezeESP32"); #if !RECOVERY_APPLICATION ESP_LOGI(TAG,"Checking if certificates need to be updated"); update_certificates(); @@ -404,10 +399,12 @@ void app_main() led_blink(LED_GREEN, 250, 250); if(bypass_wifi_manager){ - ESP_LOGW(TAG,"\n\nwifi manager is disabled. Please use wifi commands to connect to your wifi access point.\n\n"); + ESP_LOGW(TAG,"*******************************************************************************************"); + ESP_LOGW(TAG,"* wifi manager is disabled. Please use wifi commands to connect to your wifi access point."); + ESP_LOGW(TAG,"*******************************************************************************************"); } else { - ESP_LOGW(TAG,"\n\nwifi manager is ENABLED. Starting...\n\n"); + ESP_LOGI(TAG,"Starting Wifi Manager"); wifi_manager_start(); wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip); wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected); From 59a617a40d7f0289b53f9afcdffade1d2178b6bb Mon Sep 17 00:00:00 2001 From: Sebastien Date: Wed, 26 Feb 2020 23:18:58 -0500 Subject: [PATCH 07/18] merge display updates from master - release --- components/squeezelite-ota/squeezelite-ota.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index 8b48e840..734f827c 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -40,7 +40,7 @@ extern const char * get_certificate(); #define OTA_CORE 1 #endif - +static const size_t bin_ota_chunk = 40000; static const char *TAG = "squeezelite-ota"; esp_http_client_handle_t ota_http_client = NULL; #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1 @@ -235,7 +235,7 @@ esp_err_t init_config(ota_thread_parms_t * p_ota_thread_parms){ case OTA_TYPE_BUFFER: ota_status.ota_write_data = p_ota_thread_parms->bin; ota_status.total_image_len = p_ota_thread_parms->length; - + ota_status.buffer_size = bin_ota_chunk; break; default: return ESP_FAIL; @@ -418,7 +418,6 @@ void ota_task_cleanup(const char * message, ...){ void ota_task(void *pvParameter) { esp_err_t err = ESP_OK; - size_t buffer_size = BUFFSIZE; ESP_LOGD(TAG, "HTTP ota Thread started"); const esp_partition_t *configured = esp_ota_get_boot_partition(); const esp_partition_t *running = esp_ota_get_running_partition(); @@ -502,8 +501,8 @@ void ota_task(void *pvParameter) data_read = esp_http_client_read(ota_http_client, ota_status.ota_write_data, ota_status.buffer_size); } else { - if(ota_status.remain_image_len >buffer_size){ - data_read = buffer_size; + if(ota_status.remain_image_len >ota_status.buffer_size){ + data_read = ota_status.buffer_size; } else { data_read = ota_status.remain_image_len; } From 1ae8f80e536d8cacefb93df26b7d8be3a84cc0ea Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:27:28 -0800 Subject: [PATCH 08/18] Complete display + add JPEG --- components/display/SSD132x.c | 20 ++ components/display/core/gds.c | 14 +- components/display/core/gds_default_if.h | 2 +- components/display/core/gds_image.c | 225 ++++++++++++++++++ components/display/core/gds_image.h | 23 ++ .../display/core/ifaces/default_if_i2c.c | 9 +- components/display/display.c | 3 +- components/services/accessors.c | 4 +- components/services/globdefs.h | 1 + components/services/services.c | 1 + 10 files changed, 290 insertions(+), 12 deletions(-) create mode 100644 components/display/core/gds_image.c create mode 100644 components/display/core/gds_image.h diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index 0508554c..cdb5779f 100644 --- a/components/display/SSD132x.c +++ b/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; } diff --git a/components/display/core/gds.c b/components/display/core/gds.c index 353dc414..7f59f79e 100644 --- a/components/display/core/gds.c +++ b/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); } } diff --git a/components/display/core/gds_default_if.h b/components/display/core/gds_default_if.h index 698cb5e1..aac616e0 100644 --- a/components/display/core/gds_default_if.h +++ b/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 ); diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c new file mode 100644 index 00000000..0500ac1a --- /dev/null +++ b/components/display/core/gds_image.c @@ -0,0 +1,225 @@ +#include +#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) { + // ready to decode + Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); + uint8_t N = 0, iScale = 1.0 / Scale; + while (iScale >>= 1) N++; + 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; +} + diff --git a/components/display/core/gds_image.h b/components/display/core/gds_image.h new file mode 100644 index 00000000..fa87001a --- /dev/null +++ b/components/display/core/gds_image.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#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); \ No newline at end of file diff --git a/components/display/core/ifaces/default_if_i2c.c b/components/display/core/ifaces/default_if_i2c.c index a2b80c8b..c4b91b16 100644 --- a/components/display/core/ifaces/default_if_i2c.c +++ b/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 ); } diff --git a/components/display/display.c b/components/display/display.c index e5e28a70..689aa8e1 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -89,11 +89,12 @@ void display_init(char *welcome) { // Detect driver interface if (strstr(config, "I2C") && i2c_system_port != -1) { int address = 0x3C; + int speed = 0; 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); diff --git a/components/services/accessors.c b/components/services/accessors.c index 72543de8..cd274476 100644 --- a/components/services/accessors.c +++ b/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); diff --git a/components/services/globdefs.h b/components/services/globdefs.h index 5fdfec5b..8faf2d48 100644 --- a/components/services/globdefs.h +++ b/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; diff --git a/components/services/services.c b/components/services/services.c index 36e53b60..6538f128 100644 --- a/components/services/services.c +++ b/components/services/services.c @@ -22,6 +22,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; From c00f513be7f87db33dfe7cf8f1a5ca61062390c7 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:48:18 -0800 Subject: [PATCH 09/18] forgot a few backports - release --- components/display/core/gds_draw.c | 44 +-------------------------- components/display/core/gds_draw.h | 5 --- components/display/core/gds_image.c | 9 ++++-- components/display/core/gds_private.h | 2 +- 4 files changed, 8 insertions(+), 52 deletions(-) diff --git a/components/display/core/gds_draw.c b/components/display/core/gds_draw.c index f22d1d00..60467a33 100644 --- a/components/display/core/gds_draw.c +++ b/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; - } - } -} +} \ No newline at end of file diff --git a/components/display/core/gds_draw.h b/components/display/core/gds_draw.h index 9daf654a..e98f958c 100644 --- a/components/display/core/gds_draw.h +++ b/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); diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index 0500ac1a..e7d4dac7 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -90,10 +90,13 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - // ready to decode + // find the scaling factor Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); - uint8_t N = 0, iScale = 1.0 / Scale; - while (iScale >>= 1) N++; + 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); diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index bc883009..60893be5 100644 --- a/components/display/core/gds_private.h +++ b/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 From bbe602284414df211d250079cd8fef9890e5bef9 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:53:42 -0800 Subject: [PATCH 10/18] add AirPlay/BT pause length option - release --- components/display/display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/display/display.c b/components/display/display.c index 689aa8e1..a1cb0e4d 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -89,7 +89,6 @@ void display_init(char *welcome) { // Detect driver interface if (strstr(config, "I2C") && i2c_system_port != -1) { int address = 0x3C; - int speed = 0; if ((p = strcasestr(config, "address")) != NULL) address = atoi(strchr(p, '=') + 1); @@ -275,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); From 5fb4b14e87562219a30dbd40ac6082417cd2dae0 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 26 Feb 2020 23:56:20 -0800 Subject: [PATCH 11/18] Update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index eed79d09..bb701d3b 100644 --- a/README.md +++ b/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=,scl=[,port=0|1][,speed=] ``` @@ -62,16 +62,18 @@ I2C,width=,height=[address=][,HFlip][,VFlip][driver SPI,width=,height=,cs=[,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=][,speed=] +[format=][,speed=][,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 will be displayed not " - <title>". ### Set GPIO From e550c08273af906b9cdd26626edc50024481c64c Mon Sep 17 00:00:00 2001 From: Sebastien <sle118@hotmail.com> Date: Thu, 27 Feb 2020 17:45:24 -0500 Subject: [PATCH 12/18] leverage displayer to show flash update on display --- components/squeezelite-ota/squeezelite-ota.c | 37 +++++++++++++++++--- components/squeezelite-ota/squeezelite-ota.h | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index 734f827c..e0fe10c5 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -31,6 +31,7 @@ #include "messaging.h" #include "trace.h" #include "esp_ota_ops.h" +#include "display.h" extern const char * get_certificate(); @@ -40,7 +41,7 @@ extern const char * get_certificate(); #define OTA_CORE 1 #endif -static const size_t bin_ota_chunk = 40000; +static const size_t bin_ota_chunk = 4096*2; static const char *TAG = "squeezelite-ota"; esp_http_client_handle_t ota_http_client = NULL; #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1 @@ -85,7 +86,12 @@ uint8_t ota_get_pct_complete(){ return ota_status.total_image_len==0?0: (uint8_t)((float)ota_status.actual_image_len/(float)ota_status.total_image_len*100.0f); } - +static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd); +static bool display_dummy_handler(void *from, enum display_bus_cmd_e cmd) { + // chain to rest of "bus" + if (display_bus_chain) return (*display_bus_chain)(from, cmd); + else return true; +} void sendMessaging(messaging_types type,const char * fmt, ...){ va_list args; cJSON * msg = cJSON_CreateObject(); @@ -111,7 +117,7 @@ void sendMessaging(messaging_types type,const char * fmt, ...){ } va_end(args); - + displayer_scroll(msg_str, 33); cJSON_AddStringToObject(msg,"ota_dsc",str_or_unknown(msg_str)); free(msg_str); cJSON_AddNumberToObject(msg,"ota_pct", ota_get_pct_complete() ); @@ -121,7 +127,19 @@ void sendMessaging(messaging_types type,const char * fmt, ...){ cJSON_free(msg); _printMemStats(); } - +//esp_err_t decode_alloc_ota_message(single_message_t * message, char * ota_dsc, uint8_t * ota_pct ){ +// if(!message || !message->message) return ESP_ERR_INVALID_ARG; +// cJSON * json = cJSON_Parse(message->message); +// if(!json) return ESP_FAIL; +// if(ota_dsc) { +// ota_dsc = strdup(cJSON_GetObjectItem(json, "ota_dsc")?cJSON_GetStringValue(cJSON_GetObjectItem(json, "ota_dsc")):""); +// } +// if(ota_pct){ +// *ota_pct = cJSON_GetObjectItem(json, "ota_pct")?cJSON_GetObjectItem(json, "ota_pct")->valueint:0; +// } +// cJSON_free(json); +// return ESP_OK; +//} static void __attribute__((noreturn)) task_fatal_error(void) { @@ -413,11 +431,16 @@ void ota_task_cleanup(const char * message, ...){ ota_http_client=NULL; } ota_status.bOTAStarted = false; + displayer_control(DISPLAYER_SHUTDOWN); task_fatal_error(); } + + void ota_task(void *pvParameter) { esp_err_t err = ESP_OK; + displayer_control(DISPLAYER_ACTIVATE, "Firmware update"); + displayer_scroll("Initializing...", 33); ESP_LOGD(TAG, "HTTP ota Thread started"); const esp_partition_t *configured = esp_ota_get_boot_partition(); const esp_partition_t *running = esp_ota_get_running_partition(); @@ -559,11 +582,14 @@ void ota_task(void *pvParameter) ota_status.ota_write_data+= data_read; } ESP_LOGD(TAG, "Written image length %d", ota_status.actual_image_len); + if(ota_get_pct_complete()%5 == 0) ota_status.newpct = ota_get_pct_complete(); if(ota_status.lastpct!=ota_status.newpct ) { + gettimeofday(&tv, NULL); uint32_t elapsed_ms= (tv.tv_sec-ota_status.OTA_start.tv_sec )*1000+(tv.tv_usec-ota_status.OTA_start.tv_usec)/1000; ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.actual_image_len, ota_status.total_image_len, ota_status.newpct, elapsed_ms>0?ota_status.actual_image_len*1000/elapsed_ms/1024:0); + sendMessaging(MESSAGING_INFO,ota_status.ota_type == OTA_TYPE_HTTP?"Downloading & writing update.":"Writing binary file."); ota_status.lastpct=ota_status.newpct; } @@ -592,6 +618,7 @@ void ota_task(void *pvParameter) if (err == ESP_OK) { ESP_LOGI(TAG,"OTA Process completed successfully!"); sendMessaging(MESSAGING_INFO,"Success!"); + vTaskDelay(1000/ portTICK_PERIOD_MS); // wait here to give the UI a chance to refresh esp_restart(); } else { @@ -604,6 +631,8 @@ void ota_task(void *pvParameter) esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length){ int ret = 0; + display_bus_chain = display_bus; + display_bus = display_dummy_handler; uint16_t stack_size, task_priority; if(ota_status.bOTAThreadStarted){ ESP_LOGE(TAG,"OTA Already started. "); diff --git a/components/squeezelite-ota/squeezelite-ota.h b/components/squeezelite-ota/squeezelite-ota.h index 8629404f..0d8718e9 100644 --- a/components/squeezelite-ota/squeezelite-ota.h +++ b/components/squeezelite-ota/squeezelite-ota.h @@ -11,6 +11,7 @@ #include "esp_ota_ops.h" #include "sys/param.h" + #if RECOVERY_APPLICATION #define CODE_RAM_LOCATION #define RECOVERY_IRAM_FUNCTION IRAM_ATTR @@ -38,4 +39,3 @@ esp_err_t start_ota(const char * bin_url, char * bin_buffer, uint32_t length); - From 876ae491a1c21a6c456c64ee4043730b5cdc104e Mon Sep 17 00:00:00 2001 From: philippe44 <philippe_44@outlook.com> Date: Thu, 27 Feb 2020 18:23:00 -0800 Subject: [PATCH 13/18] add 'pause' --- components/display/SSD1306.c | 19 +++++++++++++++++-- components/display/display.c | 3 ++- components/display/display.h | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index 044aca24..1e27fcb7 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -39,6 +39,7 @@ static void Update( struct GDS_Device* Device ) { // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + int CurrentRow = -1, FirstCol = -1, LastCol = -1; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -53,8 +54,22 @@ static void Update( struct GDS_Device* Device ) { // now update the display by "byte rows" if (first--) { - SetColumnAddress( Device, first, last ); - SetPageAddress( Device, r, r); + + // only set column when useful, saves a fair bit of CPU + if (first > FirstCol && first <= FirstCol + 4 && last < LastCol && last >= LastCol - 4) { + first = FirstCol; + last = LastCol; + } else { + SetColumnAddress( Device, first, last ); + FirstCol = first; + LastCol = last; + } + + // Set row only when needed, otherwise let auto-increment work + if (r != CurrentRow) SetPageAddress( Device, r, Device->Height / 8 - 1 ); + CurrentRow = r + 1; + + // actual write Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); } } diff --git a/components/display/display.c b/components/display/display.c index a1cb0e4d..8741cd7b 100644 --- a/components/display/display.c +++ b/components/display/display.c @@ -289,13 +289,14 @@ void displayer_metadata(char *artist, char *album, char *title) { /**************************************************************************************** * */ -void displayer_scroll(char *string, int speed) { +void displayer_scroll(char *string, int speed, int pause) { // need a display! if (!display) return; xSemaphoreTake(displayer.mutex, portMAX_DELAY); if (speed) displayer.speed = speed; + if (pause) displayer.pause = pause; displayer.offset = 0; strncpy(displayer.string, string, SCROLLABLE_SIZE); displayer.string[SCROLLABLE_SIZE] = '\0'; diff --git a/components/display/display.h b/components/display/display.h index 98b2dc92..e1b809e5 100644 --- a/components/display/display.h +++ b/components/display/display.h @@ -42,7 +42,7 @@ enum displayer_time_e { DISPLAYER_ELAPSED, DISPLAYER_REMAINING }; enum display_bus_cmd_e { DISPLAY_BUS_TAKE, DISPLAY_BUS_GIVE }; bool (*display_bus)(void *from, enum display_bus_cmd_e cmd); -void displayer_scroll(char *string, int speed); +void displayer_scroll(char *string, int speed, int pause); void displayer_control(enum displayer_cmd_e cmd, ...); void displayer_metadata(char *artist, char *album, char *title); void displayer_timer(enum displayer_time_e mode, int elapsed, int duration); From 2fadea10b0e984b71bf940b744ee1c0e4516c729 Mon Sep 17 00:00:00 2001 From: Sebastien <sle118@hotmail.com> Date: Thu, 27 Feb 2020 21:24:12 -0500 Subject: [PATCH 14/18] add display for OTA progress --- components/squeezelite-ota/squeezelite-ota.c | 7 ++++--- main/console.c | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index e0fe10c5..12b62a68 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -88,7 +88,8 @@ uint8_t ota_get_pct_complete(){ } static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd); static bool display_dummy_handler(void *from, enum display_bus_cmd_e cmd) { - // chain to rest of "bus" + + // Nothing implemented at this point if (display_bus_chain) return (*display_bus_chain)(from, cmd); else return true; } @@ -117,7 +118,7 @@ void sendMessaging(messaging_types type,const char * fmt, ...){ } va_end(args); - displayer_scroll(msg_str, 33); + displayer_scroll(msg_str, 33, 250); cJSON_AddStringToObject(msg,"ota_dsc",str_or_unknown(msg_str)); free(msg_str); cJSON_AddNumberToObject(msg,"ota_pct", ota_get_pct_complete() ); @@ -440,7 +441,7 @@ void ota_task(void *pvParameter) { esp_err_t err = ESP_OK; displayer_control(DISPLAYER_ACTIVATE, "Firmware update"); - displayer_scroll("Initializing...", 33); + displayer_scroll("Initializing...", 33, 250); ESP_LOGD(TAG, "HTTP ota Thread started"); const esp_partition_t *configured = esp_ota_get_boot_partition(); const esp_partition_t *running = esp_ota_get_running_partition(); diff --git a/main/console.c b/main/console.c index 62bc5199..5daa7164 100644 --- a/main/console.c +++ b/main/console.c @@ -26,6 +26,11 @@ #include "console.h" #include "wifi_manager.h" #include "telnet.h" +#include "gds.h" +#include "gds_default_if.h" +#include "gds_draw.h" +#include "gds_text.h" +#include "gds_font.h" #include "cmd_squeezelite.h" #include "config.h" @@ -239,6 +244,9 @@ void console_start() { #if !RECOVERY_APPLICATION // process autoexec locally, as we're not going to start the console thread process_autoexec(); +#else + GDS_ClearExt(display, true); + GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_UPDATE, "Recovery mode"); #endif } } From 439f5b88510efd97638b2900c93974e92d88b3f4 Mon Sep 17 00:00:00 2001 From: Sebastien <sle118@hotmail.com> Date: Thu, 27 Feb 2020 21:33:02 -0500 Subject: [PATCH 15/18] tweaking recovery display --- main/console.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main/console.c b/main/console.c index 5daa7164..6accf459 100644 --- a/main/console.c +++ b/main/console.c @@ -31,7 +31,7 @@ #include "gds_draw.h" #include "gds_text.h" #include "gds_font.h" - +#include "display.h" #include "cmd_squeezelite.h" #include "config.h" pthread_t thread_console; @@ -191,6 +191,7 @@ void console_start() { if(!is_serial_suppressed()){ printf("\n" #if RECOVERY_APPLICATION + "****************************************************************\n" "RECOVERY APPLICATION\n" "This mode is used to flash Squeezelite into the OTA partition\n" @@ -207,7 +208,11 @@ void console_start() { #endif "\n" "\n"); +#if RECOVERY_APPLICATION + GDS_SetFont(display, &Font_droid_sans_fallback_15x17 ); + GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY"); +#endif /* Figure out if the terminal supports escape sequences */ int probe_status = linenoiseProbe(); if (probe_status) { /* zero indicates success */ @@ -244,9 +249,7 @@ void console_start() { #if !RECOVERY_APPLICATION // process autoexec locally, as we're not going to start the console thread process_autoexec(); -#else - GDS_ClearExt(display, true); - GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_UPDATE, "Recovery mode"); + #endif } } From 7584b1bd5b9a65db330820021126a195a716f2ee Mon Sep 17 00:00:00 2001 From: philippe44 <philippe_44@outlook.com> Date: Thu, 27 Feb 2020 23:05:04 -0800 Subject: [PATCH 16/18] scroller correction + JPEG improvements --- components/display/SH1106.c | 18 +++++++++++------ components/display/SSD1306.c | 21 +++++++++++-------- components/display/SSD132x.c | 21 ++++++++++--------- components/display/core/gds.h | 7 ++++--- components/display/core/gds_image.c | 29 +++++++++++++++++++-------- components/display/core/gds_private.h | 2 +- components/squeezelite/display.c | 16 ++++++++------- 7 files changed, 71 insertions(+), 43 deletions(-) diff --git a/components/display/SH1106.c b/components/display/SH1106.c index f6c1dc2f..f4b93dfe 100644 --- a/components/display/SH1106.c +++ b/components/display/SH1106.c @@ -21,6 +21,10 @@ static char TAG[] = "SH1106"; +struct SH1106_Private { + uint8_t *Shadowbuffer; +}; + // Functions are not declared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { @@ -36,9 +40,10 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SH1106_Private *Private = (struct SH1106_Private*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -55,7 +60,7 @@ static void Update( struct GDS_Device* Device ) { if (first--) { SetColumnAddress( Device, first, last ); SetPageAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -83,15 +88,16 @@ static bool Init( struct GDS_Device* Device ) { // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER + struct SH1106_Private *Private = (struct SH1106_Private*) Device->Private; 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 ); + if (Device->IF == IF_SPI) Private->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); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->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 diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index 1e27fcb7..7cea4505 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -21,6 +21,10 @@ static char TAG[] = "SSD1306"; +struct SSD1306_Private { + uint8_t *Shadowbuffer; +}; + // Functions are not deckared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { @@ -36,9 +40,10 @@ static void SetPageAddress( struct GDS_Device* Device, uint8_t Start, uint8_t En static void Update( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SSD1306_Private *Private = (struct SSD1306_Private*) Device->Private; // not sure the compiler does not have to redo all calculation in for loops, so local it is int width = Device->Width, rows = Device->Height / 8; - uint8_t *optr = Device->Shadowbuffer, *iptr = Device->Framebuffer; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; int CurrentRow = -1, FirstCol = -1, LastCol = -1; // by row, find first and last columns that have been updated @@ -54,7 +59,6 @@ static void Update( struct GDS_Device* Device ) { // now update the display by "byte rows" if (first--) { - // only set column when useful, saves a fair bit of CPU if (first > FirstCol && first <= FirstCol + 4 && last < LastCol && last >= LastCol - 4) { first = FirstCol; @@ -70,7 +74,7 @@ static void Update( struct GDS_Device* Device ) { CurrentRow = r + 1; // actual write - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -94,17 +98,18 @@ static void SetContrast( struct GDS_Device* Device, uint8_t Contrast ) { static bool Init( struct GDS_Device* Device ) { Device->FramebufferSize = ( Device->Width * Device->Height ) / 8; -// benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy + // benchmarks showed little gain to have SPI memory already in IRAL vs letting driver copy #ifdef SHADOW_BUFFER + struct SSD1306_Private *Private = (struct SSD1306_Private*) Device->Private; 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 ); + if (Device->IF == IF_SPI) Private->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); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + NullCheck( Private->Shadowbuffer, return false ); + memset(Private->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 diff --git a/components/display/SSD132x.c b/components/display/SSD132x.c index cdb5779f..cdbadc2b 100644 --- a/components/display/SSD132x.c +++ b/components/display/SSD132x.c @@ -27,7 +27,7 @@ static char TAG[] = "SSD132x"; enum { SSD1326, SSD1327 }; struct SSD132x_Private { - uint8_t *iRAM; + uint8_t *iRAM, *Shadowbuffer; uint8_t ReMap, PageSize; uint8_t Model; }; @@ -73,7 +73,7 @@ static void Update4( struct GDS_Device* Device ) { SetColumnAddress( Device, 0, Device->Width / 2 - 1); #ifdef SHADOW_BUFFER - uint16_t *optr = (uint16_t*) Device->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; + uint16_t *optr = (uint16_t*) Private->Shadowbuffer, *iptr = (uint16_t*) Device->Framebuffer; bool dirty = false; for (int r = 0, page = 0; r < Device->Height; r++) { @@ -92,10 +92,10 @@ static void Update4( struct GDS_Device* Device ) { 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 ); + memcpy(Private->iRAM, Private->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 ); + Device->WriteData( Device, Private->Shadowbuffer + (r - page + 1) * Device->Width / 2, page * Device->Width / 2 ); } dirty = false; } @@ -122,9 +122,10 @@ static void Update4( struct GDS_Device* Device ) { */ static void Update1( struct GDS_Device* Device ) { #ifdef SHADOW_BUFFER + struct SSD132x_Private *Private = (struct SSD132x_Private*) Device->Private; // 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; + uint8_t *optr = Private->Shadowbuffer, *iptr = Device->Framebuffer; // by row, find first and last columns that have been updated for (int r = 0; r < rows; r++) { @@ -141,7 +142,7 @@ static void Update1( struct GDS_Device* Device ) { if (first--) { SetColumnAddress( Device, first, last ); SetRowAddress( Device, r, r); - Device->WriteData( Device, Device->Shadowbuffer + r*width + first, last - first + 1); + Device->WriteData( Device, Private->Shadowbuffer + r*width + first, last - first + 1); } } #else @@ -242,15 +243,15 @@ static bool Init( struct GDS_Device* Device ) { #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 ); + Private->Shadowbuffer = heap_caps_malloc( Device->FramebufferSize, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA ); } else { - Device->Shadowbuffer = malloc( Device->FramebufferSize ); + Private->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); + Private->Shadowbuffer = malloc( Device->FramebufferSize ); + memset(Private->Shadowbuffer, 0xFF, Device->FramebufferSize); #else // not SHADOW_BUFFER #ifdef USE_IRAM if (Device->IF == IF_SPI) { diff --git a/components/display/core/gds.h b/components/display/core/gds.h index 2c3a9e26..f1b5475c 100644 --- a/components/display/core/gds.h +++ b/components/display/core/gds.h @@ -13,13 +13,14 @@ 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, +enum { GDS_COLOR_L0 = 0, GDS_COLOR_L1 = 1, 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, + GDS_COLOR_MAX }; #define GDS_COLOR_BLACK GDS_COLOR_L0 -#define GDS_COLOR_WHITE GDS_COLOR_L15 -#define GDS_COLOR_XOR 2 +#define GDS_COLOR_WHITE GDS_COLOR_L1 +#define GDS_COLOR_XOR (GDS_COLOR_MAX + 1) struct GDS_Device; struct GDS_FontDef; diff --git a/components/display/core/gds_image.c b/components/display/core/gds_image.c index e7d4dac7..a5919f74 100644 --- a/components/display/core/gds_image.c +++ b/components/display/core/gds_image.c @@ -21,6 +21,7 @@ typedef struct { struct { // DirectDraw struct GDS_Device * Device; int XOfs, YOfs; + int XMin, YMin; int Depth; }; }; @@ -38,9 +39,7 @@ static unsigned OutHandler(JDEC *Decoder, void *Bitmap, JRECT *Frame) { 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; @@ -57,12 +56,14 @@ static unsigned OutHandlerDirect(JDEC *Decoder, void *Bitmap, JRECT *Frame) { int Shift = 8 - Context->Depth; for (int y = Frame->top; y <= Frame->bottom; y++) { + if (y < Context->YMin) continue; for (int x = Frame->left; x <= Frame->right; x++) { + if (x < Context->XMin) continue; // 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); + GDS_DrawPixel( Context->Device, x + Context->XOfs, y + Context->YOfs, Value); } } return 1; @@ -90,11 +91,16 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal Decoder.scale = Scale; if (Res == JDR_OK && !SizeOnly) { - // find the scaling factor Context.OutData = malloc(Decoder.width * Decoder.height * sizeof(uint16_t)); + + // find the scaling factor uint8_t N = 0, ScaleInt = ceil(1.0 / Scale); ScaleInt--; ScaleInt |= ScaleInt >> 1; ScaleInt |= ScaleInt >> 2; ScaleInt++; while (ScaleInt >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } // ready to decode if (Context.OutData) { @@ -102,7 +108,7 @@ static uint16_t* DecodeJPEG(uint8_t *Source, int *Width, int *Height, float Scal 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); + Res = jd_decomp(&Decoder, OutHandler, N); if (Res != JDR_OK) { ESP_LOGE(TAG, "Image decoder: jd_decode failed (%d)", Res); } @@ -202,16 +208,23 @@ bool GDS_DrawJPEG( struct GDS_Device* Device, uint8_t *Source, int x, int y, int uint8_t Ratio = XRatio < YRatio ? ceil(1/XRatio) : ceil(1/YRatio); Ratio--; Ratio |= Ratio >> 1; Ratio |= Ratio >> 2; Ratio++; while (Ratio >>= 1) N++; + if (N > 3) { + ESP_LOGW(TAG, "Image will not fit %dx%d", Decoder.width, Decoder.height); + N = 3; + } 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; + if (Fit & GDS_IMAGE_CENTER_X) Context.XOfs = (Device->Width + x - Context.Width) / 2; + if (Fit & GDS_IMAGE_CENTER_Y) Context.YOfs = (Device->Height + y - Context.Height) / 2; + Context.XMin = x - Context.XOfs; + Context.YMin = y - Context.YOfs; + // do decompress & draw - Res = jd_decomp(&Decoder, OutHandlerDirect, N > 3 ? 3 : N); + Res = jd_decomp(&Decoder, OutHandlerDirect, N); if (Res == JDR_OK) { Ret = true; } else { diff --git a/components/display/core/gds_private.h b/components/display/core/gds_private.h index 60893be5..76fcb167 100644 --- a/components/display/core/gds_private.h +++ b/components/display/core/gds_private.h @@ -83,7 +83,7 @@ struct GDS_Device { uint16_t Height; uint8_t Depth; - uint8_t* Framebuffer, *Shadowbuffer; + uint8_t* Framebuffer; uint16_t FramebufferSize; bool Dirty; diff --git a/components/squeezelite/display.c b/components/squeezelite/display.c index f7c9b385..91030330 100644 --- a/components/squeezelite/display.c +++ b/components/squeezelite/display.c @@ -115,7 +115,7 @@ static struct scroller_s { u16_t mode; s16_t by; // scroller management & sharing between grfg and scrolling task - bool active, first; + bool active, first, overflow; int scrolled; struct { u8_t *frame; @@ -234,7 +234,7 @@ bool sb_display_init(void) { displayer.task = xTaskCreateStatic( (TaskFunction_t) displayer_task, "displayer_thread", SCROLL_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, &xTaskBuffer); // size scroller (width + current screen) - scroller.scroll.max = (displayer.width * displayer.height / 8) * (10 + 1); + scroller.scroll.max = (displayer.width * displayer.height / 8) * (15 + 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); @@ -559,6 +559,7 @@ static void grfs_handler(u8_t *data, int len) { scroller.mode = htons(pkt->mode); scroller.scroll.width = htons(pkt->width); scroller.first = true; + scroller.overflow = false; // background excludes space taken by visu (if any) scroller.back.width = displayer.width - ((visu.mode && visu.row < SB_HEIGHT) ? visu.width : 0); @@ -576,13 +577,14 @@ static void grfs_handler(u8_t *data, int len) { } // copy scroll frame data (no semaphore needed) - if (scroller.scroll.size + size < scroller.scroll.max) { + if (scroller.scroll.size + size < scroller.scroll.max && !scroller.overflow) { memcpy(scroller.scroll.frame + offset, data + sizeof(struct grfs_packet), size); scroller.scroll.size = offset + size; - LOG_INFO("scroller current size %u", scroller.scroll.size); + LOG_INFO("scroller current size %u (w:%u)", scroller.scroll.size, scroller.scroll.width); } else { - 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); + LOG_INFO("scroller too large %u/%u (w:%u)", scroller.scroll.size + size, scroller.scroll.max, scroller.scroll.width); + scroller.scroll.width = scroller.scroll.size / (displayer.height / 8) - scroller.back.width; + scroller.overflow = true; } } @@ -860,7 +862,7 @@ static void displayer_task(void *args) { // by default go for the long sleep, will change below if required scroller.wake = LONG_WAKE; - // do we have more to scroll (scroll.width is the last column from which we havea full zone) + // do we have more to scroll (scroll.width is the last column from which we have a full zone) if (scroller.by > 0 ? (scroller.scrolled <= scroller.scroll.width) : (scroller.scrolled >= 0)) { 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]; From 04f74b310dd756e98f9d5be2598cdc7cb455c03f Mon Sep 17 00:00:00 2001 From: philippe44 <philippe_44@outlook.com> Date: Thu, 27 Feb 2020 23:07:27 -0800 Subject: [PATCH 17/18] typos - release --- components/display/SSD1306.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/display/SSD1306.c b/components/display/SSD1306.c index 7cea4505..eada1e12 100644 --- a/components/display/SSD1306.c +++ b/components/display/SSD1306.c @@ -25,7 +25,7 @@ struct SSD1306_Private { uint8_t *Shadowbuffer; }; -// Functions are not deckared to minimize # of lines +// Functions are not declared to minimize # of lines static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) { Device->WriteCommand( Device, 0x21 ); From c9998281970fb5cc399eb7aa7ccbcc9a79536171 Mon Sep 17 00:00:00 2001 From: Sebastien <sle118@hotmail.com> Date: Fri, 28 Feb 2020 12:29:34 -0500 Subject: [PATCH 18/18] OTA feedback on local display - release --- components/squeezelite-ota/squeezelite-ota.c | 380 ++++++++++++------- main/console.c | 1 + 2 files changed, 235 insertions(+), 146 deletions(-) diff --git a/components/squeezelite-ota/squeezelite-ota.c b/components/squeezelite-ota/squeezelite-ota.c index 12b62a68..1ca23773 100644 --- a/components/squeezelite-ota/squeezelite-ota.c +++ b/components/squeezelite-ota/squeezelite-ota.c @@ -32,6 +32,9 @@ #include "trace.h" #include "esp_ota_ops.h" #include "display.h" +#include "gds.h" +#include "gds_text.h" +#include "gds_draw.h" extern const char * get_certificate(); @@ -41,7 +44,6 @@ extern const char * get_certificate(); #define OTA_CORE 1 #endif -static const size_t bin_ota_chunk = 4096*2; static const char *TAG = "squeezelite-ota"; esp_http_client_handle_t ota_http_client = NULL; #define IMAGE_HEADER_SIZE sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t) + 1 @@ -58,20 +60,27 @@ typedef enum { OTA_TYPE_BUFFER, OTA_TYPE_INVALID } ota_type_t; + static struct { uint32_t actual_image_len; uint32_t total_image_len; uint32_t remain_image_len; ota_type_t ota_type; char * ota_write_data; + char * bin_data; bool bOTAStarted; size_t buffer_size; uint8_t lastpct; uint8_t newpct; struct timeval OTA_start; bool bOTAThreadStarted; - + const esp_partition_t *configured; + const esp_partition_t *running; + const esp_partition_t * update_partition; + const esp_partition_t* last_invalid_app ; + const esp_partition_t * ota_partition; } ota_status; + struct timeval tv; static esp_http_client_config_t ota_config; @@ -86,12 +95,66 @@ uint8_t ota_get_pct_complete(){ return ota_status.total_image_len==0?0: (uint8_t)((float)ota_status.actual_image_len/(float)ota_status.total_image_len*100.0f); } -static bool (*display_bus_chain)(void *from, enum display_bus_cmd_e cmd); -static bool display_dummy_handler(void *from, enum display_bus_cmd_e cmd) { +typedef struct { + int x1,y1,x2,y2,width,height; +} rect_t; +typedef struct _progress { + int border_thickness; + int sides_margin; + int vertical_margin; + int bar_tot_height; + int bar_fill_height; + rect_t border; + rect_t filler; +} progress_t; - // Nothing implemented at this point - if (display_bus_chain) return (*display_bus_chain)(from, cmd); - else return true; +static progress_t * loc_displayer_get_progress_dft(){ + int start_coord_offset=0; + static progress_t def={ + .border_thickness = 2, + .sides_margin = 2, + .bar_tot_height = 7, + }; + def.bar_fill_height= def.bar_tot_height-(def.border_thickness*2); + def.border.x1=start_coord_offset+def.sides_margin; + def.border.x2=GDS_GetWidth(display)-def.sides_margin; + // progress bar will be drawn at the bottom of the display + def.border.y2= GDS_GetHeight(display)-def.border_thickness; + def.border.y1= def.border.y2-def.bar_tot_height; + def.border.width=def.border.x2-def.border.x1; + def.border.height=def.border.y2-def.border.y1; + def.filler.x1= def.border.x1+def.border_thickness; + def.filler.x2= def.border.x2-def.border_thickness; + def.filler.y1= def.border.y1+def.border_thickness; + def.filler.y2= def.border.y2-def.border_thickness; + def.filler.width=def.filler.x2-def.filler.x1; + def.filler.height=def.filler.y2-def.filler.y1; + assert(def.filler.width>0); + assert(def.filler.height>0); + assert(def.border.width>0); + assert(def.border.height>0); + assert(def.border.width>def.filler.width); + assert(def.border.height>def.filler.height); + return &def; + +} +static void loc_displayer_progressbar(uint8_t pct){ + static progress_t * progress_coordinates; + if(!progress_coordinates) progress_coordinates = loc_displayer_get_progress_dft(); + int filler_x=progress_coordinates->filler.x1+(int)((float)progress_coordinates->filler.width*(float)pct/(float)100); + + ESP_LOGI(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2); + GDS_DrawBox(display,progress_coordinates->border.x1,progress_coordinates->border.y1,progress_coordinates->border.x2,progress_coordinates->border.y2,GDS_COLOR_WHITE,false); + ESP_LOGI(TAG,"Drawing %d,%d,%d,%d",progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2); + if(filler_x > progress_coordinates->filler.x1){ + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,filler_x,progress_coordinates->filler.y2,GDS_COLOR_WHITE,true); + } + else { + // Clear the inner box + GDS_DrawBox(display,progress_coordinates->filler.x1,progress_coordinates->filler.y1,progress_coordinates->filler.x2,progress_coordinates->filler.y2,GDS_COLOR_BLACK,true); + } + ESP_LOGI(TAG,"Updating Display"); + GDS_Update(display); } void sendMessaging(messaging_types type,const char * fmt, ...){ va_list args; @@ -117,8 +180,10 @@ void sendMessaging(messaging_types type,const char * fmt, ...){ ESP_LOGW(TAG, "Sending empty string message"); } va_end(args); + if(type!=MESSAGING_INFO){ + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, msg_str); + } - displayer_scroll(msg_str, 33, 250); cJSON_AddStringToObject(msg,"ota_dsc",str_or_unknown(msg_str)); free(msg_str); cJSON_AddNumberToObject(msg,"ota_pct", ota_get_pct_complete() ); @@ -219,6 +284,7 @@ esp_err_t _http_event_handler(esp_http_client_event_t *evt) esp_err_t init_config(ota_thread_parms_t * p_ota_thread_parms){ memset(&ota_config, 0x00, sizeof(ota_config)); sendMessaging(MESSAGING_INFO,"Initializing..."); + loc_displayer_progressbar(0); ota_status.ota_type= OTA_TYPE_INVALID; if(p_ota_thread_parms->url !=NULL && strlen(p_ota_thread_parms->url)>0 ){ ota_status.ota_type= OTA_TYPE_HTTP; @@ -231,30 +297,25 @@ esp_err_t init_config(ota_thread_parms_t * p_ota_thread_parms){ ESP_LOGE(TAG,"HTTP OTA called without a url or a binary buffer"); return ESP_ERR_INVALID_ARG; } + ota_status.buffer_size = BUFFSIZE; + ota_status.ota_write_data = heap_caps_malloc(ota_status.buffer_size+1 , (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT)); + if(ota_status.ota_write_data== NULL){ + ESP_LOGE(TAG,"Error allocating the ota buffer"); + return ESP_ERR_NO_MEM; + } switch (ota_status.ota_type) { case OTA_TYPE_HTTP: ota_config.cert_pem =get_certificate(); ota_config.event_handler = _http_event_handler; - ota_config.buffer_size = ota_status.buffer_size; ota_config.disable_auto_redirect=true; ota_config.skip_cert_common_name_check = false; ota_config.url = strdup(p_ota_thread_parms->url); ota_config.max_redirection_count = 3; - - - ota_status.ota_write_data = heap_caps_malloc(ota_status.buffer_size+1 , (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT)); - - if(ota_status.ota_write_data== NULL){ - ESP_LOGE(TAG,"Error allocating the ota buffer"); - return ESP_ERR_NO_MEM; - } - break; case OTA_TYPE_BUFFER: - ota_status.ota_write_data = p_ota_thread_parms->bin; + ota_status.bin_data = p_ota_thread_parms->bin; ota_status.total_image_len = p_ota_thread_parms->length; - ota_status.buffer_size = bin_ota_chunk; break; default: return ESP_FAIL; @@ -287,7 +348,7 @@ esp_partition_t * _get_ota_partition(esp_partition_subtype_t subtype){ -esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition) +esp_err_t _erase_last_boot_app_partition(const esp_partition_t *ota_partition) { uint16_t num_passes=0; uint16_t remain_size=0; @@ -320,16 +381,19 @@ esp_err_t _erase_last_boot_app_partition(esp_partition_t *ota_partition) err=esp_partition_erase_range(ota_partition, i*single_pass_size, single_pass_size); if(err!=ESP_OK) return err; if(i%2) { + loc_displayer_progressbar((int)(((float)i/(float)num_passes)*100.0f)); sendMessaging(MESSAGING_INFO,"Erasing flash (%u/%u)",i,num_passes); } vTaskDelay(100/ portTICK_PERIOD_MS); // wait here for a short amount of time. This will help with reducing WDT errors } if(remain_size>0){ err=esp_partition_erase_range(ota_partition, ota_partition->size-remain_size, remain_size); + if(err!=ESP_OK) return err; } sendMessaging(MESSAGING_INFO,"Erasing flash complete."); - + loc_displayer_progressbar(100); + vTaskDelay(200/ portTICK_PERIOD_MS); return ESP_OK; } @@ -418,42 +482,135 @@ static esp_err_t _http_connect(esp_http_client_handle_t http_client) } void ota_task_cleanup(const char * message, ...){ ota_status.bOTAThreadStarted=false; + loc_displayer_progressbar(0); if(message!=NULL){ va_list args; va_start(args, message); sendMessaging(MESSAGING_ERROR,message, args); va_end(args); } - if(ota_status.ota_type == OTA_TYPE_HTTP){ - FREE_RESET(ota_status.ota_write_data); - } + FREE_RESET(ota_status.ota_write_data); + FREE_RESET(ota_status.bin_data); if(ota_http_client!=NULL) { esp_http_client_cleanup(ota_http_client); ota_http_client=NULL; } ota_status.bOTAStarted = false; - displayer_control(DISPLAYER_SHUTDOWN); task_fatal_error(); } +esp_err_t ota_buffer_all(){ + int data_read=0; + esp_err_t err=ESP_OK; + if (ota_status.ota_type == OTA_TYPE_HTTP){ + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Downloading file"); + ota_http_client = esp_http_client_init(&ota_config); + if (ota_http_client == NULL) { + sendMessaging(MESSAGING_ERROR,"Error: Failed to initialize HTTP connection."); + return ESP_FAIL; + } + _printMemStats(); + // Open the http connection and follow any redirection + err = _http_connect(ota_http_client); + if (err != ESP_OK) { + sendMessaging(MESSAGING_ERROR,"Error: HTTP Start read failed. (%s)",esp_err_to_name(err)); + return err; + } + if(ota_status.total_image_len<=0){ + sendMessaging(MESSAGING_ERROR,"Error: Invalid image length"); + return ESP_FAIL; + } + ota_status.bin_data= malloc(ota_status.total_image_len); + if(ota_status.bin_data==NULL){ + sendMessaging(MESSAGING_ERROR,"Error: buffer alloc error"); + return ESP_FAIL; + } + data_read = esp_http_client_read(ota_http_client, ota_status.bin_data, ota_status.total_image_len); + if(data_read != ota_status.total_image_len){ + sendMessaging(MESSAGING_ERROR,"Error: Binary incomplete"); + return ESP_FAIL; + } + } + else { + gettimeofday(&ota_status.OTA_start, NULL); + } + ota_status.remain_image_len=ota_status.total_image_len; + return err; +} +int ota_buffer_read(){ + int data_read=0; + if(ota_status.remain_image_len >ota_status.buffer_size){ + data_read = ota_status.buffer_size; + } else { + data_read = ota_status.remain_image_len; + } + memcpy(ota_status.ota_write_data, &ota_status.bin_data[ota_status.actual_image_len], data_read); + + ota_status.actual_image_len += data_read; + ota_status.remain_image_len -= data_read; + return data_read; +} +esp_err_t ota_header_check(){ + esp_app_desc_t new_app_info; + esp_app_desc_t running_app_info; + + ota_status.configured = esp_ota_get_boot_partition(); + ota_status.running = esp_ota_get_running_partition(); + ota_status.update_partition = esp_ota_get_next_update_partition(NULL); + ota_status.last_invalid_app= esp_ota_get_last_invalid_partition(); + ota_status.ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0); + + ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", ota_status.running->label, ota_status.running->type, ota_status.running->subtype, ota_status.running->address); + if (ota_status.total_image_len > ota_status.ota_partition->size){ + ota_task_cleanup("Error: Image size too large to fit in partition."); + return ESP_FAIL; + } + if(ota_status.ota_partition == NULL){ + ESP_LOGE(TAG,"Unable to locate OTA application partition. "); + ota_task_cleanup("Error: OTA partition not found"); + return ESP_FAIL; + } + if (ota_status.configured != ota_status.running) { + ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", ota_status.configured->address, ota_status.running->address); + ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); + } + ESP_LOGI(TAG, "Next ota update partition is: [%s] subtype %d at offset 0x%x", + ota_status.update_partition->label, ota_status.update_partition->subtype, ota_status.update_partition->address); + + if (ota_status.total_image_len >= IMAGE_HEADER_SIZE) { + // check current version with downloading + memcpy(&new_app_info, &ota_status.bin_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); + ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); + if (esp_ota_get_partition_description(ota_status.running, &running_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version); + } + + esp_app_desc_t invalid_app_info; + if (esp_ota_get_partition_description(ota_status.last_invalid_app, &invalid_app_info) == ESP_OK) { + ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); + } + + if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { + ESP_LOGW(TAG, "Current running version is the same as a new."); + } + return ESP_OK; + } + else{ + ota_task_cleanup("Error: Binary file too small"); + } + return ESP_FAIL; +} void ota_task(void *pvParameter) { esp_err_t err = ESP_OK; - displayer_control(DISPLAYER_ACTIVATE, "Firmware update"); - displayer_scroll("Initializing...", 33, 250); + int data_read = 0; + GDS_TextSetFont(display,2,&Font_droid_sans_fallback_15x17,-2); + GDS_ClearExt(display, true); + GDS_TextLine(display, 1, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Firmware update"); + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Initializing"); + loc_displayer_progressbar(0); ESP_LOGD(TAG, "HTTP ota Thread started"); - const esp_partition_t *configured = esp_ota_get_boot_partition(); - const esp_partition_t *running = esp_ota_get_running_partition(); - const esp_partition_t * update_partition = esp_ota_get_next_update_partition(NULL); - ESP_LOGI(TAG, "esp_ota_get_next_update_partition returned : partition [%s] subtype %d at offset 0x%x", - update_partition->label, update_partition->subtype, update_partition->address); - - if (configured != running) { - ESP_LOGW(TAG, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x", configured->address, running->address); - ESP_LOGW(TAG, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)"); - } - ESP_LOGI(TAG, "Running partition [%s] type %d subtype %d (offset 0x%08x)", running->label, running->type, running->subtype, running->address); _printMemStats(); @@ -464,134 +621,66 @@ void ota_task(void *pvParameter) return; } + _printMemStats(); + ota_status.bOTAStarted = true; + sendMessaging(MESSAGING_INFO,"Starting OTA..."); + err=ota_buffer_all(); + if(err!=ESP_OK){ + ota_task_cleanup(NULL); + return; + } + + + if(ota_header_check()!=ESP_OK){ + ota_task_cleanup(NULL); + return; + } + /* Locate and erase ota application partition */ ESP_LOGW(TAG,"**************** Expecting WATCHDOG errors below during flash erase. This is OK and not to worry about **************** "); - sendMessaging(MESSAGING_INFO,"Erasing OTA partition"); - esp_partition_t *ota_partition = _get_ota_partition(ESP_PARTITION_SUBTYPE_APP_OTA_0); - if(ota_partition == NULL){ - ESP_LOGE(TAG,"Unable to locate OTA application partition. "); - ota_task_cleanup("Error: OTA application partition not found. (%s)",esp_err_to_name(err)); - return; - } - if(ota_status.ota_type == OTA_TYPE_BUFFER){ - if(ota_status.total_image_len > ota_partition->size){ - ota_task_cleanup("Error: Image size too large to fit in partition."); - return; - } - } + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Formatting partition"); + sendMessaging(MESSAGING_INFO,"Formatting OTA partition"); _printMemStats(); - err=_erase_last_boot_app_partition(ota_partition); + err=_erase_last_boot_app_partition(ota_status.ota_partition); if(err!=ESP_OK){ ota_task_cleanup("Error: Unable to erase last APP partition. (%s)",esp_err_to_name(err)); return; } - - _printMemStats(); - ota_status.bOTAStarted = true; - sendMessaging(MESSAGING_INFO,"Starting OTA..."); - if (ota_status.ota_type == OTA_TYPE_HTTP){ - ota_http_client = esp_http_client_init(&ota_config); - if (ota_http_client == NULL) { - ota_task_cleanup("Error: Failed to initialize HTTP connection."); - return; - } - _printMemStats(); - // Open the http connection and follow any redirection - err = _http_connect(ota_http_client); - if (err != ESP_OK) { - ota_task_cleanup("Error: HTTP Start read failed. (%s)",esp_err_to_name(err)); - return; - } - } - else { - gettimeofday(&ota_status.OTA_start, NULL); - } + loc_displayer_progressbar(0); _printMemStats(); + + // Call OTA Begin with a small partition size - this minimizes the time spent in erasing partition, + // which was already done above esp_ota_handle_t update_handle = 0 ; - /*deal with all receive packet*/ - bool image_header_was_checked = false; - int data_read = 0; - if (ota_status.ota_type == OTA_TYPE_HTTP && ota_status.total_image_len > ota_partition->size){ - ota_task_cleanup("Error: Image size too large to fit in partition."); - return; + err = esp_ota_begin(ota_status.ota_partition, 512, &update_handle); + if (err != ESP_OK) { + ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err)); + return; } + ESP_LOGD(TAG, "esp_ota_begin succeeded"); + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Writing image..."); + while (ota_status.remain_image_len>0) { - while (1) { - ota_status.remain_image_len =ota_status.total_image_len -ota_status.actual_image_len; - - if (ota_status.ota_type == OTA_TYPE_HTTP){ - - data_read = esp_http_client_read(ota_http_client, ota_status.ota_write_data, ota_status.buffer_size); - } - else { - if(ota_status.remain_image_len >ota_status.buffer_size){ - data_read = ota_status.buffer_size; - } else { - data_read = ota_status.remain_image_len; - } - } - if (data_read < 0) { + data_read = ota_buffer_read(); + if (data_read <= 0) { ota_task_cleanup("Error: Data read error"); return; } else if (data_read > 0) { - if (image_header_was_checked == false) { - esp_app_desc_t new_app_info; - if (data_read >= IMAGE_HEADER_SIZE) { - // check current version with downloading - memcpy(&new_app_info, &ota_status.ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t)); - ESP_LOGI(TAG, "New firmware version: %s", new_app_info.version); - - esp_app_desc_t running_app_info; - if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) { - ESP_LOGI(TAG, "Running recovery version: %s", running_app_info.version); - } - - const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition(); - esp_app_desc_t invalid_app_info; - if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) { - ESP_LOGI(TAG, "Last invalid firmware version: %s", invalid_app_info.version); - } - - if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) { - ESP_LOGW(TAG, "Current running version is the same as a new."); - } - - image_header_was_checked = true; - // Call OTA Begin with a small partition size - this drives the erase operation which was already done; - err = esp_ota_begin(ota_partition, 512, &update_handle); - if (err != ESP_OK) { - ota_task_cleanup("esp_ota_begin failed (%s)", esp_err_to_name(err)); - return; - } - ESP_LOGD(TAG, "esp_ota_begin succeeded"); - } - else { - ota_task_cleanup("Error: Increase ota http buffer."); - return; - } - } - err = esp_ota_write( update_handle, (const void *)ota_status.ota_write_data, data_read); if (err != ESP_OK) { ota_task_cleanup("Error: OTA Partition write failure. (%s)",esp_err_to_name(err)); return; } - ota_status.actual_image_len += data_read; - if(ota_status.ota_type == OTA_TYPE_BUFFER){ - // position the ota buffer in the next buffer chunk - ota_status.ota_write_data+= data_read; - } ESP_LOGD(TAG, "Written image length %d", ota_status.actual_image_len); if(ota_get_pct_complete()%5 == 0) ota_status.newpct = ota_get_pct_complete(); if(ota_status.lastpct!=ota_status.newpct ) { - + loc_displayer_progressbar(ota_status.newpct); gettimeofday(&tv, NULL); uint32_t elapsed_ms= (tv.tv_sec-ota_status.OTA_start.tv_sec )*1000+(tv.tv_usec-ota_status.OTA_start.tv_usec)/1000; ESP_LOGI(TAG,"OTA progress : %d/%d (%d pct), %d KB/s", ota_status.actual_image_len, ota_status.total_image_len, ota_status.newpct, elapsed_ms>0?ota_status.actual_image_len*1000/elapsed_ms/1024:0); - - sendMessaging(MESSAGING_INFO,ota_status.ota_type == OTA_TYPE_HTTP?"Downloading & writing update.":"Writing binary file."); + sendMessaging(MESSAGING_INFO,"Writing binary file."); ota_status.lastpct=ota_status.newpct; } taskYIELD(); @@ -608,19 +697,20 @@ void ota_task(void *pvParameter) return; } _printMemStats(); - + loc_displayer_progressbar(100); err = esp_ota_end(update_handle); if (err != ESP_OK) { ota_task_cleanup("Error: %s",esp_err_to_name(err)); return; } _printMemStats(); - err = esp_ota_set_boot_partition(ota_partition); + err = esp_ota_set_boot_partition(ota_status.ota_partition); if (err == ESP_OK) { ESP_LOGI(TAG,"OTA Process completed successfully!"); sendMessaging(MESSAGING_INFO,"Success!"); - - vTaskDelay(1000/ portTICK_PERIOD_MS); // wait here to give the UI a chance to refresh + GDS_TextLine(display, 2, GDS_TEXT_LEFT, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "Success!"); + vTaskDelay(1500/ portTICK_PERIOD_MS); // wait here to give the UI a chance to refresh + GDS_Clear(display,GDS_COLOR_BLACK); esp_restart(); } else { ota_task_cleanup("Error: Unable to update boot partition [%s]",esp_err_to_name(err)); @@ -632,8 +722,6 @@ void ota_task(void *pvParameter) esp_err_t process_recovery_ota(const char * bin_url, char * bin_buffer, uint32_t length){ int ret = 0; - display_bus_chain = display_bus; - display_bus = display_dummy_handler; uint16_t stack_size, task_priority; if(ota_status.bOTAThreadStarted){ ESP_LOGE(TAG,"OTA Already started. "); diff --git a/main/console.c b/main/console.c index 6accf459..8f037fe1 100644 --- a/main/console.c +++ b/main/console.c @@ -209,6 +209,7 @@ void console_start() { "\n" "\n"); #if RECOVERY_APPLICATION + GDS_ClearExt(display, true); GDS_SetFont(display, &Font_droid_sans_fallback_15x17 ); GDS_TextPos(display, GDS_FONT_MEDIUM, GDS_TEXT_CENTERED, GDS_TEXT_CLEAR | GDS_TEXT_UPDATE, "RECOVERY");