-
Back with an alpha ( some things doesn't work as they should, and I'm still missing the finalized data ( timed mouth indices & mouths data ) ), but it's cleaner than previously ;)
also, I managed to wreck my code somehow somewhere & now I can't control the last led any more :|
if anyone's feeling brave enough to visually troubleshoot, be my guest ;)
( as always, I'll do so after some rest ;) )this being said, the pre-pre-pre-alpha code
/* ==== Akali's Mask V0.1a ==== */ // -- MODULES -- // - little 'dotstar' module - // usage: // var dotStar = exports(spi); // pass spi obj //dotStar.write(ledsValues); // pass array of leds colors var exports = function (spi) { return { write: function(ledsData){ spi.write([0x00,0x00,0x00,0x00]); // start frame spi.write(ledsData); // led frames for(var y=0; y<(ledsData.length/2)/8; y++){ spi.write(0x00); } // end frame } }; }; // -- COLORS/PATTERNS HELPER -- // R: APA102 are BGR for me function getColoredPattern(colR, colG, colB, start, end, clear) { var tstart = (start > 0)? start*4-3 : 0 || 0; var tend = end*4 || ledsBuff.length; for (var i=0;i<ledsBuff.length;i+=4) { // Q: is it where my 'one less led' comes from ? :/ if(i>=tstart && i < tend){ ledsBuff[i] = 0xe0+31; ledsBuff[i+1] = colB; ledsBuff[i+2] = colG; ledsBuff[i+3] = colR; } else if(clear) { // doesn't work ? :/ ledsBuff[i] = 0xe0+31; ledsBuff[i+1] = 0; ledsBuff[i+2] = 0; ledsBuff[i+3] = 0; } } return ledsBuff; } // R: steps may (later ) be used to either: // - map '0' to '255, 0, 0, 0' & '1' to '255, 255, 255, 255' ( by def we store 1 & 0's, but lookup table set with R,G,B vals at each index can be used ) // Nb: we could then for example get the color of the item above a dot on Illustrator as an idx of its color in our 'shared' palette // - map 'R, G, B' to '255, B, G, R', what we currently do var colorsPalette = [[0, 0, 0], [255, 255, 255]]; function getPattern(array, steps) { for (var i=0, y=0; i<ledsBuff.length;i+=4, y+=steps) { if(steps == 1){ ledsBuff[i] = 0xe0+31; //var val = (array[y] == 1)? 255 : 0; // would only support on/ff instead of paletted idx which offer 255 choices ledsBuff[i+1] = colorsPalette[array[y]][2]; // B ledsBuff[i+2] = colorsPalette[array[y]][1]; // G ledsBuff[i+3] = colorsPalette[array[y]][0]; // R } else if(steps == 3){ ledsBuff[i] = 0xe0+31; ledsBuff[i+1] = array[y+2]; // B ledsBuff[i+2] = array[y+1]; // G ledsBuff[i+3] = array[y]; // R } else if(steps == 4){ ledsBuff[i] = array[y]; // brightness ledsBuff[i+1] = array[y+1]; // R ledsBuff[i+2] = array[y+2]; // G ledsBuff[i+3] = array[y+3]; // B } } return ledsBuff; } // -- LEDS WORKING CHECK -- function checkLeds(delay){ setTimeout(function(){ dotStar.write(getColoredPattern(255, 0, 0, 0, ledsCount)); setTimeout(function(){ dotStar.write(getColoredPattern(0, 255, 0, 0, ledsCount)); setTimeout(function(){ dotStar.write(getColoredPattern(0, 0, 255, 0, ledsCount)); setTimeout(function(){ dotStar.write(getColoredPattern(255, 255, 255, 0, ledsCount)); setTimeout(function(){ dotStar.write(getColoredPattern(0, 0, 0, 0, ledsCount)); },delay); },delay); },delay); },delay); },delay); } // -- MOUTHS -- // TODO: generate dummys & finalize the leds mouths in Illustrator // for finalized: how many ? number of leds in each ? which leds are on for each ? // R: since we have to store at least 8 different mouths ( and by default, only on/off states of leds in those ), storing 1's & 0's ( or paletted color idx) // make more sense to lessen the space taken // ex: leds * BrRGB * mouths = 45 * 4 * 8 = 1440 bytes ( with brightness control ) // leds * RGB * mouths = 45 * 3 * 8 = 1080 bytes ( any color, no brightness control ) // leds * paletted idx * mouths = 45 * 1 * 8 = 360 bytes ( 255 possible colors ) var ledsBuff = new Uint8ClampedArray(45*4); // used as tmp to fill in /map in values while keeping constant brightness var mouthsLedsArrays = [ //new Uint8ClampedArray([0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0]), //new Uint8ClampedArray(45*3), ]; // dummies generator, to test stuff before finalizing the mouths leds arrays function generateDummies(howMany){ for(var i=0; i<howMany; i++){ // R/ testing with 45 leds console.log('pushing dummy: [' + (i*9) + '..' + (i*9+9) + ']' ); //ledsBuff = new Uint8ClampedArray(45*4); // fill buff with 0's getColoredPattern(0, 0, 0, 0, ledsCount); // same as above, faster way that doesn't re-allocate a clamped array //mouthsLedsArrays.push(getColoredPattern(255, 255, 255, i*9, i*9+9)); // push 9/45 of leds to our array ( uses buff internally ) - increasing //mouthsLedsArrays.push(getColoredPattern(255, 255, 255, i*9, i*9+9)); // push 9/45 of leds to our array ( uses buff internally ) - moving .. not ? :/ mouthsLedsArrays.push(getColoredPattern(255/9*i, 255/9*i, 255/9*i, i*9, i*9+9)); // push 9/45 of leds to our array ( uses buff internally ) } } // -- TIMING -- // TODO: get final timings ( absolute or relative ) // R: we have generated 5 mouths for our debug, each with 9 leds var absTimeMouthsIdxsArray = [ //[<ms_from_start>, <mouth_idx>] [37, 0], [38, 1], [40, 2], [45, 3], [45.2, 4], [45.5, 3], [45.7, 2], [45.8, 1], [45.9, 0], [45.95, 1], [45.99, 2], [46, 3], [50, 4] ]; // R: to map abolute times to relative timing in ms: relTime = (absTime * 1000) - (1stAbsTime * 1000) var reTimeMouthsIdxsArray = []; for(var i=0; i< absTimeMouthsIdxsArray.length; i++){ var relTime = (i === 0) ? ((absTimeMouthsIdxsArray[i][0] * 1000) - (absTimeMouthsIdxsArray[i][0] * 1000)) : (absTimeMouthsIdxsArray[i][0] * 1000) - (absTimeMouthsIdxsArray[i-1][0] * 1000); reTimeMouthsIdxsArray[i] = [relTime, absTimeMouthsIdxsArray[i][1]]; } // -- ANIMATION -- // transition between mouths function nxtMouth(){ if(currMouthAnimIdx+1 < reTimeMouthsIdxsArray.length){ // didn't reach end of array yet currMouthAnimIdx = currMouthAnimIdx+1; timr = setTimeout(function(){ currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; // useful for generated dummies console.log('currMouthIdx: ' +currMouthAnimIdx); //dotStar.write(getPattern(currMouth, 4));// update leds - we pass an array of paletted idx, rgb values or BrRGB values & a 'step' to indicate format getColoredPattern(0, 0, 0, 0, ledsCount); // clear buffer dotStar.write(getColoredPattern(0, 0, 100, 0, currMouthAnimIdx*4, true)); // debuuuuug // for final impl //dotStar.write(getPattern(currMouth, 1));// update leds - we pass an array of paletted idx, rgb values or BrRGB values & a 'step' to indicate format nxtMouth(); // schedule next call }, reTimeMouthsIdxsArray[currMouthAnimIdx][0]); } else { timr = -1; console.log('end of mouth timed array reached -> animation complete !'); resetState(); } } // -- BUTTON PRESS HANDLER: START/RESET -- function handleBtnPress(){ if(animating){ // currently animating: reset resetState(); } else { // currently stopped: animate animating = true; console.log('animating !'); nxtMouth(); // TODO: uncomment once we have final mouths arrays or generated dummys } } function resetState(){ console.log('resetting !'); if(timr !== -1){ clearTimeout(timr); timr = -1; } currMouthAnimIdx = 0; currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; dotStar.write(getColoredPattern(0, 0, 0, 0, ledsCount)); // clear animating = false; } // -- SETUP -- // - modules - var spi; var dotStar; // - pins - var btn = BTN1; var dotStarSck = B3; var dotStarMosi = B5; // - watches - var btnW; // - vars - var ledsCount = 45; var animating = false; var timr = -1; var currMouthAnimIdx = 0; // where we're at in the 'reTimeMouthsIdxsArray' //var currMouthIdx = 0; var currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; function onInit(){ // setup modules spi = SPI1; spi.setup({miso:B4, mosi:dotStarMosi, sck:dotStarSck, mode:0,order:"msb", baud: 4000000}); //spi.setup({mosi:dotStarMosi, sck:dotStarSck, mode:0,order:"msb", baud: 4000000}); dotStar = exports(spi); dotStar.write(getColoredPattern(0, 0, 0, 0, ledsCount)); // start with all leds fully off // setup button //pinMode(btn, 'input_pullup'); btnW = setWatch(function(e) { handleBtnPress(); }, btn, { repeat:true, edge:'rising', debounce: 100 }); // debug logs console.log('currMouthAnimIdx : ' + currMouthAnimIdx); console.log('currMouth : ' + currMouth); console.log('reTimeMouthsIdxsArray : ' + reTimeMouthsIdxsArray); console.log('reTimeMouthsIdxsArray.length : ' + reTimeMouthsIdxsArray.length); // generate dummies to test until we have final mouths generateDummies(5); // we pass how many we want // quick leds feedback checkLeds(1000); // we pass the delay between color change // feedback - fully booted indication digitalWrite(LED3, 1); }
-
Hi there !
back on this project ;p
-> I received the said charger/booster and 'll test the pullups pins stuff ;)
-> I also had answers from the Adafruit support team, as such:forget about voltage boosting and associated limitations and inefficiencies. You are better off powering both processor and pixels direct from your battery pack. We regularly run Neopixels and Dotstars directly from 3.7v LiPos
https://forums.adafruit.com/viewtopic.php?f=8&t=147139.
So, before trying the said pullup thing, I have to try powering ~40 leds fully bright from one ( or more in parallel ? ) 3.7V 18650 batteries & powering an original Espruino board ( maybe later a Pico but I no longer have any handy ; ) ), yet for programming/flashing/debug reasons, I wish to be able to keep the USB connection to the laptop.
I currently disconnect the battery jst connector when being connected using USB while the leds are getting power from the 3.7V battery & no problem has rosen yet, but I'm wondering how the board would react if I were to keep both USB and battery on JST connected ( the leds are getting power directly from the battery, not from the Bat pin, when usb is connected or not )
Also, it seems I couldn't talk to the board when putting a 1000uF 16V cap across the battery power source & Gnd ? ( the board was recognized, but I couldn't get a working serial comm, even when using an external ftdi adapter & trying on hardware serial ports .. ( espruino original ) .. may it come from CTS/DTR left unconnected ? I'll have to try using bluetooth .. )This being said, I'm onto modding my current code to try the said ~40 leds, hoping I can have them 'blinking in sync' & last longer at least a minute ..
ps: mode 0 seems to work reliably on my current setup, big thanks for the hint ;)
last but not least, would you advise using external pullups when running off 3.7V & controlling 3.7V-powered leds ( without opendrain since no more needed ? I wonder how to fasten the said "voltage swing" .. )
-> I'll see if I can adapt some "rainbow scrolleré onto those as a test ;p
-
Hi there !
'll be back on the subject later this week ( I hope :) ), although I've just added the "partial flip" we talked about aside a little benchmarking on the subject ( see http://forum.espruino.com/conversations/269808/#comment14607110)
-
-
hi there !
got it ;p
For my early tests, it seems the benefits of line by line VS 16 of them is ok unless area modified is bigger than LCD_WIDTH/2 ( or a little more ).
Also, I currently have no idea of how to optimize this using either inlineC or "compiled" :/ ( or just another way to do it in js )
I stumbled upon this topic, but not sure at all if & which concepts could be useful here ..
This being said, porting this to oled screens instead of colored ones may get quite handy for even faster updates :DAlso, as a side subject, any idea of a hack to allow stuff already on screen of color other than black ( for ex ) to be all set to a particular color ?
this being said, here's the code for partial flip :D
/* black + 1x16bit color instant refresh on ST7735S ( https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf ? ) LCD screen: 0.96" 80x160 RGB IPS 65K 4-wire SPI --> currently fixing & dev stuff on 1.44" 128x128 ;p */ // Backlight is always on var DCpin = A4; //B0; var CSpin = B0; var RESpin = B1; var SCKpin = A5; var MOSIpin = A7; require("Font8x12").add(Graphics); // -- modded module -- var exports = {}; var LCD_WIDTH = 128; var LCD_HEIGHT = 128; var colStart = 2; var rowStart = 3; function init(spi, dc, ce, rst, callback) { function cmd(c,d) { dc.reset(); spi.write(c, ce); if (d!==undefined) { dc.set(); spi.write(d, ce); } } if (rst) { digitalPulse(rst,0,10); } else { cmd(0x01); //Software reset } setTimeout(function() { cmd(0x11); //Exit Sleep setTimeout(function() { cmd(0x26, 0x04); //Set Default Gamma //cmd(0xF2, 0x00) //E0h & E1h Enable/Disable ( from ILI9163AN datasheet - Laibo1.8" / CMO1.8" ) cmd(0xB1, [0x0e,0x10]); //Set Frame Rate -> default //cmd(0xB1, [0x0C,0x14]); //Set Frame Rate ( from ILI9163AN datasheet - Laibo1.8" / CMO1.8" ) cmd(0xC0, [0x08,0]); //Set VRH1[4:0] & VC[2:0] for VCI1 & GVDD //cmd(0xC0, [0x0C,0x05]); // Set VRH1[4:0] & VC[2:0] for VCI1 & GVDD ( from ILI9163AN datasheet - Laibo1.8" / CMO1.8" ) cmd(0xC1, 0x05); //Set BT[2:0] for AVDD & VCL & VGH & VGL //cmd(0xC1, 0x02); //Set BT[2:0] for AVDD & VCL & VGH & VGL ( from ILI9163AN datasheet ) cmd(0xC5, [0x38,0x40]); //Set VMH[6:0] & VML[6:0] for VOMH & VCOML //cmd(0xC5, [0x32,0x3B]); //Set VMH[6:0] & VML[6:0] for VOMH & VCOML ( from ILI9163AN datasheet - Laibo1.8" ) ////cmd(0xC5, [0x29,0x43]); //Set VMH[6:0] & VML[6:0] for VOMH & VCOML ( from ILI9163AN datasheet - CMO1.8" ) //cmd(0xC7, 0x40); // ??? ( from ILI9163AN datasheet ) cmd(0x3a, 5); //Set Color Format, 5=16 bit,3=12 bit //cmd(0x36, 0xc8); //RGB - nope, on my screen its BRG -> produces artifacts cmd(0x36, 0xce); //RGB - nope, on my screen its BRG -> same, less artifacts cmd(0x2A,[0,0,0,LCD_WIDTH]); //Set Column Address R: digg if we can set the offset only here instead of within 3 fcns //cmd(0x2A,[0,0,0,0x7F]); //Set Column Address 0x7F = 127 ( from ILI9163AN datasheet ) cmd(0x2B,[0,0,0,LCD_HEIGHT]); //Set Page Address R: digg if we can set the offset only here instead of within 3 fcns //cmd(0x2B,[0,0,0,0x9F]); //Set Page Address 0x9F = 135 ( from ILI9163AN datasheet ) //cmd(0x36, 0xC0); // Set Scanning Direction ( from ILI9163AN datasheet - Laibo1.8" / CMO1.8" ) cmd(0xB4, 0); // display inversion //cmd(0xB7, 0x00); // Set Source Output Direction ( from ILI9163AN datasheet - CMO1.8" ) cmd(0xf2, 1); //Enable Gamma bit cmd(0xE0,[0x3f,0x22,0x20,0x30,0x29,0x0c,0x4e,0xb7,0x3c,0x19,0x22,0x1e,0x02,0x01,0x00]); cmd(0xE1,[0x00,0x1b,0x1f,0x0f,0x16,0x13,0x31,0x84,0x43,0x06,0x1d,0x21,0x3d,0x3e,0x3f]); cmd(0x29); // Display On cmd(0x2C); // reset frame ptr if (callback) callback(); },20); } ,100); } // modded 'connect()' to fix the offset & pixel artifacts exports.connect = function(spi, dc, ce, rst, callback) { var g = Graphics.createCallback(LCD_WIDTH, LCD_HEIGHT, 16, { setPixel:function(x,y,c){ ce.reset(); spi.write(0x2A,dc); //spi.write(0,x,0,x+1); // default spi.write(0,x+colStart,0,x+colStart+1); spi.write(0x2B,dc); //spi.write(0,y,0,y+1); // default spi.write(0,y+rowStart,0,y+rowStart+1); spi.write(0x2C,dc); spi.write(c>>8,c); ce.set(); }, fillRect:function(x1,y1,x2,y2,c){ ce.reset(); spi.write(0x2A,dc); //spi.write(0,x1,0,x2); // default spi.write(0,x1+colStart,0,x2+colStart); spi.write(0x2B,dc); //spi.write(0,y1,0,y2); // default spi.write(0,y1+rowStart,0,y2+rowStart); spi.write(0x2C,dc); //spi.write({data:String.fromCharCode(c>>8,c), count:(x2-x1+1)*(y2-y1+1)}); // default spi.write({data:String.fromCharCode(c>>8,c), count:((x2+colStart)-(x1+colStart)+1)*((y2+rowStart)-(y1+rowStart)+1)}); // USELESS ?! -> test with above ce.set(); } }); // quick & hacky fix g.flip = function() { /*prevents error during my quick tests*/ }; init(spi, dc, ce, rst, callback); return g; }; // modded 'connectPaletted()' to fix 'italic" text exports.connectPaletted = function(palette, spi, dc, ce, rst, callback) { //var bits = 8; /**/ var bits; if (palette.length>16) bits=8; else if (palette.length>4) bits=4; else if (palette.length>2) bits=2; else bits=1; /**/ var g = Graphics.createArrayBuffer(LCD_WIDTH, LCD_HEIGHT, bits, { msb:true, vertical_byte: false }); g.flip = function() { ce.reset(); spi.write(0x2A,dc); //spi.write(0,0,0,LCD_WIDTH); // default - otherwise, we had spi.write(0,x+colStart,0,x+colStart+1); //spi.write(0,2,0,LCD_WIDTH); // thx to @Jorgen who inspired this - italic but other orientation spi.write(0,1,0,LCD_WIDTH); // WORKS: made the trick for non-italic text drawn at once ! :D spi.write(0x2B,dc); spi.write(0,1,0,LCD_HEIGHT); // otherwise, we had spi.write(0,y+rowStart,0,y+rowStart+1); //spi.write(0,3,0,LCD_HEIGHT); // thx to @Jorgen who inspired this - italic but other orientation spi.write(0x2C,dc); var lines = 16; // size of buffer to use for un-paletting var a = new Uint16Array(LCD_WIDTH*lines); // default for (var y=0;y<LCD_HEIGHT;y+=lines) { // default E.mapInPlace(new Uint8Array(g.buffer, y*LCD_WIDTH*bits/8, a.length), a, palette, bits); // default spi.write(a.buffer); } ce.set(); }; /* ---- HUGE WIP: PARTIAL FLIP USING GETMODIFIED ---- */ // wip usage: g.flipP(g.getModified()) g.flipP = function(options) { // check if modified //var tmp = g.getModified(); var tmp = g.getModified(true); // check & clear for update if so if(typeof tmp === "undefined"){ // nothing to update: force ? console.log('nothing to flip'); } else { console.log('flipping modified stuff ..'); console.log(tmp); if(typeof options === "undefined") options = {}; var y1 = options.y1 || tmp.y1 || 0; var y2 = options.y2 || tmp.y2 || LCD_HEIGHT; var x1 = options.x1 || tmp.x1 || 0; var x2 = options.x2 || tmp.x2 || LCD_WIDTH; // -- TMP OVERRIDE -- //x1 = 0; //x2= 11; //x2 = LCD_WIDTH; // => COMMENT OUT FOR PARTIAL FLIP WITHOUT LINE CHUNKS //x2 = LCD_WIDTH/4; //x2 = LCD_WIDTH/2; var twidth = x2-x1; var theight = y2-y1; /* var tpxNum = twidth*theight; //var tbits = tpxNum*16/5; // may not be always tru ? ( found while trying to make sense of debug pokes values ..) //var tbits = tpxNum*bits/8; //same as tpxNum*16/8; var tbits = tpxNum*bits; //var tbits2 = tpxNum*(16-theight); var tbytes = tbits/8; console.log('area width: '+twidth+'\nheight: '+theight+'\npixels count: '+tpxNum+'\nbits / bytes: '+tbits+' / ' +tbytes); //+'\ntbits2: '+tbits2); */ ce.reset(); spi.write(0x2A,dc); //spi.write(0,x1+1,0,(x2-x1)); //spi.write(0,x1+1+colStart,0,(x2-x1)+colStart); //spi.write(0,x1+1+colStart,0,twidth+colStart); spi.write(0,x1+1+colStart,0,twidth+colStart); spi.write(0x2B,dc); //spi.write(0,y1+1,-2,(y2-y1)+1); spi.write(0,y1+rowStart,0,theight+rowStart); spi.write(0x2C,dc); var lines = 1; // gives us 19 loops for the two squares to be drawn ( these totalize 20 in height ) & 256 bits buffer //var lines = 16; // => COMMENT OUT FOR PARTIAL FLIP WITHOUT LINE CHUNKS //var lines = 10; // => COMMENT OUT FOR PARTIAL FLIP WITHOUT LINE CHUNKS var a = new Uint16Array(twidth*lines); // debug bits counter //var bitsCntr = 0; for (var y=0;y<theight;y+=lines) { // shall stay as is !! //E.mapInPlace(new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8, a.length), a, palette, bits); E.mapInPlace(new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8+ x1*bits/8, a.length), a, palette, bits); // WORKS WITH lines=1 spi.write(a.buffer); //bitsCntr += a.buffer.length; //console.log('y = '+y+' -> bitsCntr + ' + a.buffer.length + " = " + bitsCntr); } // ---- trying to optimize stuff via "compiled" ---- /* //loopSendBufferChunks(a, x1, y1, theight, bits); // trying to get faster using "compiled" helper to loop over n lines, map & send over spi for (var y=0;y<theight;y+=lines) { // shall stay as is !! //E.mapInPlace(new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8, a.length), a, palette, bits); var arr = new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8+ x1*bits/8, a.length); mapAndSend(arr, a, bits, palette); // no gain whatsoever :/ .. //bitsCntr += a.buffer.length; //console.log('y = '+y+' -> bitsCntr + ' + a.buffer.length + " = " + bitsCntr); } */ ce.set(); //console.log('bits written ' + bitsCntr); } }; init(spi, dc, ce, rst, callback); return g; }; // compiled js helper to decrease transfer time ? function mapAndSend(arr, a, bits, palette){ "compiled"; E.mapInPlace(arr, a, palette, bits); // WORKS WITH lines=1 spi.write(a.buffer); } function createNewArr(offset, length){ return new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8+ x1*bits/8, a.length); } function loopSendBufferChunks(a, x1, y1, height, bits){ "compiled"; var sp = spi; // assign to local variable so no name lookup on each call for (var y=0;y<height;y+=lines) { //E.mapInPlace(new Uint8Array(g.buffer, y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8+ x1*bits/8, a.length), a, palette, bits); var arr = createNewArr(y1*LCD_WIDTH*bits/8 + y*LCD_WIDTH*bits/8+ x1*bits/8, a.length); // create new array externally to "compiled" code ? E.mapInPlace(arr, a, palette, bits); spi.write(a.buffer); } } // ---- test code ---- var spi = new SPI(); //spi.setup({mosi:MOSIpin /* sda */, sck:SCKpin /* scl */, baud: 1000000}); spi.setup({mosi:MOSIpin /* sda */, sck:SCKpin /* scl */, baud: 16000000}); //spi.setup({mosi:MOSIpin /* sda */, sck:SCKpin /* scl */, baud: 100000}); //spi.setup({mosi:MOSIpin /* sda */, sck:SCKpin /* scl */}); //var colorPalette = new Uint16Array([0, 0xf8, 0xe007, 0x1f00]); // black, red, green ,blue, finally ? ... YES !! // the following helper does the RGB888 -> GBRG565 mapping trick // wip fix: remapping the RGB565 as // GGGBBBBBRRRRRGGG: ( weird 'GBRG' ?) // G3,G2,G1, B5,B4,B4,B2,B1, R5,R4,R3,R2,R1, G6,G5,G4 // LSB MSB LSB MSB LSB MSB function rgb888To565t2(rgb888){ return ((rgb888 & 0x1c00) << 3)|((rgb888 & 0xf8) << 5)|((rgb888 & 0xf80000) >> 16)|((rgb888 & 0xe000) >> 13); } var colMap = rgb888To565t2; var colorPalette = new Uint16Array([0, colMap(0xff0000), colMap(0x00ff00), colMap(0x0000ff)]); // black, red, green ,blue, finally ? ... YES !! //var g = exports.connect(spi, DCpin, CSpin, RESpin, function() { // works fine var g = exports.connectPaletted(colorPalette, spi, DCpin, CSpin, RESpin, function() { g.clear(); g.flip(); // needed when using 'paletted' mode g.setFont8x12(); // perfect size for 128x128 :) g.setColor(1); g.drawString("should be red !", 2, 2); g.setColor(2); g.drawString("should be green !", 2, 20); g.setColor(3); g.drawString("should be blue !", 2, 40); g.flip(); // needed when using 'paletted' mode // from now, assume that nothing new was drawn g.setColor(3); //g.fillRect(0,10, 10, 20); g.fillRect(2,10, 12, 20); g.flip(); g.getModified(true); g.flipP(); // 'll try clearing modified & flipping stuff if found anything -> should say 'nothing to update' setTimeout(function(){ g.setColor(1); //g.setColor(1); // modifies stuff, even if its blanking it .. //g.fillRect(0,0, 10, 10); //testing y on flipP //g.fillRect(0,50, 10, 60); //testing x on flipP //g.setColor(2); //g.fillRect(10,50+10, 20, 60+10); // the +10 forces a second loop (20 > 16, the un-paletting buffer size ) //g.fillRect(10+26,50+6, 20+26, 60+6); // benchmarking partial flip with lines chunks VS partial flip, to know when one becomes faster than the other ( both should be faster than flip ;) ) //g.fillRect(0,50, LCD_WIDTH/4, 60); // 32px for 128px screen: 57ms for 'line chunk' version, 179.252ms for 16lines pflip //g.fillRect(0,50, LCD_WIDTH/3, 60); // 32px for 128px screen: 57ms for 'line chunk' version, 179.295ms for 16lines pflip, 113.227ms for 10lines pflip //g.fillRect(0,50, LCD_WIDTH/2, 60); // 32px for 128px screen: 83.032ms for 'line chunk' version, 113.348ms for 10lines pflip g.fillRect(0,50, LCD_WIDTH-1, 60); // 32px for 128px screen: 133.300ms for 'line chunk' version, 113.680ms for 10lines pflip //g.drawString("HELLO FLIP-P", 2, 30); //g.flip(); var bFlipT = getTime(); g.flipP(); // 'll try clearing modified & flipping stuff if found anything -> should update stuff CORRECTLY ? .. var aFlipT = getTime(); console.log('partial flip took ' + (aFlipT-bFlipT)*1000+ 'ms'); //console.log('partial flip took ' + (aFlipT-bFlipT)+ 's'); },2000); });
-
Hi there !
I had a quick take on getting that partial flip thing to work, but for a reason I can't figure out ( I'm either too dumb, tired, or both ;p ) I can't make it work for only a vertical portion of the screen ( smaller than LCD_WIDTH )
any quick hint on this ?
( --> currently fixing & dev stuff on 1.44" 128x128 ;p )// working version // .. spi.write(0x2A,dc); spi.write(0,1,0,LCD_WIDTH); // .. spi.write(0x2B,dc); spi.write(0,y1+1,-2,(y2-y1)+1); // .. var lines = 16; // size of buffer to use for un-paletting var a = new Uint16Array(LCD_WIDTHlines); for (var y=0;y<y2-y1;y+=lines) { E.mapInPlace(new Uint8Array(g.buffer, y1LCD_WIDTHbits/8 + yLCD_WIDTHbits/8, a.length), a, palette, bits); //works whatever x & y !!! spi.write(a.buffer); } // not working version :frowning: .. // .. spi.write(0x2A,dc); spi.write(0,x1+1,0,(x2-x1)); // .. spi.write(0x2B,dc); spi.write(0,y1+1,-2,(y2-y1)+1); // .. var lines = 16; // size of buffer to use for un-paletting var a = new Uint16Array((x2-x1)lines); for (var y=0;y<y2-y1;y+=lines) { E.mapInPlace(new Uint8Array(g.buffer, y1LCD_WIDTHbits/8 + yLCD_WIDTHbits/8+ x1*bits/8, a.length), a, palette, bits); // wip ... FIX THIS !! :| spi.write(a.buffer); }
Also, I've been able to figure out that my screen seems to accept either RBG or BRG, not RGB, and thanks to the following link /code, here's a quick & handy remapper for those in need ;)
// txh to http://www.barth-dev.de/online/rgb565-color-picker/ function rgb888To565(rgb888){ //return ( (((rgb888&0xf80000)>>8) + ((rgb888&0xfc00)>>5) + ((rgb888&0xf8)>>3)) ).toString(16); return ( (((rgb888&0xf80000)>>8) | ((rgb888&0xfc00)>>5) | ((rgb888&0xf8)>>3)) ).toString(16); } // wip fix: remapping the RGB565 as // GGGBBBBBRRRRRGGG: ( weird 'GBRG' ?) // G3,G2,G1, B5,B4,B4,B2,B1, R5,R4,R3,R2,R1, G6,G5,G4 // LSB MSB LSB MSB LSB MSB function rgb888To565t2(rgb888){ return ((rgb888 & 0x1c00) << 3)|((rgb888 & 0xf8) << 5)|((rgb888 & 0xf80000) >> 16)|((rgb888 & 0xe000) >> 13); }
-> I hope I'll be able to draw things faster in "unicolor" that way ;p ( one of the goals is to be able to draw in 2 colors, one black & the other dynamically changing )
-
So !
here's the "delayed power off" thing, but I'm wondering what'd be the best alternative to the D1 diode ( to lessen any voltage drop ), aside from that, I guess/hope "correct" resistor value + those 2 transistors 'll do the trickNow, to be able to use the above circuit, I'll need 2 pins .. so I better go trying the "shared SPI pins" before planning on doing so ;p
-
Hi
I just tried with different "small" caps ( 1pF, 100nF, 1uF ), yet it seems I don't get any changes in the output ( as without, *3.3, I get "X.YZnnnn" where X & Y are constant, Z seems to be +/- 1 & n's are not constant whatsoever .. )
-> I'm currently finishing the "delayed power off" thing, posting it right after it's done & then onto testing those SPI sharedp ins hacks ;)
-
Hi there,
1: I'm currently using B3 & B5 ( yup .. ), but I guess I'll switch to B13 & B15 or just use software SPI instead + 'opendrain'. I'll try the long wire + 1.5k pullup to 3.3V ( on both datat & clk or just one iyo ? ) - I can borrow an oscilloscope ( .. )
2: understood for the "no that much gain" ;)
3: yup for the logs ;p, and I'll try getTime benchmarking as you advise. Also I'll avoid compiled stuff for this particular goal ;)
4: I 'll manage to get myself a scope & learn its many tricks, yet as far as I can tell, the following seemd to work ( even without 'opendrain' -> anyway, I'll go for safety with your above suggestions )
// Nb: I have to digg SPI modes .. // Nb2: the LEDs seemed to respond even when passing "8000000" as baud ? .. //spi.setup({miso:B4, mosi:B5, sck:B3, mode:1,order:"msb", baud: 4000000}); // seems ok for software spi & hardware spi spi.setup({miso:B4, mosi:B5, sck:B3, mode:0,order:"msb", baud: 4000000}); // seems ok for software spi - mode 0||3, 1||2 ? // for SPI modes on APA102: https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/ // for SPI modes in general ? // R: mode "1" was the 1st tested, seemed to work // mode "0" seems to also work ?
-
@Robin thx ! -> same conclusion for the 4 args max, & thks for the 333 us /30 ms reminder ;)
- yes, I could actually pass those as arrays no troubles
- got it for the 1ms js execution limit
- ok, I'll try to find alternatives to the creation of structs/array in my current code
- for the "all in ints" part, I tested using ints but I intended to ultimately use floats ( ex: for the bezierCurve() fcn ), but I guess it could be done with "* tricks"
-> I'll have another take on this as soon as I make further progress in some other projects ;)
thanks a lot :)
+ - yes, I could actually pass those as arrays no troubles
-
-
-
Hi there !
I hadn't really considered that, but yes, that would work great.
Actually, I thought about it when reading your suggestion ;p
For the SD card you have a chip select pin so that might give you something you can use as well?
Indeed: it also gives me a ( silly ? ) idea: using the SDCARD SPI CS pin as the pin on which to set the APA102 SPI CLK () so that the APA102 CLK 'll be tied low while the SD card is in use ? ( so by writing to the SD, it shall auto-disable the APA102 comm ? I guess it'd be even better than tying the SD CS pin to Gnd & then doing the above 'different pin for the clock signal' hack ? )
-> I gotta try those tomorrow ;)Onto the "2pins analog setWatch" hack, I had some success with a quite simple circuit in which the A0 pin ( on which a digital setWatch() is set ) is connected directly to the A1 pin, used as analog input.
An external 10k pulldown & setting it as "analog" did the trick on giving me slightly more meaningfull results :)
In order to get reliable results ( and this, without an added transitor as I first planned ), the least voltage I get is 2.09V ( so some safety from 1.70 ;p )
I didn't try ( yet ? ) adding a "small" ( 1 uF ? ) cap - how would it "smooth" the result ?put the battery charge status and/or toggle switch on exactly the same A0 pin as well. If your 5 voltages are 2v,2.25v,2.5v,2.75v and 3v then if the toggle switch varied the voltage by 0.05v you could detect that.
I'm not sure yet if my setup is precise enought & on how to implement that ( for either the batt. status pin, the toggle, or both ? .. - I plan to use the MCP73831 's stat pin as battery status pin, for info .. )
Now, the voltages I currently get ( from the resistor values I could find around easily ) are the ones in the following code, I'm currently happy with those ( seems to work flawlessly ), but if anyone has hints on better resistor values, I'm a taker ;)
nb: I'm not quite sure of the followings ( gotten using successive tries on tinkercad .. ), but these may be the resistor values you had in mind to produe your proposed voltages ?
( all the followings uses a 10k pulldown as R1 )
3.3Vin-> 3Vout => R2: 1315 Ohms
3.3Vin-> 2.75Vout => R2: 2350 Ohms
3.3Vin-> 2.5Vout => R2: 3600 Ohms
3.3Vin-> 2.25Vout => R2: 5100 Ohms
3.3Vin-> 2.00Vout => R2: 7000 OhmsSo, here's some wip working code :)
// trying to get this 5-way switch from 5 pins down to 1 digital // ( for setWatch() ) & one analog ( to read the five different values through different resistors ) // pin 0 has the digital watch on it pinMode(A0, "input_pulldown"); //pinMode(A1, "input"); // has external 10k pulldown resistor pinMode(A1, "analog"); // has external 10k pulldown resistor -> yup, I am DUMB ! /* setWatch(function(e) { console.log('A0 pressed'); console.log('A1 biggest is: ' + analogReadBiggest(A1, 1000)); console.log('A1 lowest is: ' + analogReadLowest(A1, 1000)); }, A0, { repeat:true, edge:'rising', debounce:100 }); */ // wip 'analog2pinWatch' function setWatchA(digitalPin, analogPin, callbackFcn){ setWatch(function(e) { callbackFcn(analogRead(analogPin)*3.3); }, digitalPin, { repeat:true, edge:'rising', debounce:100 }); } // usage: setWatchA(A0, A1, function(buttonAnalogVal){ console.log('button on A0 pressed: analog read =' + buttonAnalogVal); if(buttonAnalogVal > 3.15) console.log('btn1'); else if(buttonAnalogVal > 2.85) console.log('btn2'); else if(buttonAnalogVal > 2.50) console.log('btn3'); else if(buttonAnalogVal > 2.30) console.log('btn4'); else if(buttonAnalogVal > 2.00) console.log('btn5'); //else if(buttonAnalogVal > 1.70) console.log('btn6'); // N/A for 5-way switch .. }); // for a 10k pulldown & pinMode(A1, "analog"), we get the below outputs // 1: // 10k & nothing ( 500 readings using analogReadBiggest) -> 0.99708552681 | *3.3 = 3.29038223847 // 10k & nothing ( 500 readings using analogReadLowest) -> 0.99781795986 | *3.3 = 3.29924467841 // 2: // 10k & 1k ( 500 readings using analogReadBiggest) -> 0.90626382848 | *3.3 = 2.99067063398 // 10k & 1k ( 500 readings using analogReadLowest) -> 0.90553139543 | *3.3 = 2.98825360491 // 3: // 10k & 2.2k ( 500 readings using analogReadBiggest) -> 0.82032501716 | *3.3 = 2.70707255662 // 10k & 2.2k ( 500 readings using analogReadLowest) -> 0.82008087281 | *3.3 = 2.70626688027 // 4: // 10k & 3.2k ( 500 readings using analogReadBiggest) -> 0.75660334172 | *3.3 = 2.49679102767 // 10k & 3.2k ( 500 readings using analogReadLowest) -> 0.75611505302 | *3.3 = 2.49517967496 // 5: // 10k & 4.7k ( 500 readings using analogReadBiggest) -> 0.67969787136 | *3.3 = 2.24300297548 // 10k & 4.7k ( 500 readings using analogReadLowest) -> 0.67896543831 | *3.3 = 2.24058594642 // 6: // 10k & 5.7k ( 500 readings using analogReadBiggest) -> 0.63526359960 | *3.3 = 2.09636987868 // 10k & 5.7k ( 500 readings using analogReadLowest) -> 0.63648432135 | *3.3 = 2.10039826045 // 7: // 10k & 10k ( 500 readings using analogReadBiggest) -> 0.50074006256 | *3.3 = 1.65244220644 // 10k & 10k ( 500 readings using analogReadLowest) -> 0.50074006256 | *3.3 = 1.65244220644 // R: currently testing with A0 wire going directly to A1 'line' ( that is, without any transistor ) // when helding a button pressed: // 1: // A1 biggest is: 0.99977111467 // A1 lowest is: 0.99977111467 // 2: // A1 biggest is: 0.88697642481 // A1 lowest is: 0.88966201266 // 3: // A1 biggest is: 0.78223849851 // A1 lowest is: 0.78590066376 // 4: // A1 biggest is: 0.71729610131 // A1 lowest is: 0.71705195696 // 5: // A1 biggest is: 0.63062485694 // A1 lowest is: 0.62940413519
This being said, I'm onto drawing a little "delayed power off" circuit that may be useful to get on one ( not shared ) pin as well ;)
Thanks for the precious hints :)
+ -
I just tested something like http://www.reuk.co.uk/wordpress/wp-content/uploads/2013/09/selector-switch-resistor-ladder-adc.jpg, but:
- 1: those resistor values don't get me "good" results for some reason when reading analog from A1
- 2: I'm not sure wiring a transistor in series with a 10k resistor with Collector on +3.3V & Emitter on A0 'll effectively give me ( for correct resistor value for 1 ) sort of an 'analogWatch' via 2 pins ?
I'm using the following code for my tests:
// pin 0 has the digital watch on it - we may change the pinMode if needed pinMode(A0, "input_pulldown"); setWatch(function(e) { console.log('A0 pressed'); }, A0, { repeat:true, edge:'rising', debounce:100 }); // first thing I tried ;) var N = 20; var voltage = 0; for (var i=0;i<N;i++) { voltage += analogRead(A1)*3.3/N; } console.log('voltage: ' + voltage); // when reading, get the biggest val (mod from @MaBe's "analogReadMedian()" ) analogReadBiggest = function(p, sampleCount) { var i, median; var samples = Math.floor(sampleCount); var analogValues = new Array(samples); // read analog values into array i = samples; while(i--) analogValues[i] = analogRead(p); //sort array, smalest first and largest last analogValues.sort(function(a, b){return a-b;}); return analogValues[analogValues.length-1]; // return biggest };
I guess I'll have to be more cautious on the length of the wires used between uC & the resistors to have something ~constant in this circuit, hoping the erroneous readings come from that ..
---- update ----
My last try for today, unsuccessful :/// trying to get this 5-way switch from 5 pins down to 1 digital // ( for setWatch() ) & one analog ( to read the five different values through different resistors ) // pin 0 has the digital watch on it pinMode(A0, "input_pulldown"); setWatch(function(e) { console.log('A0 pressed'); console.log('A1 biggest is: ' + analogReadBiggest(A1, 1000)); console.log('A1 lowest is: ' + analogReadLowest(A1, 1000)); }, A0, { repeat:true, edge:'rising', debounce:100 }); pinMode(A1, "input"); // has external 10k pulldown resistor // pin A1 has a 10k pulldown & various pullup resistors depending on the "path travelled" for 5-way switch // to get the biggest val analogReadBiggest = function(p, sampleCount) { var i; var samples = Math.floor(sampleCount); var analogValues = new Array(samples); // read analog values into array i = samples; while(i--) analogValues[i] = analogRead(p); return analogValues.sort(function(a, b){return a-b;})[analogValues.length-1]; // return biggest }; analogReadLowest = function(p, sampleCount) { var i; var samples = Math.floor(sampleCount); var analogValues = new Array(samples); // read analog values into array i = samples; while(i--) analogValues[i] = analogRead(p); return analogValues.sort(function(a, b){return a-b;})[0]; // return lowest }; // for a 10k pulldown, we get the below ( erratic ) outputs // 1: // 10k & nothing ( 500 readings using analogReadBiggest) -> 0.00537117570 // 10k & nothing ( 500 readings using analogReadLowest) -> 0.00512703135 // 2: // 10k & 10k ( 500 readings using analogReadBiggest) -> 0.00537117570 // 10k & 10k ( 500 readings using analogReadLowest) -> 0.00561532005 // 3: // 10k & 2.2k ( 500 readings using analogReadBiggest) -> 0.00512703135 // 10k & 2.2k ( 500 readings using analogReadLowest) -> 0.00512703135 // 4: // 10k & 3.2k ( 500 readings using analogReadBiggest) -> 0.00537117570 // 10k & 3.2k ( 500 readings using analogReadLowest) -> 0.00537117570 // 5: // 10k & 5.7k ( 500 readings using analogReadBiggest) -> 0.00512703135 // 10k & 5.7k ( 500 readings using analogReadLowest) -> 0.00512703135
Hoping to have a clearer view of the troubles in the morning ;)
++ - 1: those resistor values don't get me "good" results for some reason when reading analog from A1
-
All right, I'll have to test it & come back with the results ;)
If I understand you correctly, your suggestions ( software spi or pin swapping ) won't help reduce the number of pins used, right ?
Or by 'pins swapping', do you mean declaring software or hardware spi/i2c but with a different pin for the clock signal so as to only have the device(s) with this clock pin connected to actually received data ?
See the attached image for the list of pins I need to connect everything to the Espruino Wifi
To "optimize" the pins mapping ( and at least get below 21 ;) ), I was thinking of a way to free 3 pins while still allowing the use of 'setWatch()' for the 5-way switch, but this may or not work at all depending on if I can manage to get just the right resistor values AND have the transistor go saturated for every button ( maybe by wiring it's base to each button output, not only the last one ? .. )
-> see the following link on this subject ( if one's kind enough to get "correct" values, I'd be happy to return the favor ;p ) https://www.tinkercad.com/things/9egZEDSHPMv-exquisite-crift-inari/editel?tenant=circuits?sharecode=Srm7jaS-vy22K2qTF8z6q6bGo-_pqyLyWWvrb9ZA5SY=I'm thinking of another way to doing so but didn't take the time to write them down or have a try on them ( .. )
Can wait to have everything fit on the EspruinoWifi pins in one way or another & hope my program fits in the memory before finally trying this project .. finger's crossed :)
' be posting updates on the spi/i2c APA thing & the 5-way switch as soon as can do ;)
-
Hi !
To increase the number of available pins on the Espruino Wifi ofr a current project, I wish to share the I2C & SPI bus for several peripherals:
On the I2C side:
- OLED screen ( addr 0x3C )
- Color sensor ( addr 0x29 )
On the SPI side: - APA102
- SDCARD
I had a quick thnk on 'may-be solutions', but I'd be glad receiving further advices before not knowingly messing around
The following drawing is for the SPI side of things, but I wonder if fiddling with the I2C CLK would lead to same behavior ?
Thanks in advance :)
++
- OLED screen ( addr 0x3C )
-
Hi there !
I'm currently looking for ways to optimise the way curves are drawn on an OLED screen ( & later rounded polygons ;) ).
By lessenning the number of "steps" used for a curve, I'm getting close to "instantaneous refresh", but I bet there's some way to use "compiled" flag to get even faster calls ( which 'll get handy when drawing more than one curve, for example two rounded polys ).
I tried rewriting the following functions, but always ended up with either an error ( "ObjectExpression is not implemented yet" or "ArrayExpression is not implemented yet" ).
When trying "E.compiledC()", it seems there's a limit on the maximum number of args for a function ( 4 arguments if I got it right from the below test code, if more I got a "Error Parsing signature at argument number " )Any hint on getting this as fast as can be ? ( I plan to using the above two "tricks" - "compiled" & "E.compiledC" - and also to later drive some strips of APA102 in a POV manner, and this before moving onto creating a "firmware Extension" ( .. ) )
Thanks in advance ! ;)
Curves helpers
function bezierCurve(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y){ var t2 = t * t; var t3 = t2 * t; var oneMinT = 1 - t; var oneMinT2 = oneMinT * oneMinT; var oneMinT3 = oneMinT2 * oneMinT; return { x: p0x * oneMinT3 + 3 * p1x * t * oneMinT2 + 3 * p2x * t2 * oneMinT + p3x * t3, y: p0y * oneMinT3 + 3 * p1y * t * oneMinT2 + 3 * p2y * t2 * oneMinT + p3y * t3 }; } function quadraticCurve(t, p0x, p0y, p1x, p1y, p2x, p2y){ var t2 = t * t; var oneMinT = 1 - t; var oneMinT2 = oneMinT * oneMinT; return { x: p0x * oneMinT2 + 2 * p1x * t * oneMinT + p2x *t2, y: p0y * oneMinT2 + 2 * p1y * t * oneMinT + p2y *t2 }; }
no-that-fast curve drawing
function drawCurve(){ var p0 = { x: 20, y: 10}; var p1 = { x: 20, y: 63}; var p2 = { x: 127, y: 10}; var time = 0; //var stepping = 0.005; // seems the nicest //var stepping = 0.05; // a little less neat, yet faster var stepping = 0.1; // quick enough ? var pathPts = []; for(time = 0; time <= 1; time+= stepping){ var pos = quadraticCurve(time, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y); pathPts.push(pos.x, pos.y); } g.drawPoly(pathPts, false); g.flip(); }
Max 4 arguments limit ? ( and yes, I put 'int' instead of 'float' to test out .. )
var c = E.compiledC(` // int quadraticCurveCX(int, int , int ,int) int quadraticCurveCX(int t, int p0x, int p0y, int p1x){ // maximum 4 args .. //int t2 = t*t; //int oneMinT = 1-t; //int oneMinT2 = oneMinT * oneMinT; return 0; //p0x * oneMinT2 + 2 * p1x * t * oneMinT + p2x *t2; } `);
Chunks of code from last unsuccessful try
function quadraticCurveCY(t, p0x, p0y, p1x, p1y, p2x, p2y){ //"compiled"; var t2 = t*t; var oneMinT = 1-t; var oneMinT2 = oneMinT * oneMinT; //return p0x * oneMinT2 + 2 * p1x * t * oneMinT + p2x *t2; return p0y * oneMinT2 + 2 * p1y * t * oneMinT + p2y *t2; } function getCurvePts(){ //"compiled"; var p0x = 20, p0y = 10; var p1x = 20, p1y = 63; var p2x = 127, p2y =10; var time = 0; var stepping = 0.005; // seems the nicest var pathPts = []; for(time = 0; time <= 1; time+= stepping){ //var pos = quadraticCurveC(time, p0x, p0y, p1x, p1y, p2x, p2y); //pathPts.push(pos[0], pos[0]); //var posX, posY; //var pos = quadraticCurveC(time, p0x, p0y, p1x, p1y, p2x, p2y, posX, posY); //pathPts.push(posX, posY); //var posX = c.quadraticCurveCX(time, p0x, p0y, p1x, p1y, p2x, p2y); } return pathPts; } function drawCurve(){ g.drawPoly(getCurvePts(), false); g.flip(); }
I see if I can find time to learn & try writing my own C extensions today ( right after some test on OLED screen SSD1306: it seems passing a bitrate of 800000 works, but I'm not sure if the difference 'll be visible since I'm not sure of the "resolution*" of 'setInterval/setTimeout' on Espruino).
*Putting it another way, does Espruino handle <1ms as setInterval/setTimeout arg ?
( & If so, how to get such small delay in a reliable manner ? )This being said, good weekend everyone ;)
++ -
Hi there !
If you ever heard about 'Cozmo', it's a tiny robot that does stuff ( & that seems quite capable seeing what it has embedded for doing so) & that costs $$$ ( 200 if I'm right ).
A while ago, I've stumbled across an instructable project ( gotta retrieve the link .. ) taking on the 'movement' side of things of a little robot, but that only used images to display few faces inspired from Cozmo's.
Having some fun with OLED screens & Espruino, It seemed a good idea to try getting those cute expressions onto our favorite uC :)
I started with 2 rectangles, one for each eye
As images/sprites: heavy for Espruino's ( but can't fit all of them on esp8266's )
As objects with 2 eye rectangles with relative coords for a center point, a little heavy since we have many expressions, but maybe doable if storing the expressions in memory & retrieving them later on ?I'm currently able to display each of the facial expressions ok, so the next steps are:
- animating the 'center point' position, to have both eyes move around the screen
- tweening the transition from a facial expressions rectangle's coords to another ( to get a 'morphing ' eyes shapes animation )
- getting round corners for those rectangles to achieve a nice smooth look
- being able to change the one color used for the onscreen shapes ( to still get 'instant' refresh but express 'emotion' through color as well as facial expressions & their roundness )
- being able to have one or two additional colors relative to the current one ( little brighter & little darker ), to get even smoother using a tiny color gradient ( more advanced gradient & even 'spotlight'/'shades' in a corner of the eye(s) 'd be awesome, but where's not quite there yet I guess .. )
What we currently have:
- POC code in html5 canvas & Espruino for animating a pixel's movement across the screen( very basic unoptimized tweening )
- POC code to display facial expressions in html5 canvas ( allows to keep all of them & get quicker debug for drawings )
- POC code to 'emulate' needed features of Espruino's Graphics context via html5 canvas ( very early stage, but helps to later copy/paste code to Espruino directly )
- POC code to draw curves on html5 canvas, not yet tested on Espruino
If anyone's adventurous/skilled/interested/free-timed enough, I'll be more than happy to update the project & the ~'lib/api' part & fork advances in the facial expressions part as well :)
Also, it crossed my mind to have a 'fake' screen ( using html5 canvas running from local server & communicating via serial connection to get updates ), but this 'd prevent being able to use the Espruino IDE at the same time ( or someone has a neat trick to forward serial comm to Espruino IDE while connected to other server itself talking web sockets with a webpage ? ), hence the idea of an html5 canvas embed within Espruino IDE could make a lot of sense there ( as well as for other stuff than a fake screen, ex: simulating a board, attached components .. oops, it's getting bigger & monstrous, sorry ;p -> but 'plugins' acting as fake connected devices may make sense for quick testing ? )
Since the code is quite big, I only post links to the two files for now:
- the facial expressions exported via Illustrator script & tweeting tests as well as console pixel preview: https://github.com/stephaneAG/Espruino-Cozmo/blob/master/expressionsTests.md
- round corners tests, to be added to code using Graphics once I'm sure of the best way to do so ( goal is to directly use the custom illustrator export script to get the curves right as we did for the rectangles ): https://github.com/stephaneAG/Espruino-Cozmo/blob/master/roundCornersTests.md
and the direct jsbins:
- expressions: https://jsbin.com/deritexoya/edit?html,css,js,console,output
-rounded corners: https://jsbin.com/kovihocugi/edit?html,css,js,console,output
on the attached images: the gib one is one of the original Cozmo expression ( a screenshot of the robot's screen ), while the second is a quick save of a vector ( .ai ), and the goal is to reproduce & morph between those on an OLED with Espruino :)
- animating the 'center point' position, to have both eyes move around the screen
-
.. and the code that tries ( in a very dumb manner currently ) to find the shortest path between all dots ( it 'd be neat if it was optimized & not accepting lines crossing among other stuff, but this is a very quick & dumb take on the travelling salesman problem or one of its derivative ? .. )
If someone want to have fun ,why not allowing to specify ( or not ) start and/or end points & then let the magic happen ?
so, the ( ugly ) code, enjoy ! :)
<!DOCTYPE html> <!-- Created using JS Bin http://jsbin.com Copyright (c) 2019 by anonymous (http://jsbin.com/qawemovawa/1/edit) Released under the MIT license: http://jsbin.mit-license.org --> <meta name="robots" content="noindex"> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> <style id="jsbin-css"> [#canvas](https://forum.espruino.com/search/?q=%23canvas) { /* width: 160px; height: 80px; */ border : 1px solid blue; } </style> </head> <body> <canvas id="canvas"></canvas> <script id="jsbin-javascript"> // https://www.youtube.com/watch?v=BAejnwN4Ccw // Coding Challenge #35.2: Lexicographic Order: https://www.youtube.com/watch?v=goUlyp4rwiU // Coding Challenge #35.3: Traveling Salesperson with Lexicographic Order // Coding Challenge #35.4: Traveling Salesperson with Genetic Algorithm // Coding Challenge #35.4: Traveling Salesperson with Genetic Algorithm and Crossover: https://www.youtube.com/watch?v=hnxn6DtLYcY var cnvs = document.querySelector('#canvas'); var ctx = cnvs.getContext('2d'); //cnvs.width = 160; //cnvs.height = 80; cnvs.width = 160*2; cnvs.height = 80*2; // draw dot helper var drawDot = function(ctx, x, y, rad){ ctx.beginPath(); ctx.arc(x, y, rad, 0, 2 * Math.PI); ctx.stroke(); ctx.fill(); } // coords array from Illustrator /* var coordsArr = [[38.6038818359375,-28.5126953125],[53.5712890625,-20.8525390625],[67.61376953125,-23.0546875],[83.61376953125,-28.2216796875],[99.447265625,-23.0546875],[113.6318359375,-20.9112854003906],[128.481628417969,-28.4538269042969],[128.457397460938,-47.0709228515625],[113.490051269531,-54.731201171875],[99.44775390625,-52.5283203125],[83.447509765625,-47.361328125],[67.61376953125,-52.5283203125],[53.4285278320312,-54.671875],[38.5792846679688,-47.12939453125]]; for(var i=0; i< coordsArr.length; i++){ drawDot(ctx, coordsArr[i][0], cnvs.height + coordsArr[i][1], 2); }*/ /* ==== following the "coding train" ==== */ // swapping helper function swap(a, i, j){ var tmp = a[i]; a[i] = a[j]; a[j] = tmp; } // distance calculation helper function dist(p1x, p1y, p2x, p2y){ return Math.sqrt( Math.pow((p1x - p2x), 2) + Math.pow((p1y - p2y), 2) ); } function calcDistance(points){ var sum = 0; for(var i=0; i < points.length-1; i++){ var d = dist(points[i][0], points[i][1], points[i+1][0], points[i+1][1]); sum += d; } return sum; } var cities = []; var totalCities = 10; var recordDistance; var bestEver; function setup(){ /* -- random pts -- for(var i=0; i< totalCities; i++){ var v = [Math.random()*cnvs.width, Math.random()*cnvs.height]; //var v = [10, 10]; cities[i] = v; } */ ///* -- pts from Illu // 1st arr is "manually optimized" //var coordsArr = [[38.6038818359375,-28.5126953125],[53.5712890625,-20.8525390625],[67.61376953125,-23.0546875],[83.61376953125,-28.2216796875],[99.447265625,-23.0546875],[113.6318359375,-20.9112854003906],[128.481628417969,-28.4538269042969],[128.457397460938,-47.0709228515625],[113.490051269531,-54.731201171875],[99.44775390625,-52.5283203125],[83.447509765625,-47.361328125],[67.61376953125,-52.5283203125],[53.4285278320312,-54.671875],[38.5792846679688,-47.12939453125]]; // 2nd array is "intentionally un-optimized" of the 1st var coordsArr = [[38.5792846679688,-47.12939453125],[53.5712890625,-20.8525390625],[67.61376953125,-23.0546875],[38.6038818359375,-28.5126953125],[99.447265625,-23.0546875],[83.61376953125,-28.2216796875],[113.6318359375,-20.9112854003906],[128.457397460938,-47.0709228515625],[67.61376953125,-52.5283203125],[113.490051269531,-54.731201171875],[99.44775390625,-52.5283203125],[128.481628417969,-28.4538269042969],[83.447509765625,-47.361328125],[53.4285278320312,-54.671875],]; for(var i=0; i< coordsArr.length; i++){ cities[i] = [coordsArr[i][0], cnvs.height + coordsArr[i][1]]; } totalCities = cities.length; //*/ //console.log(cities); var d = calcDistance(cities); recordDistance = d; bestEver = cities.slice(); console.log('recordDistance: ' + recordDistance); } function draw(){ // redraw requestAnimationFrame(draw); //setTimeout(draw, 200); //console.log('drawing ..'); //ctx.clearRect(0, 0, cnvs.width, cnvs.height); ctx.fillStyle = 'black'; ctx.fillRect(0, 0, cnvs.width, cnvs.height); // draw dots ctx.strokeStyle = 'white'; ctx.fillStyle = 'white'; for(var i=0; i< totalCities; i++){ drawDot(ctx, cities[i][0], cities[i][1], 4); } // draw current cities liaisons ctx.strokeStyle = 'white'; ctx.strokeWeight = 2; ctx.beginPath(); // connect via lines for(var i=0; i< totalCities-1; i++){ ctx.moveTo(cities[i][0], cities[i][1]); ctx.lineTo(cities[i+1][0], cities[i+1][1]); } ctx.closePath(); ctx.stroke(); // draw best cities liaisons ctx.strokeStyle = 'blue'; ctx.strokeWeight = 4; ctx.beginPath(); // connect via lines for(var i=0; i< totalCities-1; i++){ ctx.moveTo(bestEver[i][0], bestEver[i][1]); ctx.lineTo(bestEver[i+1][0], bestEver[i+1][1]); } ctx.closePath(); ctx.stroke(); // swap things var i = Math.floor( Math.random()*cities.length); var j = Math.floor( Math.random()*cities.length); //cities = swap(cities, i, j); swap(cities, i, j); var d = calcDistance(cities); if(d < recordDistance){ recordDistance = d; bestEver = cities.slice(); console.log('recordDistance: ' + recordDistance); } } setup(); draw(); </script> </body> </html>
-
The code generated by the Illustrator script & the Illustrator script ( CS5+ ) lies below
// the generated led colors from above dots pathItem Y/N [[0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0],[255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255]] // the dots coords, for debug ( & also to add in canvas script to try generating shortest path between dots ) [[38.6038818359375,-28.5126953125],[53.5712890625,-20.8525390625],[67.61376953125,-23.0546875],[83.61376953125,-28.2216796875],[99.447265625,-23.0546875],[113.6318359375,-20.9112854003906],[128.481628417969,-28.4538269042969],[128.457397460938,-47.0709228515625],[113.490051269531,-54.731201171875],[99.44775390625,-52.5283203125],[83.447509765625,-47.361328125],[67.61376953125,-52.5283203125],[53.4285278320312,-54.671875],[38.5792846679688,-47.12939453125]]
Illustrator script, where logic is:
- order of dots items on dots layer in .ai file determines generated colors indices
for each mouth layer, produce an array of color indices
/* Generate a mouthsLedsArrays.json from the shapes in layers other than the top one in the currently opened Illustrator document */ // ==== INCLUDES ==== [#include](https://forum.espruino.com/search/?q=%23include) "json2.js" // jshint ignore:line // ==== ILLUSTRATOR FLAGS ==== app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM; // use artboard-based coords ( needed to easily get noc_x & noc_y ) // ==== PNP HELPER ==== // To digg: https://stackoverflow.com/questions/22521982/check-if-point-inside-a-polygon // also:http://alienryderflex.com/polygon/ function inside(point, vs) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html var x = point[0], y = point[1]; var inside = false; for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { var xi = vs[i][0], yi = vs[i][1]; var xj = vs[j][0], yj = vs[j][1]; var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; }; // ==== SHORTEST COMBO HELPER ==== function perm(xs) { var ret = []; for (var i = 0; i < xs.length; i = i + 1) { var rest = perm(xs.slice(0, i).concat(xs.slice(i + 1))); if(!rest.length) { ret.push([xs[i]]) } else { for(var j = 0; j < rest.length; j = j + 1) { ret.push([xs[i]].concat(rest[j])) } } } return ret; } //console.log(perm([0,1,2]).join("\n")); /* alternative timed permutator function permutator(inputArr) { var results = []; function permute(arr, memo) { var cur, memo = memo || []; for (var i = 0; i < arr.length; i++) { cur = arr.splice(i, 1); if (arr.length === 0) { results.push(memo.concat(cur)); } permute(arr.slice(), memo.concat(cur)); arr.splice(i, 0, cur[0]); } return results; } return permute(inputArr); } var t0 = performance.now(); //console.time('someFunction'); //var res = permutator([0,1,2,3,4,5,6,7,8]); // ~ 1191.80ms //var res = permutator([0,1,2,3,4,5,6,7,8,9]); // ~12969.20ms var res = permutator([0,1,2,3,4,5,6,7,8,9,10]); // ~ 147567.70 mins console.log(res.length); var t1 = performance.now(); // console.timeEnd('someFunction'); console.log("Call to doSomething took " + (t1 - t0).toFixed(2) + " milliseconds.") */ // ==== UNITS HELPERS ==== //function rndMm(num){ return ( num * 0.3528 ).toFixed(2); } // quick helper - getting mm function rndMm(num){ return ( num ).toFixed(2); } // quick helper - getting pixels // -= STEPS =- // - 0: for each mouth shape on the 'template' ( top-most ) layer, position one ( or more equally distant ) 'dot(s)' on the 'dots' layer // - 1: get the second-most layer ( the 'dots' one ) // - 2: get all of its 'dots' pathItems in an array // - 3: create a 'maskMouthsArray', and for [2..n] other ( down from second ) layers, create a 'mouthArray' and: // ---- 4: for each dot on the 'dots' layer: // ------- 5: for evey shape in layer n: // ---------- 6: if shape 'contains' the dot, either set our current mouthArray[dot] to '1' or directly to '255, 255, 255' ( for direct RGB White mapping ) // - 7: push our current 'mouthArray' to our 'maskMouthsArray' // - 8: once all layers have been processed, save the 'maskMouthsArray' in the 'mouthsLedsArrays.json' output file // -= QUICK IMPLM =- var layers = app.activeDocument.layers; // - 0: for each mouth shape on the 'template' ( top-most ) layer, position one ( or more equally distant ) 'dot(s)' on the 'dots' layer [TODO] var templateLayer = layers[0]; alert('templateLayer: '+ templateLayer.name); // - 1: get the second-most layer ( the 'dots' one ) var dotsLayer = layers[2]; alert('dotsLayer: '+ dotsLayer.name); // - 2: get all of its 'dots' pathItems ( an array ) var dots = dotsLayer.pathItems; // ---- BRUTE-FORCE OPTIMAL DOTS WIRING ORDER: start ---- var dotsCoordsArr = []; // - 2.1: get array of the coords of the dots for(var dotI = 0; dotI < dots.length; dotI++){ var dotC = dots[dotI].position; dotC[0] += dots[dotI].width/2.0; // dotCx dotC[1] -= dots[dotI].height/2.0; // dotCy dotsCoordsArr.push([ dotC[0], dotC[1] ]); } /* // - 2.2: get all possible permutations of their indices ( from 0 to length-1 ) var dotsIndices = []; for(var dotI = 0; dotI < dots.length; dotI++){ dotsIndices.push(dotI); } // get every possible index // GET ONLY FEW FIRST DOTS TO TRY GOING THROUGH WHILE NOT FREEZING ?? //dotsIndices = dotsIndices.slice(0, 4); // get 4 dots out of n ( 14 in our example ) -> gives 24 possible permutations dotsIndices = dotsIndices.slice(0, 8); var possibleIndicesPermutations = perm(dotsIndices); // --> THE LINE ABOVE IS WHAT MAKES ILLUSTRATOR JS INTERPRETER FREEZE WHEN APPLIED TO AN ARRAY OF 14 ITEMS // considering for [0..8] we'd get 362880 possible permutations alert('possible permutations: ' + possibleIndicesPermutations.length); //* --> the following make Illustrator no longer responsive .. aka crash ;/ // - 2.3: for all permutations, sum the distances between i & i+1 for each permutation var summedDistances = []; for(var dotPermsI = 0; dotPermsI < possibleIndicesPermutations.length; dotPermsI++){ // for each permutation var summedDists = 0; var currPerm = possibleIndicesPermutations[dotPermsI]; //alert('current permutation: ' + JSON.stringify(currPerm) ); //for(var dotPermI; dotPermI < possibleIndicesPermutations[dotPermsI].length; dotPermI++ ){ // for each dot index in it for(var dotPermI = 0; dotPermI < currPerm.length; dotPermI++ ){ // for each dot index in it var currPermCurrDot = currPerm[dotPermI]; //alert('current permutation current dot idx: ' + JSON.stringify(currPerm[currPermCurrDot]) ); //alert('current permutation current dot idx: ' + JSON.stringify(currPermCurrDot) ); dots[currPermCurrDot].name = 'LOL'+currPermCurrDot; // get distance between dot[i] & dot[i+1] if(dotPermI < possibleIndicesPermutations[dotPermsI].length-1){ // beginning to end var dotC = dots[currPermCurrDot].position; dotC[0] += dots[currPermCurrDot].width/2.0; // dotCx dotC[1] -= dots[currPermCurrDot].height/2.0; // dotCy var dotC2 = dots[currPermCurrDot].position; dotC2[0] += dots[currPermCurrDot].width/2.0; // dotC2x dotC2[1] -= dots[currPermCurrDot].height/2.0; // dotC2y var dist = Math.sqrt( Math.pow((dotC[0] - dotC2[0]), 2) + Math.pow((dotC[1] - dotC2[1]), 2) ); summedDists += dist; } else { // end to beginning var dotC = dots[currPermCurrDot].position; dotC[0] += dots[currPermCurrDot].width/2.0; // dotCx dotC[1] -= dots[currPermCurrDot].height/2.0; // dotCy var dotC2 = dots[currPerm[0]].position; dotC2[0] += dots[currPerm[0]].width/2.0; // dotC2x dotC2[1] -= dots[currPerm[0]].height/2.0; // dotC2y var dist = Math.sqrt( Math.pow((dotC[0] - dotC2[0]), 2) + Math.pow((dotC[1] - dotC2[1]), 2) ); summedDists += dist; } } summedDistances.push(summedDists); } // - 2.4: check which of the sum is the lowest var lowestSummedDistIdx = 0; var lowestSummedDist = summedDistances[0]; // set as 1st permutation's sum to start for(var sumDistI = 0; sumDistI < summedDistances.length; sumDistI++){ if(summedDistances[sumDistI] < lowestSummedDist){ lowestSummedDistIdx = sumDistI; lowestSummedDist = summedDistances[sumDistI]; } } alert('best permutation total length: ' + lowestSummedDist); // - 2.5: re-order thz z-index of the dots by looping over the indices in the permutation result that has the lowest sum, // from end to beginning & doing 'bring to front' operation on i var bestPermutation = possibleIndicesPermutations[lowestSummedDistIdx]; for (var i = bestPermutation.length - 1; i >= 0; --i) { dots[bestPermutation[i]].zOrder(ZOrderMethod.BRINGTOFRONT); } */ // ---- BRUTE-FORCE OPTIMAL DOTS WIRING ORDER: end ---- // DEBUG: for now, rename the dots based on their zIndexPosition var debugLayer = layers[1]; var dotsPathPts = []; // used to draw one big path instead of many little ones for(var dotI = 0; dotI < dots.length; dotI++){ dots[dotI].name = dotI; //dots[dotI].zOrderPosition; // TODO: add txt right within dot with idx //var pointTextRef = docRef.textFrames.add(); var pointTextRef = debugLayer.textFrames.add(); pointTextRef.contents = dotI; //"TextFrame #3"; pointTextRef.story.textRange.justification = Justification.CENTER; pointTextRef.textRange.size = 5; //var textColor_gray = new GrayColor(); //textColor_gray.gray = 70; //pointTextRef.textRange.paragraphs[0].fillColor = textColor_gray; var textColor_rgb = new RGBColor(); textColor_rgb.blue = 255; pointTextRef.textRange.paragraphs[0].fillColor = textColor_rgb; //pointTextRef.contents.fillColor = app.colors.item("Red"); var dotC = dots[dotI].position; dotC[0] += dots[dotI].width/2.0; // dotCx dotC[1] -= dots[dotI].height/2.0; // dotCy pointTextRef.top = dotC[1]; //700; pointTextRef.left = dotC[0]; //400; //pointTextRef.selected = true; //redraw(); // draw a line between those as well if(dotI < dots.length-1){ // get nxt dot var dotC2 = dots[dotI+1].position; dotC2[0] += dots[dotI+1].width/2.0; // dotCx dotC2[1] -= dots[dotI+1].height/2.0; // dotCy dotsPathPts.push([ dotC[0], dotC[1] ], [ dotC2[0], dotC2[1] ]); /* var strokeColor = new RGBColor(); strokeColor.green = 255; var line = debugLayer.pathItems.add(); line.stroked = true; // to be able to see it line.strokeColor = strokeColor; line.strokeWidth = 4 ; line.setEntirePath( [ [ dotC[0], dotC[1] ], [ dotC2[0], dotC2[1] ] ] ); */ } } var strokeColor = new RGBColor(); strokeColor.green = 255; var line = debugLayer.pathItems.add(); line.stroked = true; // to be able to see it line.filled = false; line.strokeColor = strokeColor; line.strokeWidth = 1; //line.setEntirePath( [ [ dotC[0], dotC[1] ], [ dotC2[0], dotC2[1] ] ] ); line.setEntirePath( dotsPathPts ); redraw(); // - 3: create a 'maskMouthsArray', and for [2..n] other ( down from second ) layers, create a 'mouthArray' and: var maskMouthsArr = []; //var layerIdx= 2; var layerIdx= 3; // take in account added "debug" layer ( where we trace debug lines & where dots numbers are displayed ) //for(layerIdx; layerIdx < layers.length -2; layerIdx++){ // process every layer except 1st & 2nd top-most for(layerIdx; layerIdx < layers.length; layerIdx++){ // process every layer except 1st & 2nd top-most alert('processing layer: '+ layers[layerIdx].name); var mouthArr = []; // ---- 4: for each dot on the 'dots' layer: for(var dotIdx = 0; dotIdx < dots.length; dotIdx++){ // - 4.1.: getting the center of the dot var dotC = dots[dotIdx].position; dotC[0] += dots[dotIdx].width/2.0; // dotCx dotC[1] -= dots[dotIdx].height/2.0; // dotCy // ------- 5: for evey shape in layer n: var nLayerShapes = layers[layerIdx].pathItems; var dotShapeFound = false; // whether or not the dot has found a shape it fits in for(var layerShapeIdx = 0; layerShapeIdx < nLayerShapes.length; layerShapeIdx++){ if(dotShapeFound == true) break; // don't loop further over other shapes since our current dot has found its shape mate // else, continue looping until we deduce that the 'mouth section' corresponding to that dot is not used on this 'mouthArray' // ---------- 6: if shape 'contains' the dot, either set our current mouthArray[dot] to '1' or directly to '255, 255, 255' ( for direct RGB White mapping ) /* // --> HUGELY-IN-POC-AND-MAYBE-PROGRESS PART: bypassing the 'visibleBounds' & 'geometricBounds' limitations to check whether a dot is 'within' a shape <-- // - 6.0: getting the center of the shape var c1 = nLayerShapes[layerShapeIdx].position; c1[0] += nLayerShapes[layerShapeIdx].width/2.0; //center[1] += obj.height/2.0; c1[1] -= nLayerShapes[layerShapeIdx].height/2.0; // - 6.1: change currently selected objs to have dots[dotIdx] & as selection for futher pathfinder bool op .. or just to 'group' those & apply the same idea ;) dots[dotIdx].selected = true; nLayerShapes[layerShapeIdx].selected = true; // - 6.2: group those to see if this affect the shape's center by a yota or more */ // get pathPoints from shape ( R: currently, curves may produce weird results .. ) var shapePathPts = nLayerShapes[layerShapeIdx].pathPoints; // loop over those & build an array of coords var polygon = []; for(var j=0; j < shapePathPts.length; j++){ var anch = shapePathPts[j].anchor; // its anchor polygon.push([anch[0], anch[1]]); } //var polygon = [ [ 1, 1 ], [ 1, 2 ], [ 2, 2 ], [ 2, 1 ] ]; // array of coordinates of each vertex of the polygon //inside([ 1.5, 1.5 ], polygon); // true - whether the test point in inside or outside the polygon if( inside([ dotC[0], dotC[1] ], polygon) == true ){ dotShapeFound = true; // break outta loop } } if(dotShapeFound == true){ mouthArr.push(255, 255, 255); // RGB LEDS like APA102 - full on ( white ) } else { mouthArr.push(0, 0, 0); // RGB LEDS like APA102 - full off } } // - 7: push our current 'mouthArray' to our 'maskMouthsArray' // all dots have been processed for current mouth layer: push it to our maskMouths array maskMouthsArr.push(mouthArr) } // - 8: once all layers have been processed, save the 'maskMouthsArray' in the 'mouthsLedsArrays.json' output file //var container = { "expression": expression}; //alert(JSON.stringify(container) ); //File.saveDialog (prompt[, preset]) var jsonFile = new File("mouthsLedsArrays.json"); // def file name var saveFilePath = jsonFile.saveDlg("Where to save generated stuff ? :)"); //var filepath = "~/Desktop/" + randomname + ".txt"; var write_file = File(saveFilePath); if (!write_file.exists) { // if the file does not exist create one //write_file = new File(filepath); write_file = new File(saveFilePath); } else { // if it exists ask the user if it should be overwritten var res = confirm("The file already exists. Overwrite ?", true, "titleWINonly"); // if the user hits no stop the script if (res !== true) { //return; // threw an error on AI CS5 .. write_file = ''; } } var out; // our output ( we know already that the file exist but to be sure ) if (write_file !== '') { out = write_file.open('w', undefined, undefined); //Open the file for writing. write_file.encoding = "UTF-8"; write_file.lineFeed = "Unix"; //convert to UNIX lineFeed // txtFile.lineFeed = "Windows"; // txtFile.lineFeed = "Macintosh"; } if (out !== false) { // got an output? write_file.writeln( JSON.stringify(maskMouthsArr) ); // write generated stuff to json file write_file.writeln( JSON.stringify(dotsCoordsArr) ); // debug coords array write_file.close(); } /* // ==== EAGLE ILLUSTRATOR HELPER ==== //var eagleCenter = undefined; //var eagleCenterPos = {}; // getting the items selected var selecteditems = app.activeDocument.selection; //alert("Found" + selecteditems.length + " selected items .."); if(selecteditems.length < 1 ){ // || selecteditems.length > 2 ){ alert("Please select two elements, not less or more"); } else { // start building our expression var expression = {}; // get objects centers var obj1 = selecteditems[0]; alert('nullObjectCenter -> obj.name: ' + obj1.name); var c1 = obj1.position; c1[0] += obj1.width/2.0; //center[1] += obj.height/2.0; c1[1] -= obj1.height/2.0; expression.noc_x = parseFloat( rndMm( c1[0] ) ); //expression.noc_y = parseFloat( rndMm( c1[1] ) ); //expression.noc_y = parseFloat( rndMm( 64+ c1[1] ) ); expression.noc_y = parseFloat( rndMm( c1[1] * -1 ) ); /* var obj2 = selecteditems[1]; var c2 = obj2.position; c2[0] += obj2.width/2.0; //center[1] += obj.height/2.0; c2[1] -= obj2.height/2.0; var objs = [ {cX: rndMm(c1[0]), cY: rndMm(c1[1])}, {cX: rndMm(c2[0]), cY: rndMm(c2[1])}, ]; * / expression.eyes = []; // for each object selected after the 1st one // /!\ R: the order is the STACKING order, not the one in which we selected objects for(var i=1; i < selecteditems.length; i++){ var selectedItem = selecteditems[i]; alert(selectedItem.typename); var pathPts = selectedItem.pathPoints; //alert('pathPts: ' + selectedItem.pathPoints); var polyPts = {}; // loop over those //var cntr = 1; for(var j=0; j < pathPts.length; j++){ var pathPt1 = pathPts[j]; // the point var pt1Anch = pathPt1.anchor; // its anchor var p1x = pt1Anch[0]; var p1y = pt1Anch[1]; / * // also get next points info ( to ease later parsing, we change the way path points are stored in the json to something quicker eagle-side ) var pathPt2 = pathPts[j+1]; // the next point var pt2Anch = pathPt2.anchor; // its anchor var p2x = pt2Anch[0]; var p2y = pt2Anch[1]; * / // instead of adding those directly to our array holding the pathPts ready for Eagle, we compute their positions relative to our 'eagle_center' //var relPt1x = parseFloat( rndMm( p1x - eagleCenterPos['cx'] ) ); //var relPt1y = parseFloat( rndMm( p1y - eagleCenterPos['cy'] ) ); //var relPt2x = parseFloat( rndMm( p2x - eagleCenterPos['cx'] ) ); //var relPt2y = parseFloat( rndMm( p2y - eagleCenterPos['cy'] ) ); var relPt1x = parseFloat( rndMm( p1x - c1[0] ) ); var relPt1y = parseFloat( rndMm( p1y - c1[1] ) ); // std pathPoint.anchor x & y polyPts['x'+(j+1)] = relPt1x; polyPts['y'+(j+1)] = relPt1y; // DEBUG -- polyPts['X_POS'+(j+1)] = parseFloat( rndMm( p1x ) ); polyPts['Y_POS'+(j+1)] = parseFloat( rndMm( p1y ) );; // DEBUG -- // quick test, to understand WHY & HOW I can't get bezier data out of shapes drawn using the pen tool .. / * if(pathPt1.leftDirection && pathPt1.rightDirection ) alert('pathPt1.leftDirection exist on a point of that path, be it aof type "CORNER" or not ?! --> in: (' + pathPt1.leftDirection[0] + ', ' + pathPt1.leftDirection[1] + ') out: (' + pathPt1.rightDirection[0] + ', ' + pathPt1.rightDirection[1] + ') ' ); * / // for smoothed stuff, handle in & out control points // R: a rectangle or other shape becomes "SMOOTH" as soon as one of its point is modded ( ex: via pen tool ) // Q: are Illustrator actual cubic curves ? ( then we'd need to add the next anchor point as p3 - but we don't need to do so here -> so no storage overhead from that ;) ) if(pathPt1.pointType !== PointType.CORNER){ // other type is PointType.SMOOTH ( https://illustrator-scripting-guide.readthedocs.io/jsobjref/PathPoint/ ) polyPts['x'+(j+1)+'in'] = parseFloat( rndMm( pathPt1.leftDirection[0] - c1[0] ) ); polyPts['y'+(j+1)+'in'] = parseFloat( rndMm( pathPt1.leftDirection[1] - c1[1] ) ); polyPts['x'+(j+1)+'out'] = parseFloat( rndMm( pathPt1.rightDirection[0] - c1[0] ) ); polyPts['y'+(j+1)+'out'] = parseFloat( rndMm( pathPt1.rightDirection[1] - c1[1] ) ); } // as a test polyPts['x'+(j+1)+'pointType'] = pathPt1.pointType.toString().substr(pathPt1.pointType.toString().indexOf('.')+1); } expression.eyes.push(polyPts); } //alert(JSON.stringify(expression) ); var container = { "expression": expression}; alert(JSON.stringify(container) ); } */
- order of dots items on dots layer in .ai file determines generated colors indices
-
( no enough space in one post .. )
The debug code currently handling the logic is the following
( I'll post the Illustrator script below in this post, as well as a a 'dumb try to get the shortest path linking every dots' ( html5 canvas pathfinding stuff ) not quite working as expected .. but hey, it may get better if given time to dod so ? ;p )// -- AKALI MASK -- // - array generated from an Illustrator script pushing either '0, 0, 0' or '255,255,255' depending on the presence of pathItems above dots at certain coords in the artboard var mouthsLedsArrays = [ [0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0], [255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,255,255,255,255,255,255] ]; // - timings extracted from a video/audio analysis of a chunk from the LoL soundtrack var absTimeMouthsIdxsArray = [ //[<ms_from_start>, <mouth_idx>] [37, 0], [38, 1], [40, 0], [45, 1], [45.2, 0], [45.5, 1] ]; // - relative timings to be used with 'setTimeout' to schedule the mouth updates // R: to map abolute times to relative timing in ms: // relTime = (absTime * 1000) - (1stAbsTime * 1000) var reTimeMouthsIdxsArray = []; for(var i=0; i< absTimeMouthsIdxsArray.length; i++){ var relTime = (i === 0) ? ((absTimeMouthsIdxsArray[i][0] * 1000) - (absTimeMouthsIdxsArray[i][0] * 1000)) : (absTimeMouthsIdxsArray[i][0] * 1000) - (absTimeMouthsIdxsArray[i-1][0] * 1000); reTimeMouthsIdxsArray[i] = [relTime, absTimeMouthsIdxsArray[i][1]]; } console.log('reTimeMouthsIdxsArray : ' + reTimeMouthsIdxsArray); console.log('reTimeMouthsIdxsArray.length : ' + reTimeMouthsIdxsArray.length); // few vars var timr = -1; var currMouthAnimIdx = 0; // where we're at in the 'reTimeMouthsIdxsArray' //var currMouthIdx = 0; var currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; console.log('currMouthAnimIdx : ' + currMouthAnimIdx); console.log('currMouth : ' + currMouth); // - to be replaced by spy calls to update the leds // display mouth ( via LEDS or pretty much anything ?) function displayCurrMouth(){ console.log('NEW MOUTH: ' + currMouth); if(currMouthAnimIdx+1 < reTimeMouthsIdxsArray.length){ console.log('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx+1][0] ); g.clear(); g.drawString('mouth idx: ' + currMouthAnimIdx, 10, 10); g.drawString('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx+1][0], 10, 20); g.flip(); } /* g.clear(); g.drawString('mouth idx: ' + currMouthAnimIdx, 10, 10); //g.drawString('mouth: ' + currMouth.join(','), 10, 20); g.drawString('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx][0] + 'ms', 10, 20); //g.drawString('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx][0] + 'ms', 10, 30); g.flip(); */ } // transition between mouths function nxtMouth(){ if(currMouthAnimIdx+1 < reTimeMouthsIdxsArray.length){ // didn't reach end of array yet currMouthAnimIdx = currMouthAnimIdx+1; //console.log('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx][0] ); //if(currMouthAnimIdx+1 < reTimeMouthsIdxsArray.length){ console.log('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx+1][0] ); } timr = setTimeout(function(){ currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; //console.log('NEW MOUTH: ' + currMouth); displayCurrMouth(); nxtMouth(); // schedule next call }, reTimeMouthsIdxsArray[currMouthAnimIdx][0]); } else { timr = -1; console.log('end of mouth timed array reached -> animation complete !'); g.clear(); g.drawString('ANIMATION COMPLETE !', 10, 10); g.drawString('press startBtn again ;)', 10, 20); g.flip(); } } // start & reset function startAnim(){ console.log('start !'); g.clear(); g.drawString('next mouth in: ' + reTimeMouthsIdxsArray[currMouthAnimIdx+1][0], 10, 10); g.flip(); nxtMouth(); } function resetAnim(){ console.log('reset !'); if(timr !== -1){ clearTimeout(timr); timr = -1; } currMouthAnimIdx = 0; //currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; currMouth = mouthsLedsArrays[reTimeMouthsIdxsArray[currMouthAnimIdx][1]]; g.clear(); g.drawString('RESET COMPLETE !', 10, 10); g.drawString('press startBtn again ;)', 10, 20); g.flip(); } // uC-specific pins var startBtnPin = A0; var resetBtnPin = A1; // watches var startBtnW; var resetBtnW; function startOled(){ g.clear(); g.drawString('waiting startBtn press ..', 10, 10); g.flip(); // setup btns pinMode(startBtnPin, 'input_pullup'); startBtnW = setWatch(function(e) { /*console.log(e.time-e.lastTime);*/ startAnim(); }, startBtnPin, { repeat:true, edge:'falling' }); pinMode(resetBtnPin, 'input_pullup'); resetBtnW = setWatch(function(e) { /*console.log(e.time-e.lastTime);*/ resetAnim(); }, resetBtnPin, { repeat:true, edge:'falling' }); } var g = undefined; function onInit(){ // I2C I2C1.setup({scl:B6,sda:B7, bitrate:1000000}); g = require("SSD1306").connect(I2C1, startOled, { height : 32, contrast : 255 }); }
-
Hi there !
I'm currently helping a friend building an animated "mask" from the LoL game, as seen on the following picture:
We plan to have a total of 8 different "mouths", and the idea is to use a set of daisy-chained leds positioned within different "cavities" to represent each desired "leds combo": by turning on or off leds in different "cavities", we display the mouth n ( more or less like a "segment display", but using LEDs )
The goal is to be able to start scheduled transitions betweens mouths at the press of the start button
Now, onto the needy greedy:
POC BOM:
- currently using an Original Espruino board as controller
- 2 x tactile switches to act as 'start' & 'reset' buttons
- a PowerBoost 500C to boost [3.7V .. 4.2V] to 5V ( to power the LEDs & the Espruino board )
- a 18650 12000mAh Li-Ion battery
- APA102 ( or is it APA102C or another clone ? ) LEDS strip ( 25 of them )
The charger/booster may be replaced by the 1000C version ( allowing up to 2A, in contrary to the 500C which is said to provide 1A from 3.7V batteries - although I'm driving 16 LEDs while testing since it shall draw max (60mA/led)*16= 960mA )
It seems I'm getting somewhere in driving the LEDs thanks to this posts:
- http://forum.espruino.com/conversations/280762/
- https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/comment-page-1/#comment-7082
Thing is, I'm now sure these HAVE to be driven at 5V ( else I got erratic colors powering 1, 3 or 16 of those through the 'Bat' pin on the original Espruino board) so powering those from 3.7V battery directly is a no-go, but I'm unsure of the best setup to control them: the SPI seems to be 'somewhat glitchy', and I'm guessing sure this comes from the pin config and not the spi.write calls themselves.
- I tried with a level shifter, no success
- I tried with 'af_opendrain' & 1.5k pullup resistors to 5v, no success
- I tried with directly wiring Espruino pin ( B3 sck & B5 mosi ), success ONLY with software SPI
I had some relative success driving 16 LEDs ( powered from BoostCharger 5V - actually 4.87V ) directly wired to B3 & B5, but in my tests, 'setInterval' wasn't 'as precise' as in my earlier test sketch ( in which I was using an OLED screen to display the mouth index & the timeout before a mouth update ). As the goal is to have 'somewhat precise' schedule for mouth updates, I think I have some things to pack right for this goal to be met ..
So here goes the questions:
- 1: IYO, what 'd be the best way to wire to those Sck & Data pins ?
- 2: any way to get hardware SPI working to increase the refresh rate & lessen cpu usage ?
- 3: would 'compiled' code helps in getting more precise & faster 'scheduled updates' ?
- 4: am I driving these correctly at all ? ( or what may the 'spi behavior'* come from ? )
*I find it quite weird that software spin kinda works but hardware doesn't
The debug code currently used to drive the LEDs is the following
// -- extracts from APA102 test code -- var spi = undefined; // ... spi = new SPI(); spi.setup({miso:B4, mosi:B5, sck:B3, mode:1,order:"msb", baud: 4000000}); // seems ok for software spi // ... // driving 3 LEDS // syntax //spi.write(0,0,0,0, 0xe0+31,255,0,0, 0xe0+31,0,255,0, 0xe0+31,0,0,255, 0xFF ); // 1st R 2nd B 3rd G // .. var myArr = [ new Uint8Array([0,0,0,0, 0xe0+31,255,255,255, 0xe0+31,255,0,0, 0xe0+31,0,10,0, 0xFF]), new Uint8Array([0,0,0,0, 0xe0+31,255,255,255, 0xe0+31,255,0,0, 0xe0+31,255,255,255, 0xFF]), new Uint8Array([0,0,0,0, 0xe0+31,0,0,255, 0xe0+31,255,0,0, 0xe0+31,255,255,255, 0xFF]), new Uint8Array([0,0,0,0, 0xe0+31,255,0,255, 0xe0+31,255,255,255, 0xe0+31,255,255,255, 0xFF]), new Uint8Array([0,0,0,0, 0xe0+31,255,0,0, 0xe0+31,0,0,0, 0xe0+31,255,255,255, 0xFF]), new Uint8Array([0,0,0,0, 0xe0+31,255,0,0, 0xe0+31,0,255,0, 0xe0+31,0,0,255, 0xFF]) ]; var idx = 0; var max = myArr.length; function loopOver(){ //console.log(idx); if(idx+1 < max) idx++; else idx = 0; spi.write(myArr[idx]); } //var timr = setInterval(loopOver, 200);
- currently using an Original Espruino board as controller
-
Hi there !
-> it's always a pleasure sharing findings ;)A whole module seems a little overkill for stuff that could be set via options ( or it seems to me, at least ? ), also I have a little 64x48 old screen for which I can't fix the display ( I didn't mod the SSD1306 module right during my tests, reason for the horizontal white lines I'm getting :/ .. )
I totally agree on putting the rows/cols offsets in the init cmds & I tired to do so, but I still don't have enough hints on the params of column & page address ( I could successfully fiddle with those to fix the "italic" text, but couldn't get the same fix working with tweaks on init .. ) -> if you have any docs on those, I'll be very glad to read about it :)
cmd(0x2A,[0,0,0,LCD_WIDTH]); cmd(0x2B,[0,0,0,LCD_HEIGHT]);
Understood for the DMA stuff ( I 'll have to continue my beginner tries on how to use DMA before jumping to Espruino code that would relate to the subject .. but not skilled enough to help you there :/ )
For the "other mode", I was thinking of something that would actually work on the official Espruino boards as well & use some "partial clear & flip()" to minimize the ram & memory needed :)
Thanks for the link on ".getModified()", it'd be pretty nice to clear the last stuff drawn :)
For your proposal on 'flip()' you'd wish something that clears only the last area drawn or every areas drawn ? (in other words, if I do the following, do I get a "cumulative" modified area ? )
g.setPixel(10,20); // 1st thing draw g.getModified(); //> {x1:10, y1:20, x2:10, y2:20} g.setPixel(20,30); // 2nd thing draw g.getModified(); //> {x1:10, y1:20, x2:20, y2:30} --> I get the src correct ?
If the above is actually what's returned, I'll be happy modding the flip() fcn in order to only send the modified rows ( shall we include a bool to "force" re-sending unmodified stuff as well ? ) :)
For the "italic" text, it was actually a glitch from setting the 1st or 3rd params in the 0x2A/0x2B calls, but thanks for the hint on the font doubling ;)
-> it reminds me I "extracted" the font that was used by Autodesk for their 123DCircuit tool before it went offline, and being pretty nice for pcbs it'd be pretty cool to also have it on displays ;)For the "alternative behavior" of the "paletted mode", I was getting weird colors mainly because of having a not big enough palette I guess ( it seemed to went away when having a palette of 16 items, clearer after reading the source again .. ;) )
For the "instant logo drawing", I studied your platform game code ( & tested it -> it seemed I had some fixes to do be done with the player "flying" and going down otherwise, but this may have come from the way I set my btns ;) )
-> I'll have to try the flappy example & the neat clock one :)The goal being to save memory & get a colored logo drawn at once in whatever paletted/normal mode, I guess a possible solution would be to 1st save it to .boot0 & cie & then have the code that draws it on power up & reboot ?
-> I'll try the said stuff in few mins & give an update that as well ( .. )
Last but not least, I digged within jswrap_graphics.h/.c & graphics.h.c to better understand stuff .. and there's a lot to grasp :) !
I think aside from the 'flip()' update, a version accepting args 'd be still relevant, as well as 'clear(area(s))' ( & maybe 'clip(area(s))' to only draw stuff that's within the said area(s) - for interesting & retro text effects ;p ? )For the "partial clear" part, what about the following ?
// jswrap_graphics.h void jswrap_graphics_clearP(JsVar *parent, int x1, int y1, int x2, int y2); // jswrap_graphics.c /*JSON{ "type" : "method", "class" : "Graphics", "name" : "clearP", "generate" : "jswrap_graphics_clearP" } Clear the LCD partially with the Background Color */ void jswrap_graphics_clearP(JsVar *parent, int x1, int y1, int x2, int y2) { JsGraphics gfx; if (!graphicsGetFromVar(&gfx, parent)) return; graphicsClear(&gfx); graphicsSetVar(&gfx); // gfx data changed because modified area } // graphics.h void graphicsClearP(JsGraphics *gfx, short x1, short y1, short x2, short y2); // graphics.c void graphicsClearP(JsGraphics *gfx, int x1, int y1, int x2, int y2) { unsigned int c = gfx->data.fgColor; gfx->data.fgColor = gfx->data.bgColor; graphicsFillRectDevice(gfx,x1,y1,x2,y2); gfx->data.fgColor = c; }
For the 'flip()' update, maybe something like the following to be able to either try a flip or a partial flip if something was modified, and "force" it for whatever reason if needed - but I guess the "force" bool wouldn't be used .. ?
// R: untested yet ! ;) /* options = { force: false, // to force a refresh even if g.buffer is unmodified ( some debug mess happened ? ) y1: 0, // for specific rows handle ( partial ) y2: LCD_HEIGHT, // for specific rows handle ( partial ) x1: 0, // 'll surely be too slow for usage .. x2: LCD_WIDTH // 'll surely be too slow for usage .. } */ g.flip = function(options) { // check if modified var tmp = g.getModified(); if(typeof tmp === "undefined"){ // nothing to update: force ? } else { var y1 = options.y1 || 0; var y2 = options.y2 || LCD_HEIGHT; var y1 = options.x1 || 0; var y1 = options.x2 || LCD_WIDTH; ce.reset(); spi.write(0x2A,dc); //spi.write(0,1,0,LCD_WIDTH); // WORKS: made the trick for non-italic text drawn at once ! :D spi.write(0,1+ x1,0,x2); spi.write(0x2B,dc); //spi.write(0,0,0,LCD_HEIGHT); // otherwise, we had spi.write(0,y+rowStart,0,y+rowStart+1); spi.write(0,y1,0,y2); spi.write(0x2C,dc); var lines = 16; // size of buffer to use for un-paletting //var a = new Uint16Array(LCD_WIDTH*lines); // default var a = new Uint16Array((x2-x1)*lines); // default //for (var y=0;y<LCD_HEIGHT;y+=lines) { // default for (var y=0;y<y2-y1;y+=lines) { // default //E.mapInPlace(new Uint8Array(g.buffer, y*LCD_WIDTH*bits/8, a.length), a, palette, bits); E.mapInPlace(new Uint8Array(g.buffer, y*(x2-x1)*bits/8, a.length), a, palette, bits); spi.write(a.buffer); } ce.set(); } };
This being said, I'm onto some fun with g.buffer ;)
I hope the above 'll work as is ( I still have to make my 1st own Espruino build before trying further modding the src ;)
-
a quick update on the subject: I tried for a while getting the following to work but yet without success ( it seems I get the beginning right, but then my code happily produces 'instant pixel artifacts garbage' instead of sending part of the buffer for spi refresh )
The idea behind the 'flipP' ( fliPartial() ) & 'clearP()' ( clearPartial() ) is to be able to later build upon those in order to provide saving & restoring an ArrayBuffer ( g.buffer ) smaller than the entire screen as well as providing a way to quickly update or clear only part of the screen ( in normal & paletted mode )
If anyone's skilled & brave enough to defeat the troubles that lies below, I guess I'm not the only one who'd benefit for such kindness ;p
the non-working-quite-right versions ( rest of the code is as above )
exports.connectPaletted = function(palette, spi, dc, ce, rst, callback) { // ( .. ) // flipP(): execute a partial refresh of the buffer to the screen - seems working for whatever fy & fheight if fx==0 & fwidth==LCD_WIDTH g.flipP = function(fx, fy, fwidth ,fheight) { ce.reset(); spi.write(0x2A,dc); //spi.write(0,fx+1,0,fwidth+1); //spi.write(0,fx+2,0,fwidth); //spi.write(0,fx+1,0,fwidth); // the +1 is what actually does the trick spi.write(0,fx+1,0,fwidth); //spi.write(0,fx+1, 0,fx+fwidth); // COMBO THAT SEEMS TO PASS ( seemed to trick this out for fwidth == lcd_width ) ?! spi.write(0x2B,dc); //spi.write(0,fy,0,fy+fheight); // responsible for the glitches ? ( stuff 'rolling over' itself on the vertical axis ) //spi.write(0,fy+1,fheight+1); //spi.write(0,fy,fheight); // COMBO THAT SEEMS TO PASS for fwidth == lcd_width, but was missing an arg ?! //spi.write(0,fy,1,fheight); spi.write(1,fy,0,fheight); spi.write(0x2C,dc); var lines = 16; // size of buffer to use for un-paletting var a = new Uint16Array(fwidth*lines); // default //for (var y=0;y<fheight;y+=lines) { // default - doesn't work for stuff not at top of screen for (var y=fy;y<fy+fheight;y+=lines) { // A-way - seemed to work for fxwidth == lcd_width & stuff not at top of screen //E.mapInPlace(new Uint8Array(g.buffer, y*fwidth*bits/8, a.length), a, palette, bits); // default - seems to work for whatever fheight & fy // idea: (new Uint8Array(g.buffer, (y+(fx*y))*fwidth*bits/8, a.length), a, palette, bits); E.mapInPlace(new Uint8Array(g.buffer, (y+(fx*y))*fwidth*bits/8, a.length), a, palette, bits); // seemed to work for whatever fheight & fy spi.write(a.buffer); } ce.set(); }; // clearP(): execute a partial clear of the buffer to the screen - currently discovered as an error in the above 'flipP' code .. :| g.clearP = function(fx, fy, fwidth ,fheight) { ce.reset(); spi.write(0x2A,dc); //spi.write(0,fx+1,0,fwidth+1); //spi.write(0,fx+2,0,fwidth); //spi.write(0,fx,0,fwidth); spi.write(0,fx+1,0,fwidth); // the +1 is what actually does the trick //spi.write(0,fx+1, 0,fx+fwidth); // COMBO THAT SEEMS TO PASS ( seemed to trick this out for fwidth == lcd_width ) ?! spi.write(0x2B,dc); //spi.write(0,fy,0,fy+fheight); // responsible for the glitches ? ( stuff 'rolling over' itself on the vertical axis ) //spi.write(0,fy+1,fheight+1); //spi.write(0,fy,fheight); // COMBO THAT SEEMS TO PASS for fwidth == lcd_width, but was missing an arg ?! spi.write(0,fy,0,fheight); //spi.write(0,fy,1,fheight); //spi.write(0,fy+1,0,fheight); spi.write(0x2C,dc); var lines = 16; // size of buffer to use for un-paletting var a = new Uint16Array(fwidth*lines); // default //for (var y=0;y<fheight;y+=lines) { // default - doesn't work for stuff not at top of screen for (var y=fy;y<fy+fheight;y+=lines) { // default - seemed to work for fxwidth == lcd_width & stuff not at top of screen E.mapInPlace(new Uint8Array(g.buffer, y*fwidth*bits/8, a.length), a, palette, bits); // default // idea: (new Uint8Array(g.buffer, (y+(fx*y))*fwidth*bits/8, a.length), a, palette, bits); //E.mapInPlace(new Uint8Array(g.buffer, (y+(fx*y))*fwidth*bits/8, a.length), a, palette, bits); // give vertical-space colored glitches //E.mapInPlace(new Uint8Array(g.buffer, y*(fx+y)*fwidth*bits/8, a.length), a, palette, bits); // give vertical-space colored glitches spi.write(a.buffer); } ce.set(); };
test code ( better tried with something already drawn on screen, like some polygons or a logo ..
var g = exports.connectPaletted(colorPalette, spi, screen_dc, screen_cs, screen_rst, function() { //g.clear(); // NOPE, we DON'T ;) g.setFont8x12(); // ( .. ) g.setColor(3); g.drawString("Hello, world!", 2, 2); g.setColor(2); g.drawString("Hello, world 2!", 2, 16); //g.flip(); // NOPE, we DON'T use this one either .. // full changing both but nothing else g.flipP(0, 0, LCD_WIDTH, 12); // seems to work fine g.flipP(0, 16, LCD_WIDTH, 12); // seems to work fine // trying to partially change both setTimeout(function(){ g.setColor(8); g.drawString("Karaki", 2, 2); g.setColor(9); g.drawString("Karaki2", 2, 16); //g.flipP(0, 2, LCD_WIDTH, 4); // cut its top ?! :L g.flipP(0, 0, LCD_WIDTH, 4); // what about its top ? g.flipP(0, 16+2, LCD_WIDTH, 4); // doesn't work at all :L console.log('color change 2 ( partial horizontal ) occured ?'); setTimeout(function(){ g.setColor(10); g.drawString("Karaki", 2, 2); g.setColor(11); g.drawString("Karaki2", 2, 16); g.flipP(0, 0, 50, 4); // so how is its beginning ? g.flipP(0, 16+2, 50, 4); // & this one's ? console.log('color change 3 ( partial vertical ) occured ?'); // -> getting garbage for both .. }, 6000); }, 2000);
I achieved 'horizontal color stripes' on the 1st item with 3 redrawns before messing with the code & not being able to fix my mess back to that moment I wished I saved it :/ ( gotta upload picture of this & other cases as well .. )
this being said, I'm onto a little break ..
All right, that one good news ! :)
-> I was currently disconnecting the battery when connecting over USB but it seemed I could control the leds "ok" ( but maybe not as reliabiliy as when running everything off the battery ;p )
I'm currently working on how to optimise the leds/leds strip chunks so that I have the shortest non-crossing traces across the board ( I'm quite afraid using short wires 'll be quite hard since it'd unsolder one end while solderng the other due to heat .. I experienced that when joining two strip with a wire between both .. )
I plan to try two different approaches:
The ability to get two layers flexible boards 'd be awesome ;p
Ps: do you think I may have killed the last led on the strip by getting electrically shocked by touching it or is it just some code-side pb ? ( fact is: I tried loading previous versions of my code: still same trouble with the last one, aside from the scheduling maybe not as fast as expected & my test patterns not doing correctly .. yup, I have some mess to fix .. ) or would it come from me controlling leds powered via bat+ pin & no battery while connecting over usb ? :| .. ( I'm not sure how to test if an APA102 is dead or not, aside from directly connecting to it & sending controls to it only - thus this 'd mean a crack in the strip connection, which I doubt since it's not where I joined chuncks .. )
-> I'll check that once done with my mapping problems ;)
ps: do you / does anyone have any knowledge / experience with "breadth-first search" & the TSP problem ? ( I could really get some help on either writing or modding an Illustrator plugin to do some related stuff .. )
thanks for the hints :)
+