RaspberryIO/Unosquare.RaspberryIO/Gpio/SpiChannel.cs
2019-12-03 18:43:54 +01:00

155 lines
5.1 KiB
C#

using Unosquare.RaspberryIO.Native;
using Unosquare.Swan;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Unosquare.RaspberryIO.Gpio {
/// <summary>
/// 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.
/// </summary>
public sealed class SpiChannel {
/// <summary>
/// The minimum frequency of an SPI Channel
/// </summary>
public const Int32 MinFrequency = 500000;
/// <summary>
/// The maximum frequency of an SPI channel
/// </summary>
public const Int32 MaxFrequency = 32000000;
/// <summary>
/// The default frequency of SPI channels
/// This is set to 8 Mhz wich is typical in modern hardware.
/// </summary>
public const Int32 DefaultFrequency = 8000000;
private static readonly Object SyncRoot = new Object();
private static readonly Dictionary<SpiChannelNumber, SpiChannel> Buses = new Dictionary<SpiChannelNumber, SpiChannel>();
private readonly Object _syncLock = new Object();
/// <summary>
/// Initializes a new instance of the <see cref="SpiChannel"/> class.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="frequency">The frequency.</param>
private SpiChannel(SpiChannelNumber channel, Int32 frequency) {
lock(SyncRoot) {
this.Frequency = frequency.Clamp(MinFrequency, MaxFrequency);
this.Channel = (Int32)channel;
this.FileDescriptor = WiringPi.WiringPiSPISetup((Int32)channel, this.Frequency);
if(this.FileDescriptor < 0) {
HardwareException.Throw(nameof(SpiChannel), channel.ToString());
}
}
}
/// <summary>
/// Gets the standard initialization file descriptor.
/// anything negative means error.
/// </summary>
/// <value>
/// The file descriptor.
/// </value>
public Int32 FileDescriptor {
get;
}
/// <summary>
/// Gets the channel.
/// </summary>
public Int32 Channel {
get;
}
/// <summary>
/// Gets the frequency.
/// </summary>
public Int32 Frequency {
get;
}
/// <summary>
/// Sends data and simultaneously receives the data in the return buffer
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>The read bytes from the ring-style bus</returns>
public Byte[] SendReceive(Byte[] buffer) {
if(buffer == null || buffer.Length == 0) {
return null;
}
lock(this._syncLock) {
Byte[] spiBuffer = new Byte[buffer.Length];
Array.Copy(buffer, spiBuffer, buffer.Length);
Int32 result = WiringPi.WiringPiSPIDataRW(this.Channel, spiBuffer, spiBuffer.Length);
if(result < 0) {
HardwareException.Throw(nameof(SpiChannel), nameof(SendReceive));
}
return spiBuffer;
}
}
/// <summary>
/// Sends data and simultaneously receives the data in the return buffer
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>
/// The read bytes from the ring-style bus
/// </returns>
public Task<Byte[]> SendReceiveAsync(Byte[] buffer) => Task.Run(() => this.SendReceive(buffer));
/// <summary>
/// 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
/// </summary>
/// <param name="buffer">The buffer.</param>
public void Write(Byte[] buffer) {
lock(this._syncLock) {
Int32 result = Standard.Write(this.FileDescriptor, buffer, buffer.Length);
if(result < 0) {
HardwareException.Throw(nameof(SpiChannel), nameof(Write));
}
}
}
/// <summary>
/// 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
/// </summary>
/// <param name="buffer">The buffer.</param>
/// <returns>The awaitable task</returns>
public Task WriteAsync(Byte[] buffer) => Task.Run(() => this.Write(buffer));
/// <summary>
/// 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.
/// </summary>
/// <param name="channel">The channel.</param>
/// <param name="frequency">The frequency.</param>
/// <returns>The usable SPI channel</returns>
internal static SpiChannel Retrieve(SpiChannelNumber channel, Int32 frequency) {
lock(SyncRoot) {
if(Buses.ContainsKey(channel)) {
return Buses[channel];
}
SpiChannel newBus = new SpiChannel(channel, frequency);
Buses[channel] = newBus;
return newBus;
}
}
}
}