• An other multi part series

    This conversation covers some leg work to be done for next steps in * DIY Marine GPS using enhanced GPS Module, u-blox NEO-6M GPS receiver, and ILI9341 Module controlled 2.8" Color TFT LCD* project (http://forum.espruino.com/conversations/­255759) and Exploring 2.8" Color TFT Touch LCD by PacMan Game attempt project (http://forum.espruino.com/conversations/­127039).

    In both projects I intend to use the touch capability of the display.

    The touch screen is a resistive touch screen. A good description you can find at https://www.sparkfun.com/datasheets/LCD/­HOW%20DOES%20IT%20WORK.pdf.

    Basically, a touch screen consists of two stacked 'sheets' with a small gap in between. When touching the (flexible) top sheet, it will touch the (rigid) bottom sheet in the area of the touch point.

    Cross cut / side view:
    
    Flexible top sheet:              --------------------------------------      
    small gap                                                                    
    Rigid bottom sheet:              --------------------------------------
    

    When touching the (flexible) top sheet, it will touch the (rigid) bottom sheet in the are of the touch point.

    Because the sheets' facing sides are coated with a conductive material with significant resistance versus a plain wire, and one sheet is put under current with one edge all along the edge to ground (0 Volts) and the opposite edge to supply (3.3 Volts), the other sheet senses at the touch point something in between 0 and 3.3 Volts.

    Cross cut / side view:                              Touched area             
                                                              |                   
                                                              V                   
    Flexible sheet under power:  0 V |--------------------.   @   .-------> 3.3 V
    small gap                                              \_____/               
    Rigid sheet sensing touch: 2.2 V <-----------------------^^^-----------
    

    The sensing sheet is connected to a pin (for example, C2) with an analog-to-digital converter (ADC), which provides through analogRead(C3); a voltage-related floating point value from roughly 0.000 to 1. Of course, the edges of the sheet under power are also connected to pins of which one is set to Ground (0 Volts) with C0.reset();, and the other one is set to supply (3.3 Volts) using C1.set(); in order to set that sheet under power.

    Assuming a screen of 240 horizontal (x) by 320 vertical (y) resolution and below axis configuration with origin {x:0,y:0} in the top left corner of the screen, and the top sheet's left edge to ground (0 V) at x=0 and right edge to supply (3.3 V) at x=239, a touch point sensing (2.2 V) is about 2 thirds in at about x=160.

    Top view:
    
      0V, C0   pins  C1, 3.3V
      |                  |   
      V  ---> x-Axis     V   
      .------------------.   
      |0              240|   
    | |      Touch       |   
    | |      point       |--.
    V |           \      |  |
    y |            @     |  |
    - |            :     |  |
    A |            :     |  |
    x |            :     |  |
    i | top        :     |  |
    s | powered    :     |  |
      |            :     |  |
      |320         :     |  |
      '------------------'  |
         | bottom  |        |
         | sensing |        |
         '---------o--------' 
                   |         
                   V         
       ADC pin C3, 2.2V
            --> x value
    

    But this is only the x-axis value... - what about the y-axis value?

    Ingeniously, the the bottom sheet has 90 degrees turned edge connectors as well - at the top and the bottom - of which only the bottom is used for the sensing.

    But: now roles of the sheets are flipped:

    1. The bottom sheet is now put under power by connecting the top edge to ground (0 V) through pin C2 and the bottom edge to (3.3 V) through pin C3.
    2. The top sheet is now the sensing sheet and connected (by either left or right edge) to the pin with the analog-to-digital converter.

    Et voila: we get the value y for the y-axis, because the a touch at the top is (0 V) and corresponds to y=0, and a touch at the bottom corresponds to (3.3 V) and corresponds to y=240; and a touch point a quarter way down will provide (0.825 V = 3.3 / 4 V) corresponding to a value of y=80.

    Top view:
    
      V  ---> x-Axis                   
      .------------------.             
      |0              240|             
    | |      Touch       |    pin C2   
    | |      point       |--.<----0V   
    V |           \      |  |          
    y |            @ ----o---->0.825V  
    - |                  |  |  pin C1  
    A |                  |  |  ADC     
    x |                  |  |  --> y
    i |                  |  |  value   
    s |                  |  |          
      |                  |  |          
      |320   top sensing |  |          
      '------------------'  |          
         |                  |          
         |   bottom powered |          
         '------------------'<---3.3V   
                               pin C1  
                                       
    

    I said basically... because looking into the produced values, it is not that a clear cut - at least not for all values.

    There is also much more to the building if a touch screen - for example: In order that the sheets to not accidentally touch by sagging or vibration over a too wide area, very small insulating spacers spread in the gap. But because the top is flexible, touching happens between the spacers (S)... --- Ignore the forum's syntax coloring... ;-)

    Cross cut / side view:
    
                \                             /
                 \     Touching Object:      /
                  \   'Soft, Fat' Finger    /
    ------------.  '-.___.-----._________.-'  .--------
                 \       _______             /
        S         '-----'   S   '-----------'     S
    ----------------^^^-------------^^^^^---­-----------
                     ^                ^
                     |                |
                     |                |
                   Multiple Touch 'Points'
    

    Edit 2018-07-21: Found attached pic on https://www.epectec.com/articles/the-tec­hnology-within-touch-panels.html:


    1 Attachment

    • touch_r_epectecDotCom.png
  • But there is also some good news:

    You may wonder whether the edge matters where the sensing sheet is connected. It does not (really). Why: the resistance of the coating from the touch point to the edge is (practically) negligible compared to the ADC pin's input impedance (inner resistance) and therefore practically no power (amperes) flows compared to the power that flows in the powered sheet's resistive coating, which is the voltage divider with the division point at the touch point. In other words, it is as if the pin would connect right there where the touch point is. It's all about Ohm's Law.

    And last but not least: your soft, fat finger creates anyway a bit a fuzzy touch area with 'a lot of (electrical) noise'. But because it is fat - compared to the location sensitivity of the touch screen (or pad), there have anyway to be multiple measuring and algorithm that find kind of the center of gravity of the touch point. But to that later.

    There are chips out there - touch controllers - that handle all the cases and corner cases, create an interrupt, and deliver (more then less) clean numbers through a simple connection interface (see http://www.espruino.com/ADS7843 - ADS7843 TOUCHSCREEN).

    But I want to explore how Espruino can do that, I connect Espruino directly to the edges of the sheets. The mentioned color TFT LCD is (or has) a (transparent) resistive touch screen (on top of the display) and the carrier/breakout board makes the 4 edges available (sequence as on the boards bottom connector from left to right, pins counted from 1...):

    // touch2X.js [1v70] [1v99] // updated 20180925
    //
    //  Pin      Touch  Connected to:       Connected to left/right /top/bottom edge
    //  on LCD   Screen Espruino Board      of top/bottom sheet... (TBFO)
    //  Carrier  Edges  ADC Pin             (|||| = touch screen connectors at bottom
    //  Board    Label  var Orig Pico               left of TFT LCD display)
    // 1. 11:     +Y    pYp C3   B1         '...... sheet' .... edge
    // 2. 12:     +X    pXp C1   A6         '...... sheet' .... edge
    // 3. 13:     -Y    pYn C2   A7         '.......sheet' .... edge
    // 4. 14:     -X    pXn C0   A5         '.......sheet' .... edge
    
    // Uncomment line w/ pin assignments to be used:
    //var pXn=C0, pXp=C1, pYn=C2, pYp=C3; // pin assignments Original
    var pXn=A6, pXp=B1, pYn=A7, pYp=A5; // pin assignments Pico
    
    var enabled = false; // for start and stop
    var minMaxDisplayed = true; // ...and 1st time reads determination
    var cnt,cnt0; // for 'new' tap (touch) detection
    var x ,xMin ,xMax;
    var y ,yMin ,yMax;
    var x2,xMin2,xMax2;
    var y2,yMin2,yMax2;
    var toggle = function() { enabled = !enabled; };
    var logXY = function() {
      readXY(function(xy) {
        if (enabled) {
          if (minMaxDisplayed) {
            minMaxDisplayed = false;
            xMin  = 99999; xMax  = -9999;
            yMin  = 99999; yMax  = -9999;
            xMin2 = 99999; xMax2 = -9999;
            yMin2 = 99999; yMax2 = -9999;
            cnt = 10000; cnt0 = cnt;
            console.log("----- enabled");
            console.log("cycle","x   ","y   "," ","x2  ","y2  ","  ","dx dy");
          }
          cnt++;
          x  = Math.round(xy.x  * 10000) ; y  = Math.round(xy.y  * 10000);
          x2 = Math.round(xy.x2 * 10000) ; y2 = Math.round(xy.y2 * 10000);
          if ((x > 1000) || (y > 1000) || (x2 > 1000) || (y2 > 1000)) {
            if (cnt > cnt0 + 4) { // tap gap detection - 5[Hz] - 200[ms]
              console.log("-----");
            }
            cnt0 = cnt;
            xMin  = (x  < xMin ) ? x  : xMin ;
            xMax  = (x  > xMax ) ? x  : xMax ;
            yMin  = (y  < yMin ) ? y  : yMin ;
            yMax  = (y  > yMax ) ? y  : yMax ;
            xMin2 = (x2 < xMin2) ? x2 : xMin2;
            xMax2 = (x2 > xMax2) ? x2 : xMax2;
            yMin2 = (y2 < yMin2) ? y2 : yMin2;
            yMax2 = (y2 > yMax2) ? y2 : yMax2;
            console.log(cnt,x,y,"-",x2,y2,"d#",(x-x2­),(y-y2));
          }
        } else {
          if (!minMaxDisplayed) {
            minMaxDisplayed = true;
            console.log("----- Min/Max values for x, y, and x2, y2:");
            console.log("x [",xMin ,"..",xMax," ] - y [",yMin ,"..",yMax ,"]");
            console.log("x2[",xMin2,"..",xMax2,"] - y2[",yMin2,"..",yMax2,"]");
            console.log("----- disabled");
          }
        }
       });
      setTimeout("logXY();",50); // 20[Hz] sample rate - 50[ms]
    };
    var readXY = function(callback) {
      var xy = {};
      pXn.set();
      pXp.reset();
      analogRead(pYn);
      analogRead(pYp);
      setTimeout(function(){
        xy.x  = analogRead(pYn);
        xy.x2 = analogRead(pYp);
        pYn.set();
        pYp.reset();
        analogRead(pXn);
        analogRead(pXp);
        setTimeout(function(){
          xy.y  = analogRead(pXn);
          xy.y2 = analogRead(pXp);
          callback(xy);
        },1);
      },1);
    };
    
    function onInit() {
     enabled = true;
     logXY();
    }
    
    setTimeout(onInit,1000);
    

    These are the delivered values:

    >toggle()
    =undefined
    ----- enabled
    cycle x    y      x2   y2      dx dy  (post run added comment)
    -----                           ...was tap in top left corner
    10146 1338 908 - 1345 903 d# -7 5
    10147 1340 896 - 1348 906 d# -8 -10
    ----- 
                                    ...was tap in top right corner
    10172 8569 999 - 8574 1003 d# -5 -4
    10173 8567 1006 - 8562 999 d# 5 7
    10174 8557 1003 - 8555 996 d# 2 7
    10175 8450 288 - 8323 405 d# 127 -117
    -----                           ...was top in bottom left corner
    10204 1543 8565 - 1538 8587 d# 5 -22
    10205 1533 8574 - 1528 8574 d# 5 0
    10206 1531 8555 - 1526 8560 d# 5 -5
    10207 1526 8567 - 1533 8565 d# -7 2
    -----                           ...was tab in bottom right corner
    10232 437 415 - 569 8633 d# -132 -8218
    10233 8518 8655 - 8508 8650 d# 10 5
    10234 8486 8650 - 8499 8648 d# -13 2
    10235 8499 8648 - 8504 8662 d# -5 -14
    10236 8508 8665 - 8511 8655 d# -3 10
    10237 8489 8682 - 8486 8672 d# 3 10
    -----                           ...was tap in (about) the center 
    10270 4829 4763 - 4829 4746 d# 0 17
    10271 4836 4753 - 4854 4761 d# -18 -8
    10272 4834 4756 - 4827 4753 d# 7 3
    10273 4834 4763 - 4829 4763 d# 5 0
    -----                           ...horizontal line starting in top left corner 
    10304 1448 894 - 1448 903 d# 0 -9
    10305 1638 974 - 1638 969 d# 0 5
    10306 2344 1050 - 2346 1045 d# -2 5
    10307 3274 1106 - 3274 1108 d# 0 -2
    10308 4622 1089 - 4612 1091 d# 10 -2
    10309 5684 1150 - 5686 1157 d# -2 -7
    10310 6729 1184 - 6734 1177 d# -5 7
    10311 7537 1157 - 7532 1157 d# 5 0
    10312 8113 1123 - 8125 1116 d# -12 7
    10313 8284 1138 - 8279 1226 d# 5 -88
    -----                           ...vertical line starting in top left corner
    10333 1433 1091 - 1426 1091 d# 7 0
    10334 1423 1416 - 1426 1433 d# -3 -17
    10335 1377 2512 - 1389 2522 d# -12 -10
    10336 1428 3867 - 1436 3877 d# -8 -10
    10340 1272 7954 - 1262 7947 d# 10 7
    10341 1265 8552 - 1255 8557 d# 10 -5
    -----                           ...diagonal line starting in top left corner
    10370 393 1116 - 520 1111 d# -127 5
    10371 1643 1812 - 1633 1819 d# 10 -7
    10372 2427 2874 - 2432 2881 d# -5 -7
    10373 3276 3867 - 3281 3879 d# -5 -12
    10374 4116 4795 - 4124 4788 d# -8 7
    10375 5217 5830 - 5234 5830 d# -17 0
    10376 5977 6758 - 5982 6751 d# -5 7
    10377 6912 7578 - 6914 7571 d# -2 7
    10378 7334 7952 - 7341 7947 d# -7 5
    10379 7764 8245 - 7766 8255 d# -2 -10
    10380 7857 8389 - 7839 8381 d# 18 8
    10381 7859 8589 - 7891 8587 d# -32 2
    -----                           ...diagonal line starting in bottom left corner
    10406 1382 8650 - 1382 8667 d# 0 -17
    10407 1631 8362 - 1633 8352 d# -2 10
    10408 2302 7559 - 2322 7556 d# -20 3
    10409 3355 6453 - 3359 6443 d# -4 10
    10410 4587 5125 - 4583 5134 d# 4 -9
    10411 5689 3997 - 5693 3994 d# -4 3
    10412 6697 2986 - 6692 2981 d# 5 5
    10413 7422 2200 - 7439 2205 d# -17 -5
    10414 7952 1523 - 7944 1523 d# 8 0
    10415 8338 1160 - 8347 1152 d# -9 8
    >toggle();
    =undefined
    ----- Min/Max values for x, y, and x2, y2:
    x [ 393 .. 8569  ] - y [ 288 .. 8682 ]
    x2[ 520 .. 8574 ] - y2[ 405 .. 8672 ]
    ----- disabled
    >
    

    After sending to the board, the code immediately starts running. With a frequency of 20Hz - twenty times per second / in 50[ms] intervals - it invokes the self explanatory logXY() function. Since the code is initialized with var enabled = false, the main part of the function body is skipped. At the end of the method, the method sets a timeout - of 50[ms] - for self-invocation.

    After enabling with command toggle(); in command pane, the code initializes some control values and various minimum and maximum variables, and prints a header in the console pane for the values to be logged (lines 27..36). Furthermore, the code starts to count in cnt variable - from 10000 upwards for aligned column oriented output - the cycles - 20 per second - as defined by the setTimeout (line 65) and invokes the reading of x and y (voltage) values. Since the reading needs some setup and settling time, the statistics / calculation and logging is passed as a callback to the readXY() function. More about that readXY() function a bit later.

    The callback code will detect when a touch happens by the fact that any of the x or y values are beyond a certain threshold: 1000 (line 40). The threshold's value was determined by some code tinkering and is touch screen dependent. The initial value ranging from 0.0000 to 1 is multiplied by 10000 and rounded to an integer (line 38) to trigger the set threshold, and to ease the - optical - tracking of value changes and detecting value and value change patterns. When the threshold is overstepped, the current cnt counter is checked against the past cnt2 counter and if there is a difference of more than 4 (missed cycles - 200[ms], line 41), the touch (tap) is considered a new one. past cnt2 is updated to current cnt as long as the touch keeps going (line 44).

    Lines 45 thru 52 do statistics to keep track of the minimum and maximum values.

    Finally, line 53 prints the following values:

    1. Cycle as counted by cnt variable. It is consecutive for each touch or tap including related moving/draggin on the touch screen. A new touch (or tap) related value sequence is separated from the previous one by a line of five (5) dashes (with posthume added comment about the touch/drag action).
    2. x and y values - as read from one edge of the sensing sheet.
    3. x2 and y2 values - as read from other edge of the sensing sheet.
    4. the differences between x and x2 and y and y2.

    As you can see, the difference of the values from both edges are very small compared to the read values (with a few exceptions). This proofs, that is sufficient to read just from one edge of the sensing sheet. Codes touch2.js (as shown above) and touch3.js (reading from one edge of the sensing sheet only) are attached as files at the very bottom of this post.

    The reading algorithm with its settings of the pins and stabilization times is in lines 68 thru 87 - implemented as readXY() function.

    @Gordon, could you please comment on optimal pin setting and reading including timing and provide advice? = From what I understood from other implementations is that it is crucial for clean measuring that no output conflicts are there and powering of the to-power sheet has to stabilize, as well as the sensing ADC pin has to be ready and eventual capacitive energy has been discharged before measurements are taken. Most applications take multiple measurements and increase the accuracy of the (virtual) touch point coordinates.

    If the same effects can be achieved with less statements the better. Because when integrated into any other apps, every spared (cpu) cycle will be welcome to not just detect a touch or swipe, but more so to process it and its related biz or tech process.

    So much for now. Next steps will be to get some basic algorithm for increasing accuracy of mapping of read value to actual coordinate on screen. For that, some material is there done as part of designing dedicated touch controllers (see http://www.ti.com/lit/an/sbaa036/sbaa036­.pdf, http://www.ti.com/lit/an/slyt277/slyt277­.pdf, and most helpful http://www.ti.com/lit/an/sbaa155a/sbaa15­5a.pdf - Reducing Analog Input Noise in Touch Screen Systems). After that a module will be sketched that includes also some basic calibration support - Do you remember when you had to tap with your stylus X-es in the corners and center of the screen?

    Codes attached as files touch2X.js:


    1 Attachment

  • Looks great - it could make a really handy module...

    About pins, what you've got looks good. Personally, I'd change the code to use the multiple digitalRead/Write functionality - and just for safety I'd make sure you switch the pins to inputs before you set the other pins to outputs...

    I'm not sure if using digitalRead (instead of analogRead) will work for setting state, but it's worth a try.

    Also it's worth trying without the setTimeouts - as the code takes a while to execute anyway, you'll probably find you don't need them.

    var readXY = function(callback) {
      var xy = {};
      digitalRead([C2,C3]);
      digitalWrite([C1,C0],1);
      xy.x  = analogRead(C2);
      xy.x2 = analogRead(C3);
      digitalRead([C0,C1]);
      digitalWrite([C3,C2],1);
      xy.y  = analogRead(C0);
      xy.y2 = analogRead(C1);
      callback(xy);
    };
    

    or with multiple reads:

    var readXY = function(callback) {
      var xy = {};
      digitalRead([C2,C3]);
      digitalWrite([C1,C0],1);
      xy.x  = (analogRead(C2)+analogRead(C2))/2;
      xy.x2 = (analogRead(C3)+analogRead(C3))/2;
      digitalRead([C0,C1]);
      digitalWrite([C3,C2],1);
      xy.y  = (analogRead(C0)+analogRead(C0))/2;
      xy.y2 = (analogRead(C1)+analogRead(C1))/2;
      callback(xy);
    };
    

    One neat addition would be to detect taps without polling, and then to only poll when you know a finger is pressed. I'm not 100% sure if this works, but what about:

    function listen() {
      pinMode(C0,"input_pulldown");
      digitalRead(C1);
      digitalWrite([C2,C3],3);
      setWatch(function() {
        pinMode(C0); // reset pin state
        // start polling... when finger is released, stop polling and call 'listen' again
      }, C2, {edge:rising, repeat:false});
    

    That'd effectively stop using CPU cycles when a finger wasn't pressed. For other applications where you want long battery life it'd be handy too.

  • just for safety I'd make sure you switch the pins to inputs before you set the other pins to outputs...

    I had intended that - ...crucial... that no output conflicts are there... - but obviously missed to code it properly... :(

    Blind or continuous polling is not - practically never - sustainable, especially when not the only thing that has to go on - not just in regard of (battery) power consumption, but more so for wasting cycles. Was going for touch begin and end detection in a later state when having figured out the calculation stuff. For now, I just have the counters. I'll definitively try the provided code...

    When I thought about the edge-event drivenness, I was not sure if I would get always a clean edge-trigger because of the voltage divider circuitry of the powered plane edges preset to supply and ground voltage. Your code I read the solution: you set both C2 AND C3 to high, C1 to a float (free, 'un-pulled input), and C0 to (pulled-down) input with (one time only) edge trigger (interrupt set to be fire only once). I see no reason why this should not work for a touch begin-detection. After a begin has been detected, the pin settings for polling and polling will take place until end-detection. End-detection is required - because the trigger has to be setup again (you may notice that in my code has only begin-detection).

    When timeout can be dropped, then there is no need for callback pattern either. Callback was only introduced to make delayed reading possible. Some testing could show if the code execution creates enough delay for the outputs and inputs to stabilize / settle.

  • ...have a hard time to detect when to listen again. Btw, in the listen(), setWatch() has to be on C1 and not on C2, because latter has just been set to high and therefore will never rise... ;).

    The issue I have is after 'untouch' to read values different from when touched. With suggested readXY() I get values within the touched range. Interestingly, I did not get such values with my initial code (touch2.js). After 'fixing' the listen(), I used a hybrid of suggested listen() and my initially working readXY() code, but with the same result: for untouched I get values within the range of touched.

    So, I have to do a bit backing out....

  • After backing out and rebuilding up to including the onDown touch event and investigating how to detect an onUp untouch event, I ended up with a code containing the core elements shown below as listen() and read xy() methods (code uploaded as touch4.js). The read xy() method is not just for reading the coordinates onDown, but later also for possible dragging while touching (onTrack), and - last but not least - for 'untouch' (onUp). Since with every event time is provided in addition to the x-y coordinates, touch durations can be used to determine short, long, very long, and dobule touches or taps.

    The code incorporates the suggestions from @Gordon. The essentials in a nutshell:

    1. detect a touch by hardware event using setWatch() in listen() method- rational: no continuous polling; enter polling mode only while touching for tracking
    2. eliminate the setTimeout() for reading x and y in read xy() method - rational: execution of the JS code provides enough time for the pins to settle on output for powering plane one and on input for analog-reading of plane two.

    // touch4.js [1v70]
    
    function TOUCH(xn,xp,yn,yp,onDownCB) { // pins in example: C0,C1,C2,C3...
      this.xn = xn; // ...C0
      this.xp = xp; // ...C1
      this.yn = yn; // ...C2
      this.yp = yp; // ...C3
      this.onDownCB = onDownCB;
      this.x = this.x0 = this.x1 = -1;
      this.y = this.y0 = this.y1 = -1;
      this.t = this.t0 = this.t1 =  0;
    }
    
    var pt = TOUCH.prototype;
    
    pt.listen = function() {
      var _this = this;
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],3);
      setWatch( function(){ 
        pinMode(_this.xn);
        _this.xy(_this._onDown);
       }, this.xp, {edge:rising, repeat:false} );
    };
    
    pt._onDown = function() {
      this.onDownCB(this, this.x0 = this.x, this.y0 = this.y, this.t0 = this.t);
    };
    
    pt.xy = function(callback) {
      this.t = new Date().getTime();
      digitalRead([this.yn,this.yp]);
      digitalWrite([this.xn,this.xp],2);
      this.x = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;
      digitalRead([this.xn,this.xp]);
      digitalWrite([this.yn,this.yp],2);
      this.y = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      if (callback) { callback.call(this); }
    };
    
    // -----
    
    var touchModule = 
    { connect: function(xn,xp,yn,yp,onDown) {
        var touch = new TOUCH(xn,xp,yn,yp,onDown); touch.listen();
        return touch;
      }
    };
    
    var cnt = -1, cnt2;
    touchModule.connect(C0,C1,C2,C3,function­(touch,x,y,t){
      cnt2 = 0; console.log(++cnt,cnt2,"onDown:",x,"-",y­,"@",t);
      setTimeout(function(){
        touch.xy(); 
        setTimeout(function(){
          console.log("untouched: ",touch.x,"-",touch.y,"@",touch.t);
          console.log("---");
          setTimeout(function(){ touch.listen(); },100);
        },100);
       },800);
     });
    
    

    This is the output for touching the four corners ( 240 (x) px horizontal x 320 (y) px vertical screen):

    1. top-left corner (x=0, y=0)
    2. top-right corner (x=239, y=0)
    3. bottom-left corner (x=0, y=319)
    4. bottom-right corner (x=239, y=319)

    >echo(0);
    =undefined
    0 0 onDown: 0.14363825945 - 0.09122860557 @ 239187.00858778625
    untouched:  0.45956104880 - 0.23576206098 @ 239992.94179389314
    ---
    1 0 onDown: 0.84319320464 - 0.09904122479 @ 243184.38072519082
    untouched:  0.45630579079 - 0.23633173113 @ 243990.34160305344
    ---
    2 0 onDown: 0.14290582640 - 0.85898120597 @ 246428.01812977099
    untouched:  0.28068462144 - 0.32902520281 @ 247233.97805343510
    ---
    3 0 onDown: 0.85588871086 - 0.85653976246 @ 249302.83683206106
    untouched:  0.27962666259 - 0.33260598662 @ 250108.79580152672
    ---
    > 
    

    The above data shows the trouble in the untouched stage: untouched values are in the range of touch min-max values. Elaborating on different items - even bringing in the reading of the xy from the old touch2.js code with the 1[ms] timeouts, which worked before for detecting the untouched state - did not help. Just having dropped the continuous polling and the timeouts for a faster and cycle/time-leaner execution, I did not want to go back and increase the timeouts to may-be have success.

    The analysis of the data - untouched values are influenced by the previously touched area - and the difference between touch2.js and touch4.js code - unintended output-clashes - made me think that there is not enough time to 'discharge' the previously powered plane sufficiently to be ready for reading.

    Adding very brief input with pull-down on the reading plane pin AFTER powering the powered plane provided the solution. Somehow, the lingering capacities between the planes in combination with the very sensitive, input-/sink-frugal ADCs lead to the 'misreadings'. Below the xy() method with the added lines to sink-away lingering capacities, and the new, related output data.

    pt.xy = function(callback) {
      this.t = new Date().getTime();
      digitalRead([this.yn,this.yp]);
      digitalWrite([this.xn,this.xp],2);
      pinMode(this.yn,"input_pulldown");     // <--- extra lines to sink-away
      pinMode(this.yn);                      // <--- ...lingering capacities 
      this.x = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;
      digitalRead([this.xn,this.xp]);
      digitalWrite([this.yn,this.yp],2);
      pinMode(this.xn,"input_pulldown");     // <--- extra lines to sink-away
      pinMode(this.xn);                      // <--- ...lingering capacities 
      this.y = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      if (callback) { callback.call(this); }
    };
    

    The new data show now untouched values that are comfortably outside of the range of the touched values.

    >echo(0);
    =undefined
    0 0 onDown: 0.15332265201 - 0.09342590473 @ 396399.69179389311
    untouched:  0.05224689097 - 0.03605198240 @ 397206.41984732821
    ---
    1 0 onDown: 0.85312174156 - 0.10058747234 @ 399303.29770992364
    untouched:  0.05485109737 - 0.03580783805 @ 400110.02385496185
    ---
    2 0 onDown: 0.13517458864 - 0.86378271152 @ 402053.27290076337
    untouched:  0.05843188118 - 0.03605198240 @ 402860.03339694655
    ---
    3 0 onDown: 0.84970372065 - 0.84156557564 @ 404042.44847328244
    untouched:  0.05875740698 - 0.03637750820 @ 404849.19465648854
    ---
    > 
    

    The pin-set sequence can now also be optimized towards the added lines. Looking at in what state the pins are left by the reading of xy(), I reduced listen() to just the pulling hight the second edge of the powered plane and call the xy() before the listen() at connect. But for some reason, this made the watch() to trip on a regular basis without a touch happening (and triggered xy() showed also an 'untouch'...). So I left the listen and optimized only the reading of xy(). Playing around while optimizing, I also noticed that keeping the pull-down had no adverse effect, so I eliminated its removal. (The complete code for the touch initialization and touch onDown event detection is uploaded as touch5.js file.)

    pt.xy = function(callback) {
      this.t = new Date().getTime();
      pinMode(this.yn,"input_pulldown");
      digitalRead(this.yp);
      digitalWrite([this.xn,this.xp],2);
      this.x = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;   
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],2);
      this.y = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      if (callback) { callback.call(this); }
    };
    

    Next steps are now to bring back all the complete onDown, onTrack - track while touching and possibly dragging - and optional on onUp support.


    2 Attachments

  • Yeah!... got my first module running... in the sandbox... anyway... thanks for the posts Writing modular code using "require" at http://forum.espruino.com/conversations/­256773. I knew about and used the Web available modules from repository only, so far... Now I can easily QA the modules locally before thinking about publishing for use from the Web.

    I created a folder, registered it in Web IDE Settings - Project - Sandbox, and placed the TOUCH.js module file in the sandbox's modules folder.

    The TOUCH module is now at a stage where I need to combine the TOUCH SCREEN with the LCD for calibration purposes to finish the implementations of the onTouch, onTrack (while touching) and onUp functions. The TOUCH module goes into calibration mode on a very long touch - 5 [s] - with 'no' movement/dragging (<10%) anywhere on the screen. Calibration mode will show spots on the LCD to touch / tap. The spots' x-y coordinates are then formula-mapped to the related, read x-y coordinate values (0.000..0.999) for the calculation of the x-y pixel coordinates of the 240 horizontal x 320 vertical (2.8" TFT 262K Color) LCD.

    The TOUCH module so far:

    // TOUCH.js [0v01] [1v70]
    
    // pins for connect in example: C0,C1,C2,C3
    // callbacks optional on connect 
    function TOUCH(xn,xp,yn,yp,onDownCB,onTrackCB,onU­pCB) {
      this.xn = xn; // ...C0
      this.xp = xp; // ...C1
      this.yn = yn; // ...C2
      this.yp = yp; // ...C3
      this.onDownCB  = onDownCB;
      this.onTrackCB = onTrackCB;
      this.onUpCB    = onUpCB;
      this.upCnt = 0;
      this.x = this.x0 = this.x1 = -1;
      this.y = this.y0 = this.y1 = -1;
      this.t = this.t0 = this.t1 =  0;
      this.tc = 0;
    }
    
    var pt = TOUCH.prototype;
    
    // tracking Interval in [ms], upThreshold, required up counts
    pt.C =
    { trkIv: 100
    , upThX: 0.08
    , upThY: 0.05
    , upCnt: 2
    };
    
    pt.listen = function() {
      var _this = this;
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],3);
      setWatch( function(){ 
        pinMode(_this.xn);
        _this.xy(_this._onDown);
       }, this.xp, {edge:rising, repeat:false} );
    };
    
    pt._onDown = function() {
      this.x0 = this.x1 = this.x; this.y0 = this.y1 = this.y;
      this.tc = this.t0 = this.t1 = this.t;
      if (this.onDownCB) { this.onDownCB(this, this.x, this.y, this.t); }
      this.track();
    };
    
    pt.onTrack = function(onTrackCB,onUpCB) {
      this.onTrackCB = onTrackCB;
      this.onUpCB = onUpCB;
    };
    
    pt.track = function() {
      var _this = this; 
      setTimeout(function(){
        _this.xy(_this._onTrack);
       },this.C.trkIv);
    };
    
    pt._onTrack = function() {
      if ((this.x > this.C.upThX) || (this.y > this.C.upThdY)) {
        this.x1 = this.x; this.y1 = this.y; this.t1 = this.t; this.upCnt = 0;
        if (this.t1 - this.tc > 5000) {
          if (    (Math.abs((this.x0 - this.x1) / this.x0) < 0.1)
               && (Math.abs((this.y0 - this.y1) / this.y0) < 0.1) ) {
            this.tc = -1;
            this.calib();
          } else {
            this.tc = this.t;
          }
        }
        if (this.tc > 0) {
          if (this.onTrackCB) { this.onTrackCB(this, this.x, this.y, this.t); }
          this.track();
        }
      } else {
        if (this.upCnt++ < this.C.upCnt) {
          this.track();
        } else {
          this.x = this.x1; this.y = this.y1; this.t = this.t1;
          if (this.onUpCB) { this.onUpCB(this, this.x, this.y, this.t); }
          this.listen();
        }
      }
    };
    
    pt.xy = function(callback) {
      this.t = new Date().getTime();
      pinMode(this.yn,"input_pulldown");
      digitalRead(this.yp);
      digitalWrite([this.xn,this.xp],2);
      this.x = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;   
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],2);
      this.y = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      if (callback) { callback.call(this); }
    };
    
    pt.calib = function() { // ...(empty) placeholder
      console.log("*********** calibrate...");
      var _this = this;
      setTimeout(function(){ _this.resume(); },3000);
    };
    
    pt.resume = function() {
      console.log("...done **********");
      this.listen(); 
    };
    
    exports.connect = function(xn,xp,yn,yp,onDown) {
        var touch = new TOUCH(xn,xp,yn,yp,onDown); touch.listen();
        return touch;
    };
    

    The TOUCH module usage example (sampling/polling interval on tracking/while touching is 100 [ms] / 10Hz / 10 times per second - defined in .C.trkIv constant):

    var cnt = -1, cnt2;
    require("TOUCH").connect(C0,C1,C2,C3, // suggested default ADC pins
      function(touch,x,y,t){ // --- onDown callback function
        cnt2 = 0; console.log(++cnt,cnt2,"onDown:",x,"-",y­,"@",t);
        touch.onTrack(
           function(touch,x,y,t){ // --- onTrack callback function
            console.log(cnt,++cnt2,"onTrack:",x,"-",­y,"@",t);
         },function(touch,x,y,t){ // --- onUp callback function
            // x, y, t values are the same as touch.x1, .y1, and .t1 values
            console.log(cnt,++cnt2,"onUp:",touch.x0,­"-",touch.y0," X ",touch.x1,"-",touch.y1);
            console.log("...in ",touch.t1 - touch.t0,"[ms]");
            touch.xy(); console.log("up:",touch.x,"-",touch.y);
         });
       });
    

    The examples output:

    The first number on line - cnt - counts the touches, second number counts the track events. Lines with 5 dots (.....) indicated removed ('redundandent/boring' ) output to keep post conciser.

    >echo(0);
    =undefined
    
    " top-left (0,0) corner touch "
    0 0 onDown: 0.13021032018 - 0.08894992497 @ 10273559.50381679460
    0 1 onTrack: 0.13037308308 - 0.08862439917 @ 10273666.97709923610
    0 2 onUp: 0.13021032018 - 0.08894992497  X  0.13037308308 - 0.08862439917
    ...in  107.47328244149 [ms]
    up: 0.04728262251 - 0.02799521883
    
    " top-right (239,0) corner touch "
    1 0 onDown: 0.84099590549 - 0.10091299814 @ 10276249.70896946638
    .....
    1 3 onUp: 0.84099590549 - 0.10091299814  X  0.84335596754 - 0.10107576104
    ...in  214.33396946452 [ms]
    
    " bottom-left (0,319) corner touch "
    2 0 onDown: 0.14705628035 - 0.85393555606 @ 10277957.31583969481
    .....
    2 3 onUp: 0.14705628035 - 0.85393555606  X  0.14062714579 - 0.85491213346
    .....
    
    " bottom-right (239,319) corner touch "
    3 0 onDown: 0.84058899824 - 0.84799471020 @ 10279814.59160305373
    .....
    3 2 onUp: 0.84058899824 - 0.84799471020  X  0.84905266905 - 0.84823885455
    .....
    
    " Diagonal touch-drag [top-left (0,0) to bottom-right (239,319)] in 0.5 sec "
    4 0 onDown: 0.14648661020 - 0.09521629663 @ 10283725.73377862572
    4 1 onTrack: 0.25952544441 - 0.22501970956 @ 10283833.24904580228
    4 2 onTrack: 0.46045624475 - 0.42757813890 @ 10283940.03148854896
    4 3 onTrack: 0.64901706467 - 0.66057323058 @ 10284046.82061068713
    4 4 onTrack: 0.76742707459 - 0.77947152920 @ 10284153.65076335892
    4 5 onTrack: 0.80779227384 - 0.82219679051 @ 10284260.44942748174
    4 6 onUp: 0.14648661020 - 0.09521629663  X  0.80779227384 - 0.82219679051
    ...in  534.71564885601 [ms]
    
    " calibration touch/tap of more than 5 secs in the center of the screen "
    5 0 onDown: 0.46851300831 - 0.47632562752 @ 10287615.08874045871
    5 1 onTrack: 0.47518628722 - 0.47193102922 @ 10287722.65458015352
    .....
    .....
    .....
    5 45 onTrack: 0.47510490577 - 0.47543043157 @ 10292422.00858778692
    5 46 onTrack: 0.47616286462 - 0.47559319447 @ 10292528.86354961805
    *********** calibrate...
    ...done **********
    
    " touch in the center of the screen " 
    6 0 onDown: 0.46590880191 - 0.51237760992 @ 10299584.01717557199
    6 1 onTrack: 0.46607156481 - 0.51172655832 @ 10299691.64503816701
    6 2 onUp: 0.46590880191 - 0.51237760992  X  0.46607156481 - 0.51172655832
    > 
    

    Notes:

    1. All short touches / taps were actually longer then the tracking 100[ms] / 0.1[s] interval / 10Hz
    2. onUp: row shows the down and up touch/untouch coordinate values.
    3. The onUp: values are the same as the last onTrack ones...If there is no onTrack event - in other words: the touch was of shorter duration than the tracking interval (of 0.1 [s]) - then the values are the same as the onDown ones.
    4. For first touch the measured (typical) values for up: are shown (not blanked out).
    5. Tracking including the (slow) console output used less than 10% of elapsed time: the code made it to 46 of theoretical 50 track points before entering the calibration (placeholder).
    6. More applications samples - from very simple to quite advanced will be posted at a later time.

    #require #module #localmodule

  • This looks great - I'm glad you got the setWatch-based touch detection working so well... It'd be really neat on battery powered devices. Now we just need a touchscreen LCD that doesn't draw loads of power! Actually I wonder how well those 320x240 LCDs cope without a backlight - I've never tried them.

    By the way, on the other touchscreen modules I've done before like this I've tended to just have a single callback, and to call it with no arguments when a finger is lifted off. It's probably not better, but it might be best to keep the APIs the same.

  • Now we just need a touchscreen LCD that doesn't draw loads of power!

    1. Without power, you see nothing at all on the LCD, even with white text on black background... So I have to think about some power management to save power: PWM instead of B2.set().
    2. The read xy() could 'de-power' the powered plane to save power.
    3. The scan/poll rate for tracking can be modified from 'snappy to ok' to save some power as well - of course not too much to avoid a too sluggish UI feel...
    4. There may be other power saving options - with the LCD controller chip. Have to look into it.

    ...other touchscreen modules I've done before...

    For a decent touch interface - touch duration (at one point), stay or drag and drag-drop - it is not good enough to have only an onUp... but my API can do all. I picked down as the primary because it enables all options, even just an onUp. And beyond that, whit the down at a particular location, the onTrack and onUp can be modified to that particular location in order to simplify and speed-up the subsequently needed detection processing... Looking at the ADS7843 implementation, the question arise: how much or little - no insult here - a module should do... Initially I had only one callback that received a touch object with the defining parameters inside, such as x and y, time, and state - down, track, up. I dropped it - at least for now - for several reasons, mainly for speed and directness in resource constraint environment, such as Espruino, and because JavaScript applications at first sight use just global functions... hence JavaScript being at first sight 'just' a functional language. I intentionally flipped the 'chicken and egg'... (That JavaScript is at first sight a functional language is last but not least noticeably too by the needed hassle of var _this = this; all over the place when going light-weight object-oriented... alleviated though by some frameworks which introduce an optional parameter following the callback function to provide that 'deferred' resolved this context object. But that becomes cumbersome when having multiple callbacks).

    I'm about to publish a calibration module - it is already working. I needed the code to do initial settings in the TOUCH module for transforming the analog value into pixel coordinates. In the calibration module I just use the onUp - with the very same TOUCH module. Notice the context/callback stack that is implemented in the [0v02] TOUCH module!

    This is the current [0v02] state of the TOUCH nodule:

    // TOUCH.js [0v02] [1v70] 20141018 (c) muet.com
    
    // suggested pins for connect: C0(xn=X-),C1(xp=X+),C2(yn=Y-),C3(yp=Y+)
    // callbacks optional on connect 
    function TOUCH(xn,xp,yn,yp,onDownCB,onTrackCB,onU­pCB) {
      this.xn = xn; this.xp = xp; this.yn = yn; this.yp = yp;
      this.cbs = [[onDownCB,onTrackCB,onUpCB]]; this.upCnt = 0; this.up = true;
      this.x = this.x0 = this.x1 = -1;
      this.y = this.y0 = this.y1 = -1;
      this.t = this.t0 = this.t1 =  0;
      this.tc = 0; this.calib = null;
    }
    
    var pt = TOUCH.prototype;
    
    // tracking Interval in [ms], upThresholds, required up counts
    pt.C =
    { trkIv: 100
    , upThX: 0.08
    , upThY: 0.05
    , upCnt: 2
    , clbTme: 5000
    };
    
    pt.listen = function() {
      var _this = this;
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],3);
      setWatch( function(){
        pinMode(_this.xn);
        _this.xy(_this._onDown);
       }, this.xp, {edge:rising, repeat:false} );
    };
    
    pt._onDown = function() {
      this.x0 = this.x1 = this.x; this.y0 = this.y1 = this.y;
      this.tc = this.t0 = this.t1 = this.t; this.up = false;
      var q = this.cbs.length - 1;
      if (this.cbs[q][0]) { this.cbs[q][0](this, this.x, this.y, this.t); }
      this.track();
    };
    
    pt.onTrack = function(onTrackCB,onUpCB) {
      var q = this.cbs.length - 1;
      this.cbs[q][1] = onTrackCB;
      this.cbs[q][2] = onUpCB;
    };
    
    pt.track = function() {
      var _this = this; 
      setTimeout(function(){
        _this.xy(_this._onTrack);
       },this.C.trkIv);
    };
    
    
    pt._onTrack = function() {
      var q = this.cbs.length - 1;
      if ((this.x > this.C.upThX) || (this.y > this.C.upThdY)) {
        this.x1 = this.x; this.y1 = this.y; this.t1 = this.t; this.upCnt = 0;
        if (this.t1 - this.tc > this.C.clbTme) {
          if (    (Math.abs((this.x0 - this.x1) / this.x0) < 0.1)
               && (Math.abs((this.y0 - this.y1) / this.y0) < 0.1)
               && this.calib
               && this.calib.step == -1 ) {
            this.tc = -1;
            this.calib.enter();
          } else {
            this.tc = this.t;
          }
        }
        if (this.tc > 0) {
          if (this.cbs[q][1]) { this.cbs[q][1](this, this.x, this.y, this.t); }
          this.track();
        }
      } else {
        if (this.upCnt++ < this.C.upCnt) {
          this.track();
        } else {
          this.x = this.x1; this.y = this.y1; this.t = this.t1; this.up = true;
          if (this.cbs[q][2]) { this.cbs[q][2](this, this.x, this.y, this.t); }
          this.listen();
        }
      }
    };
    
    pt.xy = function(callback) {
      this.t = new Date().getTime();
      pinMode(this.yn,"input_pulldown");
      digitalRead(this.yp);
      digitalWrite([this.xn,this.xp],2);
      this.x = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;   
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],2);
      this.y = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      if (callback) { callback.call(this); }
    };
    
    exports.connect = function(xn,xp,yn,yp,onDown) {
        var touch = new TOUCH(xn,xp,yn,yp,onDown); touch.listen();
        return touch;
    };
    
    

    So much for calibration for now: TOUCH enters the calibration - or callout - when touching for longer than a specified quite long time at the same location (within +-10% in regard to the onDown). For calibration, the callout darks the screen for a bit for UI feedback, and comes back with five markers to touch - one after the other - to gather data and then perform the calibration / adjustment. The calibration has also a timeout to return when accidentally entered or/and not completed. With more experience, calibration may be needed only once to get a particular type and instance of touch screen going. After that it may not be needed anymore, because the once set parameters stay accurate enough over time. If so, I'n thinking of enabling additional optional parms to provide these values on connect (which leads me to think for some JS extensions, such as the very convenient mix(), mixin(), saveMixin()... I may start other conversations for those matters).

    I have also some thoughts about naming the things... to not restrict the mind when thinking how applying modules. For example, the (optional) calibration intercept/interrupt related things are called something calib..., but it can be used for just anything a user likes to do. Any callback (or callout) could be set, even with coexistence of different ones at different times/states. For a final settling some feedback from the community could help. The current forum does not have a voting implemented/enabled, which could be used for a RFC process... ;-) - Btw, I did not get a single response for the RFC for my Software Buttons - Many buttons from just one hardware button at http://forum.espruino.com/comments/49040­59/.

    Having now some +-experience with the 'cheap' resistive touch screen technology, I'm thinking about capacitive touch screen technology... I do not like the spongy feeling and the noise rate. But first things first: bring the began project(s) to the finish line.

    Attached file: TOUCH.js [0v02]


    1 Attachment

  • Actually I wonder how well those 320x240 LCDs cope without a backlight

    Pretty poorly, like any other unbacklit color LCD - it'll look like a laptop screen with a burned out backlight (if you owned a pre-recession Acer laptop, you're lucky if you haven't seen this). If it's assembled, you can barely make out large shapes if you hold it up to light at the right angle. If you can pull the backing layers off without ruining it, you could probably read it with difficulty if you hold it up to the light.

    If screen power usage is a big issue (can't you just turn off the backlight except when it's in use?), you could put a touch overlay on top of a B&W LCD or even one of those exorbitantly expensive e-Paper modules.

  • Thanks...

    I just remember that the N95 had a screen that worked passably well without a backlight (it had a clock that was displayed on it). It was a similar size so I wondered if it might be a similar display.

  • Here the first cut of LCD and TOUCHR(esistive) screen combined with calibration module connected.

    // touchrCalib.js [0v03] [1v70] 20141022 (c) muet.com
    
    // temp beg ----------------
    
    // TOUCHR.js [0v04] [1v70] 20141022 (c) muet.com
    
    // suggested pins for connect: C0(xn=X-),C1(xp=X+),C2(yn=Y-),C3(yp=Y+)
    // CallBacks and Config optional on connect 
    function TOUCHR(xn,xp,yn,yp,onDownCB,onTrackCB,on­UpCB,onEscCB,C) {
      this.xn = xn; this.xp = xp; this.yn = yn; this.yp = yp;
      this.cbs = [[onDownCB,onTrackCB,onUpCB,onEscCB]]; 
      this.te = 0; this.upCnt = 0; this.up = true;
      this.xr = this.xrr = this.yr = this.yrr = -1;
      this.x = this.x0 = this.x1 = -1;
      this.y = this.y0 = this.y1 = -1;
      this.t = this.t0 = this.t1 =  0;
      if (C) { for (var v in C) { console.log("v: ",C[v]); this.C[v] = C[v]; } }
    }
    
    var pt = TOUCHR.prototype;
    
    // tracking Interval in [ms], upThresholds, required up counts
    // escape time [ms], LCD and screen config parms
    pt.C =
    { trkIv: 100
    , upThX: 0.08
    , upThY: 0.05
    , upCnt: 2
    , escTme: 10000, sameThd: 12
    , xmx:239, xo: 0.11210664669 ,xd: 0.00318978292
    , ymx:319, yo: 0.07025931859 ,yd: 0.00251604316
    };
    
    pt.listen = function() {
      var _this = this;
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],3);
      setWatch( function(){
        pinMode(_this.xn);
        _this.xyr(_this._onDown);
       }, this.xp, {edge:rising, repeat:false} );
    };
    
    pt._onDown = function() {
      this._xyc(this.C,true);
      this.x0 = this.x1 = this.x; this.y0 = this.y1 = this.y;
      this.te = this.t0 = this.t1 = this.t; this.up = false;
      var q = this.cbs.length - 1;
      if (this.cbs[q][0]) { this.cbs[q][0](this, this.x, this.y, this.t); }
      this.track();
    };
    
    pt._onTrack = function() {
      var q = this.cbs.length - 1, C = this.C;
      if ((this.xrr > C.upThX) || (this.yrr > C.upThdY)) {
        this._xyc(C,true);
        this.x1 = this.x; this.y1 = this.y; this.t1 = this.t; this.upCnt = 0;
        if (this.t1 - this.te > this.C.escTme) {
          if (    (Math.abs(this.x0 - this.x1) < C.sameThd)
               && (Math.abs(this.y0 - this.y1) < C.sameThd)
               && this.cbs[q][3]
               && (this.cbs[q][3](this)) ) {
            return;
          } else {
            this.te = this.t;
          }
        }
        if (this.cbs[q][1]) { this.cbs[q][1](this, this.x, this.y, this.t); }
        this.track();
      } else {
        if (this.upCnt++ < C.upCnt) {
          this.track();
        } else {
          this.x = this.x1; this.y = this.y1; this.t = this.t1; this.up = true;
          if (this.cbs[q][2]) { this.cbs[q][2](this, this.x, this.y, this.t); }
          this.listen();
        }
      }
    };
    
    pt.onTrack = function(onTrackCB,onUpCB) {
      var q = this.cbs.length - 1;
      this.cbs[q][1] = onTrackCB;
      this.cbs[q][2] = onUpCB;
    };
    
    pt.onEsc = function(onEscCB) {
      var q = this.cbs.length - 1;
      this.cbs[q][3] = onEscCB;
    };
    
    pt.track = function() {
      var _this = this; 
      setTimeout(function(){
        _this.xyr(_this._onTrack);
       },this.C.trkIv);
    };
    
    pt._xyc = function(C,c) {
      if (c) {
        var x = Math.round(((this.xr = this.xrr) - C.xo) / C.xd);
        var y = Math.round(((this.yr = this.yrr) - C.yo) / C.yd);
        this.x = (x < 0) ? 0 : (x <= C.xmx) ? x : C.xmx; 
        this.y = (y < 0) ? 0 : (y <= C.ymx) ? y : C.ymx;  
      } else {
        this.x = undefined;
        this.y = undefined;
      }
    };
    
    pt.xy = function(callback) {
      this.xyr();
      this._xyc(this.C,(this.xrr > C.upThX) || (this.yrr > C.upThdY));
      if (callback) { callback.call(this); }
    };
    
    pt.xyr = function(callback) {
      this.t = new Date().getTime();
      pinMode(this.yn,"input_pulldown");
      digitalRead(this.yp);
      digitalWrite([this.xn,this.xp],2);
      this.xrr = (analogRead(this.yn)+analogRead(this.yn)­+analogRead(this.yn))/3;   
      pinMode(this.xn,"input_pulldown");
      digitalRead(this.xp);
      digitalWrite([this.yn,this.yp],2);
      this.yrr = (analogRead(this.xn)+analogRead(this.xn)­+analogRead(this.xn))/3;
      digitalRead([this.yn,this.yp]);
      if (callback) { callback.call(this); }
    };
    
    // exports.connect = function(xn,xp,yn,yp,onDown,onTrack,onUp­,onEsc,C) {
    var touchrModule =
    { connect: function(xn,xp,yn,yp,onDown,onTrack,onUp­,onEsc,C) {
        var touch = new TOUCHR(xn,xp,yn,yp,onDown,onTrack,onUp,o­nEsc,C); touch.listen();
        return touch;
      }
    };
    
    // temp end ----------------
    
    // function xy(xr,yr,C) {
    //  var x = Math.round((xr - C.xo) / C.xd), y = Math.round((yr - C.yo) / C.yd);
    //  x = (x < 0) ? 0 : (x < C.xmx) ? x : C.xmx; y = (y < 0) ? 0 : (y < C.ymx) ? y : C.ymx;
    //  console.log("----->",x,"-",y);
    // }
    
    var cnt = -1, cnt2;
    // var touch = require("TOUCHR").connect(C0,C1,C2,C3, // suggested default ADC pins
    var touch = touchrModule.connect(C0,C1,C2,C3, // suggested default ADC pins
      function(touch,x,y,t){ // --- onDown callback function
        cnt2 = 0;
        console.log(++cnt,cnt2,"onDown-r:",touch­.xr,"-",touch.yr,"@",t);
        console.log(++cnt,cnt2,"onDown:  ",x,"-",y,"@",t);
        touch.onTrack(
           function(touch,x,y,t){ // --- onTrack callback function
            cnt2++;
            console.log(cnt,cnt2,"onTrack: ",x,"-",y,"@",t);
         },function(touch,x,y,t){ // --- onUp callback function
            cnt2++;
            // x, y, t values are the same as touch.x1, .y1, and .t1 values
            console.log(cnt,cnt2,"onUp:    ",touch.x0,"-",touch.y0,"X",touch.x1,"-"­,touch.y1);
            console.log(cnt,cnt2,"up-r:    ",touch.xr, "-",touch.yr );
            console.log(cnt,cnt2,"up--rr:  ",touch.xrr,"-",touch.yrr);
            console.log("...in ",touch.t1 - touch.t0,"[ms]");
         });
    //    touch.onEsc(
    //       function() { // --- empty 'not-available' onEscape callback function
    //         cnt2++;
    //         console.log(cnt,cnt2,"onEscape - but 'not-available', hence returning false");
    //         return false;
    //     });
    });
     
    function TOUCHRCALIB(touch,lcd,noteCB,exitCB) {
      this.touch = touch;
      this.lcd = lcd;
      this.noteCB = noteCB;
      this.exitCB = exitCB;
      this.step = -1;
      this.rdy = false;
      this.ps = null;
      this.vs = null;
      this.exitTmr = null;
      var _this = this;
      touch.onEsc(function(){ return _this.enter(); });
    }
    
    var pt = TOUCHRCALIB.prototype;
    
    pt.C =
    { exitTme: 30000
    , noteTme: 2000
    , markDly: 500
    , markSze: 20
    };
    
    pt.enter = function() {
      if (this.step == -1) {
        var tme = this._note(true);
        console.log("tme =",tme," -  exit =",tme + this.C.exitTme);
        var _this = this; this.vs = [];
        this.exitTmr = setTimeout(function(){ _this._exit(); },tme + this.C.exitTme);
        setTimeout(function(){ _this.step = 0; _this.rdy = true; _this.touch.track(); },tme);
        this.touch.cbs.push([null,null,function(­touch,x,y,t){ _this._next(x,y,t); },null]);
        return true;
      } else {
        return false;
      }
    };
    
    pt._next = function(x,y,t) {
      var s = this.step;
      if (this.rdy) {
        this.rdy = false;
        if (s > 0) {
          this.vs.push(this.touch.xr); this.vs.push(this.touch.yr);
          this._unmark(this.lcd,this.ps[s*2-2],thi­s.ps[s*2-1],this.C.markSze);
        }
        if (s < 5) {
          this.step++;
          this._mark(s);
          this.rdy = true;
        } else {
          clearTimeout(this.exitTmr);
          this.step = 6;
          this._calib();
          this._exit();
        }
      }
    };
    
    pt._exit = function() {
      console.log("calib _exit():");
      var tme = this._note(false);
      this.ps = null;
      this.vs = null;
      var _this = this;
      setTimeout(function(){
        _this.touch.cbs.pop();
        _this.step = -1; 
        _this.rdy = false;
        if (_this.exitCB) { _this.exitCB(); }
       },tme);
    };
    
    pt._mark = function(s) {
      var m = this.C.markSze, m2 = Math.floor(m / 2 - 0.5) + 1;
      if (s === 0) {
        var x = this.lcd.getWidth(), y = this.lcd.getHeight();
        this.touch.C.xmx = x - 1; this.touch.C.ymx = y - 1;
        this.ps = [0,0, x-m-1,0, x-m-1,y-m-1, 0,y-m-1, x/2-m2-1, y/2-m2-1];
        console.log("ps:",this.ps);
      }
      this._mark0(this.lcd,this.ps[s*2],this.p­s[s*2+1],m,m2);
    };
    
    pt._mark0 = function(l,x,y,m,m2) {
      l.setColor(1.0,1.0,1.0); l.fillRect(x,y,x+m,y+m); 
      l.setColor(0.0,0.0,0.0); l.drawRect(x+1,y+1,x+m-1,y+m-1);
      l.drawLine(x,y,x+m,y+m); l.drawLine(x+m,y,x,y+m);
      l.fillRect(x+m2-2,y+m2-2,x+m2+2,y+m2+2);­
    };
    
    pt._unmark = function(l,x,y,m) {
      l.setColor(0.0,0.0,0.0); l.fillRect(x,y,x+m,y+m); 
    };
    
    pt._calib = function() {
      var m = this.C.markSze, m2 = Math.floor(m / 2 - 0.5) + 1;
      var vs = this.vs;
      var xl = (vs[0] + vs[6]) / 2, xu = (vs[2] + vs[4]) / 2;
      var yl = (vs[1] + vs[3]) / 2, yu = (vs[5] + vs[7]) / 2;
      var xd = (xu - xl) / (this.lcd.getWidth()  - m);
      var yd = (yu - yl) / (this.lcd.getHeight() - m);
      xl = xl - xd * m2; xu = xu + xd * m2;
      yl = yl - yd * m2; yu = yu + yd * m2;
      var xc = (xl + xu) / 2, yc = (yl + yu) / 2;
      var C = this.touch.C;
      if (console && console.log) {
        console.log("calib _calib():");
        var i = -1; while (i < 4) { i++;
          console.log("i:",i," ",vs[i*2],"-",vs[i*2+1]); }
        console.log("xd:",xd," yd:",yd," - ds:",xd - C.xd," ",yd - C.yd);
        console.log("x-:",xl," x+:",xu," - d: ",xl - C.xo);
        console.log("y-:",yl," y+:",yu," - d: ",yl - C.yo);
        console.log("xc:",xc," yc:",yc);
      }
      C.xmx = this.lcd.getWidth()  - 1; C.xo = xl; C.xd = xd;
      C.ymx = this.lcd.getHeight() - 1; C.yo = yl; C.yd = yd;
    };
    
    pt._note = function(evt) {
      console.log("calib _note(): evt =",((evt) ? "beg" : "end"));
      var tme = (this.noteCB) ? this.noteCB(evt) : this.C.noteTme;
      return (!isNaN(tme) && (tme >= this.C.noteTme)) ? tme : this.C.noteTme;
    };
    
    var touchrcalibModule = 
    { connect:function(touch,lcd,noteCB,exitCB­){
        var calib = new TOUCHRCALIB(touch,lcd,noteCB,exitCB);
        return calib;
       }
    };   
    
    B2.set();
    SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
    var lcd = require("ILI9341");
    lcd = lcd.connect(SPI1, B6, B8, B7, function() {
      lcd.clear();
      lcd.setFontVector(20);
      lcd.drawString("Hello Espruino!",35,0);
      lcd.setColor(0.0,1.0,1.0);
      lcd.drawRect(0,0,lcd.getWidth()-1,lcd.ge­tHeight()-1);
      touchrcalibModule.connect(touch,lcd,func­tion(beg){
          console.log("noteCB w/ noteTme = 2500");
          var noteTme = 2500; B2.reset();
          setTimeout("B2.set()",noteTme);
          return noteTme;
         });
    });
    

    The code is still pretty fat and needs to go for some diet. On the other hand it shows that not for no reason touch controller chips are available to offload an application controller from that tedious work... Some fat is intended to be burned - saved - when going for a simple UI w/ buttons and sliders.

    Below some output, including the re-calibration.

    >echo(0);
    =undefined
    0 0 onDown-r: 0.16691335418 - 0.09798326593 @ 5888100.53244274854
    1 0 onDown:   17 - 11 @ 5888100.53244274854
    1 1 onUp:     17 - 11 X 17 - 11
    1 1 up-r:     0.16691335418 - 0.09798326593
    1 1 up--rr:   0.04085348795 - 0
    ...in  0 [ms]
    2 0 onDown-r: 0.82789349202 - 0.12483914447 @ 5890403.95133587811
    .....
    5 0 onDown:   17 - 305 @ 5896185.48854961805
    .....
    6 0 onDown:   227 - 307 @ 5897569.74809160269
    6 1 onTrack:  228 - 308 @ 5897683.27194656524
    6 2 onTrack:  229 - 307 @ 5897792.12309160269
    6 3 onUp:     227 - 307 X 229 - 307
    ...in  222.375 [ms]
    7 0 onDown:   115 - 159 @ 5899129.74427480902
    7 1 onTrack:  116 - 160 @ 5899243.35782442707
    7 2 onUp:     115 - 159 X 116 - 160
    8 0 onDown:   116 - 275 @ 5909195.74141221400
    8 1 onTrack:  116 - 275 @ 5909309.24713740497
    8 2 onTrack:  116 - 273 @ 5909418.09828244242
    8 3 onTrack:  116 - 273 @ 5909526.95801526680
    .....
    .....
    .....
    8 89 onTrack:  114 - 272 @ 5918888.63454198464
    8 90 onTrack:  115 - 271 @ 5918997.46374045778
    8 91 onTrack:  114 - 271 @ 5919106.32442748080
    calib _note(): evt = beg
    .....
    calib _calib():
    i: 0   0.14144096030 - 0.09773912158
    i: 1   0.84278629739 - 0.09871569899
    i: 2   0.85735357696 - 0.85027339080
    i: 3   0.15844968337 - 0.86101574222
    i: 4   0.48812593779 - 0.48470791688
    xd: 0.00318238461  yd: 0.00252472385  - ds: -0.00000739830   0.00000868069
    x-: 0.11812147568  x+: 0.88189378333  - d:  0.00601482899
    y-: 0.07298017174  y+: 0.88089180505  - d:  0.00272085315
    xc: 0.50000762951  yc: 0.47693598840
    calib _exit():
    .....
    9 0 onDown:   20 - 11 @ 5947261.60019083973
    9 1 onUp:     20 - 11 X 20 - 11
    ...in  0 [ms]
    .....
    

    As you may notice, most touches / taps with a resistive and thus spongy feel are longer than the set track interval of 100 [ms] - 10 cycles or reads/second when while touching... and two for safely detecting - and 'debouncing' an 'untouch'. Therefore you see onTrack: output lines - such as 14, 15, and 19, even though I intended only a (short) touch / tap.

    With output line 8 a very long tap starts... After the defined esc(ape)Time of 10 [sec] the connected recalibrate callback will kick in - at line 31 - after 91 track (way) points (of 100 theoretical possible ones). This is a great testimony Espruino's amazing performance.

    Lines 34..42 show calibration values. First - lines 34..38 - show the read raw ADC values of top-left, top-right, bottom-right, and bottom-left corners (@ 10 px from the border) and center. (In the attached shot I commented the removal of the markers to tap to show all locations at once. The marker size is 20x20 px).

    Surprising to me was the quite accurate reproducibility of the values between calibration. The last two numbers in line 39 show the ADC value difference for 1 px in x and y axis, and the last number on line 40 and 41 show the offset difference. Touch detection accuracy is a fraction of the display accuracy of +-1 pixel. The shape of the touching object and the pressure add quite some variation - especially a finger tip - which is reflected in unexpected high value of 12 px for sameThd (...Threshold) to detect still same touch location. For a pointy pen 3 px worked (line 29 in the code).

    With the experience made, I do not (anymore) believe that calibration has to be available at runtme. It is good enough to verify with a particular new Touch LCD device - and if the device is within the tolerance, no value adjustments are needed. If there are - because of different types - then the connect (and constructor) allows to pass a C(onfig) object with the parameters that need to be different from what the module code has coded.

    More about the code and some application samples in a different post.

    Attached touchrCalib.js file (with touch and calibration 'module').


    2 Attachments

  • Below module code I intend to use for my resistive touch screen. It is a much lighter version of what you have seen from me so far. It is inspired by @Gordon 's ADS7843 touch controller module AND is - of course - 100% callback backward-compatible. Planned name for the module is TOUCHR - R for resistive.

    The module includes the defaults configuration for any ILI9341 controlled 320x240 262K Color TFT LCD with resistive touch screen (used in 65K color mode). Any of the default LCD and touch screen parameters can be overwritten on connect() to use it with other displays and touch screen, and to manage variations between same touch screens.

    Power consumption is optimized and can be lowered even more for touch tracking by setting the scan interval i at connect time or afterwards, even dynamically in callback depending tracking needs identified on particular touch.

    // TOUCHRsimple_inline.js
    
    // Module for connecting resistive touch screen (passive X- X+ Y- Y+)
    
    // var touchr = require("TOUCHR").connect(C0,C1,C2,C3,fu­nction(x,y,tr){
    //   if (tr.t) { console.log(x,y); } else { console.log("up"); }
    //  }).listen(true);
    //
    // // probe xy when NOT listening - touch.listen(false);
    // if (touchr.xy().t) console.log(touchr.x,touchr.y);
    
    // connect(X- C0,X+ C1,Y- C2,Y+ C3,callback(x,y,touchr),C{onfig}  
    // listen(boolean): start/stop
    // xy(optCallback): read xy (ONLY when NOT listening)
    // ...all returning touchr
    
    // Config (for overriding adafruit's 2.8" 320x240 res touch LCD TFT):
    // X: horizontal px #
    // Y: vertical px #
    // xt: x touch threshold #
    // yt: y touch threshold #
    // xc: x center value #
    // yc: y center value #
    // xd: x delta per px #
    // yd: y delta per px #
    // xr: x read (raw) # 0
    // yr: y read (raw) # 0
    // x: x touching # 0..X-1
    // y: y douching # 0..Y-1
    // t: touching boolean (null = ambiguous)
    // i: track intervall [ms]
    // l: listening boolean 
    // w: watch id
    
    var touchrModule = { connect: function(xn,xp,yn,yp,callback,C) {
    var tr =  
    { X: 240
    , Y: 320
    , xt: 0.08
    , yt: 0.05
    , xc: 0.4960
    , yc: 0.4795
    , xd: 0.00318
    , yd: 0.00251
    , xr: 0
    , yr: 0
    , x: 0
    , y: 0
    , t: false
    , i: 100
    , l: true
    , w: false
    , cb: callback
    };
    if (C) { for (var v in C) tr[v] = C[v]; }
    tr.xy = function(cb,l) {
      pinMode(yn,"input_pulldown");
      digitalRead(yp);
      digitalWrite([xn,xp],2);
      tr.xr = (analogRead(yn)+analogRead(yn))/2;   
      pinMode(xn,"input_pulldown");
      digitalRead(xp);
      digitalWrite([yn,yp],2);
      tr.yr = (analogRead(xn)+analogRead(xn))/2;
      digitalRead([yn,yp]); tr.t = null;
      if (tr.xr>tr.xt && tr.yr>tr.yt) { tr.t = true;
        tr.x = ((tr.x = Math.round(tr.X/2+(tr.xr-tr.xc)/tr.xd)-1­) 
          <0) ? 0 : (tr.x<tr.X) ? tr.x : tr.X-1;
        tr.y = ((tr.y = Math.round(tr.Y/2+(tr.yr-tr.yc)/tr.yd)-1­) 
          <0) ? 0 : (tr.y<tr.Y) ? tr.y : tr.Y-1;
        if (cb) { cb(tr.x,tr.y,tr); }
      } else if (tr.xr<tr.xt && tr.yr<tr.yt) { tr.t = false;
        if (cb) { cb(undefined,undefined,tr); }
      } if (!l && cb) { cb(undefined,undefined,tr); }
      return tr;
    };
    tr.track = function() {
      tr.w = null; pinMode(xn); tr.xy(tr.cb,true); 
      if (tr.t === false) { tr.listen(tr.l);
      } else { setTimeout(function(){ tr.track(); },tr.i); }
    };
    tr.listen = function(b) {
      tr.l = b; if (b && !tr.w) {
        pinMode(xn,"input_pulldown");
        digitalRead(xp);
        digitalWrite([yn,yp],3);
        tr.w = setWatch(tr.track,xp,{edge:rising, repeat:false});
      } else {
        if (tr.w) { clearWatch(tr.w); tr.w = null; }
      } return tr;
    };
    return tr;
    }
    };
    

    The sample test code

    var touchr = touchrModule.connect(C0,C1,C2,C3,functio­n(x,y,tr) {
      console.log((tr.t) ? "down" : "up  ",x,y,tr.xr,tr.yr);
     }).listen(true);
    

    shows the console output below on touching top-left, top-right, bottom-right, and bottom-left corner and the center of the LCD area. On touching, it shows the calculated x and y coordinates and the raw rx and ry values read by the ADC pins. On up (just once after touching) just the raw values read by the DC pin are shown. The touch thresholds (xt, yt) for detecting an un-touch are between the lowest read touch value and the up (un-touch) value and are different for x and y axis. Touches are detected by a hardware event caught by setWatch().

     1v70 Copyright 2014 G.Williams
    >echo(0);
    =undefined
    down 0 3 0.11865415426 0.08740367742
    down 0 3 0.11609063859 0.08813611047
    up   undefined undefined 0.04345769436 0.02355992980
    down 231 6 0.85181963836 0.09472800793
    up   undefined undefined 0.03808651865 0.02075226977
    down 232 312 0.85621423666 0.86243991760
    down 233 311 0.85743495841 0.86060883497
    down 232 311 0.85584802014 0.86073090714
    up   undefined undefined 0.04345769436 0.02490272373
    down 5 314 0.13464560921 0.86915388723
    down 3 314 0.12610055695 0.86903181506
    up   undefined undefined 0.04406805523 0.02331578545
    down 119 154 0.49488059815 0.46802471961
    up   undefined undefined 0.04321355001 0.02331578545
    > 
    

    Because Espruino IS the (software) controller - like the ADS7843 chip in the ADS7843 module - the TOUCHR module returns a (singleton) control object at connect() to provide additional access - control and data. The control object returns itself also in listen() and xy() methods, and is passed to callback as third (3rd) argument. On connect() or connect().listen(true), it is useful to hold on to the control module in a global variable named, for example, touchr:

    var touchCallback = ;
    var touchr = require("TOUCHR").connect(C0,C1,C2,C3,
      function(x,y,t) {
        // your code using x, y, and t(ouch control object)
       }).listen(true);
    

    Some displays have touch screens mounted that are larger than the active display area. These areas are called porches. Some touch screens show a blank porch and some have printed icons on it, such as a letter, a clock, a calendar, etc., to mark function buttons. The porch can be used as input but. The touch screen at hand has a bottom porch of about 30px. To use the porch, the controllers Y and yc touch screen parms have to be adjusted:

    var ph = 30; // bottom porch height in px
    touchr.X += ph; // increase y detection cut toff / limit by bottom porch height
    touchr.xc += touchr.xd * ph / 2; // incr. y center touch value by 1 px value times half porch height
    

    Adjustment can be done anytime after connecting. Remember though to consider the extra y range in the callback as well and do not use it for mapping to pixel position of the display:

    var touchCallback = function(x,y,t) {
      if (t) { // touching, x and y are defined
        if (y < 320) { // touching in display area
           // display code
        } else { // touching in porch area
          // porch code
       }
     }
    };
    

    Below code is the classic drawing 'application':

    • Tapping a color in the menu at the bottom picks it up for drawing.
    • Tapping +/- increases/decreases brush size; brush size shown in black cotouchlor selection.
    • Tapping the X clears the screen for a new drawing.

    Notice the shortened tracking interval time passed on on connect (line 42). Colors of attached screen shot are 'a bit' off... angle and exposure and ambient light matter messed with capturing.

    var touchr;
    SPI1.setup({sck:B3, miso:B4, mosi:B5, baud: 1000000});
    var lcd = require("ILI9341").connect(SPI1, B6, B8, B7, function() {
      lcd.clear();
      var n=2,w=20,rgb=[0,0,0];cs=[];x=-w,ym1=300,­ym2=319;
      (function(){
        cs.push(rgb.slice(0)); 
       lcd.setColor(rgb[0],rgb[1],rgb[2]);
        x += w;
        lcd.fillRect(x,ym1,x+w-1,ym2);
        for (var r=0;r<n;r++) {     rgb[0] = 1 / (r + 1);
          for (var g=0;g<n;g++) {   rgb[1] = 1 / (g + 1);
            for (var b=0;b<n;b++) { rgb[2] = 1 / (b + 1);
              cs.push(rgb.slice(0)); 
              lcd.setColor(rgb[0],rgb[1],rgb[2]);
              x += w;
              lcd.fillRect(x,ym1,x+w-1,ym2);
            }
          }
        }
        lcd.setColor(1,1,1);
        lcd.setFontVector(16);
        x += w; lcd.drawString("+",x+3,ym1+2);
        x += w; lcd.drawString("-",x+3,ym1+2);
        x += w; lcd.drawString("X",x+3,ym1+3);
        lcd.drawLine(0,ym1,239,ym1);
       })();
      var rad = 2, ymd = ym1 - rad - 1, cxm=cs.length-1,cdx = 0,ym;
      lcd.setColor(0,0,0); lcd.fillRect(0,ym1+1,w-1,ym2);
      lcd.setColor(1,1,1); lcd.setFontVector(10); 
      lcd.drawString(rad * 2 + 1,0,ym1+5);
      rgb = cs[0]; lcd.setColor(rgb[0],rgb[1],rgb[2]);
      touchr = touchrModule.connect(C0,C1,C2,C3,functio­n(x,y,tr){
        if (tr.t) {
          if (y < ym1) {
            ym = (y < ymd) ? y : ymd;
            lcd.fillRect(x-rad,ym-rad,x+rad,ym+rad);­
          } else if (y >= ym1) {
            cdx = Math.floor(x / w);
            if (cdx <= cxm) {
              rgb = cs[cdx];
              lcd.setColor(rgb[0],rgb[1],rgb[2]);
            }
          }
        } else {
          if (tr.y >= ym1) {
            cdx = Math.floor(tr.x / w);
            if (cdx > cxm) {
             if (cdx <= cxm + 2) {
                rad = Math.round(rad + ((cdx == cxm + 1) 
                            ? (rad / 2) : (0 - rad / 3)));
                rad = (rad < 2) ? 2 : (rad <= 42) ? rad : 42; 
                ymd = ym1 - rad - 1;
                lcd.setColor(0,0,0); lcd.fillRect(0,ym1+1,w-1,ym2);
                lcd.setColor(1,1,1); lcd.setFontVector(10);
                lcd.drawString(rad * 2 + 1,0,ym1+5);
                lcd.setColor(rgb[0],rgb[1],rgb[2]);
              } else {
                rgb = cs[0]; lcd.setColor(rgb[0],rgb[1],rgb[2]);
                lcd.fillRect(0,0,239,ym1 - 1);
              }
            }
          }
        }
       },{i:20}).listen(true); // track every 50 [ms] = 20Hz = 20 times/s
     });
    

    A calibration module for TOUCHR module will be presented in a separate post. It will help you to set or adjust the touch screen parms xt, yt, xc, yc, xd, and yd for your (same or different LCD w/) resistive touch screen (...c stands for expected value at the center, ...d for the difference between pixel. In a nutshell, the calibration module uses the read raw values of touches at known (displayed px) x / y locations to calculate the touch screen parms.

    The above provided simple test code can be used to manually gather the raw values and do the calculations. Set pixels at defined x / y coordinates equally close to the corners (about 10 px away from the borders) and one in the center, touch them, and perform the calculations. Simple average and linear formulae are accurate enough:

    • xd = avrg of delta x raw touch values divided by delta x pixels
    • yd = avrg of delta y raw touch values divided by delta y pixels
    • xc = avrg of (sums of low and high x / 2) and center x values
    • yc = avrg of (sums of low and high y / 2) and center y values
    • xt = between avrg of x off and avrg of lowest x touch values
    • yt = between avrg of y off and avrg of lowest y touch values

    4 Attachments

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Resistive Touchscreen directly (no touch controller)

Posted by Avatar for allObjects @allObjects

Actions