/* * This file is part of Espruino, a JavaScript interpreter for Microcontrollers * * Copyright (C) 2013 Gordon Williams * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * ---------------------------------------------------------------------------- * Graphics Backend for drawing to ST7789 * ---------------------------------------------------------------------------- */ #include "platform_config.h" #include "jsutils.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 #define CMDINDEX_DATALEN 2 static const uint8_t ST7789_INIT_CODE[] = { // CMD,DELAY,DATA_LEN,D0,D1,D2... 0x11,0,0, // This is an unrotated screen 0x36,0,1,0, // MADCTL // These 2 rotate the screen by 180 degrees //0x36,0,1,0xC0, // MADCTL //0x37,0,2,0,80, // Vertical scroll 0x3A,0,1,0x55, // COLMOD - interface pixel format - 16bpp 0xB2,0,5,0xC,0xC,0,0x33,0x33, 0xB7,0,1,0, 0xBB,0,1,0x3E, 0xC2,0,1,1, 0xC3,0,1,0x19, 0xC4,0,1,0x20, 0xC5,0,1,0xF, 0xD0,0,2,0xA4,0xA1, 0xe0,0,14,0xd0,6,0xc,9,9,0x25,0x2e,0x33,0x45,0x36,0x12,0x12,0x2e,0x34, 0xe1,0,14,0xd0,6,0xc,9,9,0x25,0x2e,0x33,0x45,0x36,0x12,0x12,0x2e,0x34, 0x29,0,0, 0x21,0,0, // End 0, 0, 255/*DATA_LEN = 255 => END*/ }; const int LCD_BUFFER_HEIGHT = 320; 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 #define LCD_SCK_CLR() NRF_P0->OUTCLR = 1<OUTSET = 1<OUTCLR = 1<OUTSET = 1<OUTCLR = 1<OUTSET = 1<OUTCLR = 1<OUTSET = 1<OUT)=data) #define LCD_WR8(data) {LCD_DATA(data);LCD_SCK_CLR();LCD_SCK_SET();} void lcd_send_cmd(uint8_t cmd) { LCD_CS_CLR(); LCD_DC_COMMAND(); // command LCD_WR8(cmd); LCD_CS_SET(); } void lcd_send_data(uint8_t cmd) { LCD_CS_CLR(); LCD_DC_DATA(); // data LCD_WR8(cmd); LCD_CS_SET(); } #endif // EMSCRIPTEN void lcdST7789_cmd(int cmd, int dataLen, const uint8_t *data) { #ifndef EMSCRIPTEN LCD_CS_CLR(); LCD_DC_COMMAND(); // command LCD_WR8(cmd); if (dataLen) { LCD_DC_DATA(); // data while (dataLen) { LCD_WR8(*(data++)); dataLen--; } } LCD_CS_SET(); #endif } void lcdST7789_setMode(LCDST7789Mode mode) { if (lcdMode != mode) { uint8_t buf[4]; lcdMode = mode; switch (lcdMode) { case LCDST7789_MODE_UNBUFFERED: case LCDST7789_MODE_BUFFER_120x120: case LCDST7789_MODE_BUFFER_80x80: lcdST7789_cmd(0x13,0,NULL); buf[0] = 0; buf[1] = 0; lcdST7789_cmd(0x37,2,buf); break; case LCDST7789_MODE_DOUBLEBUFFERED: buf[0] = 0; buf[1] = 40; buf[2] = 0; buf[3] = 199; lcdST7789_cmd(0x30,4,buf); lcdST7789_cmd(0x12,0,NULL); lcdScrollY = 0; buf[0] = 0; buf[1] = 120; lcdST7789_cmd(0x37,2,buf); break; } } } void lcdST7789_flip(JsGraphics *gfx) { #ifndef EMSCRIPTEN unsigned char buf[2]; 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; int y1 = y+lcdScrollY; if (y1>=LCD_BUFFER_HEIGHT) y1-=LCD_BUFFER_HEIGHT; int x2 = x+w; int y2 = y+h+lcdScrollY; if (y2>=LCD_BUFFER_HEIGHT) y2-=LCD_BUFFER_HEIGHT; y += lcdScrollY; LCD_CS_CLR(); LCD_DC_COMMAND(); // command LCD_WR8(0x2A); LCD_DC_DATA(); // data LCD_WR8(0); LCD_WR8(x1); LCD_WR8(0); LCD_WR8(x2); LCD_DC_COMMAND(); // command LCD_WR8(0x2B); LCD_DC_DATA(); // data LCD_WR8(y1 >> 8); LCD_WR8(y1); LCD_WR8(y2 >> 8); LCD_WR8(y2); LCD_DC_COMMAND(); // command LCD_WR8(0x2C); LCD_DC_DATA(); // data #endif } 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 */ LCD_DATA(col>>8); asm("nop");asm("nop"); LCD_SCK_CLR_FAST(); LCD_SCK_SET_FAST(); LCD_DATA(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; lcdNextX=x+1; y += lcdScrollY; if (y>=LCD_BUFFER_HEIGHT) y-=LCD_BUFFER_HEIGHT; LCD_DC_COMMAND(); // command LCD_WR8(0x2A); LCD_DC_DATA(); // data LCD_WR8(0); LCD_WR8(x); LCD_WR8(0); LCD_WR8(LCD_WIDTH-1); LCD_DC_COMMAND(); // command LCD_WR8(0x2B); LCD_DC_DATA(); // data LCD_WR8(y>>8); LCD_WR8(y); LCD_WR8(y >> 8); LCD_WR8(y); LCD_DC_COMMAND(); // command LCD_WR8(0x2C); LCD_DC_DATA(); // data } else lcdNextX++; LCD_DATA(col>>8); asm("nop");asm("nop"); LCD_SCK_CLR_FAST(); LCD_SCK_SET_FAST(); LCD_DATA(col); asm("nop");asm("nop"); 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; y2 += lcdScrollY; if (y2>=LCD_BUFFER_HEIGHT) y2-=LCD_BUFFER_HEIGHT; // if scrolling meant we wrapped, we have to write it two separate calls int nexty = -1; if (y2>8); LCD_WR8(y1); LCD_WR8(y2>>8); LCD_WR8(y2); LCD_DC_COMMAND(); // command LCD_WR8(0x2C); LCD_DC_DATA(); // data lcdNextY=-1; lcdNextX=-1; if ((col&255) == (col>>8)) { // top and bottom bits are the same, we can just mash SCK LCD_DATA(col); asm("nop");asm("nop"); while (pixels--) { LCD_SCK_CLR_FAST();LCD_SCK_SET_FAST(); asm("nop"); LCD_SCK_CLR_FAST();LCD_SCK_SET_FAST(); } } else { // colors different, send manually while (pixels--) { LCD_DATA(col>>8); asm("nop");asm("nop"); LCD_SCK_CLR_FAST();LCD_SCK_SET_FAST(); LCD_DATA(col); asm("nop");asm("nop"); LCD_SCK_CLR_FAST();LCD_SCK_SET_FAST(); } } LCD_CS_SET(); // We might have to wrap over and start again if (nexty<0) return; y1 = 0; y2 = nexty; 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; } /* We can't read data back, so we can't do left/right scrolling! However we can change our index in the memory buffer window which allows us to use the LCD itself for scrolling */ lcdScrollY-=ydir; while (lcdScrollY<0) lcdScrollY+=LCD_BUFFER_HEIGHT; while (lcdScrollY>=LCD_BUFFER_HEIGHT) lcdScrollY-=LCD_BUFFER_HEIGHT; unsigned char buf[2]; 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) { lcdST7789_cmd(cmd[CMDINDEX_CMD], cmd[CMDINDEX_DATALEN], &cmd[3]); if (cmd[CMDINDEX_DELAY]) jshDelayMicroseconds(1000*cmd[CMDINDEX_DELAY]); cmd += 3 + cmd[CMDINDEX_DATALEN]; } #endif lcdNextX = -1; lcdNextY = -1; lcdScrollY = 0; lcdMode = LCDST7789_MODE_UNBUFFERED; } void lcdST7789_setCallbacks(JsGraphics *gfx) { 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; } }