• 'After-completed-comment': Check out existing KeyPad.js module and its doc... and look at this conversation's code as a programming exercise... and a different style of doing things in JS...

    Adding a human interfac / user interface is always a challenge. Below code provides you with function to drive and sense a matrix key pad with any nuber of rows and columns directly with Espruino. You need no other things than a passive - bought or DIY - key pad. For DIY, you need only push buttons and wire them in a matrix and let each button connect its row with its column wire when pressed. Then you connect the row and column wires to Espruino GPIO pins.

    Schema of market available or DIY key pad with 12 keys (4 rows, 3 cols):

              col:0    col:1    col:2
                 A4       A5       A6
              
                  |        |        |
           btn __ | btn __ | btn __ |
             0 ||_|   1 ||_|   2 ||_|
               |  |     |  |     |  | 
    row:0 --------{--------(--------(---
       A0         |        |        |
           btn __ | btn __ | btn __ |
             3 ||_|   4 ||_|   5 ||_|
               |  |     |  |     |  | 
    row:1 --------{--------(--------(---
       A1         |        |        |
           btn __ | btn __ | btn __ |
             6 ||_|   7 ||_|   8 ||_|
               |  |     |  |     |  | 
    row:2 --------{--------(--------(---
       A2         |        |        |
           btn __ | btn __ | btn __ |
             9 ||_|  10 ||_|  11 ||_|
               |  |     |  |     |  | 
    row:3 --------{--------(--------(---
       A3         |        |        |
                  |        |        |
    

    No additional components are needed: GPIO pins provide the built-in pull-up/down resistors to supprt the press detection and scanning.

    Usage in your applicaiton is very simple - for example, for a 12-key pad driven with 4 row pins A0..3 and sensed with 3 column pins A4..6 (and default values for start enabled, watching falling edge, and 200[ms] debouncing):

    // var keypad = require("KeypadMDS").connect(... // when available as module 
    var keypad = keypadModule.connect([A0,A1,A2,A3],[A4,A­5,A6],function(btn,tme){ 
       console.log("At " + tme + " btn # " + btn + " was pressed"); });
    

    Principle of operation is:

    • drive pins set to output and low on rows
    • sense pins set to input-pullup and set on watch for scan
    • any press (watch) triggers the scan (passing triggering column and time); scan does:
      • clears all watches
      • sets all drive pins to input
      • sets drive pins individually to output and checks triggering/sensing column pin
      • calculates button number form row(drive) and column(sense) index
      • puts all drive pins back to output and low and set sense pins on watch again
      • invokes callback passing calculated button number and time of press

    The code inlcudes some more functions to avoid overlapping and overruning events. It also includes two console log/debug statments in .scan()- lines 73 and 80 - to be removed for use in your project. Code is attached for easy download and use. Placed in 'project sandbox modules' folder (see Espruiono IDE setings), it can be pulled as KeypadMDS module with require("KeypadMDS").

    // Module for Drive/Sense Multiplexed Keypad 
    //
    // ...provided by allObjects - MIT Lic (C) 2015.
    //
    // Keypad
    // - supports any number of driving pins (rows)
    // - supports any number of sensing pins (cols)
    // - has enable(true/false) for controlled actions
    // - callback called w/ button # 0,1,2,3,... and time
    // - optional:
    //   - start disabled vs. default enabled (eval boolean)
    //   - driving high/watch_rising vs. default low/falling
    //   - custom debounce vs. 200[ms] default (and minimum)
    //
    //
    // Note: momentary-on push switches connect rows w/ cols
    //
    // Example: Keypad controlling AC (pins Espruino Pico)
    //
    // \    sense 
    //   \  pins: pin B1   pin B10
    //     \       |        |        
    // drive \     |        |
    // pins:   \   |        |
    //           \ |        |
    // pin B13 ----0:pwr----1:------ pwr on/off(toggle), N/C
    //             |        |
    // pin B14 ----2:tDwn---3:tUp--- target temp down  , up
    //             |        |
    // pin B15 ----4:fan----5:------ fan on/off(toggle), N/C
    //             |        |
    
    var keypadModule = (function(){
    
      var Keypad = function(dps,sps,cb,e,p,d) {
        this.dps = dps;
        this.sps = sps;
        this.cb = cb;
        this.p = p;
        this.d = d;
        this.e = e;
        this.pl = (p === "up") ? 1 : 0;
        this.np = 0;
        this.ws = null;
      }, p = Keypad.prototype;
    
      p.init = function(_this) {
        this.sps.forEach(function(sp,i){
          pinMode(sp,"input_pull" + _this.p);
        });
        this.drive(this);
        this.watch(this);
      };
      p.drive = function(_this){
        this.dps.forEach(function(dp){
          pinMode(dp,"output");
          digitalWrite(dp,1 - _this.p);
        });
      };
      p.watch = function(_this){
        this.ws = [];
        this.sps.forEach(function(sp,i){ var si = i;
          _this.ws.push( setWatch( 
                function(ep){ _this.scan(si,ep.time); }
              , sp
              , { repeat: false
                , edge: (_this.pl) ? "falling" : "rising"
                , debounce: _this.d } ) );
        });
        this.w = true;
      };
      p.scan = function(si,tp) {
        if (dbg) log(tp - this.np);
        this.ws.forEach(function(w,i){ clearWatch(w); });
        if (this.e && (tp > this.np)) {
          this.dps.forEach(function(dp){ pinMode(dp,"input"); });
          var b = -1, di, dl = this.dps.length, dp;
          var sp = this.sps[si], s = 1 - this.pl;
          for (di = 0; di < dl; di++) { 
            if (dbg) log("scan si: ",si," - di: ", di, " - s: ", s); 
            pinMode(dp = this.dps[di],"output");
            if (digitalRead(sp) === s) {
              b = di * this.sps.length + si; dl = 0;
            } pinMode(dp,"input");
          } this.drive(this); this.watch(this);
          this.np = getTime() + (this.d / 100);
          if (b >= 0) { this.cb(b,tp); } 
        } else { this.watch(this); }
      };
    
      p.enable = function(e) { this.e = !!e; };
    
      return ({connect:function(
         drivePins  // array of drive pins 
        ,sensePins  // array of sense pins;
        ,callback   // callback acceptin button # 0,1,2,...
        ,enabled    // optional, default = true
        ,pull       // optional, default = "up"
        ,debounce   // optional, default = 200, min = 200, [ms]
        ){
          var kp = new Keypad(drivePins,sensePins,callback
            , !enabled
            , ((pull === "up") ? pull : (pull === "down") ? pull : "up")
            , (    (isNaN(debounce)
                || ((debounce * 1) < 200)
              ) ? 200 : debounce * 1) );
           kp.init(kp);
           return kp;
      }});
    })();
    
    var dbg = true;
    var log = function() { console.log(arguments.join("")); };
    
    // var keypad = require("KeypadMDS").connect(
    var keypad = keypadModule.connect(
        [B13,B14,B15], [B1,B10], function(btn,tme){ // 0,1,2,3...
          if (dbg) log("" + tme + ": Button # ",btn," pressed: "
              , [ "pwr toggle","N/C"
                , "temp down" ,"temp up"
                , "fan toggle","N/C"
                ][btn] );
      }); 
    

    *Fedback is welcome. 'Exercise' was triggered by - paraphrased - How can I do some Arduino(-like) loop to monitor my hardware events...* (see conversation).


    3 Attachments

  • Sample output from KeypadMDS_Inline.js code (with AC control application in mind that toggles power and fan and 'ups' and 'down' temperature):

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v80 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    949377234.41604518890
    scan si: 0 - di: 0 - s: 0
    949377234.41604518890: Button # 0 pressed: pwr toggle
    4.81315994262
    scan si: 0 - di: 0 - s: 0
    scan si: 0 - di: 1 - s: 0
    949377241.43724060058: Button # 2 pressed: temp down
    -1.82220757007
    2.95375728607
    scan si: 0 - di: 0 - s: 0
    scan si: 0 - di: 1 - s: 0
    scan si: 0 - di: 2 - s: 0
    949377246.60013008117: Button # 4 pressed: fan toggle
    6.82493495941
    scan si: 0 - di: 0 - s: 0
    scan si: 0 - di: 1 - s: 0
    949377255.63546943664: Button # 2 pressed: temp down
    78.17845439910
    scan si: 1 - di: 0 - s: 0
    scan si: 1 - di: 1 - s: 0
    949377336.02310276031: Button # 3 pressed: temp up
    -1.92238414287
    2.93543815612
    scan si: 1 - di: 0 - s: 0
    949377341.16769027709: Button # 1 pressed: N/C
    0.59415245056
    scan si: 1 - di: 0 - s: 0
    scan si: 1 - di: 1 - s: 0
    scan si: 1 - di: 2 - s: 0
    949377343.96973037719: Button # 5 pressed: N/C
    > 
    

    Noteworthy are lines with the negative numbers: Events in espruino are queued.... and with a lousy switch - I just used I wire to touch a Espruino Pico castellation pad which produced a lot of queud events... interestingly not on the touch, but on the 'move away' which then on scan did not show any buttons pressed. Therefore, .np - earliest / Next allowed Press time is set - with current time plus set debounce time - and checked to filter and ignore these events. It helos avoid faulty callbacks for now, but I'm not settled on it it yet. There must be a better approach... I hope.

    Verified module usage with this code:

    var dbg = true;
    var log = function() { console.log(arguments.join("")); };
    
    // var keypad = keypadModule.connect( // for inline use
    var keypad = require("KeypadMDS").connect( // for module use
        [B13,B14,B15], [B1,B10], function(btn,tme){ // 0,1,2,3...
          if (dbg) log("" + tme + ": Button # ",btn," pressed: "
              , [ "pwr toggle","N/C"
                , "temp down" ,"temp up"
                , "fan toggle","N/C"
                ][btn] );
      });
    

    Producing this output:

     _____                 _
    |   __|___ ___ ___ _ _|_|___ ___
    |   __|_ -| . |  _| | | |   | . |
    |_____|___|  _|_| |___|_|_|_|___|
              |_| http://espruino.com
     1v80 Copyright 2015 G.Williams
    >echo(0);
    =undefined
    949385591.92534446716: Button # 3 pressed: temp up
    949385600.08324909210: Button # 5 pressed: N/C
    949385630.64551258087: Button # 0 pressed: pwr toggle
    949385633.89211368560: Button # 2 pressed: temp down
    949385641.41957569122: Button # 0 pressed: pwr toggle
    949385645.01201820373: Button # 4 pressed: fan toggle
    > 
    
  • Looks great - just wondered though, how is this different to the existing http://www.espruino.com/KeyPad module? I think that does a very similar thing with pullups/watches.

  • @Gordon, ...just returning from looking at it for he first time... :( ... :[ ... 8{... LOL

    Intially, I had the feeling there must be already something because you listed a keypad under items with Espruino... but somehow did not look it up... and the problem kind of 'challenged' me. So my work was totally unbiased... may be because of the forum question and not finding a reference to the keypad module in your post there, I ventured into this exercise. Looking now at the existing moudule, it feels really like a programming excercise (like at a job interview)... but it was fun and is even more fun now to look at both and have 3rd thoughts (phase of 2nd thoughts have long gone).

    Conceptually I find the same things: array of driving pins and array of sensing pins, setting and clearing watches. Differences are:

    1. 'both' and 'repeat' on watches vs. only falling or rising and single
    2. read/write with array of pins in one shot where possible vs. looping through pin arrays
    3. closure/ module using functions vs. object with methods
    4. user can ask status (scan status) when no callback is provided vs. always requires callback
    5. available module has no enable/disable

    Have to test and figure out behavior... used just wire to touch the castellation pads (have no keypad and was too lazy to wire up a DIY), and got flurry of events, which puzzled me, because I asked for 'repeat: false' in setWatch-option. Initially, in scan (readStatus in existing module), I wanted to use just the digitalWrite/Read with implicite output / input pinMod() setting on the driving pins, but it did not do what I wanted... may be I missed something or most likely got derailed by some other temporary issues the code had.

    Other than that, about the same... the available module looks a bit leaner due to function use and write/read of array. Both use set and clear watches... not much other way to do that.

  • After having looked through both implementations side by side, I'm looking for some enhancement:

    • control - restrict to or allow - single mutual and multiple simultaneous pressed buttons, respectivley (easier to implement in the readStatus() approach of existing module)
    • information about button-down (press event) / button-up (release event) either by info in single callback or by having separate callbacks
    • long-press support to call callback(s) multiple times with repetition speed option (useful in settings dialogs over a range with incrementing increments/decrements, 1 unit for 5 units or for defined time, 5...n units after that for number of units or defined time, n...m units after that...

    I'm thinking about a simple and lean for simple requirements, and dynamically extensible for more elaborate requirements. Minification may though defeat that in standard use. Minify and split up could be an option... or just have two modules with compatible simple interface.

  • multiple simultaneous pressed buttons

    That could be difficult - it was talked about in another thread on here, but unless you have diodes across each button it can get difficult to determine which button is pressed without 'ghosting'.

    But yes, I'm happy for you to submit a separate KeyPad module with extra features in it...

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

Keypad - bought or DIY - controlled directly by Espruino

Posted by Avatar for allObjects @allObjects

Actions