Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
shaggygi opened this issue Dec 27, 2018 · 8 comments
Closed

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

shaggygi opened this issue Dec 27, 2018 · 8 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Device.Gpio Contains types for using general-purpose I/O (GPIO) pins up-for-grabs Good issue for external contributors to iot

Comments

@shaggygi
Copy link
Contributor

shaggygi commented Dec 27, 2018

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?

@joperezr joperezr added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Device.Gpio Contains types for using general-purpose I/O (GPIO) pins labels Jan 10, 2019
@joperezr
Copy link
Member

I believe what you ant here is the ability of having software SPI instead of hardware SPI as we have today. If that's the case, I would instead prefer having the device binding take in the GPIO pin numbers instead of taking in a device, and inside its implementation it would do the software SPI. This is the way that a lot of Adafruit device bindings work today as well. Here is an example of a binding already in our repo that does it:

public Mcp3008(SpiDevice spiDevice)
{
_spiDevice = spiDevice;
_protocol = CommunicationProtocol.Spi;
}
public Mcp3008(int clk, int miso, int mosi, int cs)
{
_controller = new GpioController();
_clk = clk;
_miso = miso;
_mosi = mosi;
_cs = cs;
_controller.OpenPin(_clk, PinMode.Output);
_controller.OpenPin(_miso, PinMode.Input);
_controller.OpenPin(_mosi, PinMode.Output);
_controller.OpenPin(_cs, PinMode.Output);
_protocol = CommunicationProtocol.Gpio;
}

As you can see, this binding will allow for you to initialize it with a spidevice, but it will also support you passing in the pin numbers manually and it will instead use GPIO to do a software SPI.

@shaggygi
Copy link
Contributor Author

I guess I was thinking it would be no different than passing in a UnixSpiDevice or a newly offered GpioSpiDevice for the abstract. You still have to pass in the pin numbers and settings info for a GpioController or GpioSpiDevice.

As attempted here, you pass in the related info and the GpioSpiDevice creates the GpioController behind the scenes.

I guess you could pass in the pin numbers and settings like you suggested and have the GpioSpiDevice be created behind the scenes for the binding.

I like the GpioSpiDevice concept for a couple of reasons...

  1. You only have to worry about 2 options to decide on protocol to use (if the device offers both I2C and SPI on same chip). Related to [Proposal] Add ConnectionType enum to System.Devices.* API #89, if you had both and offered GPIO to do the bit-banging... it would be 4 options.

  2. The GpioSpiDevice includes all the logic to perform the protocol, including clocking in/out, polarity, phase, etc. If you pass only the pin numbers and create a GpioController, every binding would have to implement the logic for doing this. Reference the current ReadGpio method for Mcp3008 as an example. One other thing to point out... the Mcp3008 binding is currently coded to only support Mode0, but the device also supports Mode3. Meaning, more logic to add to the individual binding.

I have not had time, but think this same concept applies for a GpioI2cDevice, as well.

@joperezr
Copy link
Member

There has been some changes lately around what to do with communication protocols. We now plan to create protocol interfaces, IGpioController has been added already, but we want to add similar ones for IPwmController, ISpiController, and II2cController. I think it would be better to first finish defining those remaining ones, and that way we can switch our current hardware implementation of the protocols to implement that interface.

Once we have that, device bindings can have constructors like MyDeviceBinding(ISpiController spiDevice) and you can pass in either a hardware based spi device or a software (gpio) based one that implements that interface. Once we have those interfaces, we should revisit this issue and make sure we add the software implementation of those protocols.

@krwq
Copy link
Member

krwq commented Feb 22, 2019

I think this should be something like I2cDevice.Create(settings) and SpiDevice.Create(settings) (I just got a quick look at the issue so disregard if I'm offtopic)

@krwq krwq added this to the 3.0 milestone Feb 22, 2019
@joperezr
Copy link
Member

Marking as up-for-grabs. The next step in this issue would be to send a PR with the software implementation of SPI protocol. Initially we would want to have it under the devices tree, and once we have the interfaces for the SPI protocol ready then we can move this to the main library. In terms of Api Surface, this class should have the same signatures as our regular SpiDevice, except that it would take pin numbers instead of Spi Channels and connection settings.

@joperezr joperezr added enhancement New feature or request up-for-grabs Good issue for external contributors to iot labels Apr 16, 2019
@joperezr joperezr changed the title [Proposal] Provide a new GpioSpiDevice [Proposal] Provide a Software implementation of the SPI protocol. Apr 16, 2019
@joperezr joperezr modified the milestones: 3.0, Future Apr 16, 2019
@joperezr joperezr removed the enhancement New feature or request label Apr 17, 2019
@shaggygi
Copy link
Contributor Author

shaggygi commented Jun 5, 2019

except that it would take pin numbers instead of Spi Channels and connection settings.

I think the constructor would still use SpiConnectionSettings. Now that SpiConnectionSettings include ChipSelectLineActiveState, DataFlow, and DataBitLength, it would make it easier to configure. But as mentioned, it should also have the GPIO pins.

// Bus ID = 0, Chip Select Pin = 25.
var settings = new SpiConnectionSettings(0, 25)
{
    ChipSelectLineActiveState = PinValue.High,
    DataBitLength = 5,
    DataFlow = DataFlow.LsbFirst,
    // other properties set as needed.
}

// SCLK = 18, MISO = 23, MOSI = 24.
var spiDevice = new GpioSpiDevice(18, 23, 24, settings);
var myBinding = new MyBinding(spiDevice);

@Ellerbach
Copy link
Member

@shaggygi do you feel this is still to be open? With all the recent addition to SPI and I2C, I feel this could be closed.

@shaggygi
Copy link
Contributor Author

@Ellerbach We have this one, but I was thinking it was eventually going to be moved within core API. We can go ahead and close for now.

Add Support for new communication Protocols automation moved this from To do to Done Dec 22, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Jan 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Device.Gpio Contains types for using general-purpose I/O (GPIO) pins up-for-grabs Good issue for external contributors to iot
Projects
No open projects
Development

No branches or pull requests

4 participants