Help with SPI and MC23S17

Can anyone help me out with MC23S17, ive adapted this example but its not working for me.

this is my code:

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.Spi;
using GHIElectronics.TinyCLR.Pins;
using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace PWMExpander
{
class Program
{
    static SpiDevice device;

    static void Main()
    {
        SpiConnectionSettings settings = new SpiConnectionSettings()
        {
            ChipSelectType = SpiChipSelectType.Gpio,
            ChipSelectLine = G30.GpioPin.PC12,
            Mode = SpiMode.Mode0,
            ClockFrequency = 4 * 1000 * 1000,       //4Mhz
            DataBitLength = 8,
        };

        SpiController controller = SpiController.FromName(G30.SpiBus.Spi2);
        device = controller.GetDevice(settings);

        WriteRegister8(Register.IOCONA, HAEN); // enable the hardware address incase there is more than one chip
        WriteRegister16(Register.IODIRA, pinMode); // Set the default or current pin mode

        SetPinMode(5);

        while (true)
        {
            WritePin(0, 1);
            for (byte i = 0; i < 15; i++)
            {
                Debug.WriteLine(ReadPin(i).ToString());     
            }
            Thread.Sleep(2000);
        }
    }

    public enum Register : byte
    {

        /// <summary>I/O Direction A - controls the direction of the data I/O.</summary>
        IODIRA = 0x00,
        /// <summary>I/O Direction B - controls the direction of the data I/O.</summary>
        IODIRB = 0x01,
        /// <summary>Input Polarity A - allows the user to configure the polarity on the corresponding GPIO port bits.</summary>
        IPOLA = 0x02,
        /// <summary>Input Polarity B - allows the user to configure the polarity on the corresponding GPIO port bits.</summary>
        IPOLB = 0x03,
        /// <summary>Interrupt on change control A - controls the interrupt-on-change feature for each pin.</summary>
        GPINTENA = 0x04,
        /// <summary>Interrupt on change control B - controls the interrupt-on-change feature for each pin.</summary>
        GPINTENB = 0x05,
        /// <summary>Default compare for interrupt on change A</summary>
        DEFVALA = 0x06,
        /// <summary>Default compare for interrupt on change B</summary>
        DEFVALB = 0x06,
        /// <summary>Interrupt Control A - controls how the associated pin value is compared for the interrupt-on-change feature.</summary>
        INTCONA = 0x08,
        /// <summary>Interrupt Control B - controls how the associated pin value is compared for the interrupt-on-change feature.</summary>
        INTCONB = 0x09,
        /// <summary>I/O Expander Configuration A - contains several bits for configuring the device.</summary>
        IOCONA = 0x0A,
        /// <summary>I/O Expander Configuration B - contains several bits for configuring the device.</summary>
        IOCONB = 0x0B,
        /// <summary>Pull Up resistor configuration register A - controls the pull-up resistors for the port pins.</summary>
        GPPUA = 0x0C,
        /// <summary>Pull Up resistor configuration B - controls the pull-up resistors for the port pins.</summary>
        GPPUB = 0x0D,
        /// <summary>Interrupt Flag A - reflects the interrupt condition on the port pins of any pin that is enabled for interrupts via the GPINTEN register.</summary>
        INTFA = 0x0E,
        /// <summary>Interrupt Flag B - reflects the interrupt condition on the port pins of any pin that is enabled for interrupts via the GPINTEN register.</summary>
        INTFB = 0x0F,
        /// <summary>Interrupt Capture A - captures the GPIO port value at the time the interrupt occurred.</summary>
        INTCAPA = 0x10,
        /// <summary>Interrupt Capture B - captures the GPIO port value at the time the interrupt occurred.</summary>
        INTCAPB = 0x11,
        /// <summary>GPIO A - reflects the value on the port.</summary>
        GPIOA = 0x12,
        /// <summary>GPIO B - reflects the value on the port.</summary>
        GPIOB = 0x13,
        /// <summary>Output Latch A - provides access to the output latches.</summary>
        OLATA = 0x14,
        /// <summary>Output Latch B - provides access to the output latches.</summary>
        OLATB = 0x15

    }

    /// <summary>Represents an On state</summary>
    public const byte On = 1;
    /// <summary>Represents an Off state</summary>
    public const byte Off = 0;
    /// <summary>Represents the Output state.</summary>
    public const byte Output = 0;
    /// <summary>Represents the Input state.</summary>
    public const byte Input = 1;
           
    private const byte Address = 0x00; // offset address if hardware addressing is on and is 0 - 7 (A0 - A2) 
    private const byte BaseAddW = 0x40; // MCP23S17 Write base address
    private const byte BaseAddR = 0x41; // MCP23S17 Read Base Address

    /// <summary>IOCON register for MCP23S17, x08 enables hardware address so sent address must match hardware pins A0-A2</summary>
    private const byte HAEN = 0x08;

    /// <summary>default Pinmode for the MXP23S17 set to inputs</summary>
    private static ushort pinMode = 0XFFFF;
    /// <summary>default pullups for the MXP23S17 set to weak pullup</summary>
    private static ushort pullUpMode = 0XFFFF;

    /// <summary>Holds output data</summary>
    private static readonly byte[] ReadBuffer3 = new byte[3];
    /// <summary>Holds output data</summary>
    private static readonly byte[] ReadBuffer4 = new byte[4];

    /// <summary>Register, then 16 bit value</summary>
    private static readonly byte[] WriteBuffer3 = new byte[3];
    /// <summary>Register, then 16 bit value</summary>
    private static readonly byte[] WriteBuffer4 = new byte[4];


    /// <summary>The state of the pins</summary>
    public static ushort PinState { get; set; }

    /// <summary>The inversion mode used for each pin</summary>
    public static ushort InversionMode { get; set; }


    /// <summary>Read the value of a given pin</summary>
    public static ushort ReadPin(byte pin)

    {
        if (pin > 15)
        {
            return 0x00; // If the pin value is not valid (1-16) return, do nothing and return
        }

        ushort value = ReadRegister16(); // Initialize a variable to hold the read values to be returned
        ushort pinmask = (ushort)(1 << pin); // Initialize a variable to hold the read values to be returned
        return ((value & pinmask) > 0) ? On : Off;

        // Call the word reading function, extract HIGH/LOW information from the requested pin
    }

    /// <summary>Read the values of <see cref="Register.GPIOA" />
    public static ushort ReadRegister16()
    {
        WriteBuffer4[0] = (BaseAddR | (Address << 1));
        WriteBuffer4[1] = (byte)Register.GPIOA;
        WriteBuffer4[2] = 0;
        WriteBuffer4[3] = 0;

        device.TransferFullDuplex(WriteBuffer4, ReadBuffer4);

        return ConvertToUnsignedShort(ReadBuffer4); // Return the constructed word, the format is 0x(register value)
    }

    /// <summary>Reads a <see langword="byte" /> from the given <paramref name="register" />
    public static byte ReadRegister8(byte register)

    {
        WriteBuffer3[0] = (BaseAddR | (Address << 1)); // Send the MCP23S17 opcode, chip address, and read bit
        WriteBuffer3[1] = register;

        device.TransferFullDuplex(WriteBuffer3, ReadBuffer3);

        return ReadBuffer4[2];

        // convertToInt(readBuffer);                             // Return the constructed word, the format is 0x(register value)
    }

    /// <summary>Set the inversion of input polarity a pin at a time.</summary>
    public static void SetInversionMode(byte pin, byte mode)
    {
        if (pin > 15)
        {
            return;
        }

        if (mode == On)
        {
            InversionMode |= (ushort)(1 << (pin - 1));
        }
        else
        {
            InversionMode &= (ushort)(~(1 << (pin - 1)));
        }

        WriteRegister16(Register.IPOLA, InversionMode);
    }

    /// <summary>Set the inversion of input polarity for all pins.</summary>
    public static void SetInversionMode(ushort mode)
    {
        WriteRegister16(Register.IPOLA, mode);
        InversionMode = mode;
    }

    /// <summary>Sets the given <paramref name="pin" /> to the given <paramref name="mode" />.</summary>
    public static void SetPinMode(byte pin, byte mode)
    {
        if (pin > 15)
        {
            return; // only a 16bit port so do a bounds check, it cant be less than zero as this is a byte value
        }

        if (mode == Input)
        {
            pinMode |= (ushort)(1 << (pin)); // update the pinMode register with new direction
        }
        else
        {
            pinMode &= (ushort)(~(1 << (pin))); // update the pinMode register with new direction
        }

        WriteRegister16(Register.IODIRA, pinMode);

        // Call the generic word writer with start register and the mode cache
    }

    /// <summary>Sets all pins to the given <paramref name="mode" />.</summary>
    public static void SetPinMode(ushort mode)
    {
        WriteRegister16(Register.IODIRA, mode);
        pinMode = mode;
    }

    /// <summary>Set the pull up mode</summary>
    public static void SetPullUpMode(byte pin, byte mode)
    {
        if (pin > 15)
        {
            return;
        }

        if (mode == On)
        {
            pullUpMode |= (ushort)(1 << (pin));
        }
        else
        {
            pullUpMode &= (ushort)(~(1 << (pin)));
        }

        WriteRegister16(Register.GPPUA, pullUpMode);
    }

    /// <summary>Set the pull up mode</summary>
    public static void SetPullUpMode(ushort mode)
    {
        WriteRegister16(Register.GPPUA, mode);
        pullUpMode = mode;
    }

    /// <summary>Write a value to the pin and record it's state in PinState</summary>
    public static void WritePin(byte pin, byte value)
    {
        if (pin > 15)
        {
            return;
        }

        if (value > 1)
        {
            return;
        }

        if (value == 1)
        {
            PinState |= (ushort)(1 << pin);
        }
        else
        {
            PinState &= (ushort)(~(1 << pin));
        }

        WriteRegister16(Register.GPIOA, PinState);
    }

    /// <summary>Write the given <paramref name="value" /> to the given register</summary>
    public static void WriteRegister16(byte register, ushort value)
    {
        WriteBuffer4[0] = (BaseAddW | (Address << 1));
        WriteBuffer4[1] = register;
        WriteBuffer4[2] = (byte)(value >> 8);
        WriteBuffer4[3] = (byte)(value & 0XFF);

        device.Write(WriteBuffer4);
    }

    /// <summary>Writes the supplied <paramref name="word" /> to the given <paramref name="register" />
    public static void WriteRegister16(Register register, ushort word)
    {
        WriteRegister16((byte)register, word);
    }

    /// <summary>Write the given <paramref name="value" /> to the given register</summary>
    public static void WriteRegister8(byte register, byte value)
    {
        // Direct port manipulation speeds taking Slave Select LOW before SPI action
        WriteBuffer3[0] = (BaseAddW | (Address << 1));
        WriteBuffer3[1] = register;
        WriteBuffer3[2] = value;

        device.Write(WriteBuffer3);
    }

    /// <summary>Writes the supplied <paramref name="value" /> to the given <paramref name="register" />
    public static void WriteRegister8(Register register, byte value)
    {
        WriteRegister8((byte)register, value);
    }

    /// <summary>Write a <paramref name="value" /> to the device record it's state in <see cref="PinState" />.</summary>
    public static void WriteWord(ushort value)
    {
        WriteRegister16(Register.GPIOA, value);
        PinState = value;
    }

    private static ushort ConvertToUnsignedShort(byte[] data)
    {
        // byte[0] = command, byte[1] register, byte[2] = data high, byte[3] = data low
        ushort result = (ushort)(data[2] & 0xFF);
        result <<= 8;
        result += data[3];
        return result;
    }

}
}

the code you started with uses the I2C as the first byte in the transaction. This is improper use but it works! TinyCLR expect you to set the slave address in the config separately and then only handle the data. So you will not be sending the first byte manually TinyCLR will do it automatically.

This should never be in your code WriteBuffer3[0] = (BaseAddR | (Address << 1)); // Send the MCP23S17 opcode, chip address, and read bit

See this please I2C

I believe OP was asking about SPI not IIC.

@Darko
I wrote a working tinyCLR library for the MCP23S18, I believe that’s the open collector version of the MCP23S17, but there may have been other slight differences, I can’t recall.

The project now defunct so I never bothered adding in advanced chip features like input interrupts and what not but if your interested in contributing to the library, I’ll post a repo on GitHub

Oh snap, seems i left this and much of my TinyCLR resources behind at my last employer…

Anyone have a MCP23xxx io expander library for TinyCLR they would care to share?
Shouldn’t be all that much to reproduce but id rather not keep reinventing the wheel if i don’t need to…

you can find it on netduino old forum (.NET Microframework and could easy convert to TinyCLR)…

using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Toolbox.NETMF.Hardware; // Can be removed if only one device is being used and MultiSPI is not being used

namespace Expanders
{
    /// <summary>
    /// Driver for a MCP23S08 I/O expander (http://www.microchip.com/wwwproducts/Devices.aspx?product=MCP23S08)
    /// </summary>
    public class MCP23S08
    {
        /// <summary>
        /// Enumeration of the GPIOs 0-7
        /// </summary>
        public enum ports : byte
        {
            GP0,
            GP1,
            GP2,
            GP3,
            GP4,
            GP5,
            GP6,
            GP7
        }

        /// <summary>
        /// Device configuration settings
        /// </summary>
        public enum settings : byte
        {
            /// <summary>
            /// Enable sequential operation mode
            /// </summary>
            SEQOP_ENABLE = 0xDF,
            /// <summary>
            /// Disable sequential operation mode
            /// </summary>
            SEQOP_DISABLE = 0x20,
            /// <summary>
            /// Enable slew control for SDA pins
            /// </summary>
            DISSLW_ENABLE = 0xEF,
            /// <summary>
            /// Disable slew control for SDA pins
            /// </summary>
            DISSLW_DISABLE = 0x10,
            /// <summary>
            /// Enable hardware address pins (allows multiple MCP23S08 on one chip select pin)
            /// </summary>
            HAEN_ENABLE = 0X08,
            /// <summary>
            /// Disable hardware address pins (set both to 0)
            /// </summary>
            HAEN_DISABLE = 0xF7,
            /// <summary>
            /// Set the interrupt pin to be open-drain
            /// </summary>
            ODR_OPEN_DRAIN = 0x04,
            /// <summary>
            /// Set the interrupt pin to active driver
            /// </summary>
            ODR_ACTICE_DRIVER = 0xFB,
            /// <summary>
            /// Set interrupt pin to active high when in active driver mode
            /// </summary>
            INTPOL_ACTIVE_HIGH = 0x02,
            /// <summary>
            /// Set interrupt pin to active low when in active driver mode
            /// </summary>
            INTPOL_ACTIVE_LOW = 0xFD
        }

        /// <summary>
        /// Port configuration settings
        /// </summary>
        public enum portConfig : byte
        {
            /// <summary>
            /// Set port as output
            /// </summary>
            OUTPUT,
            /// <summary>
            /// Set port as input
            /// </summary>
            INPUT,
            /// <summary>
            /// Set the polarity of the input to the same as the port state (i.e. a high port state is reported as 1, and vice versa)
            /// </summary>
            INPUT_POLARITY_SAME,
            /// <summary>
            /// Set the polarity of the input to the opposite as the port state (i.e. a high port state is reported as 0, and vice versa)
            /// </summary>
            INPUT_POLARITY_OPP,
            /// <summary>
            /// Enable interrupt on port
            /// </summary>
            ENABLE_INTERRUPT,
            /// <summary>
            /// Disable interrupt on port
            /// </summary>
            DISABLE_INTERRUPT,
            /// <summary>
            /// Set the port interrupt state to low
            /// </summary>
            INTTERUPT_LOW,
            /// <summary>
            /// Set the port interrupt state to high
            /// </summary>
            INTTERUPT_HIGH,
            /// <summary>
            /// Set the port interrupt state to both (i.e. change)
            /// </summary>
            INTERRUPT_BOTH,
            /// <summary>
            /// Enable port pull-up resistor (input only)
            /// </summary>
            PULL_UP_ENABLE,
            /// <summary>
            /// Disable port pull-up resistor (input only)
            /// </summary>
            PULL_UP_DISABLE
        }

        /// <summary>
        /// Register map, see the data sheet for details
        /// </summary>
        private enum register : byte
        {
            IODIR = 0x00,
            IPOL = 0x01,
            GPINTEN = 0x02,
            DEFVAL = 0X03,
            INTCON = 0x04,
            IOCON = 0x05,
            GPPU = 0x06,
            INTF = 0x07,
            INTCAP = 0x08,
            GPIO = 0x09,
            OLAT = 0x0A
        }

        // Store some settings
        private MultiSPI _MCP23S08; // MultiSPI can be swapped with regular SPI if using one device
        private byte _opcodeW, _opcodeR;

        /// <summary>
        /// Constructs a MCP23S08 object
        /// </summary>
        /// <param name="cs">The chip select pin used</param>
        /// <param name="address">The address defined by pins A0 and A1</param>
        public MCP23S08(Cpu.Pin cs, byte address)
        {
            // Can be replaced with regular SPI if using one device
            _MCP23S08 = new MultiSPI(new SPI.Configuration(
                ChipSelect_Port: cs,
                ChipSelect_ActiveState: false,
                ChipSelect_SetupTime: 0,
                ChipSelect_HoldTime: 0,
                Clock_IdleState: false,
                Clock_Edge: true,
                Clock_RateKHz: 4800,                   
                SPI_mod: SPI.SPI_module.SPI1
            ));
            
            // The opcodes are a combination of the address and a 0 (write) or a 1 (read)
            _opcodeW = (byte)((0x08 << 3) | (address << 1));
            _opcodeR = (byte)(_opcodeW + 1);

            // Disable sequential operation mode as this driver is not configured to use it
            configure(settings.SEQOP_DISABLE);

            // Set up the exapnder to use its address by default
            configure(settings.HAEN_ENABLE);
        }

        /// <summary>
        /// Configure settings for the MCP23S08
        /// </summary>
        /// <param name="set">The setting to apply</param>
        public void configure(settings set)
        {
            byte config = read(register.IOCON);
            if ((byte)set <= 0x20)
                config |= (byte)set;
            else
                config &= (byte)set;
            write(register.IOCON, config);
        }

        /// <summary>
        /// Configure the settings of a port
        /// </summary>
        /// <param name="port">The port to configure</param>
        /// <param name="config">The setting to apply</param>
        public void setupPort(ports port, portConfig config)
        {
            byte buffer;
            switch (config)
            {
                case portConfig.OUTPUT:
                    buffer = read(register.IODIR);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.IODIR, buffer);
                    break;
                case portConfig.INPUT:
                    buffer = read(register.IODIR);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.IODIR, buffer);
                    break;
                case portConfig.INPUT_POLARITY_SAME:
                    buffer = read(register.IPOL);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.IPOL, buffer);
                    break;
                case portConfig.INPUT_POLARITY_OPP:
                    buffer = read(register.IPOL);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.IPOL, buffer);
                    break;
                case portConfig.DISABLE_INTERRUPT:
                    buffer = read(register.GPINTEN);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.GPINTEN, buffer);
                    break;
                case portConfig.ENABLE_INTERRUPT:
                    buffer = read(register.GPINTEN);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.GPINTEN, buffer);
                    break;
                case portConfig.INTTERUPT_LOW:
                    buffer = read(register.DEFVAL);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.DEFVAL, buffer);
                    buffer = read(register.INTCON);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.INTTERUPT_HIGH:
                    buffer = read(register.DEFVAL);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.DEFVAL, buffer);
                    buffer = read(register.INTCON);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.INTERRUPT_BOTH:
                    buffer = read(register.INTCON);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.PULL_UP_DISABLE:
                    buffer = read(register.GPPU);
                    buffer &= (byte)~(1 << (byte)(port));
                    write(register.GPPU, buffer);
                    break;
                case portConfig.PULL_UP_ENABLE:
                    buffer = read(register.GPPU);
                    buffer |= (byte)(1 << (byte)(port));
                    write(register.GPPU, buffer);
                    break;
            }
        }

        /// <summary>
        /// Set the states of the port latches, which correspond to the states of the ouput ports
        /// </summary>
        /// <param name="portSettings">The latch settings to apply to the ports</param>
        public void writePorts(byte portSettings)
        {
            write(register.OLAT, portSettings);
        }

        /// <summary>
        /// Read the states of all the GPIO ports or the port latches
        /// </summary>
        /// <param name="latch">If true, returns the latch states for the ports instead of the port states</param>
        /// <returns>A byte where each bit repersents the state of the corresponding port</returns>
        public byte readPorts(bool latch = false)
        {
            if (latch)
                return read(register.OLAT);
            else
                return read(register.GPIO);
        }

        /// <summary>
        /// Retrieves the parameters related to an interrupt event
        /// </summary>
        /// <returns>A byte array containing two bytes corresponding to the INTF and INTCAP registers respectively. See the data sheet for details</returns>
        public byte[] checkInterrupt()
        {
            byte[] buffer = new byte[2];
            buffer[0] = read(register.INTF);
            buffer[1] = read(register.INTCAP);
            return buffer;
        }

        /// <summary>
        /// Reads the data from a register
        /// </summary>
        /// <param name="reg">The register to read</param>
        /// <returns>The data in the register</returns>
        private byte read(register reg)
        {
            byte[] bufferW = { _opcodeR , (byte)reg };
            byte[] bufferR = new byte[1];
            // Need to wait for both the opcode AND the register address to be sent before beginning to read data
            _MCP23S08.WriteRead(bufferW, bufferR, 2);
            return bufferR[0];
        }
        
        /// <summary>
        /// Writes data to a register
        /// </summary>
        /// <param name="reg">The register to write</param>
        /// <param name="data">The data to write</param>
        private void write(register reg, byte data)
        {
            if (reg == register.INTCAP || reg == register.INTF)
                throw new System.ArgumentException("INTCAP and INTF are read-only", "reg");
            byte[] buffer = { _opcodeW, (byte)reg, data };
            _MCP23S08.Write(buffer);
        }

    }

    /// <summary>
    /// Driver for a MCP23S17 I/O expander (http://www.microchip.com/wwwproducts/Devices.aspx?product=MCP23S17)
    /// </summary>
    public class MCP23S17
    {
        /// <summary>
        /// Enumeration of the GPIOs: 0-7 (GPA0-GPA7), 8-15 (GPB0-GPB7)
        /// </summary>
        public enum ports : byte
        {
            GP0, // GPA0
            GP1,
            GP2,
            GP3,
            GP4,
            GP5,
            GP6,
            GP7, // GPA7
            GP8, // GPB0
            GP9,
            GP10,
            GP11,
            GP12,
            GP13,
            GP14,
            GP15  // GPB7
        }

        /// <summary>
        /// Device configuration settings
        /// </summary>
        public enum settings : ushort
        {
            /// <summary>
            /// Set to two 8-bit banks
            /// </summary>
            BANK_SEP = 0x8080,
            /// <summary>
            /// Set to one 16-bit bank (this dirver is configured for 16-bit use)
            /// </summary>
            BANK_SAME = 0x7F7F,
            /// <summary>
            /// Enable mirroring of interrupt ports
            /// </summary>
            MIRROR_ENABLE = 0x4040,
            /// <summary>
            /// Disable mirroring of interrupt ports
            /// </summary>
            MIRROR_DISABLE = 0xBFBF,
            /// <summary>
            /// Enable sequential operation mode (not recommended)
            /// </summary>
            SEQOP_ENABLE = 0xDFDF,
            /// <summary>
            /// Disable sequential operation mode
            /// </summary>
            SEQOP_DISABLE = 0x2020,
            /// <summary>
            /// Enable slew control for SDA pins
            /// </summary>
            DISSLW_ENABLE = 0xEFEF,
            /// <summary>
            /// Disable slew control for SDA pins
            /// </summary>
            DISSLW_DISABLE = 0x1010,
            /// <summary>
            /// Enable hardware address pins (allows multiple MCP23S17 on one chip select pin)
            /// </summary>
            HAEN_ENABLE = 0X0808,
            /// <summary>
            /// Disable hardware address pins
            /// </summary>
            HAEN_DISABLE = 0xF7F7,
            /// <summary>
            /// Set the interrupt pin to be open-drain
            /// </summary>
            ODR_OPEN_DRAIN = 0x0404,
            /// <summary>
            /// Set the interrupt pin to active driver
            /// </summary>
            ODR_ACTICE_DRIVER = 0xFBFB,
            /// <summary>
            /// Set interrupt pin to active high when in active driver mode
            /// </summary>
            INTPOL_ACTIVE_HIGH = 0x0202,
            /// <summary>
            /// Set interrupt pin to active low when in active driver mode
            /// </summary>
            INTPOL_ACTIVE_LOW = 0xFDFD
        }

        /// <summary>
        /// Port configuration settings
        /// </summary>
        public enum portConfig : byte
        {
            /// <summary>
            /// Set port as output
            /// </summary>
            OUTPUT,
            /// <summary>
            /// Set port as input
            /// </summary>
            INPUT,
            /// <summary>
            /// Set the polarity of the input to the same as the port state (i.e. a high port state is reported as 1, and vice versa)
            /// </summary>
            INPUT_POLARITY_SAME,
            /// <summary>
            /// Set the polarity of the input to the opposite as the port state (i.e. a high port state is reported as 0, and vice versa)
            /// </summary>
            INPUT_POLARITY_OPP,
            /// <summary>
            /// Enable interrupt on port
            /// </summary>
            ENABLE_INTERRUPT,
            /// <summary>
            /// Disable interrupt on port
            /// </summary>
            DISABLE_INTERRUPT,
            /// <summary>
            /// Set the port interrupt state to low
            /// </summary>
            INTTERUPT_LOW,
            /// <summary>
            /// Set the port interrupt state to high
            /// </summary>
            INTTERUPT_HIGH,
            /// <summary>
            /// Set the port interrupt state to both (i.e. change)
            /// </summary>
            INTERRUPT_BOTH,
            /// <summary>
            /// Enable port pull-up resistor (input only)
            /// </summary>
            PULL_UP_ENABLE,
            /// <summary>
            /// Disable port pull-up resistor (input only)
            /// </summary>
            PULL_UP_DISABLE
        }

        /// <summary>
        /// Register map, see the data sheet for details
        /// </summary>
        private enum register : byte
        {
            IODIR = 0x01,
            IPOL = 0x03,
            GPINTEN = 0x05,
            DEFVAL = 0X07,
            INTCON = 0x09,
            IOCON = 0x0A,
            GPPU = 0x0D,
            INTF = 0x0F,
            INTCAP = 0x11,
            GPIO = 0x13,
            OLAT = 0x15
        }

        // Store some settings
        private MultiSPI _MCP23S17; // MultiSPI can be swapped with regular SPI if using one device
        private byte _opcodeW, _opcodeR;

        /// <summary>
        /// Constructs a MCP23S17 object
        /// </summary>
        /// <param name="cs">The chip select pin used</param>
        /// <param name="address">The address defined by pins A0, A1, and A2</param>
        public MCP23S17(Cpu.Pin cs, byte address)
        {
            // Can be replaced with regular SPI if using one device
            _MCP23S17 = new MultiSPI(new SPI.Configuration(
                ChipSelect_Port: cs,
                ChipSelect_ActiveState: false,
                ChipSelect_SetupTime: 0,
                ChipSelect_HoldTime: 0,
                Clock_IdleState: false,
                Clock_Edge: true,
                Clock_RateKHz: 4800,
                SPI_mod: SPI.SPI_module.SPI1
            ));

            // The opcodes are a combination of the address and a 0 (write) or a 1 (read)
            _opcodeW = (byte)((0x04 << 4) | (address << 1));
            _opcodeR = (byte)(_opcodeW + 1);

            // Disable sequential operation mode as this driver is not configured to use it
            configure(settings.SEQOP_DISABLE);
            // Set up the exapnder to use its address by default
            configure(settings.HAEN_ENABLE);
        }

        /// <summary>
        /// Configure settings for the MCP23S17
        /// </summary>
        /// <param name="set">The setting to apply</param>
        public void configure(settings set)
        {
            ushort config = read(register.IOCON);
            if ((ushort)set <= 0x8080)
                config |= (ushort)set;
            else
                config &= (ushort)set;
            write(register.IOCON, config);
        }

        /// <summary>
        /// Configure the settings of a port
        /// </summary>
        /// <param name="port">The port to configure</param>
        /// <param name="config">The setting to apply</param>
        public void setupPort(ports port, portConfig config)
        {
            ushort buffer;
            switch (config)
            {
                case portConfig.OUTPUT:
                    buffer = read(register.IODIR);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.IODIR, buffer);
                    break;
                case portConfig.INPUT:
                    buffer = read(register.IODIR);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.IODIR, buffer);
                    break;
                case portConfig.INPUT_POLARITY_SAME:
                    buffer = read(register.IPOL);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.IPOL, buffer);
                    break;
                case portConfig.INPUT_POLARITY_OPP:
                    buffer = read(register.IPOL);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.IPOL, buffer);
                    break;
                case portConfig.DISABLE_INTERRUPT:
                    buffer = read(register.GPINTEN);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.GPINTEN, buffer);
                    break;
                case portConfig.ENABLE_INTERRUPT:
                    buffer = read(register.GPINTEN);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.GPINTEN, buffer);
                    break;
                case portConfig.INTTERUPT_LOW:
                    buffer = read(register.DEFVAL);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.DEFVAL, buffer);
                    buffer = read(register.INTCON);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.INTTERUPT_HIGH:
                    buffer = read(register.DEFVAL);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.DEFVAL, buffer);
                    buffer = read(register.INTCON);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.INTERRUPT_BOTH:
                    buffer = read(register.INTCON);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.INTCON, buffer);
                    break;
                case portConfig.PULL_UP_DISABLE:
                    buffer = read(register.GPPU);
                    buffer &= (ushort)~(1 << (byte)(port));
                    write(register.GPPU, buffer);
                    break;
                case portConfig.PULL_UP_ENABLE:
                    buffer = read(register.GPPU);
                    buffer |= (ushort)(1 << (byte)(port));
                    write(register.GPPU, buffer);
                    break;
            }
        }

        /// <summary>
        /// Set the states of the port latches, which correspond to the states of the ouput ports
        /// </summary>
        /// <param name="portSettings">The latch settings to apply to the ports</param>
        public void writePorts(ushort portSettings)
        {
            write(register.OLAT, portSettings);
        }

        /// <summary>
        /// Read the states of all the GPIO ports or the port latches
        /// </summary>
        /// <param name="latch">If true, returns the latch states for the ports instead of the port states</param>
        /// <returns>A ushort where each bit repersents the state of the corresponding port</returns>
        public ushort readPorts(bool latch = false)
        {
            if (latch)
                return read(register.OLAT);
            else
                return read(register.GPIO);
        }

        /// <summary>
        /// Retrieves the parameters related to an interrupt event
        /// </summary>
        /// <returns>A ushort array containing two ushorts corresponding to the INTF and INTCAP registers respectively. See the data sheet for details</returns>
        public ushort[] checkInterrupt()
        {
            ushort[] buffer = new ushort[2];
            buffer[0] = read(register.INTF);
            buffer[1] = read(register.INTCAP);
            return buffer;
        }

        /// <summary>
        /// Reads the data from a register
        /// </summary>
        /// <param name="reg">The register to read</param>
        /// <returns>The data in the register</returns>
        private ushort read(register reg)
        {
            ushort[] bufferW = { (ushort)((_opcodeR << 8) | (byte)reg) };
            ushort[] bufferR = new ushort[1];
            // Need to wait for the opcode and register address to be sent before beginning to read data
            _MCP23S17.WriteRead(bufferW, bufferR, 1);
            return bufferR[0];
        }

        /// <summary>
        /// Writes data to a register
        /// </summary>
        /// <param name="reg">The register to write</param>
        /// <param name="data">The data to write</param>
        private void write(register reg, ushort data)
        {
            if (reg == register.INTCAP || reg == register.INTF)
                throw new System.ArgumentException("INTCAP and INTF are read-only", "reg");
            ushort opcode = (ushort)((_opcodeW << 8) | (byte)reg);
            ushort[] buffer = { opcode, data };
            _MCP23S17.Write(buffer);
        }

    }

    /// <summary>
    /// Class for using the MCP23S08/MCP23S17 ports like native input ports 
    /// </summary>
    public class ExpandedInputPort : IDisposable
    {
        // Store some settings
        private ushort _port;
        private MCP23S08 _expander08 = null;
        private MCP23S17 _expander17 = null;

        /// <summary>
        /// Creates an input port on a MCP23S08 expander
        /// </summary>
        /// <param name="port">The port to use</param>
        /// <param name="resistor">Enable the 100kOhm pull-up resistor</param>
        /// <param name="expander">The MCP23S08 with the port</param>
        public ExpandedInputPort(MCP23S08.ports port, Port.ResistorMode resistor, MCP23S08 expander)
        {
            _port = (ushort)(1 << (byte)port);
            _expander08 = expander;
            _expander08.setupPort(port, MCP23S08.portConfig.INPUT);

            switch (resistor)
            {
                case Port.ResistorMode.Disabled:
                    _expander08.setupPort(port, MCP23S08.portConfig.PULL_UP_DISABLE);
                    break;
                case Port.ResistorMode.PullUp:
                    _expander08.setupPort(port, MCP23S08.portConfig.PULL_UP_ENABLE);
                    break;
                default:
                    throw new System.NotSupportedException("Pull-down resistors not supported");
            }
        }
        /// <summary>
        /// Creates an input port on a MCP23S17 expander
        /// </summary>
        /// <param name="port">The port to use</param>
        /// <param name="resistor">Enable the 100kOhm pull-up resistor</param>
        /// <param name="expander">The MCP23S17 with the port</param>
        public ExpandedInputPort(MCP23S17.ports port, Port.ResistorMode resistor, MCP23S17 expander)
        {
            _port = (ushort)(1 << (byte)port);
            _expander17 = expander;
            _expander17.setupPort(port, MCP23S17.portConfig.INPUT);

            switch (resistor)
            {
                case Port.ResistorMode.Disabled:
                    _expander17.setupPort(port, MCP23S17.portConfig.PULL_UP_DISABLE);
                    break;
                case Port.ResistorMode.PullUp:
                    _expander17.setupPort(port, MCP23S17.portConfig.PULL_UP_ENABLE);
                    break;
                default:
                    throw new System.NotSupportedException("Pull-down resistors not supported");
            }
        }

        /// <summary>
        /// Read the state of the port
        /// </summary>
        /// <returns>The state of the port</returns>
        public bool Read()
        {
            if (_expander08 != null)
            {
                byte buffer = _expander08.readPorts();
                return _port == (buffer & _port);
            }
            else
            {
                ushort buffer = _expander17.readPorts();
                return _port == (buffer & _port);
            }
        }

        /// <summary>
        /// Disposes of the port
        /// </summary>
        public void Dispose()
        {
            _expander08 = null;
            _expander17 = null;
        }

        ~ExpandedInputPort()
        {
            Dispose();
        }
    }

    /// <summary>
    /// Class for using the MCP23S08/MCP23S17 ports like native output ports 
    /// </summary>
    public class ExpandedOuputPort : IDisposable
    {
        // Store some settings
        private ushort _port;
        private MCP23S08 _expander08 = null;
        private MCP23S17 _expander17 = null;

        /// <summary>
        /// Creates an output port on a MCP23S08 expander
        /// </summary>
        /// <param name="port">The port to use</param>
        /// <param name="initialState">The initial state of the port</param>
        /// <param name="expander">The MCP23S08 with the port</param>
        public ExpandedOuputPort(MCP23S08.ports port, bool initialState, MCP23S08 expander)
        {
            _expander08 = expander;
            _expander08.setupPort(port, MCP23S08.portConfig.OUTPUT);
            _port = (ushort)(1 << (byte)port);
            byte buffer = _expander08.readPorts(true);
            if (initialState)
                buffer |= (byte)_port;
            else
                buffer &= (byte)~_port;
            _expander08.writePorts(buffer);
        }
        /// <summary>
        /// Creates an output port on a MCP23S17 expander
        /// </summary>
        /// <param name="port">The port to use</param>
        /// <param name="initialState">The initial state of the port</param>
        /// <param name="expander">The MCP23S08 with the port</param>
        public ExpandedOuputPort(MCP23S17.ports port, bool initialState, MCP23S17 expander)
        {
            _expander17 = expander;
            _expander17.setupPort(port, MCP23S17.portConfig.OUTPUT);
            _port = (ushort)(1 << (byte)port);
            ushort buffer = _expander17.readPorts(true);
            if (initialState)
                buffer |= _port;
            else
                buffer &= (ushort)~_port;
            _expander17.writePorts(buffer);
        }

        /// <summary>
        /// Read the state of the port
        /// </summary>
        /// <returns>The state of the port</returns>
        public bool Read()
        {
            if (_expander08 != null)
            {
                byte buffer = _expander08.readPorts();
                return _port == (buffer & _port);
            }
            else
            {
                ushort buffer = _expander17.readPorts();
                return _port == (buffer & _port);
            }
        }

        /// <summary>
        /// Write the state of the port
        /// </summary>
        /// <param name="state">The state to write</param>
        public void Write(bool state)
        {
            if (_expander08 != null)
            {
                byte buffer = _expander08.readPorts(true);
                if (state)
                    buffer |= (byte)_port;
                else
                    buffer &= (byte)~_port;
                _expander08.writePorts(buffer);
            }
            else
            {
                ushort buffer = _expander17.readPorts(true);
                if (state)
                    buffer |= _port;
                else
                    buffer &= (ushort)~_port;
                _expander17.writePorts(buffer);
            }
        }

        /// <summary>
        /// Disposes of the port
        /// </summary>
        public void Dispose()
        {
            _expander08 = null;
            _expander17 = null;
        }

        ~ExpandedOuputPort()
        {
            Dispose();
        }
    }
}

usage of driver above …


   public class Program
    {
        // Create some standard Netduino variables and ports
        static int count = 0;
        static InputPort foo = new InputPort(Pins.GPIO_PIN_D0, false, Port.ResistorMode.Disabled);
        static InputPort bar = new InputPort(Pins.GPIO_PIN_D1, false, Port.ResistorMode.Disabled);
        static InterruptPort button = new InterruptPort(Pins.ONBOARD_BTN, true, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
 
        // Create the two expander objects
        static MCP23S17 expander1 = new MCP23S17(Pins.GPIO_PIN_D10, 0x00);
        static MCP23S17 expander2 = new MCP23S17(Pins.GPIO_PIN_D10, 0x01);
 
        // Create one output on each expander 
        static ExpandedOuputPort output1 = new ExpandedOuputPort(MCP23S17.ports.GP9, false, expander1);
        static ExpandedOuputPort output2 = new ExpandedOuputPort(MCP23S17.ports.GP7, false, expander2);
 
        // Create one input on each expander
        static ExpandedInputPort input1 = new ExpandedInputPort(MCP23S17.ports.GP2, Port.ResistorMode.PullUp, expander1);
        static ExpandedInputPort input2 = new ExpandedInputPort(MCP23S17.ports.GP12, Port.ResistorMode.PullUp, expander2);
 
        public static void Main()
        {
            button.OnInterrupt += button_OnInterrupt;
 
            while (true)
            {
                // Check the states of the expanders' outputs using the Netduino inputs
                Debug.Print("One output: " + foo.Read().ToString());
                Debug.Print("Two output: " + bar.Read().ToString() + "\n");
 
                // Check the states of the expanders' inputs
                Debug.Print("One input: " + input1.Read().ToString());
                Debug.Print("Two input: " + input2.Read().ToString() + "\n\n");
 
                Thread.Sleep(1000);
            }
        }
 
        /// <summary>
        /// When the Netduino's button is pushed this will alternate between toggling each
        /// expander's output port, e.g. the first push will toggle the output on expander1, 
        /// the second push will toggle the output on expander2, etc...
        /// </summary>
        /// <param name="pin">The pin triggering the interrupt</param>
        /// <param name="state">The pin state</param>
        /// <param name="time">The time of the interrupt</param>
        static void button_OnInterrupt(uint pin, uint state, DateTime time)
        {
            if (count == 0)
            {
                output1.Write(!output1.Read());
                count++;
            }
            else
            {
                output2.Write(!output2.Read());
                count = 0;
            }
            Debug.Print("Toggled");
        }
    }

A few notes:

This driver does not prevent multiple assignments: e.g. one could assign a port as an output port, and later assign it as an input port and no error would be thrown. So it’s important to take care when assigning ports.

Writing/reading to/from the all the ports simultaneously can be done while using the ExpandedInputPort or ExpandedOutputPort classes, however the two methods will overwrite each other, i.e whichever method is used last will be the one that is in effect.

I have not implemented an ExpandedInterruptPort class. However, the interrupt functions can still be used with the checkInterrupt() method, which will return the contents of the INTF and INTCAP registers.

Thank you i didnt concider looking there.

I ported alot from this c# implementation when i made my TinyCLR 1.x libraries…

This time i vow to get my stuff up on github. We should all have something ready to go that just works…

2 Likes