From cf2e0727d7d5085a7bb7ff20a7c24da57756b10c Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Mon, 2 Dec 2019 17:33:56 +0000 Subject: [PATCH] Bangle.js: New 120x120 and 80x80 high speed buffered modes (could be faster) --- ChangeLog | 1 + libs/banglejs/jswrap_bangle.c | 34 ++++- libs/graphics/jswrap_graphics.h | 7 + libs/graphics/lcd_st7789_8bit.c | 232 +++++++++++++++++++++++++++++--- libs/graphics/lcd_st7789_8bit.h | 4 +- 5 files changed, 257 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index b32b36621..db48bafb2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -48,6 +48,7 @@ Added ability to compile Espruino to JavaScript with Emscripten Allow g.setColor/setBgColor to take hex Strings of the form `'#00ff00'` Bangle.js: Added heart rate autocorrelation, setHRMPower and 'HRM' event + Bangle.js: New 120x120 and 80x80 high speed buffered modes 2v04 : Allow \1..\9 escape codes in RegExp ESP8266: reading storage is not working for boot from user2 (fix #1507) diff --git a/libs/banglejs/jswrap_bangle.c b/libs/banglejs/jswrap_bangle.c index 0b92f14d1..8094d79a6 100644 --- a/libs/banglejs/jswrap_bangle.c +++ b/libs/banglejs/jswrap_bangle.c @@ -381,7 +381,14 @@ void lcd_flip(JsVar *parent) { jswrap_banglejs_setLCDPower(1); } flipTimer = 0; - lcdST7789_flip(); + + JsVar *graphics = jsvObjectGetChild(execInfo.hiddenRoot, JS_GRAPHICS_VAR, 0); + if (!graphics) return; + JsGraphics gfx; + if (!graphicsGetFromVar(&gfx, graphics)) return; + lcdST7789_flip(&gfx); + graphicsSetVar(&gfx); + jsvUnLock(graphics); } char clipi8(int x) { @@ -740,6 +747,10 @@ void jswrap_banglejs_setLCDMode(JsVar *mode) { lcdMode = LCDST7789_MODE_UNBUFFERED; else if (jsvIsStringEqual(mode,"doublebuffered")) lcdMode = LCDST7789_MODE_DOUBLEBUFFERED; + else if (jsvIsStringEqual(mode,"120x120")) + lcdMode = LCDST7789_MODE_BUFFER_120x120; + else if (jsvIsStringEqual(mode,"80x80")) + lcdMode = LCDST7789_MODE_BUFFER_80x80; else jsExceptionHere(JSET_ERROR,"Unknown LCD Mode %j",mode); @@ -747,7 +758,26 @@ void jswrap_banglejs_setLCDMode(JsVar *mode) { if (!graphics) return; JsGraphics gfx; if (!graphicsGetFromVar(&gfx, graphics)) return; - gfx.data.height = (lcdMode==LCDST7789_MODE_DOUBLEBUFFERED) ? 160 : LCD_HEIGHT; + // remove the buffer if it was defined + jsvObjectSetOrRemoveChild(gfx.graphicsVar, "buffer", 0); + switch (lcdMode) { + case LCDST7789_MODE_UNBUFFERED: + gfx.data.height = LCD_HEIGHT; + break; + case LCDST7789_MODE_DOUBLEBUFFERED: + gfx.data.height = 160; + break; + case LCDST7789_MODE_BUFFER_120x120: + gfx.data.width = 120; + gfx.data.height = 120; + jsvObjectSetChildAndUnLock(gfx.graphicsVar, "buffer", jswrap_arraybuffer_constructor(120*120)); + break; + case LCDST7789_MODE_BUFFER_80x80: + gfx.data.width = 80; + gfx.data.height = 80; + jsvObjectSetChildAndUnLock(gfx.graphicsVar, "buffer", jswrap_arraybuffer_constructor(80*80)); + break; + } graphicsSetVar(&gfx); jsvUnLock(graphics); lcdST7789_setMode( lcdMode ); diff --git a/libs/graphics/jswrap_graphics.h b/libs/graphics/jswrap_graphics.h index 08550b0a9..8e28383dc 100644 --- a/libs/graphics/jswrap_graphics.h +++ b/libs/graphics/jswrap_graphics.h @@ -17,6 +17,13 @@ #include "jsvar.h" #include "graphics.h" +#ifdef GRAPHICS_PALETTED_IMAGES +// 16 color MAC OS palette +extern const uint16_t PALETTE_4BIT[16]; +// 256 color 16 bit Web-safe palette +extern const uint16_t PALETTE_8BIT[256]; +#endif + bool jswrap_graphics_idle(); void jswrap_graphics_init(); diff --git a/libs/graphics/lcd_st7789_8bit.c b/libs/graphics/lcd_st7789_8bit.c index 7fa1b181c..c0a68b26b 100644 --- a/libs/graphics/lcd_st7789_8bit.c +++ b/libs/graphics/lcd_st7789_8bit.c @@ -14,9 +14,12 @@ #include "platform_config.h" #include "jsutils.h" -#include "jshardware.h" #include "lcd_st7789_8bit.h" +#include "jswrap_graphics.h" +#ifndef EMSCRIPTEN +#include "jshardware.h" #include "nrf_gpio.h" +#endif #define CMDINDEX_CMD 0 #define CMDINDEX_DELAY 1 @@ -51,7 +54,14 @@ LCDST7789Mode lcdMode; int lcdNextX, lcdNextY; int lcdScrollY; +#ifdef EMSCRIPTEN +#define EMSCRIPTEN_GFX_STRIDE (240*2) +char EMSCRIPTEN_GFX_BUFFER[240*240*2]; +char EMSCRIPTEN_GFX_BUFFER_OFFSCREEN[240*160*2]; +bool EMSCRIPTEN_GFX_CHANGED; +#endif +#ifndef EMSCRIPTEN /* We have to be careful about this since we can write faster than the LCD is happy about @@ -68,6 +78,7 @@ write faster than the LCD is happy about #define LCD_DC_COMMAND() jshPinSetValue(LCD_PIN_DC,0); #define LCD_DC_DATA() jshPinSetValue(LCD_PIN_DC,1); */ + #define LCD_CS_CLR() NRF_P0->OUTCLR = 1<OUTSET = 1<OUTCLR = 1<>8; - buf[1] = offs; - lcdST7789_cmd(0x37,2,buf); + switch (lcdMode) { + case LCDST7789_MODE_UNBUFFERED: + // unbuffered - flip has no effect + break; + case LCDST7789_MODE_DOUBLEBUFFERED: { + // buffered - flip using LCD itself + unsigned short offs; + if (lcdScrollY==0) { + lcdScrollY = 160; + offs = 280; + } else { + lcdScrollY = 0; + offs = 120; + } + buf[0] = offs>>8; + buf[1] = offs; + lcdST7789_cmd(0x37,2,buf); + } break; + case LCDST7789_MODE_BUFFER_120x120: { + // offscreen buffer - BLIT + JsVar *buffer = jsvObjectGetChild(gfx->graphicsVar, "buffer", 0); + size_t len = 0; + unsigned char *dataPtr = (unsigned char*)jsvGetDataPointer(buffer, &len); + jsvUnLock(buffer); + if (dataPtr && len>=(120*120)) { + // reset scroll to 0 + buf[0] = 0; + buf[1] = 0; + lcdST7789_cmd(0x37,2,buf); + // blit + lcdST7789_blitStart(0,0,239,239); + for (int y=0;y<240;y++) { + for (int x=0;x<120;x++) { + unsigned int c = PALETTE_8BIT[*(dataPtr++)]; + lcdST7789_blitPixel(c); + lcdST7789_blitPixel(c); + } + // display the same row twice + if (!(y&1)) dataPtr -= 120; + } + lcdST7789_blitEnd(); + } + } break; + case LCDST7789_MODE_BUFFER_80x80: { + // offscreen buffer - BLIT + JsVar *buffer = jsvObjectGetChild(gfx->graphicsVar, "buffer", 0); + size_t len = 0; + unsigned char *dataPtr = (unsigned char*)jsvGetDataPointer(buffer, &len); + jsvUnLock(buffer); + if (dataPtr && len>=(80*80)) { + // reset scroll to 0 + buf[0] = 0; + buf[1] = 0; + lcdST7789_cmd(0x37,2,buf); + // blit + lcdST7789_blitStart(0,0,239,239); + for (int y=0;y<80;y++) { + for (int n=0;n<3;n++) { + for (int x=0;x<80;x++) { + unsigned int c = PALETTE_8BIT[*(dataPtr++)]; + lcdST7789_blitPixel(c); + lcdST7789_blitPixel(c); + lcdST7789_blitPixel(c); + } + if (n<2) dataPtr -= 80; + } + } + lcdST7789_blitEnd(); + } + } break; + } +#else + switch (lcdMode) { + case LCDST7789_MODE_UNBUFFERED: + // unbuffered - flip has no effect + break; + case LCDST7789_MODE_DOUBLEBUFFERED: { + memcpy(&EMSCRIPTEN_GFX_BUFFER[EMSCRIPTEN_GFX_BUFFER_STRIDE*40], + EMSCRIPTEN_GFX_BUFFER_OFFSCREEN, + sizeof(EMSCRIPTEN_GFX_BUFFER_OFFSCREEN)); + } break; + case LCDST7789_MODE_BUFFER_120x120: { + uint16_t *o = (uint16_t*)EMSCRIPTEN_GFX_BUFFER; + for (int y=0;y<240;y++) { + for (int x=0;x<120;x++) { + unsigned int c = PALETTE_8BIT[*(dataPtr++)]; + *(o++) = c; + *(o++) = c; + } + // display the same row twice + if (!(y&1)) dataPtr -= 120; + } + } break; + case LCDST7789_MODE_BUFFER_80x80: { + uint16_t *o = (uint16_t*)EMSCRIPTEN_GFX_BUFFER; + for (int y=0;y<80;y++) { + for (int n=0;n<3;n++) { + for (int x=0;x<80;x++) { + unsigned int c = PALETTE_8BIT[*(dataPtr++)]; + *(o++) = c; + *(o++) = c; + *(o++) = c; + } + if (n<2) dataPtr -= 80; + } + } + } break; + } + EMSCRIPTEN_GFX_CHANGED = true; +#endif } /// Starts a blit operation - call this, then blitPixel (a lot) then blitEnd. No bounds checking void lcdST7789_blitStart(int x, int y, int w, int h) { +#ifndef EMSCRIPTEN lcdNextY=-1; lcdNextX=-1; int x1 = x; @@ -179,8 +295,10 @@ void lcdST7789_blitStart(int x, int y, int w, int h) { LCD_DC_COMMAND(); // command LCD_WR8(0x2C); LCD_DC_DATA(); // data +#endif } -void lcdST7789_blitPixel(unsigned int col) { +inline void lcdST7789_blitPixel(unsigned int col) { +#ifndef EMSCRIPTEN /* FIXME: Handle case where scrolling means * we wrap around the memory area - see what * lcdST7789_fillRect does */ @@ -192,13 +310,17 @@ void lcdST7789_blitPixel(unsigned int col) { asm("nop");asm("nop"); LCD_SCK_CLR_FAST(); LCD_SCK_SET_FAST(); +#endif } void lcdST7789_blitEnd() { +#ifndef EMSCRIPTEN LCD_CS_SET(); +#endif } void lcdST7789_setPixel(JsGraphics *gfx, int x, int y, unsigned int col) { +#ifndef EMSCRIPTEN LCD_CS_CLR(); if ((y!=lcdNextY) || (x!=lcdNextX)) { lcdNextY=y; @@ -233,8 +355,25 @@ void lcdST7789_setPixel(JsGraphics *gfx, int x, int y, unsigned int col) { LCD_SCK_CLR_FAST(); LCD_SCK_SET_FAST(); LCD_CS_SET(); +#else // EMSCRIPTEN + switch (lcdMode) { + case LCDST7789_MODE_UNBUFFERED: + ((uint16_t*)EMSCRIPTEN_GFX_BUFFER)[x+y*240] = col; + break; + case LCDST7789_MODE_DOUBLEBUFFERED: { + ((uint16_t*)EMSCRIPTEN_GFX_BUFFER_OFFSCREEN)[x+y*240] = col; + } break; + case LCDST7789_MODE_BUFFER_120x120: { + EMSCRIPTEN_GFX_BUFFER_OFFSCREEN[x+y*120] = col; + } break; + case LCDST7789_MODE_BUFFER_80x80: { + EMSCRIPTEN_GFX_BUFFER_OFFSCREEN[x+y*80] = col; + } break; + } +#endif } +#ifndef EMSCRIPTEN void lcdST7789_fillRect(JsGraphics *gfx, int x1, int y1, int x2, int y2, unsigned int col) { y1 += lcdScrollY; if (y1>=LCD_BUFFER_HEIGHT) y1-=LCD_BUFFER_HEIGHT; @@ -297,8 +436,10 @@ void lcdST7789_fillRect(JsGraphics *gfx, int x1, int y1, int x2, int y2, unsigne nexty = -1; } } +#endif void lcdST7789_scroll(JsGraphics *gfx, int xdir, int ydir) { +#ifndef EMSCRIPTEN if (lcdMode != LCDST7789_MODE_UNBUFFERED) { // No way this is going to work double buffered! return; @@ -314,12 +455,48 @@ void lcdST7789_scroll(JsGraphics *gfx, int xdir, int ydir) { buf[0] = lcdScrollY>>8; buf[1] = lcdScrollY; lcdST7789_cmd(0x37, 2, buf); +#else // EMSCRIPTEN + if (ydir>0) { + memcpy( + &EMSCRIPTEN_GFX_BUFFER[ydir], + &EMSCRIPTEN_GFX_BUFFER[0], + EMSCRIPTEN_GFX_STRIDE*(240-ydir) + } else if (ydir<0) { + memcpy( + &EMSCRIPTEN_GFX_BUFFER[0], + &EMSCRIPTEN_GFX_BUFFER[-ydir], + EMSCRIPTEN_GFX_STRIDE*(240+ydir) + } + EMSCRIPTEN_GFX_CHENGED=true; +#endif } + +// ==================================================================================== +void lcdST7789buf_setPixel(JsGraphics *gfx, int x, int y, unsigned int col) { + if (!gfx->backendData) return; + ((uint8_t*)gfx->backendData)[x + y*gfx->data.width] = col; +} + +unsigned int lcdST7789buf_getPixel(struct JsGraphics *gfx, int x, int y) { + if (!gfx->backendData) return 0; + return ((uint8_t*)gfx->backendData)[x + y*gfx->data.width]; +} + +void lcdST7789buf_scroll(JsGraphics *gfx, int xdir, int ydir) { + if (!gfx->backendData) return; + int pixels = xdir + ydir*gfx->data.width; + int l = gfx->data.width*gfx->data.height; + if (pixels>0) memcpy(&((uint8_t*)gfx->backendData)[0],&((uint8_t*)gfx->backendData)[pixels],l-pixels); + else if (pixels<0) memcpy(&((uint8_t*)gfx->backendData)[-pixels],&((uint8_t*)gfx->backendData)[0],l+pixels); +} +// ==================================================================================== + void lcdST7789_init(JsGraphics *gfx) { assert(gfx->data.bpp == 16); // LCD pins need initialising and LCD needs reset beforehand - done in jswrap_bangle.c +#ifndef EMSCRIPTEN // Send initialization commands to ST7789 const uint8_t *cmd = ST7789_INIT_CODE; while(cmd[CMDINDEX_DATALEN]!=255) { @@ -328,6 +505,7 @@ void lcdST7789_init(JsGraphics *gfx) { jshDelayMicroseconds(1000*cmd[CMDINDEX_DELAY]); cmd += 3 + cmd[CMDINDEX_DATALEN]; } +#endif lcdNextX = -1; lcdNextY = -1; @@ -336,8 +514,26 @@ void lcdST7789_init(JsGraphics *gfx) { } void lcdST7789_setCallbacks(JsGraphics *gfx) { - gfx->setPixel = lcdST7789_setPixel; - gfx->fillRect = lcdST7789_fillRect; - gfx->scroll = lcdST7789_scroll; + if (lcdMode==LCDST7789_MODE_BUFFER_120x120 || + lcdMode==LCDST7789_MODE_BUFFER_80x80) { + size_t expectedLen = (lcdMode==LCDST7789_MODE_BUFFER_120x120) ? (120*120) : (80*80); + JsVar *buf = jsvObjectGetChild(gfx->graphicsVar, "buffer", 0); + size_t len = 0; + char *dataPtr = jsvGetDataPointer(buf, &len); + jsvUnLock(buf); + if (dataPtr && len>=expectedLen) { + gfx->backendData = dataPtr; + gfx->setPixel = lcdST7789buf_setPixel; + gfx->getPixel = lcdST7789buf_getPixel; + // gfx->fillRect = lcdST7789buf_fillRect; // maybe later... + gfx->scroll = lcdST7789buf_scroll; + } + } else { + gfx->setPixel = lcdST7789_setPixel; +#ifndef EMSCRIPTEN + gfx->fillRect = lcdST7789_fillRect; +#endif + gfx->scroll = lcdST7789_scroll; + } } diff --git a/libs/graphics/lcd_st7789_8bit.h b/libs/graphics/lcd_st7789_8bit.h index e0ba3144f..0c94e4afc 100644 --- a/libs/graphics/lcd_st7789_8bit.h +++ b/libs/graphics/lcd_st7789_8bit.h @@ -19,6 +19,8 @@ void lcdST7789_setCallbacks(JsGraphics *gfx); typedef enum { LCDST7789_MODE_UNBUFFERED, // normal, 240x240 LCDST7789_MODE_DOUBLEBUFFERED, // 240x160 widescreen, double-buffered + LCDST7789_MODE_BUFFER_120x120, // 120x120 8 bit buffer + LCDST7789_MODE_BUFFER_80x80, // 80x80 8 bit buffer } LCDST7789Mode; /// Send a command direct to the screen @@ -26,7 +28,7 @@ void lcdST7789_cmd(int cmd, int dataLen, const uint8_t *data); /// Set double buffered or normal modes void lcdST7789_setMode(LCDST7789Mode mode); /// When in double-buffered mode, flip the screen -void lcdST7789_flip(); +void lcdST7789_flip(JsGraphics *gfx); /// Starts a blit operation - call this, then blitPixel (a lot) then blitEnd. No bounds checking void lcdST7789_blitStart(int x, int y, int w, int h);