ST7789.h
  1  /**
  2   * The MIT License (MIT)
  3   *
  4   * Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
  5   * Copyright (c) 2018 by Fabrice Weinberg
  6   * Copyright (c) 2024 by Heltec AutoMation
  7   * Permission is hereby granted, free of charge, to any person obtaining a copy
  8   * of this software and associated documentation files (the "Software"), to deal
  9   * in the Software without restriction, including without limitation the rights
 10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11   * copies of the Software, and to permit persons to whom the Software is
 12   * furnished to do so, subject to the following conditions:
 13   *
 14   * The above copyright notice and this permission notice shall be included in all
 15   * copies or substantial portions of the Software.
 16   *
 17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 23   * SOFTWARE.
 24   *
 25   * ThingPulse invests considerable time and money to develop these open source libraries.
 26   * Please support us by buying our products (and not the clones) from
 27   * https://thingpulse.com
 28   *
 29   */
 30  
 31  #ifndef ST7789Spi_h
 32  #define ST7789Spi_h
 33  
 34  #include "OLEDDisplay.h"
 35  #include <SPI.h>
 36  
 37  
 38  #define ST_CMD_DELAY 0x80 // special signifier for command lists
 39  
 40  #define ST77XX_NOP 0x00
 41  #define ST77XX_SWRESET 0x01
 42  #define ST77XX_RDDID 0x04
 43  #define ST77XX_RDDST 0x09
 44  
 45  #define ST77XX_SLPIN 0x10
 46  #define ST77XX_SLPOUT 0x11
 47  #define ST77XX_PTLON 0x12
 48  #define ST77XX_NORON 0x13
 49  
 50  #define ST77XX_INVOFF 0x20
 51  #define ST77XX_INVON 0x21
 52  #define ST77XX_DISPOFF 0x28
 53  #define ST77XX_DISPON 0x29
 54  #define ST77XX_CASET 0x2A
 55  #define ST77XX_RASET 0x2B
 56  #define ST77XX_RAMWR 0x2C
 57  #define ST77XX_RAMRD 0x2E
 58  
 59  #define ST77XX_PTLAR 0x30
 60  #define ST77XX_TEOFF 0x34
 61  #define ST77XX_TEON 0x35
 62  #define ST77XX_MADCTL 0x36
 63  #define ST77XX_COLMOD 0x3A
 64  
 65  #define ST77XX_MADCTL_MY 0x80
 66  #define ST77XX_MADCTL_MX 0x40
 67  #define ST77XX_MADCTL_MV 0x20
 68  #define ST77XX_MADCTL_ML 0x10
 69  #define ST77XX_MADCTL_RGB 0x00
 70  
 71  #define ST77XX_RDID1 0xDA
 72  #define ST77XX_RDID2 0xDB
 73  #define ST77XX_RDID3 0xDC
 74  #define ST77XX_RDID4 0xDD
 75  
 76  // Some ready-made 16-bit ('565') color settings:
 77  #define ST77XX_BLACK 0x0000
 78  #define ST77XX_WHITE 0xFFFF
 79  #define ST77XX_RED 0xF800
 80  #define ST77XX_GREEN 0x07E0
 81  #define ST77XX_BLUE 0x001F
 82  #define ST77XX_CYAN 0x07FF
 83  #define ST77XX_MAGENTA 0xF81F
 84  #define ST77XX_YELLOW 0xFFE0
 85  #define ST77XX_ORANGE 0xFC00
 86  
 87  #define LED_A_ON LOW
 88  
 89  #ifdef ESP_PLATFORM
 90  #undef LED_A_ON
 91  #define LED_A_ON HIGH
 92  #define rtos_free free
 93  #define rtos_malloc malloc
 94  //SPIClass SPI1(HSPI);
 95  #endif
 96  class ST7789Spi : public OLEDDisplay {
 97    private:
 98        uint8_t             _rst;
 99        uint8_t             _dc;
100        uint8_t             _cs;
101        uint8_t             _ledA;
102        int             _miso;
103        int             _mosi;
104        int             _clk;
105        SPIClass * _spi;
106        SPISettings           _spiSettings;
107        uint16_t            _RGB=0xFFFF;
108        uint8_t             _buffheight;
109    public:
110      /* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */
111      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) {
112        this->_spi = spiClass;
113        this->_rst = _rst;
114        this->_dc  = _dc;
115        this->_cs  = _cs;
116        this->_mosi=mosi;
117        this->_miso=miso;
118        this->_clk=clk;
119        //this->_ledA  = _ledA;
120        _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
121        setGeometry(g,width,height);
122      }
123  
124      bool connect(){
125        this->_buffheight=displayHeight / 8;
126        this->_buffheight+=displayHeight % 8 ? 1:0;
127        pinMode(_cs, OUTPUT);
128        pinMode(_dc, OUTPUT);
129        //pinMode(_ledA, OUTPUT);
130        if (_cs != (uint8_t) -1) {
131          pinMode(_cs, OUTPUT);
132        }  
133        pinMode(_rst, OUTPUT);
134  
135  #ifdef ESP_PLATFORM
136        _spi->begin(_clk,_miso,_mosi,-1);
137  #else
138        _spi->begin();
139  #endif
140        _spi->setClockDivider (SPI_CLOCK_DIV2);
141  
142        // Pulse Reset low for 10ms
143        digitalWrite(_rst, HIGH);
144        delay(1);
145        digitalWrite(_rst, LOW);
146        delay(10);
147        digitalWrite(_rst, HIGH);
148        _spi->begin ();
149        //digitalWrite(_ledA, LED_A_ON);
150        return true;
151      }
152  
153      void display(void) {
154      #ifdef OLEDDISPLAY_DOUBLE_BUFFER
155  
156         uint16_t minBoundY = UINT16_MAX;
157         uint16_t maxBoundY = 0;
158  
159         uint16_t minBoundX = UINT16_MAX;
160         uint16_t maxBoundX = 0;
161  
162         uint16_t x, y;
163          
164         // Calculate the Y bounding box of changes
165         // and copy buffer[pos] to buffer_back[pos];
166         for (y = 0; y < _buffheight; y++) {
167           for (x = 0; x < displayWidth; x++) {
168            //Serial.printf("x  %d y %d\r\n",x,y);
169            uint16_t pos = x + y * displayWidth;
170            if (buffer[pos] != buffer_back[pos]) {
171              minBoundY = min(minBoundY, y);
172              maxBoundY = max(maxBoundY, y);
173              minBoundX = min(minBoundX, x);
174              maxBoundX = max(maxBoundX, x);
175            }
176            buffer_back[pos] = buffer[pos];
177          }
178          yield();
179         }
180  
181         // If the minBoundY wasn't updated
182         // we can savely assume that buffer_back[pos] == buffer[pos]
183         // holdes true for all values of pos
184         if (minBoundY == UINT16_MAX) return;
185  
186            set_CS(LOW);
187            _spi->beginTransaction(_spiSettings);
188  
189            for (y = minBoundY; y <= maxBoundY; y++)
190            {
191              for(int temp = 0; temp<8;temp++)
192              {
193                //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
194                setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
195                //setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1);
196                uint32_t const pixbufcount = maxBoundX-minBoundX+1;
197                uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
198                for (x = minBoundX; x <= maxBoundX; x++)
199                {
200                  pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
201                }
202  #ifdef ESP_PLATFORM
203                _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
204  #else
205                _spi->transfer(pixbuf, NULL, 2 * pixbufcount);
206  #endif
207                rtos_free(pixbuf);
208              }
209            }
210        _spi->endTransaction();
211        set_CS(HIGH);
212  
213       #else
214            set_CS(LOW);
215            _spi->beginTransaction(_spiSettings);
216          uint8_t x, y;
217            for (y = 0; y < _buffheight; y++)
218            {
219              for(int temp = 0; temp<8;temp++)
220              {
221                //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
222                //setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
223                setAddrWindow(y*8+temp,0,1,displayWidth);
224                uint32_t const pixbufcount = displayWidth;
225                uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
226                for (x = 0; x < displayWidth; x++)
227                {
228                  pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
229                }
230  #ifdef ESP_PLATFORM
231                _spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
232  #else
233                _spi->transfer(pixbuf, NULL, 2 * pixbufcount);
234  #endif
235                rtos_free(pixbuf);
236              }
237            }
238        _spi->endTransaction();
239        set_CS(HIGH);
240  
241       #endif
242      }
243  
244   virtual void resetOrientation() {
245      uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX;
246      sendCommand(ST77XX_MADCTL);
247      WriteData(madctl);
248      delay(10);
249    }
250    
251   virtual void flipScreenVertically() {
252      uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY;
253      sendCommand(ST77XX_MADCTL);
254      WriteData(madctl);
255      delay(10);
256    }
257    
258   virtual void mirrorScreen() {
259      uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY;
260      sendCommand(ST77XX_MADCTL);
261      WriteData(madctl);
262      delay(10);
263    }
264  
265    virtual void setRotation(uint8_t r) {
266      uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX;
267      if (r == 1) { madctl = 0xC0; }
268      if (r == 2) { madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY; }
269      if (r == 3) { madctl = 0x00; }
270      sendCommand(ST77XX_MADCTL);
271      WriteData(madctl);
272      delay(10);
273    }
274  
275    void setRGB(uint16_t c)
276    {
277  
278      this->_RGB=0x00|c>>8|c<<8&0xFF00;
279    }
280    
281    void displayOn(void) {
282    //sendCommand(DISPLAYON);
283    }
284  
285    void displayOff(void) {
286    //sendCommand(DISPLAYOFF);
287    }
288    
289  //#define ST77XX_MADCTL_MY 0x80
290  //#define ST77XX_MADCTL_MX 0x40
291  //#define ST77XX_MADCTL_MV 0x20
292  //#define ST77XX_MADCTL_ML 0x10
293    protected:
294      // Send all the init commands
295      virtual void sendInitCommands()
296      {
297          sendCommand(ST77XX_SWRESET); //  1: Software reset, no args, w/delay
298          delay(150);
299  
300          sendCommand(ST77XX_SLPOUT); //  2: Out of sleep mode, no args, w/delay
301          delay(10);
302  
303          sendCommand(ST77XX_COLMOD); //  3: Set color mode, 16-bit color
304          WriteData(0x55); 
305          delay(10);
306          
307          sendCommand(ST77XX_MADCTL); //  4: Mem access ctrl (directions), Row/col addr, bottom-top refresh
308          WriteData(0x08); 
309          
310          sendCommand(ST77XX_CASET); //   5: Column addr set, 
311          WriteData(0x00); 
312          WriteData(0x00);         //    XSTART = 0
313          WriteData(0x00); 
314          WriteData(240);          //     XEND = 240
315          
316          sendCommand(ST77XX_RASET); //   6: Row addr set, 
317          WriteData(0x00); 
318          WriteData(0x00);         //    YSTART = 0
319          WriteData(320>>8); 
320          WriteData(320&0xFF);          //    YSTART = 320
321          
322          sendCommand(ST77XX_SLPOUT); //  7: hack
323          delay(10);
324          
325          sendCommand(ST77XX_NORON); //  8: Normal display on, no args, w/delay
326          delay(10);
327          
328          sendCommand(ST77XX_DISPON); //  9: Main screen turn on, no args, delay
329          delay(10);
330  
331          sendCommand(ST77XX_INVON); //  10: invert
332          delay(10);
333  
334          //uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MX;
335          uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX;
336          sendCommand(ST77XX_MADCTL);
337          WriteData(madctl);
338          delay(10);
339          setRGB(ST77XX_GREEN);
340      }
341  
342  
343    private:
344  
345     void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
346      x += (320-displayWidth)/2;
347      y += (240-displayHeight)/2;
348      uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
349      uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
350  
351      writeCommand(ST77XX_CASET); // Column addr set
352      SPI_WRITE32(xa);
353  
354      writeCommand(ST77XX_RASET); // Row addr set
355      SPI_WRITE32(ya);
356  
357      writeCommand(ST77XX_RAMWR); // write to RAM
358    }
359      int getBufferOffset(void) {
360          return 0;
361      }
362      inline void set_CS(bool level) {
363        if (_cs != (uint8_t) -1) {
364          digitalWrite(_cs, level);
365        }
366      };
367      inline void sendCommand(uint8_t com) __attribute__((always_inline)){
368        set_CS(HIGH);
369        digitalWrite(_dc, LOW);
370        set_CS(LOW);
371        _spi->beginTransaction(_spiSettings);
372        _spi->transfer(com);
373        _spi->endTransaction();
374        set_CS(HIGH);
375        digitalWrite(_dc, HIGH);
376      }
377      
378      inline void WriteData(uint8_t data) __attribute__((always_inline)){
379          digitalWrite(_cs, LOW);
380          _spi->beginTransaction(_spiSettings);
381          _spi->transfer(data);
382          _spi->endTransaction();
383          digitalWrite(_cs, HIGH);
384      }
385     void SPI_WRITE32(uint32_t l)
386     {
387        _spi->transfer(l >> 24);
388        _spi->transfer(l >> 16);
389        _spi->transfer(l >> 8);
390        _spi->transfer(l);
391     }
392    void writeCommand(uint8_t cmd) {
393      digitalWrite(_dc, LOW);
394      _spi->transfer(cmd);
395      digitalWrite(_dc, HIGH);
396    }
397  
398  // Private functions
399    void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) {
400      this->geometry = g;
401  
402      switch (g) {
403        case GEOMETRY_128_128:
404          this->displayWidth = 128;
405          this->displayHeight = 128;
406          break;
407        case GEOMETRY_128_64:
408          this->displayWidth = 128;
409          this->displayHeight = 64;
410          break;
411        case GEOMETRY_128_32:
412          this->displayWidth = 128;
413          this->displayHeight = 32;
414          break;
415        case GEOMETRY_64_48:
416          this->displayWidth = 64;
417          this->displayHeight = 48;
418          break;
419        case GEOMETRY_64_32:
420          this->displayWidth = 64;
421          this->displayHeight = 32;
422          break;
423        case GEOMETRY_RAWMODE:
424          this->displayWidth = width > 0 ? width : 128;
425          this->displayHeight = height > 0 ? height : 64;
426          break;
427      }
428      uint8_t tmp=displayHeight % 8;
429      uint8_t _buffheight=displayHeight / 8;
430  
431      if(tmp!=0)
432        _buffheight++;
433      this->displayBufferSize = displayWidth * _buffheight ;
434    }
435    
436  
437  
438  };
439  
440  #endif