/** * 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