diff --git a/README.md b/README.md index 59cb0af..37e3049 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# spidriver-net -An unofficial .NET SDK for the SPIDriver debugger + + +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/alandoherty/spidriver-net/master/LICENSE) +[![GitHub issues](https://img.shields.io/github/issues/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/issues) +[![GitHub stars](https://img.shields.io/github/stars/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/alandoherty/spidriver-net.svg?style=flat-square)](https://github.com/alandoherty/spidriver-net/network) +[![GitHub forks](https://img.shields.io/nuget/dt/SPIDriver.svg?style=flat-square)](https://www.nuget.org/packages/SPIDriver/) + + + +# spidriver + +An unofficial .NET library for [SPIDriver](https://spidriver.com/), allows for synchronous/asynchronous control of the device via the serial port. Open permissive MIT license with a dual-compliation for .NET Standard 2.0 and .NET Framework 4.6.1. + +![SPIDriver PCB](img/spidriver.jpg) + +## Getting Started + +[![NuGet Status](https://img.shields.io/nuget/v/SPIDriver.svg?style=flat-square)](https://www.nuget.org/packages/SPIDriver/) + +You can install the package using either the CLI: + +``` +dotnet add package SPIDriver +``` + +or from the NuGet package manager: + +``` +Install-Package SPIDriver +``` + +For assistance with the SPIDriver product itself you can view the [user guide](https://spidriver.com/spidriver.pdf) hosted on the official website. + +### Example + +You can find the example project `Example.CommandLine`, which demonstrates a CLI which implements all the features of the library. + +``` +> connect +Connected successfully +> status +Model: spidriver1 +Serial Number: DO01JHMO +Uptime: 01:56:22 +Voltage: 5.203V +Current: 0A +Temperature: 32.6°C +A: True +B: True +CS: True +CRC: 0x0001 +> writef airline.mp4 +Wrote 1510004 bytes in 33.294s +``` + +## Contributing + +Any pull requests or bug reports are welcome, please try and keep to the existing style conventions and comment any additions. \ No newline at end of file diff --git a/SpiDriver.sln b/SpiDriver.sln new file mode 100644 index 0000000..a05c6ad --- /dev/null +++ b/SpiDriver.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29009.5 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpiDriver", "src\SpiDriver\SpiDriver.csproj", "{DB31321A-1176-45BA-B346-3E935C461B62}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7C86F419-D0FB-4251-AF6C-2C9194EA1C05}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{EFCCC756-6E2D-4608-865D-5CE6B204C6F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.CommandLine", "samples\Example.CommandLine\Example.CommandLine.csproj", "{69010272-1995-4708-A354-CC8485F9109F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DB31321A-1176-45BA-B346-3E935C461B62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB31321A-1176-45BA-B346-3E935C461B62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB31321A-1176-45BA-B346-3E935C461B62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB31321A-1176-45BA-B346-3E935C461B62}.Release|Any CPU.Build.0 = Release|Any CPU + {69010272-1995-4708-A354-CC8485F9109F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69010272-1995-4708-A354-CC8485F9109F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69010272-1995-4708-A354-CC8485F9109F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69010272-1995-4708-A354-CC8485F9109F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {69010272-1995-4708-A354-CC8485F9109F} = {EFCCC756-6E2D-4608-865D-5CE6B204C6F8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1AD50AD4-1933-4F12-BC76-14C291BA7587} + EndGlobalSection +EndGlobal diff --git a/img/spidriver.jpg b/img/spidriver.jpg new file mode 100644 index 0000000..379ca78 Binary files /dev/null and b/img/spidriver.jpg differ diff --git a/samples/Example.CommandLine/Example.CommandLine.csproj b/samples/Example.CommandLine/Example.CommandLine.csproj new file mode 100644 index 0000000..6c0c373 --- /dev/null +++ b/samples/Example.CommandLine/Example.CommandLine.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.2 + + + + + + + diff --git a/samples/Example.CommandLine/Program.cs b/samples/Example.CommandLine/Program.cs new file mode 100644 index 0000000..9f5a2f2 --- /dev/null +++ b/samples/Example.CommandLine/Program.cs @@ -0,0 +1,164 @@ +using SpiDriver; +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace Example.CommandLine +{ + class Program + { + static void Main(string[] argv) { + // the device + Device device = null; + + // loop through accepting commands + string line; + + while (true) { + // read next line + Console.Write("> "); + line = Console.ReadLine().Trim(); + + // parse the line + string[] components = line.Split(' '); + string[] args = components.Length == 0 ? new string[0] : new string[components.Length - 1]; + string cmd = components.Length == 0 ? null : components[0]; + + if (args.Length > 0) + Array.Copy(components, 1, args, 0, args.Length); + + // ignore empty lines + if (cmd == null) + continue; + + // process command + if (cmd.Equals("connect", StringComparison.CurrentCultureIgnoreCase)) { + if (args.Length == 0) { + Console.WriteLine("connect "); + continue; + } + + try { + device = new Device(args[0]); + device.Connect(); + Console.WriteLine("Connected successfully"); + } catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + } + } else if (cmd.Equals("a", StringComparison.CurrentCultureIgnoreCase) || cmd.Equals("b", StringComparison.CurrentCultureIgnoreCase) + || cmd.Equals("cs", StringComparison.CurrentCultureIgnoreCase)) { + // check device is connected + if (device == null) { + Console.WriteLine("Not connected to device"); + continue; + } + + // determine output + Output output = Output.A; + + if (cmd.Equals("b", StringComparison.CurrentCultureIgnoreCase)) + output = Output.B; + else if (cmd.Equals("cs", StringComparison.CurrentCultureIgnoreCase)) + output = Output.CS; + + try { + if (args.Length == 0) { + bool value = device.GetOutput(output); + device.SetOutput(output, !value); + Console.WriteLine($"{output}: {!value}"); + } else { + if (args[0].Equals("yes", StringComparison.CurrentCultureIgnoreCase) || args[0].Equals("on", StringComparison.CurrentCultureIgnoreCase)) { + device.SetOutput(output, true); + } else { + device.SetOutput(output, false); + } + } + } catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + } + } else if (cmd.Equals("status", StringComparison.CurrentCultureIgnoreCase)) { + if (device == null) { + Console.WriteLine("Not connected to device"); + continue; + } + + try { + DeviceStatus status = device.GetStatus(); + + Console.WriteLine($"Model: {status.Model}"); + Console.WriteLine($"Serial Number: {status.Serial}"); + Console.WriteLine($"Uptime: {status.Uptime}"); + Console.WriteLine($"Voltage: {status.Voltage}V"); + Console.WriteLine($"Current: {status.Current}A"); + Console.WriteLine($"Temperature: {status.Temperature}°C"); + Console.WriteLine($"A: {status.A}"); + Console.WriteLine($"B: {status.B}"); + Console.WriteLine($"CS: {status.ChipSelect}"); + Console.WriteLine($"CRC: 0x{status.Crc.ToString("x4")}"); + } catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + } + } else if (cmd.Equals("writef", StringComparison.CurrentCultureIgnoreCase)) { + string path = string.Join(' ', args); + + if (device == null) { + Console.WriteLine("Not connected to device"); + continue; + } else if (args.Length == 0) { + Console.WriteLine("No data in arguments"); + continue; + } else if (!File.Exists(path)) { + Console.WriteLine("File does not exist"); + continue; + } + + // create stopwatch to time transfer + Stopwatch stopwatch = new Stopwatch(); + + try { + byte[] fb = File.ReadAllBytes(path); + + stopwatch.Start(); + device.Write(fb, 0, fb.Length); + + Console.WriteLine($"Wrote {fb.Length} bytes in {Math.Round(stopwatch.Elapsed.TotalSeconds, 3)}s"); + } catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + } + } else if (cmd.Equals("read", StringComparison.CurrentCultureIgnoreCase)) { + int count = 0; + + if (device == null) { + Console.WriteLine("Not connected to device"); + continue; + } else if (args.Length == 0) { + Console.WriteLine("No data in arguments"); + continue; + } else if (!int.TryParse(args[0], out count)) { + Console.WriteLine("Invalid count to read"); + continue; + } + + try { + byte[] data = new byte[count]; + device.Read(data, 0, data.Length); + + Console.WriteLine(BitConverter.ToString(data)); + } catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + } + } else if (cmd.Equals("help", StringComparison.CurrentCultureIgnoreCase)) { + Console.WriteLine("connect - connect to serial port"); + Console.WriteLine("a [on/off] - toggle or set A output"); + Console.WriteLine("b [on/off] - toggle or set B output"); + Console.WriteLine("cs [on/off] - toggle or set CS output"); + Console.WriteLine("status - get device status"); + Console.WriteLine("writef - write file to SPI"); + } else if (line != "quit" && line != "q" && line != "exit") { + return; + } + } + } + } +} diff --git a/src/SpiDriver/DataStream.cs b/src/SpiDriver/DataStream.cs new file mode 100644 index 0000000..2d0a77e --- /dev/null +++ b/src/SpiDriver/DataStream.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace SpiDriver +{ + /// + /// Provides functionality to write/read data to the SPI component as a stream. + /// + public class DataStream : Stream + { + private Device _device; + + /// + /// Gets if this stream can read data. + /// + public override bool CanRead { + get { + return true; + } + } + + /// + /// Gets if this stream can seek. + /// + public override bool CanSeek { + get { + return false; + } + } + + /// + /// Gets if this stream can write data. + /// + public override bool CanWrite { + get { + return true; + } + } + + /// + /// Not supported. + /// + public override long Length { + get { + return 0; + } + } + + /// + /// Not supported. + /// + public override long Position { + get { + throw new NotImplementedException(); + } set { + throw new NotSupportedException(); + } + } + + /// + /// Flushes the stream. + /// + public override void Flush() { + } + + /// + /// Reads data from the SPI device. + /// + /// The byte array. + /// The offset in the byte array to begin reading. + /// The number of bytes to read. + /// The number of bytes read. + public override int Read(byte[] buffer, int offset, int count) { + return _device.Read(buffer, offset, count); + } + + /// + /// Not supported. + /// + /// + /// + /// + public override long Seek(long offset, SeekOrigin origin) { + throw new NotSupportedException(); + } + + /// + /// Not supported. + /// + /// + public override void SetLength(long value) { + throw new NotSupportedException(); + } + + /// + /// Writes the byte array to the SPI device. + /// + /// The buffer. + /// The offset. + /// The count. + public override void Write(byte[] buffer, int offset, int count) { + _device.Write(buffer, offset, count); + } + + /// + /// Reads and writes data to and from the SPI device. + /// + /// The input byte array. + /// The offset in the byte array to begin reading. + /// The output byte array. + /// The offset in the byte array to begin writing. + /// The number of bytes to read. + /// The number of bytes written and read. + public int ReadWrite(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) { + return _device.ReadWrite(inBytes, inOffset, outBytes, outOffset, count); + } + + /// + /// Creates a new data stream. + /// + /// The device. + internal DataStream(Device device) { + _device = device; + } + } +} diff --git a/src/SpiDriver/Device.cs b/src/SpiDriver/Device.cs new file mode 100644 index 0000000..8237bf0 --- /dev/null +++ b/src/SpiDriver/Device.cs @@ -0,0 +1,543 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.IO.Ports; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SpiDriver +{ + /// + /// Provides functionality to communicate with SPIDriver devices. + /// + public class Device + { + internal SerialPort _port; + private SemaphoreSlim _portSemaphore = new SemaphoreSlim(1, 1); + + /// + /// Gets or sets the read timeout, the default is infinite. + /// + public TimeSpan ReadTimeout { + get { + return TimeSpan.FromMilliseconds(_port.ReadTimeout); + } set { + _port.ReadTimeout = (int)value.TotalMilliseconds; + } + } + + /// + /// Gets or sets the write timeout, the default is infinite. + /// + public TimeSpan WriteTimeout { + get { + return TimeSpan.FromMilliseconds(_port.WriteTimeout); + } + set { + _port.WriteTimeout = (int)value.TotalMilliseconds; + } + } + + /// + /// Gets the serial port, direct access is not thread safe. + /// + public SerialPort Port { + get { + return _port; + } + } + + /// + /// Gets if the connection to the device is open. + /// + public bool IsOpen { + get { + return _port.IsOpen; + } + } + + private void ReadWire(byte[] buffer, int offset, int count) { + int dataRemaining = count; + + while (dataRemaining > 0) { + int dataRead = _port.Read(buffer, count - dataRemaining, dataRemaining); + dataRemaining -= dataRead; + } + } + + /// + /// Closes the device. + /// + public void Close() { + _port.Close(); + } + + /// + /// Gets a which provides read/write access to the underlying SPI data stream. + /// + /// + public Stream GetStream() { + return new DataStream(this); + } + + /// + /// Gets the current output state by querying the device status. + /// + /// The output. + /// The output value. + public bool GetOutput(Output output) { + DeviceStatus status = GetStatus(); + + if (output == Output.A) + return status.A; + else if (output == Output.B) + return status.B; + else if (output == Output.CS) + return status.ChipSelect; + else + throw new NotImplementedException(); + } + + /// + /// Gets the current output state by querying the device status asynchronously. + /// + /// The output. + /// The output value. + public async Task GetOutputAsync(Output output) { + DeviceStatus status = await GetStatusAsync().ConfigureAwait(false); + + if (output == Output.A) + return status.A; + else if (output == Output.B) + return status.B; + else if (output == Output.CS) + return status.ChipSelect; + else + throw new NotImplementedException(); + } + + /// + /// Gets the status of the device. + /// + /// The status. + public DeviceStatus GetStatus() { + if (!_port.IsOpen) + throw new InvalidOperationException("The serial port is not open"); + + _portSemaphore.Wait(); + + try { + // send request + _port.Write(new byte[] { (byte)'?' }, 0, 1); + + // get response + byte[] data = new byte[80]; + ReadWire(data, 0, 80); + + // convert to string + string dataStr = Encoding.ASCII.GetString(data) + .TrimStart('[') + .TrimEnd(']'); + + // split components + string[] dataComponents = dataStr.Split(' '); + + // fill status object + DeviceStatus status = new DeviceStatus(); + status.Model = dataComponents[0]; + status.Serial = dataComponents[1]; + status.Uptime = TimeSpan.FromSeconds((double)ulong.Parse(dataComponents[2])); + status.Voltage = float.Parse(dataComponents[3]); + status.Current = float.Parse(dataComponents[4]); + status.Temperature = float.Parse(dataComponents[5]); + status.A = int.Parse(dataComponents[6]) == 1; + status.B = int.Parse(dataComponents[7]) == 1; + status.ChipSelect = int.Parse(dataComponents[8]) == 1; + status.Crc = ushort.Parse(dataComponents[6], NumberStyles.HexNumber); + + return status; + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Gets the status of the device asynchronously. + /// + /// + public async Task GetStatusAsync() { + if (!_port.IsOpen) + throw new InvalidOperationException("The serial port is not open"); + + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + return await Task.Run(() => { + // send request + _port.Write(new byte[] { (byte)'?' }, 0, 1); + + // get response + byte[] data = new byte[80]; + ReadWire(data, 0, 80); + + // convert to string + string dataStr = Encoding.ASCII.GetString(data) + .TrimStart('[') + .TrimEnd(']'); + + // split components + string[] dataComponents = dataStr.Split(' '); + + // fill status object + DeviceStatus status = new DeviceStatus(); + status.Model = dataComponents[0]; + status.Serial = dataComponents[1]; + status.Uptime = TimeSpan.FromSeconds((double)ulong.Parse(dataComponents[2])); + status.Voltage = float.Parse(dataComponents[3]); + status.Current = float.Parse(dataComponents[4]); + status.Temperature = float.Parse(dataComponents[5]); + status.A = int.Parse(dataComponents[6]) == 1; + status.B = int.Parse(dataComponents[7]) == 1; + status.ChipSelect = int.Parse(dataComponents[8]) == 1; + status.Crc = ushort.Parse(dataComponents[6], NumberStyles.HexNumber); + + return status; + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Set the specified output pin on the device. + /// + /// The output pin. + /// The enable. + public void SetOutput(Output output, bool enable) { + if (!_port.IsOpen) + throw new InvalidOperationException("The serial port is not open"); + + _portSemaphore.Wait(); + + try { + if (output == Output.A) { + _port.Write(new byte[] { (byte)'a', enable ? (byte)1 : (byte)0 }, 0, 2); + } else if (output == Output.B) { + _port.Write(new byte[] { (byte)'b', enable ? (byte)1 : (byte)0 }, 0, 2); + } else if (output == Output.CS) { + _port.Write(new byte[] { enable ? (byte)'s' : (byte)'u'}, 0, 2); + } else { + throw new NotImplementedException(); + } + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Set the specified output pin on the device asynchronously. + /// + /// The output pin. + /// The enable. + public async Task SetOutputAsync(Output output, bool enable) { + if (!_port.IsOpen) + throw new InvalidOperationException("The serial port is not open"); + + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + await Task.Run(() => { + if (output == Output.A) { + _port.Write(new byte[] { (byte)'a', enable ? (byte)1 : (byte)0 }, 0, 2); + } else if (output == Output.B) { + _port.Write(new byte[] { (byte)'b', enable ? (byte)1 : (byte)0 }, 0, 2); + } else if (output == Output.CS) { + _port.Write(new byte[] { enable ? (byte)'s' : (byte)'u' }, 0, 2); + } else { + throw new NotImplementedException(); + } + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Connects to the serial port and performs connection tests. + /// + public void Connect() { + _portSemaphore.Wait(); + + try { + if (!_port.IsOpen) + _port.Open(); + + // write init message + _port.Write(Encoding.ASCII.GetBytes(new string('@', 64)), 0, 64); + + // write tests + byte[] tests = new byte[] { (byte)'A', (byte)'\r', (byte)'\n', 0xFF }; + byte[] writeCmd = new byte[] { (byte)'e', 0 }; + byte[] readCmd = new byte[] { 0 }; + + for (int i = 0; i < 4; i++) { + writeCmd[1] = tests[i]; + + // write test byte + _port.Write(writeCmd, 0, writeCmd.Length); + + // read test byte + ReadWire(readCmd, 0, 1); + + if (readCmd[0] != tests[i]) + throw new Exception("Response invalid during connection test"); + } + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Connects to the serial port and performs connection tests asynchronously. + /// + public async Task ConnectAsync() { + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + await Task.Run(() => { + if (!_port.IsOpen) + _port.Open(); + + // write init message + _port.Write(Encoding.ASCII.GetBytes(new string('@', 64)), 0, 64); + + // write tests + byte[] tests = new byte[] { (byte)'A', (byte)'\r', (byte)'\n', 0xFF }; + byte[] writeCmd = new byte[] { (byte)'e', 0 }; + byte[] readCmd = new byte[] { 0 }; + + for (int i = 0; i < 4; i++) { + writeCmd[1] = tests[i]; + + // write test byte + _port.Write(writeCmd, 0, writeCmd.Length); + + // read test byte + ReadWire(readCmd, 0, 1); + + if (readCmd[0] != tests[i]) + throw new Exception("Response invalid during connection test"); + } + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Writes data to the SPI device. + /// + /// The byte array. + /// The offset in the byte array to begin writing. + /// The number of bytes to write. + public void Write(byte[] bytes, int offset, int count) { + if (count == 0) + return; + + _portSemaphore.Wait(); + + try { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + + for (int j = 0; j < cmd.Length; j++) { + cmd[j] = (byte)(0xC0 + len - 1); + } + + Buffer.BlockCopy(bytes, offset + i, cmd, 1, len); + _port.Write(cmd, 0, 1 + len); + } + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Writes data to the SPI device asynchronously. + /// + /// The byte array. + /// The offset in the byte array to begin writing. + /// The number of bytes to write. + public async Task WriteAsync(byte[] bytes, int offset, int count) { + if (count == 0) + return; + + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + await Task.Run(() => { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + + for (int j = 0; j < cmd.Length; j++) { + cmd[j] = (byte)(0xC0 + len - 1); + } + + Buffer.BlockCopy(bytes, offset + i, cmd, 1, len); + _port.Write(cmd, 0, 1 + len); + } + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + } + + /// + /// Reads data from the SPI device. + /// + /// The byte array. + /// The offset in the byte array to begin reading. + /// The number of bytes to read. + /// The number of bytes read. + public int Read(byte[] bytes, int offset, int count) { + if (count == 0) + return 0; + + _portSemaphore.Wait(); + + try { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + cmd[0] = (byte)(0x80 + len - 1); + _port.Write(cmd, 0, len + 1); + ReadWire(bytes, i + offset, len); + } + } finally { + _portSemaphore.Release(); + } + + return count; + } + + /// + /// Reads data from the SPI device. + /// + /// The byte array. + /// The offset in the byte array to begin reading. + /// The number of bytes to read. + /// The number of bytes read. + public async Task ReadAsync(byte[] bytes, int offset, int count) { + if (count == 0) + return 0; + + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + await Task.Run(() => { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + cmd[0] = (byte)(0x80 + len - 1); + _port.Write(cmd, 0, len + 1); + ReadWire(bytes, i + offset, len); + } + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + + return count; + } + + /// + /// Reads and writes data to and from the SPI device. + /// + /// The input byte array. + /// The offset in the byte array to begin reading. + /// The output byte array. + /// The offset in the byte array to begin writing. + /// The number of bytes to read. + /// The number of bytes written and read. + public int ReadWrite(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) { + if (count == 0) + return 0; + + _portSemaphore.Wait(); + + try { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + cmd[0] = (byte)(0x80 + len - 1); + Buffer.BlockCopy(outBytes, outOffset + i, cmd, 1, len); + _port.Write(cmd, 0, len + 1); + ReadWire(inBytes, i + inOffset, len); + } + } finally { + _portSemaphore.Release(); + } + + return count; + } + + /// + /// Reads and writes data to and from the SPI device. + /// + /// The input byte array. + /// The offset in the byte array to begin reading. + /// The output byte array. + /// The offset in the byte array to begin writing. + /// The number of bytes to read. + /// The number of bytes written and read. + public async Task ReadWriteAsync(byte[] inBytes, int inOffset, byte[] outBytes, int outOffset, int count) { + if (count == 0) + return 0; + + await _portSemaphore.WaitAsync().ConfigureAwait(false); + + try { + await Task.Run(() => { + byte[] cmd = new byte[65]; + + for (int i = 0; i < count; i += 64) { + int len = ((count - i) < 64) ? (count - i) : 64; + cmd[0] = (byte)(0x80 + len - 1); + Buffer.BlockCopy(outBytes, outOffset + i, cmd, 1, len); + _port.Write(cmd, 0, len + 1); + ReadWire(inBytes, i + inOffset, len); + } + }).ConfigureAwait(false); + } finally { + _portSemaphore.Release(); + } + + return count; + } + + /// + /// Creates a new device with the specified serial port. + /// + /// The port. + public Device(SerialPort port) { + _port = port; + } + + /// + /// Creates a new device with the specified serial port. + /// + /// The port name. + public Device(string portName) { + _port = new SerialPort(portName, 460800, Parity.None, 8, StopBits.One); + } + } +} diff --git a/src/SpiDriver/DeviceStatus.cs b/src/SpiDriver/DeviceStatus.cs new file mode 100644 index 0000000..0926ac5 --- /dev/null +++ b/src/SpiDriver/DeviceStatus.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SpiDriver +{ + /// + /// Represents status information for the driver. + /// + public class DeviceStatus + { + /// + /// Gets the model. + /// + public string Model { get; internal set; } + + /// + /// Gets the serial number. + /// + public string Serial { get; internal set; } + + /// + /// Gets the uptime. + /// + public TimeSpan Uptime { get; internal set; } + + /// + /// Gets the voltage of the USB line. + /// + public float Voltage { get; internal set; } + + /// + /// Gets the current used by the target SPI device. + /// + public float Current { get; internal set; } + + /// + /// Gets the device temperature. + /// + public float Temperature { get; internal set; } + + /// + /// Gets the A state. + /// + public bool A { get; internal set; } + + /// + /// Gets the B state. + /// + public bool B { get; internal set; } + + /// + /// Gets the chip select state. + /// + public bool ChipSelect { get; internal set; } + + /// + /// Gets thge current CRC value. + /// + public ushort Crc { get; internal set; } + } +} diff --git a/src/SpiDriver/Output.cs b/src/SpiDriver/Output.cs new file mode 100644 index 0000000..d4032e9 --- /dev/null +++ b/src/SpiDriver/Output.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SpiDriver +{ + /// + /// Defines the output pins on the device. + /// + public enum Output + { + /// + /// The A output. + /// + A, + + /// + /// The B output. + /// + B, + + /// + /// The chip select output. + /// + CS + } +} diff --git a/src/SpiDriver/SpiDriver.csproj b/src/SpiDriver/SpiDriver.csproj new file mode 100644 index 0000000..25ac155 --- /dev/null +++ b/src/SpiDriver/SpiDriver.csproj @@ -0,0 +1,41 @@ + + + + netstandard2.0 + LICENSE + 0.1.0 + Alan Doherty + Alan Doherty + SPIDriver + SPIDriver + An unofficial library for SPIDriver allows for synchronous/asynchronous control of the device via the serial port + https://spidriver.com + https://github.com/alandoherty/spidriver-net + git + 0.1.0.0 + 0.1.0.0 + true + Alan Doherty (C) 2019 + https://s3-eu-west-1.amazonaws.com/assets.alandoherty.co.uk/github/spidriver-net-nuget.png + + + + bin\Debug\netstandard2.0\SPIDriver.xml + + + + bin\Release\netstandard2.0\SPIDriver.xml + + + + + + + + + True + + + + +