MPR121 module - how to know when an electrode is touched?

Posted on
Page
of 2
/ 2
Next
  • Hi - just getting started with an original Espruino board. I'm trying to test my MPR121 capacitive sensor breakout board, which I've connected via I2C.

    I am aware of this: https://www.espruino.com/MPR121 but I just don't know where to start understanding how to talk to the 12 electrodes.

    Anyone have a beginner-friendly explanation of how to set up some basic code or blocks - something that would display something like, "Key 1 of 12 has been pressed!" "Key 2 of 12 has been released!"

    Truly, any nudges in a learning direction would be much appreciated.


    1 Attachment

    • IMG_20210913_224733827.jpg
  • Hi! It looks like you have to repeatedly 'poll' the module to ask it what keys are pressed, and then you get back a number where each bit represents a key.

    I2C1.setup({scl:B6,sda:B7});
    
    var lastKeysPressed = 0;
    var mpr = require("MPR121").connect(I2C1, {
       // device is ready
        setInterval(function() {
            var keys = mpr.touched();
            if (keys!=lastKeysPressed) {
              // for each key
              for (var i=0;i<12;i++) {
                var kbit = 1<<i;
                // if it is pressed now, but not before
                if ((keys&kbit) && !(lastKeysPressed&kbit))
                  console.log("Touched: " + i);
                // you can also check the opposite to see if it's released
              }
            }
             lastKeysPressed = keys;
        }, 100); // 100ms = 10 times a second
    });
    

    This isn't tested, but should work...

  • Oh wow - thank you for such a quick reply! I will go test and report back. You are a true blessing!

  • It is starting to make sense. Thanks again!

    Currently, the Espruino IDE is unhappy with the word, "function," as a 'reserved word,' and is giving red flags there and at the 100ms specification. I shall read up on setInterval and tinker more...

    Here are some bold (untested) modifications for my project, in case anyone wishes to see:

    I2C1.setup({scl:B8,sda:B9});
    I2C2.setup({scl:B10,sda:B11});
    
    var lastKeyPressed = 0;
    var lastBankSelected = 0;
    
    var mpr1 = require("MPR121").connect(I2C1, {
      //device 1 (12-key musical keyboard) is ready
      setInterval(function() {
        var keys = mpr1.touched();
        if (keys!=lastKeyPressed){
          for (var i=0;i<12;i++){
            var kbit = 1<<i;
            if ((keys&kbit) && !(lastKeyPressed&kbit))
              //do something, for example:
              console.log("Key: " + i);
          }
        }
        lastKeyPressed = keys;
      }, 100);
    });
                                         
    var mpr2 = require("MPR121").connect(I2C2, {
      //device 2 (12-position rotary switch) is ready
      setInterval(function() {
        var banks = mpr2.touched();
        if (banks!=lastBankSelected){
          for (var j=0;j<12;j++){
            var bbit = 1<<j;
            if ((banks&bbit) && !(lastBankSelected&bbit))
              //do something, for example:
              console.log("Bank: " + j);
          }
        }
        lastBankSelected = banks;
      }, 100);
    });
    
  • Wed 2021.09.22

    Not near development PC.

    Guess here: Missing { } L15 following the 'if'

    Would you mind posting the contents of the L-Hand console side showing that error.

  • Check for mis-matched curly braces by loading code file into notepad++ editor and use it's verification tool. Click on a trailing curly brace to hilight in red. Look upwards in file for matching leading brace, also hilit in red.

    Also look for missing commas and semi-colons using red arrows in IDE.

  • @bertjerred,

    two (2) issues there:

    • the .connect() arguments are not as expected - function object is expected - and
    • malformed themselves - not a proper object, that's why the syntax complain: no object property can be named function, because of function being a reserved word.

    The second argument of .connect() has to be a function() object and the code passes a malformed object { setInterval(... }.

    Small change fixes this.

    I2C1.setup({scl:B8,sda:B9});
    I2C2.setup({scl:B10,sda:B11});
    var mpr1;
    var mpr2;
    var lastKeyPressed = 0;
    var lastBankSelected = 0;
    
    function keyDev() {
      // device 1 (12-key musical keyboard) is ready
      setInterval(function() {
        var keys = mpr1.touched();
        if (keys!=lastKeyPressed){
          for (var i=0;i<12;i++){
            var kbit = 1<<i;
            if ((keys&kbit) && !(lastKeyPressed&kbit))
              // do something, for example:
              console.log("Key: " + i);
          }
        }
        lastKeyPressed = keys;
      }, 100);
    };
    
    funciton bankDev() {
      // device 2 (12-position rotary switch) is ready
      setInterval(function() {
        var banks = mpr2.touched();
        if (banks!=lastBankSelected){
          for (var j=0;j<12;j++){
            var bbit = 1<<j;
            if ((banks&bbit) && !(lastBankSelected&bbit))
              // do something, for example:
              console.log("Bank: " + j);
          }
        }
        lastBankSelected = banks;
      }, 100);
    };
    
    function onInit() {
      mpr1 = require("MPR121").connect(I2C1, keyDev);
      mpr2 = require("MPR121").connect(I2C2, bankDev);
    }
    
    setTimeout(onInit,999); // for dev; comment before save()
    

    If you do not like the extra ('external', non-anonymous, named) keyDev(){...} and bankDev(){}...} functions, you can define them inline as anonymous, just like in the setInterval(function(){...},...); function:

    I2C1.setup({scl:B8,sda:B9});
    I2C2.setup({scl:B10,sda:B11});
    var mpr1;
    var mpr2;
    var lastKeyPressed = 0;
    var lastBankSelected = 0;
    
    function onInit() {
      mpr1 = require("MPR121").connect(I2C1, function(){
        //device 1 (12-key musical keyboard) is ready
        setInterval(function() {
          var keys = mpr1.touched();
          if (keys!=lastKeyPressed){
            for (var i=0;i<12;i++){
              var kbit = 1<<i;
              if ((keys&kbit) && !(lastKeyPressed&kbit))
                //do something, for example:
                console.log("Key: " + i);
            }
          }
          lastKeyPressed = keys;
        }, 100);
      });
    
      mpr2 = require("MPR121").connect(I2C2, function() {
        //device 2 (12-position rotary switch) is ready
        setInterval(function() {
          var banks = mpr2.touched();
          if (banks!=lastBankSelected){
            for (var j=0;j<12;j++){
              var bbit = 1<<j;
              if ((banks&bbit) && !(lastBankSelected&bbit))
                //do something, for example:
                console.log("Bank: " + j);
            }
          }
          lastBankSelected = banks;
        }, 100);
      });
    }
    
    setTimeout(onInit,999); // for dev; comment before save()
    
  • Thank you very much for this advice, and for your patience with my learning curve. I will get notepad++ and use it as you have suggested to make sure all my things are properly enclosed. Awesome!

  • Again, thank you very much for taking the time. I imagine my console stuff is less interesting (for the version I was writing, which had malformed elements) given @allObjects solution below. I appreciate you!

  • I really am amazed at how much time you just saved me. Thank you so much. Later tonight, I'll be able to hook up my device and send this to it.

    As you may have guessed, I am at a very beginner level of understanding - yet I understand and appreciate your clear explanation. The fact that you handed it to me on a silver platter is just amazing. Thank you so much.

    Once I have my beep-boop-beep DIY "synth" making sounds, I will show the results.

    Lastly, I've never experienced a more positive helpful forum anywhere on the Internet. So, thank you @Gordon @allObjects @Robin

  • Hi Bert,
    I think you have another option on the approach with the mpr121 that avoids polling.
    The chip and the breakout boards usually have an interrupt pin that will trigger an output when ever one of the switches is touched.
    If you wire this to an input pin on the Expruino then you can use a setWatch function to execute a function to read the mrp1.touched function.
    https://www.espruino.com/Reference#t_l__­global_setWatch

  • Sorry not trying to confuse, if polling works that's fine I'm sure. Just so you know the option.

  • Wed 2021.09.22

    'another option on the approach with the mpr121 that avoids polling'

    I like that post #11 suggestion @SimonGAndrews and although I haven't measured, I believe that technique also extends battery life, as Javascript isn't continuously executing as a result of the setInterval duration.

  • Wed 2021.09.22

    post #10   'Lastly, I've never experienced a more positive helpful forum anywhere on the Internet. So, thank you'

    Thank you for the kind affirmation @bertjerred

    I agree wholeheartedly as there are many that frequent the forums providing exceptional responses.

    Welcome to the Espruino community!!

  • Wed 2021.09.22

    To extend your exposure to Espruino,

    There are many available examples along with tutorials to learn from:

    https://www.espruino.com/Tutorials

    In addtition, I recently put together a quick summary of links that seem to be visited often:

    Commonly Referenced Links to Review and Bookmark

  • No apologies required! :) This project has already brought me closer than ever to "bare metal" than I've really gone before (like, looking - even from the distance of a free library that I didn't have to write - at things like hardware addresses), so it is all new, all fun, all confusing, and all very much appreciated.

    I also find myself into deeper functions - more complex thinking - than I'm used to, but I welcome the challenge fully. So, your suggestion helps me learn and do new things. How amazing. And you have drawn my attention to options I've certainly overlooked. I was accidentally pretending the IRQ pin wasn't even there. ;)

  • That is a useful insight! The device I'm working on will be plugged in via USB the whole time, but future projects might be portable... thank you!

  • Oh wow - great! Thank you for this as well. I imagine it's hard to predict where people come in from when they're trying out a project, but I guess if you know where they end up, the "commonly referenced links" is pretty handy. I'll be checking that out soon for sure.

    As a side note, my day job is writing instructor at a small college in Syracuse - and one of the things that interests me is hardware/software documentation online. A fascinatingly complex endeavor - and not sure if it could even exist outside of group efforts and the thoughtfulness of people like yourself who contribute to a community's efficiency.

  • Pure coincidence but I was working with an MRP121 keyboard on an Espruino MDBT42Q using the interrupt method. The interrupt is actually active low and hence needs a falling edge detection on the setwatch() function. Also the direct connection of the interrupt to an Espruino pin needs the internal pullup resistor enabling. Here is a working snipet to see the approach.

    // Setup pins and interface to MPR121
    const sclPin = D15;
    const sdaPin = D14;
    const irqPin = D16; // interrupt from MRP121
    
    I2C1.setup({scl:sclPin,sda:sdaPin});
    
    // IRQ is Active Low, open Drain (ref datasheet) - make use of internal pullup 
    pinMode(irqPin,'input_pullup');
    
    // connect to MRP121 via Espruino MRP121 library
    function mprConnected() {console.log ("MPR121 Connected");}
    var mpr = require("MPR121").connect(I2C1, mprConnected , { address: 0x5B});
    
    // define function for interrupt (fires on touch AND release)
    function touchedIRQ() {
            //console.log ("debug - interrupt received");
            var keys = mpr.touched();
            if (keys != 0) {console.log("Touched: " + keys);}
    }
    
    // watch the irqPin and fire the interrupt function on capacitance change
    // IRQ is active low, so trigger the interrupt on a 'falling edge'
    if (setWatch(touchedIRQ,irqPin,{repeat:true­, edge: 'falling'}) === 1) {
      console.log( "MPR Interrupt Enabled" );
    } 
    else {console.log ("error - set watch on interrput pin NOT enabled"); }
    

    Also Note the MPR121 keyboard im using does not have pullup resistors for the I2C: SCL and SDA, so I have added a 4.7 K resistor on my prototype board between each of: sclPin and 3.3v and sdaPin and 3.3v , I believe some MRP121 breakout boards already have these.


    1 Attachment

    • IMG_3212.jpg
  • also note the interrupt fires on both the contact and release of the keypad button. In this snip Im assuming on release , mpr.touched() = 0, that is : no keys are touched so dont print the value. I imangine on a keyboard this may not be the case, so will need another way to to handle this. Good luck :).

  • @bertjerred,

    I'd go for the interrupt setup like @SimonGAndrews shows. You will get a better overall behavior than you get with polling, especially when you plan to do also additional things in our application. First it may look more complicated, but in the end it will be much simpler. You obviously still have to do a comparison what has changed since you always get all bits back.

  • Ta, and sorry for typos above. It is an MPR 121 and not MRP!

  • @SimonGAndrews @allObjects thank you so much. This looks fun, and I feel a wave of new questions percolating. But to be sure, I first have some testing and experimenting to do. I'm going to try the IRQ method.

    @SimonGAndrews, with your 12-key project, do you happen to remember where you embedded your key-specific functionality? For example, did they live inside your touchedIRQ() function? And were they, like, "If keys == 3, do something?"

  • Oh I see - maybe they would be part of the subsequent "If..." section...?

  • @bertjerred,

    you mean something like this (code for 4 bits / 4 touch buttons):

    function onInit() {
      
    console.log("example where bit 0 changed to 1 and bit 3 to 0:");
    
    // event handler functions for 4 bits 0..3 - one for each change to 0 and 1 
    var f0 = // ...turned to 0
    [ () => { console.log("0:0"); } // bit 0 changed to 0 (untouched)
    , () => { console.log("1:0"); } // bit 1 changed to 0 (untouched)
    , () => { console.log("2:0"); } // bit 2 changed to 0 (untouched)
    , () => { console.log("3:0"); } // bit 3 changed to 0 (untouched)
    ];
    var f1 = // ...turned to 1
    [ () => { console.log("0:1"); } // bit 0 changed to 1 (touched)
    , () => { console.log("1:1"); } // bit 1 changed to 1 (touched)
    , () => { console.log("2:1"); } // bit 2 changed to 1 (touched)
    , () => { console.log("3:1"); } // bit 3 changed to 1 (touched)
    ];
    
    // emulating the previously read (old) bits:
    var oldBits = 0b1010; // 10 = 8+2 = 2^3+2^1 - previously read
    // emulating the newly read bits (in poll or sensor interrupt handler):
    var newBits = 0b0011; //  3 = 2+1 = 2^2+2^0 - newly read
    // if-section:
    var difBits = oldBits ^ newBits; // 'xor': 10 ^ 3 =  0b1001 = 9 = 2^3+2^0
    var bit = -1, val = 1;
    while (++bit<4) { // 1,2,4,8 = 4 bits 0..3
      if (difBits & val) (newBits & val) ? f1[bit]() : f0[bit](); // ignore warn
      val<<=1;
    }
    oldBits = newBits;
    
    } // /onInit()
    
    setTimeout(onInit,999);
    

    Ignore the IDE warning for line #.

    There are other options: composing an attribute name from the bit and the bit's value identifying the respective bit handling function in a functions object:

    ...
    var fs = // functions object with bit handler functions as attributes
    { f00: () => console.log("0:0")
    , f01: () => console.log("0:1")
    , f10: () => console.log("1:0")
    , f12: () => console.log("1:1")
    ...
    , f30: () => console.log("3:0")
    , f38: () => console.log("3:1")
    ...
    var bit = -1, val = 1;
    while (++bit<4) { if  (difBits & val) fs["f"+bit+(newBits & val)](); val<<=1; }
    }
    

    To use just the value of the bit - think one bit shifted to the left:

    ...
    var fs = // functions object with bit handler functions as attributes
    { f20: () => console.log("0:0")
    , f22: () => console.log("0:1")
    , f40: () => console.log("1:0")
    , f44: () => console.log("1:1")
    ...
    , f160: () => console.log("3:0")
    , f1616: () => console.log("3:1")
    };
    ...
    var oldBits = 0b1010<<1; // 10 = 8+2 = 2^3+2^1 - previously read
    // emulating the newly read bits (in poll or sensor interrupt handler):
    var newBits = 0b0011<<1; //  3 = 2+1 = 2^2+2^0 - newly read
    ...
    val = 1;
    while ((val<<=1)<32) if  (difBits & val) fs["f"+val+(newBits & val)](); 
    ...
    

    Would be interesting to have some timing stats on the variations to find the least processing overhead - least amount of time to figure what function to call and the calling of it.

    Remember, Espruino interprets on the source - no JIT or other byte code compile - and therefore less source code bytes / statements to process in a loop, the faster the loop and the more responsive the behavior of the device.

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

MPR121 module - how to know when an electrode is touched?

Posted by Avatar for bertjerred @bertjerred

Actions