• I ventured into the ILI9341 datasheet and - of course - some coding. First I did an analysis of the existing module... I hope I got the comments right.. I added also getPixel(), getRect()angle, setRect() - for saving and restoring on overlay, and a helper method to convert from 3 byte 6 bit <<2 pixel data from reading a pixel w/ getPixel() to writing using setPixel()*. Code - ready to be loaded into the editor, a shot and a clip are attached to the post.

    The attached clip shows you the behavior:

    1. LCD initializes, a border 1px border is drawn, and Hello Espruino! is printed.
    2. Top left a green, 20x20 px rectangular is drawn (filled).
    3. A 20x20 px 'save' is taken from the area between Hellow and Espruino
    4. A red , 20x20 px rectangular is drawn, just overriding the saved area
    5. Task and time is printed (save: 495 [ms])
    6. Red rectangular overridden area is restored
    7. Task and time is printed (restore: 1 [ms])

      Attached is the inline file that can be loaded right into the editor.
      
      // ILI9341E_DOCD_inline.js
      
      /* Copyright (c) 2013 Juergen Marsch and Gordon Williams, Pur3 Ltd. See the file LICENSE for copying permission. */
      /*
      Module Enhanced for the ILI9341 LCD controller
      
      Just:
      
      SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
      var g = require("ILI9341").connect(SPI1, B6, B8, B7, function() {
      g.clear();
      g.drawString("Hello",0,0);
      g.setFontVector(20);
      g.setColor(0,0.5,1);
      g.drawString("Espruino",0,10);
      // --- "Enhacements"
      g.getPixel(x,y) -> [R,G,B] each 6 bits << 2 (white = 252,252,252)
      g.pxToInt([R,G,B] (from getPixel()) -> int color for setPixel(x,y,color)
      g.getRect(x1,y1,x2,y2); --> Uint16Array of [colors,...] 
      g.setRect(x1,y1,x2,y2,rect); // rect from getRect() 
      });
      
      */
      var LCD_WIDTH = 240;
      var LCD_HEIGHT = 320;
      
      var ILI9341 = {connect: function(spi, dc, ce, rst, callback) { // inline for next line
      // exports.connect = function(spi, dc, ce, rst, callback) { 
        
      // about SPI (see http://www.espruino.com/Reference#SPI):  
      // d is data1, p is data1 or pin, w/ data1 is int|string|array|{data:e, count:#} and 
      // e is data2, w/ data2 is int|string|array
      // - spi.write(d,...,p); // sends LSBytes of all d, and if last arg is pin, pin held 0  
      // - spi.send(e,p);      // sends LSBytes of e or all in e, if p present, pin p held 0
        
      function writeCMD(c){  // --- write command (cmd) only
      ce.write(0);         // chip enable
      spi.write(c,dc);     // spi write cmd w/ dc data/cmd pin held cmd mode (0)
      }
      
      function writeCD(c,d){ // --- write command w/ data/parms
      ce.write(0);         // chip enable
      spi.write(c,dc);     // spi write cmd w/ dc data/cmd pin held cmd mode (0)
      spi.send(d);         // spi send data/parms
      ce.write(1);         // chip disable
      }
      
      var LCD = Graphics.createCallback(LCD_WIDTH, LCD_HEIGHT, 16, {
      
      // set pixel is like fill rectangle w/ [x1/y1=x,y inclusive and x2/y2=x+1,y+1) exclusive
      setPixel:function(x,y,c){   // x, y, c(olor)/image data for 1px
        ce.reset();               // chip enable
        spi.write(0x2A,dc);       // cmd 0x2A - x/col addr - w/ dc for cmd (0) 
        spi.write(x>>8,x,x>>8,x); // x [Start Point MSByte, LSByte, then End Point)
        spi.write(0x2B,dc);       // cmd 0x2B - y/row/page addr - w/ dc for cmd (0)
        spi.write(y>>8,y,y>>8,y); // x [Start Point MSByte, LSByte, then End Point) 
        spi.write(0x2C,dc);       // cmd 0x2C - memory write - w/ dc for cmd (0)
        spi.write(c>>8,c);        // 65K 2 bytes color (LSByte 1st, MSB 2nd)
        ce.set();
      },
      
      fillRect:function(x1,y1,x2,y2,c){ // x1,y1,y1,y2, c(olor)/image data for 1px
        ce.reset();                     // chip enable
        spi.write(0x2A,dc);             // cmd 0x2A - x/col addr - w/ dc for cmd (0)
        spi.write(x1>>8,x1,x2>>8,x2);   // x [Start Point MSByte, LSByte, then End Point)
        spi.write(0x2B,dc);             // cmd 0x2B - y/row/page addr - w/ dc for cmd (0)
        spi.write(y1>>8,y1,y2>>8,y2);   // y [Start Point MSByte, LSByte, then End Point)
        spi.write(0x2C,dc);             // md 0x2C - memory write - w/ dc for cmd (0)
        spi.write({data:String.fromCharCode(c>>8­)+String.fromCharCode(c), count:(x2-x1+1)*(y2-y1+1)});
        ce.set();                       // chip disable
      }
      });
      
      ce.write(1);               // chip disable
      dc.write(1);               // data mode 
      rst.write(0);              // reset
      setTimeout(function(){     // time reset pin low 1[ms] using timeout
      rst.write(1);            // end reset 
      setTimeout(function(){   // time after reset pin back to high 5[ms]
        writeCMD(0x01);        // cmd 01 software Reset
        setTimeout(function(){ // time for software Reset 5[ms]
          writeCMD(0x28);      // cmd 28 Display OFF
          ce.write(1);               // chip disable
          writeCD(0xCF,[0x00,0x83,0x30]);      // CF Power control B 
          writeCD(0xED,[0x64,0x03,0x12,0x81]); // ED Power on sequence control
          writeCD(0xE8,[0x85,0x01,0x79]);      // E8 Driver timing control A
          writeCD(0xCB,[0x39,0x2C,0x00,0x34,0x02])­; // CB Power control B
          writeCD(0xF7,0x20);        // F7 Pump ratio control
          writeCD(0xEA,[0x00,0x00]); // EA Driver timing control B
          writeCD(0xC0,0x26);        // C0 Power Control 1
          writeCD(0xC1,0x11);        // C1 Power Control 2
          writeCD(0xC5,[0x35,0x3E]); // C5 VCOM Control 1
          writeCD(0xC7,0xBE);        // C7 VCOM Control 2
          writeCD(0x36,0x48);        // 36 Memory Access Control: row col exch, BGR order
          writeCD(0x3A,0x55);        // 3A COLMOD: Pixel Format Set 65K 16bit R(5) G(6) B(5)
          writeCD(0xB1,[0x00,0x1B]); // B1 Frame Rate Control (in Normal Mode/Full Colors)
          writeCD(0xF2,0x08);        // F2 Enable 3G
          writeCD(0x26,0x01);        // 26 Gamma Set // E0 Positive Gamma Correction // E1 Negative...
          writeCD(0xE0,[0x1F,0x1A,0x18,0x0A,0x0F,0­x06,0x45,0x87,0x32,0x0A,0x07,0x02,0x07,0­x05,0x00]);
          writeCD(0xE1,[0x00,0x25,0x27,0x05,0x10,0­x09,0x3A,0x78,0x4D,0x05,0x18,0x0D,0x38,0­x3A,0x1F]);
          writeCD(0xB7,0x07);                  // B7 Entry Mode Set
          writeCD(0xB6,[0x0A,0x82,0x27,0x00]); // B6 Display Function Control
          writeCMD(0x11);                      // 11 Sleep Out
          ce.write(1);               // chip disable 
          setTimeout(function(){        // time for Sleep Out 100[ms]
            writeCMD(0x29);ce.write(1); // 29 Display ON
            setTimeout(function(){      // time for DISPLAY ON 100[ms]
              if (callback!==undefined) callback(); // application callback
            },100); // 100[ms] for switching DISPLAY ON
          },100);   // 100[ms] for Sleep Out
        },5);       //   5[ms] for Software Reset
      },5);         //   5[ms] time after reset pin back to high
      },1);
        
      LCD.getPixel = function(x,y) {
      ce.reset();               // chip enable
      spi.write(0x2A,dc);       // cmd 0x2A - x/col addr - w/ dc for cmd (0) 
      spi.write(x>>8,x,x>>8,x); // x [Start Point MSByte, LSByte, then End Point)
      spi.write(0x2B,dc);       // cmd 0x2B - y/row/page addr - w/ dc for cmd (0)
      spi.write(y>>8,y,y>>8,y); // x [Start Point MSByte, LSByte, then End Point) 
      spi.write(0x2E,dc);       // cmd 0x2E - memory red - w/ dc for cmd (0)
      spi.write(0x00,0x00,0x00);
      var p = spi.send([0x00,0x00,0x00]);
      ce.set();
      return p;
      };
        
      LCD.pxToInt = function(p) { return ((p[0]>>3)<<3)+(p[1]>>5)*256+(p[1]<<3)%2­56+(p[2]>>3); };
      
      LCD.getRect = function(x1,y1,x2,y2) {
      var m = 0, n = (x2-x1+1)*(y2-y1+1)*2, r = new Uint8Array(n);
      var xfr = function(p) {
        r[m++] = ((p[0]>>3)<<3)+(p[1]>>5); r[m++] = (p[1]<<3)%256+(p[2]>>3); };
      ce.reset();                   // chip enable
      spi.write(0x2A,dc);           // cmd 0x2A - x/col addr - w/ dc for cmd (0) 
      spi.write(x1>>8,x1,x2>>8,x2); // x [Start/Ent Pts MSByte, LSByte)
      spi.write(0x2B,dc);           // cmd 0x2B - y/row/page addr - w/ dc for cmd (0)
      spi.write(y1>>8,y1,y2>>8,y2); // x [Start/End PTs MSByte, LSByte) 
      spi.write(0x2E,dc);           // cmd 0x2E - memory read - w/ dc for cmd (0)
      spi.write(0x00,0x00,0x00);    // dmy reads
      while (m < n) { xfr(spi.send([0x00,0x00,0x00])); } // a px at a time incl conv to Uint16
      ce.set();
      return r;
      };
         
      LCD.setRect = function(x1,y1,x2,y2,r) {
      ce.reset();                   // chip enable
      spi.write(0x2A,dc);           // cmd 0x2A - x/col addr - w/ dc for cmd (0) 
      spi.write(x1>>8,x1,x2>>8,x2); // x [Start Point MSByte, LSByte, then End Point)
      spi.write(0x2B,dc);           // cmd 0x2B - y/row/page addr - w/ dc for cmd (0)
      spi.write(y1>>8,y1,y2>>8,y2); // x [Start Point MSByte, LSByte, then End Point) 
      spi.write(0x2C,dc);           // cmd 0x2C - memory write - w/ dc for cmd (0)
      spi.write(r);
      ce.set();
      };  
      return LCD;
      } // inline
      };
      
      
      SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
      var lcd = ILI9341.connect(SPI1, B6, B8, B7, function() { // inline for next line
      // var lcd = require("ILI9341").connect(SPI1, B6, B8, B7, function() {
      B2.set();
      lcd.clear();
      lcd.setColor(0.0,1.0,1.0);
      lcd.drawRect(0,0,lcd.getWidth()-1,lcd.ge­tHeight()-1);
      lcd.setColor(1.0,1.0,1.0);
      lcd.setFontVector(20);
      lcd.drawString("Hello Espruino!",35,5);
      lcd.setFontVector(15);
      var r,t;
      lcd.setColor(0.0,1.0,0.0);
      lcd.fillRect(1,1,20,20);
      t = getTime();
      r = lcd.getRect( 85,  0,104, 19);   // get/save rectangle for later restor
      console.log(t = getTime() - t,"[s]  bytes:",r.length," memory",process.memory().usage);
      lcd.setColor(1.0,1.0,1.0);
      lcd.drawString("save 20x20 rectangle:",5,35);
      lcd.setColor(1.0,1.0,0.0);
      lcd.drawString(Math.round(t*1000) + " [ms] 1200-->800 bytes",5,55);
      setTimeout(function(){
      lcd.setColor(1.0,0.0,0.0); 
      lcd.fillRect( 85,  0,104, 19);    // override saved rectangle area
      setTimeout(function(){
        t = getTime();
        lcd.setRect( 85,  0,104, 19,r); // restore rectangle area
        console.log(t = getTime() - t,"[s]  bytes:",r.length," memory",process.memory().usage);
        lcd.setColor(1.0,1.0,1.0);
        lcd.drawString("restore 20x20 rectangle:",5,75);
        lcd.setColor(1.0,1.0,0.0);
        lcd.drawString(Math.round(t*1000) + " [ms] <--800 bytes",5,95);
      },1000);
      },1000);
      });
      

    Most difficult part in getRect() and setRect() for save and restore (or copy) of an area was the different pixel format: it is read with 3 6bit bytes and a left shift of <<2 (White is 252,252,252). Writing back requires two Uint8 that cover 5 bits read, 6 bits green (straddling the bytes), and 5 bits blue. After a while I got the hang of it.

    You can use the color values to set the color and then call setPixel with no color argument... but that's super strolly...

    var px = lcd.getPixel(x,y); // --> array [R,G,B], 6 bit each with left shift of <<2
    lcd.setColor(px[0]/252,px[1]/252,px[2]/2­52);
    lcd.setPixel(x,y);
    

    The method .pixToInt(px) in line 126 helps to get this a bit easier (and is borrowed from getRect() method (line 133) - a slow version of ILICalc in assembler from @JumJum ;), figured that out in hindsight.

    Initially I just read all the bytes in one swoosh by creating and sending a 0x00 initialized Uint8Array --- and wasted lots of memory... 4 or more bytes per pixel... and could read only 10x10 before running out of memory. Also, I kept the read format until restoring and did above mentioned conversion from 3 to 2 bytes when restoring: Another tie up of additional 50% memory. So I came up with the one px at a time (which works quite well - form me - but could be optimized by some bulking algorithm), and do the conversion right when reading.. Now, only the bare minimum on memory is used. This seems to work for my purposes. But I'm looking int adding some serial RAM (flash is an option too, but sucks in power... on the other hand it's on the board...) I was also thinking of using the controller's 'porches' outside of the visual area - if there is memory left; but I did not go into detail thoughts yet).

    The approach of reading just a pixel and do the conversion turned out not much slower than reading and keeping all data memory efficiently, but the write is now a very quick one:

    1. time 566 [ms] is the read time for 20x20 area... 1200 Bytes packed into 800 to be keept until restore.
    2. time of 14 [ms] is the restore / write time.

      1v70 Copyright 2014 G.Williams
      >echo(0);
      =undefined
      0.56678676605 [s]  bytes: 800  memory 1315
      0.01441287994 [s]  bytes: 800  memory 1312
      >
      

    I hoped to get more out of it when making it a module and using minification: Speed was insignificant faster... 12% (last but not least removed the inline/infunction comments), but memory looks great:

     1v70 Copyright 2014 G.Williams
    >echo(0);
    =undefined
    0.49496555328 [s]  bytes: 800  memory 678
    0.01411247253 [s]  bytes: 800  memory 682
    > 
    

    I had also some difficulties with reading from the controller via SPI, because for each byte to read, a byte has to be sent (?) with SPI.send(). Unfortunately, SPI.send() is not supporting the object {data:data, count#} parameter as the write does... and the SPI.write - which supports it - does not return anything... So my suggestion is that the send() should support the object parameter option... the count would have made it really easy: repeatedly sending 0x00 (send is always a 0x00)

    Small changes to the existing module are possible: when defining the rectangle (column and page address), the rectangle can have an extend of 0: no need to add 1. And as said, the spi.send could use the object parameter option... (and a native map.reduce function...).

    @JumJum, I did not venture into assembler yet...


    3 Attachments

About

Avatar for allObjects @allObjects started