Port expander (MCP23017) and software SPI / I2C

Posted on
  • Is it possible to use ports from an expander to do software SPI or I2C? My scenario involves driving a MAX7219 display or NRF24L01 via the expander.


  • Most likely you cann0t use a port from a PortExpander to do software SPI / I2C... for sure not with the Espruino built-in software... you would have to write it yourself... and because you talk already through SPI / I2C, you just cannot hit the same speed/timing...

  • You could do very slow software SPI pretty easily over a port expander, and could even write your own SPI class which you could feed into existing modules.

    I have literally just made digitalWrite/Read able to use an object passed in, so assuming you're using a 'cutting edge' build of Espruino (or 1v95 when it's available), you should be able to do:

    var spi = {
      write : function() { SPI write code }
    var cspin = {
      write : function(v) { write value to pin }
    var disp = require("MAX7219").connect(spi,cspin);
  • I just saw your other post and the title, and with the MCP23017 the code would look a little like this:

    var port = require("MCP23017").connect(i2c, null, address);
    var mosi = port.A0;
    var sclk = port.A1;
    var spi = {
      write : function(data,cs) {
        if (cs) cs.write(0);
        E.toUint8Array(data).forEach(function(by­te) {
         for (var i=7;i>=0;i++) {
        if (cs) cs.write(1);
    var disp = require("MAX7219").connect(spi,port.A2);­

    Not tested, but it should work. The nRF24 reads from SPI, so you'd need to implement an SPI.send function to get it working though.

  • Thanks so much! I’ll give it a try. If I can drive the display via port expander, then I can use regular SPI (hw or sw) for nRF24.

  • You know you can also share the SPI pins? So you just re-use the same MOSI, MOSI and SCLK for all your parts, and then give them separate CS pins?

    Also with I2C you can just share the same 2 wires for everything, as long as you have different devices (so they have different addresses). It might even be that you can do what you need without a port expander?

  • That's a great solution! I still need the expander for incorporating an 8x8 key matrix, but then with the nRF24 and MAX7219 sharing the SPI pins I can do hardware SPI for them. Then I'll still have additional GPIO on the expander for simpler input/output.

    Question, can I share I2C pin (ex. CLK) with SPI usage, esp. when the I2C pins have external pullup resistors?

  • BTW user81779 is also me (N00b) - replied using another pc with cookie with that one (aargh)

  • I am in the near future in a similar situation (needing to address multiple spi, i2c and one uart device). If my research so far is correct sharing pins between protocols will not work.. maybe if your SPI devices and I2C devices can work at the same frequency.. but even then i am skeptic.

    To my astonishment there seems to be no easy way to share pins with multiple devices for SPI.
    I found a I2C hub from seeed studio which i will give a try.. but common solution seems to involve soldering, making your own cables or using a breadboard. Have you by any chance found a solution for this?

  • To drive multiple SPIs, you need multiple NSS (negative serial select) pins, which you can and usually do specify in the .send() and .write() so you do not have to do some extra code to pull the NSS pin low (negative) before the transmission and push it back up high after transmission for devices that need it. Espruno (= @Gordon) made this so simple that you can use an array and nss as parameters to avoid having to push and pull the NSS yourself, even for multi-segmented transmission. If you cannot - or do not want to go with an array or for what ever reason want to control NSS at your terms, you pull it low before the transmissions, transmit without nss parameter, use nss parameter with the last transmission, or separately after last transmission without nss parameter. Having the NSS(s) on portexpander ports works well, because there is not really a time constraint from pulling it low to the begin of transmission and from the end of transmission to pulling it up high again. SPI even allows you to interrupt a current transmission for handling something more important or pressing and then resume transmission. For interruptions you need though another line, which is usually called NHOLD - negative hold. SPI devices for mass data - such as SPI driven memory (FRAM/MRAM/SRAM/EEPROM) - have usually this NHOLD implemented (see, for example, FRAM Ferroelectric RAM as used in the conversation about 256-Kbit (32 K × 8) Serial (SPI) F-RAM - Ferroelectric RAM - SPI challenges). (FRAM is like FLASH EEPROM but without EEPROMs' drawbacks: no wait after write and no erase of pages to write again. It is like non-volatile RAM that holds data without having to be powered. TI uses it in some of their micro controllers which make a save before hibernate or power fail a non-event / obsolete. Btw, the FRAM application code uses a combination of separated and .send() / .write() integrated NSS handling (multi segment transmissions consists of send command, send address, send/receive data byte(s)...).

    If you run out of pins, you can use a portexpander on SPI - or, if you still have a few pins to play with - use a plain 3-to-8 Line Decoder (74HC138) - to drive the NSS, NCS (Negative Chip Select) or what-ever-(time-uncritical)-control lines. The 74HC138 has even 3 'Enable' lines - 1 ENABLE and 2 NEGATIVE ENABLE for easy cascading: 4 pins control 16 NSS with simple digital write.

    Minimal number of control ports and wiring aren't the only reasons to pick a portexpander solution: (especially MCP23017) portexpanders have good interrupt support to minimize amount of code to execute to get the job(s) done. For less demanding applications, you can use SNx4HC595 8-Bit Shift Registers With 3-State Output Registers as demonstrated in conversations about Driving LED matrix with shift registers such as 74HC595 and Retro Bubble Displays driven with 74HC595.

    You can use up to 8 SPI connected MCP23017 portexpanders without spending any extra pin: you just connect the MCP23017 portexpanders' A0..A1 pins accordingly to GND and 3.3V and supply the 'HAEN: Hardware Address Enable bit' bit as 1 and portexpander address A0..A2 in the command byte to address the portexpander you want to talk to. There is practically no limit when using (cascaded) portexpanders: one portexpander you use to control the address lines (dynamically).

    So far about SPI. I2C is not much different then that all goes solely over hardware control lines and no chip/device address information data in the commands. Even with a puck that has very limited pins (and pads) as ports exposed, you can run a multi SPI and multi I2C configuration... Most likely you will run out of processing power before you ran out of addresses.

  • common solution seems to involve soldering, making your own cables or using a breadboard. Have you by any chance found a solution for this?

    You must solder, make a Y-cable (probably via soldering) or use a breadboard. It may be possible to buy Y-cables, but I haven't found a decent source for them.

    I often use little pieces of prototyping board as splitters - but you still have to solder the wires or pins to it. (I sell boards like this here: https://www.tindie.com/products/DrAzzy/m­ini-protoboard-pieces-28-pcs-set-only-6-­/ )

  • Thx @DrAzzy i think this could help me.. and i did see a version with mounting holes in your tindie store that i like even more. While researching easy solutions i came across this https://www.youtube.com/watch?v=Fau2naIk­bNU

    .. so awesome!

    -- Edit --
    Just ordered some boards Thanks! :)

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

Port expander (MCP23017) and software SPI / I2C

Posted by Avatar for n00b @n00b