-
• #2
Here it is, the calibration, resulting in a new module and a calibration function:
// TFT24TRCportraitCalibr2.js // // 2.4" 240x320 262K Color TFT LCD ILI9341 on SPI1 with // Resistive Touch Screen Controller XPT2046 on SPI2 // using out of box ILI9341 and ADS7843 modules. // // Wiring: // 2.4" // DSP PICO [<---USB // 1 VCC red 3 3.3 shared // 2 GND blk 1 GND shared // 3 CS blu 7 B6 CS LCD // 4 RST brn 9 A8 reset RST LCD // 5 D/C grn 8 B7 D/C LCD // 6 MOSI ylw 6 B5 SPI1 MOSI LCD // 7 SCK wht 4 B3 SPI1 SCK LCD // 8 LED org 18 A5 LED LCD // 9 MISO grn 5 B4 SPI1 MISO LCD // 10 T_CLK wht 23 B13 SPI2 SCK Touch // 11 T_CS ylw 22 B10 CS Touch // 12 T_MOSI grn 25 B15 SPI2 MOSI Touch // 13 T_MISO blu 24 B14 SPI2 MISO Touch // 14 T_IRQ blk 21 B1 IRQ Touch var dW = 240, dH = 320, dsp, dMod = require("ILI9341"), tSPI=SPI2, tCs = B10, tIrq = B1, touch, tMod = // require("ADS7843"); // future XPT2046 touchscreen controller // default calculation is a pass thru { calc: function(x, y, d) { return [x,y,d]; } // default calculation , connect: function(spi, cs, irq, callback, calc) { // overwrite default 'calculation': pass thru incl. raw values if (calc) { this.calc = calc; } // wake the controller up spi.send([0x90,0],cs); // look for a press var watchFunction = function() { var interval = setInterval(function () { if (!digitalRead(irq)) { // touch down var d = spi.send([0x90,0,0xD0,0,0], cs); callback.apply(undefined, this.calc(d[1]*256+d[2], d[3]*256+d[4], d)); } else { callback(); clearInterval(interval); interval = undefined; setWatch(watchFunction, irq, { repeat : false, edge: "falling" }); } }.bind(this), 50); }.bind(this); setWatch(watchFunction, irq, { repeat : false, edge: "falling" }); return this; } }; // marker specs (lik dice face 5; top left to bottom right) var mrks = [{i:1, label:"topLeft ", x: 20, y: 20, xOff: 14, yOff:-6} ,{i:2, label:"topRight ", x:220, y: 20, xOff:-66, yOff:-6} ,{i:3, label:"center ", x:120, y:160, xOff:-29, yOff:14} ,{i:4, label:"bottomLeft ", x: 20, y:300, xOff: 14, yOff:-6} ,{i:5, label:"bottomRight", x:220, y:300, xOff:-74, yOff:-6} ]; var mSiz = 9; // half of marker size, (0.7 of font size) // print marker at x/y of size s with x/y label function mrkr(d,x,y,s,xOff,yOff) { dsp.setColor(1,1,1); dsp.drawRect(x-s,y-s,x+s,y+s); dsp.drawLine(x-s,y,x+s,y); dsp.drawLine(x,y-s,x,y+s); dsp.setFontVector(s / 0.7); dsp.drawString(x + " / " + y, x+xOff, y+yOff); } function average(arry) { // return avarage of array values return arry.reduce( function(p,c) { return p + c; },0) / arry.length; } function fInt(i,l) { return (" " + i).substr(-l); } function logXY(m,xp,yp,l,d) { console.log( m.i , m.label , fInt(m.x,4), fInt(m.y,4) , fInt(m[xp],l), (d) ? "(" + fInt(m[xp] - m.x,4) + ")" : "" , fInt(m[yp],l), (d) ? "(" + fInt(m[yp] - m.y,4) + ")" : "" ); } function handleAverage(seq,xs,ys) { // log x / y average var m = mrks[seq], x = Math.round(average(xs)), y = Math.round(average(ys)); m.xAvg = x; m.yAvg = y; logXY(m,"xAvg","yAvg",6); if (seq === 4) { // all in, calc calibration parms and calib with new calc function var xscs = [], // xscales = x deltas per pixel yscs = [], // yscales = y deltas per pixel xoss = [], // xoffsets yoss = []; // yoffsets xscs.push((mrks[1].xAvg - mrks[0].xAvg) / (mrks[1].x - mrks[0].x)); // top x deltas xscs.push((mrks[2].xAvg - mrks[0].xAvg) / (mrks[2].x - mrks[0].x)); // top left - cent xscs.push((mrks[1].xAvg - mrks[2].xAvg) / (mrks[1].x - mrks[2].x)); // cent - top right xscs.push((mrks[4].xAvg - mrks[3].xAvg) / (mrks[4].x - mrks[3].x)); // bottom x deltas xscs.push((mrks[2].xAvg - mrks[3].xAvg) / (mrks[2].x - mrks[3].x)); // bot left - cent xscs.push((mrks[4].xAvg - mrks[2].xAvg) / (mrks[4].x - mrks[2].x)); // cent - top right console.log(xscs); yscs.push((mrks[3].yAvg - mrks[0].yAvg) / (mrks[3].y - mrks[0].y)); // left y deltas yscs.push((mrks[2].yAvg - mrks[0].yAvg) / (mrks[2].y - mrks[0].y)); // top left - cent yscs.push((mrks[3].yAvg - mrks[2].yAvg) / (mrks[3].y - mrks[2].y)); // cent - left bottom yscs.push((mrks[4].yAvg - mrks[1].yAvg) / (mrks[4].y - mrks[1].y)); // right y deltas yscs.push((mrks[2].yAvg - mrks[1].yAvg) / (mrks[2].y - mrks[1].y)); // top right - cent yscs.push((mrks[4].yAvg - mrks[2].yAvg) / (mrks[4].y - mrks[2].y)); // cent - bot right console.log(yscs); var xsc = average(xscs), ysc = average(yscs); mrks.forEach(function(m) { xoss.push(m.x - m.xAvg / xsc); yoss.push(m.y - m.yAvg / ysc); }); console.log(xoss); console.log(yoss); var xos = average(xoss), yos = average(yoss); console.log("x / y scale", xsc, ysc); console.log("x / y offset", xos, yos); mrks.forEach(function(m){ m.xc = Math.round((m.xAvg / xsc) + xos); m.yc = Math.round((m.yAvg / ysc) + yos); logXY(m,"xc","yc",4,true); }); // calibrated calc function set in touch: // x / y scale -121.5825 89.79285714285 // x / y offset 258.33405300927 -18.53090446265 touch.calc = function(yr, xr) { // xy raw return [Math.round(xr / xsc + xos) // / x scale per pixel + x offset ,Math.round(yr / ysc + yos) // / y scale per pixel + y offset ]; }; calibrated = true; console.log("---- calibrated, normal mode"); console.log(" touch markers for validation."); } } var calibrated = false; function onInit() { A5.set(); SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000}); // spi, dc, cs, rst, callback dsp = dMod.connect(SPI1, B7, B6, A8, function() { dsp.clear(); // print markers like dice face of 5 mrks.forEach(function(m) { mrkr(dsp, m.x, m.y, mSiz, m.xOff, m.yOff); }); console.log("---- in calibration mode:"); console.log(" touch markers from top left to bottom right."); // setup touchscreen with callback averaging // x and y sequence for calibration of // touchscreen to lcd display. SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); var seq = 0, xs = [], ys = [], x, y; touch = tMod.connect( // spi, cs, irq, callback, calc // landscape SPI2, B10, B1, function(m1, m2, rd) { // portrait var xt, yt; if (calibrated) { // calibrated mode xt = m1; yt = m2; if (xt !== undefined) { console.log(fInt(xt,4),fInt(yt,4)); } } else { // calibration mode xt = m2; yt = m1; if (xt !== undefined) { // console.log(rd); // collect x and y for averaging xs.push(xt); ys.push(yt); } else { // log x and y avarages in console handleAverage(seq, xs, ys); xs = []; ys = []; seq = (seq +1) % 5; } } }); }); } onInit();
Related console output:
1v86 Copyright 2016 G.Williams >echo(0); =undefined ---- in calibration mode: touch markers from top left to bottom right. 1 topLeft 20 20 29148 3490 2 topRight 220 20 4862 3495 3 center 120 160 17118 16160 4 bottomLeft 20 300 28996 28347 5 bottomRight 220 300 4706 28424 [ -121.43, -120.3, -122.56, -121.45, -118.78, -124.12 ] [ 88.775, 90.5, 87.05, 89.03214285714, 90.46428571428, 87.6 ] [ 260.01976284584, 260.03623188405, 260.95849802371, 258.76811594202, 258.75164690382 ] [ -19.25601574739, -19.31225645763, -21.76997549511, -18.85108263367, -19.71718957136 ] x / y scale -121.44 88.90357142857 x / y offset 259.70685111989 -19.78130398103 1 topLeft 20 20 20 ( 0) 19 ( -1) 2 topRight 220 20 220 ( 0) 20 ( 0) 3 center 120 160 119 ( -1) 162 ( 2) 4 bottomLeft 20 300 21 ( 1) 299 ( -1) 5 bottomRight 220 300 221 ( 1) 300 ( 0) ---- calibrated, normal mode touch markers for validation. 21 18 23 17 18 18 222 19 221 19 223 20 120 160 121 160 116 160 121 159 19 303 20 303 16 299 220 304 221 301 221 299 222 301 >
The calibration goes as follows:
30..53
provided a new module base on existing one that allows to set a customized calculation function
-30
shows the default - pass through function - which accepts the raw coordinates and the read data. Read data is pass just in case...
-41
shows the calculation invocation with the pre-calculation of the raw coordinates (same as known from existing modules: highByte * 256 + lowByte)
-48 and 49
are noteworthy where context of module / touch singleton is bound in order to usethis.calc(...)
function in nested anonymous watch and interval functions.
-176..187
raw values are collected while touching the calibration marks from top left to bottom right.
-97
starts the actual calibration calculations...
-98..117
calculates raw value per pixel based on known coordinates of the markers and measured values: scale value (raw value) / pixel = read value difference divided by pixel delta of the markers
-118..128
calculates the offset for each axis.
-126 and 127
show the calculated scale and offset values.
-128..132
calculates the new values from the initially read values (1..5
console output) with given scaling and offset and shows them in the console for verification (117..121
console output)
-134..141
sets the new calibrated calculation function in touch singleton
-143
switches into regular mode for verification of the calc function (touching/tapping the markers again shows new calculated values as expected.
1 Attachment
-
• #3
XPT2046.js module code:
/* XPT2046 (pin/function compatible to ADS7843) Resistive Touch Screen Controller module ` ` ` var touch, touchModule = require("XPT2046"); function onInit() { // ... // setup touchscreen SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); touch = tMod.connect( // spi, cs, irq, callback, calc // landscape SPI2, B10, B1, function(x, y, rd) { // portrait if (x !== undefined) { console.log(x,y); } }, function(yr, xr, d) { // calc function - converts raw data to x / y coordinates; // scale and offset values calculated using markers and default calc function. // see http://forum.espruino.com/conversations/292641 - Touchscreen // return [ Math.round(xr / xsc + xos) // / x scale per pixel + x offset // , Math.round(yr / ysc + yos) // / y scale per pixel + y offset return [ Math.round(xr / -121.44 + 259.70685111989) , Math.round(yr / 88.90357142857 + -19.78130398103) ]; }); // ... } ` ` ` */ exports = // XPT2046 module { calc: function(x, y, d) { return [x,y,d]; } // default calculation , connect: function(spi, cs, irq, callback, calc) { // overwrite default 'calculation': pass thru incl. raw values if (calc) { this.calc = calc; } // wake the controller up spi.send([0x90,0],cs); // look for a press var watchFunction = function() { var interval = setInterval(function () { if (!digitalRead(irq)) { // touch down var d = spi.send([0x90,0,0xD0,0,0], cs); callback.apply(undefined, this.calc(d[1]*256+d[2], d[3]*256+d[4], d)); } else { callback(); clearInterval(interval); interval = undefined; setWatch(watchFunction, irq, { repeat : false, edge: "falling" }); } }.bind(this), 50); }.bind(this); setWatch(watchFunction, irq, { repeat : false, edge: "falling" }); return this; } };
Application using the (minified) module code from local Web IDE sandbox/modules folder. Note the touchscreen module
.connect()
that is supplied with the calculation function to converts the raw screen controller data into x and y for the screen.// TFT24TRCportraitCalibr3.js - XPT2046 module test // // 2.4" 240x320 262K Color TFT LCD ILI9341 on SPI1 with // Resistive Touch Screen Controller XPT2046 on SPI2 // using out of box ILI9341 and ADS7843 modules. // // Wiring: // 2.4" // DSP PICO [<---USB // 1 VCC red 3 3.3 shared // 2 GND blk 1 GND shared // 3 CS blu 7 B6 CS LCD // 4 RST brn 9 A8 reset RST LCD // 5 D/C grn 8 B7 D/C LCD // 6 MOSI ylw 6 B5 SPI1 MOSI LCD // 7 SCK wht 4 B3 SPI1 SCK LCD // 8 LED org 18 A5 LED LCD // 9 MISO grn 5 B4 SPI1 MISO LCD // 10 T_CLK wht 23 B13 SPI2 SCK Touch // 11 T_CS ylw 22 B10 CS Touch // 12 T_MOSI grn 25 B15 SPI2 MOSI Touch // 13 T_MISO blu 24 B14 SPI2 MISO Touch // 14 T_IRQ blk 21 B1 IRQ Touch var dW = 240, dH = 320, dsp, dMod = require("ILI9341"), touch, tMod = require("XPT2046"); // marker specs (lik dice face 5; top left to bottom right) var mrks = [{i:1, label:"topLeft ", x: 20, y: 20, xOff: 14, yOff:-6} ,{i:2, label:"topRight ", x:220, y: 20, xOff:-66, yOff:-6} ,{i:3, label:"center ", x:120, y:160, xOff:-29, yOff:14} ,{i:4, label:"bottomLeft ", x: 20, y:300, xOff: 14, yOff:-6} ,{i:5, label:"bottomRight", x:220, y:300, xOff:-74, yOff:-6} ]; var mSiz = 9; // half of marker size, (0.7 of font size) // print marker at x/y of size s with x/y label function mrkr(d,x,y,s,xOff,yOff) { dsp.setColor(1,1,1); dsp.drawRect(x-s,y-s,x+s,y+s); dsp.drawLine(x-s,y,x+s,y); dsp.drawLine(x,y-s,x,y+s); dsp.setFontVector(s / 0.7); dsp.drawString(x + " / " + y, x+xOff, y+yOff); } function fInt(i,l) { return (" " + i).substr(-l); } function onInit() { A5.set(); SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000}); // spi, dc, cs, rst, callback dsp = dMod.connect(SPI1, B7, B6, A8, function() { dsp.clear(); // print markers like dice face of 5 mrks.forEach(function(m) { mrkr(dsp, m.x, m.y, mSiz, m.xOff, m.yOff); }); // setup touchscreen SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); touch = tMod.connect( // spi, cs, irq, callback, calc SPI2, B10, B1, function(x, y) { if (x !== undefined) { console.log(fInt(x,4),fInt(y,4)); } }, function(yr, xr, d) { // calc function - converts raw data to x / y coordinates; // scale and offset values calculated using calibration // markers and default calc function. For more details see // http://forum.espruino.com/conversations/292641 - Touchscreen // return [ Math.round(xr / xsc + xos) // / x scale per pixel + x offset // , Math.round(yr / ysc + yos) // / y scale per pixel + y offset return [ Math.round(xr / -121.44 + 259.70685111989) , Math.round(yr / 88.90357142857 + -19.78130398103) ]; }); }); } onInit();
Related console output (tapping on markers on screen).
NOTE the last 4 lines which lay outside of the display, because the touch screen has a pretty sizable porch at the bottom.
1v86 Copyright 2016 G.Williams >echo(0); =undefined 21 18 22 19 22 18 223 20 222 19 121 162 118 160 121 162 18 303 19 303 17 301 219 303 219 300 220 300 220 302 130 333 127 324 128 328 126 324 >
Note: This module is used in Modular and extensible UI framework and ui elements conversation.
1 Attachment
In many previous projects I used a 2.8" TFT 262K Color LCD display with resistive touchscreen. Since the display had no touchscreen controller, I implemented one on Espruino (Resistive Touchscreen directly (no touch controller)). Recently I wanted to use the display again, but for whatever reason it turned out to be dead not only like a possum, but really really dead. So I ordered some cheaper once of 2.2" size, since needed them just to verify code for the ILI9341 display controller.
The cheap 2.4" display showed though up with a XPT2046 touchscreen controller from XPTEK TECHNOLOGY CO., LTD, SHENZHEN. After consulting the data sheet it seems that the XPT2046 is functionally about equal to Burr-Brown's / TI's ADS7843 and thus Espruino's ADS7843 module works... at least, it returns coordinates... dodged that bullet... easily...
But from there on in went tricky to say the least... and unfortunately ended with the conclusion: module useless (from the point of usable values,.. and more), because the user's touch accuracy is about +- 2..3 pixels... but the module error is up to 32... just not useful for a touch UI.
Why?
Below code and and attached shot show. The code sets up the display and the touch screen, then draws calibration markers in all corners and the center and then logs values for calibration.
The issues are:
ILI9341 initializes in portrait with x=0..239 - 240 pixels wide - and y=0...319 - 320 pixels high - and 0 / 0 top left (see shot).
XPT2046 controller is wired portrait, but not in a twisted way... not just rotated by 90 degrees... It could be fixed with the some additional calculation: with or hight - coordinate touch value.
it is not good enough to have an x and y offset for the controller function to calculate the actual x and y for a given display size. The offset helps with the touch screen not aligning with the display, but it messes up the scaling: if the low value is ok, the high value is too much off and vice versa. This can be seen in the output generated by above code when touching the calibration points from top left to bottom right.
Because many touch screens are larger than the display because they show fixed, printed buttons in the overhang area. For touching in the overhang, proper calculation returns values beyond the screen coordinates.
Produced output (aligned and with headings added):
Looking at x coordinate, the low value is about correct, but the deviation for higher values goes linearly up by about 10 per 120 pixels (half screen width). The 'balconies' or 'porches'
(screen vs display sizes and alignments) impact the calculation beyond what an offset can handle.
Same can be said for the y coordinate: after adjusting with an offset, the error is even worse.
Since the issues are hardware - wiring, size and alignment dependent, a module has to provide an option to pass a calculation function that takes minimum AND maximum touch values into account to scale properly with the display size. I'll take a stab at it and will publish in a next post.
2 Attachments