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:
LCD initializes, a border 1px border is drawn, and Hello Espruino! is printed.
Top left a green, 20x20 px rectangular is drawn (filled).
A 20x20 px 'save' is taken from the area between Hellow and Espruino
A red , 20x20 px rectangular is drawn, just overriding the saved area
Task and time is printed (save: 495 [ms])
Red rectangular overridden area is restored
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,0x06,0x45,0x87,0x32,0x0A,0x07,0x02,0x07,0x05,0x00]);
writeCD(0xE1,[0x00,0x25,0x27,0x05,0x10,0x09,0x3A,0x78,0x4D,0x05,0x18,0x0D,0x38,0x3A,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)%256+(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.getHeight()-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]/252);
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:
time 566 [ms] is the read time for 20x20 area... 1200 Bytes packed into 800 to be keept until restore.
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:
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...).
Espruino is a JavaScript interpreter for low-power Microcontrollers. This site is both a support community for Espruino and a place to share what you are working on.
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:
Task and time is printed (restore: 1 [ms])
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...
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:
time of 14 [ms] is the restore / write time.
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:
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