Skip to content

[Proposal] Provide a Software implementation of the SPI protocol. #122

@shaggygi

Description

@shaggygi

Core Objective

As a device binding producer, I would like the ability to instantiate the binding with a GPIO SPI implementation instead of redundant code to bit-bang logic within each binding. This would be similar to how a UnixSpiDevice or Windows10SpiDevice is currently used to create a binding.

NOTE: There is a "very early" prototype located here.

Use the Mcp23xxx binding and a UnixSpiDevice for example:

// With UnixSpiDevice.
var settings = new SpiConnectionSettings(0, 0)
{
    ClockFrequency = 1000000,
    Mode = SpiMode.Mode0
};

var spiDevice = new UnixSpiDevice(settings);
var mcp23xxx = new Mcp23xxx(spiDevice);

Having a GpioSpiDevice offering, there could be something like the following:
NOTE: chipSelectActiveLow is active low in most cases, but some chips and hardware designs use active high.

public GpioSpiDevice(int sclk, int miso, int mosi, SpiConnectionSettings connectionSettings, bool chipSelectActiveLow = true)

Therefore, you could pass in the GpioSpiDevice like below:
NOTE: Notice different modes can also be used and low-level should accommodate (assuming the device supports that mode 😄). Frequency (clock delay) is not currently coded in prototype.

// With GpioSpiDevice.
var settings = new SpiConnectionSettings(0, 25)
{
    ClockFrequency = 1000000,
    Mode = SpiMode.Mode3
};

var spiDevice = new GpioSpiDevice(18, 23, 24, settings);
var mcp23xxx = new Mcp23xxx(spiDevice);

GpioSpiDevice would be located with the other current SpiDevice drivers.

System
  Device
    Spi
      Drivers
        GpioSpiDevice.cs  // Or GpioSpiDriver depending on naming scheme
        UnixSpiDevice.Linux.cs
        ...

This might help with having abstract classes using the different types of interface devices. For example...

  • GpioSpiDevice, UnixSpiDevice and Windows10SpiDevice could be passed to an Mcp23Sxx device where 'S' represents SPI devices.
  • GpioI2cDevice (a future proposal), UnixI2cDevice and Windows10I2cDevice could be passed to an Mcp230xx device where '0' represents I2C devices.

A few thoughts to point out

  • busId doesn't really mean anything in this scenario. It could be ignored by creating an overloaded constructor new SpiConnectionSettings(chipSelectLine).

  • chipSelectActiveLow seems to be more for settings so it could be added to SpiConnectionSettings and defaulted to TRUE instead of passing into GpioSpiDevice constructor. So the following could be used:
    new SpiConnectionSettings(chipSelectLine, chipSelectActiveLow). This might confuse peeps when using for native interface and configured elsewhere. I'm on the fence with this.

  • There is a proposal for a GPIO Toggle helper where it would help support logic in other areas, as well.

  • It would be nice to have some bit helper methods to perform MSB/LSB as this varies with components.

  • It would be nice to have a helper for PinValue.Low = false or PinValue.High = true. There is a proposal related to this here: [Proposal] Add ReadBool and WriteBool to GpioController #121

  • One other thing to point out, is how much code can be reduced like in the Mcp3008 binding. The current code to read via GPIO SPI shown below:

private int ReadGpio(int channel, InputConfiguration inputConfiguration)
{
    while (true)
    {
        int result = 0;
        byte command = GetConfigurationBits(channel, inputConfiguration);

        _controller.Write(_cs, PinValue.High);
        _controller.Write(_clk, PinValue.Low);
        _controller.Write(_cs, PinValue.Low);

        for (int cnt = 0; cnt < 5; cnt++)
        {
            if ((command & 0b1000_0000) > 0)
            {
                _controller.Write(_mosi, PinValue.High);
            }
            else
            {
                _controller.Write(_mosi, PinValue.Low);
            }

            command <<= 1;
            _controller.Write(_clk, PinValue.High);
            _controller.Write(_clk, PinValue.Low);
        }

        for (int cnt = 0; cnt < 12; cnt++)
        {
            _controller.Write(_clk, PinValue.High);
            _controller.Write(_clk, PinValue.Low);
            result <<= 1;

            if (_controller.Read(_miso) == PinValue.High)
            {
                result |= 0b0000_0001;
            }
        }

        _controller.Write(_cs, PinValue.High);

        result >>= 1;
        return result;
    }
}

Basically, can use the same code as the ReadSpi method (replacing with the GpioSpiDevice, of course 😄):

private int ReadSpi(int channel, InputConfiguration inputConfiguration)
{
    byte configurationBits = GetConfigurationBits(channel, inputConfiguration);
    byte[] writeBuffer = new byte[] { configurationBits, 0, 0 };
    byte[] readBuffer = new byte[3];

    _spiDevice.TransferFullDuplex(writeBuffer, readBuffer);

    int result = (readBuffer[0] & 0b0000_0001) << 9;
    result |= (readBuffer[1] & 0b1111_1111) << 1;
    result |= (readBuffer[2] & 0b1000_0000) >> 7;
    result = result & 0b0011_1111_1111;
    return result;
}

As mentioned, this is just a prototype, but works. I'm still testing bits/modes and such.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Device.GpioContains types for using general-purpose I/O (GPIO) pinsup-for-grabsGood issue for external contributors to iot

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions