diff --git a/datasheets/XPT2046.pdf b/datasheets/XPT2046.pdf new file mode 100644 index 000000000..62edb4210 Binary files /dev/null and b/datasheets/XPT2046.pdf differ diff --git a/devices/XPT2046.js b/devices/XPT2046.js new file mode 100644 index 000000000..2d2d4919c --- /dev/null +++ b/devices/XPT2046.js @@ -0,0 +1,106 @@ +/* Copyright (c) 2013 Gordon Williams, Pur3 Ltd. and 2016 muet.com See the file LICENSE for copying permission. */ +/* + +----- XPT2046 ------------------------- 77 vars, 85 with calibrated calculation. + +Module for connecting to the XPT2046 resistive touchscreen controller (pin and funcgtion compatible to ADS7843). + +The callback function has four arguments (X, Y, readData and module). When you move your finger on the touchscreen the X and Y coordinates are reported back, and when you lift your finger, the callback function is called once with X and Y and readData set to undefined. + +Connect expects a calculation function that converts raw x and raw y to x and y. Calculation formula is rawValue / rawValuePerPixel + offset for x and y. Calculation arguments are rawX, rawY, readData and module. Calculation returns x, y, readData and module. + + +Usage + +Landscape format: + +``` +SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); +var touch = require("XPT2046").connect( + SPI2, B10, B1, function(x, y){ + if (x !== undefined) { + console.log( x + " @ " + y); + } + }, function(yR, xR, d, m) { // portrait 240 x 320 + return [ //rawVal / valPerPx * offset + Math.round(xR / -121.44 + 259.707) + , Math.round(yR / 88.904 + -19.781) + , d, m + ]; + }).listen(); +``` + +Creates this output when swiping diagonally from top/left to +bottom/right on a 240 x 320 portrait display w/ touch screen: + +``` +24 @ 30 +45 @ 64 +66 @ 108 +100 @ 153 +139 @ 203 +171 @ 262 +208 @ 308 +``` + +Landscape format: + +``` + ... + }, function(xR, yR, d, m) { // landscape 320 x 240 + return [ //rawVal / valPerPx + offset + Math.round(xR / -88.904 + 339.781) + , Math.round(yR / -121.44 + 259.707) + ... +``` + + */ + +exports = // XPT2046 module - +{ poll: 50 // default poll interval when touched in milliseconds +, calc: function(xRaw, yRaw, data, module) { // default x / y + return [xRaw, yRaw, data, module]; // ...calculation... + } // ... (pass thru) +, lstn: false // is listening to touch and untouch (calling back) +, listen: function(lstn) { + this.lstn = ((lstn === undefined) || lstn); + return this; + } +, connect: function(spi, cs, irq, callback, calc, poll) { + // overwrite default 'calculation' (pass thru raw values) + if (calc) { this.calc = calc; } + // overwrite default poll interval (50) in milliseconds + if (poll) { this.poll = poll; } + // 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 data = spi.send([0x90,0,0xD0,0,0], cs); + if (this.lstn) { + callback.apply( + undefined + , this.calc( + data[1]*256+data[2], data[3]*256+data[4] + , data + , this + ) + ); + } + } else { // 'untouch' + clearInterval(interval); + interval = undefined; + if (this.lstn) { + callback(undefined, undefined, undefined, this); + } + setWatch(watchFunction, irq + , { repeat : false, edge: "falling" }); + } + }.bind(this), this.poll); // poll interval on touch + }.bind(this); + setWatch(watchFunction, irq + , { repeat : false, edge: "falling" }); + return this; + } +}; diff --git a/devices/XPT2046.md b/devices/XPT2046.md new file mode 100644 index 000000000..5f48d3936 --- /dev/null +++ b/devices/XPT2046.md @@ -0,0 +1,435 @@ + +XPT2046 Resistive Touchscreen Controller +================= + +* KEYWORDS: Module,HYSTM32,HYSTM32_24,HYSTM32_28,HYSTM32_32,Touchscreen,SPI,XPT2046,ADS7843,Sensor + +![XPT2046 (ADS7843) Touchscreen Controller Module Calibration](XPT2046/module.jpg) + +Overview +----------------- + +The XPT2046 chip is a common touchscreen controller used in a lot of boards. The XPT2046 is pin and function compatible with the ADS7843 (data sheets: [XPT2046](/datasheets/XPT2046.pdf) and [ADS7843](/datasheets/ADS7843.pdf)). + +Both chips can be handled by either [[XPT2046.js]] or [[ADS7843.js]] modules. The difference between the modules is that the XPT046 module has the function externalized that calculates x and y from raw values read from the chip. Externalization allows a more precise calculation and more so consideration of different coverage and alignment of the touch screen relative to the display. + +About how a resistive touchscreen works see forum conversation about [Resistive Touchscreen directly (no touch controller)](http://forum.espruino.com/conversations/256122). The conversation also shows how to make Espruino the touchscreen controller to handle bare touch screens. + +Touchscreen challenges +----------------- + +The ADS7843 touchscreen controller module (implementation approach) has challenges with + +- touch screen and display having different sizes +- touch screen porches (overhanging the display) +- alignment of touch screen and display +- variation by manufacturers for the displays, touchscreens, and carrier boards +- no (runtime) customization + +The simplified calculation of x and y from raw data with given connect parameters - with, hight, x offset, y offset - is fix built into [[ADS7843.js]] and there is no module (internal) calibration possible. For more details about the ramifications for accuracy see forum conversation about [Touchscreeen](http://forum.espruino.com/conversations/292641). + +[[XPT2046.js]] touch screen controller is based on ADS7843's logic for hardware handling, but allows customizing the externally accessible calculation function to overcome explained shortcomings. To support a wide variety of customization from simple to complex to comprehensive, the integration of the function passes next to the raw x and y values also the raw data as read from the chip AND the module. The customizable calculation function defines in the returned array what is passed to the callback. In other words, the module is a solid framework to handle the hardware events where as the calculation function is the glue to the application for delivering the desired values in an adjustable manner. + +XPT2046 vs. ADS7843 +----------------- + +The most obvious difference to the ADS7843 module is the externalized calculation function to calculate actual x and y from raw values, value per pixel (scale) and offset to cover all aspects of linearity and offset. Because the touchscreen can extend beyond the display, x and y can be negative or larger than maximum display with and. + +Other differences are: + +- touchscreen events can be listen to or be ignored by `touch.listen()` or `touch.listen(true)` or `touch.listen(false)` (any truey or falsy value does the job). + +- poll intervall on touch can be set custom on connect or later with `touch.poll = 20;` (in milliseconds. Default is 50 milliseconds, which means x and y position are polled and callback is invoked 20 times per second. + +Last but not least, the controller chip has auxiliary ADC inputs to measure, for example, battery power, and it has also a built-in temperature sensor. Any of these features are not yet taken advantage of in the existing modules, but could be nicely tapped into with extensions. + +**Note:** You can use this module also on a board with a built-in LCD. Run calibration to determine values for calculation function to convert x and y from raw values. For wiring take a look at [Touchscreen](Touchscreen). + +In the module, `connect(...)` takes arguments for + +1. spi : SPI +2. cs : Chip Select - to make chip listen to SPI communication +3. irq : IRQ - indicates when touched +4. callback : callback function - application function invoked on touch event +5. calc : calculation function - function to calculate x and y from chip raw data +6. poll : optional poll interval on touch - for customization + +Default for the poll interval on touch is 50 milliseconds, in other words, 20 time per second the controler is asked for the new touch position until not touched anymore. With faster clocked Espruino / Hardware, this can be adjusted to a shorter interval to give a smoother tracking. On the other hand, updating the display may put a limit to this, depending on the performance of the update. + +The callback function is - on touch - called, 'usually' with four arguments (as returned as array from calculation function: + +1. x - as defined / calculated by the calculation function +2. y - as defined / calculated by the calculation function +3. readData (raw format as received from chip through SPI as by the calculation function) +4 module (touch module as by the calculation function) + +The calc function is called for every touch event (except untouch) and is provided four arguments: + +1. xR : x raw as derived from readData (or yR) +2. yR : y raw as derived from readData (or xR) +3. d : readData - as read from the chip on touch event +4. m : module - touch module for easy access in callback for adjust module behavior dynamically + +When you move your finger or pen on the touchscreen, x and y coordinates are reported back every poll interval until you lift your finger or pen, where the callback function is called once with x, y and readData set to undefined. + +The XPT2046 module is implemented as singleton object (versus anonymous function with inaccessible values for the ADS7843) and passed into the callback to allow dynamic behavior of the controller set forth in the callback... for example increase or slow down polling on touch up to cease listening to touch events while callback is in process... + +Usage +------------ + +Display with touchscreen and related modules can be used in portrait or landscape. Default for both display and touch screen module are portrait (even though touchscreen hints more for landscape. + +To use the display with touchscreen in portrait, the calculation function takes the first parameter as raw y and the second one as raw x, where as for landscape, the first is taken as raw x and the second one raw y. Signs and offsets are different, but absolute values per pixel stay the same except they are swapped for x and y. + +For both portrait and landscape usage, 0 @ 0 is top / left: x-axes starting at the left border and increasing to the right one, and y-axes starting at the top and increasing to the bottom. Placement of 0/0 axis' directions can though be defined differently, for example, 0 @ 0 in the center of the display and touchscreen with x increasing to the right and y increasing to the top. + +All of the definitions are done in the externalized calculation function - and can be adjusted at runtime... + +Portrait format usage +------------ + +To use the display with touchscreen in portrait, the calculation function takes the first parameter as raw y and the second as raw x. + +```JavaScript +// touch22_240Wx320H_portrait.js +SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); +var touch = require("XPT2046").connect( + SPI2, B10, B1, function(x, y){ // ignoring readData and module parms + if (x !== undefined) { + console.log( x + " @ " + y); + } + }, function(yR, xR, d, m) { // portrait 240 x 320 + return [ //rawVal / valPerPx * offset + Math.round(xR / -121.44 + 259.707) + , Math.round(yR / 88.904 + -19.781) + , d, m + ]; + }).listen(); +``` + +Swiping diagonally from top/left to bottom/right on a 240 x 320 portrait display +w/ touch screen creates this output in the Esdpruino Web IDE console: + +``` +24 @ 30 +45 @ 64 +66 @ 108 +100 @ 153 +139 @ 203 +171 @ 262 +208 @ 308 +``` + +Landscape format usage +------------ + +To use the display with touchscreen in landscape, the calculation function takes the first parameter as raw x and the second as raw y. + +```JavaScript +// touch22_320Wx240H_landscape.js +SPI2.setup({sck:B13, miso:B14, mosi:B15, baud: 2000000}); + var touch = require("XPT2046").connect( + SPI2, B10, B1, function(x, y){ // ignoring readData and module parms + if (x !== undefined) { + console.log( x + " @ " + y); + } + }, function(xR, yR, d, m) { // landscape 320 x 240 + return [ //rawVal / valPerPx + offset + Math.round(xR / -88.904 + 339.781) + , Math.round(yR / -121.44 + 259.707) + , d, m + ]; + }).listen(); +``` + +Predefined calculation function +------------ + +Default calculation function is just passing on raw values: x raw, y raw, read data and module. It works well for calibration. + +Above calculation function is optimal for ('my' particular example of) [2.2" 240x320 262K Color TFT LCD with resistive touchscreen and display controller ILI9341](ILI9341), also shown in attached picture. + +The values may work ok for same display and touchscreen, but may not be good enough for real use, since the user precision is surprisingly high: about +/-1..2px... + +To determine the calculation function for 'your' device, use the calibration process and code as provided in the next section. + +Touchscreen calibration +------------ + +The calibration process - to specify the calculation function - consists of 4 major phases: + +1. Display five (5) calibration markers with known coordinates in layout of dice's 5: top/left, top/right, middle/center, bottom/left and bottom/right. +2. Gather raw data by user - you - by touching - with a pen - the markers' cross hairs from left top to bottom right (in the sequence as listed under 1.). +3. Calculate from known marker positions and recorded raw values scalings - value per pixel - and offsets - for x and y - for calculation function, build it, show it and apply it to the touch module. To calculate the value per pixel and offset, all possible x and y deltas are taken into account and averages are calculated to balance out variations. +4. Validate applied calculation function by user - you - by touching - with a pen - the markers' cross hairs again and by comparing the console printed values vs. expected values as by the markers on the display. + +For actual use of the values in the calculation function, it is good enough to take the 5..6 most significant digits (required number of significant digits is relative to the display size in pixel). + +For displays of sizes other than 240x320, adjust ```dW``` and ```dH``` to get respective values. + +```JavaScript +// calibration.js +// +// 2.4" 240x320 262K Color TFT LCD ILI9341 on SPI1 with +// Resistive Touch Screen Controller XPT2046 on SPI2 +// using XPT2046 and out of box ILI9341 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; // width 240 pixel +var dH = 320; // height 320 pixel + +var dsp, dMod = require("ILI9341"), + tSPI=SPI2, tCs = B10, tIrq = B1, + 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:dW-20, y: 20, xOff:-66, yOff:-6} + ,{i:3, label:"center ", x:dW/2 , y:dH/2 , xOff:-29, yOff:14} + ,{i:4, label:"bottomLeft ", x: 20, y:dH-20, xOff: 14, yOff:-6} + ,{i:5, label:"bottomRight", x:dW-20, y:dH-20, 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 vm(v) { // return value rounded to 1/1000 (millis) + return Math.round(v * 1000) / 1000; +} + +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 + console.log(""); + console.log("3. ---- calculate calculation 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 + xsc = vm(xsc); xos = vm(xos); + ysc = vm(ysc); yos = vm(yos); + console.log(""); + console.log("Calciulation function is (copy-paste into application code):"); + console.log(""); + console.log(" function(yR, xR, d, m) { // portrait 240 x 320"); + console.log(" return [ //rawVal / valPerPx * offset "); + console.log(" Math.round(xR / " + xsc + " + " + xos + ")"); + console.log(" , Math.round(yR / " + ysc + " + " + yos + ")"); + console.log(" , d, m"); + console.log(" ];"); + console.log(" }"); + console.log(""); + touch.calc = function(yr, xr, d, m) { // 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 + , d, m]; + }; + + calibrated = true; + console.log(""); + console.log("4. ---- in calibrated, normal mode - validating:"); + 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 + console.log(""); + console.log("1. ---- Display markers."); + mrks.forEach(function(m) { + mrkr(dsp, m.x, m.y, mSiz, m.xOff, m.yOff); }); + console.log(""); + console.log("2. ---- in calibration mode - capturing raw values:"); + 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) { + xt = m1; yt = m2; + if (xt !== undefined) { + console.log(fInt(xt,4),fInt(yt,4)); + } + } else { + xt = m2; yt = m1; + if (xt !== undefined) { + // console.log(seq +1,": ",xt,yt); + // 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; + } + } + }).listen(); + }); +} + +onInit(); +``` + +Console output from calibration incl. Calculation function source: + +``` + 1v87 Copyright 2016 G.Williams +>echo(0); +=undefined + +1. ---- Display markers. + +2. ---- in calibration mode - capturing raw values: + touch markers from top left to bottom right. +1 topLeft 20 20 28715 3436 +2 topRight 220 20 4881 3537 +3 center 120 160 16735 16081 +4 bottomLeft 20 300 29159 28476 +5 bottomRight 220 300 4637 28611 + +3. ---- calculate calculation function +[ -119.17, -119.8, -118.54, -122.61, -124.24, -120.98 ] +[ 89.42857142857, 90.32142857142, 88.53571428571, 89.55, 89.6, 89.5 ] +[ 257.52998593762, 260.37554801886, 258.43163206220, 261.20274629828, 258.35718421705 ] +[ -18.39565789998, -19.52428463104, -19.69748972343, -18.20569102446, -19.71425150656 ] +x / y scale -120.89 89.48928571428 +x / y offset 259.17941930680 -19.10747495709 +1 topLeft 20 20 22 ( 2) 19 ( -1) +2 topRight 220 20 219 ( -1) 20 ( 0) +3 center 120 160 121 ( 1) 161 ( 1) +4 bottomLeft 20 300 18 ( -2) 299 ( -1) +5 bottomRight 220 300 221 ( 1) 301 ( 1) + +Calculation function is (copy-paste into application code): + + function(yR, xR, d, m) { // portrait 240 x 320 + return [ //rawVal / valPerPx * offset + Math.round(xR / -120.89 + 259.179) + , Math.round(yR / 89.489 + -19.107) + , d, m + ]; + } + +4. ---- in calibrated, normal mode - validating: + touch markers for validation. + 20 21 + 19 21 + 19 20 + 20 20 + 219 21 + 220 20 + 221 21 + 220 21 + 221 20 + 120 161 + 119 160 + 120 162 + 118 162 + 119 161 + 19 301 + 20 301 + 19 301 + 21 300 + 19 299 + 221 300 + 219 301 + 221 299 + 221 300 + 220 301 +> +``` + +See also [ILI9341 LCD CONTROLLER](ILI9341) - 2.2 / 2.8 / ... SPI Serial ILI9341 TFT Color LCD Screen Display with Touch 240x320 Module, and forum conversation about [Touchscreeen](http://forum.espruino.com/conversations/292641). +