From 62cd3977c7c79e60ca9d001ac5db99279b23b4eb Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Mon, 13 Jan 2025 11:36:28 +0100 Subject: [PATCH] Added modified ST7789 driver for Heltec T114 --- ST7789.h | 440 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 ST7789.h diff --git a/ST7789.h b/ST7789.h new file mode 100644 index 0000000..85012e8 --- /dev/null +++ b/ST7789.h @@ -0,0 +1,440 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn + * Copyright (c) 2018 by Fabrice Weinberg + * Copyright (c) 2024 by Heltec AutoMation + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * ThingPulse invests considerable time and money to develop these open source libraries. + * Please support us by buying our products (and not the clones) from + * https://thingpulse.com + * + */ + +#ifndef ST7789Spi_h +#define ST7789Spi_h + +#include "OLEDDisplay.h" +#include + + +#define ST_CMD_DELAY 0x80 // special signifier for command lists + +#define ST77XX_NOP 0x00 +#define ST77XX_SWRESET 0x01 +#define ST77XX_RDDID 0x04 +#define ST77XX_RDDST 0x09 + +#define ST77XX_SLPIN 0x10 +#define ST77XX_SLPOUT 0x11 +#define ST77XX_PTLON 0x12 +#define ST77XX_NORON 0x13 + +#define ST77XX_INVOFF 0x20 +#define ST77XX_INVON 0x21 +#define ST77XX_DISPOFF 0x28 +#define ST77XX_DISPON 0x29 +#define ST77XX_CASET 0x2A +#define ST77XX_RASET 0x2B +#define ST77XX_RAMWR 0x2C +#define ST77XX_RAMRD 0x2E + +#define ST77XX_PTLAR 0x30 +#define ST77XX_TEOFF 0x34 +#define ST77XX_TEON 0x35 +#define ST77XX_MADCTL 0x36 +#define ST77XX_COLMOD 0x3A + +#define ST77XX_MADCTL_MY 0x80 +#define ST77XX_MADCTL_MX 0x40 +#define ST77XX_MADCTL_MV 0x20 +#define ST77XX_MADCTL_ML 0x10 +#define ST77XX_MADCTL_RGB 0x00 + +#define ST77XX_RDID1 0xDA +#define ST77XX_RDID2 0xDB +#define ST77XX_RDID3 0xDC +#define ST77XX_RDID4 0xDD + +// Some ready-made 16-bit ('565') color settings: +#define ST77XX_BLACK 0x0000 +#define ST77XX_WHITE 0xFFFF +#define ST77XX_RED 0xF800 +#define ST77XX_GREEN 0x07E0 +#define ST77XX_BLUE 0x001F +#define ST77XX_CYAN 0x07FF +#define ST77XX_MAGENTA 0xF81F +#define ST77XX_YELLOW 0xFFE0 +#define ST77XX_ORANGE 0xFC00 + +#define LED_A_ON LOW + +#ifdef ESP_PLATFORM +#undef LED_A_ON +#define LED_A_ON HIGH +#define rtos_free free +#define rtos_malloc malloc +//SPIClass SPI1(HSPI); +#endif +class ST7789Spi : public OLEDDisplay { + private: + uint8_t _rst; + uint8_t _dc; + uint8_t _cs; + uint8_t _ledA; + int _miso; + int _mosi; + int _clk; + SPIClass * _spi; + SPISettings _spiSettings; + uint16_t _RGB=0xFFFF; + uint8_t _buffheight; + public: + /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */ + ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=320,int mosi=-1,int miso=-1,int clk=-1) { + this->_spi = spiClass; + this->_rst = _rst; + this->_dc = _dc; + this->_cs = _cs; + this->_mosi=mosi; + this->_miso=miso; + this->_clk=clk; + //this->_ledA = _ledA; + _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0); + setGeometry(g,width,height); + } + + bool connect(){ + this->_buffheight=displayHeight / 8; + this->_buffheight+=displayHeight % 8 ? 1:0; + pinMode(_cs, OUTPUT); + pinMode(_dc, OUTPUT); + //pinMode(_ledA, OUTPUT); + if (_cs != (uint8_t) -1) { + pinMode(_cs, OUTPUT); + } + pinMode(_rst, OUTPUT); + +#ifdef ESP_PLATFORM + _spi->begin(_clk,_miso,_mosi,-1); +#else + _spi->begin(); +#endif + _spi->setClockDivider (SPI_CLOCK_DIV2); + + // Pulse Reset low for 10ms + digitalWrite(_rst, HIGH); + delay(1); + digitalWrite(_rst, LOW); + delay(10); + digitalWrite(_rst, HIGH); + _spi->begin (); + //digitalWrite(_ledA, LED_A_ON); + return true; + } + + void display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + + uint16_t minBoundY = UINT16_MAX; + uint16_t maxBoundY = 0; + + uint16_t minBoundX = UINT16_MAX; + uint16_t maxBoundX = 0; + + uint16_t x, y; + + // Calculate the Y bounding box of changes + // and copy buffer[pos] to buffer_back[pos]; + for (y = 0; y < _buffheight; y++) { + for (x = 0; x < displayWidth; x++) { + //Serial.printf("x %d y %d\r\n",x,y); + uint16_t pos = x + y * displayWidth; + if (buffer[pos] != buffer_back[pos]) { + minBoundY = min(minBoundY, y); + maxBoundY = max(maxBoundY, y); + minBoundX = min(minBoundX, x); + maxBoundX = max(maxBoundX, x); + } + buffer_back[pos] = buffer[pos]; + } + yield(); + } + + // If the minBoundY wasn't updated + // we can savely assume that buffer_back[pos] == buffer[pos] + // holdes true for all values of pos + if (minBoundY == UINT16_MAX) return; + + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + + for (y = minBoundY; y <= maxBoundY; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1); + uint32_t const pixbufcount = maxBoundX-minBoundX+1; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = minBoundX; x <= maxBoundX; x++) + { + pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #else + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + uint8_t x, y; + for (y = 0; y < _buffheight; y++) + { + for(int temp = 0; temp<8;temp++) + { + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1); + setAddrWindow(y*8+temp,0,1,displayWidth); + uint32_t const pixbufcount = displayWidth; + uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount); + for (x = 0; x < displayWidth; x++) + { + pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0; + } +#ifdef ESP_PLATFORM + _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount); +#else + _spi->transfer(pixbuf, NULL, 2 * pixbufcount); +#endif + rtos_free(pixbuf); + } + } + _spi->endTransaction(); + set_CS(HIGH); + + #endif + } + + virtual void resetOrientation() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void flipScreenVertically() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void mirrorScreen() { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + virtual void setRotation(uint8_t r) { + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + if (r == 1) { madctl = 0xC0; } + if (r == 2) { madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; } + if (r == 3) { madctl = 0x00; } + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + } + + void setRGB(uint16_t c) + { + + this->_RGB=0x00|c>>8|c<<8&0xFF00; + } + + void displayOn(void) { + //sendCommand(DISPLAYON); + } + + void displayOff(void) { + //sendCommand(DISPLAYOFF); + } + +//#define ST77XX_MADCTL_MY 0x80 +//#define ST77XX_MADCTL_MX 0x40 +//#define ST77XX_MADCTL_MV 0x20 +//#define ST77XX_MADCTL_ML 0x10 + protected: + // Send all the init commands + virtual void sendInitCommands() + { + sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay + delay(150); + + sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay + delay(10); + + sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color + WriteData(0x55); + delay(10); + + sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions), Row/col addr, bottom-top refresh + WriteData(0x08); + + sendCommand(ST77XX_CASET); // 5: Column addr set, + WriteData(0x00); + WriteData(0x00); // XSTART = 0 + WriteData(0x00); + WriteData(240); // XEND = 240 + + sendCommand(ST77XX_RASET); // 6: Row addr set, + WriteData(0x00); + WriteData(0x00); // YSTART = 0 + WriteData(320>>8); + WriteData(320&0xFF); // YSTART = 320 + + sendCommand(ST77XX_SLPOUT); // 7: hack + delay(10); + + sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay + delay(10); + + sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay + delay(10); + + sendCommand(ST77XX_INVON); // 10: invert + delay(10); + + //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX; + uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX; + sendCommand(ST77XX_MADCTL); + WriteData(madctl); + delay(10); + setRGB(ST77XX_GREEN); + } + + + private: + + void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + x += (320-displayWidth)/2; + y += (240-displayHeight)/2; + uint32_t xa = ((uint32_t)x << 16) | (x + w - 1); + uint32_t ya = ((uint32_t)y << 16) | (y + h - 1); + + writeCommand(ST77XX_CASET); // Column addr set + SPI_WRITE32(xa); + + writeCommand(ST77XX_RASET); // Row addr set + SPI_WRITE32(ya); + + writeCommand(ST77XX_RAMWR); // write to RAM + } + int getBufferOffset(void) { + return 0; + } + inline void set_CS(bool level) { + if (_cs != (uint8_t) -1) { + digitalWrite(_cs, level); + } + }; + inline void sendCommand(uint8_t com) __attribute__((always_inline)){ + set_CS(HIGH); + digitalWrite(_dc, LOW); + set_CS(LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(com); + _spi->endTransaction(); + set_CS(HIGH); + digitalWrite(_dc, HIGH); + } + + inline void WriteData(uint8_t data) __attribute__((always_inline)){ + digitalWrite(_cs, LOW); + _spi->beginTransaction(_spiSettings); + _spi->transfer(data); + _spi->endTransaction(); + digitalWrite(_cs, HIGH); + } + void SPI_WRITE32(uint32_t l) + { + _spi->transfer(l >> 24); + _spi->transfer(l >> 16); + _spi->transfer(l >> 8); + _spi->transfer(l); + } + void writeCommand(uint8_t cmd) { + digitalWrite(_dc, LOW); + _spi->transfer(cmd); + digitalWrite(_dc, HIGH); + } + +// Private functions + void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) { + this->geometry = g; + + switch (g) { + case GEOMETRY_128_128: + this->displayWidth = 128; + this->displayHeight = 128; + break; + case GEOMETRY_128_64: + this->displayWidth = 128; + this->displayHeight = 64; + break; + case GEOMETRY_128_32: + this->displayWidth = 128; + this->displayHeight = 32; + break; + case GEOMETRY_64_48: + this->displayWidth = 64; + this->displayHeight = 48; + break; + case GEOMETRY_64_32: + this->displayWidth = 64; + this->displayHeight = 32; + break; + case GEOMETRY_RAWMODE: + this->displayWidth = width > 0 ? width : 128; + this->displayHeight = height > 0 ? height : 64; + break; + } + uint8_t tmp=displayHeight % 8; + uint8_t _buffheight=displayHeight / 8; + + if(tmp!=0) + _buffheight++; + this->displayBufferSize = displayWidth * _buffheight ; + } + + + +}; + +#endif \ No newline at end of file