From f008229acd006de623caaefdf25b10fa6c5ff4be Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 24 Feb 2020 21:54:51 -0800 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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;