namespace Unosquare.WiringPi
{
using Native;
using RaspberryIO.Abstractions;
using RaspberryIO.Abstractions.Native;
using Swan;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
///
/// Provides access to using the SPI buses on the GPIO.
/// SPI is a bus that works like a ring shift register
/// The number of bytes pushed is equal to the number of bytes received.
///
public sealed class SpiChannel : ISpiChannel
{
///
/// The minimum frequency of a SPI Channel.
///
public const int MinFrequency = 500000;
///
/// The maximum frequency of a SPI channel.
///
public const int MaxFrequency = 32000000;
private static readonly object SyncRoot = new object();
private static readonly Dictionary Buses = new Dictionary();
private readonly object _syncLock = new object();
///
/// Initializes a new instance of the class.
///
/// The channel.
/// The frequency.
private SpiChannel(SpiChannelNumber channel, int frequency)
{
lock (SyncRoot)
{
Frequency = frequency.Clamp(MinFrequency, MaxFrequency);
Channel = (int)channel;
FileDescriptor = WiringPi.WiringPiSPISetup((int)channel, Frequency);
if (FileDescriptor < 0)
{
HardwareException.Throw(nameof(SpiChannel), channel.ToString());
}
}
}
///
public int FileDescriptor { get; }
///
public int Channel { get; }
///
public int Frequency { get; }
///
public byte[] SendReceive(byte[] buffer)
{
if (buffer == null || buffer.Length == 0)
return null;
lock (_syncLock)
{
var spiBuffer = new byte[buffer.Length];
Array.Copy(buffer, spiBuffer, buffer.Length);
var result = WiringPi.WiringPiSPIDataRW(Channel, spiBuffer, spiBuffer.Length);
if (result < 0) HardwareException.Throw(nameof(SpiChannel), nameof(SendReceive));
return spiBuffer;
}
}
///
/// Sends data and simultaneously receives the data in the return buffer.
///
/// The buffer.
///
/// The read bytes from the ring-style bus.
///
public Task SendReceiveAsync(byte[] buffer) => Task.Run(() => SendReceive(buffer));
///
public void Write(byte[] buffer)
{
lock (_syncLock)
{
var result = SysCall.Write(FileDescriptor, buffer, buffer.Length);
if (result < 0)
HardwareException.Throw(nameof(SpiChannel), nameof(Write));
}
}
///
/// Writes the specified buffer the the underlying FileDescriptor.
/// Do not use this method if you expect data back.
/// This method is efficient if used in a fire-and-forget scenario
/// like sending data over to those long RGB LED strips.
///
/// The buffer.
/// The awaitable task.
public Task WriteAsync(byte[] buffer) => Task.Run(() => { Write(buffer); });
///
/// Retrieves the spi bus. If the bus channel is not registered it sets it up automatically.
/// If it had been previously registered, then the bus is simply returned.
///
/// The channel.
/// The frequency.
/// The usable SPI channel.
internal static ISpiChannel Retrieve(SpiChannelNumber channel, int frequency)
{
lock (SyncRoot)
{
if (Buses.ContainsKey(channel))
return Buses[channel];
var newBus = new SpiChannel(channel, frequency);
Buses[channel] = newBus;
return newBus;
}
}
}
}