• While exploring MCP23017 PortExpander driving 4x4 KeyPad w/ interrupt / setWatch for 'keyDown' detection @Gordon posed the question in post #3

    could (interrupt handling) be added to the existing MCP23017 module?

    This conversation is about exploring the options of that.

    As first thing I made a deep dive into the current MCP12017.sjs I2C module and built a test bed - wiring and code - as a solid regression base for adding things and not breaking things (the current code though already breaks things a bit by its different naming... mainly to support my thought process), but that can be reversed.

    Test wiring is for 3 ports - for now - involving these Portexpander and Espruino(-Wifi) pins (as taken from the code. - x.A0 referring Port A0 of Portexpander; var d=... is there only for high lighting the usually very dim and illegible code comments by this forum's css settings --- fine for focus on code, but not so useful when trying to explain code through 'inline' comments):

    var d=`
    // ***** REGRESSION *****
    // --- wiring
    //   Portexpander          Espruino-WiFi (drivers, probes)
    //   (pullup ~100K         (pullup/pulldown ~30k..40k,
    //    when enabled)         when enbaled)
    //   x.A0 ---------------- A1 driver
    //   x.A1 -----.---R1K---- A4 driver
    //             |---------- A5 (probe digital and anlog)
    //             '---R47K--- GND (Low)
    //   x.A2 -----.---R1K---- A6 driver
    //             |---------- A7 (probe digital and analog)
    //             '---R47K----3.3V (High)

    The breadboard setup is shown in attached screen shot.

    Related test output in console looks like this (example: testing digital input):

    T01---plain_digital_input, probes digital
        S00 reset
        S01 drivers digital input
        S05 probes digital
          oooook A5=false
          oooook A7=true
          oooook x.A0=false
          oooook x.A1=false
          oooook x.A2=true
      ok T01---plain_digital_input, probes digital

    T## identify Tests, S## identify Setups. Setups are small pieces of code that configure and re/set the ports. They are combined to build a complete setup as part of a test.

    ...to be continued...

    1 Attachment

    • testWiringR.jpg
  • @Gordon, a quick thought for a - first minimal, but quite useful - setWatch enhancement is:

    Could the setWatch be given a simple (compiled - for speed) callback in the options? That callback would in this case just read the interrupt relevant registers and pass them - as object - to the existing interrupt handling. The handling would then stick it with the usual information into the JS event/interrupt queue?

    In other words: provide a hook for the application to contribute to the event payload... by extending the existing option with a custom interrupt handler function property. The existing Espruino interrupt handling would - in addition what it already does - invoke the function and stick its return value/object with the other stuff in the JS event/interrupt queue and deliver it as payload to the setWatch callback.

    I understand your 'voiced' concern about '...it could make Espruino instable...' (as any JS -
    and other code anyway already does... because it hogs the single and only processor / execution tread).

    I do not know the struct of the Js event/interrupt queue entries, but I'm sure that it can hold a reference to an object... This object would then include all what is needed as payload when invoking the setWatch callback. (If it is just one object, then this object would include the current information plus the reference to the additional object. I appreciate if it also includes the setWatch handle and the options object, because it comes handy for the application in the setWatch callback (no extra - globals / variables would then have to hang around).

  • extending the existing option with a custom interrupt handler function property

    If you're executing an arbitrary JS function for the interrupt itself then surely there's no need to use the setWatch event queue? You could just emit an event, or do any number of other things.

    it could make Espruino instable

    It's worse than that - the interpreter wasn't designed for reentrancy. While I'm allocating variables in IRQs for the nRF52, it's something I'm actually considering taking out. The issue is that occasionally the interpreter will have to do a GC pass - and it'll do that outside of an IRQ. During that time, any IRQs that fires won't be able to allocate variables.

    But all this seems needlessly complicated - if Espruino isn't too busy, any function supplied to setWatch should execute almost immediately anyway - despite running outside of an IRQ.

  • I guess I understand what your are saying about reentrance...

    I also conclude that the process is to complex and variable to be implemented without memory resources that are exposed to potential GC.

    I though still expect/require a modification to existing setWatch() and clearWatch() functions in order to make it transparent for the application whether it is a built-in pin or or an extension-pin (a pin of any sort of portextender):

    • a) setWatch() has to:
      1. invoke .setWatch() method on passed the extender pin and pass the callback and options object.
      2. hold on to passed portextender pin object in the internal watch object (for any clearWatch() down the line).
    • b) clearWatch() has to:
      1. invoke .clearWatch() method on the held-on portextender pin object(s - plural, when clearWatch is called without arguments).

    With these two extensions it is possible to make any watch operation absolutely transparent - for the pin:

    • setWatch()
    • clearWatch() with watch ID / handle as argument
    • clearWatch() without argument

    Should a)2. - holding on to passed portextender pin object in the internal watch object for any reasons - such as potential memory leak - not be possible, then this alternative is good enough:

    • setWatch has to:
      1. ONLY return the passed portextender pin object reference as the watch ID (rather than just an integer number) and nothin else.
    • clearWatch has to:
      1. detect that the passed-in watch ID is a portextender pin
      2. invoke .clearWatch on the portextender pin
      3. nothing else since there exists no internal watch info

    The alternative does not support clearWatch() with no arguments, and - from my point of view - this is acceptable, because using clearWatch() with no arguments has anyway its challenges. Still transparent to the pin are:

    • setWatch()
    • clearWatch() with watch ID / handle as argument

    With the alternative, Espruino has to store nothing about the watch. Non-built-in, portextender pins trigger the redirect of the argument(s) for both .setWatch() and .clearWatch().

    For alternative it works also that the portextender object is passed rather than a pin. The portextender object has then to understand .setWatch() and .clearWatch, which could set interrupts for groups of pins. The implementations can even go that far, that .setWatch() can return any custom object deemed useful in the watch life cycle, as long as built-in clearWatch() can detect that passed in 'pin' object is not a built-in pin.

  • Thanks - I've just filed an issue for that here: https://github.com/espruino/Espruino/iss­ues/1268

    I think holding on to a reference to the pin could be a bit painful - however your idea of setWatch passing the object back through sounds absolutely perfect, and pretty easy to implement. When I have a free moment I'll have a go at implementing it.

  • That's cool - thank you. In the meantime I use a setWatchX / clearWatchX as stand-in for the respective future implementations. It allows me moving on with the exploration.

  • Great - you know you can also do stuff like:

    (function() {
     var _setWatch = setWatch;
     setWatch = function(...) { _setWatch(...); }

    To fix up the existing setWatch implementation to how you want it. Any modules should then use your new setWatch.

  • ...yes, but I didn't want to get into the business of having to determine whether the passed pin is a 'real' / built-in pin or an 'imaginary' / portexpander pin... when I can get it 'for free' from you... ;)... last but not least also because typeof 'built-in pin' does NOT return something like "Pin". Of course, I can go after all the other things, but that makes it not universal (built-in Pin class would be a 'reserved word') and, takes away the option to turn just any thing in any fashion into a watchable... (variation of object.on("<eventName>",callback)). Alternative to have a working typeof <built-in pin> could be an extension of E like E.isE(spruino)Pin(<object>). ;):.something else I'd like to get for free... and may get so ;? ...or is it already 'there', and I just don't know about it?

  • I think x instanceof Pin will work?

    Although perhaps checking "object"==typeof x is better?

  • Instanceof does work. I would advice against typeof because its not precise enough.
    Just my 2¢ :)

  • Ahh - typeof D0 is actually "number" - so I meant actually using it to check whether someone had fed an object in...

    A bit like:

    (function() {
     var _setWatch = setWatch;
     setWatch = function(a,pin,b) { 
       if ("object"==typeof pin && pin.setWatch)
  • @PaddeK and @Gordon, thank you for the input.

    • you know you can also do stuff like:...

    And here it is this ... (from post #7):

    (function() {
     var _setWatch = setWatch;
     setWatch = function(cb,pin,opts) {
       return (pin instanceof Pin)
        ? _setWatch(cb,pin,opts) // Espruino built-in pin class Pin
        : pin.setWatch(cb,opts); // Portexpander pin class PXP 

    Espruino (you?) is(are) are not so happy with it - understandably:

    Uncaught Error: Unable to assign value to non-reference Function
     at line 6 col 2

    If require() has the same issue, could you 'fix' that too? ...I'm milling around the require() as from require.js (requireJS.org - as it works in the browser) to make AMDs available on Espruino (via communication connection). Detection of alternative implementation is by number of arguments > 1).

    If setWatch() cannot or should not be modified to become assignable, add a property to the options, add a property to the setWatch / clearWatch function that allows alternative implementations... for example:

    setWatch.altImpls = { nonPinClass_1: alternativeImplementationFunciton_1, ... }
    clearWatch.altImpls = { nonPinClass_1: alternativeImplementationFunciton_1, ... }
  • Try global.setWatch = ... - that seems to work. Not 100% sure why doing it directly wouldn't work - I'll file a bug

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

Exploring adding setWatch/clearWatch (inerrupt handling) to MCP23017 (MCP2308) Portexpander Ports

Posted by Avatar for allObjects @allObjects