-
• #2
Hello allObjects,
could you please give some more information about the display ?
Software module you used is for 9341, which is a 2,2" display.
I did not find anything about a 931 display 2,8" having an SPI interface. -
• #3
@JumJum, there are multiple TFT LCD displays of different size - 2.2", 2.8", 3.2",... even up to 5.7", all controllable by the ILI9341 controller chip, and therefore have all the same resolution and color depth: 320x240px 262K colors. For helpful information on Espruino site see http://www.espruino.com/ILI9341 - I know that the Buying on eBay link on this site goes for 2.2". Just search directly in eBay with above given sizes, for example: 2.8" TFT LCD. Resolution(s), Color depth(s) and interface(s) are defined by the controller. Some controllers have it all configurable. Some controllers can be cascaded to achieve higher resolutions, for example, https://www.displaytech-us.com/sites/default/files/driver-ic-data-sheet/HX8264-D02_DS_v01.pdf
For more details about the ILI9341 controller see
http://www.newhavendisplay.com/app_notes/ILI9341.pdf #ILI9341 #ili9341datasheet - -
• #4
In this update, the project uses the GPS module variation with the array of tuples of lineHandlers and callbacks - as discussed in http://forum.espruino.com/conversations/255757 - GPS Module, Extensions, and u-blox NEO-6M GPS receiver.
This is the running code - with a first cut of the display implementation. Since the display poses significant performance challenges, expect updates, and furthermore, expect updates too for the GPS module for handling multiple GPS sentences, for which the implementation has not settled yet. As usual I (will) add shot and clip to see what's going on.
Don't try to copy from below code section into Web IDE: it will corrupt the code. Download the code from uploaded navi5p.js file at the very bottom of this post.
// navi5p.js // ---- beg (modified) GPS module code function ggaHandler(line, callback) { // ggaHandler var tag = line.substr(3,3); if (tag=="GGA") { var d = line.split(","); var dlat = d[2].indexOf("."); var dlon = d[4].indexOf("."); callback({ time : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2), lat : (parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1), lon : (parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1), fix : parseInt(d[6],10), satellites : parseInt(d[7],10), altitude : parseFloat(d[9]) }); return true; } else { return false; } } function handle(line,handlers) { var idx = handlers.length; while(idx > 0) { idx--; try { // if (line.indexOf("$GPGGA") > -1) { console.log("---"); } // console.log(line); if (handlers[idx][0](line,handlers[idx][1])) return; } catch(x) { if (console) console.log("GPS:",x); } } } var connect = function(serial, handling) { // replace with next line for module // exports.connect = function(serial, handling) { var handlers = (typeof handling == "function") ? [[null,handling]] : handling; if (!handlers[0][0]) handlers[0][0] = ggaHandler; var gps = { line:"", handlers: handlers, enabled: true }; serial.on('data', function(data) { gps.line += data; var idx = gps.line.indexOf("\n"); while (idx>=0) { var line = gps.line.substr(0, idx); gps.line = gps.line.substr(idx+1); if (gps.enabled) handle(line, gps.handlers); idx = gps.line.indexOf("\n"); } if (gps.line.length > 80) gps.line = gps.line.substr(-80); }); return gps; } ; var gps = {connect: connect}; // drop line for module // ----- end (modified) GPS module code // ===== NAVI ===== to display (changed) data on (ILI9341 controlled) LDC as 'sent' by GPS var navi = { lcd: null , gps: null , data: { timAdj: -7 , timc: "#" , fixa: -1 , sata: -1 , latcn: -999.9 , latcs: ["#","$"] , loncn: -999.9 , loncs: ["#","$"] , velcs: "####" } , input: { cnt: 0, cnt2: 0 , tag: function(n,d,p,v,v0) { } , timc: function(n,d,p,v,v0) { this.cnt = (this.cnt < 9) ? this.cnt + 1 : 0; if ((this.cnt2 = (this.cnt2 < 59) ? this.cnt2 + 1 : 0) == 10) v0 = "#"; var l,s,x,y; if (v && v.length == 6) { var j = -1, i = -1; while (i < 5) { i++; if (v.charAt(i) != v0.charAt(i)) { j = i; i = 6; } } if (j > -1) { d.timc = v; v0 = v.split(""); if (j < 2) { j = 0; i = parseInt(v[0],10) * 10 + parseInt(v[1],10) + d.timAdj; i = (i < 0) ? i + 24 : (i > 24) ? i - 24 : i; v0[1] = i % 10; v0[0] = (i - v0[1]) / 10; } j = (j >= 4 ) ? j + 2 : (j >= 2) ? j + 1 : j; v0.splice(4,0,":"); v0.splice(2,0,":"); // console.log(".......".substr(0,j) + v0.join("").substr(j)); s = n.fonts[2], x = 240 - 0 - 8 * s * 0.7, y = 5 + 0 * s * 1.6; n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(8-j)*s*0.7,y+s*1.5); while (j < 8) { n.drawString(2,6,v0[j],x + j * s * 0.7,y); j++; } } } else { s = n.fonts[2], x = 240 - 0 - 8 * s * 0.7, y = 5 + 0 * s * 1.6; n.setClr((this.cnt % 2) * 2); n.lcd.fillRect((l=x),y,l+(1)*s*0.7,y+s*1.5); } } , fixa: function(n,d,p,v,v0) { if (this.cnt2 == 20) { v0 = -1; } else { if (this.cnt != 2) return; } if (v != v0) { d.fixa = v; var s = n.fonts[2], x = 5, y = 5 + 1 * s *1.6; n.setClr(0); n.lcd.fillRect((i=x),y,i+6*s*0.8,y+s*1.5); n.drawString(2,((v) ? 3 : 2),((v) ? "OK " + v : "NOT ok"),x,y); } } , sata: function(n,d,p,v,v0) { if (this.cnt2 == 20) { v0 = -1; } else { if (this.cnt != 4) return; } if (v != v0) { d.sata = v; var s = n.fonts[2], x = 5 + 7 * s * 0.7, y = 5 + 1 * s * 1.6; n.setClr(0); n.lcd.fillRect((i=x),y,i+3*s*0.7,y+s*1.5); n.drawString(2,4,v + "s",x,y); } } , latcs: function(n,d,p,v,v0) { if (this.cnt2 == 40) { v0 = ["#","X"]; } else { if (this.cnt != 6) return; } var v_0 = v[0], v0_0 = v0[0]; if (v_0 && (v_0.length == 9)) { var j = -1, i = -1; while (i < 8) { i = (i == 4) ? i + 2 : i + 1; if (v_0.charAt(i) != v0_0.charAt(i)) { j = i; i = 9; } } var l,s,x,y; if (j > -1) { d.latcs[0] = v_0; d.latcn = 9999.9; v_0 = v_0.split(""); j = (j >= 2) ? j + 1 : j; v_0.splice(2,0,"°"); v_0[5] = "'"; // console.log(".......".substr(0,j) + v_0.join("").substr(j)); l = n.fonts[2]; s = n.fonts[1], x = 240 - 5 - 12 * s * 0.7, y = 5 + ((l * 2) + (s * 0)) * 1.6; n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(10-j)*s*0.7,y+s*1.5); while (j < 10) { n.drawString(1,1,v_0[j],x + j * s * 0.7,y); j++; } } if (v[1] != v0[1]) { if (v[1] && (v[1].length == 1)) { d.latcs[1] = v[1]; d.latcn = 9999.99; l = n.fonts[2]; s = n.fonts[1], x = 240 - 1 * s * 1.1, y = 5 + l * 2 * 1.6; n.setClr(0); n.lcd.fillRect((i=x),y,i+1*s*0.7,y+s*1.5); n.drawString(1,1,v[1],x,y); } } } } , loncs: function(n,d,p,v,v0) { if (this.cnt2 == 50) { v0 = ["#","X"]; } else { if (this.cnt != 8) return; } var v_0 = v[0], v0_0 = v0[0]; if (v_0 && (v_0.length == 10)) { var j = -1, i = -1; while (i < 9) { i = (i == 4) ? i + 2 : i + 1; if (v_0.charAt(i) != v0_0.charAt(i)) { j = i; i = 10; } } var l,s,x,y; if (j > -1) { d.loncs[0] = v_0; d.loncn = 9999.9; v_0 = v_0.split(""); j = (j >= 3) ? j + 1 : j; v_0.splice(3,0,"°"); v_0[6] = "'"; // console.log(".......".substr(0,j) + v_0.join("").substr(j)); l = n.fonts[2]; s = n.fonts[1], x = 240 - 5 - 13 * s * 0.7, y = 5 + ((l * 2) + (s * 1)) * 1.6; n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(11-j)*s*0.7,y+s*1.5); while (j < 11) { n.drawString(1,1,v_0[j],x + j * s * 0.7,y); j++; } } if (v[1] != v0[1]) { if (v[1] && (v[1].length == 1)) { d.loncs[1] = v[1]; d.loncn = 9999.99; l = n.fonts[2]; s = n.fonts[1], x = 240 - 1 * s * 1.3, y = 5 + ((l * 2) + (s * 1)) * 1.6; n.setClr(0); n.lcd.fillRect((i=x),y,i+1*s*0.7,y+s*1.5); n.drawString(1,1,v[1],x,y); } } } } , velcs: function(n,d,p,v,v0) { if (this.cnt2 == 30) { v0 = "#"; } else { if (this.cnt % 3 != 0) return; } if (v && (v.length == 5)) { var j = -1, i = -1; while (i < 4) { i++; if (v.charAt(i) != v0.charAt(i)) { j = i; i = 4; } } var l,s,x,y; if (j > -1) { d.velcs = v; v = v.split(""); // console.log(".......".substr(0,j) + v.join("").substr(j)); l = n.fonts[2]; s = n.fonts[1], x = 240 - 5 - 5 * s * 0.7, y = 5 + ((l * 2) + (s * 2)) * 1.6; n.setClr(0); n.lcd.fillRect((i=x+j*s*0.7),y,i+(5-j)*s*0.7,y+s*1.5); while (j < 5) { n.drawString(1,1,v[j],x + j * s * 0.7,y); j++; } } } } } , initDsp: function() { this.lcd.clear(); } , clrs: [ [ 0.0, 0.0, 0.0 ] // 0 = black , [ 1.0, 1.0, 1.0 ] // 1 = white , [ 1.0, 0.8, 0.8 ] // 2 = light red , [ 0.8, 1.0, 0.8 ] // 3 = light green , [ 0.9, 0.9, 1.0 ] // 4 = light blue , [ 1.0, 1.0, 0.0 ] // 5 = yellow , [ 1.0, 1.0, 0.9 ] // 6 = light yellow ] , cid: -1 , setClr: function(cid) { if ((cid >= 0) && (this.cid != cid)) { this.cid = cid; var rgb = this.clrs[cid]; lcd.setColor(rgb[0], rgb[1], rgb[2]); } } , fonts: [ 20 // 0 = 'small' , 24 // 1 = 'medium' , 32 // 2 = 'large' ] , fid: -1 , setFont: function(fid) { if ((fid >= 0) && (this.fid != fid)) { this.fid = fid; lcd.setFontVector(this.fonts[fid]); } } , drawString: function(f,c,s,x,y) { this.setFont(f); this.setClr(c); this.lcd.drawString(s,x,y); } , handleGGA: function(data) { } , init: function(lcd,gps) { this.lcd = lcd; this.gps = gps; this.initDsp(); this._connectGPSHandlers(gps.handlers); gps.enabled = true; } , _connectGPSHandlers: function(handlers) { var _this = this, d = this.data; handlers[0][1] = handlers[1][1] = function(data) { for (var p in data) { _this.input[p](_this,d,p,data[p],d[p]); } }; } }; // ===== GPS ===== // ----- gpsSetup Serial4.setup(9600,{tx:C10,rx:C11}); // var gps = require("GPS"); // uncomment when using GPS code as module // ----- gpsHandlers - call navi to handle the messages var gpsHandlers = [ [ function(line,callback) { // ggaHandler ('clone' of GPS module's) var tag = line.substr(3,3); if (tag=="GGA") { var d = line.split(","); var dlat = d[2].indexOf("."); var dlon = d[4].indexOf("."); callback( { tag : "GGA" // , tima : d[1].substr(0,2)+":"+d[1].substr(2,2)+":"+d[1].substr(4,2) // , lata : (parseInt(d[2].substr(0,dlat-2),10)+parseFloat(d[2].substr(dlat-2))/60)*(d[3]=="S"?-1:1) // , lona : (parseInt(d[4].substr(0,dlon-2),10)+parseFloat(d[4].substr(dlon-2))/60)*(d[5]=="W"?-1:1) , fixa : parseInt(d[6],10) , sata : parseInt(d[7],10) // , alta : parseFloat(d[9]) }); return true; } else { return false; } } , function(data) { } ] , [ function(line,callback) { // rmcHandler var tag = line.substr(3,3); if (tag=="RMC") { var d = line.split(","); callback( { tag: "RMC" , timc: d[1].substr(0,6) , latcs: [d[3].substr(0, 9),d[4]] // , latcn: (parseInt(d[3].substr(0,2),10) + parseFloat(d[3].substr(2))/60) * (d[4]=="S"?-1:1) , loncs: [d[5].substr(0,10),d[6]] // , loncn: (parseInt(d[5].substr(0,3),10) + parseFloat(d[5].substr(3))/60) * (d[6]=="W"?-1:1) , velcs: d[7].substr(0,5) // , velc: parseFloat(d[7]) // , angc: d[8] // , datc: d[9] }); return true; } else { return false; } } , function(data) { } ] ]; // ===== LCD ==== // ---- (ILI9341) common setup; B2.set(); SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000}); var lcd = require("ILI9341"); // function naviInit() { lcd = lcd.connect(SPI1, B6, B8, B7, function(){ setTimeout(function(){ gps = gps.connect(Serial4, gpsHandlers); navi.init(lcd,gps); },100); }); // }
The first shot was taken at the moment when the last digit of the time in top row was re-drawn - digit 8. The clip shows the sequence after 'warm'-reboot (send code to board, GPS already locked in). You will notice the partial drawing... because of the display's performance drawing strings... You can see code of the sort of this.cnt = ... this.cnt + ... and this.cnt2 = ...this.cnt2 + .... These are counters from 0..9 and 0..59 - updated every second - that control partial and staggered full re-draw of the data in addition to the code that triggers partial redraw only on data changes. The redrawing code is still in its infancy and needs to be refactored for less redundancy (copy-paste-modify is good only for figuring something out). The second shot was taken after returning from work with taking the GPS for a spin for the commute.
The display shows - from top to bottom:
- Time - adjusted to time zone from GPS receiver's UTC in very light yellow color (row 1)
- OK and Fix value of NMEA's $GGA sentence in light green color - value 1..7 are 'good' values. For the value 0 Not ok is shown in light red color (left half of row 2) - and number of satellites discovered by the GPS receiver, like 8s in light blue color (right half of row 2)
- Latitude in degrees, minutes, and decimal fractions of minutes (seconds and fractions 'spared' for later) and N/S hemisphere, all in white (row 3)
- Longitude in degrees, minutes, and decimal fractions of minutes (seconds and fractions 'spared' for later) and E/W of Prime Meridian (Greenwich, London - see http://en.wikipedia.org/wiki/Primemeridian(Greenwich)), all in white (row 4).
- Velocity over ground in nautical miles (55 Nautical Miles ~~ 65 Statuary US Miles), all in white; shows fanny values when not moving at all. (row 5).
Note: the degree symbol - character - is not part of LCD controller's font - assuming the LCD controller takes the character and draws it... (@Gordon, is that assumption correct?)
TBC - to be completed w/ some code walk through in a bit. For now, enjoy the short clips (links below):
- First clip shows a warm boot of the code right after resetting and clearing the display (GPS receiver already locked in - Fix value > 0 - with more than 4..5 satellites discovered and position sufficiently accurate calculated). Display of velocity not yet implemented.
- Second clip shows a brief sequence parking on my drive way after having the 'contraption' taken for a spin to work and back. It just behaved pretty well.
5 Attachments
- Time - adjusted to time zone from GPS receiver's UTC in very light yellow color (row 1)
-
• #5
Not sure how I missed this - it looks great!
@allObjects, depending how serious you are about this (and if you can spare 20 IO pins for the LCD), you could recompile your own version of Espruino with the hardware (parallel) ILI9341 support (which is much faster).
-
• #6
Thanks for your compliments. You did not miss anything... - Parallel driving: Was thinking about it. It for sure performs better. With serial: for complete drawing of time, 1 second is sufficient; drawing latitude and longitude takes a bit longer... What adds pain to the (re)drawing is the absence of a fixed size font (...may be I just have not discovered it yet). Your hint to use a parallel interface makes me rethink my assumption about 'who' runs the drawing: a) Espruino - or b) LCD controller... and I tend now towards assuming a) Espruino. --- Btw, I like to 'fix' hardware using software... - or more accurately - complement and be complemented by hardware. ;).
-
• #7
There's a small bitmap font built-in, or you can load more as modules: http://www.espruino.com/Fonts
I should come up with a 'fast SPI' graphics driver really. If done right it should be able to support a few different modules - however the speed of
clear
is as fast as possible right now (which isn't that quick) so even if I make it as fast as possible, it'll never be amazing.If you're interested, there are other options:
You can't store the whole screen in Espruino's memory, because it's just too big. However you could:
- Store the screen in 1bpp, treat it as a black and white screen. (not sure how you'd convert 1bpp -> 16bpp quickly though)
- Render the whole screen, but in chunks. Eg. have an ArrayBuffer Graphics, of 240x32 - then render each of 10 chunks, and send each block over SPI. That'd be pretty speedy.
- Store the screen in 1bpp, treat it as a black and white screen. (not sure how you'd convert 1bpp -> 16bpp quickly though)
-
• #8
Ic. Many options. For now I have dashed the display speed challenge. With more changing data to display, this may change though, and all options will be back on the table. Fixed font for a limited number of characters / symbols in 1-bit foreground/background color mask and may be compressed/deflated will save me the storage. Using a streaming algorithm that just in time inflates two levels - first from compressed to full 1 bit mask and second from 1 to 18 bits by picking 18 bits background or 18 bit foreground depending the filter bit and sent them over a parallel interface, the display may give me the speed I could just dream of . To have performing inflations most likely require 'going underground' to (inline) assembler or using dedicated helper added to the firmware. This approach eliminates then the current drawing of a rectangle in background color before redrawing with current drawString()... ;) - Btw, with limited font options, all digits of a font should be same width, preferably two spaces and two dots (decimal points) wide, and also the -, +, and parenthesis should have this width. These measures provide the means to manageably display changing number values in fixed font and related (unchanging) labels in variable font. Time may come and make me build such a font.
-
• #9
Extended the display with the display of the velocity. Velocity is shown in nautical miles. Code is updated.
Next steps are:
- Migrate code to use GPS module version as recommended by @Gordon in particular post http://forum.espruino.com/comments/11859055/ GPS Module, Extensions, and u-blox NEO-6M GPS receiver
- Add battery power supply, stick it into a water proof box / container, and take it on the water.
- Add Wifi module and create a http server to give access to navi data over LAN / WAN.
- Write a first cut of a browser app - of course in JavaScript - that pulls the data with an AJAX request, processes is and displays it text and graphics... and eventually will drive a widget or window with a google map positioned to the received geo location.
- Add touch 'platform' w/ handling APIs - .handleTouch() .
- Add Pause / Resume / Restart(Warm restart) using touch areas.
- Add touch areas for complete clear of background and redraw of individual rows.
Time line is open....
- Migrate code to use GPS module version as recommended by @Gordon in particular post http://forum.espruino.com/comments/11859055/ GPS Module, Extensions, and u-blox NEO-6M GPS receiver
-
• #10
:) Actually it's pretty neat that your display has the touchscreen on. The 2.2" ones have the foil on them, but for some unknown reason they didn't bother bringing it out to connections!
The end goal is to have a GPS that is also connected to other inputs - such as Wind direction, Wind speed, heeling, etc. Of course: there is plenty of electronics to buy (and rely on), but it is a fun and entertaining training project to get all these things working together.
Basis for the basic GPS code (module) is the extended code as posted with title GPS Module, Extensions for handling other sentences, and u-blox NEO-6M GPS receiver on http://forum.espruino.com/conversations/255757/. Get the GPS code from there.
The gpsHandlerObject is picking up and transforming the gps receiver sentences and sending it to the navi object which displays the information on the LCD display (code below).
In this stage, the whole Hardware and Software system works as GPS / satellite driven clock... with time zone adjustment ;). Provisions for handling changes due to jitters and displaying of other essential data are in infancy to absence.
The satelliteClock.mp4 clip - link below the code - shows a send-to-board / restart phase.
The satelliteClock.mp4 clip (link below) shows a send-to-board / restart phase. Notice the nice skipping of displaying some time, and also that only the part of the string that changes is redrawn. Uncommenting line 28 will show the not redrawn part as dots followed by the part to redraw on the LCD in the console pane (see below). Most of the code of .timc() method is dedicated to that part and is combined with the time zone adjustment for display. In other words, internally, UTC is used, and converted only to local time for display.
The difference detection algorithm has though still a flaw! What is the flaw? When will it show? And how could it be fixed with practically the same code lines?
2 Attachments