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

Posted on
Page
of 2
Prev
/ 2
  • Wow, thank you. So many things here for me to digest. As always, please forgive my lack of experience, but 1) these are functions within a function... each assigned to 1-bit changes? Is that right? and 2) Espruino (and JavaScript) interpret 'on the fly,' so to speak?

    And 3) if I was using, say, a polling version for MPR121 key strokes successfully (e.g., I can play a C# for as long as I hold down the appropriate key), and I wanted to use an ADC (potentiometer) to alter PWM in realtime while the note is playing continuously, could I?

    I'm not sure I fully understand the order in which the code gets interpreted by the Espruino, and therefore where to embed my "mod wheel" potentiometer loop. Or maybe I just get confused by loops within loops...

    In any case, I sure do appreciate all of this, @allObjects!

  • 1) functions in arrays addressable by index and the index is the bit that has changed.
    2) indeed - because memory is in very short supply in MCs.
    3) Polling or interrupt driven, both methods will provided the bits that are pressed, and the application has to figure what happened by comparing to the previously received bits. Yes, you would make one of the capacitive pads to turn on the C# - when the bits switches to 1 - and the other turn if of C#. The functions would turn on the signal generator that is tuned to a C#. The question is how many signal (generator) you have... I assume you have one for every key.

    I'm not an audio engineer, but from the past I know you can use various devices to turn on the path and let the signal pass to a sum... I'm thinking about a 4916 or better a 4066 - see for example https://www.ti.com/lit/ds/symlink/cd74hc­4016.pdf?ts=1632556242364 , or a DG408LE https://www.vishay.com/docs/78084/dg408l­e.pdf. You could also use an op-amp.

    For producing a sound based on a rotary device: yes you can use a pot and poll with an ADC... (I did this with a circuit to excite an (RF) LC circuit to figure its resonance frequencies... see Espruino controlling LC resonance experiment in HAM Radio class). But you also can just use an encoder that delivers you pulses and a direction - clock and counter clock wise. You set a watch - like you do on an interrupt of a device - and then read the direction in the function and adjust the frequency... In my ham circuit I used watch and polling: watched buttons that increase / lower the frequency, and polling of a sliding pot... The pot I use two ways: controlling the frequency increment/decrement speed when clicking (and holding) the red and green buttons, and when pressed at the same time, the app switches into the analog mode where I poll pot and map it to a frequency.

  • @allObjects you are a Godesend! I'm going to stop asking questions for a while and go try to build build build with all of this magnificent teaching. You (and others on this thread) are really very good educators and I respect the levels of complexity.

    Would love to see some pictures (when the mood strikes you) of things you've made - not that I've ever ventured into ham, but it is intriguing. The closest I've come is an SDR dongle as a listener/explorer of frequencies...

  • ...did dig up the code:

    `
    // resonance.js
    // resonance experiment
    // PWM signal generator with variable frequency
    // agitates LC with variable L and C
    // observed to find resoncances
    
    // *** Exciter, resonance, detector circuit
    //
    // <--sigPin ----------------------------------------­-. 
    //   resonance                 TP3  TP4           TP5 | 
    //   signal s      multi-tap  /    /     D       /    | 
    //   detected  MW(AM) HF Air .----.-----|>|-----.-----: 
    //   0..290mV  Coil 100 wind |    |  Ge E110LF  |     | 
    //               0.5mm Cu on (    |  Si 1N4148  |     | 
    //               41mm/1-5/8" (    |             |     | 
    //         _                 (    |             |+    | 
    //    TP1 | |_| 3.3VPP   TP2 (   ===           ===    / 
    //   /                  /    (    | Cx      C1 ---    \ 
    // >.-outPin--/\/\/\-->.     (    | 1nF    3.3u |-    / 
    //   PWM      R1 330R  )     (    | 2.2n*       |  R2 \ 
    //   variable          )  8x (    | 3.2n        | 56K / 
    //   frequency f    20 )  10 (    | 4.7n        |     | 
    //   32K..262KHz...  w |   w |    | 5.7n        |     | 
    //   for receiver      |     |    | 9.4n        |     | 
    //   antenna          ___   ___  ___           ___   ___
    //   simulation        -     -    -             -     - 
    //                     .     .    .             .     . 
    
    // *** UI / Control / Feedback circuitry to vary frequency f
    //
    // <-slidePin------------------------------­-------------------. 
    //   0..3.3V                                                  | 
    //            3.3V      3.3V      3.3V      3.3V  3.3V        | 
    //              ^         ^         ^         ^     ^         | 
    //              |         |         |   extra |     |         | 
    //              |         |         | read of |     |         | 
    //              \  f down |    f up |  signal |     /         | 
    //           R3 /  button |  button |  button |     \         | 
    //    100..300R \     red |   green |  yellow |  R4 /    R5   | 
    //              /   SW1 |      SW2 |     SW3 |  10K \<-/\/\/\-: 
    //              |         |         |         | Sli /    1K   | 
    //             .-./|      |         |         | der \         | 
    //     speaker | | |      |         |         |     /         | 
    //             '-'\|      |         |         |     \         | 
    //    _        +|         |         |         |     |         | 
    //   | |_|     ===        |         |         |     |         | 
    //   3.3VPP    --- C2     |         |         |     |         |+
    //   PWM       -| 33u     |         |         |     |        ===
    // >-audioPin-->'         |         |         |     |     C3 ---
    //   wave 7 octaves lower |         |         |     |    3.3u |-
    //                        |         |         |     |         | 
    // <-btnPins[1]-----------'         |         |     |         | 
    // <-btnPins[0]---------------------'         |    ___       ___
    // <-sRBtnPin------------------------------­---'     -         - 
    // internal input_pulldown (30k..40K)               .         . 
    
    var go        = true   // keep going on self invocation / 'interval'
      , f         = -1     // frequency put out
      , fMin      = 32768  // lowest used freequency
      , fMax      = 262144 // highst, 65536 * 4 = 18 bits unsigned ()
      , fUp       = true   // frequency hopping up (wobble)
      , outPin    = B7     // wave exciting antenna coil
      , duty      = 0.5    // 0..1
      , audioPin  = B10    // speaker
      , audioDiv  = 128    // 7 octavae lower into audible space
      , s         = 0.0       // signal (resonance, rectified) - discrete
      , smV       = 0.0       // signal in [mV], continuously
      , sigPin    = A0        // signal pin (analog read 0..1=0..3.3V)
      , sChngSens = 0.7       // signal change sensitivity relative
      , sCalib    = 303.334   // signal calibration to show mV
      , sRBtnPin  = B13       // singal extra read button pin
      , ctrlMode  = 0         // 0=buttons, 1=slider
      , ctrlModeI = 1000 // [ms] delay after ctrlMode chnage
      , btnPins   = [B15,B14] // down|up button, both = ctrlMode change
      , debounced = false     // on button press do custom debounce
      , debounceI = 10   // [ms] debounce interval
      , btnHopInt = 10   // [ms] button hop interval min, slider modified
      , btnHopMul = 49        // button hop interval extension multiplier
      , slidePin  = B1        // analog to read voltage divider from slider
      , slideVal  = 0.0       // last read different slider value
      , slideSens = 0.003     // slider sensitivity to detect different val
      , slideAdjM = -0.005    // adjust slider min to cover [0..1]
      , slideAdjR = 1.05      // adjust slider range to cover [0..1]
      , slideHopI = 50   // [ms] slider check/hop interval
      ;
    var hTFact = 1.059463094359; // half tone factor (2**1/12)**12 = 2 oktav
    var qTFact = 1.029302236643495; // quarter tone  (2**1/24)**24 = 2 oktav
    var eTFact = 1.014545334937521; // eight tone f  (2**1/48)**48 = 2 oktav
    
    var lon = true; // log on
    function log() { console.log.apply(console,arguments); }
    
    pinMode(outPin,"output");
    pinMode(sigPin,"analog");
    pinMode(sRBtnPin,"input_pulldown");
    btnPins.forEach(function(b){ pinMode(b,"input_pulldown"); });
    pinMode(slidePin,"analog");
    
    var cnt = 0;
    function upAndDown(fInp,fCdPre) {
      var fChgd = (fInp != f); // f changed
      if (fChgd) { // ----- put freq out if changed
        f = fInp; fChngedT = getTime();
        analogWrite(outPin,duty,{freq:f});
        analogWrite(audioPin,duty,{freq:f/audioD­iv});
      }
      var btnVals = digitalRead(btnPins), nextF = f, hopI, val;
      // if (lon) log(++cnt, btnVals, ctrlMode);
      if (btnVals && ( ! debounced)) { debounced = true;
        setTimeout(upAndDown,debounceI,fInp,fChg­d); // ...w/ same f
      } else {
        debounced = false;
        if (btnVals === 3) { // --- ctrlMode change
          ctrlMode = (ctrlMode === 0) ? 1 : 0;
          if (lon) log("New ctrlMode =",ctrlMode);
          setTimeout(upAndDown,ctrlModeI,fInp,fals­e); // ...w/ same f
        } else {
          if (ctrlMode === 0) { // --- calc next freq on button presses
            slideVal = -1.0; // forces slide value on ctrlMode change
            if        (btnVals === 1) { // --- up
              nextF = (f<fMax) ? f*tFact : fMax;
            } else if (btnVals === 2) { // --- down
              nextF = (f>fMin) ? f/tFact : fMin;
            } hopI = Math.floor((1+analogRead(slidePin)*btnHo­pMul)*btnHopInt);
          } else { // --- calc next freq from slider pos / voltage divider
            val = (slideAdjM + analogRead(slidePin)) * slideAdjR;
            if (Math.abs(val - slideVal) > slideSens) {
              slideVal = val;
              nextF = Math.floor(fMin + (fMax - fMin) * slideVal);
              nextF = (nextF<fMin) ? fMin : (nextF>fMax) ? fMax : nextF;
            } hopI = slideHopI;
          }
          val = analogRead(sigPin);
          if (lon) {
            if (    digitalRead(sRBtnPin)
                 || (fChgd || fCdPre)
                 || (    (Math.abs(s - val) > s*sChngSens)
                      && (f !== fMin) && (f !== fMax) ) ) {
              s = val;
              smV = val * sCalib;
              log(++cnt,"f =",Math.round(f),Math.floor(smV));
          } }
          if (go) setTimeout(upAndDown,hopI,nextF,false);
        }
      }
    }
    
    function h() { go = false; } // halt
    
    function onInit() {
      go = true;
      tFact = eTFact;
      ctrlMode = 0;
      fUp=true;
      upAndDown(fMin);
    }
    
    setTimeout(onInit,500); // while dev'ing
    `
    
  • Sat 2021.09.25

    I wanted to use an ADC (potentiometer) to alter PWM in realtime while the note is playing continuously, could I?

    Code snippets:

    http://www.espruino.com/Software+PWM
    http://www.espruino.com/ADC
    found at
    http://www.espruino.com/Tutorials



    Keeping it short so you can:

    post #28 'go try to build build build . . . '

  • @allObjects , all useful stuff above Ta,. ...not to distract from @bertjerred 's build mission but Think it may be useful here to note what happens if a setWatch interrupt occurs while the previous SetWatch function is still running. Was going to test but see you comented on the subject ( 7 yrs ago :). http://forum.espruino.com/comments/11932­979/
    Is it correct still that setWatch triggered functions are queued and executed by the JS Interpreter in the order in which they occurred?
    (* in the simple case assuming the advanced IRQ' parameter features of set watch are not used )

  • Sun 2021.09.26

    http://www.espruino.com/Reference#l__glo­bal_setWatch

    'Internally, an interrupt writes the time of the pin's state change into a queue with the exact time that it happened, and the function supplied to setWatch is executed only from the main message loop. However, if the callback is a native function void (bool state) then you can add irq:true to options, which will cause the function to be called from within the IRQ. When doing this, interrupts will happen on both edges and there will be no debouncing.'

  • @SimonGAndrews,

    any event / interrupt goes into the queue w/ related context and queue is worked on by the JS 'loop'. Espruino error flags tell you about overruns - http://www.espruino.com/Reference#l_E_ge­tErrorFlags .

    Messing with Espruino's architectural setup is not forbidden, but one should not be surprised when things don't go the way as expected... For 'extremely' time sensitive things, write a dedicated client ad let it co-op with Espruiono as the 'overseer'.

  • I've never seen anyone draw their circuit in the comments before and I love it so much.

  • Thank you :)

  • Mon 2021.09.27

    reply to post #34

    'I've never seen anyone draw their circuit in the comments before and I love it so much'

    @bertjerred feel free to peruse the plethora of posts that @allObjects has done over the years. His expertise in complete embedded ASCII circuit documentation is legendary here within the Espruino forum. I too love it when his skill with a new presentation piques a forgotten hidden corner of the cob web infested aging mind. Always something to garner from that insight.

    I also am old school and keeping documentation within the code file ensures that it won't get lost or more so not forgotten during a future code update or modification. When I stumble across some of those special treats, I'll send or post here.



    reply to post #35

    post #28   Thank you :)   Keeping it short so you can: 'go try to build build build . . . '

    Hope you had an enjoyable time over the week end bit twiddling!! ;-)

  • @Gordon: does the MPR121 module support the filtered and baseline touch values like the Adafruit MPR121 library? The functions are: filteredData(pin) and baselineData(pin).

    https://github.com/adafruit/node_mpr121/­blob/master/index.js

  • Hi! It doesn't support it, but it should be easy enough to add. If you stick this code after you instantiate it, it should add the functionality:

    mpr.readWord = function(reg) {
          i2c.writeTo({address:0x5A, stop:false}, reg);
          var data = i2c.readFrom(0x5A, 2);
          return (data[1] << 8) || data[0];
        };
    mpr.readByte = function(reg) {
          i2c.writeTo({address:0x5A, stop:false}, reg);
          return i2c.readFrom(0x5A, 1)[0];
        };
    mpr.filteredData = function(pin) {
        if (pin<0 || pin>=12) throw new Error("Invalid pin");
        return this.readWord(0x04 + pin*2);
     };
    mpr.baselineData = function(pin) {
        if (pin<0 || pin>=12) throw new Error("Invalid pin");
        return this.readByte(0x1E + pin)<<2;
     };
    

    If you can let me know if it works for you then I'll add it into the main module - but I can't test it here as I don't have the hardware :)

  • Thanks, @Gordon.
    I am getting a response from the following code using your code snippet.
    I am just logging out the ouput of each MPR121 sensor pin as I run my finger over them.
    Two observations: 1) the filteredData output seem to indicate which pins were touched as 2^pin. If pin 0 and pin 2 are touched, I get 2^0+2^2 = 5. I was expecting to get a value indicating the capacitance. 2) the baselineData output is 4 x the filteredData output, but not consistently.

    // set up I2C
    var i2c = new I2C();
    i2c.setup({ scl : D28, sda: D29 });
    
    // Setup pins and interface to MPR121
    const sclPin = D28;
    const sdaPin = D29;
    const irqPin = D30; // 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(i2c, mprConnected , { address: 0x5A});
    
    // define function for interrupt (fires on touch AND release)
    function readValues() {
        for(let i=0; i<12;i++)
    	  console.log(mpr.filteredData(i) + ", " + mpr.baselineData(i));
    }
    
    
    mpr.readWord = function(reg) {
          this.write(reg);
          var data = this.read(2);
          return (data[1] << 8) || data[0];
        };
    mpr.readByte = function(reg) {
          this.write(reg);
          return this.read(1)[0];
        };
    mpr.filteredData = function(pin) {
        if (pin<0 || pin>=12) throw new Error("Invalid pin");
        return this.readWord(0x04 + pin*2);
     };
    mpr.baselineData = function(pin) {
        if (pin<0 || pin>=12) throw new Error("Invalid pin");
        return this.readByte(0x1E + pin)<<2;
     };
    
    setInterval(function(){ 
    	readValues();
      console.log(" " +"\n");
    }, 5000);
    
    
    

    2 Attachments

    • Puck.js IDE console output.png
    • puck.js connected to MPR121.jpg
  • Ok, thanks! I think what might be happening is the readWord/readByte isn't working properly - they're just effectively reading from address 0. It could be because the sensor resets if it receives a STOP I2C signal between packets...

    Please could you try:

    mpr.readWord = function(reg) {
          i2c.writeTo({address:0x5A, stop:false}, reg);
          var data = i2c.readFrom(0x5A, 2);
          return (data[1] << 8) || data[0];
        };
    mpr.readByte = function(reg) {
          i2c.writeTo({address:0x5A, stop:false}, reg);
          return i2c.readFrom(0x5A, 1)[0];
        };
    
  • Thanks, @gordon. It's working much better now.
    Baseline numbers seem stable and I am getting changes on the filtered numbers: untouched pins show 512. Touched pins values drop in the 200s range.

    I'll do more tests to determine the dynamic range.
    I appreciate your help.


    1 Attachment

    • Puck.js IDE console output2.png
  • Great, glad it's working! Let me know when you're happy that it's working as expected and I can push an update to the module.

  • @Gordon, skimming over the code in post #38 as the extension (and post #39 in its use), I think that the 4th 'item' in post #39 should read .baselineData as said in post #39 by @mix2009 (instead of repeating 3th 'item' .filteredData). ...just in case for what you will copy into the update of the module... ;-)

  • Thanks - yes, I noticed @mmix2009 had already tweaked that ;) I've just changed it, and added my updated I2C comms code

  • @Gordon: I have completed my tests.
    Connecting wires of about 10cm drops the readings from 512 to 256.
    When I touch or squeeze the wires, the values range from 5 to 256.
    Results are repeatable, so for my purposes the code is working as expected.
    Thanks again for your help.

  • Great! I'll update the module

  • 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