• 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

About

Avatar for allObjects @allObjects started