-
• #2
@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).
-
• #3
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. -
• #4
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()
andclearWatch()
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:
- invoke
.setWatch()
method on passed the extender pin and pass the callback and options object. - hold on to passed portextender pin object in the internal watch object (for any
clearWatch()
down the line).
- invoke
- b) clearWatch() has to:
- invoke
.clearWatch()
method on the held-on portextender pin object(s - plural, when clearWatch is called without arguments).
- invoke
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:
- ONLY return the passed portextender pin object reference as the watch ID (rather than just an integer number) and nothin else.
- ONLY return the passed portextender pin object reference as the watch ID (rather than just an integer number) and nothin else.
- clearWatch has to:
- detect that the passed-in watch ID is a portextender pin
- invoke
.clearWatch
on the portextender pin - nothing else since there exists no internal watch info
- detect that the passed-in watch ID is a portextender pin
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 asbuilt-in clearWatch()
can detect that passed in 'pin' object is not a built-in pin. - a) setWatch() has to:
-
• #5
Thanks - I've just filed an issue for that here: https://github.com/espruino/Espruino/issues/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. -
• #6
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.
-
• #7
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.
-
• #8
...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 ofobject.on("<eventName>",callback)
). Alternative to have a workingtypeof <built-in pin>
could be an extension ofE
likeE.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? -
• #9
I think
x instanceof Pin
will work?Although perhaps checking
"object"==typeof x
is better? -
• #10
Instanceof does work. I would advice against typeof because its not precise enough.
Just my 2ยข :) -
• #11
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) pin.setWatch(a,b); else _setWatch(a,pin,b); }; })()
-
• #13
- 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 therequire()
as fromrequire.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, ... }
- you know you can also do stuff like:...
-
• #14
Try
global.setWatch = ...
- that seems to work. Not 100% sure why doing it directly wouldn't work - I'll file a bug -
• #15
... did not share the code I used... let me do it in the state when I hit the wall with the spike caused by Espriuno pin mode change. It is now - 1v95 - fixed. Did not have the time to resume... :( ...yet... :]. Note the rewrite of the rewrite of the MCP23017 module... with the new name MCP23017i2c16w - MCP2317 in i2c 16 bit (default) mode and w atch-able interrupts.
// MCP23017i2c16watched7.js // // Work in progress: Adding setWatch/clearWatch // to MCP23017.js module pins by allObjects // NOTE: MCP23017 w/ i2c in 16 bit mode and interrupt watched; // Reset / default mode --- BANK=0, MIRROR, SEQOP: // 16 bit, mirror interrupt, sequential r/w // Port A = LSByte and Port B = MSByte of 16 bit word var exports = {}; // module business Modules.addCached("MCP23017i2c16w", function() { exports.connect = function(i2,rst,i2a,irq,ioc) { return new MCP23017i2c16w(i2,rst,i2a,irq,ioc); }; // PortExpander (PX) MCP23017 w/ i2c interface and watched irq // constructor var MCP23017i2c16w = function (i2 // Espruino (ESP) i2c appliance driving PortExpander (PX) ,rst // (optional/falsy) ESP pin PX's Reset pin is connected to ,i2a // 3 bit (0..7) PX Address ,irq // ESP pin PX's Interrupt pin is connected to ,ioc // (optional) IOCONfig, enforced to 16bit, mirror, seq r/w ){this.i2 = i2; // ESP i2c appliance if (rst!==null) rst.write(0); // activate PX reset if ESP pin if ((this.rst=rst)!==null) rst.write(1); // de-activate PX rst this.i2a = (i2a)?32+i2a:32; // PX i2c addr (7 bit) this.irq = irq; // ESP interrupt pin (w) if ((irq!==undefined)&&(irq!==null)) irq.mode("input_pullup"); this.ioc = ((ioc)?ioc&22:0)|64; // enforced 16b, mirr, seq r/w this.wR(10,this.ioc); // IOCONfig reg BANK=0, MIRROR, SEQOP this.io = 65535; // IODIR BA regs int val, 0/1=out/input this.pu = 0; // GPPU BA regs int val, 0/1=no/pull-up this.ol = 0; // OLAT BA regs int val, 0b16 (0.16.0) this.pMs = ["output","input","input_pullup"]; this.wCB = this._wCb.bind(this); // watch cb bound to this (w) this.wId = null; // watch ID (w) this.ie = 0; // GPINTEN BA regs (IRQ/INTerrupt ENAbled) (w) this.ps = []; // pins B7..B0,A7..A0 for iteration (w) var i=65536,j=16,p; while(i>>=1){ // create Pins (P) B7..0,A7..0 this[((--j>7)?"B":"A")+j%8] = (p = new PXP // new Pin|Port: (this // ref to MCP23017 - owning PortExpander (PX) ,i // id of Pin|Port - (single bit in 16 bit value) )); this.ps.splice(0,0,p); } // ...; (w) }, x = MCP23017i2c16w.prototype; x.read=function // read all P's @ once (a word, BA of an integer) (){return this.rR(18); }; // all 16 PX Ps @ once (8..15,0..7) x.write=function // write all P's @ once (a word, BA of an int) (w // a 16 bit word (2 LSBytes BA of the integer) ){return this.wR(18,w); }; x.rBA=function // read all P's @ once as ByteArray/Uint8Array[2] (){return this.rRBA(18); }; x.rR=function // read from r AB a word (2 bytes) as (BA) integer (r // reg in 16 bit / BANK=0 mode and evens of 0x00..0x15 ){this.i2.writeTo(this.i2a,r); // select reg to read from var w=this.i2.readFrom(this.i2a,2); // read Uint8Array[2] return w[0]|w[1]<<8; }; // return int (BA) x.wR=function // write to AB r a word (2 LSBytes BA of an int) (r // reg in 16 bit / BANK=0 mode and evens of 0x00..0x15 ,w // word as integer (2 LSBytes of an integer) ){this.i2.writeTo(this.i2a,r,[w&255,w>>8]); // w A,B of (BA) int return this; }; x.rRBA=function // read from r AB 2 bytes (a word) as Byte Array (r // reg in 16 bit / BANK=0 mode and evens of 0x00..0x15 ){this.i2.writeTo(this.i2a,r); // select reg to read return this.i2.readFrom(this.i2a,2); }; // ret read Uint8Arr[2] x.wRBOA=function // w n bytes (byte|array in 8|16b, no|seq r/w // always starts/ends w/ cfg @ connect (16b,BANK=0, seq r/w) (ioc // can only contol BANK (128), SEQOP (32), DISSLW (16) ,r // register w/ BANK=x as given by ioc ,boa // byte or array (write 1 or n bytes) ){this.i2.writeTo(10,(ioc&176)|(this.ioc&70)); // MIR,ODR,INTPOL this.i2.writeTo(this.i2a,r,boa); // write byte or array of bts this.i2.writeTo(this.i2a,(ioc&128)?5:10,this.ioc); // ioc back return this;}; x.pM=function // set Pin mode by P's id: IODIR i/o, GPPU pull-up (i // id of Pin (single bit in 16 bit value) ,m // mode of Pin: ""output"|input"|"input_pullup" ){var x=this.pMs.indexOf(m); if (x<0) { throw "Pin mode '"+m+"' invalid ("+this.pMs+")"; } this.wR( 0,(x&1) ? this.io&=~i : this.io|= i ); this.wR(12,(x&2) ? this.pu|= i : this.pu&=~i ); return this; }; x.gPM=function // get pin mode by P id (i // id of Pin (single bit in 16 bit value) ){var v=this.rR(0)&i; // IODIR i/o bit, if 0 GPPUP pull-up bit return ((!v)?this.pMs[0]:this.pMs[1+(this.rR(12)&i)?1:0]); }; x.rP=function // read P's pin input value by P id (i // id of Port (single bit in 16 bit value) ){return (this.rR(18)&i) ? true : false; }; x.wP=function // write P's pin output (latched) value by P id (i // id / value of Port (single bit in 16 bit value) ,v // truey/falsy output value ){this.wR(18,this.ol=(this.ol&~i)|(v)?i:0); return this; }; x.wX=function // write to all 16 PX Ps @ once (0..15, A+B 0..7) (v // value as int (16 bit value) ){this.wR(18,this.ol=v); return this; }; x.gP = function // get pin by ID i (w) (i // id of Port (single bit in 16 bit value) ,p // do not supply (implicit var p) ){return this.ps.reduce((pr,px)=>{ return (pr===undefined)?(px.i===i)?px:pr:pr; },p); }; // setWatch / fire watch / clearWatch additions to PX x.sPW=function // set pin watch (w) (cb // watch callback ,p // pin / port ,os // watch options ){var i=p.i; if ((this.ie&i)||(!this.io&i)) throw "Portexpander pin "+i +" already watched or not in input or input_pullup mode"; l(".sPW(): enable interrupt on pin "+i); (os=(p.wOs=os||{}))._cb=cb; // set/create os obj w/ cb,... os._r=!!os.repeat; // ...watch opt repeat,... os._e=(os.edge=="rising" )?true // ...edge rising :(os.edge=="falling")?false // ...edge falling : undefined; // ...edge both os._t=-1; // ...irg last time if (this.ie) { // already irq enabled pins: clear irq watch this.wId = clearWatch(this.wId); l(".sPW(): ad pin "+i+" to other watched ones"); } else { // this is first (and only) irq enabled Pin p l(".sPW(): add pin "+i+" as 1st watched"); } this.wR(4,this.ie|=i); // enable irqs incl Pin p w/ ID i this.rR(16); // clear pend irgs INTCAP, setWatch on .irq this.wId=setWatch(this.wCB,this.irq,{repeat:true, edge:"falling",debounce:0}); return p; }; x._wCb=function(s,t,lt){ // internal watch irq callback (w) var irqLast=this.irq.read() , iFs=this.rR(14) // read irq flags INTF AB regs , iSs=this.rR(16) // read irq states INTCAP, clears irq // , i=this.wR(4,0), os; // dis irq GPINTEN (v decl i,os) , os; l("._wCb: iFs="+iFs+" iSs="+iSs+" irq:"+irqLast+"/"+this.irq.read()); this.wId=clearWatch(this.wId); // this.wId=null; // dump ID of irq watch since it happend this.ps.forEach(function(p){ // iterate all pins for iFs l("._wCB: " + p.i); if (iFs&p.i) { i=p.i;os=p.wOs;lt=0; // p i caused irq if (os._e===undefined) { // watch opt edge = both l("._wCB: irq on pin "+i+" edge:both"); lt=os.t;setTimeout(os._cb,1,iSs&i===i,os.t=t,lt); } else if (os._e) { // watch opt edge = rising l("._wCB: irq on pin "+i+" edge:rising"); if (iSs&i) { // ...and state turned true lt=os.t;setTimeout(os._cb,1,true,os.t=t,lt); } } else { // watch opt edge = falling l("._wCB: irq on pin "+i+" edge:falling"); if (i&~iSs) { // ...and state turned false lt=os.t;setTimeout(os._cb,1,false,os.t=t,lt); } } if (lt && ! os._r) { // watch opt ! rep l("._wCB: remove irq from pin "+i); this.ie&=~i; delete p.wOs; } // remove watch from p } },this); if (this.ie) { // still irq enabled pin(s) l("._wCb: enable interrupts for " + this.ie); this.wR(4,this.ie); // re-enable irqs on 'these' pins this.rR(16); // clear pend irgs INTCAP, setWatch on .irq this.wId=setWatch(this.wCB,this.irq,{repeat:true, edge:"falling",debounce:0}); } }; x.cPW=function // claer watch (w) (p // watch handle = setWatch result (= PX P pin) ){ if (p) { var i=p.i; if (this.ie&i) { l(".cPW: disable interrupt for pin "+i); this.wR(4,this.ie&=~i); delete p.wOs; // rm watch from i if ( ! this.ie) { l(".cPW: remove watch since no pin is watched anymore"); clearWatch(this.wId);this.wId=null; } } } }; // Port / Pin (P/PXP) of PortExpander (PX) - Port 'class' var PXP = function (x // owning PortEexpander (PX) ,i // id of Pin | Port (P) (single bit in 16 bit value) ){this.x=x; // owning expander this.i=i; }, p = PXP.prototype; p.set =function() { return this.x.wP(this.i,1); }; p.reset=function() { return this.x.wP(this.i,0); }; p.write=function(v){ return this.x.wP(this.i,v); }; p.read =function() { return (this.x.rR(18)&this.i)?true:false;}; p.mode =function(m){ return this.x.pM(this.i,m); }; p.getMode=function(){return this.x.gPM(this.i); }; // add watchable to PXP (PorteXpander Pin) p.setWatch = function (cb // watch callback ,os // watch options ){l("PXP.setWatch():"); return this.x.sPW(cb,this,os); }; p.clearWatch = function (wId // watch Id (PX P pin object / this) ){this.x.cPW(wId); }; }); // ***** modified setWatch / clearWatch (function() { var _setWatch = setWatch; global.setWatch = function(cb,pin,opts) { return (pin instanceof Pin) ? _setWatch(cb,pin,opts) : pin.setWatch(cb,opts); }; }()); (function() { var _clearWatch = clearWatch; global.clearWatch = function(id) { return (!isNaN(id)) ? _clearWatch(id) : id.clearWatch(id); }; }()); // ***** application / test begin ***** // --- configuration (Espruno(-WiFi)) var i2 = I2C1 // i2c appliance portexpander is connected to , i2c = B8 // i2c clock , i2d = B9 // i2c data , i2s = 100000 // i2c speed (bitrate per second 100/400/1700k) , i2a = 0 // portexpander 3-bit address (0..7) , irq = B0 // interrupt - required w/ setWatch , rst = A0 // reset (active low, 'clean' alternatives work too) , x = null // portexpander instance , xMN = "MCP23017i2c16w" // portexpander module name , xM = require(xMN) // portexpander module ; // --- init / app --- function onInit() { i2.setup({scl:i2c, sda:i2d, bitrate:i2s}); x = xM.connect(i2,rst,i2a,irq); // opt ioc ommitted / default rr(); // run readWatch BTN / C13 (Espruino-Wifi) rg(); // run greenWatch x.A7 falling } var redWatch = null; function rr() { // run red LED1 toggle when BTN released // setWatch w/ built-in port BTN (...C13 - Espruino-Wifi) var redOn = false; // red LED on redWatch = setWatch(function(s,t,lt) { digitalWrite(LED1,redOn = !redOn); }, BTN, { repeat:true, edge:"rising", debounce:10 }); } function hr() { clearWatch(redWatch); } // halt red LED1 toggling var greenWatch = null; function rg() { // run green LED2 toggle when x.A7 disconn from GND // setWatch w/ Portexpander Port x.A7 (w/ pullup) x.A7.mode("input_pullup"); var greenOn = false; // green LED on greenWatch = setWatch(function(s,t,lt) { digitalWrite(LED2,greenOn = !greenOn); }, x.A7, { repeat:true, edge:"falling" }); } function hg() { clearWatch(greenWatch); } // halt green LED2 toggling var redWatch2 = null; function rr2() { // run red LED1 toggle when x.A6 disconn from GND // setWatch w/ Portexpander Port x.A6 (w/ pullup) x.A6.mode("input_pullup"); var redOn = false; // red LED on redWatch2 = setWatch(function(s,t,lt) { digitalWrite(LED1,redOn = !redOn); }, x.A6, { repeat:true, edge:"falling" }); } function hr2() { clearWatch(redWatch2); } // halt red LED1 toggling // *** development / test helper / conveniences; // test functions to be invoked from console. // --- log function (use like console.log()) function l() { console.log.apply(console,arguments); } // --- log With Time (use like console.log()) - currently not in use function lWT() { var args=[getTime()]; var i=-1; j=arguments.length; while(++i<j) args.push(arguments[i]); console.log.apply(console,args); } // --- r() start reading every rIT[ms] all ports in 16 bit mode // and - on change - log value // --- h() stop (halt) reading ports and logging value var rIT=500, tId = false, iVLast = -1, iV; function r() { // run - continuously reading all ports in 16 bit mode if (!tid) tId = setInterval(function(){ iV = x.read(); if (iV != iVLast) console.log(iVLast = iV); },rIT); } } function h() { if (tId) tId = clearInterval(tId); } // halt - cont reading // --- rm() run monitoring every mIT[ms] and log on change // --- hm() stop (halt) monitoring var mIT=100, mId = false, iXA7=null,sIRQ=null,sFs=-1,v,s; B13.mode("input"); // probe on x.A7 function rm() { // run - monitoring mId = setInterval(function(){ s=""; v=B13.read(); if (v!=iXA7) s+=" iXA7:"+(iXA7=v); v=irq.read(); if (v!=sIRQ) s+=" sIRQ:"+(sIRQ=v); v=x.rR(14); if (v!=sFs) s+=" sFs:" +(sFs =v); if (s.length>0) console.log(s); }, mIT); } function hm() { if (mId) mId = clearInterval(mId); } // halt - monitoring // --- tg() trigger green LED2 flashing watch B14.mode="output"; B14.set(); // driving x.A7 (on input_pullup) function tg() { // trigger green_watch x.A7 digitalPulse(B14,0,0.5); } // --- rtg(n) n repeated triggers every rtIT[ms] of green LED2 flashing watch var rtIT=1000; function rtg(n) { // repeated tg() - green trigger n = ((n) ? n : 10) + 1; l(n); var rtgId = setInterval(function(){ tg(); if (!--n) clearInterval(rtgId); },rtIT); } // *** auto start on upload setTimeout(onInit,500);
Still to be retested w/ 1v95
-
• #16
Hello @allObjects,
Is it useable as it is? It is certainly cool to use signaling from external chips.
While exploring MCP23017 PortExpander driving 4x4 KeyPad w/ interrupt / setWatch for 'keyDown' detection @Gordon posed the question in post #3
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):The breadboard setup is shown in attached screen shot.
Related test output in console looks like this (example: testing digital input):
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