Main Site Documentation

Help with SPI and MC23S17


#1

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;
    }

}
}

#2

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 http://docs.ghielectronics.com/software/tinyclr/tutorials/i2c.html


#3

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