• We should probably have modules for at least one port expander - maybe something that comes in SPI and I2C versions. I would propose the MCP23008/23017/23S08/23S17 (S = SPI, 8 or 16 is number of ports) series. (the 23x16 series is not recommended for new designs per Microchip the 17 and 18 are the replacements)

    The nature of a port expander, however, requires more thought than a typical module, however, because people will want to have other devices on the other side of the port expander... such that we should, where possible, design it so that pins on the port expander could be used with existing modules, preferably without any special modifications.

    These MCP23xxx port expanders have:
    8 or 16 pins, arranged in 8-bit ports
    Pin can be input or output, and have a pullup that can be enabled
    Pins can be set to generate an interrupt on the INT pin.

    So a pin can be read, written 0 or 1, configured as input, output, or input_pullup, and enabled for interrupt.

    One of my first thoughts was to return a group of objects that act a lot like the pin class, which in turn would call a function in the port-expander object that actually translated them into a command to send over I2C/SPI

    {"A0":
    {
    "write":function(data){this.pe.write(this.port,this.bit,data);},
    "read":function(){return this.pe.read(this.port)|1<<this.bit;},
    "mode":function (mode) {this,pe.mode(this.port,this.bit,mode);}
    "bit":0, "port":0
    }

    This has a few problems, though:

    1. It'd be a little slow with all the I2C accessing going on.
    2. you can do digitalWrite([pin,pin,pin,pin]) and it will set them all almost at once. This wouldn't be possible.
    3. The real pin class doesn't have mode()
    4. most modules are currently written with arduino conventions (digitalWrite) not pin.write().

    What do people think on this? Is there a more sensible way?

  • Well, it seems sensible - just my 2p, but:

    1. Not much can be done about that I guess... Having writePort function would at least allow people to skip the individual pins if they wanted.
    2. digitalWrite could probably be modified such that it could detect objects with a write function, and could then call it. Same with digitalRead, pinMode, etc. It would add to code size, but probably wouldn't hurt execution speed as much. It's still unlikely to help with things like software SPI and OneWire, as they use a 'fast path' for writing to pins.
    3. I'd be happy to add this - it may not even require writing a new function.
    4. We could have a go at changing these. I did some quick tests, and .write is actually ~5% faster and uses less code space than digitalWrite, so changing it would make sense (unless you needed to digitalWrite arrays.
  • 1/2. Yeah - but you'd like to be able to wrap that all up into one call over I2C, instead of one per pin (which is possible, but I don't see how you'd tell digital write that).

    THREE) . Cool :-)

    FOUR) . Yeah - I think we need a module to get the basic communication to a port expander working and make sure we're not missing anything here.

    But yeah, I may take a stab at this next weekend (I ordered a few). It's conceptually simple, and I feel like done right, it would really show the Espruino programming paradigm's strong points.

    WOW this forum does NOT want you to start a list with a number other than 1, or skip a number in a list.

  • ...yea, the list detection/handling is kept very frugal. Makes it simple, but may be a bit too much! To be fair, it is not 'kisS'.

  • Loving the OO treatment toward logical sameness of something whose physical interface is different.

  • I just tried this out on an MCP23008... it actually seems to work

    
    exports.connect = function(i2c,rst,i2ca) {
    	
        return new MCP32x08(i2c,rst,i2ca);
    };
    
    function MCP32x08(i2c,rst, i2ca) {
      rst.write(0);
      this.i2c = i2c;
      this.i2ca = (i2ca===undefined) ? 32:32+i2ca;
      this.rst=rst;
      this.m=255;
      this.pu=0;
      this.olat=0;
      this.rst.write(1);
      this.A0=new PEP(0,this);
      this.A1=new PEP(1,this);
      this.A2=new PEP(2,this);
      this.A3=new PEP(3,this);
      this.A4=new PEP(4,this);
      this.A5=new PEP(5,this);
      this.A6=new PEP(6,this);
      this.A7=new PEP(7,this);
    }
    
    MCP32x08.prototype.s=function(r,d){this.i2c.writeTo(this.i2ca,r,d);};
    MCP32x08.prototype.r=function(r){this.i2c.writeTo(this.i2ca,r);return this.i2c.readFrom(this.i2ca,1)};
    
    MCP32x08.prototype.mode=function(pin,mode) {
    	var bv=1<<pin;
    	this.s(0,mode=='output'?(this.m&=~bv):(this.m |=bv));
    	this.s(6,mode=='input_pullup'?(this.pu|=bv):(this.pu&=~bv));
    };
    MCP32x08.prototype.write=function(pin,val) {
    	var bv=1<<pin;
    	this.olat&=~bv;
    	this.s(9,this.olat|=(bv*val));
    }; 
    MCP32x08.prototype.read=function(pin) {
    	var bv=1<<pin;
    	return (this.r(9)[0]&bv)>>pin;
    }; 
    
    function PEP (b,p){
    	this.b=b;
    	this.p=p;
    }
    PEP.prototype.set=function(){this.p.write(this.b,1)};
    PEP.prototype.unset=function(){this.p.write(this.b,0)};
    PEP.prototype.write=function(v){this.p.write(this.b,v)};
    PEP.prototype.read=function(){return this.p.read(this.b)};
    PEP.prototype.mode=function(m){this.p.mode(this.b,m)};
    
    
    
    >process.memory();
    ={ "free": 4824, "usage": 276, "total": 5100, "history": 121,
      "stackEndAddress": 536958396, "flash_start": 134217728, "flash_binary_end": 317952, "flash_code_start": 134234112, "flash_length": 393216 }
    >pe.A4.mode('input_pullup');
    =undefined
    >pe.A4.read()
    =1
    >pe.A4.mode('output');
    =undefined
    >pe.A4.read();
    =0
    >pe.A4.set()
    =undefined
    >pe.A4.read();
    =1
    >pe.A4.unset();
    =undefined
    >pe.A4.read();
    =0
    > 
    
    

    much easier than I'd expected (though there's still some more to to, including expanding to the 16-bit version (there are two ways the data can be organized...)

  • That looks really neat... Maybe you want some feedback if someone specifies an invalid pin mode?

    MCP32x08.prototype.mode=function(pin,mod­e) {
      if (["input","output","input_pullup"].indexOf(mode)<0)
        throw "Pin mode "+mode+" not available";
    

    And if this is a form that you'd copy for other port expanders too, a readPort/writePort (or similar) function that reads/writes all pins at once would be really cool - as I imagine it's something that might often be required.

    Looks great though - it'll be really good to have different types of expander supported.

  • Thanks, good thinking. I'd resisted the input sanity checking on .mode() out of fear of reducing performance, but I realize now that pin.mode() is the one case where you don't care so much about speed.

    The port functions are planned, and trivial to implement.

    Should the pin class (PEP) be in a different module? If so, how should I name it? I think the same pin class could be shared by most port expanders.

  • Just realised - it's reset, not unset in Espruino (although unset would probably have made more sense :).

    Hmm, I'm not sure about the separate module for PEP. It definitely feels like it should be in a different module, but actually the functions are quite small and there's not much common code - I wonder who'd use two different port expanders in the same project (which would be the only time it really helped).

    If not, defining the class in the same file means you could make it faster by 'inlining' MCP32x08.write if you wanted to... for instance:

    PEP.prototype.set=function(){this.p.s(9,this.p.olat|=1<<this.b)};
    PEP.prototype.reset=function(){this.p.s(9,this.p.olat&=~(1<<this.b))};
    
  • Just another thing (not sure if it helps) but instead of storing the pin number in the pin (which isn't really used) you could actually store the mask (1<<b) instead and make the code a bit neater?

  • Thanks - yeah, the question is between inlining and using a separate file - I'm not sure which is best...

    pin.reset()? Oh, yeah. Well it should be unset(), hrrmph!

    You know, a shift register module could be made that used similar virtual pins, though the behavior you could get from it would be limited....

    storing the bitmask would make it ever so slightly faster, that's a good idea. I guess inlining is probably the way to go, but I think that generic class has some value, maybe in a doc or tutorial page...

  • In terms of what's going to use RAM in Espruino, I'd say single file for now - it could always be changed at a later date.

    A shift register one would be good too - and you could just specify how many shift registers were chained in the constructor :)

  • Oh goddamnit, I accidentally deleted this post, thought I was replying. grumble

  • Are you reusing m once as a method/function name in line 38 [30] for 16 [8] bit expander, but overwrite it in the constructor - which happens 'after' definition of the prototype and therefore acts as an overwriting per instance with the number 65535 (0xFFFF) [255 (0xFF)] in line 16 for both 16 [and 8] bit expander? May be you wanted to call the m in the constructor mask?

    This issue completely explains what's going on: after construction the object's prototype does not matter anymore, because the constructor assigns an m for the instance...

    PS: Sorry that I could not be of more help, even though I ordered two expanders a while ago and they are sitting idle now already for more than - I guess - a month. Initially, I had planned to use them for some ueber-sized 7-segment LCD displays in the props of a children's musical with time travel... but ended up with a 'cheapo solution': large tv and a html page with add/remove css classes to lite and un-lite the segments. At first, I wanted to use SVG (scalable vector graphics), but ended up with simple table tag for the production... for time reasons (shame on me... but for redemption, I did it afterwards with vector graphics and it looks much nicer...) .

    The html with embedded javascript code was intended for functional development with simple debugging (...last but not least also to move ahead while waiting for the expanders to arrive). The timesDisplay.html was running as a pop-up driven by a Controller Web application using var win = window.open(url,...) and sending commands to the display objects in the pop-up using win[nowDisplay].print(2015);

    For production, the display would run by a Wifi connected and HTTP server running Espruino Pico, and the driver Web app would make CORS AJAX calls...

    All a bit ambitious and too much a stretch for the available build time frame (and also 'complicated'...). In the end, multiple laptops ran the show with fullscreen popups on large external second screens... controlled by stage hands behind the scene... (:

    Attached timesDisplayHTML.html file is standalone - click on it to see it running. Providing a scaling factor from 0.05 .. 1.0 as search string in the url (...?1.0) allowed adaption to the different displays used in the production. Clicking into the screen makes it fullscreen. As mentioned above, the SVG redemption was not just a 7-segment but a 17-segment display. Clicking in the display shows it scrambling.


    4 Attachments

  • Looks like @allObjects is spot on.

    Really nice 7 segment stuff there - I like the use of HTML tables in the first one ;) Shame you didn't get to make the hardware, but I can imagine it'd take a while!

    I did see a nice 7 segment build that had been done using WS2812s though. I'm very tempted to give something like that a go at some point.

  • WS2812 - indeed, because you get the 'latching' for free...

  • Before the 7-segnent display, I considered an Espruino controlled a flip-clock display / split-flap display / *Fallblatt Anzeige: split-flap 'drum' moved by stepper motor controlled by Wfii Espruino, directed by a Web app (html/javascript) provided by Espruino Web server. But that would have demanded even more 'mechanical work' - for which there was just no time... This nice javascript version of a flip-clock - see screen shot below - I just found now; shame that I did not find it before as a good head start. Building myself with '3d rotation/translation of cards' was not exactly on my agenda, even though just completed a 'dice jack' game using this css3 technology to roll dice in 3d. Dice jack game is built with plain html, some js, and css, whereas flipclockjs.com's solution uses (heavily?) jQuery...


    2 Attachments

  • Wow.

    Can't believe I missed that. Thanks.

    I think I'll be able to put in a pull request for these tonight.

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

Port Expander (working module code inside - how does it look)

Posted by Avatar for DrAzzy @DrAzzy

Actions