diff --git a/.gitignore b/.gitignore index a86c70a..c0075d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,37 @@ # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) -bin -obj +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ # mstest test results TestResults # user files **.user -**.suo \ No newline at end of file +**.suo +**.sdf +*.sln.docstates + +# resharper +/_[Rr]e[Ss]harper.* +*.[Rr]e[Ss]harper.* +*.[Rr]e[Ss]harper +_[Rr]e[Ss]harper.* + + +# OS generated files +**/[Tt]humbs.db +**/[Dd]esktop.ini + +# Nuget packages +packages/* +!packages/repositories.config + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + diff --git a/GpioTest/Program.cs b/GpioTest/Program.cs deleted file mode 100644 index 1fa397e..0000000 --- a/GpioTest/Program.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Linq; -using Raspberry.IO.GeneralPurpose; -using Raspberry.IO.GeneralPurpose.Behaviors; - -namespace GpioTest -{ - /// - /// This is a sample program. Must be modified to match your GPIO project. - /// - class Program - { - static void Main(string[] args) - { - var driver = args.GetDriver(); - var mainboard = Mainboard.Current; - - if (!mainboard.IsRaspberryPi) - { - Console.WriteLine("{0} is not a valid processor for a Raspberry Pi."); - return; - } - - // Declare outputs (leds) - var leds = new PinConfiguration[] - { - ConnectorPin.P1Pin26.Output().Name("Led1").Enable(), - ConnectorPin.P1Pin24.Output().Name("Led2"), - ConnectorPin.P1Pin22.Output().Name("Led3").Enable(), - ConnectorPin.P1Pin15.Output().Name("Led4"), - ConnectorPin.P1Pin13.Output().Name("Led5").Enable(), - ConnectorPin.P1Pin11.Output().Name("Led6") - }; - - // Assign a behavior to the leds - var behavior = new ChaserBehavior(leds) - { - Loop = args.GetLoop(), - RoundTrip = args.GetRoundTrip(), - Width = args.GetWidth(), - Interval = args.GetSpeed() - }; - - // Alternate behaviors... - /* - var random = new Random(); - var behavior = new PatternBehavior(leds, Enumerable.Range(0, 5).Select(i => random.Next(511))) - { - Loop = Helpers.GetLoop(args), - RoundTrip = Helpers.GetRoundTrip(args), - Interval = Helpers.GetSpeed(args) - };*/ - - /* - var behavior = new BlinkBehavior(leds) - { - Count = args.GetWidth(), - Interval = args.GetSpeed() - };*/ - - // Declare input (switchButton) interacting with the leds behavior - var switchButton = ConnectorPin.P1Pin3.Input().Name("Switch").Revert().Switch().Enable().OnStatusChanged(b => behavior.RoundTrip = !behavior.RoundTrip); - - // Create connection - using (var connection = new GpioConnection(driver)) - { - connection.Add(switchButton); - - Console.WriteLine("Running on Raspberry firmware rev{0}, board rev{1}, processor {2}", mainboard.FirmwareRevision, mainboard.BoardRevision, mainboard.Processor); - Console.WriteLine("Using {0}, frequency {1:0.##}hz", connection.Driver.GetType().Name, 1000.0 / args.GetSpeed()); - - connection.Start(behavior); // Starting the behavior automatically registers the pins to the connection, if needed. - - Console.ReadKey(true); - - connection.Stop(behavior); - } - } - } -} diff --git a/GpioTest/app.config b/GpioTest/app.config deleted file mode 100644 index 98787ab..0000000 --- a/GpioTest/app.config +++ /dev/null @@ -1,12 +0,0 @@ - - - -
- - - - - - - - diff --git a/Icon.png b/Icon.png new file mode 100644 index 0000000..5db0375 Binary files /dev/null and b/Icon.png differ diff --git a/README.md b/README.md index 68e7561..2ef6f8a 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,65 @@ -

Raspberry# IO

+Raspberry# IO +============= -

Introduction

-Raspberry# IO is a .NET/Mono IO Library for Raspberry Pi. +See the **[Raspberry\# IO Wiki](https://github.com/raspberry-sharp/raspberry-sharp-io/wiki)** for full documentation and samples. -Development is in a preliminary stage, project structure is subject to frequent changes. -Raspberry# IO currently support basic GPIO input/output. +Introduction +------------ +Raspberry# IO is a .NET/Mono IO Library for Raspberry Pi. This project is an initiative of the [Raspberry#](http://www.raspberry-sharp.org) Community. -Support for extended IO (such as support for I2C peripherals). +Current release is an early public release. Some features may not have been extensively tested. +Raspberry# IO currently supports low- and high-level GPIO input/output, support for SPI and I2C peripherals. -

Features

-Raspberry# IO provides a convenient way to use Raspberry Pi GPIO, while using .NET concepts, syntax and case. +Samples for SPI (for MCP3008 ADC or MCP4822 DAC), I2C (for MCP23017 I/O Expander), HD44780 LCD display and HC-SR04 distance sensor are provided. -
    -
  • Support for both memory (through libBCM2835 library) and file (native) access to GPIO
  • -
  • Support for processor and connector pin addressing
  • -
  • Support aliasing of pins for better customizing
  • -
  • Support both Raspberry B rev1 and rev2 pins mapping, as well as rev2 P5 connector
  • -
  • Easy-of-use, declarative configuration of pins
  • -
  • Support event firing when an input pin status change
  • -
  • Controlled use of resources using a IDisposable component
  • -
\ No newline at end of file +Support for extended I/O (such as SDI, or PWM for motor control) is planned for future releases. + +Programs using Raspberry# IO must be run with elevated privileges, for example the Test.Gpio.Chaser program included in solution: + + sudo mono Test.Gpio.Chaser.exe -loop + +Features +-------- + +### Raspberry.IO.GeneralPurpose +Raspberry.IO.GeneralPurpose provides a convenient way to use Raspberry Pi GPIO pins, while using .NET concepts, syntax and case. +You can easily add a reference to it in your Visual Studio projects using the **[Raspberry.IO.GeneralPurpose Nuget](https://www.nuget.org/packages/Raspberry.IO.GeneralPurpose)**. + +It currently support the following features: + +Low-level: + ++ Access to GPIO pins through in 3 flavors: basic (using files), through memory, and full (memory with support for edge detection through "pseudo-interrupt"). By default, full driver is used. ++ Addressing through **processor pin number or connector pin number** ++ Pin assignment of various Raspberry Pi revisions (as of 2013-09, **Raspberry Pi model B rev1 and rev2 as well as Raspberry Pi model A**, including rev2 P5 connector) ++ Controlled use of resources using a IDisposable component and ability to use edge detection instead of polling ++ Support sub-millisecond polling of input pins + +High-level: + ++ Giving custom name to pins for more readable code ++ Easy-to-use, declarative configuration of pins. Ability to revert the polarity (1/0) of pins; ability to **use an input pin as a switch button** ++ Firing of **events when pin status change** (input as well as output), using polling ++ **High-level behaviors** for output pins, including *blink*, *pattern* and *chaser* + +### Raspberry.IO.SerialPeripheralInterface + ++ Preliminary support for SPI through Raspberry.IO.SerialPeripheralInterface assembly ++ Includes SPI samples for MCP3008 ADC and MCP4822 DAC ++ Includes support for Linux's kernel SPI module driver spi-bcm2708 (/dev/spidev0.0) + +### Raspberry.IO.InterIntegratedCircuit + ++ Preliminary support for I2C through Raspberry.IO.InterIntegratedCircuit assembly ++ Includes I2C sample for MCP23017 I/O expander + +### Raspberry.IO.Components + ++ Preliminary support for various components through Raspberry.IO.Components assembly ++ Includes samples for + - HD44780 LCD display + - HC-SR04 distance detector + - Pca9685 PWM LED Controller (as used in the [Adafruit 16-Channel 12-bit PWM/Servo Driver](http://www.adafruit.com/products/815)) + - TLC59711 PWM LED Controller (as used in the [Adafruit 12-Channel 16-bit PWM LED Driver](http://www.adafruit.com/products/1455)) + +Parts of Raspberry# IO are inspired by [BCM2835 C Library](http://www.airspayce.com/mikem/bcm2835/) and Gordon Henderson's [WiringPi](http://wiringpi.com/). diff --git a/Raspberry.IO.Components/Clocks/Ds1307Connection.cs b/Raspberry.IO.Components/Clocks/Ds1307Connection.cs new file mode 100644 index 0000000..f80046b --- /dev/null +++ b/Raspberry.IO.Components/Clocks/Ds1307Connection.cs @@ -0,0 +1,237 @@ +using System; +using System.Collections.Generic; +using Raspberry.IO.InterIntegratedCircuit; +using System.Collections; + +namespace Raspberry.IO.Components.Clocks.Ds1307 +{ + /// + /// Provides functionality for the DS1307 Real-Time Clock. + /// + /// + /// The Clock Module needs to be modified to work with the Raspberry Pi + /// according to this article: http://electronics.stackexchange.com/questions/98361/how-to-modify-ds1307-rtc-to-use-3-3v-for-raspberry-pi + /// The Datasheet can be found here: http://www.alldatasheet.com/datasheet-pdf/pdf/58481/DALLAS/DS1307.html (the Memory Map + /// is on Page 5. Note that the values in RAM are stored in Nibbles). + /// + public class Ds1307Connection + { + public I2cDeviceConnection Connection { get; set; } + + /// + /// Creates a new instance of the class using the provided I2C Connection. + /// + /// I2C Connection to the Clock. + public Ds1307Connection(I2cDeviceConnection connection) + { + Connection = connection; + } + + /// + /// Reads the Seconds-byte (first byte in the RAM) from the Clock and returns it. + /// + /// The Seconds-Byte including the CH-Flag in bit 7. + private byte ReadSeconds() + { + Connection.Write(0x00); + return Connection.ReadByte(); + } + + /// + /// Reads 7 bytes from the Clock and returns them. + /// + /// 7 Bytes from the Clock. + private byte[] ReadAll() + { + Connection.Write(0x00); + return Connection.Read(7); + } + + /// + /// Reads the Date and Time from the Ds1307 and returns it. + /// + /// Date. + public DateTime GetDate() + { + return GetDate(ReadAll()); + } + + /// + /// Converts the provided bytes to a DateTime. + /// + /// Bytes that should be converted. + /// DateTime resulting from the bytes. + private DateTime GetDate(byte[] input) + { + /* Byte 1: CH-Flag + Seconds (00-59) + * Byte 2: Minutes (00-59) + * Byte 3: 12/24-Flag, AM/PM, Hours (01-12 or 00-23) + * Byte 4: Day of week (1-7) + * Byte 5: Day (01-31) + * Byte 6: Month (01-12) + * Byte 7: Year (00-99) + * Byte 8: Control Register (for enabling/disabling Sqare Wave) + */ + + int seconds = input[0]; + if (!IsRtcEnabled(input[0])) seconds = seconds - 128; // Remove "CH"-bit from the seconds if present + + seconds = NibbleToInt((byte)seconds); + + int minutes = NibbleToInt(input[1]); + int hours = NibbleToInt(input[2]); + + if ((hours & 64) == 64) + { + throw new NotImplementedException("AM/PM Time is currently not supported."); + // 12 h Format + //if ((hours & 32) == 32) + //{ + // //PM + //} + //else + //{ + // //AM + //} + } + + int dayOfWeek = NibbleToInt(input[3]); + + int day = NibbleToInt(input[4]); + int month = NibbleToInt(input[5]); + int year = NibbleToInt(input[6]); + + if (year == 0) return new DateTime(); + + return new DateTime(year + 2000, month, day, hours, minutes, seconds); + } + + /// + /// Writes the provided Date and Time to the Ds1307. + /// + /// The Date that should be set. + public void SetDate(DateTime date) + { + List toWrite = new List(); + + toWrite.Add(0x00); + toWrite.Add(SetEnabledDisableRtc(IntToNibble(date.Second), !IsRtcEnabled())); + toWrite.Add(IntToNibble(date.Minute)); + toWrite.Add(IntToNibble(date.Hour)); + toWrite.Add(Convert.ToByte(date.DayOfWeek)); + toWrite.Add(IntToNibble(date.Day)); + toWrite.Add(IntToNibble(date.Month)); + toWrite.Add(IntToNibble(Convert.ToInt16(date.ToString("yy"), 10))); + + Connection.Write(toWrite.ToArray()); + } + + /// + /// Enables the Clock. + /// + public void EnableRtc() + { + // CH=1: Disabled, CH=0: Enabled + byte seconds = ReadSeconds(); + if (IsRtcEnabled(seconds)) return; + + Connection.Write(0x00, SetEnabledDisableRtc(seconds, false)); + } + + /// + /// Disables the Clock. When the Clock is diabled, it not ticking. + /// + public void DisableRtc() + { + byte seconds = ReadSeconds(); + if (!IsRtcEnabled(seconds)) return; + + Connection.Write(0x00, SetEnabledDisableRtc(seconds, true)); + } + + /// + /// Disables or enables the Clock. + /// + /// The byte that contains the seconds and the CH-Flag. + /// true will disable the Clock, false will enable it. + /// + /// The disable/enabled-Flag is stored in bit 7 within the seconds-byte. + private byte SetEnabledDisableRtc(byte seconds, bool disable) + { + BitArray bits = new BitArray(new byte[] { seconds }); + bits.Set(7, disable); + + byte[] result = new byte[1]; + bits.CopyTo(result, 0); + + return result[0]; + } + + /// + /// Returns true, if the Clock is enabled, otherwise false is returned. + /// + /// true: Clock is enabled, false: Clock is diabled. + public bool IsRtcEnabled() + { + return IsRtcEnabled(ReadSeconds()); + } + + /// + /// Returns true, if the Clock is enabled, otherwise false. + /// + /// The byte, that contains the seconds and the CH-Flag. + /// true: Clock is enabled, false: Clock is diabled. + private bool IsRtcEnabled(byte seconds) + { + return !((seconds & 128) == 128); + } + + /// + /// Resets the Clock to the Factory Defaults. + /// + public void ResetToFactoryDefaults() + { + // The Factory Default is: 80,00,00,01,01,01,00,B3 + Connection.Write(0x00, 0x80, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00); + } + + /// + /// Writes the current System Date to the Clock. + /// + public void SystemTimeToRtc() + { + SetDate(DateTime.Now); + } + + /// + /// Converts the specified two-nibble-byte to an integer. + /// + /// Nibble that should be converted. + /// Integer representation of the nibble. + private static int NibbleToInt(byte nibble) + { + int result = 0; + result *= 100; + result += (10 * (nibble >> 4)); + result += nibble & 0xf; + return result; + } + + /// + /// Converts the specified integer to a two-nibble-byte. + /// + /// The integer that should be converted. Maximum is 99. + /// A byte with two nibbles that contains the integer. + private static byte IntToNibble(int number) + { + int bcd = 0; + for (int digit = 0; digit < 4; ++digit) + { + int nibble = number % 10; + bcd |= nibble << (digit * 4); + number /= 10; + } + return (byte)(bcd & 0xff); + } + } +} diff --git a/Raspberry.IO.Components/Controllers/HT16K33/HT16K33Connection.cs b/Raspberry.IO.Components/Controllers/HT16K33/HT16K33Connection.cs new file mode 100644 index 0000000..8c18d81 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/HT16K33/HT16K33Connection.cs @@ -0,0 +1,157 @@ +//Copyright (c) 2016 Logic Ethos Ltd +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +using System; +using Common.Logging; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.Timers; +using UnitsNet; +using System.IO; + +namespace Raspberry.IO.Components.Controllers.HT16K33 +{ + + /// + /// Driver for Holtek HT16K33 LED Matrix driver + /// As used by Adafruit devices + /// + public class HT16K33Connection //: IPwmDevice + { + + + public enum Flash : byte + { + Off = 0x00, + On = 0x01, + TwoHZ = 0x02, + OneHZ = 0x04, + HalfHZ = 0x06, + } + + public enum Command : byte + { + DisplayDataAddress = 0x00, + System_Setup = 0x20, + KeyDataAddressPointer = 0x40, + INTFlagAddressPointer = 0x60, + Flash = 0x80, + RowIntSet = 0xA0, + DimmingSet = 0xE0, + TestMode = 0xD9, + } + + const byte DEFAULT_ADDRESS = 0x70; + const byte HT16K33_Oscillator = 0x01; + const byte HT16K33_DisplayOn = 0x01; + + private readonly I2cDeviceConnection connection; + private static readonly ILog log = LogManager.GetLogger(); + + public byte[] LEDBuffer {get; private set;} //Max 16 rows, 8 bits (leds) + + + /// + /// Initializes a new instance of the + /// class. + /// + /// I2c connection. + /// Rows in use (1 to 16) + public HT16K33Connection(I2cDeviceConnection connection, int RowCount) + { + LEDBuffer = new byte[RowCount]; + this.connection = connection; + + log.Info(m => m("Resetting HT16K33")); + + connection.Write((byte)Command.System_Setup | (byte)HT16K33_Oscillator); //Turn on the oscillator. + connection.Write((byte)Command.Flash | (byte)HT16K33_DisplayOn | (byte)Flash.Off); + connection.Write((byte)Command.DimmingSet | (byte)15); + + // connection.Write(SetupSequence); + } + + + /// + /// Flash display at specified frequency. + /// + /// . + public void SetFlash(Flash frequency) + { + connection.WriteByte((byte)((byte)Command.Flash | HT16K33_DisplayOn | (byte)frequency)); + } + + /// + /// Set brightness of entire display to specified value (0 to 15). + /// + /// . + public void SetBrightness(uint brightness) + { + if (brightness > 15) brightness = 15; + connection.WriteByte((byte)((byte)Command.DimmingSet | (byte)brightness)); + } + + /// + /// Sets specified LED (0-[row-count] rows, 0 to 7 leds) + /// + /// . + public void SetLed(uint row, uint led, bool OutputOn) + { + if (row >= LEDBuffer.Length) throw new Exception("Row out of range"); + if (led > 7) throw new Exception("LED out of range 0 to 7"); + + if (OutputOn) + { + LEDBuffer[row] |= (byte)(1 << (int)led); //Turn on the speciried LED (set bit to one). + } + else + { + LEDBuffer[row] &= (byte)~(1 << (int)led); //Turn off the specified LED (set bit to zero). + } + connection.Write(new byte[] {(byte)row, LEDBuffer[row]}); + } + + + /// + /// Write display buffer to display hardware. + /// + /// . + public void WriteDisplayBuffer() + { + for (int i = 0; i < LEDBuffer.Length;i++) + { + connection.Write((byte)i,LEDBuffer[i]); + } + } + + /// + /// Clear contents of display buffer. + /// + /// . + public void Clear() + { + for (int i = 0; i < LEDBuffer.Length;i++) + { + LEDBuffer[i] = 0; + } + WriteDisplayBuffer(); + } + + /// + /// Set all LEDs On. + /// + /// . + public void SetAllOn() + { + for (int i = 0; i < LEDBuffer.Length;i++) + { + LEDBuffer[i] = 1; + } + WriteDisplayBuffer(); + } + + } +} diff --git a/Raspberry.IO.Components/Controllers/Pca9685/IPwmDevice.cs b/Raspberry.IO.Components/Controllers/Pca9685/IPwmDevice.cs new file mode 100644 index 0000000..7dfa152 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Pca9685/IPwmDevice.cs @@ -0,0 +1,35 @@ +using UnitsNet; + +namespace Raspberry.IO.Components.Controllers.Pca9685 +{ + /// + /// Provides an interface for PWM devices. + /// + public interface IPwmDevice + { + #region Methods + + /// + /// Sets the PWM update rate. + /// + /// The frequency. + void SetPwmUpdateRate(Frequency frequency); + + /// + /// Sets a single PWM channel with on / off values to control the duty cycle + /// + /// The channel. + /// The on values. + /// The off values. + void SetPwm(PwmChannel channel, int on, int off); + + /// + /// Set a channel to fully on or off + /// + /// The channel. + /// if set to true, all values are on; otherwise they are all off. + void SetFull(PwmChannel channel, bool fullOn); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Pca9685/Pca9685Connection.cs b/Raspberry.IO.Components/Controllers/Pca9685/Pca9685Connection.cs new file mode 100644 index 0000000..4360a8f --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Pca9685/Pca9685Connection.cs @@ -0,0 +1,161 @@ +#region References + +using System; +using Common.Logging; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.Timers; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Pca9685 +{ + /// + /// Driver for Adafruit 16-channel PWM/Servo Shield which uses the + /// NXP PCA9685 16-channel, 12-bit PWM Fm+ I2C-bus LED controller + /// Ported from + /// https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_PWM_Servo_Driver/Adafruit_PWM_Servo_Driver.py + /// + public class Pca9685Connection : IPwmDevice + { + #region Fields + + private readonly I2cDeviceConnection connection; + + private static readonly ILog log = LogManager.GetLogger(); + private static readonly TimeSpan delay = TimeSpan.FromMilliseconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The I2C connection. + public Pca9685Connection(I2cDeviceConnection connection) + { + this.connection = connection; + + log.Info(m => m("Resetting PCA9685")); + WriteRegister(Register.MODE1, 0x00); + } + + #endregion + + #region Methods + + /// + /// Sets the PWM update rate. + /// + /// The frequency, in hz. + /// Datasheet: 7.3.5 PWM frequency PRE_SCALE + public void SetPwmUpdateRate(Frequency frequency) + { + var preScale = 25000000.0m; // 25MHz + preScale /= 4096m; // 12-bit + preScale /= (int)frequency.Hertz; + + preScale -= 1.0m; + + log.Trace(m => m("Setting PWM frequency to {0} Hz", frequency)); + log.Trace(m => m("Estimated pre-maximum: {0}", preScale)); + + var prescale = Math.Floor(preScale + 0.5m); + + log.Trace(m => m("Final pre-maximum: {0}", prescale)); + + var oldmode = ReadRegister(Register.MODE1); + var newmode = (byte) ((oldmode & 0x7F) | 0x10); // sleep + + + WriteRegister(Register.MODE1, newmode); // go to sleep + + WriteRegister(Register.PRESCALE, (byte) Math.Floor(prescale)); + WriteRegister(Register.MODE1, oldmode); + + Timer.Sleep(delay); + + WriteRegister(Register.MODE1, oldmode | 0x80); + } + + /// + /// Sets a single PWM channel + /// + /// The channel. + /// The on values. + /// The off values. + public void SetPwm(PwmChannel channel, int on, int off) + { + WriteRegister(Register.LED0_ON_L + 4*(int) channel, on & 0xFF); + WriteRegister(Register.LED0_ON_H + 4*(int) channel, on >> 8); + WriteRegister(Register.LED0_OFF_L + 4*(int) channel, off & 0xFF); + WriteRegister(Register.LED0_OFF_H + 4*(int) channel, off >> 8); + } + + /// + /// Set a channel to fully on or off + /// + /// The channel. + /// if set to true, all values are on; otherwise they are all off. + public void SetFull(PwmChannel channel, bool fullOn) + { + if (fullOn) + SetFullOn(channel); + else + SetFullOff(channel); + } + + #endregion + + #region Private Helpers + + private enum Register + { + //SUBADR1 = 0x02, + //SUBADR2 = 0x03, + //SUBADR3 = 0x04, + MODE1 = 0x00, + PRESCALE = 0xFE, + LED0_ON_L = 0x06, + LED0_ON_H = 0x07, + LED0_OFF_L = 0x08, + LED0_OFF_H = 0x09, + //ALLLED_ON_L = 0xFA, + //ALLLED_ON_H = 0xFB, + //ALLLED_OFF_L = 0xFC, + //ALLLED_OFF_H = 0xFD, + } + + private void SetFullOn(PwmChannel channel) + { + WriteRegister(Register.LED0_ON_H + 4*(int) channel, 0x10); + WriteRegister(Register.LED0_OFF_H + 4*(int) channel, 0x00); + } + + private void SetFullOff(PwmChannel channel) + { + WriteRegister(Register.LED0_ON_H + 4*(int) channel, 0x00); + WriteRegister(Register.LED0_OFF_H + 4*(int) channel, 0x10); + } + + private void WriteRegister(Register register, byte data) + { + connection.Write(new[] {(byte) register, data}); + } + + private void WriteRegister(Register register, int data) + { + WriteRegister(register, (byte) data); + } + + private byte ReadRegister(Register register) + { + connection.Write((byte) register); + var value = connection.ReadByte(); + return value; + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Controllers/Pca9685/PwmChannel.cs b/Raspberry.IO.Components/Controllers/Pca9685/PwmChannel.cs new file mode 100644 index 0000000..0104072 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Pca9685/PwmChannel.cs @@ -0,0 +1,25 @@ +namespace Raspberry.IO.Components.Controllers.Pca9685 +{ + /// + /// Denotes available PWM channels + /// + public enum PwmChannel + { + C0 = 0, + C1 = 1, + C2 = 2, + C3 = 3, + C4 = 4, + C5 = 5, + C6 = 6, + C7 = 7, + C8 = 8, + C9 = 9, + C10 = 10, + C11 = 11, + C12 = 12, + C13 = 13, + C14 = 14, + C15 = 15 + } +} diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/ExtensionMethods/ByteExtensionMethods.cs b/Raspberry.IO.Components/Controllers/Tlc59711/ExtensionMethods/ByteExtensionMethods.cs new file mode 100644 index 0000000..e89c951 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/ExtensionMethods/ByteExtensionMethods.cs @@ -0,0 +1,28 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + internal static class ByteExtensionMethods + { + #region Constants + public const byte BRIGHTNESS_CONTROL_MAX = 127; + #endregion + + #region Methods + + public static void ThrowOnInvalidBrightnessControl(this byte value) { + if (value <= BRIGHTNESS_CONTROL_MAX) + return; + + var message = String.Format("The maximum value for brightness control is {0}. You set a value of {1}.", BRIGHTNESS_CONTROL_MAX, value); + + throw new ArgumentException(message, "value"); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/IPwmChannels.cs b/Raspberry.IO.Components/Controllers/Tlc59711/IPwmChannels.cs new file mode 100644 index 0000000..6b5e422 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/IPwmChannels.cs @@ -0,0 +1,48 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// The Pulse-width modulation (PWM) channels. + /// + public interface IPwmChannels { + + #region Properties + + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself to modify PWM channel values. + /// + /// channel index + /// The current PWM value from + UInt16 this[int index] { get; set; } + + /// + /// Returns the number of channels. + /// + int Count { get; } + + #endregion + + #region Methods + + /// + /// Returns the PWM value at the specified channel . + /// + /// Channel index + /// The PWM value at the specified channel + UInt16 Get(int index); + + /// + /// Sets the PWM value at channel . + /// + /// Channel index + /// The PWM value + void Set(int index, UInt16 value); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/IPwmDevice.cs b/Raspberry.IO.Components/Controllers/Tlc59711/IPwmDevice.cs new file mode 100644 index 0000000..90ca993 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/IPwmDevice.cs @@ -0,0 +1,17 @@ +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// A pulse-width modulation (PWM) device + /// + public interface IPwmDevice + { + #region Properties + + /// + /// The PWM channels + /// + IPwmChannels Channels { get; } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Cluster.cs b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Cluster.cs new file mode 100644 index 0000000..158faf8 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Cluster.cs @@ -0,0 +1,48 @@ +#region References + +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// A chained cluster of Adafruit's 12-channel 16bit PWM/LED driver TLC59711. + /// The devices should be connected together with their SDTI/SDTO pins. + /// + public interface ITlc59711Cluster : IEnumerable, IPwmDevice + { + #region Properties + + /// + /// Number of TLC59711 devices chained together + /// + int Count { get; } + + /// + /// Returns the TLC59711 device at the requested position + /// + /// TLC59711 index + /// TLC59711 device + ITlc59711Device this[int index] { get; } + + #endregion + + #region Methods + + /// + /// Returns the TLC59711 device at the requested position + /// + /// TLC59711 index + /// TLC59711 device + ITlc59711Device Get(int index); + + /// + /// Set BLANK on/off at all connected devices. + /// + /// If set to true all outputs are forced off. + void Blank(bool blank); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Connection.cs b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Connection.cs new file mode 100644 index 0000000..357b2d0 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Connection.cs @@ -0,0 +1,32 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// A connection the one or more TLC59711 devices. + /// + public interface ITlc59711Connection : IDisposable + { + #region Properties + + /// + /// A chained cluster of Adafruit's 12-channel 16bit PWM/LED driver TLC59711. + /// + ITlc59711Cluster Devices { get; } + + #endregion + + #region Methods + + /// + /// Creates a TLC59711 command and sends it to the first device using the SPI bus. + /// + void Update(); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Device.cs b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Device.cs new file mode 100644 index 0000000..320bfb8 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Device.cs @@ -0,0 +1,17 @@ +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// Adafruit 12-Channel 16-bit PWM LED Driver TLC59711 + /// + public interface ITlc59711Device : IPwmDevice, ITlc59711Settings + { + #region Methods + + /// + /// Initializes the device with default values. + /// + void Reset(); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Settings.cs b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Settings.cs new file mode 100644 index 0000000..ce63d7e --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/ITlc59711Settings.cs @@ -0,0 +1,90 @@ +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// TLC59711 settings + /// + public interface ITlc59711Settings + { + + #region Properties + + /// + /// BLANK: If set to true all outputs are forced off. Default value is true. + /// + bool Blank { get; set; } + + /// + /// DSPRPT: Auto display repeat mode. If false the auto repeat function is disabled. + /// + /// + /// Each constant-current output is only turned on once, according the GS data after + /// is set to false or after the internal latch pulse is + /// generated with set to true. If true + /// each output turns on and off according to the GS data every 65536 GS reference clocks. + /// + bool DisplayRepeatMode { get; set; } + + /// + /// TMGRST: Display timing reset mode. Set to false to disable. + /// + /// + /// If true, the GS counter is reset to '0' and all constant-current + /// outputs are forced off when the internal latch pulse is generated for data latching. + /// This function is the same when is set to false. + /// Therefore, does not need to be controlled by an external controller + /// when this mode is enabled. If false, the GS counter is not reset and no output + /// is forced off even if the internal latch pulse is generated. + /// + bool DisplayTimingResetMode { get; set; } + + /// + /// EXTGCK: GS reference clock selection. (false = internal oscillator clock, true = SCKI clock.) + /// + /// + /// If true, PWM timing refers to the SCKI clock. If false, PWM timing + /// refers to the internal oscillator clock. + /// + bool ReferenceClock { get; set; } + + /// + /// OUTTMG: GS reference clock edge selection for OUTXn on-off timing control. (false = falling edge, true = rising edge). + /// + /// + /// If true, OUTXn are turned on or off at the rising edge of the selected GS reference clock. + /// If false, OUTXn are turned on or off at the falling edge of the selected clock. + /// + bool ReferenceClockEdge { get; set; } + + /// + /// BCR: Global brightness control for OUTR0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + byte BrightnessControlR { get; set; } + + /// + /// BCG: Global brightness control for OUTG0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + byte BrightnessControlG { get; set; } + + /// + /// BCB: Global brightness control for OUTB0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + byte BrightnessControlB { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Channels.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Channels.cs new file mode 100644 index 0000000..b6934ca --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Channels.cs @@ -0,0 +1,100 @@ +#region References + +using System; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// The PWM channels of a TLC59711 + /// + internal sealed class Tlc59711Channels : IPwmChannels + { + #region Constants + private const int NUMBER_OF_CHANNELS = 12; + #endregion + + #region Fields + private readonly IMemory memory; + private readonly int channelOffset; + #endregion + + #region Instance Management + /// + /// Creates a new instance of the class. + /// + /// Memory to work with + /// Byte offset to the first channel index + public Tlc59711Channels(IMemory memory, int offset) { + this.memory = memory; + channelOffset = offset; + } + #endregion + + #region Properties + /// + /// Returns the number of channels. + /// + public int Count { + get { return NUMBER_OF_CHANNELS; } + } + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself to modify PWM channel values. + /// + /// channel index + /// The current PWM value from + public UInt16 this[int index] { + get { return Get(index); } + set { Set(index, value); } + } + #endregion + + #region Methods + + /// + /// Returns the PWM value at the specified channel . + /// + /// Channel index + /// The PWM value at the specified channel + public UInt16 Get(int index) { + ThrowOnInvalidChannelIndex(index); + + var offset = channelOffset + (index * 2); + + var high = memory.Read(offset); + var low = memory.Read(offset + 1); + + return unchecked((UInt16)((high << 8) | low)); + } + + /// + /// Sets the PWM value at channel . + /// + /// Channel index + /// The PWM value + public void Set(int index, UInt16 value) { + ThrowOnInvalidChannelIndex(index); + + var offset = channelOffset + (index * 2); + + memory.Write(offset, unchecked((byte)(value >> 8))); + memory.Write(offset + 1, unchecked((byte)value)); + } + #endregion + + #region Private Helpers + + private static void ThrowOnInvalidChannelIndex(int index) { + if (index >= 0 && index < NUMBER_OF_CHANNELS) { + return; + } + + var message = string.Format("The index must be greater or equal than 0 and lower than {0}.", NUMBER_OF_CHANNELS); + throw new ArgumentOutOfRangeException("index", index, message); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Cluster.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Cluster.cs new file mode 100644 index 0000000..82edf60 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Cluster.cs @@ -0,0 +1,141 @@ +#region References + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// A chained cluster of Adafruit's 12-channel 16bit PWM/LED driver TLC59711. + /// The devices should be connected together with their SDTI/SDTO pins. + /// + public class Tlc59711Cluster : ITlc59711Cluster + { + #region Constants + private const int COMMAND_SIZE = Tlc59711Device.COMMAND_SIZE; + #endregion + + #region Fields + private readonly ITlc59711Device[] devices; + private readonly IPwmChannels channels; + #endregion + + #region Instance Management + + /// + /// Creates a new instance of the class. + /// + /// Memory to work with. + /// Number of s connected together. + public Tlc59711Cluster(IMemory memory, int numberOfDevices) { + if (ReferenceEquals(memory, null)) + throw new ArgumentNullException("memory"); + if (numberOfDevices <= 0) + throw new ArgumentOutOfRangeException("numberOfDevices", "You cannot create a cluster with less than one device."); + + var minimumRequiredMemorySize = (numberOfDevices * COMMAND_SIZE); + if (memory.Length < minimumRequiredMemorySize) { + var message = string.Format("For {0} device(s) you have to provide a minimum of {1} bytes of memory.", numberOfDevices, minimumRequiredMemorySize); + throw new InsufficientMemoryException(message); + } + + devices = CreateDevices(memory, numberOfDevices).ToArray(); + channels = new Tlc59711ClusterChannels(devices); + } + + /// + /// Creates a new instance of the class. + /// + /// The devices, that are chained together + public Tlc59711Cluster(IEnumerable devices) { + this.devices = devices.ToArray(); + channels = new Tlc59711ClusterChannels(this.devices); + } + + #endregion + + #region Properties + /// + /// Number of TLC59711 devices chained together + /// + public int Count { + get { return devices.Length; } + } + + /// + /// Returns the TLC59711 device at the requested position + /// + /// TLC59711 index + /// TLC59711 device + public ITlc59711Device this[int index] { + get { return devices[index]; } + } + + /// + /// The PWM channels + /// + public IPwmChannels Channels { + get { return channels; } + } + #endregion + + #region Methods + + /// + /// Returns an enumerator + /// + /// + /// An object. + /// + /// 2 + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + /// + /// Returns an enumerator + /// + /// + /// An object. + /// + /// 1 + public IEnumerator GetEnumerator() { + return ((IEnumerable) devices) + .GetEnumerator(); + } + + /// + /// Returns the TLC59711 device at the requested position + /// + /// TLC59711 index + /// TLC59711 device + public ITlc59711Device Get(int index) { + return devices[index]; + } + + /// + /// Set BLANK on/off at all connected devices. + /// + /// If set to true all outputs are forced off. + public void Blank(bool blank) { + foreach (var device in devices) { + device.Blank = blank; + } + } + #endregion + + #region Private Helpers + private static IEnumerable CreateDevices(IMemory memory, int numberOfDevices) { + for (var i = 0; i < numberOfDevices; i++) { + var subset = new MemorySubset(memory, i * COMMAND_SIZE, COMMAND_SIZE, false); + yield return new Tlc59711Device(subset); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711ClusterChannels.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711ClusterChannels.cs new file mode 100644 index 0000000..cda95d3 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711ClusterChannels.cs @@ -0,0 +1,102 @@ +#region References + +using System; +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// The PWM channels of a TLC59711 device cluster + /// + internal sealed class Tlc59711ClusterChannels : IPwmChannels + { + #region Structures + private struct Mapping + { + private readonly ITlc59711Device device; + private readonly int channelIndex; + + public Mapping(ITlc59711Device device, int channelIndex) { + this.device = device; + this.channelIndex = channelIndex; + } + + public ITlc59711Device Device { + get { return device; } + } + public int ChannelIndex { + get { return channelIndex; } + } + } + #endregion + + #region Fields + private readonly List deviceMap = new List(); + #endregion + + #region Instance Management + + /// + /// Creates a new instance of the class. + /// + /// TLC59711 devices + public Tlc59711ClusterChannels(IEnumerable devices) + { + if (devices == null) + throw new ArgumentNullException("devices"); + + foreach (var device in devices) + { + if (ReferenceEquals(device, null)) + continue; + + for (var i = 0; i < device.Channels.Count; i++) + deviceMap.Add(new Mapping(device, i)); + } + } + + #endregion + + #region Properties + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself to modify PWM channel values. + /// + /// channel index + /// The current PWM value from + public ushort this[int index] { + get { return Get(index); } + set { Set(index, value); } + } + /// + /// Returns the number of channels. + /// + public int Count { + get { return deviceMap.Count; } + } + #endregion + + #region Methods + /// + /// Returns the PWM value at the specified channel . + /// + /// Channel index + /// The PWM value at the specified channel + public ushort Get(int index) { + var mapping = deviceMap[index]; + return mapping.Device.Channels[mapping.ChannelIndex]; + } + + /// + /// Sets the PWM value at channel . + /// + /// Channel index + /// The PWM value + public void Set(int index, ushort value) { + var mapping = deviceMap[index]; + mapping.Device.Channels[mapping.ChannelIndex] = value; + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Connection.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Connection.cs new file mode 100644 index 0000000..6e90609 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Connection.cs @@ -0,0 +1,98 @@ +#region References + +using System; +using Raspberry.IO.SerialPeripheralInterface; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// A connection the one or more TLC59711 devices. + /// + public class Tlc59711Connection : ITlc59711Connection + { + #region Constants + private const int BITS_PER_WORD = 8; + private const SpiMode SPI_MODE_0 = SpiMode.Mode0; + private const int SPEED = 10000000; // Spec say max 10Mhz (RaspberryPI will only use about ~7Mhz) + private const int DELAY = 20; + #endregion + + #region Fields + private readonly INativeSpiConnection connection; + private readonly ISpiTransferBuffer transferBuffer; + private readonly Tlc59711Cluster deviceCluster; + #endregion + + #region Instance Management + + /// + /// Creates a new instance of the class and initializes it. + /// + /// A open SPI connection + /// If true the SPI connection will be initialized with common data transfers settings. + /// Number of s connected together. + public Tlc59711Connection(INativeSpiConnection connection, bool initializeWithDefault, int numberOfDevices) { + if (ReferenceEquals(connection, null)) + throw new ArgumentNullException("connection"); + + if (numberOfDevices <= 0) + throw new ArgumentOutOfRangeException("numberOfDevices", "You need at least one device."); + + this.connection = connection; + + if (initializeWithDefault) { + connection.SetBitsPerWord(BITS_PER_WORD); + connection.SetSpiMode(SPI_MODE_0); + connection.SetMaxSpeed(SPEED); + connection.SetDelay(DELAY); + } + + var requiredMemorySize = numberOfDevices * Tlc59711Device.COMMAND_SIZE; + transferBuffer = connection.CreateTransferBuffer(requiredMemorySize, SpiTransferMode.Write); + + deviceCluster = new Tlc59711Cluster(transferBuffer.Tx, numberOfDevices); + } + + /// + /// Releases all managed resources. The SPI connection will be closed. + /// + /// 2 + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases all managed resources. + /// + /// If true, all managed resources including the SPI connection will be released/closed. + protected virtual void Dispose(bool disposing) { + if (disposing) { + transferBuffer.Dispose(); + connection.Dispose(); + } + } + + #endregion + + #region Properties + /// + /// A chained cluster of Adafruit's 12-channel 16bit PWM/LED driver TLC59711. + /// + public ITlc59711Cluster Devices { + get { return deviceCluster; } + } + #endregion + + #region Methods + /// + /// Creates a TLC59711 command and sends it to the first device using the SPI bus. + /// + public void Update() { + connection.Transfer(transferBuffer); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Device.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Device.cs new file mode 100644 index 0000000..83ac5b8 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Device.cs @@ -0,0 +1,265 @@ +#region References + +using System; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// Adafruit 12-channel 16bit PWM/LED driver TLC59711 + /// + public class Tlc59711Device : ITlc59711Device + { + #region Constants + public const int COMMAND_SIZE = 28; + private const byte MAGIC_WORD = 0x25 << 2; + + private const byte OUTTMG = 1 << 1; + private const byte EXTGCK = 1; + private const byte TMGRST = 1 << 7; + private const byte DSPRPT = 1 << 6; + private const byte BLANK = 1 << 5; + private const byte OFF = 0; + private const int DATA_OFFSET = 4; + private const int DATA_LENGTH = COMMAND_SIZE - DATA_OFFSET; + #endregion + + #region Fields + + private readonly IMemory memory; + private readonly ITlc59711Settings initSettings; + private readonly IPwmChannels channels; + + private byte referenceClockEdge = OUTTMG; + private byte referenceClock = OFF; + private byte displayTimingResetMode = TMGRST; + private byte displayRepeatMode = DSPRPT; + private byte blank = BLANK; + private byte bcb = 127; + private byte bcg = 127; + private byte bcr = 127; + + #endregion + + #region Instance Management + + /// + /// Creates a new instance of the class. + /// + /// Memory to work with. + /// Initial settings + public Tlc59711Device(IMemory memory, ITlc59711Settings settings = null) { + if (ReferenceEquals(memory, null)) + throw new ArgumentNullException("memory"); + + if (memory.Length < COMMAND_SIZE) { + throw new ArgumentException( + string.Format("Need at least {0} bytes, got {1} bytes.", COMMAND_SIZE, memory.Length), "memory"); + } + + this.memory = memory; + initSettings = settings; + channels = new Tlc59711Channels(memory, DATA_OFFSET); + + Reset(); + } + + #endregion + + #region Properties + + /// + /// BLANK: If set to true all outputs are forced off. Default value is true. + /// + public bool Blank { + get { return blank == BLANK; } + set { + blank = (value) ? BLANK : OFF; + WriteSecondByte(); + } + } + + /// + /// DSPRPT: Auto display repeat mode. If false the auto repeat function is disabled. + /// + /// + /// Each constant-current output is only turned on once, according the GS data after + /// is set to false or after the internal latch pulse is + /// generated with set to true. If true + /// each output turns on and off according to the GS data every 65536 GS reference clocks. + /// + public bool DisplayRepeatMode { + get { return displayRepeatMode == DSPRPT; } + set { + displayRepeatMode = (value) ? DSPRPT : OFF; + WriteSecondByte(); + } + } + + /// + /// TMGRST: Display timing reset mode. Set to false to disable. + /// + /// + /// If true, the GS counter is reset to '0' and all constant-current + /// outputs are forced off when the internal latch pulse is generated for data latching. + /// This function is the same when is set to false. + /// Therefore, does not need to be controlled by an external controller + /// when this mode is enabled. If false, the GS counter is not reset and no output + /// is forced off even if the internal latch pulse is generated. + /// + public bool DisplayTimingResetMode { + get { return displayTimingResetMode == TMGRST; } + set { + displayTimingResetMode = (value) ? TMGRST : OFF; + WriteSecondByte(); + } + } + + /// + /// EXTGCK: GS reference clock selection. (false = internal oscillator clock, true = SCKI clock.) + /// + /// + /// If true, PWM timing refers to the SCKI clock. If false, PWM timing + /// refers to the internal oscillator clock. + /// + public bool ReferenceClock { + get { return referenceClock == EXTGCK; } + set { + referenceClock = (value) ? EXTGCK : OFF; + WriteFirstByte(); + } + } + + /// + /// OUTTMG: GS reference clock edge selection for OUTXn on-off timing control. (false = falling edge, true = rising edge). + /// + /// + /// If true, OUTXn are turned on or off at the rising edge of the selected GS reference clock. + /// If false, OUTXn are turned on or off at the falling edge of the selected clock. + /// + public bool ReferenceClockEdge { + get { return referenceClockEdge == OUTTMG; } + set { + referenceClockEdge = (value) ? OUTTMG : OFF; + WriteFirstByte(); + } + } + + /// + /// BCR: Global brightness control for OUTR0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlR { + get { return bcr; } + set { + value.ThrowOnInvalidBrightnessControl(); + + bcr = value; + WriteFourthByte(); + } + } + + /// + /// BCG: Global brightness control for OUTG0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlG { + get { return bcg; } + set { + value.ThrowOnInvalidBrightnessControl(); + + bcg = value; + WriteThirdByte(); + WriteFourthByte(); + } + } + + /// + /// BCB: Global brightness control for OUTB0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlB { + get { return bcb; } + set { + value.ThrowOnInvalidBrightnessControl(); + + bcb = value; + WriteSecondByte(); + WriteThirdByte(); + } + } + + /// + /// The PWM channels + /// + public IPwmChannels Channels { + get { return channels; } + } + + #endregion + + #region Methods + + /// + /// Initializes the device with default values. + /// + public void Reset() { + Initialize(initSettings ?? new Tlc59711Settings()); + } + + #endregion + + #region Private Helpers + + private void Initialize(ITlc59711Settings settings) { + referenceClockEdge = settings.ReferenceClockEdge ? OUTTMG : OFF; + referenceClock = settings.ReferenceClock ? EXTGCK : OFF; + displayTimingResetMode = settings.DisplayTimingResetMode ? TMGRST : OFF; + displayRepeatMode = settings.DisplayRepeatMode ? DSPRPT : OFF; + blank = settings.Blank ? BLANK : OFF; + bcb = settings.BrightnessControlB; + bcg = settings.BrightnessControlG; + bcr = settings.BrightnessControlR; + + WriteFirstByte(); + WriteSecondByte(); + WriteThirdByte(); + WriteFourthByte(); + + var zero = new byte[DATA_LENGTH]; + memory.Copy(zero, 0, DATA_OFFSET, DATA_LENGTH); + } + + private void WriteFirstByte() { + memory.Write(0, (byte) (MAGIC_WORD | referenceClockEdge | referenceClock)); + } + + private void WriteSecondByte() { + memory.Write(1, (byte) (displayTimingResetMode | displayRepeatMode | blank | (bcb >> 2))); + } + + private void WriteThirdByte() { + memory.Write(2, (byte) ((bcb << 6) | (bcg >> 1))); + } + + private void WriteFourthByte() { + memory.Write(3, (byte) ((bcg << 7) | bcr)); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Settings.cs b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Settings.cs new file mode 100644 index 0000000..1c23440 --- /dev/null +++ b/Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711Settings.cs @@ -0,0 +1,131 @@ +namespace Raspberry.IO.Components.Controllers.Tlc59711 +{ + /// + /// TLC59711 settings + /// + public class Tlc59711Settings : ITlc59711Settings + { + #region Fields + + private bool blank = true; + private bool displayRepeatMode = true; + private bool displayTimingResetMode = true; + private bool referenceClockEdge = true; + private byte brightnessControlR = 127; + private byte brightnessControlG = 127; + private byte brightnessControlB = 127; + + #endregion + + #region Properties + + /// + /// BLANK: If set to true all outputs are forced off. Default value is true. + /// + public bool Blank { + get { return blank; } + set { blank = value; } + } + + /// + /// DSPRPT: Auto display repeat mode. If false the auto repeat function is disabled. + /// + /// + /// Each constant-current output is only turned on once, according the GS data after + /// is set to false or after the internal latch pulse is + /// generated with set to true. If true + /// each output turns on and off according to the GS data every 65536 GS reference clocks. + /// + public bool DisplayRepeatMode { + get { return displayRepeatMode; } + set { displayRepeatMode = value; } + } + + /// + /// TMGRST: Display timing reset mode. Set to false to disable. + /// + /// + /// If true, the GS counter is reset to '0' and all constant-current + /// outputs are forced off when the internal latch pulse is generated for data latching. + /// This function is the same when is set to false. + /// Therefore, does not need to be controlled by an external controller + /// when this mode is enabled. If false, the GS counter is not reset and no output + /// is forced off even if the internal latch pulse is generated. + /// + public bool DisplayTimingResetMode { + get { return displayTimingResetMode; } + set { displayTimingResetMode = value; } + } + + /// + /// EXTGCK: GS reference clock selection. (false = internal oscillator clock, true = SCKI clock.) + /// + /// + /// If true, PWM timing refers to the SCKI clock. If false, PWM timing + /// refers to the internal oscillator clock. + /// + public bool ReferenceClock { get; set; } + + /// + /// OUTTMG: GS reference clock edge selection for OUTXn on-off timing control. (false = falling edge, true = rising edge). + /// + /// + /// If true, OUTXn are turned on or off at the rising edge of the selected GS reference clock. + /// If false, OUTXn are turned on or off at the falling edge of the selected clock. + /// + public bool ReferenceClockEdge { + get { return referenceClockEdge; } + set { referenceClockEdge = value; } + } + + /// + /// BCR: Global brightness control for OUTR0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlR { + get { return brightnessControlR; } + set { + value.ThrowOnInvalidBrightnessControl(); + brightnessControlR = value; + } + } + + /// + /// BCG: Global brightness control for OUTG0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlG { + get { return brightnessControlG; } + set { + value.ThrowOnInvalidBrightnessControl(); + brightnessControlG = value; + } + } + + /// + /// BCB: Global brightness control for OUTB0-3. Default value is 127. + /// + /// + /// The BC data are seven bits long, which allows each color group output + /// current to be adjusted in 128 steps (0-127) from 0% to 100% of the + /// maximum output current. + /// + public byte BrightnessControlB { + get { return brightnessControlB; } + set { + value.ThrowOnInvalidBrightnessControl(); + brightnessControlB = value; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002AnalogPinExtensionMethods.cs b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002AnalogPinExtensionMethods.cs new file mode 100644 index 0000000..a42fc51 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002AnalogPinExtensionMethods.cs @@ -0,0 +1,20 @@ +namespace Raspberry.IO.Components.Converters.Mcp3002 +{ + public static class Mcp3002AnalogPinExtensionMethods + { + #region Methods + + /// + /// Creates an analog input pin. + /// + /// The connection. + /// The channel. + /// The pin. + public static Mcp3002InputAnalogPin In(this Mcp3002SpiConnection connection, Mcp3002Channel channel) + { + return new Mcp3002InputAnalogPin(connection, channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002Channel.cs b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002Channel.cs new file mode 100644 index 0000000..8c06ae2 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002Channel.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Converters.Mcp3002 +{ + public enum Mcp3002Channel + { + Channel0 = 0, + Channel1 = 1 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002InputAnalogPin.cs b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002InputAnalogPin.cs new file mode 100644 index 0000000..71e01ca --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002InputAnalogPin.cs @@ -0,0 +1,47 @@ +namespace Raspberry.IO.Components.Converters.Mcp3002 +{ + public class Mcp3002InputAnalogPin : IInputAnalogPin + { + #region Fields + + private readonly Mcp3002SpiConnection connection; + private readonly Mcp3002Channel channel; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The channel. + public Mcp3002InputAnalogPin(Mcp3002SpiConnection connection, Mcp3002Channel channel) + { + this.connection = connection; + this.channel = channel; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the value of the pin. + /// + /// + /// The value. + /// + public AnalogValue Read() + { + return connection.Read(channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002SpiConnection.cs b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002SpiConnection.cs new file mode 100644 index 0000000..c46f2b8 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3002/Mcp3002SpiConnection.cs @@ -0,0 +1,85 @@ +#region References + +using System; +using Raspberry.IO.SerialPeripheralInterface; + +#endregion + +namespace Raspberry.IO.Components.Converters.Mcp3002 +{ + /// + /// Represents a connection to MCP3002 ADC converter. + /// + public class Mcp3002SpiConnection : IDisposable + { + #region Fields + + private readonly SpiConnection spiConnection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The clock pin. + /// The slave select pin. + /// The miso pin. + /// The mosi pin. + public Mcp3002SpiConnection(IOutputBinaryPin clockPin, IOutputBinaryPin slaveSelectPin, IInputBinaryPin misoPin, IOutputBinaryPin mosiPin) + { + spiConnection = new SpiConnection(clockPin, slaveSelectPin, misoPin, mosiPin, Endianness.LittleEndian); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Reads the specified channel. + /// + /// The channel. + /// The value + public AnalogValue Read(Mcp3002Channel channel) + { + using(spiConnection.SelectSlave()) + { + // Start bit + spiConnection.Write(true); + + // Channel is single-ended + spiConnection.Write(true); + + // Channel Id + spiConnection.Write((byte)channel, 1); + + // Let one clock to sample + spiConnection.Synchronize(); + + // Read 10 bits + var data = (int)spiConnection.Read(10); + + return new AnalogValue(data, 0x3FF); + } + } + + /// + /// Closes this instance. + /// + public void Close() + { + spiConnection.Close(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008AnalogPinExtensionMethods.cs b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008AnalogPinExtensionMethods.cs new file mode 100644 index 0000000..61fe162 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008AnalogPinExtensionMethods.cs @@ -0,0 +1,20 @@ +namespace Raspberry.IO.Components.Converters.Mcp3008 +{ + public static class Mcp3008AnalogPinExtensionMethods + { + #region Methods + + /// + /// Creates an analog input pin. + /// + /// The connection. + /// The channel. + /// The pin. + public static Mcp3008InputAnalogPin In(this Mcp3008SpiConnection connection, Mcp3008Channel channel) + { + return new Mcp3008InputAnalogPin(connection, channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008Channel.cs b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008Channel.cs new file mode 100644 index 0000000..75dafc7 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008Channel.cs @@ -0,0 +1,14 @@ +namespace Raspberry.IO.Components.Converters.Mcp3008 +{ + public enum Mcp3008Channel + { + Channel0 = 0, + Channel1 = 1, + Channel2 = 2, + Channel3 = 3, + Channel4 = 4, + Channel5 = 5, + Channel6 = 6, + Channel7 = 7 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008InputAnalogPin.cs b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008InputAnalogPin.cs new file mode 100644 index 0000000..546e858 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008InputAnalogPin.cs @@ -0,0 +1,47 @@ +namespace Raspberry.IO.Components.Converters.Mcp3008 +{ + public class Mcp3008InputAnalogPin : IInputAnalogPin + { + #region Fields + + private readonly Mcp3008SpiConnection connection; + private readonly Mcp3008Channel channel; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The channel. + public Mcp3008InputAnalogPin(Mcp3008SpiConnection connection, Mcp3008Channel channel) + { + this.connection = connection; + this.channel = channel; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the value of the pin. + /// + /// + /// The value. + /// + public AnalogValue Read() + { + return connection.Read(channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008SpiConnection.cs b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008SpiConnection.cs new file mode 100644 index 0000000..71ae0f0 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3008/Mcp3008SpiConnection.cs @@ -0,0 +1,88 @@ +#region References + +using System; +using Raspberry.IO.SerialPeripheralInterface; + +#endregion + +namespace Raspberry.IO.Components.Converters.Mcp3008 +{ + /// + /// Represents a connection to MCP3004/3008 ADC converter. + /// + /// + /// See specification at http://www.adafruit.com/datasheets/MCP3008.pdf + /// + public class Mcp3008SpiConnection : IDisposable + { + #region Fields + + private readonly SpiConnection spiConnection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The clock pin. + /// The slave select pin. + /// The miso pin. + /// The mosi pin. + public Mcp3008SpiConnection(IOutputBinaryPin clockPin, IOutputBinaryPin slaveSelectPin, IInputBinaryPin misoPin, IOutputBinaryPin mosiPin) + { + spiConnection = new SpiConnection(clockPin, slaveSelectPin, misoPin, mosiPin, Endianness.LittleEndian); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Reads the specified channel. + /// + /// The channel. + /// The value + public AnalogValue Read(Mcp3008Channel channel) + { + using(spiConnection.SelectSlave()) + { + // Start bit + spiConnection.Write(true); + + // Channel is single-ended + spiConnection.Write(true); + + // Channel Id + spiConnection.Write((byte)channel, 3); + + // Let one clock to sample + spiConnection.Synchronize(); + + // Read 10 bits + var data = (int)spiConnection.Read(10); + + return new AnalogValue(data, 0x3FF); + } + } + + /// + /// Closes this instance. + /// + public void Close() + { + spiConnection.Close(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208AnalogPinExtensionMethods.cs b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208AnalogPinExtensionMethods.cs new file mode 100644 index 0000000..9a125d3 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208AnalogPinExtensionMethods.cs @@ -0,0 +1,20 @@ +namespace Raspberry.IO.Components.Converters.Mcp3208 +{ + public static class Mcp3208AnalogPinExtensionMethods + { + #region Methods + + /// + /// Creates an analog input pin. + /// + /// The connection. + /// The channel. + /// The pin. + public static Mcp3208InputAnalogPin In(this Mcp3208SpiConnection connection, Mcp3208Channel channel) + { + return new Mcp3208InputAnalogPin(connection, channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208Channel.cs b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208Channel.cs new file mode 100644 index 0000000..b757889 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208Channel.cs @@ -0,0 +1,14 @@ +namespace Raspberry.IO.Components.Converters.Mcp3208 +{ + public enum Mcp3208Channel + { + Channel0 = 0, + Channel1 = 1, + Channel2 = 2, + Channel3 = 3, + Channel4 = 4, + Channel5 = 5, + Channel6 = 6, + Channel7 = 7 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208InputAnalogPin.cs b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208InputAnalogPin.cs new file mode 100644 index 0000000..1062325 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208InputAnalogPin.cs @@ -0,0 +1,47 @@ +namespace Raspberry.IO.Components.Converters.Mcp3208 +{ + public class Mcp3208InputAnalogPin : IInputAnalogPin + { + #region Fields + + private readonly Mcp3208SpiConnection connection; + private readonly Mcp3208Channel channel; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The channel. + public Mcp3208InputAnalogPin(Mcp3208SpiConnection connection, Mcp3208Channel channel) + { + this.connection = connection; + this.channel = channel; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the value of the pin. + /// + /// + /// The value. + /// + public AnalogValue Read() + { + return connection.Read(channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208SpiConnection.cs b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208SpiConnection.cs new file mode 100644 index 0000000..fe6b6f0 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp3208/Mcp3208SpiConnection.cs @@ -0,0 +1,87 @@ +#region References + +using System; +using Raspberry.IO.SerialPeripheralInterface; + +#endregion + +namespace Raspberry.IO.Components.Converters.Mcp3208 +{ + /// + /// Represents a connection to MCP3204/3208 ADC converter. + /// + /// + /// See specification at http://www.adafruit.com/datasheets/MCP3208.pdf + /// + public class Mcp3208SpiConnection : IDisposable + { + #region Fields + + private readonly SpiConnection spiConnection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The clock pin. + /// The slave select pin. + /// The miso pin. + /// The mosi pin. + public Mcp3208SpiConnection(IOutputBinaryPin clockPin, IOutputBinaryPin slaveSelectPin, IInputBinaryPin misoPin, IOutputBinaryPin mosiPin) + { + spiConnection = new SpiConnection(clockPin, slaveSelectPin, misoPin, mosiPin, Endianness.LittleEndian); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Reads the specified channel. + /// + /// The channel. + /// The value + public AnalogValue Read(Mcp3208Channel channel) + { + using (spiConnection.SelectSlave()) + { + // Start bit + spiConnection.Write(true); + + // Channel is single-ended + spiConnection.Write(true); + + spiConnection.Write((byte)channel, 3); + + // Let one clock to sample + spiConnection.Synchronize(); + + // Read 12 bits + var data = (int)spiConnection.Read(12); + + return new AnalogValue(data, 0xFFF); + } + } + + /// + /// Closes this instance. + /// + public void Close() + { + spiConnection.Close(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822AnalogPinExtensionMethods.cs b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822AnalogPinExtensionMethods.cs new file mode 100644 index 0000000..7bab568 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822AnalogPinExtensionMethods.cs @@ -0,0 +1,21 @@ +namespace Raspberry.IO.Components.Converters.Mcp4822 +{ + public static class Mcp4822AnalogPinExtensionMethods + { + #region Methods + + /// + /// Creates an output analog pin. + /// + /// The connection. + /// The channel. + /// The maximum. + /// The pin. + public static Mcp4822OutputAnalogPin Out(this Mcp4822SpiConnection connection, Mcp4822Channel channel) + { + return new Mcp4822OutputAnalogPin(connection, channel); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822Channel.cs b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822Channel.cs new file mode 100644 index 0000000..fd94a65 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822Channel.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Converters.Mcp4822 +{ + public enum Mcp4822Channel + { + ChannelA = 0, + ChannelB = 1 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822OutputAnalogPin.cs b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822OutputAnalogPin.cs new file mode 100644 index 0000000..2e94047 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822OutputAnalogPin.cs @@ -0,0 +1,47 @@ +namespace Raspberry.IO.Components.Converters.Mcp4822 +{ + public class Mcp4822OutputAnalogPin : IOutputAnalogPin + { + #region Fields + + private readonly Mcp4822SpiConnection connection; + private readonly Mcp4822Channel channel; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The channel. + public Mcp4822OutputAnalogPin(Mcp4822SpiConnection connection, Mcp4822Channel channel) + { + this.connection = connection; + this.channel = channel; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + } + + #endregion + + #region Methods + + /// + /// Writes the specified value to the pin. + /// + /// The value. + public void Write(AnalogValue value) + { + connection.Write(channel, value); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822SpiConnection.cs b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822SpiConnection.cs new file mode 100644 index 0000000..b8598c0 --- /dev/null +++ b/Raspberry.IO.Components/Converters/Mcp4822/Mcp4822SpiConnection.cs @@ -0,0 +1,87 @@ +#region References + +using System; +using Raspberry.IO.SerialPeripheralInterface; + +#endregion + +namespace Raspberry.IO.Components.Converters.Mcp4822 +{ + /// + /// Represents a SPI connection to a MCP4802/4812/4822 DAC. + /// + /// See http://ww1.microchip.com/downloads/en/DeviceDoc/22249A.pdf for specifications. + public class Mcp4822SpiConnection : IDisposable + { + #region Fields + + private readonly SpiConnection spiConnection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The clock pin. + /// The slave select pin. + /// The mosi pin. + public Mcp4822SpiConnection(IOutputBinaryPin clockPin, IOutputBinaryPin slaveSelectPin, IOutputBinaryPin mosiPin) + { + spiConnection = new SpiConnection(clockPin, slaveSelectPin, null, mosiPin, Endianness.LittleEndian); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Closes this instance. + /// + public void Close() + { + spiConnection.Close(); + } + + /// + /// Writes the specified data. + /// + /// The channel. + /// The data. + public void Write(Mcp4822Channel channel, AnalogValue data) + { + using (spiConnection.SelectSlave()) + { + var value = (uint)(data.Relative * 0xFFF); + if (value > 0xFFF) + value = 0xFFF; + + // Set active channel + spiConnection.Write(channel == Mcp4822Channel.ChannelB); + + // Ignored bit + spiConnection.Synchronize(); + + // Select 1x Gain + spiConnection.Write(true); + + // Active mode operation + spiConnection.Write(true); + + // Write 12 bits data (some lower bits are ignored by MCP4802/4812 + spiConnection.Write(value, 12); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedArgs.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedArgs.cs new file mode 100644 index 0000000..78dec43 --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedArgs.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + public class InputPinChangedArgs : EventArgs + { + public PiFaceInputPin pin; + } +} diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedHandler.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedHandler.cs new file mode 100644 index 0000000..977b2a1 --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/InputPinChangedHandler.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + /// + /// delgate for pin state changed events. The pin that changed is in the args + /// + /// + /// + public delegate void InputPinChangedHandler (object sender, InputPinChangedArgs e); + +} diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceDigitalDevice.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceDigitalDevice.cs new file mode 100644 index 0000000..a07e84d --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceDigitalDevice.cs @@ -0,0 +1,249 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Raspberry.IO.SerialPeripheralInterface; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + /// + /// Controls a PiFace Digital device on a Raspberry Pi. + /// Pins on the board have software counterparts to control or read state of the physical pins. + /// Polling is required for the input pin values as the PiFace Digital has no interrupt support. + /// + /// This driver uses a NativeSpiConnection which requires the native SPI driver be enabled + /// + public class PiFaceDigitalDevice : IDisposable + { + + #region Constants + + private const byte IOCON_SEQOP = 0x00; + private const byte All_Pins_Input = 0xFF; + private const byte All_Pins_Output = 0x00; + private const byte CMD_WRITE = 0x40; + private const byte CMD_READ = 0x41; + + #endregion + + #region Enums + + /// + /// Registers on the MCP23S17 chip + /// + internal enum mcp23s17Register + { + IODIRA = 0x00, + IODIRB = 0x01, + IOCON = 0x0A, + GPPUB = 0x0D, + GPIOA = 0x12, + GPIOB = 0x13 + }; + + #endregion + + #region Fields + + private string driverName; + + private INativeSpiConnection spiConnection = null; + + /// + /// Re-usable buffer for reading input pins state to reduce the polling overhead + /// + private ISpiTransferBuffer inputPollBuffer; + + /// + /// Last known state of the inputs, used to optimize detecting changes + /// + private byte CachedInputState; + + bool disposed = false; + + #endregion + + #region Instance Management + + /// + /// Create the device with a custom driver name + /// + /// name of the device driver e.g. /dev/... + public PiFaceDigitalDevice(string driverName = "/dev/spidev0.0") + { + this.driverName = driverName; + InitPiFace(); + } + + /// + /// Create the device injecting an SPI connection + /// + /// connection to the SPI driver. + public PiFaceDigitalDevice(INativeSpiConnection nativeSpiConnection) + { + spiConnection = nativeSpiConnection; + InitPiFace(); + } + + + public void Dispose() + { + // Dispose of unmanaged resources. + Dispose(true); + // Suppress finalization. + GC.SuppressFinalize(this); + } + + + // Protected implementation of Dispose pattern. + protected void Dispose(bool disposing) + { + if (disposed) + { + return; + } + + if (disposing) + { + spiConnection.Dispose(); + inputPollBuffer.Dispose(); + } + + disposed = true; + } + + #endregion + + #region Properties + + /// + /// Software proxy for the input pins of the PiFace Digital Board + /// + public PiFaceInputPin[] InputPins { get; private set; } + + /// + /// Software proxy for the output pins of the PiFace Digital Board + /// + public PiFaceOutputPin[] OutputPins { get; private set; } + + #endregion + + #region Methods + + /// + /// Set up the MCP23S17 for the PiFace Digital board + /// + private void InitPiFace() + { + InputPins = new PiFaceInputPin[8]; + for (int pinNo = 0; pinNo < 8; pinNo++) + { + InputPins[pinNo] = new PiFaceInputPin(pinNo); + } + + OutputPins = new PiFaceOutputPin[8]; + for (int pinNo = 0; pinNo < 8; pinNo++) + { + OutputPins[pinNo] = new PiFaceOutputPin(pinNo); + } + + if (spiConnection == null) + { + SpiConnectionSettings spiSettings = new SpiConnectionSettings { BitsPerWord = 8, Delay = 1, MaxSpeed = 5000000, Mode = SpiMode.Mode0 }; + spiConnection = new NativeSpiConnection(driverName, spiSettings); + } + + Write(mcp23s17Register.IOCON, IOCON_SEQOP); + SetAllOutputPins(0); + + // initialize output and input pins + Write(mcp23s17Register.IODIRA, All_Pins_Output); + Write(mcp23s17Register.IODIRB, All_Pins_Input); + + // set resistor on all input pins to pull up + Write(mcp23s17Register.GPPUB, 0xFF); + + // set outputs + UpdatePiFaceOutputPins(); + + // Create re-usable buffer for polling input pins + CreateReusableBufferForInputPolling(); + + // Get the initial software input pin state and compare to actual inputs + CachedInputState = PiFaceInputPin.AllPinState(InputPins); + PollInputPins(); + } + + /// + /// Sets up the transfer buffer for reading input pins; + /// + private void CreateReusableBufferForInputPolling() + { + inputPollBuffer = spiConnection.CreateTransferBuffer(3, SpiTransferMode.ReadWrite); + inputPollBuffer.Tx[0] = CMD_READ; + inputPollBuffer.Tx[1] = (byte)mcp23s17Register.GPIOB; + inputPollBuffer.Tx[2] = 0; + } + + private int Write(mcp23s17Register port, byte data) + { + ISpiTransferBuffer transferBuffer = spiConnection.CreateTransferBuffer(3, SpiTransferMode.Write); + transferBuffer.Tx[0] = CMD_WRITE; + transferBuffer.Tx[1] = (byte)port; + transferBuffer.Tx[2] = data; + var result = spiConnection.Transfer(transferBuffer); + return result; + } + + internal byte Read(mcp23s17Register port) + { + ISpiTransferBuffer transferBuffer = spiConnection.CreateTransferBuffer(3, SpiTransferMode.ReadWrite); + transferBuffer.Tx[0] = CMD_READ; + transferBuffer.Tx[1] = (byte)port; + transferBuffer.Tx[2] = 0; + var result = spiConnection.Transfer(transferBuffer); + return transferBuffer.Rx[2]; + } + + #endregion + + #region Public Methods + + /// + /// Configure the output pins with a byte that has one bit per pin and set the pins on the PiFaceDigital + /// + /// + public void SetAllOutputPins(byte allPinsState) + { + foreach (var oPin in OutputPins) + { + oPin.Update(allPinsState); + } + Write(mcp23s17Register.GPIOA, allPinsState); + } + + /// + /// Update PiFace board with the current vales of the software output pins + /// + public void UpdatePiFaceOutputPins() + { + Write(mcp23s17Register.GPIOA, PiFaceOutputPin.AllPinState(OutputPins)); + } + + /// + /// Read the state of the input pins. Will trigger any Onchanged events registered + /// + public void PollInputPins() + { + var result = spiConnection.Transfer(inputPollBuffer); + var state = inputPollBuffer.Rx[2]; + if (state != CachedInputState) + { + CachedInputState = state; + PiFaceInputPin.SetAllPinStates(InputPins, state); + } + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceInputPin.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceInputPin.cs new file mode 100644 index 0000000..8faa3d5 --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceInputPin.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + public class PiFaceInputPin : PiFacePin + { + + #region events + /// + /// Event fired when the state of a pin changes + /// + public event InputPinChangedHandler OnStateChanged; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// Number of the pin in the range 0 to 7 + internal PiFaceInputPin(int pinNumber) + : base(pinNumber) + { + } + + #endregion + + #region Properties + + /// + /// Indicates that sensor grounding the input pin is closed + /// + public bool IsGrounded { get { return !state; } } + + /// + /// Gets the state of the pin, note true indicates the pin is high i.e. open. + /// if this throws off your logic use the IsGrounded property instead. + /// + public bool State + { + get { return state; } + } + + #endregion + + #region Methods + + /// + /// Update the state of this pin based on a byte that contains the state of every pin + /// + /// byte with all pin values + internal override void Update(byte allPinState) + { + var oldState = state; + state = (allPinState & mask) > 0; + if (oldState != state && OnStateChanged != null) + { + OnStateChanged(this, new InputPinChangedArgs { pin = this }); + } + } + + /// + /// helper to set the state of every pin in a collection + /// + /// + /// + internal static void SetAllPinStates(IEnumerable inputPins, byte allPinState) + { + foreach (var inputPin in inputPins) + { + inputPin.Update(allPinState); + } + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceOutputPin.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceOutputPin.cs new file mode 100644 index 0000000..1540f9b --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFaceOutputPin.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + /// + /// Derivative of PiFacePin that allows setting of the internal state + /// + public class PiFaceOutputPin : PiFacePin + { + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// Number of the pin in the range 0 to 7 + internal PiFaceOutputPin(int pinNumber) + : base(pinNumber) + { + } + + #endregion + + #region Properties + + /// + /// Gets/Sets the state of the pin in software but does not update the PiFaceDigital device + /// Allows individual pins to be modified then everything updated with a call to UpdatePiFaceOutputPins + /// + public bool State + { + get { return state; } + set { state = value; } + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Devices/PiFaceDigital/PiFacePin.cs b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFacePin.cs new file mode 100644 index 0000000..8e6c41e --- /dev/null +++ b/Raspberry.IO.Components/Devices/PiFaceDigital/PiFacePin.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Devices.PiFaceDigital +{ + /// + /// Represents the pins on a PiFace Digital board + /// + public abstract class PiFacePin + { + + + #region Fields + + /// + /// bit mask that allows this pin to get / set it's value from a byte + /// + protected byte mask; + + /// + /// state of this pin + /// + protected bool state; + + #endregion + + + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// Number of the pin in the range 0 to 7 + internal PiFacePin(int pinNumber) + { + if (pinNumber < 0 || pinNumber > 7) + { + throw new ArgumentOutOfRangeException("pin numbers must be in the range 0 to 7"); + } + mask = (byte)(1 << pinNumber); + Id = pinNumber; + } + + #endregion + + #region Properties + + /// + /// Number of the pin 0..7 + /// + public int Id { get; private set; } + + #endregion + + #region Methods + + /// + /// Update this pin based on a byte that contains the data for every pin + /// + /// byte with all pin values + internal virtual void Update(byte allPinState) + { + state = (allPinState & mask) > 0; + } + + /// + /// Returns a byte representing the state of all pins + /// + /// collection of pins to aggregate over + /// byte of all pin state + internal static byte AllPinState(IEnumerable pins) + { + byte allPinState = 0; + foreach (var pin in pins) + { + if (pin.state) + { + allPinState = (byte)(allPinState | pin.mask); + } + } + return allPinState; + } + + + #endregion + } +} diff --git a/Raspberry.IO.Components/Displays/Hd44780/Command.cs b/Raspberry.IO.Components/Displays/Hd44780/Command.cs new file mode 100644 index 0000000..78f4f3e --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Command.cs @@ -0,0 +1,14 @@ +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + internal enum Command + { + ClearDisplay = 0x01, + ReturnHome = 0x02, + SetEntryModeFlags = 0x04, + SetDisplayFlags = 0x08, + MoveCursor = 0x10, + SetFunctions = 0x20, + SetCGRamAddr = 0x40, + //SetDDRamAddr = 0x80 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/CursorShiftFlags.cs b/Raspberry.IO.Components/Displays/Hd44780/CursorShiftFlags.cs new file mode 100644 index 0000000..fc793ba --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/CursorShiftFlags.cs @@ -0,0 +1,16 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + [Flags] + internal enum CursorShiftFlags + { + None = 0, + + CursorMove = 0, + MoveLeft = 0, + + MoveRight = 0x04, + DisplayMove = 0x08 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/DisplayFlags.cs b/Raspberry.IO.Components/Displays/Hd44780/DisplayFlags.cs new file mode 100644 index 0000000..4118426 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/DisplayFlags.cs @@ -0,0 +1,18 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + [Flags] + internal enum DisplayFlags + { + None = 0, + + BlinkOff = 0, + CursorOff = 0, + DisplayOff = 0, + + BlinkOn = 0x01, + CursorOn = 0x02, + DisplayOn = 0x04 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/EntryModeFlags.cs b/Raspberry.IO.Components/Displays/Hd44780/EntryModeFlags.cs new file mode 100644 index 0000000..be2bd9f --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/EntryModeFlags.cs @@ -0,0 +1,16 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + [Flags] + internal enum EntryModeFlags + { + None = 0, + + EntryRight = 0, + EntryShiftDecrement = 0, + + EntryShiftIncrement = 0x01, + EntryLeft = 0x02 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Functions.cs b/Raspberry.IO.Components/Displays/Hd44780/Functions.cs new file mode 100644 index 0000000..a8e1b45 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Functions.cs @@ -0,0 +1,19 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + [Flags] + internal enum Functions + { + None = 0, + + Matrix5x8 = 0, + Matrix5x10 = 0x04, + + OneLine = 0, + TwoLines = 0x08, + + Data4bits = 0x0, + Data8bits = 0x10, + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Hd44780A00Encoding.cs b/Raspberry.IO.Components/Displays/Hd44780/Hd44780A00Encoding.cs new file mode 100644 index 0000000..2d70bc5 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Hd44780A00Encoding.cs @@ -0,0 +1,627 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +#endregion + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + /// + /// Represents encoding for HD44780 LCD with Japanese character set (ROM code A00) + /// Based on http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf + /// And http://en.wikipedia.org/wiki/Katakana + /// + public class Hd44780A00Encoding : Encoding + { + #region Fields + + private static readonly Dictionary charMap = GetMap().GroupBy(p => p.Key, p => p.Value).ToDictionary(g => g.Key, g => g.First()); + private static readonly Dictionary byteMap = GetMap().GroupBy(p => p.Value, p => p.Key).ToDictionary(g => g.Key, g => g.First()); + + private const byte missingChar = 0x3F; + private const char missingByte = '\uFFFD'; + + #endregion + + #region Properties + + /// + /// Gets the supported characters. + /// + public static IEnumerable SupportedCharacters + { + get { return charMap.Keys.Except(new[]{'\r', '\n'}); } + } + + #endregion + + #region Methods + + /// + /// When overridden in a derived class, calculates the number of bytes produced by encoding a set of characters from the specified character array. + /// + /// The character array containing the set of characters to encode. + /// The index of the first character to encode. + /// The number of characters to encode. + /// + /// The number of bytes produced by encoding the specified characters. + /// + public override int GetByteCount(char[] chars, int index, int count) + { + return count; + } + + /// + /// When overridden in a derived class, encodes a set of characters from the specified character array into the specified byte array. + /// + /// The character array containing the set of characters to encode. + /// The index of the first character to encode. + /// The number of characters to encode. + /// The byte array to contain the resulting sequence of bytes. + /// The index at which to start writing the resulting sequence of bytes. + /// + /// The actual number of bytes written into . + /// + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + Array.Copy( + chars + .Skip(charIndex) + .Take(charCount) + .Select(c => + { + byte b; + return charMap.TryGetValue(c, out b) ? b : missingChar; + }) + .ToArray(), + 0, + bytes, + byteIndex, + charCount); + + return charCount; + } + + /// + /// When overridden in a derived class, calculates the number of characters produced by decoding a sequence of bytes from the specified byte array. + /// + /// The byte array containing the sequence of bytes to decode. + /// The index of the first byte to decode. + /// The number of bytes to decode. + /// + /// The number of characters produced by decoding the specified sequence of bytes. + /// + public override int GetCharCount(byte[] bytes, int index, int count) + { + return count; + } + + /// + /// When overridden in a derived class, decodes a sequence of bytes from the specified byte array into the specified character array. + /// + /// The byte array containing the sequence of bytes to decode. + /// The index of the first byte to decode. + /// The number of bytes to decode. + /// The character array to contain the resulting set of characters. + /// The index at which to start writing the resulting set of characters. + /// + /// The actual number of characters written into . + /// + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + Array.Copy( + bytes + .Skip(byteIndex) + .Take(byteCount) + .Select(b => + { + char c; + return byteMap.TryGetValue(b, out c) ? c : missingByte; + }) + .ToArray(), + 0, + chars, + charIndex, + byteCount); + + return byteCount; + } + + /// + /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. + /// + /// The number of characters to encode. + /// + /// The maximum number of bytes produced by encoding the specified number of characters. + /// + public override int GetMaxByteCount(int charCount) + { + return charCount; + } + + /// + /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. + /// + /// The number of bytes to decode. + /// + /// The maximum number of characters produced by decoding the specified number of bytes. + /// + public override int GetMaxCharCount(int byteCount) + { + return byteCount; + } + + #endregion + + #region Private Helpers + + private static IEnumerable> GetMap() + { + // CR/LF + yield return new KeyValuePair('\u000A', 0x0A); + yield return new KeyValuePair('\u000D', 0x0A); + + // Custom characters + yield return new KeyValuePair('\u0000', 0x00); + yield return new KeyValuePair('\u0001', 0x01); + yield return new KeyValuePair('\u0002', 0x02); + yield return new KeyValuePair('\u0003', 0x03); + yield return new KeyValuePair('\u0004', 0x04); + yield return new KeyValuePair('\u0005', 0x05); + yield return new KeyValuePair('\u0006', 0x06); + yield return new KeyValuePair('\u0007', 0x07); +/* + yield return new KeyValuePair(' ', 0x08); + yield return new KeyValuePair(' ', 0x09); + yield return new KeyValuePair(' ', 0x0A); + yield return new KeyValuePair(' ', 0x0B); + yield return new KeyValuePair(' ', 0x0C); + yield return new KeyValuePair(' ', 0x0D); + yield return new KeyValuePair(' ', 0x0E); + yield return new KeyValuePair(' ', 0x0F); + + yield return new KeyValuePair(' ', 0x10); + yield return new KeyValuePair(' ', 0x11); + yield return new KeyValuePair(' ', 0x12); + yield return new KeyValuePair(' ', 0x13); + yield return new KeyValuePair(' ', 0x14); + yield return new KeyValuePair(' ', 0x15); + yield return new KeyValuePair(' ', 0x16); + yield return new KeyValuePair(' ', 0x17); + yield return new KeyValuePair(' ', 0x18); + yield return new KeyValuePair(' ', 0x19); + yield return new KeyValuePair(' ', 0x1A); + yield return new KeyValuePair(' ', 0x1B); + yield return new KeyValuePair(' ', 0x1C); + yield return new KeyValuePair(' ', 0x1D); + yield return new KeyValuePair(' ', 0x1E); + yield return new KeyValuePair(' ', 0x1F); +*/ + yield return new KeyValuePair(' ', 0x20); + // Variants + yield return new KeyValuePair('\u0009', 0x20); + yield return new KeyValuePair('\u000B', 0x20); + yield return new KeyValuePair('\u000C', 0x20); + yield return new KeyValuePair('\u0085', 0x20); + yield return new KeyValuePair('\u00A0', 0x20); + yield return new KeyValuePair('\u1680', 0x20); + yield return new KeyValuePair('\u180E', 0x20); + yield return new KeyValuePair('\u2000', 0x20); + yield return new KeyValuePair('\u2001', 0x20); + yield return new KeyValuePair('\u2002', 0x20); + yield return new KeyValuePair('\u2003', 0x20); + yield return new KeyValuePair('\u2004', 0x20); + yield return new KeyValuePair('\u2005', 0x20); + yield return new KeyValuePair('\u2006', 0x20); + yield return new KeyValuePair('\u2007', 0x20); + yield return new KeyValuePair('\u2008', 0x20); + yield return new KeyValuePair('\u2009', 0x20); + yield return new KeyValuePair('\u200A', 0x20); + yield return new KeyValuePair('\u2028', 0x20); + yield return new KeyValuePair('\u2029', 0x20); + yield return new KeyValuePair('\u202F', 0x20); + yield return new KeyValuePair('\u205F', 0x20); + yield return new KeyValuePair('\u3000', 0x20); + + yield return new KeyValuePair('!', 0x21); + + yield return new KeyValuePair('"', 0x22); + //Variants + yield return new KeyValuePair('“', 0x22); + yield return new KeyValuePair('”', 0x22); + yield return new KeyValuePair('„', 0x22); + yield return new KeyValuePair('‟', 0x22); + + yield return new KeyValuePair('#', 0x23); + yield return new KeyValuePair('$', 0x24); + yield return new KeyValuePair('%', 0x25); + yield return new KeyValuePair('&', 0x26); + + yield return new KeyValuePair('\'', 0x27); + // Variants + yield return new KeyValuePair('‘', 0x2F); + yield return new KeyValuePair('’', 0x2F); + yield return new KeyValuePair('‛', 0x2F); + yield return new KeyValuePair('′', 0x2F); + + yield return new KeyValuePair('(', 0x28); + yield return new KeyValuePair(')', 0x29); + yield return new KeyValuePair('*', 0x2A); + yield return new KeyValuePair('+', 0x2B); + yield return new KeyValuePair(',', 0x2C); + + yield return new KeyValuePair('-', 0x2D); + // Variants + yield return new KeyValuePair('‐', 0x2D); + yield return new KeyValuePair('‒', 0x2D); + yield return new KeyValuePair('–', 0x2D); + yield return new KeyValuePair('—', 0x2D); + yield return new KeyValuePair('―', 0x2D); + + yield return new KeyValuePair('.', 0x2E); + + yield return new KeyValuePair('/', 0x2F); + // Variants + yield return new KeyValuePair('⁄', 0x2F); + + yield return new KeyValuePair('0', 0x30); + yield return new KeyValuePair('1', 0x31); + yield return new KeyValuePair('2', 0x32); + yield return new KeyValuePair('3', 0x33); + yield return new KeyValuePair('4', 0x34); + yield return new KeyValuePair('5', 0x35); + yield return new KeyValuePair('6', 0x36); + yield return new KeyValuePair('7', 0x37); + yield return new KeyValuePair('8', 0x38); + yield return new KeyValuePair('9', 0x39); + yield return new KeyValuePair(':', 0x3A); + yield return new KeyValuePair(';', 0x3B); + + yield return new KeyValuePair('<', 0x3C); + // Variant + yield return new KeyValuePair('‹', 0x3C); + + yield return new KeyValuePair('=', 0x3D); + // Variant + yield return new KeyValuePair('゠', 0x3D); + + yield return new KeyValuePair('>', 0x3E); + // Variant + yield return new KeyValuePair('›', 0x3E); + + yield return new KeyValuePair('?', 0x3F); + // Variant + yield return new KeyValuePair('¿', 0x3F); + + yield return new KeyValuePair('@', 0x40); + + yield return new KeyValuePair('A', 0x41); + // Variants + yield return new KeyValuePair('À', 0x41); + yield return new KeyValuePair('Á', 0x41); + yield return new KeyValuePair('Â', 0x41); + yield return new KeyValuePair('Ã', 0x41); + yield return new KeyValuePair('Ä', 0x41); + yield return new KeyValuePair('Å', 0x41); + + yield return new KeyValuePair('B', 0x42); + + yield return new KeyValuePair('C', 0x43); + // Variant + yield return new KeyValuePair('Ç', 0x43); + + yield return new KeyValuePair('D', 0x44); + + yield return new KeyValuePair('E', 0x45); + // Variants + yield return new KeyValuePair('È', 0x45); + yield return new KeyValuePair('É', 0x45); + yield return new KeyValuePair('Ê', 0x45); + yield return new KeyValuePair('Ë', 0x45); + + yield return new KeyValuePair('F', 0x46); + yield return new KeyValuePair('G', 0x47); + yield return new KeyValuePair('H', 0x48); + + yield return new KeyValuePair('I', 0x49); + // Variants + yield return new KeyValuePair('Ì', 0x49); + yield return new KeyValuePair('Í', 0x49); + yield return new KeyValuePair('Î', 0x49); + yield return new KeyValuePair('Ï', 0x49); + + yield return new KeyValuePair('J', 0x4A); + yield return new KeyValuePair('K', 0x4B); + yield return new KeyValuePair('L', 0x4C); + yield return new KeyValuePair('M', 0x4D); + + yield return new KeyValuePair('N', 0x4E); + // Variant + yield return new KeyValuePair('Ñ', 0x4E); + + yield return new KeyValuePair('O', 0x4F); + // Variants + yield return new KeyValuePair('Ò', 0x4F); + yield return new KeyValuePair('Ó', 0x4F); + yield return new KeyValuePair('Ô', 0x4F); + yield return new KeyValuePair('Õ', 0x4F); + yield return new KeyValuePair('Ö', 0x4F); + yield return new KeyValuePair('Ø', 0x4F); + + yield return new KeyValuePair('P', 0x50); + yield return new KeyValuePair('Q', 0x51); + yield return new KeyValuePair('R', 0x52); + yield return new KeyValuePair('S', 0x53); + yield return new KeyValuePair('T', 0x54); + + yield return new KeyValuePair('U', 0x55); + // Variants + yield return new KeyValuePair('Ù', 0x55); + yield return new KeyValuePair('Ú', 0x55); + yield return new KeyValuePair('Û', 0x55); + yield return new KeyValuePair('Ü', 0x55); + + yield return new KeyValuePair('V', 0x56); + yield return new KeyValuePair('W', 0x57); + yield return new KeyValuePair('X', 0x58); + + yield return new KeyValuePair('Y', 0x59); + // Variant + yield return new KeyValuePair('Ý', 0x59); + + yield return new KeyValuePair('Z', 0x5A); + yield return new KeyValuePair('[', 0x5B); + yield return new KeyValuePair('¥', 0x5C); + yield return new KeyValuePair(']', 0x5D); + yield return new KeyValuePair('^', 0x5E); + + yield return new KeyValuePair('_', 0x5F); + // Variant + yield return new KeyValuePair('‗', 0x5F); + + yield return new KeyValuePair('`', 0x60); + + yield return new KeyValuePair('a', 0x61); + // Variants + yield return new KeyValuePair('à', 0x61); + yield return new KeyValuePair('á', 0x61); + yield return new KeyValuePair('â', 0x61); + yield return new KeyValuePair('ã', 0x61); + yield return new KeyValuePair('å', 0x61); + + yield return new KeyValuePair('b', 0x62); + + yield return new KeyValuePair('c', 0x63); + // Variant + yield return new KeyValuePair('ç', 0x63); + + yield return new KeyValuePair('d', 0x64); + + yield return new KeyValuePair('e', 0x65); + // Variants + yield return new KeyValuePair('è', 0x65); + yield return new KeyValuePair('é', 0x65); + yield return new KeyValuePair('ê', 0x65); + yield return new KeyValuePair('ë', 0x65); + + yield return new KeyValuePair('f', 0x66); + yield return new KeyValuePair('g', 0x67); + yield return new KeyValuePair('h', 0x68); + + yield return new KeyValuePair('i', 0x69); + // Variants + yield return new KeyValuePair('ì', 0x69); + yield return new KeyValuePair('í', 0x69); + yield return new KeyValuePair('î', 0x69); + yield return new KeyValuePair('ï', 0x69); + + yield return new KeyValuePair('j', 0x6A); + yield return new KeyValuePair('k', 0x6B); + yield return new KeyValuePair('l', 0x6C); + yield return new KeyValuePair('m', 0x6D); + + yield return new KeyValuePair('n', 0x6E); + // Variant + yield return new KeyValuePair('ñ', 0x6E); + + yield return new KeyValuePair('o', 0x6F); + // Variants + yield return new KeyValuePair('ò', 0x6F); + yield return new KeyValuePair('ó', 0x6F); + yield return new KeyValuePair('ô', 0x6F); + yield return new KeyValuePair('õ', 0x6F); + yield return new KeyValuePair('ö', 0x6F); + yield return new KeyValuePair('ø', 0x6F); + + yield return new KeyValuePair('p', 0x70); + yield return new KeyValuePair('q', 0x71); + yield return new KeyValuePair('r', 0x72); + yield return new KeyValuePair('s', 0x73); + yield return new KeyValuePair('t', 0x74); + + yield return new KeyValuePair('u', 0x75); + // Variants + yield return new KeyValuePair('ù', 0x75); + yield return new KeyValuePair('ú', 0x75); + yield return new KeyValuePair('û', 0x75); + yield return new KeyValuePair('ü', 0x75); + + yield return new KeyValuePair('v', 0x76); + yield return new KeyValuePair('w', 0x77); + yield return new KeyValuePair('x', 0x78); + + yield return new KeyValuePair('y', 0x79); + // Variants + yield return new KeyValuePair('ý', 0x79); + yield return new KeyValuePair('ÿ', 0x79); + + yield return new KeyValuePair('z', 0x7A); + yield return new KeyValuePair('{', 0x7B); + yield return new KeyValuePair('|', 0x7C); + yield return new KeyValuePair('}', 0x7D); + yield return new KeyValuePair('→', 0x7E); + yield return new KeyValuePair('←', 0x7F); + + yield return new KeyValuePair(' ', 0x80); + yield return new KeyValuePair(' ', 0x81); + yield return new KeyValuePair(' ', 0x82); + yield return new KeyValuePair(' ', 0x83); + yield return new KeyValuePair(' ', 0x84); + yield return new KeyValuePair(' ', 0x85); + yield return new KeyValuePair(' ', 0x86); + yield return new KeyValuePair(' ', 0x87); + yield return new KeyValuePair(' ', 0x88); + yield return new KeyValuePair(' ', 0x89); + yield return new KeyValuePair(' ', 0x8A); + yield return new KeyValuePair(' ', 0x8B); + yield return new KeyValuePair(' ', 0x8C); + yield return new KeyValuePair(' ', 0x8D); + yield return new KeyValuePair(' ', 0x8E); + yield return new KeyValuePair(' ', 0x8F); + + yield return new KeyValuePair(' ', 0x90); + yield return new KeyValuePair(' ', 0x91); + yield return new KeyValuePair(' ', 0x92); + yield return new KeyValuePair(' ', 0x93); + yield return new KeyValuePair(' ', 0x94); + yield return new KeyValuePair(' ', 0x95); + yield return new KeyValuePair(' ', 0x96); + yield return new KeyValuePair(' ', 0x97); + yield return new KeyValuePair(' ', 0x98); + yield return new KeyValuePair(' ', 0x99); + yield return new KeyValuePair(' ', 0x9A); + yield return new KeyValuePair(' ', 0x9B); + yield return new KeyValuePair(' ', 0x9C); + yield return new KeyValuePair(' ', 0x9D); + yield return new KeyValuePair(' ', 0x9E); + yield return new KeyValuePair(' ', 0x9F); + + yield return new KeyValuePair(' ', 0xA0); + yield return new KeyValuePair('▫', 0xA1); +// yield return new KeyValuePair('', 0xA2); +// yield return new KeyValuePair('', 0xA3); + yield return new KeyValuePair('ヽ', 0xA4); + yield return new KeyValuePair('・', 0xA5); + yield return new KeyValuePair('ヲ', 0xA6); + yield return new KeyValuePair('ァ', 0xA7); + yield return new KeyValuePair('ィ', 0xA8); + yield return new KeyValuePair('ゥ', 0xA9); + yield return new KeyValuePair('ェ', 0xAA); + yield return new KeyValuePair('ォ', 0xAB); + yield return new KeyValuePair('ャ', 0xAC); + yield return new KeyValuePair('ュ', 0xAD); + yield return new KeyValuePair('ョ', 0xAE); + yield return new KeyValuePair('ッ', 0xAF); + + yield return new KeyValuePair('ー', 0xB0); + yield return new KeyValuePair('ア', 0xB1); + yield return new KeyValuePair('イ', 0xB2); + yield return new KeyValuePair('ウ', 0xB3); + yield return new KeyValuePair('エ', 0xB4); + yield return new KeyValuePair('オ', 0xB5); + yield return new KeyValuePair('カ', 0xB6); + yield return new KeyValuePair('キ', 0xB7); + yield return new KeyValuePair('ク', 0xB8); + yield return new KeyValuePair('ケ', 0xB9); + yield return new KeyValuePair('コ', 0xBA); + yield return new KeyValuePair('サ', 0xBB); + yield return new KeyValuePair('シ', 0xBC); + yield return new KeyValuePair('ス', 0xBD); + yield return new KeyValuePair('セ', 0xBE); + yield return new KeyValuePair('ソ', 0xBF); + + yield return new KeyValuePair('タ', 0xC0); + yield return new KeyValuePair('チ', 0xC1); + yield return new KeyValuePair('ツ', 0xC2); + yield return new KeyValuePair('テ', 0xC3); + yield return new KeyValuePair('ト', 0xC4); + yield return new KeyValuePair('ナ', 0xC5); + yield return new KeyValuePair('ニ', 0xC6); + yield return new KeyValuePair('ヌ', 0xC7); + yield return new KeyValuePair('ネ', 0xC8); + yield return new KeyValuePair('ノ', 0xC9); + yield return new KeyValuePair('ハ', 0xCA); + yield return new KeyValuePair('ヒ', 0xCB); + yield return new KeyValuePair('フ', 0xCC); + yield return new KeyValuePair('ヘ', 0xCD); + yield return new KeyValuePair('ホ', 0xCE); + yield return new KeyValuePair('マ', 0xCF); + + yield return new KeyValuePair('ミ', 0xD0); + yield return new KeyValuePair('ム', 0xD1); + yield return new KeyValuePair('メ', 0xD2); + yield return new KeyValuePair('モ', 0xD3); + yield return new KeyValuePair('ヤ', 0xD4); + yield return new KeyValuePair('ユ', 0xD5); + yield return new KeyValuePair('ヨ', 0xD6); + yield return new KeyValuePair('ラ', 0xD7); + yield return new KeyValuePair('リ', 0xD8); + yield return new KeyValuePair('ル', 0xD9); + yield return new KeyValuePair('レ', 0xDA); + yield return new KeyValuePair('ロ', 0xDB); + yield return new KeyValuePair('ワ', 0xDC); + yield return new KeyValuePair('ン', 0xDD); + + yield return new KeyValuePair('゛', 0xDE); + + yield return new KeyValuePair('゜', 0xDF); + // Variant + yield return new KeyValuePair('°', 0xDF); + + yield return new KeyValuePair('α', 0xE0); + + yield return new KeyValuePair('ä', 0xE1); + // Variant + yield return new KeyValuePair('ӓ', 0xE1); + + yield return new KeyValuePair('β', 0xE2); + // Variant + yield return new KeyValuePair('ß', 0xE2); + + yield return new KeyValuePair('ε', 0xE3); + yield return new KeyValuePair('μ', 0xE4); + yield return new KeyValuePair('σ', 0xE5); + yield return new KeyValuePair('ρ', 0xE6); + yield return new KeyValuePair('ɡ', 0xE7); + yield return new KeyValuePair('√', 0xE8); +// yield return new KeyValuePair('', 0xE9); + yield return new KeyValuePair('ј', 0xEA); + yield return new KeyValuePair('\u033D', 0xEB); + + yield return new KeyValuePair('¢', 0xEC); + // Variants + yield return new KeyValuePair('\u023B', 0xEC); + yield return new KeyValuePair('¢', 0xEC); + +// yield return new KeyValuePair('', 0xED); + yield return new KeyValuePair('ñ', 0xEE); + yield return new KeyValuePair('ö', 0xEF); + + yield return new KeyValuePair('ρ', 0xF0); +// yield return new KeyValuePair('', 0xF1); + yield return new KeyValuePair('θ', 0xF2); + yield return new KeyValuePair('∞', 0xF3); + yield return new KeyValuePair('Ω', 0xF4); + yield return new KeyValuePair('ü', 0xF5); + yield return new KeyValuePair('Σ', 0xF6); + yield return new KeyValuePair('π', 0xF7); +// yield return new KeyValuePair('', 0xF8); + + yield return new KeyValuePair('У', 0xF9); + // Variant + yield return new KeyValuePair('у', 0xF9); + +// yield return new KeyValuePair('', 0xFA); +// yield return new KeyValuePair('', 0xFB); +// yield return new KeyValuePair('', 0xFC); + yield return new KeyValuePair('÷', 0xFD); + yield return new KeyValuePair(' ', 0xFE); + yield return new KeyValuePair('█', 0xFF); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnection.cs b/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnection.cs new file mode 100644 index 0000000..405e7b7 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnection.cs @@ -0,0 +1,529 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Text; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + /// + /// Based on https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_CharLCD/Adafruit_CharLCD.py + /// and http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf + /// and http://www.quinapalus.com/hd44780udg.html + /// and http://robo.fe.uni-lj.si/~kamnikr/sola/urac/vaja3_display/How%20to%20control%20HD44780%20display.pdf + /// and http://web.stanford.edu/class/ee281/handouts/lcd_tutorial.pdf + /// and http://www.systronix.com/access/Systronix_20x4_lcd_brief_data.pdf + /// + public class Hd44780LcdConnection : IDisposable + { + #region Fields + + private const int MAX_HEIGHT = 4; // Allow for larger displays + private const int MAX_CHAR = 80; // This allows for setups such as 40x2 or a 20x4 + + private readonly Hd44780Pins pins; + + private readonly int width; + private readonly int height; + + private readonly Functions functions; + private readonly Encoding encoding; + private readonly EntryModeFlags entryModeFlags; + + private DisplayFlags displayFlags = DisplayFlags.DisplayOn | DisplayFlags.BlinkOff | DisplayFlags.CursorOff; + private Hd44780Position currentPosition; + + private bool backlightEnabled; + + private static readonly TimeSpan syncDelay = TimeSpanUtility.FromMicroseconds(1); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The register select pin. + /// The clock pin. + /// The data pins. + public Hd44780LcdConnection(IOutputBinaryPin registerSelectPin, IOutputBinaryPin clockPin, params IOutputBinaryPin[] dataPins) : this(null, new Hd44780Pins(registerSelectPin, clockPin, dataPins)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The register select pin. + /// The clock pin. + /// The data pins. + public Hd44780LcdConnection(IOutputBinaryPin registerSelectPin, IOutputBinaryPin clockPin, IEnumerable dataPins) : this(null, new Hd44780Pins(registerSelectPin, clockPin, dataPins)) { } + + /// + /// Initializes a new instance of the class. + /// + /// The settings. + /// The pins. + /// + /// dataPins;There must be either 4 or 8 data pins + /// or + /// settings;ScreenHeight must be between 1 and 4 rows + /// or + /// settings;PatternWidth must be 5 pixels + /// or + /// settings;PatternWidth must be either 7 or 10 pixels height + /// + /// + /// At most 80 characters are allowed + /// or + /// 10 pixels height pattern cannot be used with 2 rows + /// + public Hd44780LcdConnection(Hd44780LcdConnectionSettings settings, Hd44780Pins pins) + { + settings = settings ?? new Hd44780LcdConnectionSettings(); + this.pins = pins; + + if (pins.Data.Length != 4 && pins.Data.Length != 8) + throw new ArgumentOutOfRangeException("pins", pins.Data.Length, "There must be either 4 or 8 data pins"); + + width = settings.ScreenWidth; + height = settings.ScreenHeight; + if (height < 1 || height > MAX_HEIGHT) + throw new ArgumentOutOfRangeException("settings", height, "ScreenHeight must be between 1 and 4 rows"); + if (width * height > MAX_CHAR) + throw new ArgumentException("At most 80 characters are allowed"); + + if (settings.PatternWidth != 5) + throw new ArgumentOutOfRangeException("settings", settings.PatternWidth, "PatternWidth must be 5 pixels"); + if (settings.PatternHeight != 8 && settings.PatternHeight != 10) + throw new ArgumentOutOfRangeException("settings", settings.PatternWidth, "PatternWidth must be either 7 or 10 pixels height"); + if (settings.PatternHeight == 10 && (height % 2) == 0) + throw new ArgumentException("10 pixels height pattern cannot be used with an even number of rows"); + + functions = (settings.PatternHeight == 8 ? Functions.Matrix5x8 : Functions.Matrix5x10) + | (height == 1 ? Functions.OneLine : Functions.TwoLines) + | (pins.Data.Length == 4 ? Functions.Data4bits : Functions.Data8bits); + + entryModeFlags = /*settings.RightToLeft + ? EntryModeFlags.EntryRight | EntryModeFlags.EntryShiftDecrement + :*/ EntryModeFlags.EntryLeft | EntryModeFlags.EntryShiftDecrement; + + encoding = settings.Encoding; + + BacklightEnabled = false; + + if (pins.ReadWrite != null) + pins.ReadWrite.Write(false); + + pins.RegisterSelect.Write(false); + pins.Clock.Write(false); + foreach (var dataPin in pins.Data) + dataPin.Write(false); + + WriteByte(0x33, false); // Initialize + WriteByte(0x32, false); + + WriteCommand(Command.SetFunctions, (int) functions); + WriteCommand(Command.SetDisplayFlags, (int) displayFlags); + WriteCommand(Command.SetEntryModeFlags, (int) entryModeFlags); + + Clear(); + BacklightEnabled = true; + } + + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets a value indicating whether display is enabled. + /// + /// + /// true if display is enabled; otherwise, false. + /// + public bool DisplayEnabled + { + get { return (displayFlags & DisplayFlags.DisplayOn) == DisplayFlags.DisplayOn; } + set + { + if (value) + displayFlags |= DisplayFlags.DisplayOn; + else + displayFlags &= ~DisplayFlags.DisplayOn; + + WriteCommand(Command.SetDisplayFlags, (int) displayFlags); + } + } + + /// + /// Gets or sets a value indicating whether backlight is enabled. + /// + /// + /// true if backlight is enabled; otherwise, false. + /// + public bool BacklightEnabled + { + get { return backlightEnabled; } + set + { + if (pins.Backlight == null) + return; + + pins.Backlight.Write(value); + backlightEnabled = value; + } + } + + /// + /// Gets or sets a value indicating whether cursor is enabled. + /// + /// + /// true if cursor is enabled; otherwise, false. + /// + public bool CursorEnabled + { + get { return (displayFlags & DisplayFlags.CursorOn) == DisplayFlags.CursorOn; } + set + { + if (value) + displayFlags |= DisplayFlags.CursorOn; + else + displayFlags &= ~DisplayFlags.CursorOn; + + WriteCommand(Command.SetDisplayFlags, (int) displayFlags); + } + } + + /// + /// Gets or sets a value indicating whether cursor is blinking. + /// + /// + /// true if cursor is blinking; otherwise, false. + /// + public bool CursorBlinking + { + get { return (displayFlags & DisplayFlags.BlinkOn) == DisplayFlags.BlinkOn; } + set + { + if (value) + displayFlags |= DisplayFlags.BlinkOn; + else + displayFlags &= ~DisplayFlags.BlinkOn; + + WriteCommand(Command.SetDisplayFlags, (int) displayFlags); + } + } + + #endregion + + #region Methods + + /// + /// Closes this instance. + /// + public void Close() + { + Clear(); + pins.Close(); + } + + /// + /// Set cursor to top left corner. + /// + public void Home() + { + WriteCommand(Command.ReturnHome); + currentPosition = Hd44780Position.Zero; + + Timer.Sleep(TimeSpan.FromMilliseconds(3)); + } + + /// + /// Clears the display. + /// + public void Clear() + { + WriteCommand(Command.ClearDisplay); + currentPosition = Hd44780Position.Zero; + + Timer.Sleep(TimeSpan.FromMilliseconds(3)); // Clearing the display takes a long time + } + + /// + /// Moves the cursor of the specified offset. + /// + /// The offset. + public void Move(int offset) + { + var column = currentPosition.Column += offset; + var row = currentPosition.Row; + + if (column >= width) + { + column = 0; + row++; + } + else if (column < 0) + { + column = width - 1; + row--; + } + + if (row >= height) + row = 0; + if (row < 0) + row = height - 1; + + SetCursorPosition(new Hd44780Position{Row = row, Column = column}); + } + + /// + /// Moves the cursor to the specified row and column + /// + /// The position. + public void SetCursorPosition(Hd44780Position position) + { + var row = position.Row; + if (row < 0 || height <= row) + row = height - 1; + + var column = position.Column; + if (column < 0 || width <= column) + column = width - 1; + + var address = column + GetLcdAddressLocation(row); + + WriteByte(address, false); + + currentPosition = new Hd44780Position { Row = row, Column = column }; + } + + /// + /// Sets the custom character. + /// + /// The character. + /// The pattern. + public void SetCustomCharacter(byte character, byte[] pattern) + { + if ((functions & Functions.Matrix5x8) == Functions.Matrix5x8) + Set5x8CustomCharacter(character, pattern); + else + Set5x10CustomCharacter(character, pattern); + } + + /// + /// Writes the line. + /// + /// The value. + /// The animation delay. + public void WriteLine(object value, TimeSpan animationDelay = new TimeSpan()) + { + WriteLine("{0}", value, animationDelay); + } + + /// + /// Writes the line. + /// + /// The text. + /// The animation delay. + public void WriteLine(string text, TimeSpan animationDelay = new TimeSpan()) + { + Write(text + Environment.NewLine, animationDelay); + } + + /// + /// Writes the specified value. + /// + /// The value. + /// The animation delay. + public void Write(object value, TimeSpan animationDelay = new TimeSpan()) + { + Write("{0}", value, animationDelay); + } + + /// + /// Writes the line. + /// + /// The format. + /// The values. + public void WriteLine(string format, params object[] values) + { + WriteLine(string.Format(format, values)); + } + + /// + /// Writes the specified format. + /// + /// The format. + /// The values. + public void Write(string format, params object[] values) + { + Write(string.Format(format, values)); + } + + /// + /// Writes the line. + /// + /// The format. + /// The animation delay. + /// The values. + public void WriteLine(string format, TimeSpan animationDelay, params object[] values) + { + WriteLine(string.Format(format, values), animationDelay); + } + + /// + /// Writes the specified format. + /// + /// The format. + /// The animation delay. + /// The values. + public void Write(string format, TimeSpan animationDelay, params object[] values) + { + Write(string.Format(format, values), animationDelay); + } + + /// + /// Writes the specified text. + /// + /// The text. + /// The animation delay. + public void Write(string text, TimeSpan animationDelay = new TimeSpan()) + { + var lines = text.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + + foreach (var line in lines) + { + if (string.IsNullOrEmpty(line)) + continue; + + //Console.WriteLine(line); + var bytes = encoding.GetBytes(line); + foreach (var b in bytes) + { + if (currentPosition.Column < width) + WriteByte(b, true); + + if (animationDelay > TimeSpan.Zero) + Timer.Sleep(animationDelay); + + currentPosition.Column++; + } + + if ((currentPosition.Row == 0 || (currentPosition.Row + 1) % height != 0) && height > 1) + { + var addressLocation = GetLcdAddressLocation(currentPosition.Row + 1); + + WriteByte(addressLocation, false); + currentPosition.Column = 0; + currentPosition.Row++; + } + else + { + Home(); // This was added to return home when the maximum number of row's has been achieved. + break; + } + } + } + + #endregion + + #region Private Helpers + + private void WriteCommand(Command command, int parameter = 0) + { + var bits = (int) command | parameter; + WriteByte(bits, false); + } + + private void Set5x10CustomCharacter(byte character, byte[] pattern) + { + if (character > 7 || (character & 0x1) != 0x1) + throw new ArgumentOutOfRangeException("character", character, "character must be lower or equal to 7, and not an odd number"); + if (pattern.Length != 10) + throw new ArgumentOutOfRangeException("pattern", pattern, "pattern must be 10 rows long"); + + WriteCommand(Command.SetCGRamAddr, character << 3); + for (var i = 0; i < 10; i++) + WriteByte(pattern[i], true); + WriteByte(0, true); + } + + private void Set5x8CustomCharacter(byte character, byte[] pattern) + { + if (character > 7) + throw new ArgumentOutOfRangeException("character", character, "character must be lower or equal to 7"); + if (pattern.Length != 7) + throw new ArgumentOutOfRangeException("pattern", pattern, "pattern must be 7 rows long"); + + WriteCommand(Command.SetCGRamAddr, character << 3); + for (var i = 0; i < 7; i++) + WriteByte(pattern[i], true); + WriteByte(0, true); + } + + private void WriteByte(int bits, bool charMode) + { + if (pins.Data.Length == 4) + WriteByte4Pins(bits, charMode); + else + throw new NotImplementedException("8 bits mode is currently not implemented"); + } + + private void WriteByte4Pins(int bits, bool charMode) + { + pins.RegisterSelect.Write(charMode); + + pins.Data[0].Write((bits & 0x10) != 0); + pins.Data[1].Write((bits & 0x20) != 0); + pins.Data[2].Write((bits & 0x40) != 0); + pins.Data[3].Write((bits & 0x80) != 0); + + Synchronize(); + + pins.Data[0].Write((bits & 0x01) != 0); + pins.Data[1].Write((bits & 0x02) != 0); + pins.Data[2].Write((bits & 0x04) != 0); + pins.Data[3].Write((bits & 0x08) != 0); + + Synchronize(); + } + + /// + /// Returns the Lcd Address for the given row + /// + /// A zero based row position + /// The Lcd Address as an int + /// http://www.mikroe.com/forum/viewtopic.php?t=5149 + private int GetLcdAddressLocation(int row) + { + const int baseAddress = 128; + + switch (row) + { + case 0: return baseAddress; + case 1: return (baseAddress + 64); + case 2: return (baseAddress + width); + case 3: return (baseAddress + 64 + width); + default: return baseAddress; + } + } + + private void Synchronize() + { + pins.Clock.Write(true); + Timer.Sleep(syncDelay); // 1 microsecond pause - enable pulse must be > 450ns + + pins.Clock.Write(false); + Timer.Sleep(syncDelay); // commands need > 37us to settle + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnectionSettings.cs b/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnectionSettings.cs new file mode 100644 index 0000000..90fde16 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Hd44780LcdConnectionSettings.cs @@ -0,0 +1,71 @@ +using System.Text; + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + public class Hd44780LcdConnectionSettings + { + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public Hd44780LcdConnectionSettings() + { + ScreenWidth = 20; + ScreenHeight = 2; + PatternWidth = 5; + PatternHeight = 8; + + Encoding = new Hd44780A00Encoding(); + //RightToLeft = false; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the width of the screen. + /// + /// + /// The width of the screen. + /// + public int ScreenWidth { get; set; } + + /// + /// Gets or sets the height of the screen. + /// + /// + /// The height of the screen. + /// + public int ScreenHeight { get; set; } + + /// + /// Gets or sets the width of the pattern. + /// + /// + /// The width of the pattern. + /// + public int PatternWidth { get; set; } + + /// + /// Gets or sets the height of the pattern. + /// + /// + /// The height of the pattern. + /// + public int PatternHeight { get; set; } + + /// + /// Gets or sets the encoding. + /// + /// + /// The encoding. + /// + public Encoding Encoding { get; set; } + + //public bool RightToLeft { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Hd44780Pins.cs b/Raspberry.IO.Components/Displays/Hd44780/Hd44780Pins.cs new file mode 100644 index 0000000..fdc9716 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Hd44780Pins.cs @@ -0,0 +1,104 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Linq; + +#endregion + +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + /// + /// Represents the pins of a HD44780 LCD display. + /// + public class Hd44780Pins : IDisposable + { + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The register select. + /// The clock. + /// The data. + public Hd44780Pins(IOutputBinaryPin registerSelect, IOutputBinaryPin clock, IEnumerable data) + : this(registerSelect, clock, data.ToArray()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The register select. + /// The clock. + /// The data. + public Hd44780Pins(IOutputBinaryPin registerSelect, IOutputBinaryPin clock, params IOutputBinaryPin[] data) + { + RegisterSelect = registerSelect; + Clock = clock; + Data = data; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Properties + + /// + /// The register select (RS) pin. + /// + public IOutputBinaryPin RegisterSelect { get; private set; } + + /// + /// The clock (EN) pin. + /// + public IOutputBinaryPin Clock { get; private set; } + + /// + /// The backlight pin. + /// + public IOutputBinaryPin Backlight; + + /// + /// The read write (RW) pin. + /// + public IOutputBinaryPin ReadWrite; + + /// + /// The data pins. + /// + public IOutputBinaryPin[] Data { get; private set; } + + #endregion + + #region Methods + + /// + /// Closes this instance. + /// + public void Close() + { + + RegisterSelect.Dispose(); + Clock.Dispose(); + + if (Backlight != null) + Backlight.Dispose(); + if (ReadWrite != null) + ReadWrite.Dispose(); + + foreach (var dataPin in Data) + dataPin.Dispose(); + } + + #endregion + + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Hd44780/Hd44780Position.cs b/Raspberry.IO.Components/Displays/Hd44780/Hd44780Position.cs new file mode 100644 index 0000000..0785985 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Hd44780/Hd44780Position.cs @@ -0,0 +1,13 @@ +namespace Raspberry.IO.Components.Displays.Hd44780 +{ + /// + /// Represents the position of the cursor on a Hd44780 display. + /// + public struct Hd44780Position + { + public int Row; + public int Column; + + public static Hd44780Position Zero = new Hd44780Position {Row = 0, Column = 0}; + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Sda5708/Sda5708Brightness.cs b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Brightness.cs new file mode 100644 index 0000000..8dbc18c --- /dev/null +++ b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Brightness.cs @@ -0,0 +1,17 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Sda5708 +{ + public enum Sda5708Brightness + { + Level100 = 0, + Level53 = 1, + Level40 = 2, + Level27 = 3, + Level20 = 4, + Level13 = 5, + Level6_6 = 6, + Level0 = 7 + } +} + diff --git a/Raspberry.IO.Components/Displays/Sda5708/Sda5708Connection.cs b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Connection.cs new file mode 100644 index 0000000..d8cc142 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Connection.cs @@ -0,0 +1,120 @@ +/* Sda5708Connection + * Parts of this code are ported from https://github.com/pimium/sda5708 + * which in turn used the information from http://www.sbprojects.com/knowledge/footprints/sda5708.php. + * The font is taken from http://sunge.awardspace.com/glcd-sd/node4.html + * with additional german characters added. */ +using System; +using System.IO; +using System.Text; +using System.Linq; +using System.Threading; +using Raspberry.IO; +using Raspberry.IO.GeneralPurpose; +using System.Collections.Generic; + +namespace Raspberry.IO.Components.Displays.Sda5708 +{ + public sealed class Sda5708Connection : IDisposable + { + private Sda5708Brightness _brightness = Sda5708Brightness.Level100; + + private readonly ProcessorPin _load; + private readonly ProcessorPin _data; + private readonly ProcessorPin _sdclk; + private readonly ProcessorPin _reset; + private readonly GpioConnection _baseConnection; + + public Sda5708Connection () : this( + ProcessorPin.Pin7, + ProcessorPin.Pin8, + ProcessorPin.Pin18, + ProcessorPin.Pin23) + { + } + + public Sda5708Connection (ProcessorPin load, ProcessorPin data, ProcessorPin sdclk, ProcessorPin reset) + { + this._load = load; + this._data = data; + this._sdclk = sdclk; + this._reset = reset; + + this._baseConnection = new GpioConnection ( + load.Output (), + data.Output (), + sdclk.Output (), + reset.Output ()); + + this._baseConnection [reset] = false; + this._baseConnection [reset] = false; + Thread.Sleep (50); + this._baseConnection [reset] = true; + + this.Clear(); + } + + public void SetBrightness(Sda5708Brightness brightness) + { + this._brightness = brightness; + this.Write(0xe0 | (int)brightness); + } + + public void Clear() + { + this.Write(0xc0 | (int)this._brightness); + } + + public void WriteString(string str) + { + var chars = str + .PadRight (8, ' ') + .Substring (0, 8); + + for(var i = 0; i < chars.Length; i++) + { + this.WriteChar (i, chars [i]); + } + } + + private void WriteChar(int position, char value) + { + this.Write(0xa0 + position); + + string[] pattern; + if (!Sda5708Font.Patterns.TryGetValue(value, out pattern)) + pattern = Sda5708Font.Patterns['?']; + + for(var i = 0; i < 7; i++) + { + this.Write (Convert.ToInt32 (pattern[i].Replace (' ', '0'), 2)); + } + } + + private void Write(int value) + { + this._baseConnection [this._sdclk] = false; + this._baseConnection [this._load] = false; + + for(var i = 8; i > 0; i--) + { + this._baseConnection[this._data] = (value & 0x1) == 0x1; + + this._baseConnection [this._sdclk] = true; + this._baseConnection [this._sdclk] = false; + + value = value >> 1; + } + + this._baseConnection [this._sdclk] = false; + this._baseConnection [this._load] = true; + } + + #region IDisposable implementation + public void Dispose () + { + this._baseConnection [this._reset] = false; + ((IDisposable)this._baseConnection).Dispose (); + } + #endregion + } +} diff --git a/Raspberry.IO.Components/Displays/Sda5708/Sda5708Font.cs b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Font.cs new file mode 100644 index 0000000..67444c5 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Sda5708/Sda5708Font.cs @@ -0,0 +1,825 @@ +using System; +using System.IO; +using System.Text; +using System.Linq; +using System.Threading; +using Raspberry.IO; +using Raspberry.IO.GeneralPurpose; +using System.Collections.Generic; + +namespace Raspberry.IO.Components.Displays.Sda5708 +{ + public sealed class Sda5708Font + { + public static readonly Dictionary Patterns = new Dictionary + { + { ' ', new[]{ " ", + " ", + " ", + " ", + " ", + " ", + " " }}, + + { '!', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " ", + " 1 " }}, + + { '"', new[]{ " 1 1 ", + " 1 1 ", + " 1 1 ", + " ", + " ", + " ", + " " }}, + + { '#', new[]{ " 1 1 ", + " 1 1 ", + "11111", + " 1 1 ", + "11111", + " 1 1 ", + " 1 1 " }}, + + { '$', new[]{ " 1 ", + " 1111", + "1 1 ", + " 111 ", + " 1 1", + "1111 ", + " 1 " }}, + + { '%', new[]{ "11 ", + "11 1", + " 1 ", + " 1 ", + " 1 ", + "1 11", + " 11" }}, + + { '&', new[]{ " 11 ", + "1 1 ", + "1 1 ", + " 1 ", + "1 1 1", + "1 1 ", + " 11 1" }}, + + { '\'', new[]{ " 11 ", + " 1 ", + " 1 ", + " ", + " ", + " ", + " " }}, + + { '(', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { ')', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { '*', new[]{ " ", + " 1 1 ", + " 1 ", + "11111", + " 1 ", + " 1 1 ", + " " }}, + + { '+', new[]{ " ", + " 1 ", + " 1 ", + "11111", + " 1 ", + " 1 ", + " " }}, + + { ',', new[]{ " ", + " ", + " ", + " ", + " 11 ", + " 1 ", + " 1 " }}, + + { '-', new[]{ " ", + " ", + " ", + "11111", + " ", + " ", + " " }}, + + { '.', new[]{ " ", + " ", + " ", + " ", + " ", + " 11 ", + " 11 " }}, + + { '/', new[]{ " ", + " 1", + " 1 ", + " 1 ", + " 1 ", + "1 ", + " " }}, + + { '0', new[]{ " 111 ", + "1 1", + "1 11", + "1 1 1", + "11 1", + "1 1", + " 111 " }}, + + { '1', new[]{ " 1 ", + " 11 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 111 " }}, + + { '2', new[]{ " 111 ", + "1 1", + " 1", + " 1 ", + " 1 ", + " 1 ", + "11111" }}, + + { '3', new[]{ "11111", + " 1 ", + " 1 ", + " 1 ", + " 1", + "1 1", + " 111 " }}, + + { '4', new[]{ " 1 ", + " 11 ", + " 1 1 ", + "1 1 ", + "11111", + " 1 ", + " 1 " }}, + + { '5', new[]{ "11111", + "1 ", + "1111 ", + " 1", + " 1", + "1 1", + " 111 " }}, + + { '6', new[]{ " 11 ", + " 1 ", + "1 ", + "1111 ", + "1 1", + "1 1", + " 111 " }}, + + { '7', new[]{ "11111", + " 1", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { '8', new[]{ " 111 ", + "1 1", + "1 1", + " 111 ", + "1 1", + "1 1", + " 111 " }}, + + { '9', new[]{ " 111 ", + "1 1", + "1 1", + " 1111", + " 1", + " 1 ", + " 11 " }}, + + { ':', new[]{ " ", + " 11 ", + " 11 ", + " ", + " 11 ", + " 11 ", + " " }}, + + { ';', new[]{ " ", + " 11 ", + " 11 ", + " ", + " 11 ", + " 1 ", + " 1 " }}, + + { '<', new[]{ " 1", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1" }}, + + { '=', new[]{ " ", + " ", + "11111", + " ", + "11111", + " ", + " " }}, + + { '>', new[]{ "1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + "1 " }}, + + { '?', new[]{ " 111 ", + "1 1", + " 1", + " 1 ", + " 1 ", + " ", + " 1 " }}, + + { '@', new[]{ " 111 ", + "1 1", + " 1", + " 11 1", + "1 1 1", + "1 1 1", + " 111 " }}, + + { 'A', new[]{ " 111 ", + "1 1", + "1 1", + "1 1", + "11111", + "1 1", + "1 1" }}, + + { 'Ä', new[]{ "1 1", + " ", + " 111 ", + "1 1", + "11111", + "1 1", + "1 1" }}, + + { 'B', new[]{ "1111 ", + "1 1", + "1 1", + "1111 ", + "1 1", + "1 1", + "1111 " }}, + + { 'C', new[]{ " 111 ", + "1 1", + "1 ", + "1 ", + "1 ", + "1 1", + " 111 " }}, + + { 'D', new[]{ "111 ", + "1 1 ", + "1 1", + "1 1", + "1 1", + "1 1 ", + "111 " }}, + + { 'E', new[]{ "11111", + "1 ", + "1 ", + "1111 ", + "1 ", + "1 ", + "11111" }}, + + { 'F', new[]{ "11111", + "1 ", + "1 ", + "111 ", + "1 ", + "1 ", + "1 " }}, + + { 'G', new[]{ " 111 ", + "1 1", + "1 ", + "1 ", + "1 11", + "1 1", + " 111 " }}, + + { 'H', new[]{ "1 1", + "1 1", + "1 1", + "11111", + "1 1", + "1 1", + "1 1" }}, + + { 'I', new[]{ " 111 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 111 " }}, + + { 'J', new[]{ " 111", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + "1 1 ", + " 11 " }}, + + { 'K', new[]{ "1 1", + "1 1 ", + "1 1 ", + "11 ", + "1 1 ", + "1 1 ", + "1 1" }}, + + { 'L', new[]{ "1 ", + "1 ", + "1 ", + "1 ", + "1 ", + "1 ", + "11111" }}, + + { 'M', new[]{ "1 1", + "11 11", + "1 1 1", + "1 1", + "1 1", + "1 1", + "1 1" }}, + + { 'N', new[]{ "1 1", + "1 1", + "11 1", + "1 1 1", + "1 11", + "1 1", + "1 1" }}, + + { 'O', new[]{ " 111 ", + "1 1", + "1 1", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'Ö', new[]{ "1 1", + " ", + " 111 ", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'P', new[]{ "1111 ", + "1 1", + "1 1", + "1111 ", + "1 ", + "1 ", + "1 " }}, + + { 'Q', new[]{ " 111 ", + "1 1", + "1 1", + "1 1", + "1 1 1", + "1 1 ", + " 11 1" }}, + + { 'R', new[]{ "1111 ", + "1 1", + "1 1", + "1111 ", + "1 1 ", + "1 1 ", + "1 1" }}, + + { 'S', new[]{ " 1111", + "1 ", + "1 ", + " 111 ", + " 1", + " 1", + "1111 " }}, + + { 'T', new[]{ "11111", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { 'U', new[]{ "1 1", + "1 1", + "1 1", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'Ü', new[]{ "1 1", + " ", + "1 1", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'V', new[]{ "1 1", + "1 1", + "1 1", + "1 1", + "1 1", + " 1 1 ", + " 1 " }}, + + { 'W', new[]{ "1 1", + "1 1", + "1 1", + "1 1 1", + "1 1 1", + "11 11", + "1 1" }}, + + { 'X', new[]{ "1 1", + "1 1", + " 1 1 ", + " 1 ", + " 1 1 ", + "1 1", + "1 1" }}, + + { 'Y', new[]{ "1 1", + "1 1", + " 1 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { 'Z', new[]{ "11111", + " 1", + " 1 ", + " 1 ", + " 1 ", + "1 ", + "11111" }}, + + { '[', new[]{ " 111", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 111" }}, + + { '\\', new[]{ " ", + "1 ", + " 1 ", + " 1 ", + " 1 ", + " 1", + " " }}, + + { ']', new[]{ "111 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + "111 " }}, + + { '^', new[]{ " 1 ", + " 1 1 ", + "1 1", + " ", + " ", + " ", + " " }}, + + { '_', new[]{ " ", + " ", + " ", + " ", + " ", + " ", + "11111" }}, + + { '`', new[]{ " 1 ", + " 1 ", + " 1 ", + " ", + " ", + " ", + " " }}, + + { 'a', new[]{ " ", + " ", + " 111 ", + " 1", + " 1111", + "1 1", + " 1111" }}, + + { 'ä', new[]{ " 1 1 ", + " ", + " 111 ", + " 1", + " 1111", + "1 1", + " 1111" }}, + + { 'b', new[]{ "1 ", + "1 ", + "1 11 ", + "11 1", + "1 1", + "1 1", + "1111 " }}, + + { 'c', new[]{ " ", + " ", + " 111 ", + "1 ", + "1 ", + "1 1", + " 111 " }}, + + { 'd', new[]{ " 1", + " 1", + " 11 1", + "1 11", + "1 1", + "1 1", + " 1111" }}, + + { 'e', new[]{ " ", + " ", + " 111 ", + "1 1", + "11111", + "1 ", + " 111 " }}, + + { 'f', new[]{ " 11 ", + " 1 1", + " 1 ", + "111 ", + " 1 ", + " 1 ", + " 1 " }}, + + { 'g', new[]{ " ", + " ", + " 1111", + "1 1", + " 1111", + " 1", + " 11 " }}, + + { 'h', new[]{ "1 ", + "1 ", + "1 11 ", + "11 1", + "1 1", + "1 1", + "1 1" }}, + + { 'i', new[]{ " 1 ", + " ", + " 11 ", + " 1 ", + " 1 ", + " 1 ", + " 111 " }}, + + { 'j', new[]{ " 1 ", + " ", + " 11 ", + " 1 ", + " 1 ", + "1 1 ", + " 11 " }}, + + { 'k', new[]{ " 1 ", + " 1 ", + " 1 1", + " 1 1 ", + " 11 ", + " 1 1 ", + " 1 1" }}, + + { 'l', new[]{ " 11 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 111 " }}, + + { 'm', new[]{ " ", + " ", + "11 1 ", + "1 1 1", + "1 1 1", + "1 1", + "1 1" }}, + + { 'n', new[]{ " ", + " ", + "1 11 ", + "11 1", + "1 1", + "1 1", + "1 1" }}, + + { 'o', new[]{ " ", + " ", + " 111 ", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'ö', new[]{ " 1 1 ", + " ", + " 111 ", + "1 1", + "1 1", + "1 1", + " 111 " }}, + + { 'p', new[]{ " ", + " ", + "1111 ", + "1 1", + "1111 ", + "1 ", + "1 " }}, + + { 'q', new[]{ " ", + " ", + " 11 1", + "1 11", + " 1111", + " 1", + " 1" }}, + + { 'r', new[]{ " ", + " ", + "1 11 ", + "11 1", + "1 ", + "1 ", + "1 " }}, + + { 's', new[]{ " ", + " ", + " 111 ", + "1 ", + " 111 ", + " 1", + "1111 " }}, + + { 't', new[]{ " 1 ", + " 1 ", + "111 ", + " 1 ", + " 1 ", + " 1 1", + " 11 " }}, + + { 'u', new[]{ " ", + " ", + "1 1", + "1 1", + "1 1", + "1 11", + " 11 1" }}, + + { 'ü', new[]{ " 1 1 ", + " ", + "1 1", + "1 1", + "1 1", + "1 11", + " 11 1" }}, + + { 'v', new[]{ " ", + " ", + "1 1", + "1 1", + "1 1", + " 1 1 ", + " 1 " }}, + + { 'w', new[]{ " ", + " ", + "1 1", + "1 1", + "1 1 1", + "1 1 1", + " 1 1 " }}, + + { 'x', new[]{ " ", + " ", + "1 1", + " 1 1 ", + " 1 ", + " 1 1 ", + "1 1" }}, + + { 'y', new[]{ " ", + " ", + "1 1", + "1 1", + " 1111", + " 1", + " 111 " }}, + + { 'z', new[]{ " ", + " ", + "11111", + " 1 ", + " 1 ", + " 1 ", + "11111" }}, + + { 'ß', new[]{ " 11 ", + "1 1 ", + "111 ", + "1 1 ", + "1 1 ", + "111 ", + "1 " }}, + + { '{', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { '|', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + + { '}', new[]{ " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 ", + " 1 " }}, + }; + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Command.cs b/Raspberry.IO.Components/Displays/Ssd1306/Command.cs new file mode 100644 index 0000000..b344d3d --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Command.cs @@ -0,0 +1,40 @@ +namespace Raspberry.IO.Components.Displays.Ssd1306 +{ + public class Command + { + public const byte SetContrast = 0x81; + // + public const byte DisplayAllOnResume = 0xA4; + public const byte DisplayAllOn = 0xA5; + public const byte DisplayNormal = 0xA6; + public const byte DisplayInvert = 0xA7; + public const byte DisplayOff = 0xAE; + public const byte DisplayOn = 0xAF; + // + public const byte SetDisplayOffset = 0xD3; + public const byte SetComPins = 0xDA; + public const byte SetVComDetect = 0xDB; + public const byte SetDisplayClockDivider = 0xD5; + public const byte SetPreCharge = 0xD9; + public const byte SetMultiplex = 0xA8; + public const byte SetLowColumn = 0x00; + public const byte SetHighColumn = 0x10; + public const byte SetStartLine = 0x40; + public const byte MemoryMode = 0x20; + public const byte ColumnAddress = 0x21; + public const byte PageAddress = 0x22; + // + public const byte ActivateScroll = 0x2F; + public const byte DeactivateScroll = 0x2E; + public const byte SetVerticalScrollArea = 0xA3; + public const byte SetScrollDirection = 0x25; + // + public const byte ComScanIncrement = 0xC0; + public const byte ComScanDecrement = 0xC8; + public const byte SegRemap = 0xA0; + public const byte ChargePump = 0x8D; + public const byte ExternalVcc = 0x1; + public const byte SwitchCapVcc = 0x2; + } +} + diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Fixed1L.cs b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Fixed1L.cs new file mode 100644 index 0000000..5f0b702 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Fixed1L.cs @@ -0,0 +1,115 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306.Fonts +{ + public class Fixed1L : IFont + { + private static byte[][] fontData = new byte[][] { + new byte[] { (byte)' ', 8, 8, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'!', 8, 8, 0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'"', 8, 8, 0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00 }, + new byte[] { (byte)'#', 8, 8, 0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00 }, + new byte[] { (byte)'$', 8, 8, 0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00 }, + new byte[] { (byte)'%', 8, 8, 0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00 }, + new byte[] { (byte)'&', 8, 8, 0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00 }, + new byte[] { (byte)'\'', 8, 8, 0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'(', 8, 8, 0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)')', 8, 8, 0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'*', 8, 8, 0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00 }, + new byte[] { (byte)'+', 8, 8, 0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00 }, + new byte[] { (byte)',', 8, 8, 0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'-', 8, 8, 0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00 }, + new byte[] { (byte)'.', 8, 8, 0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'/', 8, 8, 0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00 }, + new byte[] { (byte)'0', 8, 8, 0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00 }, + new byte[] { (byte)'1', 8, 8, 0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00 }, + new byte[] { (byte)'2', 8, 8, 0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00 }, + new byte[] { (byte)'3', 8, 8, 0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00 }, + new byte[] { (byte)'4', 8, 8, 0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00 }, + new byte[] { (byte)'5', 8, 8, 0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00 }, + new byte[] { (byte)'6', 8, 8, 0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00 }, + new byte[] { (byte)'7', 8, 8, 0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00 }, + new byte[] { (byte)'8', 8, 8, 0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00 }, + new byte[] { (byte)'9', 8, 8, 0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00 }, + new byte[] { (byte)':', 8, 8, 0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)';', 8, 8, 0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'<', 8, 8, 0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00 }, + new byte[] { (byte)'=', 8, 8, 0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00 }, + new byte[] { (byte)'>', 8, 8, 0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00 }, + new byte[] { (byte)'?', 8, 8, 0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00 }, + new byte[] { (byte)'@', 8, 8, 0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00 }, + new byte[] { (byte)'A', 8, 8, 0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00 }, + new byte[] { (byte)'B', 8, 8, 0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00 }, + new byte[] { (byte)'C', 8, 8, 0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00 }, + new byte[] { (byte)'D', 8, 8, 0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00 }, + new byte[] { (byte)'E', 8, 8, 0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00 }, + new byte[] { (byte)'F', 8, 8, 0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00 }, + new byte[] { (byte)'G', 8, 8, 0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00 }, + new byte[] { (byte)'H', 8, 8, 0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00 }, + new byte[] { (byte)'I', 8, 8, 0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'J', 8, 8, 0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00 }, + new byte[] { (byte)'K', 8, 8, 0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00 }, + new byte[] { (byte)'L', 8, 8, 0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00 }, + new byte[] { (byte)'M', 8, 8, 0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00 }, + new byte[] { (byte)'N', 8, 8, 0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00 }, + new byte[] { (byte)'O', 8, 8, 0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00 }, + new byte[] { (byte)'P', 8, 8, 0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00 }, + new byte[] { (byte)'Q', 8, 8, 0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00 }, + new byte[] { (byte)'R', 8, 8, 0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00 }, + new byte[] { (byte)'S', 8, 8, 0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00 }, + new byte[] { (byte)'T', 8, 8, 0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00 }, + new byte[] { (byte)'U', 8, 8, 0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00 }, + new byte[] { (byte)'V', 8, 8, 0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00 }, + new byte[] { (byte)'W', 8, 8, 0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00 }, + new byte[] { (byte)'X', 8, 8, 0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00 }, + new byte[] { (byte)'Y', 8, 8, 0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00 }, + new byte[] { (byte)'Z', 8, 8, 0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00 }, + new byte[] { (byte)'[', 8, 8, 0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'\\', 8, 8, 0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00 }, + new byte[] { (byte)']', 8, 8, 0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'^', 8, 8, 0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00 }, + new byte[] { (byte)'_', 8, 8, 0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00 }, + new byte[] { (byte)'`', 8, 8, 0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'a', 8, 8, 0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00 }, + new byte[] { (byte)'b', 8, 8, 0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00 }, + new byte[] { (byte)'c', 8, 8, 0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00 }, + new byte[] { (byte)'d', 8, 8, 0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00 }, + new byte[] { (byte)'e', 8, 8, 0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00 }, + new byte[] { (byte)'f', 8, 8, 0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00 }, + new byte[] { (byte)'g', 8, 8, 0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00 }, + new byte[] { (byte)'h', 8, 8, 0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00 }, + new byte[] { (byte)'i', 8, 8, 0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'j', 8, 8, 0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'k', 8, 8, 0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00 }, + new byte[] { (byte)'l', 8, 8, 0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'m', 8, 8, 0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00 }, + new byte[] { (byte)'n', 8, 8, 0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00 }, + new byte[] { (byte)'o', 8, 8, 0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00 }, + new byte[] { (byte)'p', 8, 8, 0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00 }, + new byte[] { (byte)'q', 8, 8, 0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00 }, + new byte[] { (byte)'r', 8, 8, 0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00 }, + new byte[] { (byte)'s', 8, 8, 0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00 }, + new byte[] { (byte)'t', 8, 8, 0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'u', 8, 8, 0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00 }, + new byte[] { (byte)'v', 8, 8, 0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00 }, + new byte[] { (byte)'w', 8, 8, 0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00 }, + new byte[] { (byte)'x', 8, 8, 0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00 }, + new byte[] { (byte)'y', 8, 8, 0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00 }, + new byte[] { (byte)'z', 8, 8, 0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00 }, + new byte[] { (byte)'{', 8, 8, 0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'|', 8, 8, 0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'}', 8, 8, 0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00 }, + new byte[] { (byte)'~', 8, 8, 0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00 }, + new byte[] { (byte)'ä', 8, 8, 0x00,0x20,0x55,0x54,0x55,0x78,0x00,0x00 }, + new byte[] { (byte)'ö', 8, 8, 0x00, 0x39, 0x44, 0x44, 0x39, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'ü', 8, 8, 0x00, 0x3D, 0x40, 0x40, 0x7D, 0x00, 0x00, 0x00 }, + new byte[] { (byte)0x7F, 8, 8, 0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00 } + }; + + public byte[][] GetData() + { + return Fixed1L.fontData; + } + } +} + diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Fonts/IFont.cs b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/IFont.cs new file mode 100644 index 0000000..3ac81b8 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/IFont.cs @@ -0,0 +1,10 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306.Fonts +{ + public interface IFont + { + byte[][] GetData(); + } +} + diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional2L.cs b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional2L.cs new file mode 100644 index 0000000..b885d5f --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional2L.cs @@ -0,0 +1,112 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306.Fonts +{ + public class Proportional2L : IFont + { + private static byte[][] fontData = { + new byte[] { (byte)' ', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'!', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'"', 8, 16, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + //new byte[] { (byte)'#', 8, 16, 0x00, 0x20, 0xe0, 0x3c, 0x20, 0xe0, 0x3c, 0x20, 0x08, 0x78, 0x0f, 0x08, 0x78, 0x0f, 0x08, 0x00 }, + new byte[] { (byte)'$', 8, 16, 0x00, 0x38, 0x44, 0x84, 0xfe, 0x04, 0x04, 0x18, 0x00, 0x18, 0x20, 0x20, 0x7f, 0x21, 0x22, 0x1c }, + new byte[] { (byte)'%', 8, 16, 0x00, 0x38, 0x44, 0x44, 0xb8, 0x60, 0x10, 0x0c, 0x00, 0x18, 0x04, 0x03, 0x1c, 0x22, 0x22, 0x1c }, + new byte[] { (byte)'&', 8, 16, 0x00, 0x00, 0x38, 0xc4, 0xc4, 0x38, 0x00, 0x80, 0x00, 0x1e, 0x21, 0x20, 0x20, 0x13, 0x0c, 0x33 }, + new byte[] { (byte)'\'', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'(', 8, 16, 0x00, 0x00, 0xe0, 0x18, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x07, 0x18, 0x20, 0x40, 0x00, 0x00 }, + new byte[] { (byte)')', 8, 16, 0x00, 0x00, 0x02, 0x04, 0x18, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x18, 0x07, 0x00, 0x00 }, + new byte[] { (byte)'*', 8, 16, 0x00, 0x40, 0x80, 0xe0, 0x80, 0x40, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x01, 0x02, 0x00, 0x00 }, + new byte[] { (byte)'+', 8, 16, 0x00, 0x80, 0x80, 0x80, 0xf0, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00 }, + new byte[] { (byte)',', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x30, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'-', 8, 16, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'.', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'/', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x30, 0x0c, 0x00, 0x00, 0x30, 0x0c, 0x03, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'0', 8, 16, 0x00, 0xf0, 0x08, 0x04, 0x04, 0x04, 0x08, 0xf0, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0f }, + new byte[] { (byte)'1', 8, 16, 0x00, 0x00, 0x08, 0x08, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'2', 8, 16, 0x00, 0x18, 0x04, 0x04, 0x04, 0x84, 0x78, 0x00, 0x00, 0x38, 0x24, 0x22, 0x21, 0x20, 0x20, 0x00 }, + new byte[] { (byte)'3', 8, 16, 0x00, 0x18, 0x84, 0x84, 0x84, 0x84, 0x78, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x20, 0x1f, 0x00 }, + new byte[] { (byte)'4', 8, 16, 0x00, 0x00, 0x00, 0xc0, 0x30, 0x0c, 0xfc, 0x00, 0x00, 0x04, 0x07, 0x04, 0x04, 0x04, 0x3f, 0x04 }, + new byte[] { (byte)'5', 8, 16, 0x00, 0xfc, 0x44, 0x44, 0x44, 0x44, 0x84, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x20, 0x1f, 0x00 }, + new byte[] { (byte)'6', 8, 16, 0x00, 0xf8, 0x84, 0x84, 0x84, 0x84, 0x18, 0x00, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x1f, 0x00 }, + new byte[] { (byte)'7', 8, 16, 0x00, 0x04, 0x04, 0x04, 0xc4, 0x34, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x01, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'8', 8, 16, 0x00, 0x78, 0x84, 0x84, 0x84, 0x84, 0x78, 0x00, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x1f, 0x00 }, + new byte[] { (byte)'9', 8, 16, 0x00, 0xf8, 0x04, 0x04, 0x04, 0x04, 0xf8, 0x00, 0x00, 0x18, 0x21, 0x21, 0x21, 0x21, 0x1f, 0x00 }, + new byte[] { (byte)':', 8, 16, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)';', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x1c, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'<', 8, 16, 0x80, 0x40, 0x40, 0x20, 0x10, 0x10, 0x08, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x04, 0x08, 0x00 }, + new byte[] { (byte)'=', 8, 16, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }, + new byte[] { (byte)'>', 8, 16, 0x00, 0x08, 0x10, 0x10, 0x20, 0x40, 0x40, 0x80, 0x00, 0x08, 0x04, 0x04, 0x02, 0x01, 0x01, 0x00 }, + //new byte[] { (byte)'|' /* ☂ */, 16, 32, 0x00, 0xe0, 0x30, 0x78, 0xf8, 0x3c, 0x3c, 0xfe, 0x7c, 0x3c, 0x3c, 0xf8, 0x78, 0x30, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'@', 8, 16, 0x00, 0xf0, 0x08, 0xc4, 0x24, 0xe4, 0x08, 0xf0, 0x00, 0x0f, 0x10, 0x23, 0x24, 0x23, 0x24, 0x17 }, + new byte[] { (byte)'A', 8, 16, 0x00, 0x00, 0x00, 0xe0, 0x1c, 0xe0, 0x00, 0x00, 0x00, 0x38, 0x07, 0x04, 0x04, 0x04, 0x07, 0x38 }, + new byte[] { (byte)'B', 8, 16, 0x00, 0xfc, 0x84, 0x84, 0x84, 0x84, 0x48, 0x30, 0x00, 0x3f, 0x20, 0x20, 0x20, 0x20, 0x11, 0x0e }, + new byte[] { (byte)'C', 8, 16, 0x00, 0xf0, 0x08, 0x04, 0x04, 0x04, 0x08, 0x30, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0c }, + new byte[] { (byte)'D', 8, 16, 0x00, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x18, 0xe0, 0x00, 0x3f, 0x20, 0x20, 0x20, 0x20, 0x18, 0x07 }, + new byte[] { (byte)'E', 8, 16, 0x00, 0xfc, 0x84, 0x84, 0x84, 0x84, 0x84, 0x04, 0x00, 0x3f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 }, + new byte[] { (byte)'F', 8, 16, 0x00, 0xfc, 0x84, 0x84, 0x84, 0x84, 0x84, 0x04, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'G', 8, 16, 0x00, 0xf0, 0x08, 0x04, 0x04, 0x04, 0x08, 0x10, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x21, 0x11, 0x3f }, + new byte[] { (byte)'H', 8, 16, 0x00, 0xfc, 0x80, 0x80, 0x80, 0x80, 0x80, 0xfc, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f }, + new byte[] { (byte)'I', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'J', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x18, 0x20, 0x20, 0x20, 0x1f, 0x00, 0x00 }, + new byte[] { (byte)'K', 8, 16, 0x00, 0xfc, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x3f, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20 }, + new byte[] { (byte)'L', 8, 16, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00 }, + new byte[] { (byte)'M', 8, 16, 0x00, 0xfc, 0x30, 0xc0, 0x00, 0xc0, 0x30, 0xfc, 0x00, 0x3f, 0x00, 0x00, 0x07, 0x00, 0x00, 0x3f }, + new byte[] { (byte)'N', 8, 16, 0x00, 0xfc, 0x18, 0x60, 0x80, 0x00, 0x00, 0xfc, 0x00, 0x3f, 0x00, 0x00, 0x01, 0x06, 0x18, 0x3f }, + new byte[] { (byte)'O', 8, 16, 0x00, 0xf0, 0x08, 0x04, 0x04, 0x04, 0x08, 0xf0, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0f }, + new byte[] { (byte)'P', 8, 16, 0x00, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x88, 0x70, 0x00, 0x3f, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00 }, + new byte[] { (byte)'Q', 8, 16, 0x00, 0xf0, 0x08, 0x04, 0x04, 0x04, 0x08, 0xf0, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x28, 0x10, 0x2f }, + new byte[] { (byte)'R', 8, 16, 0x00, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x88, 0x70, 0x00, 0x3f, 0x01, 0x01, 0x01, 0x01, 0x02, 0x3c }, + new byte[] { (byte)'S', 8, 16, 0x00, 0x38, 0x44, 0x84, 0x84, 0x04, 0x04, 0x18, 0x00, 0x18, 0x20, 0x20, 0x20, 0x21, 0x22, 0x1c }, + new byte[] { (byte)'T', 8, 16, 0x00, 0x04, 0x04, 0x04, 0xfc, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'U', 8, 16, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1f }, + new byte[] { (byte)'V', 8, 16, 0x00, 0x1c, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x1c, 0x00, 0x00, 0x00, 0x07, 0x38, 0x07, 0x00, 0x00 }, + new byte[] { (byte)'W', 8, 16, 0x00, 0xfc, 0x00, 0x80, 0x7c, 0x80, 0x00, 0xfc, 0x00, 0x03, 0x3c, 0x03, 0x00, 0x03, 0x3c, 0x03 }, + new byte[] { (byte)'X', 8, 16, 0x00, 0x0c, 0x30, 0x40, 0x80, 0x40, 0x30, 0x0c, 0x00, 0x30, 0x0c, 0x03, 0x00, 0x03, 0x0c, 0x30 }, + new byte[] { (byte)'Y', 8, 16, 0x00, 0x0c, 0x30, 0xc0, 0x00, 0xc0, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'Z', 8, 16, 0x00, 0x04, 0x04, 0x04, 0xc4, 0x34, 0x0c, 0x00, 0x00, 0x30, 0x2c, 0x23, 0x20, 0x20, 0x20, 0x00 }, + new byte[] { (byte)'[', 8, 16, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x00 }, + new byte[] { (byte)']', 8, 16, 0x00, 0x02, 0x02, 0x02, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00 }, + //new byte[] { (byte)'^', 8, 16, 0x00, 0x00, 0x08, 0x04, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'_', 8, 16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 }, + new byte[] { (byte)'`', 8, 16, 0x00, 0x00, 0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'a', 8, 16, 0x00, 0x80, 0x40, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x1f, 0x20 }, + new byte[] { (byte)'b', 8, 16, 0x00, 0xfc, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x3f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0f }, + new byte[] { (byte)'c', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x40, 0x40, 0x80, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x20, 0x10 }, + new byte[] { (byte)'d', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x40, 0x80, 0xfc, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x3f }, + new byte[] { (byte)'e', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x0f, 0x12, 0x22, 0x22, 0x22, 0x22, 0x13 }, + new byte[] { (byte)'f', 8, 16, 0x00, 0x40, 0x40, 0xf8, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'g', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x80, 0xc0, 0x00, 0x00, 0x27, 0x48, 0x50, 0x50, 0x48, 0x3f, 0x00 }, + new byte[] { (byte)'h', 8, 16, 0x00, 0xfc, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00 }, + new byte[] { (byte)'i', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'j', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'k', 8, 16, 0x00, 0xfc, 0x00, 0x00, 0x80, 0x40, 0x20, 0x00, 0x00, 0x3f, 0x02, 0x05, 0x08, 0x10, 0x20, 0x00 }, + new byte[] { (byte)'l', 8, 16, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'m', 8, 16, 0x00, 0xc0, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0x00, 0x3f, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x3f }, + new byte[] { (byte)'n', 8, 16, 0x00, 0xc0, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00 }, + new byte[] { (byte)'o', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x0f, 0x10, 0x20, 0x20, 0x20, 0x10, 0x0f }, + new byte[] { (byte)'p', 8, 16, 0x00, 0xc0, 0x80, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x7f, 0x08, 0x10, 0x10, 0x10, 0x08, 0x07 }, + new byte[] { (byte)'q', 8, 16, 0x00, 0x00, 0x80, 0x40, 0x40, 0x40, 0x80, 0xc0, 0x00, 0x07, 0x08, 0x10, 0x10, 0x10, 0x08, 0x7f }, + new byte[] { (byte)'r', 8, 16, 0x00, 0x00, 0xc0, 0x80, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'s', 8, 16, 0x00, 0x80, 0x40, 0x40, 0x40, 0x40, 0x80, 0x00, 0x00, 0x11, 0x22, 0x22, 0x24, 0x24, 0x18, 0x00 }, + new byte[] { (byte)'t', 8, 16, 0x00, 0x40, 0x40, 0xfc, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x00 }, + new byte[] { (byte)'u', 8, 16, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x3f, 0x00 }, + new byte[] { (byte)'v', 8, 16, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x03, 0x0c, 0x30, 0x0c, 0x03, 0x00 }, + new byte[] { (byte)'w', 8, 16, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x00, 0xc0, 0x00, 0x07, 0x38, 0x0c, 0x03, 0x0c, 0x38, 0x07 }, + new byte[] { (byte)'x', 8, 16, 0x00, 0x40, 0x80, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x20, 0x10, 0x09, 0x06, 0x09, 0x10, 0x20 }, + new byte[] { (byte)'y', 8, 16, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x40, 0x43, 0x4c, 0x30, 0x0c, 0x03, 0x00 }, + new byte[] { (byte)'z', 8, 16, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0xc0, 0x00, 0x00, 0x30, 0x28, 0x24, 0x22, 0x21, 0x20, 0x00 }, + new byte[] { (byte)'©', 8, 16, 0xe0, 0x18, 0xe4, 0x14, 0x14, 0x24, 0x18, 0xe0, 0x07, 0x18, 0x27, 0x28, 0x28, 0x24, 0x18, 0x07 }, + new byte[] { (byte)'®', 16, 32, 0x00, 0x00, 0xc0, 0x30, 0x08, 0xe8, 0x14, 0x14, 0x14, 0x14, 0xe8, 0x08, 0x30, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x10, 0x1f, 0x21, 0x21, 0x21, 0x21, 0x1e, 0x10, 0x0c, 0x03, 0x00, 0x00 }, + new byte[] { (byte)'\\' /* ° */, 16, 32, 0x00, 0x00, 0x18, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'#' /* bulb */, 11, 22, 0x00, 0x78, 0xe4, 0xfa, 0xfe, 0xfe, 0xfe, 0xfe, 0xfc, 0x78, 0x00, 0x00, 0x00, 0x01, 0x17, 0x77, 0x77, 0x77, 0x17, 0x01, 0x00, 0x00 }, + new byte[] { (byte)'|' /* plug */, 12, 24, 0x00, 0x60, 0xe0, 0xfe, 0xfe, 0xe0, 0xe0, 0xfe, 0xfe, 0xe0, 0x60, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x0f, 0x7f, 0x7f, 0x0f, 0x0f, 0x07, 0x00, 0x00 }, + new byte[] { (byte)'^' /* lightning */, 9, 18, 0x00, 0xe0, 0xfe, 0xfe, 0x5e, 0xc6, 0xe0, 0xe0, 0x00, 0x00, 0x01, 0x60, 0x38, 0x0e, 0x07, 0x01, 0x00, 0x00 } + }; + + public byte[][] GetData() + { + return Proportional2L.fontData; + } + } +} + diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional3L.cs b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional3L.cs new file mode 100644 index 0000000..6938cdd --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Fonts/Proportional3L.cs @@ -0,0 +1,110 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306.Fonts +{ + public class Proportional3L : IFont + { + private static byte[][] fontData = { + new byte[] { (byte)' ', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'!', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'"', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'#', 12, 36, 0x00, 0x00, 0x80, 0x80, 0x80, 0xfc, 0xfc, 0x80, 0x80, 0xfc, 0xfc, 0x80, 0x00, 0x40, 0x40, 0xc0, 0xff, 0x7f, 0x40, 0xc0, 0xff, 0x7f, 0x40, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'$', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0xff, 0xff, 0x04, 0x0c, 0x38, 0x30, 0x00, 0x00, 0x81, 0x83, 0x06, 0x04, 0xff, 0xff, 0x08, 0x18, 0xf0, 0xe0, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x3f, 0x3f, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'%', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0xfc, 0xf8, 0xc0, 0xf0, 0x3c, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x01, 0xe1, 0xf9, 0xde, 0xe7, 0x21, 0x20, 0xe0, 0xc0, 0x00, 0x00, 0x0c, 0x0f, 0x03, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x0f, 0x07 }, + new byte[] { (byte)'&', 12, 36, 0x00, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0x39, 0x0f, 0x0e, 0x3b, 0xf1, 0xc0, 0x80, 0xf0, 0x70, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x07, 0x0d, 0x08 }, + new byte[] { (byte)'\'', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'(', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x80, 0xe0, 0x78, 0x1c, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x1e, 0x38, 0x60, 0x40, 0x00, 0x00 }, + new byte[] { (byte)')', 12, 36, 0x00, 0x00, 0x02, 0x06, 0x1c, 0x78, 0xe0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x60, 0x38, 0x1e, 0x07, 0x01, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'*', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x46, 0x64, 0x3c, 0x1f, 0x1f, 0x3c, 0x64, 0x46, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'+', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0xff, 0xff, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)',', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x2c, 0x1c, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'-', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'.', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'/', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x7c, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x3e, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'0', 12, 36, 0x00, 0xc0, 0xf0, 0x38, 0x0c, 0x04, 0x04, 0x04, 0x0c, 0x38, 0xf0, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'1', 12, 36, 0x00, 0x00, 0x00, 0x10, 0x10, 0x18, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'2', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0x00, 0x80, 0xc0, 0x60, 0x20, 0x30, 0x18, 0x0e, 0x07, 0x01, 0x00, 0x00, 0x0e, 0x0f, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, + new byte[] { (byte)'3', 12, 36, 0x00, 0x70, 0x78, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0x80, 0x80, 0x00, 0x04, 0x04, 0x04, 0x04, 0x0e, 0xfb, 0xf1, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'4', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x70, 0x1c, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0x9c, 0x87, 0x81, 0x80, 0x80, 0xff, 0xff, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00 }, + new byte[] { (byte)'5', 12, 36, 0x00, 0x00, 0xfc, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x80, 0x87, 0x07, 0x02, 0x02, 0x02, 0x02, 0x06, 0xfc, 0xf8, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'6', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x38, 0x30, 0x00, 0x00, 0xff, 0xff, 0x04, 0x02, 0x02, 0x02, 0x02, 0x06, 0xfc, 0xf8, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'7', 12, 36, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xe4, 0xfc, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xf8, 0x7f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'8', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0xf1, 0xfb, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x0e, 0xfb, 0xf1, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'9', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0x07, 0x0f, 0x18, 0x10, 0x10, 0x10, 0x10, 0x08, 0xff, 0xff, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)':', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)';', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0b, 0x07, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'<', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0x60, 0x60, 0x30, 0x00, 0x00, 0x1c, 0x3e, 0x36, 0x63, 0xc1, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x06, 0x00 }, + new byte[] { (byte)'=', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'>', 12, 36, 0x00, 0x30, 0x60, 0x60, 0xc0, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xc1, 0xc1, 0x63, 0x36, 0x3e, 0x1c, 0x00, 0x00, 0x06, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + //new byte[] { (byte)'☂', 24, 72, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xf0, 0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0, 0xf0, 0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x01, 0x01, 0x03, 0x0f, 0x03, 0x01, 0x01, 0x01, 0xff, 0x07, 0x01, 0x01, 0x01, 0x03, 0x0f, 0x03, 0x01, 0x01, 0x03, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'@', 12, 36, 0x00, 0x80, 0xe0, 0x70, 0x18, 0x08, 0x88, 0x08, 0x98, 0x30, 0xe0, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x7e, 0xff, 0x81, 0xff, 0x7f, 0x80, 0xff, 0x7f, 0x00, 0x01, 0x03, 0x06, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x06, 0x02 }, + new byte[] { (byte)'A', 12, 36, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0x3c, 0xf8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xfe, 0x4f, 0x41, 0x40, 0x41, 0x4f, 0xfe, 0xf0, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f }, + new byte[] { (byte)'B', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0xfb, 0xf1, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'C', 12, 36, 0x00, 0xc0, 0xf0, 0x38, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x38, 0x30, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03 }, + new byte[] { (byte)'D', 12, 36, 0x00, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x38, 0xf0, 0xc0, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'E', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, + new byte[] { (byte)'F', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'G', 12, 36, 0x00, 0xc0, 0xf0, 0x38, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x78, 0x70, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0xfc, 0xfc, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x06, 0x0f, 0x0f }, + new byte[] { (byte)'H', 12, 36, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xff, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'I', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'J', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00, 0x00 }, + new byte[] { (byte)'K', 12, 36, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x80, 0xc0, 0x60, 0x30, 0x18, 0x0c, 0x00, 0x00, 0xff, 0xff, 0x06, 0x03, 0x0f, 0x3c, 0xf0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x0c, 0x00 }, + new byte[] { (byte)'L', 12, 36, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, + new byte[] { (byte)'M', 12, 36, 0x00, 0xfc, 0xfc, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xfc, 0xfc, 0x00, 0xff, 0xff, 0x03, 0x1f, 0xf8, 0xc0, 0xf8, 0x1f, 0x03, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0f, 0x0f }, + new byte[] { (byte)'N', 12, 36, 0x00, 0xfc, 0xfc, 0x70, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x01, 0x07, 0x1c, 0x70, 0xc0, 0xff, 0xff, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'O', 12, 36, 0x00, 0xc0, 0xf0, 0x38, 0x0c, 0x04, 0x04, 0x04, 0x0c, 0x38, 0xf0, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'P', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0xff, 0xff, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'Q', 12, 36, 0x00, 0xc0, 0xf0, 0x38, 0x0c, 0x04, 0x04, 0x04, 0x0c, 0x38, 0xf0, 0xc0, 0x00, 0xff, 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x09, 0x0b, 0x0e, 0x0f, 0x1b, 0x10 }, + new byte[] { (byte)'R', 12, 36, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0c, 0xf8, 0xf0, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0e, 0xfb, 0xf1, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'S', 12, 36, 0x00, 0xf0, 0xf8, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x38, 0x30, 0x00, 0x00, 0x80, 0x81, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x18, 0xf0, 0xe0, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'T', 12, 36, 0x00, 0x04, 0x04, 0x04, 0x04, 0xfc, 0xfc, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'U', 12, 36, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'V', 12, 36, 0x00, 0x3c, 0xfc, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xfc, 0x3c, 0x00, 0x00, 0x00, 0x03, 0x3f, 0xfc, 0xc0, 0xc0, 0xfc, 0x3f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0f, 0x0f, 0x01, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'W', 12, 36, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0xc0, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x01, 0x7f, 0xfe, 0xe0, 0x7f, 0x01, 0x7f, 0xe0, 0xfe, 0x7f, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00 }, + new byte[] { (byte)'X', 12, 36, 0x00, 0x0c, 0x3c, 0xf0, 0xc0, 0x00, 0x00, 0xc0, 0xf0, 0x3c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf3, 0x3f, 0x3f, 0xf3, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x0c, 0x00 }, + new byte[] { (byte)'Y', 12, 36, 0x00, 0x1c, 0xfc, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xfc, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0xfc, 0xfc, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'Z', 12, 36, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0xc4, 0xf4, 0x3c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xf0, 0x3c, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0f, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, + new byte[] { (byte)'[', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xfe, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x40, 0x40, 0x40, 0x00, 0x00 }, + new byte[] { (byte)']', 12, 36, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0xfe, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x7f, 0x7f, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'^', 12, 36, 0x00, 0x00, 0x10, 0x18, 0x0c, 0x06, 0x06, 0x0c, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'_', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 }, + new byte[] { (byte)'`', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0c, 0x18, 0x30, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'a', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xc6, 0x63, 0x21, 0x21, 0x21, 0x23, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x0f, 0x08 }, + new byte[] { (byte)'b', 12, 36, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x01, 0x01, 0x01, 0x03, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x04, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'c', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x03, 0x01, 0x01, 0x01, 0x03, 0x06, 0x04, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x06, 0x02, 0x00 }, + new byte[] { (byte)'d', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x03, 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x04, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'e', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x23, 0x21, 0x21, 0x21, 0x23, 0x3e, 0x3c, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x06, 0x02, 0x00 }, + new byte[] { (byte)'f', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xfc, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0xff, 0xff, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'g', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x03, 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0x00, 0x00, 0x00, 0x11, 0x33, 0x66, 0x44, 0x44, 0x44, 0x62, 0x3f, 0x1f, 0x00 }, + new byte[] { (byte)'h', 12, 36, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x01, 0x01, 0x01, 0x03, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'i', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'j', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x60, 0x3f, 0x1f, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'k', 12, 36, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x60, 0x70, 0xd8, 0x8c, 0x06, 0x03, 0x01, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x01, 0x03, 0x06, 0x0c, 0x08 }, + new byte[] { (byte)'l', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'m', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfe, 0x01, 0x01, 0xff, 0xfe, 0x01, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x0f, 0x0f }, + new byte[] { (byte)'n', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x01, 0x01, 0x03, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00 }, + new byte[] { (byte)'o', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x03, 0x01, 0x01, 0x01, 0x03, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'p', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x01, 0x01, 0x01, 0x03, 0xfe, 0xfc, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x04, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'q', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfe, 0x03, 0x01, 0x01, 0x01, 0x02, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x04, 0x7f, 0x7f, 0x00 }, + new byte[] { (byte)'r', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'s', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x1e, 0x33, 0x21, 0x21, 0x61, 0x43, 0xce, 0x8c, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x0c, 0x07, 0x03, 0x00 }, + new byte[] { (byte)'t', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0xff, 0xff, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0f, 0x08, 0x08, 0x08, 0x00, 0x00 }, + new byte[] { (byte)'u', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0c, 0x08, 0x08, 0x08, 0x04, 0x0f, 0x0f, 0x00 }, + new byte[] { (byte)'v', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3f, 0xf8, 0xc0, 0x00, 0xc0, 0xf8, 0x3f, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x0e, 0x07, 0x01, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'w', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0xf0, 0x3e, 0xf0, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x00, 0x01, 0x0f, 0x0f, 0x00, 0x00 }, + new byte[] { (byte)'x', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x9c, 0xf0, 0x60, 0xf0, 0x9c, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x0c, 0x00 }, + new byte[] { (byte)'y', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x3f, 0xf8, 0xc0, 0x00, 0x00, 0xc0, 0xf8, 0x3f, 0x07, 0x00, 0x00, 0x40, 0x40, 0x60, 0x33, 0x1f, 0x0f, 0x03, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'z', 12, 36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0xc1, 0xe1, 0x39, 0x1d, 0x07, 0x03, 0x00, 0x00, 0x00, 0x0c, 0x0e, 0x0b, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, + new byte[] { (byte)'©', 12, 36, 0x00, 0x80, 0x60, 0x10, 0x88, 0x88, 0x88, 0x88, 0x88, 0x10, 0x60, 0x80, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xc3, 0xc3, 0x00, 0xff, 0x00, 0x01, 0x06, 0x08, 0x11, 0x11, 0x11, 0x11, 0x11, 0x08, 0x06, 0x01 }, + new byte[] { (byte)'®', 24, 72, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x20, 0x10, 0x10, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x10, 0x10, 0x20, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x10, 0x10, 0x10, 0x30, 0xf0, 0xdf, 0x0e, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x04, 0x08, 0x09, 0x11, 0x10, 0x10, 0x10, 0x10, 0x10, 0x09, 0x09, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'\\' /* ° */, 10, 30, 0x00, 0x00, 0x1c, 0x36, 0x22, 0x36, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + new byte[] { (byte)'|' /* temp symbol */, 16, 48, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x81, 0x81, 0x03, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x7f, 0x00, 0xff, 0xff, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x10, 0x20, 0x4e, 0x5f, 0x5f, 0x4e, 0x20, 0x10, 0x0f, 0x00, 0x00, 0x00 } + }; + + public byte[][] GetData() + { + return Proportional3L.fontData; + } + } +} + diff --git a/Raspberry.IO.Components/Displays/Ssd1306/ScrollDirection.cs b/Raspberry.IO.Components/Displays/Ssd1306/ScrollDirection.cs new file mode 100644 index 0000000..020db0e --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/ScrollDirection.cs @@ -0,0 +1,13 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306 +{ + [Flags] + public enum ScrollDirection : byte + { + HorizontalRight = 0x01, + HorizontalLeft = 0x02, + VerticalAndHorizontalRight = 0x04, + VerticalAndHorizontalLeft = 0x05 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Ssd1306/ScrollSpeed.cs b/Raspberry.IO.Components/Displays/Ssd1306/ScrollSpeed.cs new file mode 100644 index 0000000..62310f8 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/ScrollSpeed.cs @@ -0,0 +1,17 @@ +using System; + +namespace Raspberry.IO.Components.Displays.Ssd1306 +{ + [Flags] + public enum ScrollSpeed : byte + { + F2 = 0x7, + F3 = 0x4, + F4 = 0x5, + F5 = 0x0, + F25 = 0x6, + F64 = 0x1, + F128 = 0x2, + F256 = 0x3 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Displays/Ssd1306/Ssd1306Connection.cs b/Raspberry.IO.Components/Displays/Ssd1306/Ssd1306Connection.cs new file mode 100644 index 0000000..d8ad8d5 --- /dev/null +++ b/Raspberry.IO.Components/Displays/Ssd1306/Ssd1306Connection.cs @@ -0,0 +1,261 @@ +#region References + +using System; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.IO.Components.Displays.Ssd1306.Fonts; + +#endregion + +namespace Raspberry.IO.Components.Displays.Ssd1306 +{ + /// + /// Represents a connection with an Ssd1306 I2C OLED display. + /// + public class Ssd1306Connection + { + #region Fields + + private readonly I2cDeviceConnection connection; + private readonly int displayWidth; + private readonly int displayHeight; + private int cursorX; + private int cursorY; + + private readonly object syncObject = new object(); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The display displayWidth. + /// The display displayHeight. + public Ssd1306Connection(I2cDeviceConnection connection, int displayWidth = 128, int displayHeight = 64) + { + this.connection = connection; + this.displayWidth = displayWidth; + this.displayHeight = displayHeight; + Initialize(); + } + + #endregion + + #region Methods + + /// + /// Clears the screen. + /// + public void ClearScreen() + { + lock (syncObject) + { + for (var y = 0; y < displayWidth * displayHeight / 8; y++) + connection.Write(0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); + } + } + + /// + /// Inverts the display. + /// + public void InvertDisplay() + { + SendCommand(Command.DisplayInvert); + } + + /// + /// Sets the display to normal mode. + /// + public void NormalDisplay() + { + SendCommand(Command.DisplayNormal); + } + + /// + /// Turns the display on. + /// + public void On() + { + SendCommand(Command.DisplayOn); + } + + /// + /// Turns the display off. + /// + public void Off() + { + SendCommand(Command.DisplayOff); + } + + /// + /// Sets the current cursor position to the specified column and row. + /// + /// Column. + /// Row. + public void GotoXY(int column, int row) + { + SendCommand( + (byte)(0xB0 + row), //set page address + (byte)(0x00 + (8 * column & 0x0F)), //set column lower address + (byte)(0x10 + ((8 * column >> 4) & 0x0F)) //set column higher address + ); + + cursorX = column; + cursorY = row; + } + + /// + /// Draws the text. + /// + /// Font. + /// Text. + public void DrawText(IFont font, string text) + { + var charset = font.GetData(); + foreach (var character in text) + { + var charIndex = -1; + for(var i = 0; i < charset.Length; i++) + { + if (charset[i][0] == character) + { + charIndex = i; + break; + } + } + if (charIndex == -1) + continue; + + var fontData = charset[charIndex]; + int fontWidth = fontData[1]; + int fontLength = fontData[2]; + for (var y = 0; y < (fontLength / fontWidth); y++) + { + SendCommand( + (byte)(0xB0 + cursorY + y), //set page address + (byte)(0x00 + (cursorX & 0x0F)), //set column lower address + (byte)(0x10 + ((cursorX>>4) & 0x0F)) //set column higher address + ); + + var data = new byte[fontWidth + 1]; + data[0] = 0x40; + Array.Copy(fontData, (y * fontWidth) + 3, data, 1, fontWidth); + DrawStride(data); + } + + cursorX += fontWidth; + } + } + + /// + /// Draws the image. + /// + /// Image. + public void DrawImage(byte[] image) + { + var data = new byte[image.Length + 1]; + data[0] = 0x40; + Array.Copy(image, 0, data, 1, image.Length); + + DrawStride(data); + } + + /// + /// Activates the scroll. + /// + public void ActivateScroll() + { + SendCommand(Command.ActivateScroll); + } + + /// + /// Deactivates the scroll. + /// + public void DeactivateScroll() + { + SendCommand(Command.DeactivateScroll); + } + + /// + /// Sets the scroll properties. + /// + /// Direction. + /// Scroll speed. + /// Start line. + /// End line. + public void SetScrollProperties(ScrollDirection direction, ScrollSpeed scrollSpeed, int startLine, int endLine) + { + SendCommand(new byte[] { + (byte)(Command.SetScrollDirection | (byte)direction), + 0x00, + (byte)startLine, + (byte)scrollSpeed, + (byte)endLine, + 0x00, + 0xFF + }); + } + + /// + /// Sets the contrast (brightness) of the display. Default is 127. + /// + /// A number between 0 and 255. Contrast increases as the value increases. + public void SetContrast(int contrast) + { + if (contrast < 0 || contrast > 255) throw new ArgumentOutOfRangeException("contrast", "Contrast must be between 0 and 255."); + SendCommand(Command.SetContrast, (byte)contrast); + } + + #endregion + + #region Private Helpers + + private void SendCommand(params byte[] commands) + { + lock (syncObject) + { + foreach (byte command in commands) + connection.Write(0x00, command); + } + } + + private void Initialize() + { + SendCommand( + Command.DisplayOff, + Command.SetDisplayClockDivider, 0x80, + Command.SetMultiplex, 0x3F, + Command.SetDisplayOffset, 0x00, + Command.SetStartLine | 0x0, + Command.ChargePump, 0x14, + Command.MemoryMode, 0x00, + Command.SegRemap | 0x1, + Command.ComScanDecrement, + Command.SetComPins, 0x12, + Command.SetContrast, 0x7F, + Command.SetPreCharge, 0x22, + Command.SetVComDetect, 0x40, + Command.DisplayAllOnResume, + Command.DisplayNormal + ); + + SendCommand( + Command.ColumnAddress, 0, (byte)(displayWidth - 1), + Command.PageAddress, 0, (byte)((displayHeight / 8) - 1) + ); + + ClearScreen(); + } + + private void DrawStride(byte[] data) + { + lock(syncObject) + connection.Write(data); + } + + #endregion + } +} + diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008I2cConnection.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008I2cConnection.cs new file mode 100644 index 0000000..4146d54 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008I2cConnection.cs @@ -0,0 +1,177 @@ +#region References + +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + /// + /// Represents a I2C connection to a MCP23008 I/O Expander. + /// + /// See for more information. + public class Mcp23008I2cConnection + { + #region Fields + + private readonly I2cDeviceConnection connection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + public Mcp23008I2cConnection(I2cDeviceConnection connection) + { + this.connection = connection; + } + + #endregion + + #region Methods + + /// + /// Sets the direction. + /// + /// The pin. + /// The direction. + public void SetDirection(Mcp23008Pin pin, Mcp23008PinDirection direction) + { + var register = Register.IODIR; + + connection.WriteByte((byte)register); + var directions = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + var newDirections = (direction == Mcp23008PinDirection.Input) + ? directions | bit + : directions & ~bit; + + connection.Write(new[] { (byte)register, (byte)newDirections }); + } + + /// + /// Sets the polarity. + /// + /// The pin. + /// The polarity. + public void SetPolarity(Mcp23008Pin pin, Mcp23008PinPolarity polarity) + { + var register = Register.IPOL; + + connection.WriteByte((byte)register); + var polarities = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + var newPolarities = (polarity == Mcp23008PinPolarity.Inverted) + ? polarities | bit + : polarities & ~bit; + + connection.Write(new[] { (byte)register, (byte)newPolarities }); + } + + /// + /// Sets the resistor. + /// + /// The pin. + /// The resistor. + public void SetResistor(Mcp23008Pin pin, Mcp23008PinResistor resistor) + { + var register = Register.GPPU; + + connection.WriteByte((byte)register); + var resistors = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + var newResistors = (resistor == Mcp23008PinResistor.PullUp) + ? resistors | bit + : resistors & ~bit; + + connection.Write(new[] { (byte)register, (byte)newResistors }); + } + + /// + /// Sets the pin status. + /// + /// The pin. + /// if set to true, pin is enabled. + public void SetPinStatus(Mcp23008Pin pin, bool enabled) + { + var register = Register.GPIO; + + connection.WriteByte((byte)register); + var status = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + var newStatus = enabled + ? status | bit + : status & ~bit; + + connection.Write((byte)register, (byte)newStatus); + } + + + /// + /// Gets the pin status. + /// + /// The pin. + /// The pin status. + public bool GetPinStatus(Mcp23008Pin pin) + { + var register = Register.GPIO; + + connection.WriteByte((byte)register); + var status = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + return (status & bit) != 0x00; + } + + /// + /// Toogles the specified pin. + /// + /// The pin. + public void Toogle(Mcp23008Pin pin) + { + var register = Register.GPIO; + + connection.WriteByte((byte)register); + var status = connection.ReadByte(); + + var bit = (byte)((int)pin & 0xFF); + var bitEnabled = (status & bit) != 0x00; + var newBitEnabled = !bitEnabled; + + var newStatus = newBitEnabled + ? status | bit + : status & ~bit; + + connection.Write((byte)register, (byte)newStatus); + } + + #endregion + + #region Private Helpers + + private enum Register + { + IODIR = 0x00, + IPOL = 0x01, + GPINTEN = 0x02, + DEFVAL = 0x03, + INTCON = 0x04, + IOCON = 0x05, + GPPU = 0x06, + INTF = 0x07, + INTCAP = 0x08, + GPIO = 0x09, + OLAT = 0x0A + } + + #endregion + + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008InputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008InputBinaryPin.cs new file mode 100644 index 0000000..a8a13b9 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008InputBinaryPin.cs @@ -0,0 +1,89 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + /// + /// Represents a binary intput pin on a MCP23017 I/O expander. + /// + public class Mcp23008InputBinaryPin : IInputBinaryPin + { + #region Fields + + private readonly Mcp23008I2cConnection connection; + private readonly Mcp23008Pin pin; + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + public Mcp23008InputBinaryPin(Mcp23008I2cConnection connection, Mcp23008Pin pin, + Mcp23008PinResistor resistor = Mcp23008PinResistor.None, + Mcp23008PinPolarity polarity = Mcp23008PinPolarity.Normal) + { + this.connection = connection; + this.pin = pin; + + connection.SetDirection(pin, Mcp23008PinDirection.Input); + connection.SetResistor(pin, resistor); + connection.SetPolarity(pin, polarity); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the state of the pin. + /// + /// + /// true if the pin is in high state; otherwise, false. + /// + public bool Read() + { + return connection.GetPinStatus(pin); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + public void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var startWait = DateTime.UtcNow; + if (timeout == TimeSpan.Zero) + timeout = DefaultTimeout; + + while (Read() != waitForUp) + { + if (DateTime.UtcNow - startWait >= timeout) + throw new TimeoutException("A timeout occurred while waiting for pin status to change"); + } + } + + #endregion + + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008OutputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008OutputBinaryPin.cs new file mode 100644 index 0000000..cfc3ae9 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008OutputBinaryPin.cs @@ -0,0 +1,58 @@ +using System; + +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + /// + /// Represents a binary output pin on a MCP23008 I/O expander. + /// + public class Mcp23008OutputBinaryPin : IOutputBinaryPin + { + #region Properties + + private readonly Mcp23008I2cConnection connection; + private readonly Mcp23008Pin pin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + public Mcp23008OutputBinaryPin(Mcp23008I2cConnection connection, Mcp23008Pin pin, + Mcp23008PinResistor resistor = Mcp23008PinResistor.None, + Mcp23008PinPolarity polarity = Mcp23008PinPolarity.Normal) + { + this.connection = connection; + this.pin = pin; + + connection.SetDirection(pin, Mcp23008PinDirection.Output); + connection.SetResistor(pin, resistor); + connection.SetPolarity(pin, polarity); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Writes the value of the pin. + /// + /// if set to true, pin is set to high state. + public void Write(bool state) + { + connection.SetPinStatus(pin, state); + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008Pin.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008Pin.cs new file mode 100644 index 0000000..c262007 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008Pin.cs @@ -0,0 +1,14 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + public enum Mcp23008Pin + { + Pin0 = 0x0001, + Pin1 = 0x0002, + Pin2 = 0x0004, + Pin3 = 0x0008, + Pin4 = 0x0010, + Pin5 = 0x0020, + Pin6 = 0x0040, + Pin7 = 0x0080 + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinDirection.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinDirection.cs new file mode 100644 index 0000000..4ead024 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinDirection.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + public enum Mcp23008PinDirection + { + Input, + Output + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinExtensionMethods.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinExtensionMethods.cs new file mode 100644 index 0000000..9d7e556 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinExtensionMethods.cs @@ -0,0 +1,38 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + /// + /// Provides extension methods for MCP23008 pins. + /// + public static class Mcp23008PinExtensionMethods + { + #region Methods + + /// + /// Creates an output binary pin. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + /// The pin. + public static Mcp23008OutputBinaryPin Out(this Mcp23008I2cConnection connection, Mcp23008Pin pin, Mcp23008PinResistor resistor = Mcp23008PinResistor.None, Mcp23008PinPolarity polarity = Mcp23008PinPolarity.Normal) + { + return new Mcp23008OutputBinaryPin(connection, pin, resistor, polarity); + } + + /// + /// Creates an input binary pin. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + /// The pin. + public static Mcp23008InputBinaryPin In(this Mcp23008I2cConnection connection, Mcp23008Pin pin, Mcp23008PinResistor resistor = Mcp23008PinResistor.None, Mcp23008PinPolarity polarity = Mcp23008PinPolarity.Normal) + { + return new Mcp23008InputBinaryPin(connection, pin, resistor, polarity); + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinPolarity.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinPolarity.cs new file mode 100644 index 0000000..b6e37b0 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinPolarity.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + public enum Mcp23008PinPolarity + { + Normal, + Inverted + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinResistor.cs b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinResistor.cs new file mode 100644 index 0000000..f43243a --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23008/Mcp23008PinResistor.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23008 +{ + public enum Mcp23008PinResistor + { + None, + PullUp + } +} diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017I2cConnection.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017I2cConnection.cs new file mode 100644 index 0000000..e108aea --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017I2cConnection.cs @@ -0,0 +1,172 @@ +#region References + +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + /// + /// Represents a I2C connection to a MCP23017 I/O Expander. + /// + /// See for more information. + public class Mcp23017I2cConnection + { + #region Fields + + private readonly I2cDeviceConnection connection; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + public Mcp23017I2cConnection(I2cDeviceConnection connection) + { + this.connection = connection; + } + + #endregion + + #region Methods + + /// + /// Sets the direction. + /// + /// The pin. + /// The direction. + public void SetDirection(Mcp23017Pin pin, Mcp23017PinDirection direction) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.IODIRA : Register.IODIRB; + + connection.WriteByte((byte) register); + var directions = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + var newDirections = (direction == Mcp23017PinDirection.Input) + ? directions | bit + : directions & ~bit; + + connection.Write(new[] {(byte) register, (byte) newDirections}); + } + + /// + /// Sets the polarity. + /// + /// The pin. + /// The polarity. + public void SetPolarity(Mcp23017Pin pin, Mcp23017PinPolarity polarity) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.IPOLA : Register.IPOLB; + + connection.WriteByte((byte) register); + var polarities = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + var newPolarities = (polarity == Mcp23017PinPolarity.Inverted) + ? polarities | bit + : polarities & ~bit; + + connection.Write(new[] {(byte) register, (byte) newPolarities}); + } + + /// + /// Sets the resistor. + /// + /// The pin. + /// The resistor. + public void SetResistor(Mcp23017Pin pin, Mcp23017PinResistor resistor) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.GPPUA : Register.GPPUB; + + connection.WriteByte((byte) register); + var resistors = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + var newResistors = (resistor == Mcp23017PinResistor.PullUp) + ? resistors | bit + : resistors & ~bit; + + connection.Write(new[] {(byte) register, (byte) newResistors}); + } + + /// + /// Sets the pin status. + /// + /// The pin. + /// if set to true, pin is enabled. + public void SetPinStatus(Mcp23017Pin pin, bool enabled) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.GPIOA : Register.GPIOB; + + connection.WriteByte((byte) register); + var status = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + var newStatus = enabled + ? status | bit + : status & ~bit; + + connection.Write((byte) register, (byte) newStatus); + } + + /// + /// Gets the pin status. + /// + /// The pin. + /// The pin status. + public bool GetPinStatus(Mcp23017Pin pin) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.GPIOA : Register.GPIOB; + + connection.WriteByte((byte) register); + var status = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + return (status & bit) != 0x00; + } + + /// + /// Toogles the specified pin. + /// + /// The pin. + public void Toogle(Mcp23017Pin pin) + { + var register = ((int) pin & 0x0100) == 0x0000 ? Register.GPIOA : Register.GPIOB; + + connection.WriteByte((byte) register); + var status = connection.ReadByte(); + + var bit = (byte) ((int) pin & 0xFF); + var bitEnabled = (status & bit) != 0x00; + var newBitEnabled = !bitEnabled; + + var newStatus = newBitEnabled + ? status | bit + : status & ~bit; + + connection.Write((byte) register, (byte) newStatus); + } + + #endregion + + #region Private Helpers + + private enum Register + { + IODIRA = 0x00, + IODIRB = 0x01, + IPOLA = 0x02, + IPOLB = 0x03, + GPPUA = 0x0c, + GPPUB = 0x0d, + GPIOA = 0x12, + GPIOB = 0x13 + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017InputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017InputBinaryPin.cs new file mode 100644 index 0000000..6e3e02c --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017InputBinaryPin.cs @@ -0,0 +1,88 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + /// + /// Represents a binary intput pin on a MCP23017 I/O expander. + /// + public class Mcp23017InputBinaryPin : IInputBinaryPin + { + #region Fields + + private readonly Mcp23017I2cConnection connection; + private readonly Mcp23017Pin pin; + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + public Mcp23017InputBinaryPin(Mcp23017I2cConnection connection, Mcp23017Pin pin, + Mcp23017PinResistor resistor = Mcp23017PinResistor.None, + Mcp23017PinPolarity polarity = Mcp23017PinPolarity.Normal) + { + this.connection = connection; + this.pin = pin; + + connection.SetDirection(pin, Mcp23017PinDirection.Input); + connection.SetResistor(pin, resistor); + connection.SetPolarity(pin, polarity); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the state of the pin. + /// + /// + /// true if the pin is in high state; otherwise, false. + /// + public bool Read() + { + return connection.GetPinStatus(pin); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + public void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var startWait = DateTime.UtcNow; + if (timeout == TimeSpan.Zero) + timeout = DefaultTimeout; + + while (Read() != waitForUp) + { + if (DateTime.UtcNow - startWait >= timeout) + throw new TimeoutException("A timeout occurred while waiting for pin status to change"); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017OutputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017OutputBinaryPin.cs new file mode 100644 index 0000000..4ba8335 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017OutputBinaryPin.cs @@ -0,0 +1,58 @@ +using System; + +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + /// + /// Represents a binary output pin on a MCP23017 I/O expander. + /// + public class Mcp23017OutputBinaryPin : IOutputBinaryPin + { + #region Properties + + private readonly Mcp23017I2cConnection connection; + private readonly Mcp23017Pin pin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + public Mcp23017OutputBinaryPin(Mcp23017I2cConnection connection, Mcp23017Pin pin, + Mcp23017PinResistor resistor = Mcp23017PinResistor.None, + Mcp23017PinPolarity polarity = Mcp23017PinPolarity.Normal) + { + this.connection = connection; + this.pin = pin; + + connection.SetDirection(pin, Mcp23017PinDirection.Output); + connection.SetResistor(pin, resistor); + connection.SetPolarity(pin, polarity); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Writes the value of the pin. + /// + /// if set to true, pin is set to high state. + public void Write(bool state) + { + connection.SetPinStatus(pin, state); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017Pin.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017Pin.cs new file mode 100644 index 0000000..efcf658 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017Pin.cs @@ -0,0 +1,23 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + public enum Mcp23017Pin + { + A0 = 0x0001, + A1 = 0x0002, + A2 = 0x0004, + A3 = 0x0008, + A4 = 0x0010, + A5 = 0x0020, + A6 = 0x0040, + A7 = 0x0080, + + B0 = 0x0101, + B1 = 0x0102, + B2 = 0x0104, + B3 = 0x0108, + B4 = 0x0110, + B5 = 0x0120, + B6 = 0x0140, + B7 = 0x0180, + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinDirection.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinDirection.cs new file mode 100644 index 0000000..def8164 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinDirection.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + public enum Mcp23017PinDirection + { + Input, + Output + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinExtensionMethods.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinExtensionMethods.cs new file mode 100644 index 0000000..3a023b2 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinExtensionMethods.cs @@ -0,0 +1,38 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + /// + /// Provides extension methods for MCP23017 pins. + /// + public static class Mcp23017PinExtensionMethods + { + #region Methods + + /// + /// Creates an output binary pin. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + /// The pin. + public static Mcp23017OutputBinaryPin Out(this Mcp23017I2cConnection connection, Mcp23017Pin pin, Mcp23017PinResistor resistor = Mcp23017PinResistor.None, Mcp23017PinPolarity polarity = Mcp23017PinPolarity.Normal) + { + return new Mcp23017OutputBinaryPin(connection, pin, resistor, polarity); + } + + /// + /// Creates an input binary pin. + /// + /// The connection. + /// The pin. + /// The resistor. + /// The polarity. + /// The pin. + public static Mcp23017InputBinaryPin In(this Mcp23017I2cConnection connection, Mcp23017Pin pin, Mcp23017PinResistor resistor = Mcp23017PinResistor.None, Mcp23017PinPolarity polarity = Mcp23017PinPolarity.Normal) + { + return new Mcp23017InputBinaryPin(connection, pin, resistor, polarity); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinPolarity.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinPolarity.cs new file mode 100644 index 0000000..6ea352d --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinPolarity.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + public enum Mcp23017PinPolarity + { + Normal, + Inverted + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinResistor.cs b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinResistor.cs new file mode 100644 index 0000000..02fafb0 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Mcp23017/Mcp23017PinResistor.cs @@ -0,0 +1,8 @@ +namespace Raspberry.IO.Components.Expanders.Mcp23017 +{ + public enum Mcp23017PinResistor + { + None, + PullUp + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574I2cConnection.cs b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574I2cConnection.cs new file mode 100644 index 0000000..240bc8d --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574I2cConnection.cs @@ -0,0 +1,125 @@ +#region References + +using System; +using System.Globalization; +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Pcf8574 +{ + /// + /// Represents a I2C connection to a PCF8574 I/O Expander. + /// + /// See for more information. + public class Pcf8574I2cConnection + { + #region Fields + + private readonly I2cDeviceConnection connection; + + private byte inputPins; + private byte currentStatus; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + public Pcf8574I2cConnection(I2cDeviceConnection connection) + { + this.connection = connection; + connection.WriteByte(0); + } + + #endregion + + #region Methods + + /// + /// Sets the pin status. + /// + /// The pin. + /// if set to true, specified pin is enabled. + public void SetPinStatus(Pcf8574Pin pin, bool enabled) + { + var bit = GetPinBit(pin); + if ((inputPins & bit) != 0x00) + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot set value of input pin {0}", pin)); + + var status = currentStatus; + var newStatus = (byte)(enabled + ? status | bit + : status & ~bit); + + connection.Write((byte)(newStatus | inputPins)); + currentStatus = newStatus; + } + + /// + /// Gets the pin status. + /// + /// The pin. + /// + public bool GetPinStatus(Pcf8574Pin pin) + { + var bit = GetPinBit(pin); + if ((inputPins & bit) == 0x00) + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot get value of input pin {0}", pin)); + + var status = connection.ReadByte(); + return (status & bit) != 0x00; + } + + /// + /// Toogles the specified pin. + /// + /// The pin. + public void Toogle(Pcf8574Pin pin) + { + var bit = GetPinBit(pin); + if ((inputPins & bit) != 0x00) + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, "Cannot set value of input pin {0}", pin)); + + var status = currentStatus; + + var bitEnabled = (status & bit) != 0x00; + var newBitEnabled = !bitEnabled; + + var newStatus = (byte)(newBitEnabled + ? status | bit + : status & ~bit); + + connection.Write((byte)(newStatus | inputPins)); + currentStatus = newStatus; + } + + #endregion + + #region Internal Helpers + + internal void SetInputPin(Pcf8574Pin pin, bool isInput) + { + var bit = GetPinBit(pin); + inputPins = (byte) (isInput + ? inputPins | bit + : inputPins & ~bit); + + connection.Write((byte) (currentStatus | inputPins)); + } + + #endregion + + #region Private Helpers + + private static byte GetPinBit(Pcf8574Pin pin) + { + return (byte) (int) pin; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574InputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574InputBinaryPin.cs new file mode 100644 index 0000000..1bc1e34 --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574InputBinaryPin.cs @@ -0,0 +1,82 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Pcf8574 +{ + /// + /// Represents a binary intput pin on a PCF8574 I/O expander. + /// + public class Pcf8574InputBinaryPin : IInputBinaryPin + { + #region Fields + + private readonly Pcf8574I2cConnection connection; + private readonly Pcf8574Pin pin; + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + public Pcf8574InputBinaryPin(Pcf8574I2cConnection connection, Pcf8574Pin pin) + { + this.connection = connection; + this.pin = pin; + + connection.SetInputPin(pin, true); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Reads the state of the pin. + /// + /// + /// true if the pin is in high state; otherwise, false. + /// + public bool Read() + { + return connection.GetPinStatus(pin); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + public void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var startWait = DateTime.UtcNow; + if (timeout == TimeSpan.Zero) + timeout = DefaultTimeout; + + while (Read() != waitForUp) + { + if (DateTime.UtcNow - startWait >= timeout) + throw new TimeoutException("A timeout occurred while waiting for pin status to change"); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574OutputBinaryPin.cs b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574OutputBinaryPin.cs new file mode 100644 index 0000000..64ce1df --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574OutputBinaryPin.cs @@ -0,0 +1,56 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.Components.Expanders.Pcf8574 +{ + /// + /// Represents a binary output pin on a MCP23017 I/O expander. + /// + public class Pcf8574OutputBinaryPin : IOutputBinaryPin + { + #region Properties + + private readonly Pcf8574I2cConnection connection; + private readonly Pcf8574Pin pin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + /// The pin. + public Pcf8574OutputBinaryPin(Pcf8574I2cConnection connection, Pcf8574Pin pin) + { + this.connection = connection; + this.pin = pin; + + connection.SetInputPin(pin, false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose(){} + + #endregion + + #region Methods + + /// + /// Writes the value of the pin. + /// + /// if set to true, pin is set to high state. + public void Write(bool state) + { + connection.SetPinStatus(pin, state); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574Pin.cs b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574Pin.cs new file mode 100644 index 0000000..5cd6e2f --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574Pin.cs @@ -0,0 +1,14 @@ +namespace Raspberry.IO.Components.Expanders.Pcf8574 +{ + public enum Pcf8574Pin + { + P0 = 0x01, + P1 = 0x02, + P2 = 0x04, + P3 = 0x08, + P4 = 0x10, + P5 = 0x20, + P6 = 0x40, + P7 = 0x80 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574PinExtensionMethods.cs b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574PinExtensionMethods.cs new file mode 100644 index 0000000..2485f1f --- /dev/null +++ b/Raspberry.IO.Components/Expanders/Pcf8574/Pcf8574PinExtensionMethods.cs @@ -0,0 +1,34 @@ +namespace Raspberry.IO.Components.Expanders.Pcf8574 +{ + /// + /// Provides extension methods for PCF8574 pins. + /// + public static class Pcf8574PinExtensionMethods + { + #region Methods + + /// + /// Creates an output binary pin. + /// + /// The connection. + /// The pin. + /// The pin. + public static Pcf8574OutputBinaryPin Out(this Pcf8574I2cConnection connection, Pcf8574Pin pin) + { + return new Pcf8574OutputBinaryPin(connection, pin); + } + + /// + /// Creates an input binary pin. + /// + /// The connection. + /// The pin. + /// The pin. + public static Pcf8574InputBinaryPin In(this Pcf8574I2cConnection connection, Pcf8574Pin pin) + { + return new Pcf8574InputBinaryPin(connection, pin); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Leds/BiColor24Bargraph/BiColor24Bargraph.cs b/Raspberry.IO.Components/Leds/BiColor24Bargraph/BiColor24Bargraph.cs new file mode 100644 index 0000000..58819e7 --- /dev/null +++ b/Raspberry.IO.Components/Leds/BiColor24Bargraph/BiColor24Bargraph.cs @@ -0,0 +1,74 @@ +//Copyright (c) 2016 Logic Ethos Ltd +// +//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +//The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +using System; +using Raspberry.Timers; +using Raspberry.IO.Components.Controllers.HT16K33; +using Raspberry.IO.InterIntegratedCircuit; + + +namespace Raspberry.IO.Components.Leds.BiColor24Bargraph +{ + public class BiColor24Bargraph : HT16K33Connection + { + public enum LEDState + { + Off, + Red, + Green, + Yellow + } + /// + /// Initializes a new instance of the class. + /// + /// I2c Connection. + public BiColor24Bargraph (I2cDeviceConnection connection) : base (connection,6) + { + } + + /// + /// Sets the led (0 to 23) + /// + /// Led no. + /// State. + public void SetLed(uint ledNo, LEDState state) + { + + if (ledNo > 23) throw new Exception("led must be between 0 and 23"); + + long r,c; + r = Math.DivRem(ledNo,4,out c) * 2; + if (ledNo >= 12) c += 4; + + if (r > 4) r -= 6; + + switch (state) + { + case LEDState.Off: + base.SetLed((uint)r,(uint)c, false); + base.SetLed((uint)r + 1,(uint)c, false); + break; + case LEDState.Red: + base.SetLed((uint)r,(uint)c, true); + base.SetLed((uint)r + 1,(uint)c, false); + break; + case LEDState.Yellow: + base.SetLed((uint)r,(uint)c, true); + base.SetLed((uint)r + 1,(uint)c, true); + break; + case LEDState.Green: + base.SetLed((uint)r,(uint)c, false); + base.SetLed((uint)r + 1,(uint)c, true); + break; + } + + } + } +} + + + + diff --git a/Raspberry.IO.Components/Leds/GroveBar/GroveBarConnection.cs b/Raspberry.IO.Components/Leds/GroveBar/GroveBarConnection.cs new file mode 100644 index 0000000..cee71c6 --- /dev/null +++ b/Raspberry.IO.Components/Leds/GroveBar/GroveBarConnection.cs @@ -0,0 +1,182 @@ +#region References + +using System; +using Raspberry.Timers; +using System.Text; + +#endregion + +namespace Raspberry.IO.Components.Leds.GroveBar +{ + /// + /// Represents a connection with Grove Led Bar module. + /// @see http://www.seeedstudio.com/wiki/Grove_-_LED_Bar + /// + public class GroveBarConnection : IDisposable + { + #region Fields + + private const uint CommandMode = 0x0000; + private static readonly TimeSpan delay = TimeSpan.FromTicks(1); + + private readonly IOutputBinaryPin dataPin; + private readonly IInputOutputBinaryPin clockPin; + private string currentLedsStatus = "0000000000"; + + #endregion + + #region Instance Management + + public GroveBarConnection(IOutputBinaryPin dataPin, IInputOutputBinaryPin clockPin) + { + this.dataPin = dataPin; + this.clockPin = clockPin; + Initialize(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Sets status of leds from a binary string eg: "0010101011", where "0" is off and "1" is on + /// + /// Leds string. + public void SetFromString(string ledsString) + { + currentLedsStatus = ledsString; + SendData(CommandMode); + var indexBits = (uint)Convert.ToInt32(ledsString, 2); + for (int i = 0; i < 12; i++) + { + var state = (uint)((indexBits & 0x0001) > 0 ? 0x00FF : 0x0000); + SendData(state); + indexBits = indexBits >> 1; + } + LatchData(); + } + + /// + /// Sets the level of the leds bar. + /// + /// Level. + public void SetLevel(int level) + { + var status = new StringBuilder(new string('0', 10)); + for (int i = 0; i < level; i++) + { + status[i] = '1'; + } + currentLedsStatus = status.ToString(); + SendData(CommandMode); + for (int i = 0; i < 12; i++) + { + var state = (uint)((i < level) ? 0x00FF : 0x0000); + SendData(state); + } + LatchData(); + } + + /// + /// Turn on a single led at a given position (0-9) + /// + /// Position. + public void On(int position) + { + var status = new StringBuilder(currentLedsStatus); + status[position] = '1'; + currentLedsStatus = status.ToString(); + SetFromString(currentLedsStatus); + } + + /// + /// Turn off a single led at a given position (0-9) + /// + /// Position. + public void Off(int position) + { + var status = new StringBuilder(currentLedsStatus); + status[position] = '0'; + currentLedsStatus = status.ToString(); + SetFromString(currentLedsStatus); + } + + /// + /// Turn all leds on. + /// + public void AllOn() + { + currentLedsStatus = new string('1', 10); + SetFromString(currentLedsStatus); + } + + /// + /// Turn all leds off. + /// + public void AllOff() + { + currentLedsStatus = new string('0', 10); + SetFromString(currentLedsStatus); + } + + /// + /// Closes the connection. + /// + public void Close() + { + dataPin.Dispose(); + clockPin.Dispose(); + } + + #endregion + + #region Private Helpers + + private void Initialize() + { + dataPin.Write(false); + HighResolutionTimer.Sleep(delay); + for(int i = 0; i < 4; i++) + { + dataPin.Write(true); + dataPin.Write(false); + } + + } + + private void SendData(uint data) + { + // Send 16 bit data + for(int i = 0; i < 16; i++) + { + bool state = ((data & 0x8000) > 0); + dataPin.Write(state); + state = !clockPin.Read(); + clockPin.Write(state); + data <<= 1; + } + } + + private void LatchData() + { + dataPin.Write(false); + HighResolutionTimer.Sleep(delay); + for(int i = 0; i < 4; i++) + { + dataPin.Write(true); + dataPin.Write(false); + } + } + + #endregion + } +} + diff --git a/Raspberry.IO.Components/Leds/GroveRgb/GroveRgbConnection.cs b/Raspberry.IO.Components/Leds/GroveRgb/GroveRgbConnection.cs new file mode 100644 index 0000000..4f628aa --- /dev/null +++ b/Raspberry.IO.Components/Leds/GroveRgb/GroveRgbConnection.cs @@ -0,0 +1,141 @@ +#region References + +using System; +using Raspberry.Timers; +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.Components.Leds.GroveRgb +{ + /// + /// Represents a connection with Grove Chainable RGB Led modules. + /// + /// + public class GroveRgbConnection: IDisposable + { + #region Fields + + private static readonly TimeSpan delay = TimeSpanUtility.FromMicroseconds(20); + + readonly IOutputBinaryPin dataPin; + readonly IOutputBinaryPin clockPin; + readonly List ledColors; + + #endregion + + #region Instance Management + + public GroveRgbConnection(IOutputBinaryPin dataPin, IOutputBinaryPin clockPin, int ledCount) + { + ledColors = new List(); + for (int i = 0; i < ledCount; i++) + { + // Initialize all leds with white color + ledColors.Add(new RgbColor()); + } + this.dataPin = dataPin; + this.clockPin = clockPin; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Sets the color of a led. + /// + /// Led number (zero based index). + /// The color. + public void SetColor (int ledNumber, RgbColor color) + { + // Send data frame prefix (32x "0") + SendByte(0x00); + SendByte(0x00); + SendByte(0x00); + SendByte(0x00); + + // Send color data for each one of the leds + for (int i = 0; i < ledColors.Count; i++) + { + if (i == ledNumber) + { + ledColors [i].Red = color.Red; + ledColors [i].Green = color.Green; + ledColors [i].Blue = color.Blue; + } + + // Start by sending a byte with the format "1 1 /B7 /B6 /G7 /G6 /R7 /R6" + byte prefix = Convert.ToByte("11000000", 2); + if ((color.Blue & 0x80) == 0) + prefix |= Convert.ToByte("00100000", 2); + if ((color.Blue & 0x40) == 0) + prefix |= Convert.ToByte("00010000", 2); + if ((color.Green & 0x80) == 0) + prefix |= Convert.ToByte("00001000", 2); + if ((color.Green & 0x40) == 0) + prefix |= Convert.ToByte("00000100", 2); + if ((color.Red & 0x80) == 0) + prefix |= Convert.ToByte("00000010", 2); + if ((color.Red & 0x40) == 0) + prefix |= Convert.ToByte("00000001", 2); + + SendByte(prefix); + + // Now must send the 3 colors + SendByte(ledColors [i].Blue); + SendByte(ledColors [i].Green); + SendByte(ledColors [i].Red); + } + + // Terminate data frame (32x "0") + SendByte(0x00); + SendByte(0x00); + SendByte(0x00); + SendByte(0x00); + } + + /// + /// Closes the connection. + /// + public void Close() + { + dataPin.Dispose(); + clockPin.Dispose(); + } + + #endregion + + #region Private Helpers + + private void SendByte(byte data) + { + // Send one bit at a time, starting with the MSB + for (byte i = 0; i < 8; i++) + { + // If MSB is 1, write one and clock it, else write 0 and clock + dataPin.Write((data & 0x80) != 0); + + // clk(): + clockPin.Write(false); + HighResolutionTimer.Sleep(delay); + clockPin.Write(true); + HighResolutionTimer.Sleep(delay); + + // Advance to the next bit to send + data <<= 1; + } + } + + #endregion + } +} + diff --git a/Raspberry.IO.Components/Leds/GroveRgb/RgbColor.cs b/Raspberry.IO.Components/Leds/GroveRgb/RgbColor.cs new file mode 100644 index 0000000..e0bfba4 --- /dev/null +++ b/Raspberry.IO.Components/Leds/GroveRgb/RgbColor.cs @@ -0,0 +1,144 @@ +using System; + +namespace Raspberry.IO.Components.Leds.GroveRgb +{ + public class RgbColor + { + #region Instance Management + + public RgbColor() + { + Red = 0; + Green = 0; + Blue = 0; + } + + public RgbColor(byte r, byte g, byte b) + { + Red = r; + Green = g; + Blue = b; + } + + #endregion + + #region Properties + + public byte Red { get; set; } + public byte Green { get; set; } + public byte Blue { get; set; } + + #endregion + + #region Methods + + /// + /// Gets RgbColor instance from Hsv color space. + /// + /// Hue. + /// Saturation. + /// Value. + public static RgbColor FromHsv(double hue, double sat, double val) + { + byte r = 0, g = 0, b = 0; + double H = hue * 360D; + while (H < 0) + { + H += 360; + } + while (H >= 360) + { + H -= 360; + } + double R, G, B; + if (val <= 0) + { + R = G = B = 0; + } + else if (sat <= 0) + { + R = G = B = val; + } + else + { + double hf = H / 60.0; + int i = (int)Math.Floor(hf); + double f = hf - i; + double pv = val * (1 - sat); + double qv = val * (1 - sat * f); + double tv = val * (1 - sat * (1 - f)); + switch(i) + { + // Red is the dominant color + case 0: + R = val; + G = tv; + B = pv; + break; + // Green is the dominant color + case 1: + R = qv; + G = val; + B = pv; + break; + case 2: + R = pv; + G = val; + B = tv; + break; + // Blue is the dominant color + case 3: + R = pv; + G = qv; + B = val; + break; + case 4: + R = tv; + G = pv; + B = val; + break; + // Red is the dominant color + case 5: + R = val; + G = pv; + B = qv; + break; + // Just in case we overshoot on our math by a little, we put these here. Since its a switch it won't slow us down at all to put these here. + case 6: + R = val; + G = tv; + B = pv; + break; + case -1: + R = val; + G = pv; + B = qv; + break; + // The color is not defined, we should throw an error. + default: + R = G = B = val; // Just pretend its black/white + break; + } + } + r = (byte)Clamp((int)(R * 255.0)); + g = (byte)Clamp((int)(G * 255.0)); + b = (byte)Clamp((int)(B * 255.0)); + + return new RgbColor() { Red = r, Green = g, Blue = b }; + } + + #endregion + + #region Private Helpers + + private static int Clamp(int i) + { + if (i < 0) i = 0; + if (i > 255) i = 255; + return i; + } + + #endregion + } +} + diff --git a/Raspberry.IO.Components/Properties/AssemblyInfo.cs b/Raspberry.IO.Components/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..08038bb --- /dev/null +++ b/Raspberry.IO.Components/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Raspberry.IO.Components")] +[assembly: AssemblyDescription("Raspberry Pi IO Components")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4fa05c29-4c49-48c9-a04c-7edfcd3dd944")] diff --git a/Raspberry.IO.Components/Raspberry.IO.Components.csproj b/Raspberry.IO.Components/Raspberry.IO.Components.csproj new file mode 100644 index 0000000..e875fb8 --- /dev/null +++ b/Raspberry.IO.Components/Raspberry.IO.Components.csproj @@ -0,0 +1,196 @@ + + + + Debug + AnyCPU + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Library + Properties + Raspberry.IO.Components + Raspberry.IO.Components + v4.0 + 512 + ..\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\Raspberry.System.2.1\lib\net40\Raspberry.System.dll + True + + + + + + $(SolutionDir)packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll + + + $(SolutionDir)packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll + + + $(SolutionDir)packages\UnitsNet.3.46.1\lib\net35\UnitsNet.dll + True + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {326342E5-0411-40E8-9F2D-563D6B192568} + Raspberry.IO.SerialPeripheralInterface + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + + + + + + + + + + + \ No newline at end of file diff --git a/Raspberry.IO.Components/Raspberry.IO.Components.csproj.DotSettings b/Raspberry.IO.Components/Raspberry.IO.Components.csproj.DotSettings new file mode 100644 index 0000000..01b8c9d --- /dev/null +++ b/Raspberry.IO.Components/Raspberry.IO.Components.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Distance/HcSr04/HcSr04Connection.cs b/Raspberry.IO.Components/Sensors/Distance/HcSr04/HcSr04Connection.cs new file mode 100644 index 0000000..50f5d21 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Distance/HcSr04/HcSr04Connection.cs @@ -0,0 +1,105 @@ +#region References + +using System; +using Raspberry.Timers; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Distance.HcSr04 +{ + /// + /// Represents a connection to HC-SR04 distance sensor. + /// + /// + /// for hardware specification and + /// for implementation details. + /// + public class HcSr04Connection : IDisposable + { + #region Fields + + private static readonly TimeSpan triggerTime = TimeSpanUtility.FromMicroseconds(10); + private static readonly TimeSpan echoUpTimeout = TimeSpan.FromMilliseconds(500); + + private readonly IOutputBinaryPin triggerPin; + private readonly IInputBinaryPin echoPin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The trigger pin. + /// The echo pin. + public HcSr04Connection(IOutputBinaryPin triggerPin, IInputBinaryPin echoPin) + { + this.triggerPin = triggerPin; + this.echoPin = echoPin; + + Timeout = DefaultTimeout; + + try + { + GetDistance(); + } catch {} + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Properties + + /// + /// The default timeout (50ms). + /// + /// Maximum time (if no obstacle) is 38ms. + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromMilliseconds(50); + + /// + /// Gets or sets the timeout for distance measure. + /// + /// + /// The timeout. + /// + public TimeSpan Timeout { get; set; } + + #endregion + + #region Methods + + /// + /// Gets the distance. + /// + /// The distance. + public Length GetDistance() + { + triggerPin.Write(true); + Timer.Sleep(triggerTime); + triggerPin.Write(false); + + var upTime = echoPin.Time(true, echoUpTimeout, Timeout); + return Units.Velocity.Sound.ToDistance(upTime) / 2; + } + + /// + /// Closes the connection. + /// + public void Close() + { + triggerPin.Dispose(); + echoPin.Dispose(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Distance/HcSr04/Units.cs b/Raspberry.IO.Components/Sensors/Distance/HcSr04/Units.cs new file mode 100644 index 0000000..37022a6 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Distance/HcSr04/Units.cs @@ -0,0 +1,37 @@ +using System; +using UnitsNet; + +namespace Raspberry.IO.Components.Sensors.Distance.HcSr04 +{ + internal static class Units + { + /// + /// Velocity related conversions + /// + public static class Velocity + { + /// + /// Sound velocity related conversions + /// + public static class Sound + { + #region Methods + + /// + /// Converts a time to a distance. + /// + /// The time. + /// The distance travelled by sound in one second, in meters. + public static Length ToDistance(TimeSpan time) + { + if (time < TimeSpan.Zero) + return Length.FromMeters(double.MinValue); + + return Length.FromMeters(time.TotalMilliseconds * 340 / 1000); + } + + #endregion + } + } + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Light/BH1750Connection.cs b/Raspberry.IO.Components/Sensors/Light/BH1750Connection.cs new file mode 100644 index 0000000..d840259 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Light/BH1750Connection.cs @@ -0,0 +1,53 @@ +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.Timers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Raspberry.IO.Components.Sensors.Light +{ + namespace RPI.Sensor.Sensors.Light + { + public class BH1750Connection + { + public I2cDeviceConnection Connection { get; set; } + public BH1750Connection(I2cDeviceConnection connection) + { + Connection = connection; + } + + public void SetOff() + { + Connection.Write(0x00); + } + public void SetOn() + { + Connection.Write(0x01); + } + public void Reset() + { + Connection.Write(0x07); + } + + public double GetData() + { + Connection.Write(0x10); + HighResolutionTimer.Sleep(TimeSpanUtility.FromMicroseconds(150 * 1000)); + byte[] readBuf = Connection.Read(2); + + var valf = readBuf[0] << 8; + valf |= readBuf[1]; + return valf / 1.2 * (69 / 69) / 1; + + // var valf = ((readBuf[0] << 8) | readBuf[1]) / 1.2; + // return valf; + + // return Math.Round(valf / (2 * 1.2), 2); + + } + + } + } + +} diff --git a/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Data.cs b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Data.cs new file mode 100644 index 0000000..f02a310 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Data.cs @@ -0,0 +1,11 @@ +namespace Raspberry.IO.Components.Sensors.Pressure.Bmp085 +{ + /// + /// Represents data from a . + /// + public struct Bmp085Data + { + public UnitsNet.Temperature Temperature; + public UnitsNet.Pressure Pressure; + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2CConnection.cs b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2CConnection.cs new file mode 100644 index 0000000..4285643 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2CConnection.cs @@ -0,0 +1,269 @@ +#region References + +using System; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Pressure.Bmp085 +{ + /// + /// Represents an I2C connection to a BMP085 barometer / thermometer. + /// + public class Bmp085I2cConnection + { + #region Fields + + private readonly I2cDeviceConnection connection; + private Bmp085Precision precision = Bmp085Precision.Standard; + + private short ac1; + private short ac2; + private short ac3; + private ushort ac4; + private ushort ac5; + private ushort ac6; + + private short b1; + private short b2; + private short mb; + private short mc; + private short md; + + private static readonly TimeSpan lowDelay = TimeSpan.FromMilliseconds(5); + private static readonly TimeSpan highDelay = TimeSpan.FromMilliseconds(14); + private static readonly TimeSpan highestDelay = TimeSpan.FromMilliseconds(26); + private static readonly TimeSpan defaultDelay = TimeSpan.FromMilliseconds(8); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The connection. + public Bmp085I2cConnection(I2cDeviceConnection connection) + { + this.connection = connection; + Initialize(); + } + + #endregion + + #region Properties + + public const int DefaultAddress = 0x77; + + public Bmp085Precision Precision + { + get { return precision; } + set { precision = value; } + } + + #endregion + + #region Methods + + /// + /// Gets the pressure. + /// + /// The pressure. + public UnitsNet.Pressure GetPressure() + { + return GetData().Pressure; + } + + /// + /// Gets the temperature. + /// + /// The temperature. + public UnitsNet.Temperature GetTemperature() + { + // Do not use GetData here since it would imply useless I/O and computation. + var rawTemperature = GetRawTemperature(); + var b5 = ComputeB5(rawTemperature); + + return UnitsNet.Temperature.FromDegreesCelsius((double)((b5 + 8) >> 4) / 10); + } + + /// + /// Gets the data. + /// + /// The data. + public Bmp085Data GetData() + { + var rawTemperature = GetRawTemperature(); + var rawPressure = GetRawPressure(); + + var b5 = ComputeB5(rawTemperature); + + // do pressure calcs + var b6 = b5 - 4000; + var x1 = (b2*((b6*b6) >> 12)) >> 11; + var x2 = (ac2*b6) >> 11; + var x3 = x1 + x2; + var b3 = (((ac1*4 + x3) << (int) precision) + 2)/4; + + x1 = (ac3*b6) >> 13; + x2 = (b1*((b6*b6) >> 12)) >> 16; + x3 = ((x1 + x2) + 2) >> 2; + var b4 = (ac4*(uint) (x3 + 32768)) >> 15; + var b7 = (uint) ((rawPressure - b3)*(uint) (50000UL >> (int) precision)); + + int p; + if (b4 == 0) + p = int.MaxValue; + else if (b7 < 0x80000000) + p = (int) ((b7*2)/b4); + else + p = (int) ((b7/b4)*2); + + x1 = (p >> 8)*(p >> 8); + x1 = (x1*3038) >> 16; + x2 = (-7357*p) >> 16; + + return new Bmp085Data + { + Pressure = UnitsNet.Pressure.FromPascals(p + ((x1 + x2 + 3791) >> 4)), + Temperature = UnitsNet.Temperature.FromDegreesCelsius((double)((b5 + 8) >> 4) / 10) + }; + } + + #endregion + + #region Private Helpers + + private static class Interop + { + public const byte CAL_AC1 = 0xAA; // R Calibration data (16 bits) + public const byte CAL_AC2 = 0xAC; // R Calibration data (16 bits) + public const byte CAL_AC3 = 0xAE; // R Calibration data (16 bits) + public const byte CAL_AC4 = 0xB0; // R Calibration data (16 bits) + public const byte CAL_AC5 = 0xB2; // R Calibration data (16 bits) + public const byte CAL_AC6 = 0xB4; // R Calibration data (16 bits) + public const byte CAL_B1 = 0xB6; // R Calibration data (16 bits) + public const byte CAL_B2 = 0xB8; // R Calibration data (16 bits) + public const byte CAL_MB = 0xBA; // R Calibration data (16 bits) + public const byte CAL_MC = 0xBC; // R Calibration data (16 bits) + public const byte CAL_MD = 0xBE; // R Calibration data (16 bits) + + public const byte CONTROL = 0xF4; + public const byte TEMPDATA = 0xF6; + public const byte PRESSUREDATA = 0xF6; + public const byte READTEMPCMD = 0x2E; + public const byte READPRESSURECMD = 0x34; + } + + private void Initialize() + { + if (precision > Bmp085Precision.Highest) + precision = Bmp085Precision.Highest; + + if (ReadByte(0xD0) != 0x55) + throw new InvalidOperationException("Device is not a BMP085 barometer"); + + /* read calibration data */ + ac1 = ReadInt16(Interop.CAL_AC1); + ac2 = ReadInt16(Interop.CAL_AC2); + ac3 = ReadInt16(Interop.CAL_AC3); + ac4 = ReadUInt16(Interop.CAL_AC4); + ac5 = ReadUInt16(Interop.CAL_AC5); + ac6 = ReadUInt16(Interop.CAL_AC6); + + b1 = ReadInt16(Interop.CAL_B1); + b2 = ReadInt16(Interop.CAL_B2); + + mb = ReadInt16(Interop.CAL_MB); + mc = ReadInt16(Interop.CAL_MC); + md = ReadInt16(Interop.CAL_MD); + } + + private ushort GetRawTemperature() + { + WriteByte(Interop.CONTROL, Interop.READTEMPCMD); + HighResolutionTimer.Sleep(lowDelay); + + return ReadUInt16(Interop.TEMPDATA); + } + + private uint GetRawPressure() + { + WriteByte(Interop.CONTROL, (byte)(Interop.READPRESSURECMD + ((int)precision << 6))); + + switch (precision) + { + case Bmp085Precision.Low: + HighResolutionTimer.Sleep(lowDelay); + break; + + case Bmp085Precision.High: + HighResolutionTimer.Sleep(highDelay); + break; + + case Bmp085Precision.Highest: + HighResolutionTimer.Sleep(highestDelay); + break; + + default: + HighResolutionTimer.Sleep(defaultDelay); + break; + } + + var msb = ReadByte(Interop.PRESSUREDATA); + var lsb = ReadByte(Interop.PRESSUREDATA + 1); + var xlsb = ReadByte(Interop.PRESSUREDATA + 2); + var raw = ((msb << 16) + (lsb << 8) + xlsb) >> (8 - (int) precision); + + return (uint)raw; + } + + private int ComputeB5(int ut) + { + var x1 = (ut - ac6)*ac5 >> 15; + + if (x1 + md == 0) + return int.MaxValue; + + var x2 = (mc << 11)/(x1 + md); + return x1 + x2; + } + + private byte ReadByte(byte address) + { + return ReadBytes(address, 1)[0]; + } + + private byte[] ReadBytes(byte address, int byteCount) + { + connection.WriteByte(address); + return connection.Read(byteCount); + } + + private ushort ReadUInt16(byte address) + { + var bytes = ReadBytes(address, 2); + unchecked + { + return (ushort)((bytes[0] << 8) + bytes[1]); + } + } + + private short ReadInt16(byte address) + { + var bytes = ReadBytes(address, 2); + unchecked + { + return (short)((bytes[0] << 8) + bytes[1]); + } + } + + private void WriteByte(byte address, byte data) + { + connection.Write(address, data); + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2cConnectionExtensionMethods.cs b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2cConnectionExtensionMethods.cs new file mode 100644 index 0000000..867275a --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085I2cConnectionExtensionMethods.cs @@ -0,0 +1,43 @@ +#region References + +using System; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Pressure.Bmp085 +{ + /// + /// Provides extension methods for . + /// + public static class Bmp085I2cConnectionExtensionMethods + { + #region Methods + + /// + /// Gets the sea-level pressure. + /// + /// The BMP085 connection. + /// The current altitude. + /// The pressure. + public static UnitsNet.Pressure GetSealevelPressure(this Bmp085I2cConnection connection, Length currentAltitude) + { + var pressure = connection.GetPressure(); + return UnitsNet.Pressure.FromPascals(pressure.Pascals / Math.Pow(1.0 - currentAltitude.Meters/44330, 5.255)); + } + + /// + /// Gets the altitude. + /// + /// The BMP085 connection. + /// The sealevel pressure. + /// The altitude + public static Length GetAltitude(this Bmp085I2cConnection connection, UnitsNet.Pressure sealevelPressure) + { + var pressure = connection.GetPressure(); + return Length.FromMeters(44330 * (1.0 - Math.Pow(pressure.Pascals / sealevelPressure.Pascals, 1 / 5.255))); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Precision.cs b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Precision.cs new file mode 100644 index 0000000..3bf1d80 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Pressure/Bmp085/Bmp085Precision.cs @@ -0,0 +1,10 @@ +namespace Raspberry.IO.Components.Sensors.Pressure.Bmp085 +{ + public enum Bmp085Precision + { + Low = 0, + Standard = 1, + High = 2, + Highest = 3 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/ResistiveDivider.cs b/Raspberry.IO.Components/Sensors/ResistiveDivider.cs new file mode 100644 index 0000000..b4aad9d --- /dev/null +++ b/Raspberry.IO.Components/Sensors/ResistiveDivider.cs @@ -0,0 +1,36 @@ +using System; +using UnitsNet; + +namespace Raspberry.IO.Components.Sensors +{ + public static class ResistiveDivider + { + /// + /// Gets the conversion function for the upper resistor of a voltage divider. + /// + /// The lower resistor value. + /// + /// The function. + /// + public static Func ForUpperResistor(ElectricResistance lowerResistorValue) + { + return v => v.Relative != 0 + ? lowerResistorValue * (double)((1 - v.Relative) / v.Relative) + : ElectricResistance.FromOhms(double.MaxValue); + } + + /// + /// Gets the conversion function for the lower resistor of a voltage divider. + /// + /// The upper resistor value. + /// + /// The function. + /// + public static Func ForLowerResistor(ElectricResistance upperResistorValue) + { + return v => v.Relative != 1 + ? upperResistorValue * (double)(v.Relative / (1 - v.Relative)) + : ElectricResistance.FromOhms(double.MaxValue); + } + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht11Connection.cs b/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht11Connection.cs new file mode 100644 index 0000000..00a8710 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht11Connection.cs @@ -0,0 +1,49 @@ +#region References + +using System; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Temperature.Dht +{ + /// + /// Represents a connection to a DHT11 sensor. + /// + public class Dht11Connection : DhtConnection + { + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The pin. + /// if set to true, DHT is automatically started. Default value is true. + public Dht11Connection(IInputOutputBinaryPin pin, bool autoStart = true) : base(pin, autoStart) { } + + #endregion + + #region Protected Methods + + protected override TimeSpan DefaultSamplingInterval + { + get { return TimeSpan.FromSeconds(1); } + } + + protected override TimeSpan WakeupInterval + { + get { return TimeSpan.FromMilliseconds(18); } + } + + protected override DhtData GetDhtData(int temperatureValue, int humidityValue) + { + return new DhtData + { + RelativeHumidity = Ratio.FromPercent(humidityValue/256d), + Temperature = UnitsNet.Temperature.FromDegreesCelsius(temperatureValue/256d) + }; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht22Connection.cs b/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht22Connection.cs new file mode 100644 index 0000000..0b4b7e7 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Dht/Dht22Connection.cs @@ -0,0 +1,49 @@ +#region References + +using System; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Temperature.Dht +{ + /// + /// Represents a connection to a DHT22 sensor. + /// + public class Dht22Connection : DhtConnection + { + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The pin. + /// if set to true, DHT is automatically started. Default value is true. + public Dht22Connection(IInputOutputBinaryPin pin, bool autoStart = true) : base(pin, autoStart) { } + + #endregion + + #region Protected Methods + + protected override TimeSpan DefaultSamplingInterval + { + get { return TimeSpan.FromSeconds(2); } + } + + protected override TimeSpan WakeupInterval + { + get { return TimeSpan.FromMilliseconds(1); } + } + + protected override DhtData GetDhtData(int temperatureValue, int humidityValue) + { + return new DhtData + { + RelativeHumidity = Ratio.FromPercent(humidityValue/10d), + Temperature = UnitsNet.Temperature.FromDegreesCelsius(temperatureValue/10d) + }; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtConnection.cs b/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtConnection.cs new file mode 100644 index 0000000..b41f59b --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtConnection.cs @@ -0,0 +1,236 @@ +#region References + +using System; +using System.Globalization; +using Common.Logging; +using Raspberry.IO.GeneralPurpose; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Temperature.Dht +{ + /// + /// Represents a base class for connections to a DHT-11 or DHT-22 humidity / temperature sensor. + /// + /// + /// Requires a fast input/output switch (such as ). + /// Based on , + /// Datasheet : . + /// + public abstract class DhtConnection : IDisposable + { + #region Fields + + private readonly IInputOutputBinaryPin pin; + private TimeSpan samplingInterval; + + private DateTime previousRead; + private bool started; + + private static readonly TimeSpan timeout = TimeSpan.FromMilliseconds(100); + private static readonly TimeSpan bitSetUptime = new TimeSpan(10 * (26 +70) / 2); // 26µs for "0", 70µs for "1" + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The pin. + /// if set to true, DHT is automatically started. Default value is true. + protected DhtConnection(IInputOutputBinaryPin pin, bool autoStart = true) + { + this.pin = pin; + + if (autoStart) + Start(); + else + Stop(); + } + + ~DhtConnection() + { + Close(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Close(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the sampling interval. + /// + /// + /// The sampling interval. + /// + public TimeSpan SamplingInterval + { + get { return samplingInterval != TimeSpan.Zero ? samplingInterval : DefaultSamplingInterval; } + set { samplingInterval = value; } + } + + #endregion + + #region Method + + /// + /// Starts the DHT sensor. If not called, sensor will be automatically enabled before getting data. + /// + public void Start() + { + started = true; + pin.Write(true); + previousRead = DateTime.UtcNow; + } + + /// + /// Stops the DHT sensor. If not called, sensor will be automatically disabled after getting data. + /// + public void Stop() + { + pin.Write(false); + started = false; + } + + /// + /// Gets the data. + /// + /// The DHT data. + public DhtData GetData() + { + if (!started) + { + pin.Write(true); + previousRead = DateTime.UtcNow; + } + + DhtData data = null; + var tryCount = 0; + while (data == null && tryCount++ <= 10) + { + try + { + data = TryGetData(); + data.AttemptCount = tryCount; + } + catch(Exception ex) + { + var logger = LogManager.GetLogger(); + logger.Error( + CultureInfo.InvariantCulture, + h => h("Failed to read data from DHT11, try {0}", tryCount), + ex); + } + } + + if (!started) + pin.Write(false); + + return data; + } + + /// + /// Closes this instance. + /// + public void Close() + { + GC.SuppressFinalize(this); + pin.Dispose(); + } + + #endregion + + #region Protected Methods + + protected abstract DhtData GetDhtData(int temperatureValue, int humidityValue); + + protected abstract TimeSpan DefaultSamplingInterval { get; } + + protected abstract TimeSpan WakeupInterval { get; } + + #endregion + + #region Private Helpers + + private DhtData TryGetData() + { + // Prepare buffer + var data = new byte[5]; + for (var i = 0; i < 5; i++) + data[i] = 0; + + var remainingSamplingInterval = SamplingInterval - (DateTime.UtcNow - previousRead); + if (remainingSamplingInterval > TimeSpan.Zero) + HighResolutionTimer.Sleep(remainingSamplingInterval); + + // Prepare for reading + try + { + // Measure required by host : pull down then pull up + pin.Write(false); + HighResolutionTimer.Sleep(WakeupInterval); + pin.Write(true); + + // Read acknowledgement from DHT + pin.Wait(true, timeout); + pin.Wait(false, timeout); + + // Read 40 bits output, or time-out + var cnt = 7; + var idx = 0; + for (var i = 0; i < 40; i++) + { + pin.Wait(true, timeout); + var start = DateTime.UtcNow; + pin.Wait(false, timeout); + + // Determine whether bit is "1" or "0" + if (DateTime.UtcNow - start > bitSetUptime) + data[idx] |= (byte)(1 << cnt); + + if (cnt == 0) + { + idx++; // next byte + cnt = 7; // restart at MSB + } + else + cnt--; + } + } + finally + { + // Prepare for next reading + previousRead = DateTime.UtcNow; + pin.Write(true); + } + + var checkSum = data[0] + data[1] + data[2] + data[3]; + if ((checkSum & 0xff) != data[4]) + throw new InvalidChecksumException("Invalid checksum on DHT data", data[4], (checkSum & 0xff)); + + var sign = 1; + if ((data[2] & 0x80) != 0) // negative temperature + { + data[2] = (byte)(data[2] & 0x7F); + sign = -1; + } + + var humidity = (data[0] << 8) + data[1]; + var temperature = sign * ((data[2] << 8) + data[3]); + + return GetDhtData(temperature, humidity); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtData.cs b/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtData.cs new file mode 100644 index 0000000..c1bb0f4 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Dht/DhtData.cs @@ -0,0 +1,9 @@ +namespace Raspberry.IO.Components.Sensors.Temperature.Dht +{ + public class DhtData + { + public int AttemptCount; + public UnitsNet.Temperature Temperature; + public UnitsNet.Ratio RelativeHumidity; + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Dht/InvalidChecksumException.cs b/Raspberry.IO.Components/Sensors/Temperature/Dht/InvalidChecksumException.cs new file mode 100644 index 0000000..69bb139 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Dht/InvalidChecksumException.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; + +namespace Raspberry.IO.Components.Sensors.Temperature.Dht +{ + public class InvalidChecksumException : Exception + { + private readonly long expectedValue; + private readonly long actualValue; + + public InvalidChecksumException(long expectedValue, long actualValue) : base(GetMessage(null, expectedValue, actualValue)) + { + this.expectedValue = expectedValue; + this.actualValue = actualValue; + } + + public InvalidChecksumException(string message, long expectedValue, long actualValue) : base(GetMessage(message, expectedValue, actualValue)) + { + this.expectedValue = expectedValue; + this.actualValue = actualValue; + } + + public InvalidChecksumException(string message, Exception innerException, long expectedValue, long actualValue) : base(GetMessage(message, expectedValue, actualValue), innerException) + { + this.expectedValue = expectedValue; + this.actualValue = actualValue; + } + + public long ExpectedValue + { + get { return expectedValue; } + } + + public long ActualValue + { + get { return actualValue; } + } + + private static string GetMessage(string message, long expectedValue, long actualValue) + { + var valueMessage = string.Format(CultureInfo.InvariantCulture, "Expected {0}, found {1}", expectedValue, actualValue); + return !string.IsNullOrEmpty(message) + ? message + ". " + valueMessage + : valueMessage; + } + } +} \ No newline at end of file diff --git a/Raspberry.IO.Components/Sensors/Temperature/Ds18b20/Ds18b20Connection.cs b/Raspberry.IO.Components/Sensors/Temperature/Ds18b20/Ds18b20Connection.cs new file mode 100644 index 0000000..8d7ebeb --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Ds18b20/Ds18b20Connection.cs @@ -0,0 +1,116 @@ +#region References + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Temperature.Ds18b20 +{ + /// + /// Represents a connection to a DS18B20 temperature sensor. + /// + /// See . + public class Ds18b20Connection : IDisposable + { + #region Fields + + private const string baseDir = @"/sys/bus/w1/devices/"; + + private readonly string deviceFolder; + private string deviceFile { get {return deviceFolder + @"/w1_slave";} } + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public Ds18b20Connection(string deviceId) + { + deviceFolder = baseDir + deviceId; + if (!Directory.Exists(deviceFolder)) + throw new ArgumentException(string.Format("Sensor with Id {0} not found. {1}", deviceId, ModprobeExceptionMessage), deviceId); + } + + /// + /// Initializes a new instance of the class. + /// + public Ds18b20Connection(int deviceIndex) + { + var deviceFolders = Directory.GetDirectories(baseDir, "28*").ToList(); + var deviceCount = deviceFolders.Count(); + if (deviceCount == 0) + throw new InvalidOperationException(string.Format("No sensors were found in {0}. {1}", baseDir, ModprobeExceptionMessage)); + + if (deviceCount <= deviceIndex) + throw new IndexOutOfRangeException(string.Format("There were only {0} devices found, so index {1} is invalid", deviceCount, deviceIndex)); + + deviceFolder = deviceFolders[deviceIndex]; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Gets the temperature. + /// + /// The temperature. + public UnitsNet.Temperature GetTemperature() + { + var lines = File.ReadAllLines(deviceFile); + while (!lines[0].Trim().EndsWith("YES")) + { + Thread.Sleep(2); + lines = File.ReadAllLines(deviceFile); + } + + var equalsPos = lines[1].IndexOf("t=", StringComparison.InvariantCultureIgnoreCase); + if (equalsPos == -1) + throw new InvalidOperationException("Unable to read temperature"); + + var temperatureString = lines[1].Substring(equalsPos + 2); + + return UnitsNet.Temperature.FromDegreesCelsius(double.Parse(temperatureString, CultureInfo.InvariantCulture) / 1000.0); + } + + /// + /// Closes this instance. + /// + public void Close() + { + } + + #endregion + + #region Private Helpers + + private string ModprobeExceptionMessage + { + get + { + var sb = new StringBuilder(); + sb.AppendLine("Make sure you have loaded the required kernel models."); + sb.AppendFormat("\tmodprobe w1-gpio{0}", Environment.NewLine); + sb.AppendFormat("\tmodprobe w1-therm{0}", Environment.NewLine); + return sb.ToString(); + } + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Sensors/Temperature/Tmp36/Tmp36Connection.cs b/Raspberry.IO.Components/Sensors/Temperature/Tmp36/Tmp36Connection.cs new file mode 100644 index 0000000..b2ebdd2 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/Temperature/Tmp36/Tmp36Connection.cs @@ -0,0 +1,68 @@ +#region References + +using System; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors.Temperature.Tmp36 +{ + /// + /// Represents a connection to a TMP36 temperature sensor. + /// + /// See . + public class Tmp36Connection : IDisposable + { + #region Fields + + private readonly IInputAnalogPin inputPin; + private readonly ElectricPotential referenceVoltage; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The input pin. + /// The reference voltage. + public Tmp36Connection(IInputAnalogPin inputPin, ElectricPotential referenceVoltage) + { + this.inputPin = inputPin; + this.referenceVoltage = referenceVoltage; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Gets the temperature. + /// + /// The temperature. + public UnitsNet.Temperature GetTemperature() + { + var voltage = referenceVoltage * (double)inputPin.Read().Relative; + return UnitsNet.Temperature.FromDegreesCelsius(voltage.Volts * 100 - 50); + } + + /// + /// Closes this instance. + /// + public void Close() + { + inputPin.Dispose(); + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/Sensors/VariableResistiveDividerConnection.cs b/Raspberry.IO.Components/Sensors/VariableResistiveDividerConnection.cs new file mode 100644 index 0000000..76cd899 --- /dev/null +++ b/Raspberry.IO.Components/Sensors/VariableResistiveDividerConnection.cs @@ -0,0 +1,68 @@ +#region References + +using System; +using UnitsNet; + +#endregion + +namespace Raspberry.IO.Components.Sensors +{ + /// + /// Represents a connection to an analog value coming from a resistive voltage divider + /// + public class VariableResistiveDividerConnection : IDisposable + { + #region Fields + + private readonly IInputAnalogPin analogPin; + private readonly Func resistorEvalFunc; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The analog pin. + /// The resistor eval function. + /// Methods from should be used. + public VariableResistiveDividerConnection(IInputAnalogPin analogPin, Func resistorEvalFunc) + { + this.analogPin = analogPin; + this.resistorEvalFunc = resistorEvalFunc; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Gets the electric resistance. + /// + /// The resistance value. + public ElectricResistance GetResistance() + { + var value = analogPin.Read(); + return resistorEvalFunc(value); + } + + /// + /// Closes this instance. + /// + public void Close() + { + analogPin.Dispose(); + } + + #endregion + } +} diff --git a/Raspberry.IO.Components/app.config b/Raspberry.IO.Components/app.config new file mode 100644 index 0000000..c3db251 --- /dev/null +++ b/Raspberry.IO.Components/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Raspberry.IO.Components/packages.config b/Raspberry.IO.Components/packages.config new file mode 100644 index 0000000..5b70f76 --- /dev/null +++ b/Raspberry.IO.Components/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose.nuspec b/Raspberry.IO.GeneralPurpose.nuspec new file mode 100644 index 0000000..69486f1 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose.nuspec @@ -0,0 +1,32 @@ + + + + Raspberry.IO.GeneralPurpose + 0.0.0 + Raspberry.IO.GeneralPurpose + Eric Bézine + Raspberry-Sharp + http://opensource.org/licenses/GPL-2.0 + https://github.com/raspberry-sharp/raspberry-sharp-io/wiki/Raspberry.IO + https://raw.github.com/raspberry-sharp/raspberry-sharp-io/master/Icon.png + true + + Raspberry.IO is a set of Mono/.NET assembly providing access to Raspberry Pi I/O features. + + Raspberry.IO is a set of Mono/.NET assembly providing access to Raspberry Pi I/O features. + en-US + Raspberry Pi Mono GPIO + + + + + + + + + + + + + + diff --git a/Raspberry.IO.GeneralPurpose/Behaviors/PinsBehavior.cs b/Raspberry.IO.GeneralPurpose/Behaviors/PinsBehavior.cs index 9eb7cee..fb1ac5b 100644 --- a/Raspberry.IO.GeneralPurpose/Behaviors/PinsBehavior.cs +++ b/Raspberry.IO.GeneralPurpose/Behaviors/PinsBehavior.cs @@ -1,8 +1,9 @@ #region References +using System; using System.Collections.Generic; using System.Linq; -using System.Threading; +using Raspberry.Timers; #endregion @@ -15,7 +16,7 @@ public abstract class PinsBehavior { #region Fields - private readonly Timer timer; + private readonly ITimer timer; private int currentStep; #endregion @@ -29,9 +30,10 @@ public abstract class PinsBehavior protected PinsBehavior(IEnumerable configurations) { Configurations = configurations.ToArray(); - Interval = 250; - timer = new Timer(OnTimer, null, Timeout.Infinite, Timeout.Infinite); + timer = Timer.Create(); + timer.Interval = TimeSpan.FromMilliseconds(250); + timer.Action = OnTimer; } #endregion @@ -49,7 +51,11 @@ protected PinsBehavior(IEnumerable configurations) /// /// The interval. /// - public int Interval { get; set; } + public TimeSpan Interval + { + get { return timer.Interval; } + set { timer.Interval = value; } + } #endregion @@ -90,12 +96,12 @@ internal void Start(GpioConnection connection) connection[pinConfiguration] = false; currentStep = GetFirstStep(); - timer.Change(0, Interval); + timer.Start(TimeSpan.Zero); } internal void Stop() { - timer.Change(Timeout.Infinite, Timeout.Infinite); + timer.Stop(); foreach (var pinConfiguration in Configurations) Connection[pinConfiguration] = false; @@ -105,12 +111,12 @@ internal void Stop() #region Private Helpers - private void OnTimer(object state) + private void OnTimer() { ProcessStep(currentStep); if (!TryGetNextStep(ref currentStep)) { - Thread.Sleep(Interval); + Timer.Sleep(Interval); Stop(); } } diff --git a/Raspberry.IO.GeneralPurpose/Configuration/GpioConnectionConfigurationSection.cs b/Raspberry.IO.GeneralPurpose/Configuration/GpioConnectionConfigurationSection.cs index e315d44..51cf93c 100644 --- a/Raspberry.IO.GeneralPurpose/Configuration/GpioConnectionConfigurationSection.cs +++ b/Raspberry.IO.GeneralPurpose/Configuration/GpioConnectionConfigurationSection.cs @@ -11,6 +11,15 @@ namespace Raspberry.IO.GeneralPurpose.Configuration /// public class GpioConnectionConfigurationSection : ConfigurationSection { + #region Constants + + /// + /// The default poll interval, in milliseconds. + /// + public const decimal DefaultPollInterval = 50.0m; + + #endregion + #region Properties /// @@ -26,6 +35,36 @@ public string DriverTypeName set { this["driver"] = value; } } + /// + /// Gets or sets the board connector revision. + /// + /// + /// The board revision, 0 for automatic detection, 1 for model B rev1; 2 for model B rev2 and model A, 3 for model B+, A+ and higher. + /// + [ConfigurationProperty("boardConnectorRevision", DefaultValue = 0)] + public int BoardConnectorRevision + { + get { return (int)this["boardConnectorRevision"]; } + set { this["boardConnectorRevision"] = value; } + } + + /// + /// Gets or sets the poll interval, in milliseconds. + /// + /// + /// The poll interval, in millisecond. + /// + /// + /// Default value is 50ms. + /// Values lower than 1ms may be specified on Raspberry Pi using decimal notation. + /// + [ConfigurationProperty("pollInterval", DefaultValue = "50")] + public decimal PollInterval + { + get { return (decimal)this["pollInterval"]; } + set { this["pollInterval"] = value; } + } + #endregion } } diff --git a/Raspberry.IO.GeneralPurpose/ConnectedPin.cs b/Raspberry.IO.GeneralPurpose/ConnectedPin.cs new file mode 100644 index 0000000..16bfed7 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/ConnectedPin.cs @@ -0,0 +1,110 @@ +#region References + +using System; +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a connected pin. + /// + public class ConnectedPin + { + #region Fields + + private readonly GpioConnection connection; + private readonly HashSet> events = new HashSet>(); + + #endregion + + #region Instance Management + + internal ConnectedPin(GpioConnection connection, PinConfiguration pinConfiguration) + { + this.connection = connection; + Configuration = pinConfiguration; + } + + #endregion + + #region Properties + + /// + /// Gets the configuration. + /// + public PinConfiguration Configuration { get; private set; } + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// + /// true if enabled; otherwise, false. + /// + public bool Enabled + { + get { return connection[Configuration]; } + set { connection[Configuration] = value; } + } + + #endregion + + #region Methods + + /// + /// Toggles this pin. + /// + public void Toggle() + { + connection.Toggle(Configuration); + } + + /// + /// Blinks the pin. + /// + /// The blink duration. + public void Blink(TimeSpan duration = new TimeSpan()) + { + connection.Blink(Configuration, duration); + } + + #endregion + + #region Events + + /// + /// Occurs when pin status changed. + /// + public event EventHandler StatusChanged + { + add + { + if (events.Count == 0) + connection.PinStatusChanged += ConnectionPinStatusChanged; + events.Add(value); + } + remove + { + events.Remove(value); + if (events.Count == 0) + connection.PinStatusChanged -= ConnectionPinStatusChanged; + } + } + + #endregion + + #region Private Helpers + + private void ConnectionPinStatusChanged(object sender, PinStatusEventArgs pinStatusEventArgs) + { + if (pinStatusEventArgs.Configuration.Pin != Configuration.Pin) + return; + + foreach (var eventHandler in events) + eventHandler(sender, pinStatusEventArgs); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/ConnectedPins.cs b/Raspberry.IO.GeneralPurpose/ConnectedPins.cs new file mode 100644 index 0000000..bad1787 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/ConnectedPins.cs @@ -0,0 +1,91 @@ +#region References + +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents connected pins. + /// + public class ConnectedPins : IEnumerable + { + #region Fields + + private readonly GpioConnection connection; + + #endregion + + #region Instance Management + + internal ConnectedPins(GpioConnection connection) + { + this.connection = connection; + } + + #endregion + + #region Properties + + /// + /// Gets the status of the specified pin. + /// + public ConnectedPin this[ProcessorPin pin] + { + get { return new ConnectedPin(connection, connection.GetConfiguration(pin)); } + } + + /// + /// Gets the status of the specified pin. + /// + public ConnectedPin this[string name] + { + get { return new ConnectedPin(connection, connection.GetConfiguration(name)); } + } + + /// + /// Gets the status of the specified pin. + /// + public ConnectedPin this[ConnectorPin pin] + { + get { return this[pin.ToProcessor()]; } + } + + /// + /// Gets the status of the specified pin. + /// + public ConnectedPin this[PinConfiguration pin] + { + get { return new ConnectedPin(connection, pin); } + } + + #endregion + + #region Methods + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Gets the enumerator. + /// + /// The enumerator. + public IEnumerator GetEnumerator() + { + return connection.Configurations.Select(c => new ConnectedPin(connection, c)).GetEnumerator(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/ConnectorPin.cs b/Raspberry.IO.GeneralPurpose/ConnectorPin.cs index 5af625c..01f3fc4 100644 --- a/Raspberry.IO.GeneralPurpose/ConnectorPin.cs +++ b/Raspberry.IO.GeneralPurpose/ConnectorPin.cs @@ -5,27 +5,432 @@ namespace Raspberry.IO.GeneralPurpose /// public enum ConnectorPin { + #region Raspberry Pi connectors + + /// + /// Connector P1, pin 3. + /// P1Pin3, + + /// + /// Connector P1, pin 3. + /// + P1Pin03 = P1Pin3, + + /// + /// Connector P1, pin 5. + /// P1Pin5, + + /// + /// Connector P1, pin 5. + /// + P1Pin05 = P1Pin5, + + /// + /// Connector P1, pin 7. + /// P1Pin7, + + /// + /// Connector P1, pin 7. + /// + P1Pin07 = P1Pin7, + + /// + /// Connector P1, pin 8. + /// P1Pin8, + + /// + /// Connector P1, pin 8. + /// + P1Pin08 = P1Pin8, + + /// + /// Connector P1, pin 10. + /// P1Pin10, + + /// + /// Connector P1, pin 11. + /// P1Pin11, + + /// + /// Connector P1, pin 12. + /// P1Pin12, + + /// + /// Connector P1, pin 13. + /// P1Pin13, + + /// + /// Connector P1, pin 15. + /// P1Pin15, + + /// + /// Connector P1, pin 16. + /// P1Pin16, + + /// + /// Connector P1, pin 18. + /// P1Pin18, + + /// + /// Connector P1, pin 19. + /// P1Pin19, + + /// + /// Connector P1, pin 21. + /// P1Pin21, + + /// + /// Connector P1, pin 22. + /// P1Pin22, + + /// + /// Connector P1, pin 23. + /// P1Pin23, + + /// + /// Connector P1, pin 24. + /// P1Pin24, + + /// + /// Connector P1, pin 26. + /// P1Pin26, + + // Pins 27+ exist starting from Model B+ + + /// + /// Connector P1, pin 27. + /// + P1Pin27, + + /// + /// Connector P1, pin 28. + /// + P1Pin28, + + /// + /// Connector P1, pin 29. + /// + P1Pin29, + + /// + /// Connector P1, pin 31. + /// + P1Pin31, + + /// + /// Connector P1, pin 32. + /// + P1Pin32, + + /// + /// Connector P1, pin 33. + /// + P1Pin33, + + /// + /// Connector P1, pin 35. + /// + P1Pin35, + + /// + /// Connector P1, pin 36. + /// + P1Pin36, + + /// + /// Connector P1, pin 37. + /// + P1Pin37, + + /// + /// Connector P1, pin 38. + /// + P1Pin38, + + /// + /// Connector P1, pin 40. + /// + P1Pin40, + + + // P5 Connector exist on Rev2 boards (no longer on B+) + + /// + /// Connector P5, pin 3. + /// P5Pin3, + + /// + /// Connector P5, pin 3. + /// + P5Pin03 = P5Pin3, + + /// + /// Connector P5, pin 4. + /// P5Pin4, + + /// + /// Connector P5, pin 4. + /// + P5Pin04 = P5Pin4, + + /// + /// Connector P5, pin 5. + /// P5Pin5, - P5Pin6 + + /// + /// Connector P5, pin 5. + /// + P5Pin05 = P5Pin5, + + /// + /// Connector P5, pin 6. + /// + P5Pin6, + + /// + /// Connector P5, pin 6. + /// + P5Pin06 = P5Pin6, + + #endregion + + #region CubieTruck/CubieBoard3 connectors + + // CubieTruck/CubieBoard3 Connector CN8 + + /// + /// Connector CN8, Pin 5 (PC19) + /// + CB3_CN8Pin5, + + /// + /// Connector CN8, Pin 5 (PC19) + /// + CB3_CN8Pin05 = CB3_CN8Pin5, + + /// + /// Connector CN8, Pin 6 (PC21) + /// + CB3_CN8Pin6, + + /// + /// Connector CN8, Pin 6 (PC21) + /// + CB3_CN8Pin06 = CB3_CN8Pin6, + + /// + /// Connector CN8, Pin 7 (PC20) + /// + CB3_CN8Pin7, + + /// + /// Connector CN8, Pin 7 (PC20) + /// + CB3_CN8Pin07 = CB3_CN8Pin7, + + /// + /// Connector CN8, Pin 8 (PC22) + /// + CB3_CN8Pin8, + + /// + /// Connector CN8, Pin 8 (PC22) + /// + CB3_CN8Pin08 = CB3_CN8Pin8, + + /// + /// Connector CN8, Pin 9 (PB14) + /// + CB3_CN8Pin9, + + /// + /// Connector CN8, Pin 9 (PB14) + /// + CB3_CN8Pin09 = CB3_CN8Pin9, + + /// + /// Connector CN8, Pin 10 (PB16) + /// + CB3_CN8Pin10, + + /// + /// Connector CN8, Pin 11 (PB15) + /// + CB3_CN8Pin11, + + /// + /// Connector CN8, Pin 12 (PB17) + /// + CB3_CN8Pin12, + + /// + /// Connector CN8, Pin 15 (PI20) + /// + CB3_CN8Pin15, + + /// + /// Connector CN8, Pin 16 (PI14) + /// + CB3_CN8Pin16, + + /// + /// Connector CN8, Pin 17 (PI21) + /// + CB3_CN8Pin17, + + /// + /// Connector CN8, Pin 18 (PI15) + /// + CB3_CN8Pin18, + + /// + /// Connector CN8, Pin 19 (PI3) + /// + CB3_CN8Pin19, + + /// + /// Connector CN8, Pin 20 (PB3) + /// + CB3_CN8Pin20, + + /// + /// Connector CN8, Pin 21 (PB2) + /// + CB3_CN8Pin21, + + /// + /// Connector CN8, Pin 22 (PB4) + /// + CB3_CN8Pin22, + + /// + /// Connector CN8, Pin 23 (PB18) + /// + CB3_CN8Pin23, + + /// + /// Connector CN8, Pin 25 (PB19) + /// + CB3_CN8Pin25, + + // CubieTruck/CubieBoard3 Connector CN9 + + /// + /// Connector CN9, Pin 3 (PG0) + /// + CB3_CN9Pin3, + + /// + /// Connector CN9, Pin 3 (PG0) + /// + CB3_CN9Pin03 = CB3_CN9Pin3, + + /// + /// Connector CN9, Pin 4 (PG3) + /// + CB3_CN9Pin4, + + /// + /// Connector CN9, Pin 4 (PG3) + /// + CB3_CN9Pin04 = CB3_CN9Pin4, + + /// + /// Connector CN9, Pin 5 (PG2) + /// + CB3_CN9Pin5, + + /// + /// Connector CN9, Pin 5 (PG2) + /// + CB3_CN9Pin05 = CB3_CN9Pin5, + + /// + /// Connector CN9, Pin 6 (PG1) + /// + CB3_CN9Pin6, + + /// + /// Connector CN9, Pin 6 (PG1) + /// + CB3_CN9Pin06 = CB3_CN9Pin6, + + /// + /// Connector CN9, Pin 7 (PG4) + /// + CB3_CN9Pin7, + + /// + /// Connector CN9, Pin 7 (PG4) + /// + CB3_CN9Pin07 = CB3_CN9Pin7, + + /// + /// Connector CN9, Pin 8 (PG5) + /// + CB3_CN9Pin8, + + /// + /// Connector CN9, Pin 8 (PG5) + /// + CB3_CN9Pin08 = CB3_CN9Pin8, + + /// + /// Connector CN9, Pin 9 (PG6) + /// + CB3_CN9Pin9, + + /// + /// Connector CN9, Pin 9 (PG6) + /// + CB3_CN9Pin09 = CB3_CN9Pin9, + + /// + /// Connector CN9, Pin 10 (PG7) + /// + CB3_CN9Pin10, + + /// + /// Connector CN9, Pin 11 (PG8) + /// + CB3_CN9Pin11, + + /// + /// Connector CN9, Pin 12 (PG9) + /// + CB3_CN9Pin12, + + /// + /// Connector CN9, Pin 13 (PG10) + /// + CB3_CN9Pin13, + + /// + /// Connector CN9, Pin 14 (PG11) + /// + CB3_CN9Pin14 + + #endregion } } \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/FileConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/FileConnectionDriver.cs deleted file mode 100644 index 39e049a..0000000 --- a/Raspberry.IO.GeneralPurpose/FileConnectionDriver.cs +++ /dev/null @@ -1,84 +0,0 @@ -#region References - -using System.IO; - -#endregion - -namespace Raspberry.IO.GeneralPurpose -{ - /// - /// Represents a connection driver using files. - /// - public class FileConnectionDriver : IConnectionDriver - { - #region Fields - - private const string gpioPath = "/sys/class/gpio"; - - #endregion - - #region Methods - - /// - /// Modified the status of a pin. - /// - /// The pin. - /// The pin status. - public void Write(ProcessorPin pin, bool value) - { - var gpioId = string.Format("gpio{0}", (int) pin); - var filePath = Path.Combine(gpioId, "value"); - using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, filePath), false)) - streamWriter.Write(value ? "1" : "0"); - } - - /// - /// Reads the status of the specified pin. - /// - /// The pin. - /// - /// The pin status. - /// - public bool Read(ProcessorPin pin) - { - var gpioId = string.Format("gpio{0}", (int) pin); - var filePath = Path.Combine(gpioId, "value"); - - using (var streamReader = new StreamReader(new FileStream(Path.Combine(gpioPath, filePath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) - { - var rawValue = streamReader.ReadToEnd(); - return !string.IsNullOrEmpty(rawValue) && rawValue[0] == '1'; - } - } - - /// - /// Exports the specified pin. - /// - /// The pin. - public void Export(PinConfiguration pin) - { - var gpioId = string.Format("gpio{0}", (int) pin.Pin); - if (Directory.Exists(Path.Combine(gpioPath, gpioId))) - Unexport(pin); - - using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "export"), false)) - streamWriter.Write((int) pin.Pin); - - var filePath = Path.Combine(gpioId, "direction"); - using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, filePath), false)) - streamWriter.Write(pin.Direction == PinDirection.Input ? "in" : "out"); - } - - /// - /// Unexports the specified pin. - /// - /// The pin. - public void Unexport(PinConfiguration pin) - { - using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "unexport"), false)) - streamWriter.Write((int) pin.Pin); - } - - #endregion - } -} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/FileGpioConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/FileGpioConnectionDriver.cs new file mode 100644 index 0000000..e0b2d83 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/FileGpioConnectionDriver.cs @@ -0,0 +1,235 @@ +#region References + +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a connection driver using files. + /// + public class FileGpioConnectionDriver : IGpioConnectionDriver + { + #region Fields + + private const string gpioPath = "/sys/class/gpio"; + private static readonly Dictionary gpioPathList = new Dictionary(); + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public FileGpioConnectionDriver() + { + if (Environment.OSVersion.Platform != PlatformID.Unix) + throw new NotSupportedException("FileGpioConnectionDriver is only supported in Unix"); + } + + #endregion + + #region Methods + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + GpioConnectionDriverCapabilities IGpioConnectionDriver.GetCapabilities() + { + return GetCapabilities(); + } + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + public static GpioConnectionDriverCapabilities GetCapabilities() + { + return GpioConnectionDriverCapabilities.None; + } + + /// + /// Allocates the specified pin. + /// + /// The pin. + /// The direction. + public void Allocate(ProcessorPin pin, PinDirection direction) + { + Release(pin); + + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "export"), false)) + streamWriter.Write((int)pin); + + if (!gpioPathList.ContainsKey(pin)) + { + var gpio = new FileGpioHandle { GpioPath = GuessGpioPath(pin) }; + gpioPathList.Add(pin, gpio); + } + + var filePath = Path.Combine(gpioPathList[pin].GpioPath, "direction"); + try { + SetPinDirection(filePath, direction); + } + catch (UnauthorizedAccessException) { + // program hasn't been started as root, give it a second to correct file permissions + Thread.Sleep(TimeSpan.FromSeconds(1)); + SetPinDirection(filePath, direction); + } + + gpioPathList[pin].GpioStream = new FileStream(Path.Combine(GuessGpioPath(pin), "value"), FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite); + } + + /// + /// Sets the pin resistor. + /// + /// The pin. + /// The resistor. + public void SetPinResistor(ProcessorPin pin, PinResistor resistor) + { + throw new NotSupportedException("Resistor are not supported by file GPIO connection driver"); + } + + /// + /// Sets the detected edges on an input pin. + /// + /// The pin. + /// The edges. + /// + /// + /// By default, both edges may be detected on input pins. + /// + public void SetPinDetectedEdges(ProcessorPin pin, PinDetectedEdges edges) + { + throw new NotSupportedException("Edge detection is not supported by file GPIO connection driver"); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// The pin. + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// + /// If timeout is set to , a 5 second timeout is used. + /// + public void Wait(ProcessorPin pin, bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var startWait = DateTime.UtcNow; + if (timeout == TimeSpan.Zero) + timeout = DefaultTimeout; + + while (Read(pin) != waitForUp) + { + if (DateTime.UtcNow - startWait >= timeout) + throw new TimeoutException("A timeout occurred while waiting for pin status to change"); + } + } + + /// + /// Releases the specified pin. + /// + /// The pin. + public void Release(ProcessorPin pin) + { + if (gpioPathList.ContainsKey(pin) && gpioPathList[pin].GpioStream != null) + { + gpioPathList[pin].GpioStream.Close(); + gpioPathList[pin].GpioStream = null; + } + if (Directory.Exists(GuessGpioPath(pin))) + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "unexport"), false)) + streamWriter.Write((int)pin); + } + + /// + /// Modified the status of a pin. + /// + /// The pin. + /// The pin status. + public void Write(ProcessorPin pin, bool value) + { + gpioPathList[pin].GpioStream.Seek(0, SeekOrigin.Begin); + gpioPathList[pin].GpioStream.WriteByte(value ? (byte)'1' : (byte)'0'); + gpioPathList[pin].GpioStream.Flush(); + } + + /// + /// Reads the status of the specified pin. + /// + /// The pin. + /// + /// The pin status. + /// + public bool Read(ProcessorPin pin) + { + gpioPathList[pin].GpioStream.Seek(0, SeekOrigin.Begin); + var rawValue = (char)gpioPathList[pin].GpioStream.ReadByte(); + gpioPathList[pin].GpioStream.Flush(); + return rawValue == '1'; + } + + /// + /// Reads the status of the specified pins. + /// + /// The pins. + /// + /// The pins status. + /// + public ProcessorPins Read(ProcessorPins pins) + { + return pins.Enumerate() + .Select(p => Read(p) ? (ProcessorPins) ((uint) 1 << (int) p) : ProcessorPins.None) + .Aggregate( + ProcessorPins.None, + (a, p) => a | p); + } + + #endregion + + #region Private Helpers + + private static void SetPinDirection(string fullFilePath, PinDirection direction) { + using (var streamWriter = new StreamWriter(fullFilePath, false)) { + streamWriter.Write(direction == PinDirection.Input + ? "in" + : "out"); + } + } + + private static string GuessGpioPath(ProcessorPin pin) + { + // by default use Raspberry Pi pin path format + string gpioId = string.Format("gpio{0}", (int)pin); + string pinPath = Path.Combine(gpioPath, gpioId); + // verify/lookup pin path + if (!Directory.Exists(pinPath)) + { + // check for sunxi gpio path name format (eg. "gpio11_pe10", "gpio2_pi21", ...) + string[] dirs = Directory.GetDirectories(gpioPath); + foreach (string d in dirs) + { + if (d.StartsWith(pinPath + "_")) + { + pinPath = d; + break; + } + } + } + return pinPath; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/FileGpioHandle.cs b/Raspberry.IO.GeneralPurpose/FileGpioHandle.cs new file mode 100644 index 0000000..73f32d2 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/FileGpioHandle.cs @@ -0,0 +1,26 @@ +using System.IO; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a handle on a GPIO file + /// + public class FileGpioHandle + { + /// + /// Gets or sets the gpio path. + /// + /// + /// The gpio path. + /// + public string GpioPath { get; set; } + + /// + /// Gets or sets the gpio stream. + /// + /// + /// The gpio stream. + /// + public Stream GpioStream { get; set; } + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioBinaryPinExtensionMethods.cs b/Raspberry.IO.GeneralPurpose/GpioBinaryPinExtensionMethods.cs new file mode 100644 index 0000000..0440072 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioBinaryPinExtensionMethods.cs @@ -0,0 +1,90 @@ +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Provides extensions methods GPIO binary pins. + /// + public static class GpioBinaryPinExtensionMethods + { + #region Methods + + /// + /// Gets an output pin on the current driver. + /// + /// The driver. + /// The pin. + /// The GPIO output binary pin. + public static GpioOutputBinaryPin Out(this IGpioConnectionDriver driver, ConnectorPin pin) + { + return driver.Out(pin.ToProcessor()); + } + + /// + /// Gets an output pin on the current driver. + /// + /// The driver. + /// The pin. + /// The GPIO output binary pin. + public static GpioOutputBinaryPin Out(this IGpioConnectionDriver driver, ProcessorPin pin) + { + return new GpioOutputBinaryPin(driver, pin); + } + + /// + /// Gets an input pin on the current driver. + /// + /// The driver. + /// The pin. + /// The resistor. + /// + /// The GPIO input binary pin. + /// + public static GpioInputBinaryPin In(this IGpioConnectionDriver driver, ConnectorPin pin, PinResistor resistor = PinResistor.None) + { + return driver.In(pin.ToProcessor(), resistor); + } + + /// + /// Gets an input pin on the current driver. + /// + /// The driver. + /// The pin. + /// The resistor. + /// + /// The GPIO input binary pin. + /// + public static GpioInputBinaryPin In(this IGpioConnectionDriver driver, ProcessorPin pin, PinResistor resistor = PinResistor.None) + { + return new GpioInputBinaryPin(driver, pin, resistor); + } + + /// + /// Gets a bidirectional pin on the current driver. + /// + /// The driver. + /// The pin. + /// The resistor. + /// + /// The GPIO input binary pin. + /// + public static GpioInputOutputBinaryPin InOut(this IGpioConnectionDriver driver, ConnectorPin pin, PinResistor resistor = PinResistor.None) + { + return driver.InOut(pin.ToProcessor(), resistor); + } + + /// + /// Gets a bidirectional pin on the current driver. + /// + /// The driver. + /// The pin. + /// The resistor. + /// + /// The GPIO input binary pin. + /// + public static GpioInputOutputBinaryPin InOut(this IGpioConnectionDriver driver, ProcessorPin pin, PinResistor resistor = PinResistor.None) + { + return new GpioInputOutputBinaryPin(driver, pin, resistor); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioConnection.cs b/Raspberry.IO.GeneralPurpose/GpioConnection.cs index 02cd46c..32074aa 100644 --- a/Raspberry.IO.GeneralPurpose/GpioConnection.cs +++ b/Raspberry.IO.GeneralPurpose/GpioConnection.cs @@ -1,12 +1,9 @@ #region References using System; -using System.Collections; using System.Collections.Generic; -using System.Configuration; using System.Linq; -using System.Threading; -using Raspberry.IO.GeneralPurpose.Configuration; +using Raspberry.Timers; #endregion @@ -19,18 +16,17 @@ public class GpioConnection : IDisposable { #region Fields + private readonly GpioConnectionSettings settings; + private readonly Dictionary pinConfigurations; private readonly Dictionary namedPins; - private readonly Timer timer; + private readonly ITimer timer; private readonly Dictionary pinValues = new Dictionary(); private readonly Dictionary> pinEvents = new Dictionary>(); - private readonly Dictionary pinRawValues = new Dictionary(); - /// - /// Gets the default blink duration, in milliseconds. - /// - public const int DefaultBlinkDuration = 250; + private ProcessorPins inputPins = ProcessorPins.None; + private ProcessorPins pinRawValues = ProcessorPins.None; #endregion @@ -40,68 +36,42 @@ public class GpioConnection : IDisposable /// Initializes a new instance of the class. /// /// The pins. - public GpioConnection(params PinConfiguration[] pins) : this(true, null, (IEnumerable) pins){} - - /// - /// Initializes a new instance of the class. - /// - /// The pins. - public GpioConnection(IEnumerable pins) : this(true, null, pins){} - - /// - /// Initializes a new instance of the class. - /// - /// The driver. - /// The pins. - public GpioConnection(IConnectionDriver driver, params PinConfiguration[] pins) : this(true, driver, (IEnumerable) pins){} - - /// - /// Initializes a new instance of the class. - /// - /// The driver. - /// The pins. - public GpioConnection(IConnectionDriver driver, IEnumerable pins) : this(true, driver, pins){} - - /// - /// Initializes a new instance of the class. - /// - /// if set to true, connection is opened on creation. - /// The pins. - public GpioConnection(bool open, params PinConfiguration[] pins) : this(open, null, (IEnumerable) pins){} + public GpioConnection(params PinConfiguration[] pins) : this(null, (IEnumerable) pins){} /// /// Initializes a new instance of the class. /// - /// if set to true, connection is opened on creation. - /// The driver. /// The pins. - public GpioConnection(bool open, IConnectionDriver driver, params PinConfiguration[] pins) : this(open, driver, (IEnumerable) pins){} + public GpioConnection(IEnumerable pins) : this(null, pins){} /// /// Initializes a new instance of the class. /// - /// if set to true, connection is opened on creation. + /// The settings. /// The pins. - public GpioConnection(bool open, IEnumerable pins) : this(open, null, pins){} + public GpioConnection(GpioConnectionSettings settings, params PinConfiguration[] pins) : this(settings, (IEnumerable) pins){} /// /// Initializes a new instance of the class. /// - /// if set to true, connection is opened on creation. - /// The driver. + /// The settings. /// The pins. - public GpioConnection(bool open, IConnectionDriver driver, IEnumerable pins) + public GpioConnection(GpioConnectionSettings settings, IEnumerable pins) { - Driver = driver ?? GetDefaultDriver(); + this.settings = settings ?? new GpioConnectionSettings(); Pins = new ConnectedPins(this); var pinList = pins.ToList(); pinConfigurations = pinList.ToDictionary(p => p.Pin); namedPins = pinList.Where(p => !string.IsNullOrEmpty(p.Name)).ToDictionary(p => p.Name); + + timer = Timer.Create(); - timer = new Timer(CheckInputPins, null, Timeout.Infinite, Timeout.Infinite); - if (open) + timer.Interval = this.settings.PollInterval; + timer.Action = CheckInputPins; + + if (this.settings.Opened) Open(); } @@ -122,11 +92,6 @@ void IDisposable.Dispose() /// public bool IsOpened { get; private set; } - /// - /// Gets the driver. - /// - public IConnectionDriver Driver { get; private set; } - /// /// Gets or sets the status of the pin having the specified name. /// @@ -162,7 +127,7 @@ public bool this[PinConfiguration pin] OnPinStatusChanged(new PinStatusEventArgs {Enabled = value, Configuration = pin}); } else - throw new InvalidOperationException("Input pin value cannot be set"); + throw new InvalidOperationException("Value of input pins cannot be modified"); } } @@ -196,9 +161,9 @@ public void Open() return; foreach (var pin in pinConfigurations.Values) - Export(pin); + Allocate(pin); - timer.Change(250, 50); + timer.Start(TimeSpan.FromMilliseconds(10)); IsOpened = true; } } @@ -213,9 +178,9 @@ public void Close() if (!IsOpened) return; - timer.Dispose(); + timer.Stop(); foreach (var pin in pinConfigurations.Values) - Unexport(pin); + Release(pin); IsOpened = false; } @@ -229,12 +194,14 @@ public void Clear() lock (pinConfigurations) { foreach (var pinConfiguration in pinConfigurations.Values) - Unexport(pinConfiguration); + Release(pinConfiguration); pinConfigurations.Clear(); namedPins.Clear(); - pinRawValues.Clear(); pinValues.Clear(); + + pinRawValues = ProcessorPins.None; + inputPins = ProcessorPins.None; } } @@ -256,7 +223,11 @@ public void Add(PinConfiguration pin) if (!string.IsNullOrEmpty(pin.Name)) namedPins.Add(pin.Name, pin); - Export(pin); + lock (timer) + { + if (IsOpened) + Allocate(pin); + } } } @@ -343,14 +314,20 @@ public void Remove(PinConfiguration configuration) { lock (pinConfigurations) { - Unexport(configuration); + lock (timer) + { + if (IsOpened) + Release(configuration); + } pinConfigurations.Remove(configuration.Pin); if (!string.IsNullOrEmpty(configuration.Name)) namedPins.Remove(configuration.Name); - - pinRawValues.Remove(configuration.Pin); pinValues.Remove(configuration.Pin); + + var pin = (ProcessorPins)((uint)1 << (int)configuration.Pin); + inputPins = inputPins & ~pin; + pinRawValues = pinRawValues & ~pin; } } @@ -394,11 +371,11 @@ public void Toggle(PinConfiguration configuration) /// Blinks the specified pin. /// /// Name of the pin. - /// The duration, in millisecond. - public void Blink(string pinName, int duration = DefaultBlinkDuration) + /// The duration. + public void Blink(string pinName, TimeSpan duration = new TimeSpan()) { Toggle(pinName); - Thread.Sleep(duration); + Sleep(duration); Toggle(pinName); } @@ -406,11 +383,11 @@ public void Blink(string pinName, int duration = DefaultBlinkDuration) /// Blinks the specified pin. /// /// The pin. - /// The duration, in millisecond. - public void Blink(ProcessorPin pin, int duration = DefaultBlinkDuration) + /// The duration. + public void Blink(ProcessorPin pin, TimeSpan duration = new TimeSpan()) { Toggle(pin); - Thread.Sleep(duration); + Sleep(duration); Toggle(pin); } @@ -418,11 +395,11 @@ public void Blink(ProcessorPin pin, int duration = DefaultBlinkDuration) /// Blinks the specified pin. /// /// The pin. - /// The duration, in millisecond. - public void Blink(ConnectorPin pin, int duration = DefaultBlinkDuration) + /// The duration. + public void Blink(ConnectorPin pin, TimeSpan duration = new TimeSpan()) { Toggle(pin); - Thread.Sleep(duration); + Sleep(duration); Toggle(pin); } @@ -430,11 +407,11 @@ public void Blink(ConnectorPin pin, int duration = DefaultBlinkDuration) /// Blinks the specified pin. /// /// The pin configuration. - /// The duration, in millisecond. - public void Blink(PinConfiguration configuration, int duration = DefaultBlinkDuration) + /// The duration. + public void Blink(PinConfiguration configuration, TimeSpan duration = new TimeSpan()) { Toggle(configuration); - Thread.Sleep(duration); + Sleep(duration); Toggle(configuration); } @@ -463,24 +440,39 @@ protected void OnPinStatusChanged(PinStatusEventArgs e) } #endregion + + #region Internal Methods - #region Private Helpers + internal PinConfiguration GetConfiguration(string pinName) + { + return namedPins[pinName]; + } - private IEnumerable Configurations + internal PinConfiguration GetConfiguration(ProcessorPin pin) + { + return pinConfigurations[pin]; + } + + internal IEnumerable Configurations { get { return pinConfigurations.Values; } } + + #endregion + + #region Private Helpers + + private IGpioConnectionDriver Driver + { + get { return settings.Driver; } + } - private static IConnectionDriver GetDefaultDriver() + private void Sleep(TimeSpan duration) { - var configurationSection = ConfigurationManager.GetSection("gpioConnection") as GpioConnectionConfigurationSection; - if (configurationSection != null && !string.IsNullOrEmpty(configurationSection.DriverTypeName)) - return (IConnectionDriver) Activator.CreateInstance(Type.GetType(configurationSection.DriverTypeName, true)); - else - return new MemoryConnectionDriver(); + Timer.Sleep(duration <= TimeSpan.Zero ? settings.BlinkDuration : duration); } - private void Export(PinConfiguration configuration) + private void Allocate(PinConfiguration configuration) { if (configuration.StatusChangedAction != null) { @@ -493,30 +485,37 @@ private void Export(PinConfiguration configuration) PinStatusChanged += handler; } - Driver.Export(configuration); + Driver.Allocate(configuration.Pin, configuration.Direction); var outputConfiguration = configuration as OutputPinConfiguration; if (outputConfiguration != null) - this[configuration.Pin] = outputConfiguration.GetEffective(outputConfiguration.Enabled); + this[configuration.Pin] = outputConfiguration.Enabled; else { - var pinValue = Driver.Read(configuration.Pin); - pinRawValues[configuration.Pin] = pinValue; + var inputConfiguration = (InputPinConfiguration) configuration; + var pinValue = Driver.Read(inputConfiguration.Pin); - var switchConfiguration = configuration as SwitchInputPinConfiguration; + var pin = (ProcessorPins)((uint)1 << (int)inputConfiguration.Pin); + inputPins = inputPins | pin; + pinRawValues = Driver.Read(inputPins); + + if (inputConfiguration.Resistor != PinResistor.None && (Driver.GetCapabilities() & GpioConnectionDriverCapabilities.CanSetPinResistor) > 0) + Driver.SetPinResistor(inputConfiguration.Pin, inputConfiguration.Resistor); + + var switchConfiguration = inputConfiguration as SwitchInputPinConfiguration; if (switchConfiguration != null) { - pinValues[configuration.Pin] = switchConfiguration.Enabled; - OnPinStatusChanged(new PinStatusEventArgs {Configuration = configuration, Enabled = pinValues[configuration.Pin]}); + pinValues[inputConfiguration.Pin] = switchConfiguration.Enabled; + OnPinStatusChanged(new PinStatusEventArgs { Configuration = inputConfiguration, Enabled = pinValues[inputConfiguration.Pin] }); } else { - pinValues[configuration.Pin] = configuration.GetEffective(pinValue); - OnPinStatusChanged(new PinStatusEventArgs { Configuration = configuration, Enabled = pinValues[configuration.Pin] }); + pinValues[inputConfiguration.Pin] = inputConfiguration.GetEffective(pinValue); + OnPinStatusChanged(new PinStatusEventArgs { Configuration = inputConfiguration, Enabled = pinValues[inputConfiguration.Pin] }); } } } - private void Unexport(PinConfiguration configuration) + private void Release(PinConfiguration configuration) { if (configuration.Direction == PinDirection.Output) { @@ -524,7 +523,7 @@ private void Unexport(PinConfiguration configuration) OnPinStatusChanged(new PinStatusEventArgs { Enabled = false, Configuration = configuration }); } - Driver.Unexport(configuration); + Driver.Release(configuration.Pin); EventHandler handler; if (pinEvents.TryGetValue(configuration.Pin, out handler)) @@ -534,205 +533,47 @@ private void Unexport(PinConfiguration configuration) } } - private void CheckInputPins(object state) + private void CheckInputPins() { - Dictionary newPinValues; + var newPinValues = Driver.Read(inputPins); + + var changes = newPinValues ^ pinRawValues; + if (changes == ProcessorPins.None) + return; - lock (pinConfigurations) + var notifiedConfigurations = new List(); + foreach (var np in changes.Enumerate()) { - newPinValues = pinConfigurations.Values - .Where(p => p.Direction == PinDirection.Input) - .Select(p => new {p.Pin, Value = Driver.Read(p.Pin)}) - .ToDictionary(p => p.Pin, p => p.Value); - } + var processorPin = (ProcessorPins) ((uint) 1 << (int) np); + var oldPinValue = (pinRawValues & processorPin) != ProcessorPins.None; + var newPinValue = (newPinValues & processorPin) != ProcessorPins.None; - foreach (var np in newPinValues) - { - var oldPinValue = pinRawValues[np.Key]; - var newPinValue = np.Value; - - pinRawValues[np.Key] = newPinValue; if (oldPinValue != newPinValue) { - var pin = (InputPinConfiguration) pinConfigurations[np.Key]; + var pin = (InputPinConfiguration) pinConfigurations[np]; var switchPin = pin as SwitchInputPinConfiguration; if (switchPin != null) { if (pin.GetEffective(newPinValue)) { - pinValues[np.Key] = !pinValues[np.Key]; - OnPinStatusChanged(new PinStatusEventArgs {Configuration = pin, Enabled = pinValues[np.Key]}); + pinValues[np] = !pinValues[np]; + notifiedConfigurations.Add(pin); } } else { - pinValues[np.Key] = pin.GetEffective(newPinValue); - OnPinStatusChanged(new PinStatusEventArgs {Configuration = pin, Enabled = pinValues[np.Key]}); + pinValues[np] = pin.GetEffective(newPinValue); + notifiedConfigurations.Add(pin); } } } - } - private PinConfiguration GetConfiguration(string pinName) - { - return namedPins[pinName]; - } + pinRawValues = newPinValues; - private PinConfiguration GetConfiguration(ProcessorPin pin) - { - return pinConfigurations[pin]; - } - - #endregion - - #region Inner Classes - - /// - /// Represents a connected pin. - /// - public class ConnectedPin - { - private readonly GpioConnection connection; - private readonly HashSet> events = new HashSet>(); - - /// - /// Initializes a new instance of the class. - /// - /// The connection. - /// The pin configuration. - public ConnectedPin(GpioConnection connection, PinConfiguration pinConfiguration) - { - this.connection = connection; - Configuration = pinConfiguration; - } - - /// - /// Gets the configuration. - /// - public PinConfiguration Configuration { get; private set; } - - /// - /// Gets or sets a value indicating whether this is enabled. - /// - /// - /// true if enabled; otherwise, false. - /// - public bool Enabled - { - get { return connection[Configuration]; } - set { connection[Configuration] = value; } - } - - /// - /// Toggles this pin. - /// - public void Toggle() - { - connection.Toggle(Configuration); - } - - /// - /// Blinks the pin. - /// - /// The blink duration, in millisecond. - public void Blink(int duration = DefaultBlinkDuration) - { - connection.Blink(Configuration, duration); - } - - /// - /// Occurs when pin status changed. - /// - public event EventHandler StatusChanged - { - add - { - if (events.Count == 0) - connection.PinStatusChanged += ConnectionPinStatusChanged; - events.Add(value); - } - remove - { - events.Remove(value); - if (events.Count == 0) - connection.PinStatusChanged -= ConnectionPinStatusChanged; - } - } - - private void ConnectionPinStatusChanged(object sender, PinStatusEventArgs pinStatusEventArgs) - { - if (pinStatusEventArgs.Configuration.Pin != Configuration.Pin) - return; - - foreach (var eventHandler in events) - eventHandler(sender, pinStatusEventArgs); - } - } - - /// - /// Represents connected pins. - /// - public class ConnectedPins : IEnumerable - { - private readonly GpioConnection connection; - - internal ConnectedPins(GpioConnection connection) - { - this.connection = connection; - } - - /// - /// Gets the status of the specified pin. - /// - public ConnectedPin this[ProcessorPin pin] - { - get { return new ConnectedPin(connection, connection.GetConfiguration(pin)); } - } - - /// - /// Gets the status of the specified pin. - /// - public ConnectedPin this[string name] - { - get { return new ConnectedPin(connection, connection.GetConfiguration(name)); } - } - - /// - /// Gets the status of the specified pin. - /// - public ConnectedPin this[ConnectorPin pin] - { - get { return this[pin.ToProcessor()]; } - } - - /// - /// Gets the status of the specified pin. - /// - public ConnectedPin this[PinConfiguration pin] - { - get { return new ConnectedPin(connection, pin); } - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - /// - /// Gets the enumerator. - /// - /// The enumerator. - public IEnumerator GetEnumerator() - { - return connection.Configurations.Select(c => new ConnectedPin(connection, c)).GetEnumerator(); - } + // Only fires events once all states have been modified. + foreach (var pin in notifiedConfigurations) + OnPinStatusChanged(new PinStatusEventArgs {Configuration = pin, Enabled = pinValues[pin.Pin]}); } #endregion diff --git a/Raspberry.IO.GeneralPurpose/GpioConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/GpioConnectionDriver.cs new file mode 100644 index 0000000..8cef799 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioConnectionDriver.cs @@ -0,0 +1,467 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using Raspberry.IO.Interop; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents the default connection driver that uses memory for accesses and files for edge detection. + /// + public class GpioConnectionDriver : IGpioConnectionDriver + { + #region Fields + + private const string gpioPath = "/sys/class/gpio"; + + private readonly IntPtr gpioAddress; + private readonly Dictionary pinResistors = new Dictionary(); + private readonly Dictionary pinPolls = new Dictionary(); + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + /// + /// The minimum timeout (1 milliseconds) + /// + public static readonly TimeSpan MinimumTimeout = TimeSpan.FromMilliseconds(1); + + private static readonly TimeSpan resistorSetDelay = TimeSpanUtility.FromMicroseconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public GpioConnectionDriver() { + using (var memoryFile = UnixFile.Open("/dev/mem", UnixFileMode.ReadWrite | UnixFileMode.Synchronized)) { + gpioAddress = MemoryMap.Create( + IntPtr.Zero, + Interop.BCM2835_BLOCK_SIZE, + MemoryProtection.ReadWrite, + MemoryFlags.Shared, + memoryFile.Descriptor, + GetProcessorBaseAddress(Board.Current.Processor)); + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~GpioConnectionDriver() + { + MemoryMap.Close(gpioAddress, Interop.BCM2835_BLOCK_SIZE); + } + + #endregion + + #region Methods + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + GpioConnectionDriverCapabilities IGpioConnectionDriver.GetCapabilities() + { + return GetCapabilities(); + } + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + public static GpioConnectionDriverCapabilities GetCapabilities() + { + return GpioConnectionDriverCapabilities.CanSetPinResistor | GpioConnectionDriverCapabilities.CanSetPinDetectedEdges; + } + + /// + /// Allocates the specified pin. + /// + /// The pin. + /// The direction. + public void Allocate(ProcessorPin pin, PinDirection direction) + { + var gpioId = string.Format("gpio{0}", (int)pin); + if (Directory.Exists(Path.Combine(gpioPath, gpioId))) + { + // Reinitialize pin virtual file + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "unexport"), false)) + streamWriter.Write((int) pin); + } + + // Export pin for file mode + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "export"), false)) + streamWriter.Write((int)pin); + + // Set the direction on the pin and update the exported list + SetPinMode(pin, direction == PinDirection.Input ? Interop.BCM2835_GPIO_FSEL_INPT : Interop.BCM2835_GPIO_FSEL_OUTP); + + // Set direction in pin virtual file + var filePath = Path.Combine(gpioId, "direction"); + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, filePath), false)) + streamWriter.Write(direction == PinDirection.Input ? "in" : "out"); + + if (direction == PinDirection.Input) + { + PinResistor pinResistor; + if (!pinResistors.TryGetValue(pin, out pinResistor) || pinResistor != PinResistor.None) + SetPinResistor(pin, PinResistor.None); + + SetPinDetectedEdges(pin, PinDetectedEdges.Both); + InitializePoll(pin); + } + } + + /// + /// Sets the pin resistor. + /// + /// The pin. + /// The resistor. + public void SetPinResistor(ProcessorPin pin, PinResistor resistor) + { + // Set the pullup/down resistor for a pin + // + // The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on + // the respective GPIO pins. These registers must be used in conjunction with the GPPUD + // register to effect GPIO Pull-up/down changes. The following sequence of events is + // required: + // 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither + // to remove the current Pull-up/down) + // 2. Wait 150 cycles ? this provides the required set-up time for the control signal + // 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to + // modify ? NOTE only the pads which receive a clock will be modified, all others will + // retain their previous state. + // 4. Wait 150 cycles ? this provides the required hold time for the control signal + // 5. Write to GPPUD to remove the control signal + // 6. Write to GPPUDCLK0/1 to remove the clock + // + // RPi has P1-03 and P1-05 with 1k8 pullup resistor + + uint pud; + switch (resistor) + { + case PinResistor.None: + pud = Interop.BCM2835_GPIO_PUD_OFF; + break; + case PinResistor.PullDown: + pud = Interop.BCM2835_GPIO_PUD_DOWN; + break; + case PinResistor.PullUp: + pud = Interop.BCM2835_GPIO_PUD_UP; + break; + + default: + throw new ArgumentOutOfRangeException("resistor", resistor, string.Format(CultureInfo.InvariantCulture, "{0} is not a valid value for pin resistor", resistor)); + } + + WriteResistor(pud); + HighResolutionTimer.Sleep(resistorSetDelay); + SetPinResistorClock(pin, true); + HighResolutionTimer.Sleep(resistorSetDelay); + WriteResistor(Interop.BCM2835_GPIO_PUD_OFF); + SetPinResistorClock(pin, false); + + pinResistors[pin] = PinResistor.None; + } + + /// + /// Sets the detected edges on an input pin. + /// + /// The pin. + /// The edges. + /// By default, both edges may be detected on input pins. + public void SetPinDetectedEdges(ProcessorPin pin, PinDetectedEdges edges) + { + var edgePath = Path.Combine(gpioPath, string.Format("gpio{0}/edge", (int)pin)); + using (var streamWriter = new StreamWriter(edgePath, false)) + streamWriter.Write(ToString(edges)); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// The pin. + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// + /// If timeout is set to , a 5 seconds timeout is used. + /// + public void Wait(ProcessorPin pin, bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var pinPoll = pinPolls[pin]; + if (Read(pin) == waitForUp) + return; + + var actualTimeout = GetActualTimeout(timeout); + + while (true) + { + // TODO: timeout after the remaining amount of time. + var waitResult = Interop.epoll_wait(pinPoll.PollDescriptor, pinPoll.OutEventPtr, 1, (int)actualTimeout.TotalMilliseconds); + if (waitResult > 0) + { + if (Read(pin) == waitForUp) + break; + } + else if (waitResult == 0) + throw new TimeoutException(string.Format(CultureInfo.InvariantCulture, "Operation timed out after waiting {0}ms for the pin {1} to be {2}", actualTimeout, pin, (waitForUp ? "up" : "down"))); + else + throw new IOException("Call to epoll_wait API failed"); + } + } + + /// + /// Releases the specified pin. + /// + /// The pin. + public void Release(ProcessorPin pin) + { + UninitializePoll(pin); + using (var streamWriter = new StreamWriter(Path.Combine(gpioPath, "unexport"), false)) + streamWriter.Write((int)pin); + + SetPinMode(pin, Interop.BCM2835_GPIO_FSEL_INPT); + } + + /// + /// Modified the status of a pin. + /// + /// The pin. + /// The pin status. + public void Write(ProcessorPin pin, bool value) + { + int shift; + var offset = Math.DivRem((int)pin, 32, out shift); + + var pinGroupAddress = gpioAddress + (int)((value ? Interop.BCM2835_GPSET0 : Interop.BCM2835_GPCLR0) + offset); + SafeWriteUInt32(pinGroupAddress, (uint)1 << shift); + } + + /// + /// Reads the status of the specified pin. + /// + /// The pin. + /// + /// The pin status. + /// + public bool Read(ProcessorPin pin) + { + int shift; + var offset = Math.DivRem((int)pin, 32, out shift); + + var pinGroupAddress = gpioAddress + (int)(Interop.BCM2835_GPLEV0 + offset); + var value = SafeReadUInt32(pinGroupAddress); + + return (value & (1 << shift)) != 0; + } + + /// + /// Reads the status of the specified pins. + /// + /// The pins. + /// + /// The pins status. + /// + public ProcessorPins Read(ProcessorPins pins) + { + var pinGroupAddress = gpioAddress + (int)(Interop.BCM2835_GPLEV0 + (uint)0 * 4); + var value = SafeReadUInt32(pinGroupAddress); + + return (ProcessorPins)((uint)pins & value); + } + + #endregion + + #region Private Methods + + private static uint GetProcessorBaseAddress(Processor processor) + { + switch (processor) + { + case Processor.Bcm2708: + return Interop.BCM2835_GPIO_BASE; + + case Processor.Bcm2709: + return Interop.BCM2836_GPIO_BASE; + + default: + throw new ArgumentOutOfRangeException("processor"); + } + } + + private static TimeSpan GetActualTimeout(TimeSpan timeout) + { + if (timeout > TimeSpan.Zero) + return timeout; + + if (timeout < TimeSpan.Zero) + return MinimumTimeout; + + return DefaultTimeout; + } + + private void InitializePoll(ProcessorPin pin) + { + lock (pinPolls) + { + PinPoll poll; + if (pinPolls.TryGetValue(pin, out poll)) + return; + + var pinPoll = new PinPoll(); + + pinPoll.PollDescriptor = Interop.epoll_create(1); + if (pinPoll.PollDescriptor < 0) + throw new IOException("Call to epoll_create(1) API failed with the following return value: " + pinPoll.PollDescriptor); + + var valuePath = Path.Combine(gpioPath, string.Format("gpio{0}/value", (int)pin)); + + pinPoll.FileDescriptor = UnixFile.OpenFileDescriptor(valuePath, UnixFileMode.ReadOnly | UnixFileMode.NonBlocking); + + var ev = new Interop.epoll_event + { + events = (Interop.EPOLLIN | Interop.EPOLLET | Interop.EPOLLPRI), + data = new Interop.epoll_data { fd = pinPoll.FileDescriptor } + }; + + pinPoll.InEventPtr = Marshal.AllocHGlobal(64); + Marshal.StructureToPtr(ev, pinPoll.InEventPtr, false); + + var controlResult = Interop.epoll_ctl(pinPoll.PollDescriptor, Interop.EPOLL_CTL_ADD, pinPoll.FileDescriptor, pinPoll.InEventPtr); + if (controlResult != 0) + throw new IOException("Call to epoll_ctl(EPOLL_CTL_ADD) API failed with the following return value: " + controlResult); + + pinPoll.OutEventPtr = Marshal.AllocHGlobal(64); + pinPolls[pin] = pinPoll; + } + } + + private void UninitializePoll(ProcessorPin pin) + { + PinPoll poll; + if (pinPolls.TryGetValue(pin, out poll)) + { + pinPolls.Remove(pin); + + var controlResult = poll.InEventPtr != IntPtr.Zero ? Interop.epoll_ctl(poll.PollDescriptor, Interop.EPOLL_CTL_DEL, poll.FileDescriptor, poll.InEventPtr) : 0; + + Marshal.FreeHGlobal(poll.InEventPtr); + Marshal.FreeHGlobal(poll.OutEventPtr); + + UnixFile.CloseFileDescriptor(poll.PollDescriptor); + UnixFile.CloseFileDescriptor(poll.FileDescriptor); + + if (controlResult != 0) + throw new IOException("Call to epoll_ctl(EPOLL_CTL_DEL) API failed with the following return value: " + controlResult); + } + } + + private static string ToString(PinDetectedEdges edges) + { + switch (edges) + { + case PinDetectedEdges.Both: + return "both"; + case PinDetectedEdges.Rising: + return "rising"; + case PinDetectedEdges.Falling: + return "falling"; + case PinDetectedEdges.None: + return "none"; + default: + throw new ArgumentOutOfRangeException("edges", edges, string.Format(CultureInfo.InvariantCulture, "{0} is not a valid value for edge detection", edges)); + } + } + + private void SetPinResistorClock(ProcessorPin pin, bool on) + { + int shift; + var offset = Math.DivRem((int)pin, 32, out shift); + + var clockAddress = gpioAddress + (int)(Interop.BCM2835_GPPUDCLK0 + offset); + SafeWriteUInt32(clockAddress, (uint)(on ? 1 : 0) << shift); + } + + private void WriteResistor(uint resistor) + { + var resistorPin = gpioAddress + (int)Interop.BCM2835_GPPUD; + SafeWriteUInt32(resistorPin, resistor); + } + + private void SetPinMode(ProcessorPin pin, uint mode) + { + // Function selects are 10 pins per 32 bit word, 3 bits per pin + var pinModeAddress = gpioAddress + (int)(Interop.BCM2835_GPFSEL0 + 4 * ((int)pin / 10)); + + var shift = 3 * ((int)pin % 10); + var mask = Interop.BCM2835_GPIO_FSEL_MASK << shift; + var value = mode << shift; + + WriteUInt32Mask(pinModeAddress, value, mask); + } + + private static void WriteUInt32Mask(IntPtr address, uint value, uint mask) + { + var v = SafeReadUInt32(address); + v = (v & ~mask) | (value & mask); + SafeWriteUInt32(address, v); + } + + private static uint SafeReadUInt32(IntPtr address) + { + // Make sure we dont return the _last_ read which might get lost + // if subsequent code changes to a different peripheral + var ret = ReadUInt32(address); + ReadUInt32(address); + + return ret; + } + + private static uint ReadUInt32(IntPtr address) + { + unchecked + { + return (uint)Marshal.ReadInt32(address); + } + } + + private static void SafeWriteUInt32(IntPtr address, uint value) + { + // Make sure we don't rely on the first write, which may get + // lost if the previous access was to a different peripheral. + WriteUInt32(address, value); + WriteUInt32(address, value); + } + + private static void WriteUInt32(IntPtr address, uint value) + { + unchecked + { + Marshal.WriteInt32(address, (int)value); + } + } + + private struct PinPoll + { + public int FileDescriptor; + public int PollDescriptor; + public IntPtr InEventPtr; + public IntPtr OutEventPtr; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioConnectionDriverCapabilities.cs b/Raspberry.IO.GeneralPurpose/GpioConnectionDriverCapabilities.cs new file mode 100644 index 0000000..2f04a2a --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioConnectionDriverCapabilities.cs @@ -0,0 +1,36 @@ +using System; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents capabilities of a driver. + /// + [Flags] + public enum GpioConnectionDriverCapabilities + { + /// + /// No advanced capability. + /// + None = 0, + + /// + /// The driver can set pin resistor + /// + CanSetPinResistor = 1, + + /// + /// The driver can set pin detected edges + /// + CanSetPinDetectedEdges = 2, + + /// + /// The driver can change pin direction rapidly. + /// + CanChangePinDirectionRapidly = 4, + + /// + /// The driver can work on third-party computers (not only Raspberry Pi) + /// + CanWorkOnThirdPartyComputers = 8 + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioConnectionDriverExtensionMethods.cs b/Raspberry.IO.GeneralPurpose/GpioConnectionDriverExtensionMethods.cs new file mode 100644 index 0000000..e6de449 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioConnectionDriverExtensionMethods.cs @@ -0,0 +1,39 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Provides extension methods for . + /// + public static class GpioConnectionDriverExtensionMethods + { + #region Methods + + /// + /// Waits for a pin to reach the specified state, then measures the time it remains in this state. + /// + /// The driver. + /// The measure pin. + /// if set to true, wait for the pin to be up. + /// The first phase timeout. + /// The second phase timeout. + /// + /// The time the pin remains up. + /// + public static decimal Time(this IGpioConnectionDriver driver, ProcessorPin pin, bool waitForUp = true, TimeSpan phase1Timeout = new TimeSpan(), TimeSpan phase2Timeout = new TimeSpan()) + { + driver.Wait(pin, waitForUp, phase1Timeout); + + var waitDown = DateTime.Now.Ticks; + driver.Wait(pin, !waitForUp, phase2Timeout); + + return (DateTime.Now.Ticks - waitDown)/10000m; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioConnectionSettings.cs b/Raspberry.IO.GeneralPurpose/GpioConnectionSettings.cs new file mode 100644 index 0000000..a44bc13 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioConnectionSettings.cs @@ -0,0 +1,175 @@ +#region References + +using System; +using System.Configuration; +using Raspberry.IO.GeneralPurpose.Configuration; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents settings for . + /// + public class GpioConnectionSettings + { + #region Fields + + private IGpioConnectionDriver driver; + + private TimeSpan blinkDuration; + private TimeSpan pollInterval; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public GpioConnectionSettings() + { + Driver = DefaultDriver; + BlinkDuration = DefaultBlinkDuration; + PollInterval = DefaultPollInterval; + Opened = true; + } + + #endregion + + #region Constants + + /// + /// Gets the default blink duration. + /// + public static readonly TimeSpan DefaultBlinkDuration = TimeSpan.FromMilliseconds(250); + + #endregion + + #region Properties + + /// + /// Gets or sets a value indicating whether this is opened on initialization. + /// + /// + /// true if opened on initialization; otherwise, false. + /// + public bool Opened { get; set; } + + /// + /// Gets or sets the duration of the blink. + /// + /// + /// The duration of the blink, in milliseconds. + /// + public TimeSpan BlinkDuration + { + get { return blinkDuration; } + set { blinkDuration = value >= TimeSpan.Zero ? value : DefaultBlinkDuration; } + } + + /// + /// Gets or sets the driver. + /// + /// + /// The driver. + /// + public IGpioConnectionDriver Driver + { + get { return driver; } + set { driver = value ?? DefaultDriver; } + } + + /// + /// Gets or sets the poll interval. + /// + /// + /// The poll interval. + /// + public TimeSpan PollInterval + { + get { return pollInterval; } + set { pollInterval = value >= TimeSpan.Zero ? value : DefaultPollInterval; } + } + + /// + /// Gets the default poll interval. + /// + public static TimeSpan DefaultPollInterval + { + get + { + var configurationSection = ConfigurationManager.GetSection("gpioConnection") as GpioConnectionConfigurationSection; + return TimeSpan.FromMilliseconds(configurationSection != null + ? (double)configurationSection.PollInterval + : (double)GpioConnectionConfigurationSection.DefaultPollInterval); + + } + } + + /// + /// Gets the board connector pinout. + /// + /// + /// The board connector pinout. + /// + public static ConnectorPinout ConnectorPinout + { + get + { + var configurationSection = ConfigurationManager.GetSection("gpioConnection") as GpioConnectionConfigurationSection; + if (configurationSection != null) + { + switch (configurationSection.BoardConnectorRevision) + { + case 1: + return ConnectorPinout.Rev1; + case 2: + return ConnectorPinout.Rev2; + case 3: + return ConnectorPinout.Plus; + } + } + + return Board.Current.ConnectorPinout; + } + } + + /// + /// Gets the default driver. + /// + public static IGpioConnectionDriver DefaultDriver + { + get + { + var configurationSection = ConfigurationManager.GetSection("gpioConnection") as GpioConnectionConfigurationSection; + return (configurationSection != null && !String.IsNullOrEmpty(configurationSection.DriverTypeName)) + ? (IGpioConnectionDriver) Activator.CreateInstance(Type.GetType(configurationSection.DriverTypeName, true)) + : GetBestDriver(Board.Current.IsRaspberryPi ? GpioConnectionDriverCapabilities.None : GpioConnectionDriverCapabilities.CanWorkOnThirdPartyComputers); + } + } + + #endregion + + #region Methods + + /// + /// Gets the best driver for the specified capabilities. + /// + /// The capabilities. + /// The best driver, if found; otherwise, null. + public static IGpioConnectionDriver GetBestDriver(GpioConnectionDriverCapabilities capabilities) + { + if ((GpioConnectionDriver.GetCapabilities() & capabilities) == capabilities) + return new GpioConnectionDriver(); + if ((MemoryGpioConnectionDriver.GetCapabilities() & capabilities) == capabilities) + return new MemoryGpioConnectionDriver(); + if ((FileGpioConnectionDriver.GetCapabilities() & capabilities) == capabilities) + return new FileGpioConnectionDriver(); + + return null; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioInputBinaryPin.cs b/Raspberry.IO.GeneralPurpose/GpioInputBinaryPin.cs new file mode 100644 index 0000000..43d8df1 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioInputBinaryPin.cs @@ -0,0 +1,73 @@ +using System; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a GPIO input binary pin. + /// + public class GpioInputBinaryPin : IInputBinaryPin + { + #region Fields + + private readonly IGpioConnectionDriver driver; + private readonly ProcessorPin pin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The driver. + /// The pin. + /// The resistor. + public GpioInputBinaryPin(IGpioConnectionDriver driver, ProcessorPin pin, PinResistor resistor = PinResistor.None) + { + this.driver = driver; + this.pin = pin; + + driver.Allocate(pin, PinDirection.Input); + if ((driver.GetCapabilities() & GpioConnectionDriverCapabilities.CanSetPinResistor) > 0) + driver.SetPinResistor(pin, resistor); + if ((driver.GetCapabilities() & GpioConnectionDriverCapabilities.CanSetPinDetectedEdges) > 0) + driver.SetPinDetectedEdges(pin, PinDetectedEdges.Both); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + driver.Release(pin); + } + + #endregion + + #region Methods + + /// + /// Reads the state of the pin. + /// + /// + /// true if the pin is in high state; otherwise, false. + /// + public bool Read() + { + return driver.Read(pin); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + public void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + driver.Wait(pin, waitForUp, timeout); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioInputOutputBinaryPin.cs b/Raspberry.IO.GeneralPurpose/GpioInputOutputBinaryPin.cs new file mode 100644 index 0000000..fe25b10 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioInputOutputBinaryPin.cs @@ -0,0 +1,118 @@ +using System; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a bidirectional pin on GPIO interface. + /// + public class GpioInputOutputBinaryPin : IInputOutputBinaryPin + { + #region Fields + + private readonly IGpioConnectionDriver driver; + private readonly ProcessorPin pin; + private readonly PinResistor resistor; + private PinDirection? direction; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The driver. + /// The pin. + /// The resistor. + public GpioInputOutputBinaryPin(IGpioConnectionDriver driver, ProcessorPin pin, PinResistor resistor = PinResistor.None) + { + this.driver = driver; + this.pin = pin; + this.resistor = resistor; + } + + #endregion + + #region Methods + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + public void Dispose() + { + if (direction.HasValue) + driver.Release(pin); + } + + /// + /// Reads this instance. + /// + /// The value. + public bool Read() + { + SetDirection(PinDirection.Input); + return driver.Read(pin); + } + + /// + /// Prepares the pin to act as an input. + /// + public void AsInput() + { + SetDirection(PinDirection.Input); + } + + /// + /// Prepares the pin to act as an output. + /// + public void AsOutput() + { + SetDirection(PinDirection.Output); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + public void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + SetDirection(PinDirection.Input); + driver.Wait(pin, waitForUp, timeout); + } + + /// + /// Writes the specified state. + /// + /// the state. + public void Write(bool state) + { + SetDirection(PinDirection.Output); + driver.Write(pin, state); + } + + #endregion + + #region Private Helpers + + private void SetDirection(PinDirection newDirection) + { + if (direction == newDirection) + return; + + if (direction.HasValue) + driver.Release(pin); + + driver.Allocate(pin, newDirection); + if (newDirection == PinDirection.Input + && resistor != PinResistor.None + && (driver.GetCapabilities() & GpioConnectionDriverCapabilities.CanSetPinResistor) != GpioConnectionDriverCapabilities.None) + driver.SetPinResistor(pin, resistor); + + direction = newDirection; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/GpioOutputBinaryPin.cs b/Raspberry.IO.GeneralPurpose/GpioOutputBinaryPin.cs new file mode 100644 index 0000000..19e4f29 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/GpioOutputBinaryPin.cs @@ -0,0 +1,56 @@ +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents an output pin on GPIO interface. + /// + public class GpioOutputBinaryPin : IOutputBinaryPin + { + #region Fields + + private readonly IGpioConnectionDriver driver; + private readonly ProcessorPin pin; + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The driver. + /// The pin. + /// The resistor. + public GpioOutputBinaryPin(IGpioConnectionDriver driver, ProcessorPin pin, PinResistor resistor = PinResistor.None) + { + this.driver = driver; + this.pin = pin; + + driver.Allocate(pin, PinDirection.Output); + if ((driver.GetCapabilities() & GpioConnectionDriverCapabilities.CanSetPinResistor) > 0) + driver.SetPinResistor(pin, resistor); + } + + #endregion + + #region Methods + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + driver.Release(pin); + } + + /// + /// Writes the specified state. + /// + /// The pin state. + public void Write(bool state) + { + driver.Write(pin, state); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/IConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/IConnectionDriver.cs deleted file mode 100644 index 7277a72..0000000 --- a/Raspberry.IO.GeneralPurpose/IConnectionDriver.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Raspberry.IO.GeneralPurpose -{ - /// - /// Provides an interface for connection drivers. - /// - public interface IConnectionDriver - { - #region Methods - - /// - /// Modified the status of a pin. - /// - /// The pin. - /// The pin status. - void Write(ProcessorPin pin, bool value); - - /// - /// Reads the status of the specified pin. - /// - /// The pin. - /// The pin status. - bool Read(ProcessorPin pin); - - /// - /// Exports the specified pin. - /// - /// The pin. - void Export(PinConfiguration pin); - - /// - /// Unexports the specified pin. - /// - /// The pin. - void Unexport(PinConfiguration pin); - - #endregion - } -} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/IGpioConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/IGpioConnectionDriver.cs new file mode 100644 index 0000000..5027196 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/IGpioConnectionDriver.cs @@ -0,0 +1,77 @@ +using System; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Provides an interface for connection drivers. + /// + public interface IGpioConnectionDriver + { + #region Methods + + /// + /// Gets driver capabilities. + /// + GpioConnectionDriverCapabilities GetCapabilities(); + + /// + /// Allocates the specified pin. + /// + /// The pin. + /// The direction. + void Allocate(ProcessorPin pin, PinDirection direction); + + /// + /// Sets the pin resistor. + /// + /// The pin. + /// The resistor. + void SetPinResistor(ProcessorPin pin, PinResistor resistor); + + /// + /// Sets the detected edges on an input pin. + /// + /// The pin. + /// The edges. + /// By default, both edges may be detected on input pins. + void SetPinDetectedEdges(ProcessorPin pin, PinDetectedEdges edges); + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// The pin. + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + void Wait(ProcessorPin pin, bool waitForUp = true, TimeSpan timeout = new TimeSpan()); + + /// + /// Releases the specified pin. + /// + /// The pin. + void Release(ProcessorPin pin); + + /// + /// Modified the status of a pin. + /// + /// The pin. + /// The pin status. + void Write(ProcessorPin pin, bool value); + + /// + /// Reads the status of the specified pin. + /// + /// The pin. + /// The pin status. + bool Read(ProcessorPin pin); + + /// + /// Reads the status of the specified pins. + /// + /// The pins. + /// The pins status. + ProcessorPins Read(ProcessorPins pins); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/InputPinConfiguration.cs b/Raspberry.IO.GeneralPurpose/InputPinConfiguration.cs index 415a0a8..1b510bc 100644 --- a/Raspberry.IO.GeneralPurpose/InputPinConfiguration.cs +++ b/Raspberry.IO.GeneralPurpose/InputPinConfiguration.cs @@ -25,6 +25,14 @@ public override PinDirection Direction get { return PinDirection.Input; } } + /// + /// Gets or sets the resistor. + /// + /// + /// The resistor. + /// + public PinResistor Resistor { get; set; } + #endregion } } \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/Interop.cs b/Raspberry.IO.GeneralPurpose/Interop.cs new file mode 100644 index 0000000..6abed9b --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/Interop.cs @@ -0,0 +1,115 @@ +#region References + +using System; +using System.Runtime.InteropServices; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + internal static class Interop + { + #region BCM2835 + + #region Constants + + public const uint BCM2835_PERI_BASE = 0x20000000; + public const uint BCM2835_GPIO_BASE = (BCM2835_PERI_BASE + 0x200000); + public const uint BCM2835_BSC0_BASE = (BCM2835_PERI_BASE + 0x205000); + public const uint BCM2835_BSC1_BASE = (BCM2835_PERI_BASE + 0x804000); + + public const uint BCM2836_PERI_BASE = 0x3F000000; + public const uint BCM2836_GPIO_BASE = (BCM2836_PERI_BASE + 0x200000); + public const uint BCM2836_BSC0_BASE = (BCM2836_PERI_BASE + 0x205000); + public const uint BCM2836_BSC1_BASE = (BCM2836_PERI_BASE + 0x804000); + + public const uint BCM2835_BLOCK_SIZE = (4 * 1024); + + public const uint BCM2835_BSC_C = 0x0000; + public const uint BCM2835_BSC_S = 0x0004; + public const uint BCM2835_BSC_DLEN = 0x0008; + public const uint BCM2835_BSC_A = 0x000c; + public const uint BCM2835_BSC_FIFO = 0x0010; + public const uint BCM2835_BSC_DIV = 0x0014; + + public const uint BCM2835_BSC_C_CLEAR_1 = 0x00000020; + public const uint BCM2835_BSC_C_CLEAR_2 = 0x00000010; + public const uint BCM2835_BSC_C_I2CEN = 0x00008000; + public const uint BCM2835_BSC_C_ST = 0x00000080; + public const uint BCM2835_BSC_C_READ = 0x00000001; + + public const uint BCM2835_BSC_S_CLKT = 0x00000200; + public const uint BCM2835_BSC_S_ERR = 0x00000100; + public const uint BCM2835_BSC_S_DONE = 0x00000002; + public const uint BCM2835_BSC_S_TXD = 0x00000010; + public const uint BCM2835_BSC_S_RXD = 0x00000020; + + public const uint BCM2835_BSC_FIFO_SIZE = 16; + + public const uint BCM2835_CORE_CLK_HZ = 250000000; + + public const uint BCM2835_GPIO_FSEL_INPT = 0; + public const uint BCM2835_GPIO_FSEL_OUTP = 1; + public const uint BCM2835_GPIO_FSEL_ALT0 = 4; + public const uint BCM2835_GPIO_FSEL_MASK = 7; + + public const uint BCM2835_GPFSEL0 = 0x0000; + public const uint BCM2835_GPPUD = 0x0094; + public const uint BCM2835_GPPUDCLK0 = 0x0098; + public const uint BCM2835_GPSET0 = 0x001c; + public const uint BCM2835_GPCLR0 = 0x0028; + public const uint BCM2835_GPLEV0 = 0x0034; + + public const uint BCM2835_GPIO_PUD_OFF = 0; + public const uint BCM2835_GPIO_PUD_DOWN = 1; + public const uint BCM2835_GPIO_PUD_UP = 2; + + #endregion + + #endregion + + #region Libc + + #region Constants + + public const int EPOLLIN = 1; + public const int EPOLLPRI = 2; + public const int EPOLLET = (1 << 31); + + public const int EPOLL_CTL_ADD = 0x1; + public const int EPOLL_CTL_DEL = 0x2; + + #endregion + + #region Methods + + [DllImport("libc.so.6", EntryPoint = "epoll_create")] + public static extern int epoll_create(int size); + + [DllImport("libc.so.6", EntryPoint = "epoll_ctl")] + public static extern int epoll_ctl(int epfd, int op, int fd, IntPtr epevent); + + [DllImport("libc.so.6", EntryPoint = "epoll_wait")] + public static extern int epoll_wait(int epfd, IntPtr events, int maxevents, int timeout); + + #endregion + + [StructLayout(LayoutKind.Explicit)] + public struct epoll_data + { + [FieldOffset(0)] public IntPtr ptr; + [FieldOffset(0)] public int fd; + [FieldOffset(0)] public UInt32 u32; + [FieldOffset(0)] public UInt64 u64; + }; + + [StructLayout(LayoutKind.Explicit)] + public struct epoll_event + { + [FieldOffset(0)] public int events; + [FieldOffset(4)] public epoll_data data; + }; + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/Mainboard.cs b/Raspberry.IO.GeneralPurpose/Mainboard.cs deleted file mode 100644 index 8a9c7c0..0000000 --- a/Raspberry.IO.GeneralPurpose/Mainboard.cs +++ /dev/null @@ -1,121 +0,0 @@ -#region References - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -#endregion - -namespace Raspberry.IO.GeneralPurpose -{ - /// - /// Represents the Raspberry Pi mainboard. - /// - public class Mainboard - { - #region Fields - - private static readonly Lazy board = new Lazy(LoadBoard); - private readonly Dictionary settings; - - private const string raspberryPiProcessor = "BCM2708"; - - #endregion - - #region Instance Management - - private Mainboard(Dictionary settings) - { - this.settings = settings; - } - - #endregion - - #region Properties - - /// - /// Gets the current mainboard configuration. - /// - public static Mainboard Current - { - get { return board.Value; } - } - - /// - /// Gets a value indicating whether this instance is a Raspberry Pi. - /// - /// - /// true if this instance is a Raspberry Pi; otherwise, false. - /// - public bool IsRaspberryPi - { - get { return Processor == raspberryPiProcessor; } - } - - /// - /// Gets the processor. - /// - public string Processor - { - get { return settings["Hardware"]; } - } - - /// - /// Gets the serial number. - /// - public string SerialNumber - { - get { return settings["Serial"]; } - } - - /// - /// Gets the firmware revision. - /// - public int FirmwareRevision - { - get { return int.Parse(settings["Revision"]); } - } - - /// - /// Gets the board revision. - /// - public int BoardRevision - { - get - { - var firmware = FirmwareRevision; - if (firmware <= 3) - return 1; - else if (firmware <= 6) - return 2; - else - throw new NotSupportedException("Raspberry board not supported"); - } - } - - #endregion - - #region Private Helpers - - private static Mainboard LoadBoard() - { - const string filePath = "/proc/cpuinfo"; - var settings = File.ReadAllLines(filePath) - .Where(l => !string.IsNullOrEmpty(l)) - .Select(l => - { - var separator = l.IndexOf(':'); - if (separator < 0) - return new KeyValuePair(l, null); - else - return new KeyValuePair(l.Substring(0, separator).Trim(), l.Substring(separator + 1).Trim()); - }) - .ToDictionary(p => p.Key, p => p.Value); - - return new Mainboard(settings); - } - - #endregion - } -} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/MemoryConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/MemoryConnectionDriver.cs deleted file mode 100644 index 4a0d8fb..0000000 --- a/Raspberry.IO.GeneralPurpose/MemoryConnectionDriver.cs +++ /dev/null @@ -1,99 +0,0 @@ -#region References - -using System; -using System.Runtime.InteropServices; - -#endregion - -namespace Raspberry.IO.GeneralPurpose -{ - /// - /// Represents a connection driver that uses memory. - /// - /// Based on bmc2835_gpio library. - public class MemoryConnectionDriver : IConnectionDriver - { - #region Fields - - private static readonly Lazy initialized = new Lazy(() => bcm2835_init() != 0); - - #endregion - - #region Methods - - /// - /// Modified the status of a pin. - /// - /// The pin. - /// The pin status. - public void Write(ProcessorPin pin, bool value) - { - bcm2835_gpio_write((uint) pin, (uint) (value ? 1 : 0)); - } - - /// - /// Reads the status of the specified pin. - /// - /// The pin. - /// - /// The pin status. - /// - public bool Read(ProcessorPin pin) - { - var value = bcm2835_gpio_lev((uint) pin); - return value != 0; - } - - /// - /// Exports the specified pin. - /// - /// The pin. - public void Export(PinConfiguration pin) - { - if (!initialized.Value) - throw new InvalidOperationException("Unabled to initialize driver"); - - // Set the direction on the pin and update the exported list - // BCM2835_GPIO_FSEL_INPT = 0 - // BCM2835_GPIO_FSEL_OUTP = 1 - bcm2835_gpio_fsel((uint) pin.Pin, (uint) (pin.Direction == PinDirection.Input ? 0 : 1)); - - if (pin.Direction == PinDirection.Input) - { - // BCM2835_GPIO_PUD_OFF = 0b00 = 0 - // BCM2835_GPIO_PUD_DOWN = 0b01 = 1 - // BCM2835_GPIO_PUD_UP = 0b10 = 2 - bcm2835_gpio_set_pud((uint) pin.Pin, 0); - } - } - - /// - /// Unexports the specified pin. - /// - /// The pin. - public void Unexport(PinConfiguration pin) - { - } - - #endregion - - #region Interop Methods - - [DllImport("libbcm2835.so", EntryPoint = "bcm2835_init")] - static extern int bcm2835_init(); - - [DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_fsel")] - static extern void bcm2835_gpio_fsel(uint pin, uint mode); - - [DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_write")] - static extern void bcm2835_gpio_write(uint pin, uint value); - - [DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_lev")] - static extern uint bcm2835_gpio_lev(uint pin); - - [DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_set_pud")] - static extern void bcm2835_gpio_set_pud(uint pin, uint pud); - - #endregion - } -} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/MemoryGpioConnectionDriver.cs b/Raspberry.IO.GeneralPurpose/MemoryGpioConnectionDriver.cs new file mode 100644 index 0000000..98af11f --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/MemoryGpioConnectionDriver.cs @@ -0,0 +1,331 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.InteropServices; +using Raspberry.IO.Interop; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a connection driver that uses memory. + /// + public class MemoryGpioConnectionDriver : IGpioConnectionDriver + { + #region Fields + + private readonly IntPtr gpioAddress; + private readonly Dictionary pinResistors = new Dictionary(); + + /// + /// The default timeout (5 seconds). + /// + public static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(5); + + private static readonly TimeSpan resistorSetDelay = TimeSpanUtility.FromMicroseconds(5); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + public MemoryGpioConnectionDriver() { + using (var memoryFile = UnixFile.Open("/dev/mem", UnixFileMode.ReadWrite | UnixFileMode.Synchronized)) { + gpioAddress = MemoryMap.Create( + IntPtr.Zero, + Interop.BCM2835_BLOCK_SIZE, + MemoryProtection.ReadWrite, + MemoryFlags.Shared, + memoryFile.Descriptor, + GetProcessorBaseAddress(Board.Current.Processor)); + } + } + + /// + /// Releases unmanaged resources and performs other cleanup operations before the + /// is reclaimed by garbage collection. + /// + ~MemoryGpioConnectionDriver() + { + MemoryMap.Close(gpioAddress, Interop.BCM2835_BLOCK_SIZE); + } + + #endregion + + #region Methods + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + GpioConnectionDriverCapabilities IGpioConnectionDriver.GetCapabilities() + { + return GetCapabilities(); + } + + /// + /// Gets driver capabilities. + /// + /// The capabilites. + public static GpioConnectionDriverCapabilities GetCapabilities() + { + return GpioConnectionDriverCapabilities.CanSetPinResistor | GpioConnectionDriverCapabilities.CanChangePinDirectionRapidly; + } + + /// + /// Allocates the specified pin. + /// + /// The pin. + /// The direction. + public void Allocate(ProcessorPin pin, PinDirection direction) + { + // Set the direction on the pin and update the exported list + SetPinMode(pin, direction == PinDirection.Input ? Interop.BCM2835_GPIO_FSEL_INPT : Interop.BCM2835_GPIO_FSEL_OUTP); + + if (direction == PinDirection.Input) + { + PinResistor pinResistor; + if (!pinResistors.TryGetValue(pin, out pinResistor) || pinResistor != PinResistor.None) + SetPinResistor(pin, PinResistor.None); + } + } + + /// + /// Sets the pin resistor. + /// + /// The pin. + /// The resistor. + public void SetPinResistor(ProcessorPin pin, PinResistor resistor) + { + // Set the pullup/down resistor for a pin + // + // The GPIO Pull-up/down Clock Registers control the actuation of internal pull-downs on + // the respective GPIO pins. These registers must be used in conjunction with the GPPUD + // register to effect GPIO Pull-up/down changes. The following sequence of events is + // required: + // 1. Write to GPPUD to set the required control signal (i.e. Pull-up or Pull-Down or neither + // to remove the current Pull-up/down) + // 2. Wait 150 cycles ? this provides the required set-up time for the control signal + // 3. Write to GPPUDCLK0/1 to clock the control signal into the GPIO pads you wish to + // modify ? NOTE only the pads which receive a clock will be modified, all others will + // retain their previous state. + // 4. Wait 150 cycles ? this provides the required hold time for the control signal + // 5. Write to GPPUD to remove the control signal + // 6. Write to GPPUDCLK0/1 to remove the clock + // + // RPi has P1-03 and P1-05 with 1k8 pullup resistor + + uint pud; + switch(resistor) + { + case PinResistor.None: + pud = Interop.BCM2835_GPIO_PUD_OFF; + break; + case PinResistor.PullDown: + pud = Interop.BCM2835_GPIO_PUD_DOWN; + break; + case PinResistor.PullUp: + pud = Interop.BCM2835_GPIO_PUD_UP; + break; + + default: + throw new ArgumentOutOfRangeException("resistor", resistor, string.Format(CultureInfo.InvariantCulture, "{0} is not a valid value for pin resistor", resistor)); + } + + WriteResistor(pud); + HighResolutionTimer.Sleep(resistorSetDelay); + SetPinResistorClock(pin, true); + HighResolutionTimer.Sleep(resistorSetDelay); + WriteResistor(Interop.BCM2835_GPIO_PUD_OFF); + SetPinResistorClock(pin, false); + + pinResistors[pin] = PinResistor.None; + } + + /// + /// Sets the detected edges on an input pin. + /// + /// The pin. + /// The edges. + /// + /// By default, both edges may be detected on input pins. + /// + public void SetPinDetectedEdges(ProcessorPin pin, PinDetectedEdges edges) + { + throw new NotSupportedException("Edge detection is not supported by memory GPIO connection driver"); + } + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// The pin. + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// + /// If timeout is set to , a default timeout of is used. + /// + public void Wait(ProcessorPin pin, bool waitForUp = true, TimeSpan timeout = new TimeSpan()) + { + var startWait = DateTime.UtcNow; + if (timeout == TimeSpan.Zero) + timeout = DefaultTimeout; + + while (Read(pin) != waitForUp) + { + if (DateTime.UtcNow >= startWait + timeout) + throw new TimeoutException("A timeout occurred while waiting for pin status to change"); + } + } + + /// + /// Releases the specified pin. + /// + /// The pin. + public void Release(ProcessorPin pin) + { + SetPinMode(pin, Interop.BCM2835_GPIO_FSEL_INPT); + } + + /// + /// Modified the status of a pin. + /// + /// The pin. + /// The pin status. + public void Write(ProcessorPin pin, bool value) + { + int shift; + var offset = Math.DivRem((int)pin, 32, out shift); + + var pinGroupAddress = gpioAddress + (int)((value ? Interop.BCM2835_GPSET0 : Interop.BCM2835_GPCLR0) + offset); + SafeWriteUInt32(pinGroupAddress, (uint) 1 << shift); + } + + /// + /// Reads the status of the specified pin. + /// + /// The pin. + /// + /// The pin status. + /// + public bool Read(ProcessorPin pin) + { + int shift; + var offset = Math.DivRem((int) pin, 32, out shift); + + var pinGroupAddress = gpioAddress + (int) (Interop.BCM2835_GPLEV0 + offset); + var value = SafeReadUInt32(pinGroupAddress); + + return (value & (1 << shift)) != 0; + } + + /// + /// Reads the status of the specified pins. + /// + /// The pins. + /// + /// The pins status. + /// + public ProcessorPins Read(ProcessorPins pins) + { + var pinGroupAddress = gpioAddress + (int) (Interop.BCM2835_GPLEV0 + (uint) 0 * 4); + var value = SafeReadUInt32(pinGroupAddress); + + return (ProcessorPins)((uint)pins & value); + } + + #endregion + + #region Private Methods + + private static uint GetProcessorBaseAddress(Processor processor) + { + switch (processor) + { + case Processor.Bcm2708: + return Interop.BCM2835_GPIO_BASE; + + case Processor.Bcm2709: + return Interop.BCM2836_GPIO_BASE; + + default: + throw new ArgumentOutOfRangeException("processor"); + } + } + + private void SetPinResistorClock(ProcessorPin pin, bool on) + { + int shift; + var offset = Math.DivRem((int)pin, 32, out shift); + + var clockAddress = gpioAddress + (int)(Interop.BCM2835_GPPUDCLK0 + offset); + SafeWriteUInt32(clockAddress, (uint) (on ? 1 : 0) << shift); + } + + private void WriteResistor(uint resistor) + { + var resistorPin = gpioAddress + (int) Interop.BCM2835_GPPUD; + SafeWriteUInt32(resistorPin, resistor); + } + + private void SetPinMode(ProcessorPin pin, uint mode) + { + // Function selects are 10 pins per 32 bit word, 3 bits per pin + var pinModeAddress = gpioAddress + (int) (Interop.BCM2835_GPFSEL0 + 4*((int)pin/10)); + + var shift = 3*((int) pin%10); + var mask = Interop.BCM2835_GPIO_FSEL_MASK << shift; + var value = mode << shift; + + WriteUInt32Mask(pinModeAddress, value, mask); + } + + private static void WriteUInt32Mask(IntPtr address, uint value, uint mask) + { + var v = SafeReadUInt32(address); + v = (v & ~mask) | (value & mask); + SafeWriteUInt32(address, v); + } + + private static uint SafeReadUInt32(IntPtr address) + { + // Make sure we dont return the _last_ read which might get lost + // if subsequent code changes to a different peripheral + var ret = ReadUInt32(address); + ReadUInt32(address); + + return ret; + } + + private static uint ReadUInt32(IntPtr address) + { + unchecked + { + return (uint) Marshal.ReadInt32(address); + } + } + + private static void SafeWriteUInt32(IntPtr address, uint value) + { + // Make sure we don't rely on the first write, which may get + // lost if the previous access was to a different peripheral. + WriteUInt32(address, value); + WriteUInt32(address, value); + } + + private static void WriteUInt32(IntPtr address, uint value) + { + unchecked + { + Marshal.WriteInt32(address, (int)value); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/PinConfiguration.cs b/Raspberry.IO.GeneralPurpose/PinConfiguration.cs index a602ea5..3484462 100644 --- a/Raspberry.IO.GeneralPurpose/PinConfiguration.cs +++ b/Raspberry.IO.GeneralPurpose/PinConfiguration.cs @@ -35,7 +35,7 @@ protected PinConfiguration(ProcessorPin pin) /// Gets the direction. /// public abstract PinDirection Direction { get; } - + /// /// Gets or sets the name. /// diff --git a/Raspberry.IO.GeneralPurpose/PinConfigurationExtensionMethods.cs b/Raspberry.IO.GeneralPurpose/PinConfigurationExtensionMethods.cs index d163c22..b5ca242 100644 --- a/Raspberry.IO.GeneralPurpose/PinConfigurationExtensionMethods.cs +++ b/Raspberry.IO.GeneralPurpose/PinConfigurationExtensionMethods.cs @@ -1,5 +1,9 @@ +#region References + using System; +#endregion + namespace Raspberry.IO.GeneralPurpose { /// @@ -97,6 +101,30 @@ public static T Revert(this T configuration) where T : PinConfiguration return configuration; } + /// + /// Enables pull-up resistor. + /// + /// The configuration type. + /// The configuration. + /// The pin configuration. + public static T PullUp(this T configuration) where T : InputPinConfiguration + { + configuration.Resistor = PinResistor.PullUp; + return configuration; + } + + /// + /// Enables pull-down resistor. + /// + /// The configuration type. + /// The configuration. + /// The pin configuration. + public static T PullDown(this T configuration) where T : InputPinConfiguration + { + configuration.Resistor = PinResistor.PullDown; + return configuration; + } + /// /// Indicates the specified pin is enabled on connection. /// diff --git a/Raspberry.IO.GeneralPurpose/PinDetectedEdges.cs b/Raspberry.IO.GeneralPurpose/PinDetectedEdges.cs new file mode 100644 index 0000000..e189bfc --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/PinDetectedEdges.cs @@ -0,0 +1,35 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents detected edges. + /// + [Flags] + public enum PinDetectedEdges + { + /// + /// No changes are detected. + /// + None = 0, + + /// + /// Rising edge changes are detected. + /// + Rising = 1, + + /// + /// Falling edge changes are detected. + /// + Falling = 2, + + /// + /// Both changes are detected. + /// + Both = Rising | Falling + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/PinMapping.cs b/Raspberry.IO.GeneralPurpose/PinMapping.cs index 8ac86be..a87522a 100644 --- a/Raspberry.IO.GeneralPurpose/PinMapping.cs +++ b/Raspberry.IO.GeneralPurpose/PinMapping.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Diagnostics; #endregion @@ -25,56 +26,151 @@ public static class PinMapping static PinMapping() { - var boardRevision = Mainboard.Current.BoardRevision; - var mapping = boardRevision == 1 - ? new[] - { - new {Processor = ProcessorPin.Pin0, Connector = ConnectorPin.P1Pin3}, - new {Processor = ProcessorPin.Pin1, Connector = ConnectorPin.P1Pin5}, - new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.P1Pin7}, - new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.P1Pin26}, - new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.P1Pin24}, - new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.P1Pin21}, - new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.P1Pin19}, - new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.P1Pin23}, - new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.P1Pin8}, - new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.P1Pin10}, - new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.P1Pin11}, - new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.P1Pin12}, - new {Processor = ProcessorPin.Pin21, Connector = ConnectorPin.P1Pin13}, - new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.P1Pin15}, - new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.P1Pin16}, - new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.P1Pin18}, - new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.P1Pin22} - } - : new[] - { - new {Processor = ProcessorPin.Pin2, Connector = ConnectorPin.P1Pin3}, - new {Processor = ProcessorPin.Pin3, Connector = ConnectorPin.P1Pin5}, - new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.P1Pin7}, - new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.P1Pin26}, - new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.P1Pin24}, - new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.P1Pin21}, - new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.P1Pin19}, - new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.P1Pin23}, - new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.P1Pin8}, - new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.P1Pin10}, - new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.P1Pin11}, - new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.P1Pin12}, - new {Processor = ProcessorPin.Pin27, Connector = ConnectorPin.P1Pin13}, - new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.P1Pin15}, - new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.P1Pin16}, - new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.P1Pin18}, - new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.P1Pin22}, - - new {Processor = ProcessorPin.Pin28, Connector = ConnectorPin.P5Pin3}, - new {Processor = ProcessorPin.Pin29, Connector = ConnectorPin.P5Pin4}, - new {Processor = ProcessorPin.Pin30, Connector = ConnectorPin.P5Pin5}, - new {Processor = ProcessorPin.Pin31, Connector = ConnectorPin.P5Pin6} - }; + var mapping = /* Value is not used but required for anonymous type */ new[]{ new + { + Processor = ProcessorPin.Pin0, Connector = ConnectorPin.P1Pin03 + }}; + + var uname = GetUname(); + if (uname.ToLower().Contains("cubie")) + { + mapping = new[] { + new {Processor = ProcessorPin.Pin3, Connector = ConnectorPin.CB3_CN8Pin5}, + new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.CB3_CN8Pin6}, + new {Processor = ProcessorPin.Pin5, Connector = ConnectorPin.CB3_CN8Pin7}, + new {Processor = ProcessorPin.Pin6, Connector = ConnectorPin.CB3_CN8Pin8}, + new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.CB3_CN8Pin9}, + new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.CB3_CN8Pin10}, + new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.CB3_CN8Pin11}, + new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.CB3_CN8Pin12}, + new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.CB3_CN8Pin15}, + new {Processor = ProcessorPin.Pin12, Connector = ConnectorPin.CB3_CN8Pin16}, + new {Processor = ProcessorPin.Pin13, Connector = ConnectorPin.CB3_CN8Pin17}, + new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.CB3_CN8Pin18}, + new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.CB3_CN8Pin19}, + new {Processor = ProcessorPin.Pin16, Connector = ConnectorPin.CB3_CN8Pin20}, + new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.CB3_CN8Pin21}, + new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.CB3_CN8Pin22}, + new {Processor = ProcessorPin.Pin19, Connector = ConnectorPin.CB3_CN8Pin23}, + new {Processor = ProcessorPin.Pin20, Connector = ConnectorPin.CB3_CN8Pin25}, + // + new {Processor = ProcessorPin.Pin21, Connector = ConnectorPin.CB3_CN9Pin3}, + new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.CB3_CN9Pin4}, + new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.CB3_CN9Pin5}, + new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.CB3_CN9Pin6}, + new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.CB3_CN9Pin7}, + new {Processor = ProcessorPin.Pin26, Connector = ConnectorPin.CB3_CN9Pin8}, + new {Processor = ProcessorPin.Pin27, Connector = ConnectorPin.CB3_CN9Pin9}, + new {Processor = ProcessorPin.Pin28, Connector = ConnectorPin.CB3_CN9Pin10}, + new {Processor = ProcessorPin.Pin29, Connector = ConnectorPin.CB3_CN9Pin11}, + new {Processor = ProcessorPin.Pin30, Connector = ConnectorPin.CB3_CN9Pin12}, + new {Processor = ProcessorPin.Pin31, Connector = ConnectorPin.CB3_CN9Pin13}, + new {Processor = ProcessorPin.Pin32, Connector = ConnectorPin.CB3_CN9Pin14} + }; + } + else + { + if (GpioConnectionSettings.ConnectorPinout == ConnectorPinout.Rev1) + mapping = new[] { + new {Processor = ProcessorPin.Pin0, Connector = ConnectorPin.P1Pin3}, + new {Processor = ProcessorPin.Pin1, Connector = ConnectorPin.P1Pin5}, + new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.P1Pin7}, + new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.P1Pin26}, + new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.P1Pin24}, + new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.P1Pin21}, + new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.P1Pin19}, + new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.P1Pin23}, + new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.P1Pin8}, + new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.P1Pin10}, + new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.P1Pin11}, + new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.P1Pin12}, + new {Processor = ProcessorPin.Pin21, Connector = ConnectorPin.P1Pin13}, + new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.P1Pin15}, + new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.P1Pin16}, + new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.P1Pin18}, + new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.P1Pin22} + }; + else if (GpioConnectionSettings.ConnectorPinout == ConnectorPinout.Rev2) + mapping = new[] { + new {Processor = ProcessorPin.Pin2, Connector = ConnectorPin.P1Pin3}, + new {Processor = ProcessorPin.Pin3, Connector = ConnectorPin.P1Pin5}, + new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.P1Pin7}, + new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.P1Pin26}, + new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.P1Pin24}, + new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.P1Pin21}, + new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.P1Pin19}, + new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.P1Pin23}, + new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.P1Pin8}, + new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.P1Pin10}, + new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.P1Pin11}, + new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.P1Pin12}, + new {Processor = ProcessorPin.Pin27, Connector = ConnectorPin.P1Pin13}, + new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.P1Pin15}, + new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.P1Pin16}, + new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.P1Pin18}, + new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.P1Pin22}, + new {Processor = ProcessorPin.Pin28, Connector = ConnectorPin.P5Pin3}, + new {Processor = ProcessorPin.Pin29, Connector = ConnectorPin.P5Pin4}, + new {Processor = ProcessorPin.Pin30, Connector = ConnectorPin.P5Pin5}, + new {Processor = ProcessorPin.Pin31, Connector = ConnectorPin.P5Pin6} + }; + else //if (GpioConnectionSettings.ConnectorPinout == ConnectorPinout.Plus) + mapping = new[] { + new {Processor = ProcessorPin.Pin2, Connector = ConnectorPin.P1Pin3}, + new {Processor = ProcessorPin.Pin3, Connector = ConnectorPin.P1Pin5}, + new {Processor = ProcessorPin.Pin4, Connector = ConnectorPin.P1Pin7}, + new {Processor = ProcessorPin.Pin5, Connector = ConnectorPin.P1Pin29}, + new {Processor = ProcessorPin.Pin6, Connector = ConnectorPin.P1Pin31}, + new {Processor = ProcessorPin.Pin7, Connector = ConnectorPin.P1Pin26}, + new {Processor = ProcessorPin.Pin8, Connector = ConnectorPin.P1Pin24}, + new {Processor = ProcessorPin.Pin9, Connector = ConnectorPin.P1Pin21}, + new {Processor = ProcessorPin.Pin10, Connector = ConnectorPin.P1Pin19}, + new {Processor = ProcessorPin.Pin11, Connector = ConnectorPin.P1Pin23}, + new {Processor = ProcessorPin.Pin12, Connector = ConnectorPin.P1Pin32}, + new {Processor = ProcessorPin.Pin13, Connector = ConnectorPin.P1Pin33}, + new {Processor = ProcessorPin.Pin14, Connector = ConnectorPin.P1Pin8}, + new {Processor = ProcessorPin.Pin15, Connector = ConnectorPin.P1Pin10}, + new {Processor = ProcessorPin.Pin16, Connector = ConnectorPin.P1Pin36}, + new {Processor = ProcessorPin.Pin17, Connector = ConnectorPin.P1Pin11}, + new {Processor = ProcessorPin.Pin18, Connector = ConnectorPin.P1Pin12}, + new {Processor = ProcessorPin.Pin19, Connector = ConnectorPin.P1Pin35}, + new {Processor = ProcessorPin.Pin20, Connector = ConnectorPin.P1Pin38}, + new {Processor = ProcessorPin.Pin21, Connector = ConnectorPin.P1Pin40}, + new {Processor = ProcessorPin.Pin22, Connector = ConnectorPin.P1Pin15}, + new {Processor = ProcessorPin.Pin23, Connector = ConnectorPin.P1Pin16}, + new {Processor = ProcessorPin.Pin24, Connector = ConnectorPin.P1Pin18}, + new {Processor = ProcessorPin.Pin25, Connector = ConnectorPin.P1Pin22}, + new {Processor = ProcessorPin.Pin26, Connector = ConnectorPin.P1Pin37}, + new {Processor = ProcessorPin.Pin27, Connector = ConnectorPin.P1Pin13} + }; + } processorMappings = mapping.ToDictionary(p => p.Connector, p => p.Processor); connectorMappings = mapping.ToDictionary(p => p.Processor, p => p.Connector); + + } + + static string GetUname() + { + string output = ""; + // Start the child process. + using (var p = new Process()) + { + // Redirect the output stream of the child process. + p.StartInfo.UseShellExecute = false; + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.FileName = "uname"; + p.StartInfo.Arguments = "-a"; + p.Start(); + // Do not wait for the child process to exit before + // reading to the end of its redirected stream. + // p.WaitForExit(); + // Read the output stream first and then wait. + output = p.StandardOutput.ReadToEnd(); + p.WaitForExit(); + } + //Console.WriteLine("[DEBUG] 'uname -a' => " + output); + return output; } #endregion @@ -89,10 +185,10 @@ static PinMapping() public static ProcessorPin ToProcessor(this ConnectorPin pin) { ProcessorPin processorPin; - if (processorMappings.TryGetValue(pin, out processorPin)) - return processorPin; - else - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Connector pin {0} is not mapped to processor on board revision {1}", pin.ToString().Replace("Pin", "-"), Mainboard.Current.BoardRevision)); + if (!processorMappings.TryGetValue(pin, out processorPin)) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Connector pin {0} is not mapped to processor with pin layout revision {1}", pin.ToString().Replace("Pin", "-"), GpioConnectionSettings.ConnectorPinout)); + + return processorPin; } /// @@ -103,10 +199,10 @@ public static ProcessorPin ToProcessor(this ConnectorPin pin) public static ConnectorPin ToConnector(this ProcessorPin pin) { ConnectorPin connectorPin; - if (connectorMappings.TryGetValue(pin, out connectorPin)) - return connectorPin; - else - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Processor pin {0} is not mapped to connector on board revision {1}", (int) pin, Mainboard.Current.BoardRevision)); + if (!connectorMappings.TryGetValue(pin, out connectorPin)) + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Processor pin {0} is not mapped to processor with pin layout revision {1}", (int) pin, GpioConnectionSettings.ConnectorPinout)); + + return connectorPin; } #endregion diff --git a/Raspberry.IO.GeneralPurpose/PinResistor.cs b/Raspberry.IO.GeneralPurpose/PinResistor.cs new file mode 100644 index 0000000..bf4855a --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/PinResistor.cs @@ -0,0 +1,23 @@ +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents the resistor enabled on an input. + /// + public enum PinResistor + { + /// + /// No resistor is enabled on the input. + /// + None, + + /// + /// A pull-down resistor is enabled. + /// + PullDown, + + /// + /// A pull-up resistor is enabled. + /// + PullUp + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/ProcessorPin.cs b/Raspberry.IO.GeneralPurpose/ProcessorPin.cs index 7f3622e..b57c691 100644 --- a/Raspberry.IO.GeneralPurpose/ProcessorPin.cs +++ b/Raspberry.IO.GeneralPurpose/ProcessorPin.cs @@ -5,29 +5,219 @@ namespace Raspberry.IO.GeneralPurpose /// public enum ProcessorPin { + /// + /// Pin 0. + /// Pin0 = 0, + + /// + /// Pin 0. + /// + Pin00 = Pin0, + + /// + /// Pin 1. + /// Pin1 = 1, + + /// + /// Pin 1. + /// + Pin01 = Pin1, + + /// + /// Pin 2. + /// Pin2 = 2, + + /// + /// Pin 2. + /// + Pin02 = Pin2, + + /// + /// Pin 3. + /// Pin3 = 3, + + /// + /// Pin 3. + /// + Pin03 = Pin3, + + /// + /// Pin 4. + /// Pin4 = 4, + + /// + /// Pin 4. + /// + Pin04 = Pin4, + + /// + /// Pin 5. + /// + Pin5 = 5, + + /// + /// Pin 5. + /// + Pin05 = Pin5, + + /// + /// Pin 6. + /// + Pin6 = 6, + + /// + /// Pin 6. + /// + Pin06 = Pin6, + + /// + /// Pin 7. + /// Pin7 = 7, + + /// + /// Pin 7. + /// + Pin07 = Pin7, + + /// + /// Pin 8. + /// Pin8 = 8, + + /// + /// Pin 8. + /// + Pin08 = Pin8, + + /// + /// Pin 9. + /// Pin9 = 9, + + /// + /// Pin 9. + /// + Pin09 = Pin9, + + /// + /// Pin 10. + /// Pin10 = 10, + + /// + /// Pin 11. + /// Pin11 = 11, + + /// + /// Pin 12. + /// + Pin12 = 12, + + /// + /// Pin 13. + /// + Pin13 = 13, + + /// + /// Pin 14. + /// Pin14 = 14, + + /// + /// Pin 15. + /// Pin15 = 15, + + /// + /// Pin 16. + /// + Pin16 = 16, + + /// + /// Pin 17. + /// Pin17 = 17, + + /// + /// Pin 18. + /// Pin18 = 18, + + /// + /// Pin 19. + /// + Pin19 = 19, + + /// + /// Pin 20. + /// + Pin20 = 20, + + /// + /// Pin 21. + /// Pin21 = 21, + + /// + /// Pin 22. + /// Pin22 = 22, + + /// + /// Pin 23. + /// Pin23 = 23, + + /// + /// Pin 24. + /// Pin24 = 24, + + /// + /// Pin 25. + /// Pin25 = 25, + + /// + /// Pin 26. + /// + Pin26 = 26, + + /// + /// Pin 27. + /// Pin27 = 27, + + /// + /// Pin 28. + /// Pin28 = 28, + + /// + /// Pin 29. + /// Pin29 = 29, + + /// + /// Pin 30. + /// Pin30 = 30, - Pin31 = 31 + + /// + /// Pin 31. + /// + Pin31 = 31, + + /// + /// Pin 32. (added for CubieTruck/CubieBoard3 compatibility + /// + Pin32 = 32 } } \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/ProcessorPinExtensionMethods.cs b/Raspberry.IO.GeneralPurpose/ProcessorPinExtensionMethods.cs new file mode 100644 index 0000000..b3d5f53 --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/ProcessorPinExtensionMethods.cs @@ -0,0 +1,33 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Linq; + +#endregion + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Provides extension methods for and objects. + /// + public static class ProcessorPinExtensionMethods + { + #region Methods + + /// + /// Enumerates the specified pins. + /// + /// The pins. + /// The pins. + public static IEnumerable Enumerate(this ProcessorPins pins) + { + return ((Enum.GetValues(typeof (ProcessorPin)) as ProcessorPin[]) ?? new ProcessorPin[0]) + .Distinct() + .Where(p => (pins & (ProcessorPins) ((uint) 1 << (int) p)) != ProcessorPins.None) + .ToArray(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/ProcessorPins.cs b/Raspberry.IO.GeneralPurpose/ProcessorPins.cs new file mode 100644 index 0000000..3e798fe --- /dev/null +++ b/Raspberry.IO.GeneralPurpose/ProcessorPins.cs @@ -0,0 +1,176 @@ +using System; + +namespace Raspberry.IO.GeneralPurpose +{ + /// + /// Represents a set of pins on the Raspberry Pi Processor + /// + [Flags] + public enum ProcessorPins : uint + { + /// + /// No pins selected. + /// + None = 0, + + /// + /// Pin 0 selected. + /// + Pin0 = 1 << 0, + + /// + /// Pin 0 selected. + /// + Pin00 = Pin0, + + /// + /// Pin 1 selected. + /// + Pin1 = 1 << 1, + + /// + /// Pin 1 selected. + /// + Pin01 = Pin1, + + /// + /// Pin 2 selected. + /// + Pin2 = 1 << 2, + + /// + /// Pin 2 selected. + /// + Pin02 = Pin2, + + /// + /// Pin 3 selected. + /// + Pin3 = 1 << 3, + + /// + /// Pin 3 selected. + /// + Pin03 = Pin3, + + /// + /// Pin 4 selected. + /// + Pin4 = 1 << 4, + + /// + /// Pin 4 selected. + /// + Pin04 = Pin4, + + /// + /// Pin 7 selected. + /// + Pin7 = 1 << 7, + + /// + /// Pin 7 selected. + /// + Pin07 = Pin7, + + /// + /// Pin 8 selected. + /// + Pin8 = 1 << 8, + + /// + /// Pin 8 selected. + /// + Pin08 = Pin8, + + /// + /// Pin 9 selected. + /// + Pin9 = 1 << 9, + + /// + /// Pin 9 selected. + /// + Pin09 = Pin9, + + /// + /// Pin 10 selected. + /// + Pin10 = 1 << 10, + + /// + /// Pin 11 selected. + /// + Pin11 = 1 << 11, + + /// + /// Pin 14 selected. + /// + Pin14 = 1 << 14, + + /// + /// Pin 15 selected. + /// + Pin15 = 1 << 15, + + /// + /// Pin 17 selected. + /// + Pin17 = 1 << 17, + + /// + /// Pin 18 selected. + /// + Pin18 = 1 << 18, + + /// + /// Pin 21 selected. + /// + Pin21 = 1 << 21, + + /// + /// Pin 22 selected. + /// + Pin22 = 1 << 22, + + /// + /// Pin 23 selected. + /// + Pin23 = 1 << 23, + + /// + /// Pin 24 selected. + /// + Pin24 = 1 << 24, + + /// + /// Pin 25 selected. + /// + Pin25 = 1 << 25, + + /// + /// Pin 27 selected. + /// + Pin27 = 1 << 27, + + /// + /// Pin 28 selected. + /// + Pin28 = 1 << 28, + + /// + /// Pin 29 selected. + /// + Pin29 = 1 << 29, + + /// + /// Pin 30 selected. + /// + Pin30 = 1 << 30, + + /// + /// Pin 31 selected. + /// + Pin31 = (uint)1 << 31 + } +} \ No newline at end of file diff --git a/Raspberry.IO.GeneralPurpose/Properties/AssemblyInfo.cs b/Raspberry.IO.GeneralPurpose/Properties/AssemblyInfo.cs index 830774b..e3524ad 100644 --- a/Raspberry.IO.GeneralPurpose/Properties/AssemblyInfo.cs +++ b/Raspberry.IO.GeneralPurpose/Properties/AssemblyInfo.cs @@ -7,29 +7,6 @@ [assembly: AssemblyTitle("Raspberry.IO.GeneralPurpose")] [assembly: AssemblyDescription("Raspberry Pi GPIO Mono Library")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Raspberry.IO.GeneralPurpose")] -[assembly: AssemblyCopyright("www.raspberry-sharp.org, 2012")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("d00cf11a-c5cf-4416-828e-afaa9b97e611")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Raspberry.IO.GeneralPurpose/Raspberry.IO.GeneralPurpose.csproj b/Raspberry.IO.GeneralPurpose/Raspberry.IO.GeneralPurpose.csproj index 28ad6f9..cabdaa4 100644 --- a/Raspberry.IO.GeneralPurpose/Raspberry.IO.GeneralPurpose.csproj +++ b/Raspberry.IO.GeneralPurpose/Raspberry.IO.GeneralPurpose.csproj @@ -3,8 +3,6 @@ Debug AnyCPU - 8.0.30703 - 2.0 {281C71ED-C36D-408E-8BAA-75C381DC17E7} Library Properties @@ -12,6 +10,7 @@ Raspberry.IO.GeneralPurpose v4.0 512 + ..\ true @@ -33,28 +32,45 @@ bin\Release\Raspberry.IO.GeneralPurpose.XML + + $(SolutionDir)packages\Raspberry.System.2.1\lib\net40\Raspberry.System.dll + True + - - - - - + + Properties\SharedAssemblyInfo.cs + - - + + + + + + + + + + + + + + + + + + - + - @@ -67,11 +83,18 @@ - - Always - + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + + - + \ No newline at end of file diff --git a/Raspberry.IO.InterIntegratedCircuit/packages.config b/Raspberry.IO.InterIntegratedCircuit/packages.config new file mode 100644 index 0000000..0f101ec --- /dev/null +++ b/Raspberry.IO.InterIntegratedCircuit/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Raspberry.IO.Interop/ControlDevice.cs b/Raspberry.IO.Interop/ControlDevice.cs new file mode 100644 index 0000000..687f5c4 --- /dev/null +++ b/Raspberry.IO.Interop/ControlDevice.cs @@ -0,0 +1,115 @@ +using System; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + /// + /// A Linux I/O control device. + /// + public class ControlDevice : IControlDevice + { + + #region Fields + /// + /// Device file used for communication + /// + protected readonly IFile file; + + /// + /// If true, will be disposed on + /// + protected readonly bool disposeFile; + + #endregion + + #region Libc imports + [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] + private static extern int ioctl(int descriptor, UInt32 request, IntPtr data); + + [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] + private static extern int ioctl(int descriptor, UInt32 request, ref UInt32 data); + + [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] + private static extern int ioctl(int descriptor, UInt32 request, ref byte data); + #endregion + + #region Instance Management + /// + /// Initializes a new instance of the class. + /// + /// A opened special file that can be controlled using ioctl-system calls. + /// will be disposed if the user calls on this instance. + public ControlDevice(IFile file): this(file, true) {} + + /// + /// Initializes a new instance of the class. + /// + /// A opened special file that can be controlled using ioctl-system calls. + /// If true the supplied will be disposed if the user calls on this instance. + public ControlDevice(IFile file, bool disposeFile) { + this.file = file; + this.disposeFile = disposeFile; + } + + ~ControlDevice() { + Dispose(false); + } + + /// + /// Disposes the instance. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose + /// + /// If true the managed resources will be disposed as well. + protected virtual void Dispose(bool disposing) { + if (disposing && disposeFile) { + file.Dispose(); + } + } + + #endregion + + #region Methods + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + public int Control(UInt32 request, ref UInt32 data) { + var result = ioctl(file.Descriptor, request, ref data); + return result; + } + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + public int Control(UInt32 request, ref byte data) { + var result = ioctl(file.Descriptor, request, ref data); + return result; + } + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// An untyped pointer to memory that contains the command/request data. + /// + public int Control(UInt32 request, IntPtr data) { + var result = ioctl(file.Descriptor, request, data); + return result; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/EnumTypes/MemoryFlags.cs b/Raspberry.IO.Interop/EnumTypes/MemoryFlags.cs new file mode 100644 index 0000000..865c731 --- /dev/null +++ b/Raspberry.IO.Interop/EnumTypes/MemoryFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Raspberry.IO.Interop +{ + [Flags] + public enum MemoryFlags + { + None = 0, + Shared = 1 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/EnumTypes/MemoryProtection.cs b/Raspberry.IO.Interop/EnumTypes/MemoryProtection.cs new file mode 100644 index 0000000..89ddeb9 --- /dev/null +++ b/Raspberry.IO.Interop/EnumTypes/MemoryProtection.cs @@ -0,0 +1,13 @@ +using System; + +namespace Raspberry.IO.Interop +{ + [Flags] + public enum MemoryProtection + { + None = 0, + Read = 1, + Write = 2, + ReadWrite = Read | Write + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/EnumTypes/UnixFileMode.cs b/Raspberry.IO.Interop/EnumTypes/UnixFileMode.cs new file mode 100644 index 0000000..311736a --- /dev/null +++ b/Raspberry.IO.Interop/EnumTypes/UnixFileMode.cs @@ -0,0 +1,28 @@ +using System; + +namespace Raspberry.IO.Interop +{ + /// + /// File access mode + /// + [Flags] + public enum UnixFileMode + { + /// + /// The file will be opened with read-only access. + /// + ReadOnly = 1, + /// + /// The file will be opened with read/write access. + /// + ReadWrite = 2, + /// + /// When possible, the file is opened in nonblocking mode. + /// + NonBlocking = 4, + /// + /// The file is opened for synchronous I/O. + /// + Synchronized = 10000 + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/ErrNum.cs b/Raspberry.IO.Interop/ErrNum.cs new file mode 100644 index 0000000..e8fe2ee --- /dev/null +++ b/Raspberry.IO.Interop/ErrNum.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + public static class ErrNum + { + #region libc imports + [DllImport("libc", EntryPoint = "strerror", SetLastError = true)] + private static extern IntPtr strerror(int errnum); + #endregion + + #region Methods + public static void ThrowOnPInvokeError(this int result, string message = null) + where TException : Exception, new() + { + if (result >= 0) { + return; + } + + var type = typeof(TException); + var constructorInfo = type.GetConstructor(new[] { typeof(string) }); + if (ReferenceEquals(constructorInfo, null)) { + throw new TException(); + } + + var err = Marshal.GetLastWin32Error(); + var messagePtr = strerror(err); + + var strErrorMessage = (messagePtr != IntPtr.Zero) + ? Marshal.PtrToStringAuto(messagePtr) + : "unknown"; + + var exceptionMessage = (message == null) + ? string.Format("Error {0}: {1}", err, strErrorMessage) + : string.Format(message, result, err, strErrorMessage); + + throw (TException)constructorInfo.Invoke(new object[] { exceptionMessage }); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/Exceptions/MemoryMapFailedException.cs b/Raspberry.IO.Interop/Exceptions/MemoryMapFailedException.cs new file mode 100644 index 0000000..bcc4e96 --- /dev/null +++ b/Raspberry.IO.Interop/Exceptions/MemoryMapFailedException.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.Interop +{ + [Serializable] + public class MemoryMapFailedException : Exception { + public MemoryMapFailedException() {} + public MemoryMapFailedException(string message) : base(message) {} + public MemoryMapFailedException(string message, Exception innerException) : base(message, innerException) {} + protected MemoryMapFailedException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/Exceptions/MemoryUnmapFailedException.cs b/Raspberry.IO.Interop/Exceptions/MemoryUnmapFailedException.cs new file mode 100644 index 0000000..3c0dfb5 --- /dev/null +++ b/Raspberry.IO.Interop/Exceptions/MemoryUnmapFailedException.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.Interop +{ + [Serializable] + public class MemoryUnmapFailedException : Exception { + public MemoryUnmapFailedException() {} + public MemoryUnmapFailedException(string message) : base(message) {} + public MemoryUnmapFailedException(string message, Exception innerException) : base(message, innerException) {} + protected MemoryUnmapFailedException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/IControlDevice.cs b/Raspberry.IO.Interop/IControlDevice.cs new file mode 100644 index 0000000..d844f29 --- /dev/null +++ b/Raspberry.IO.Interop/IControlDevice.cs @@ -0,0 +1,34 @@ +using System; + +namespace Raspberry.IO.Interop +{ + /// + /// A Linux I/O control device. + /// + public interface IControlDevice : IDisposable + { + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + int Control(UInt32 request, ref UInt32 data); + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + int Control(UInt32 request, ref byte data); + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// An untyped pointer to memory that contains the command/request data. + /// + int Control(UInt32 request, IntPtr data); + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/IFile.cs b/Raspberry.IO.Interop/IFile.cs new file mode 100644 index 0000000..c035771 --- /dev/null +++ b/Raspberry.IO.Interop/IFile.cs @@ -0,0 +1,19 @@ +using System; + +namespace Raspberry.IO.Interop +{ + /// + /// A file resource that is controlled by the underling operation system. + /// + public interface IFile : IDisposable { + /// + /// The file descriptor + /// + int Descriptor { get; } + + /// + /// The pathname to the file + /// + string Filename { get; } + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/IMemory.cs b/Raspberry.IO.Interop/IMemory.cs new file mode 100644 index 0000000..33dcfdf --- /dev/null +++ b/Raspberry.IO.Interop/IMemory.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; + +namespace Raspberry.IO.Interop +{ + /// + /// Managed/Unmanaged memory that can be used for P/Invoke operations. + /// + public interface IMemory : IDisposable, IEnumerable { + /// + /// Pointer to the memory address. + /// + IntPtr Pointer { get; } + + /// + /// Size in bytes + /// + int Length { get; } + + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself. + /// + /// Offset to memory + /// Byte at/from the specified position . + byte this[int index] { get; set; } + + /// + /// Writes at . + /// + /// Offset + /// Data that shall be written. + void Write(int offset, byte data); + + /// + /// Reads a byte at . + /// + /// Offset + /// The data. + byte Read(int offset); + + /// + /// Copies the bytes from to the memory. + /// + /// Source byte array. + /// Copies the data starting from . + /// Copies the data starting at to the memory. + /// Copies bytes. + void Copy(byte[] source, int sourceIndex, int destinationIndex, int length); + + /// + /// Copies data from the memory to the supplied byte array. + /// + /// Copies the data starting from . + /// Destination byte array. + /// Copies the data starting at to the destination byte array. + /// Copies bytes. + void Copy(int sourceIndex, byte[] destination, int destinationIndex, int length); + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/ManagedMemory.cs b/Raspberry.IO.Interop/ManagedMemory.cs new file mode 100644 index 0000000..99f0ea2 --- /dev/null +++ b/Raspberry.IO.Interop/ManagedMemory.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + public class ManagedMemory : IMemory + { + #region Fields + private byte[] byteArray; + private GCHandle handle; + private IntPtr memoryPointer; + #endregion + + #region Instance Management + /// + /// Allocates a byte array of the requested size and pins it in order to prevent GC from free/moving it. + /// + /// Memory size in bytes. + public ManagedMemory(int length) { + byteArray = new byte[length]; + handle = GCHandle.Alloc(byteArray, GCHandleType.Pinned); + memoryPointer = handle.AddrOfPinnedObject(); + } + + ~ManagedMemory() { + Dispose(false); + } + + /// + /// Free managed memory. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Free managed memory. + /// + /// The pinned managed memory will always be released to avoid memory leaks. If you don't want this, don't call this method () in your derived class. + protected virtual void Dispose(bool disposing) { + if (disposing) { + // Free managed here + } + + Trace.Assert(disposing, + string.Format("ERROR: GC finalized managed memory of {0} bytes for address {1} that was not disposed!", + byteArray.Length, memoryPointer.ToString("X8"))); + + + if (handle.IsAllocated) { + handle.Free(); + } + + memoryPointer = IntPtr.Zero; + byteArray = new byte[0]; + } + + #endregion + + #region Properties + /// + /// Pointer to the memory address. + /// + public IntPtr Pointer { + get { return memoryPointer; } + } + + /// + /// Size in bytes + /// + public int Length { + get { return byteArray.Length; } + } + + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself. + /// + /// Offset to memory + /// Byte at/from the specified position . + public byte this[int index] { + get { return byteArray[index]; } + set { byteArray[index] = value; } + } + #endregion + + #region Methods + + /// + /// Writes at . + /// + /// Offset + /// Data that shall be written. + public void Write(int offset, byte data) { + byteArray[offset] = data; + } + + /// + /// Reads a byte at . + /// + /// Offset + /// The data. + public byte Read(int offset) { + return byteArray[offset]; + } + + /// + /// Copies the bytes from to the memory. + /// + /// Source byte array. + /// Copies the data starting from . + /// Copies the data starting at to the memory. + /// Copies bytes. + public void Copy(byte[] source, int sourceIndex, int destinationIndex, int length) { + Array.Copy(source, sourceIndex, byteArray, destinationIndex, length); + } + + /// + /// Copies data from the memory to the supplied byte array. + /// + /// Copies the data starting from . + /// Destination byte array. + /// Copies the data starting at to the destination byte array. + /// Copies bytes. + public void Copy(int sourceIndex, byte[] destination, int destinationIndex, int length) { + Array.Copy(byteArray, sourceIndex, destination, destinationIndex, length); + } + + /// + /// Returns an enumerator; + /// + /// An enumerator + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + /// + /// Returns an enumerator; + /// + /// An enumerator + public IEnumerator GetEnumerator() { + return ((IEnumerable) byteArray) + .GetEnumerator(); + } + + /// + /// Allocates unmanaged memory for and copies its content into it. + /// + /// Structure type + /// The structure that shall be copied into the requested memory buffer. + /// The unmanaged memory buffer containing . + public static ManagedMemory CreateAndCopy(T structure) { + var requiredSize = Marshal.SizeOf(structure); + + var memory = new ManagedMemory(requiredSize); + Marshal.StructureToPtr(structure, memory.Pointer, false); + + return memory; + } + + /// + /// Allocates unmanaged memory with the size of . + /// + /// Structure type + /// The unmanaged memory buffer of size . + public static ManagedMemory CreateFor() { + var requiredSize = Marshal.SizeOf(typeof(T)); + return new ManagedMemory(requiredSize); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/MemoryMap.cs b/Raspberry.IO.Interop/MemoryMap.cs new file mode 100644 index 0000000..08cb0ef --- /dev/null +++ b/Raspberry.IO.Interop/MemoryMap.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + public static class MemoryMap + { + #region Fields + private static readonly IntPtr FAILED = new IntPtr(-1); + #endregion + + #region Libc imports + [DllImport("libc.so.6", EntryPoint = "mmap")] + private static extern IntPtr mmap(IntPtr address, UIntPtr size, int protect, int flags, int file, UIntPtr offset); + + [DllImport("libc.so.6", EntryPoint = "munmap")] + private static extern IntPtr munmap(IntPtr address, UIntPtr size); + #endregion + + #region Methods + public static IntPtr Create(IntPtr address, ulong size, MemoryProtection protection, MemoryFlags memoryflags, int fileDescriptor, ulong offset) { + var result = mmap(address, new UIntPtr(size), (int) protection, (int) memoryflags, fileDescriptor, new UIntPtr(offset)); + ThrowOnError(result); + return result; + } + + public static IntPtr Create(IntPtr address, uint size, MemoryProtection protection, MemoryFlags memoryflags, int fileDescriptor, uint offset) { + var result = mmap(address, new UIntPtr(size), (int)protection, (int)memoryflags, fileDescriptor, new UIntPtr(offset)); + ThrowOnError(result); + return result; + } + + public static void Close(IntPtr address, ulong size) { + var result = munmap(address, new UIntPtr(size)); + ThrowOnError(result); + } + + public static void Close(IntPtr address, uint size) { + var result = munmap(address, new UIntPtr(size)); + ThrowOnError(result); + } + #endregion + + #region Private Helpers + private static void ThrowOnError(IntPtr result) + where TException: Exception, new() + { + if (result == FAILED) { + throw new TException(); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/MemorySubset.cs b/Raspberry.IO.Interop/MemorySubset.cs new file mode 100644 index 0000000..347e929 --- /dev/null +++ b/Raspberry.IO.Interop/MemorySubset.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Raspberry.IO.Interop +{ + /// + /// A subset of an already allocated memory block. + /// + public class MemorySubset : IMemory + { + #region Fields + private readonly IMemory memory; + + private int memoryLength; + private int memoryOffset; + private bool owner; + private IntPtr memoryPointer; + #endregion + + #region Instance Management + /// + /// Initializes a new instance of the class. + /// + /// The origin memory block + /// Start offset of the origin memory block + /// Length of this memory subset in bytes + /// If true the origin will be disposed on . + public MemorySubset(IMemory memoryBlock, int startOffset, int length, bool isOwner) { + if (ReferenceEquals(memoryBlock, null)) { + throw new ArgumentNullException("memoryBlock"); + } + if (startOffset < 0 || startOffset > memoryBlock.Length) { + var message = string.Format("The offset must be between 0 and {0}", memoryBlock.Length); + throw new ArgumentOutOfRangeException("startOffset", startOffset, message); + } + if (length < 0 || startOffset + length > memoryBlock.Length) { + throw new ArgumentOutOfRangeException("length", length, "Invalid size"); + } + + memory = memoryBlock; + memoryOffset = startOffset; + memoryLength = length; + memoryPointer = memory.Pointer + memoryOffset; + owner = isOwner; + } + + /// + /// If owner, managed memory will be released. Otherwise this method does nothing. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Free managed memory. This method does nothing if it is not owner of origin memory block. + /// + /// If this instance is the owner of the memory block it will always release the origin memory block to avoid memory leaks. If you don't want this, don't call this method () in your derived class. + protected virtual void Dispose(bool disposing) { + if (disposing) { + // free managed here + } + + if (owner) { + memoryLength = 0; + memoryPointer = IntPtr.Zero; + memoryOffset = 0; + memory.Dispose(); + owner = false; + } + } + + #endregion + + #region Properties + + /// + /// Pointer to the memory address. + /// + public IntPtr Pointer { + get { return memoryPointer; } + } + + /// + /// Size in bytes + /// + public int Length { + get { return memoryLength; } + } + + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself. + /// + /// Offset to memory + /// Byte at/from the specified position . + public byte this[int index] { + get { return Read(index); } + set { Write(index, value); } + } + #endregion + + #region Methods + /// + /// Returns an enumerator + /// + /// + /// A + /// + public IEnumerator GetEnumerator() { + var tmp = new byte[memoryLength]; + memory.Copy(memoryOffset, tmp, 0, memoryLength); + + return ((IEnumerable) tmp) + .GetEnumerator(); + } + + /// + /// Returns an enumerator + /// + /// + /// A + /// + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + /// + /// Writes at . + /// + /// Offset + /// Data that shall be written. + public void Write(int offset, byte data) { + if (offset < 0 || offset >= memoryLength) { + throw new ArgumentOutOfRangeException("offset", offset, "invalid offset"); + } + memory.Write(memoryOffset + offset, data); + } + + /// + /// Reads a byte at . + /// + /// Offset + /// The data. + public byte Read(int offset) { + if (offset < 0 || offset >= memoryLength) { + throw new ArgumentOutOfRangeException("offset", offset, "invalid offset"); + } + return memory.Read(memoryOffset + offset); + } + + /// + /// Copies the bytes from to the memory. + /// + /// Source byte array. + /// Copies the data starting from . + /// Copies the data starting at to the memory. + /// Copies bytes. + public void Copy(byte[] source, int sourceIndex, int destinationIndex, int length) { + if (destinationIndex < 0 || destinationIndex > memoryLength) { + var message = string.Format("destination index must be greater than 0 and lower or equal to {0}", + memoryLength); + throw new ArgumentOutOfRangeException("destinationIndex", destinationIndex, + message); + } + if (destinationIndex + length > memoryLength) { + throw new ArgumentOutOfRangeException("length", length, "invalid length"); + } + + memory.Copy(source, sourceIndex, memoryOffset + destinationIndex, length); + } + + /// + /// Copies data from the memory to the supplied byte array. + /// + /// Copies the data starting from . + /// Destination byte array. + /// Copies the data starting at to the destination byte array. + /// Copies bytes. + public void Copy(int sourceIndex, byte[] destination, int destinationIndex, int length) { + if (sourceIndex < 0 || sourceIndex > memoryLength) { + var message = string.Format("source index must be greater than 0 and lower or equal to {0}", + memoryLength); + throw new ArgumentOutOfRangeException("sourceIndex", sourceIndex, message); + } + if (sourceIndex + length > memoryLength) { + throw new ArgumentOutOfRangeException("length", length, "invalid length"); + } + + memory.Copy(memoryOffset + sourceIndex, destination, destinationIndex, length); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/Properties/AssemblyInfo.cs b/Raspberry.IO.Interop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..418076c --- /dev/null +++ b/Raspberry.IO.Interop/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Raspberry.IO.Interop")] +[assembly: AssemblyDescription("Raspberry Pi Interoperability library")] +[assembly: AssemblyConfiguration("")] +[assembly: Guid("dfdae73d-4511-4e8b-a7c3-e36b32266955")] + + diff --git a/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj b/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj new file mode 100644 index 0000000..034c6fc --- /dev/null +++ b/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj @@ -0,0 +1,66 @@ + + + + + Debug + AnyCPU + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Library + Properties + Raspberry.IO.Interop + Raspberry.IO.Interop + v4.0 + 512 + + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + diff --git a/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj.DotSettings b/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj.DotSettings new file mode 100644 index 0000000..9bbc349 --- /dev/null +++ b/Raspberry.IO.Interop/Raspberry.IO.Interop.csproj.DotSettings @@ -0,0 +1,4 @@ + + True + True + True \ No newline at end of file diff --git a/Raspberry.IO.Interop/UnixFile.cs b/Raspberry.IO.Interop/UnixFile.cs new file mode 100644 index 0000000..bc4d928 --- /dev/null +++ b/Raspberry.IO.Interop/UnixFile.cs @@ -0,0 +1,128 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + /// + /// A UNIX file that is controlled by the operation system. + /// + public sealed class UnixFile : IFile + { + #region Libc imports + [DllImport("libc.so.6", EntryPoint = "open")] + private static extern int open(string fileName, int mode); + [DllImport("libc.so.6", EntryPoint = "close")] + private static extern int close(int file); + #endregion + + #region Fields + private int descriptor; + private string filename; + #endregion + + #region Instance Management + private UnixFile(int descriptor) { + this.descriptor = descriptor; + } + + /// + /// Initializes a new instance of the class. + /// + /// A pathname for the file. + /// The file access mode. + public UnixFile(string filename, UnixFileMode fileMode) + :this(OpenFileDescriptor(filename, fileMode)) { + this.filename = filename; + } + + ~UnixFile() { + Dispose(false); + GC.SuppressFinalize(this); + } + #endregion + + #region Properties + /// + /// The file descriptor + /// + public int Descriptor { + get { return descriptor; } + } + + /// + /// The pathname for the file. + /// + public string Filename { + get { return filename; } + } + #endregion + + #region Methods + /// + /// Closes the file and frees all unmanaged system resources. See for more information. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Opens a UNIX file. + /// + /// The filepath. + /// The file access mode. + /// A opened file. + public static IFile Open(string fileName, UnixFileMode fileMode) { + return new UnixFile(fileName, fileMode); + } + + /// + /// Opens a UNIX file and returns the file descriptor. + /// + /// The filepath. + /// The file access mode. + /// The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process. + public static int OpenFileDescriptor(string fileName, UnixFileMode fileMode) { + var mode = unchecked ((int) fileMode); + return open(fileName, mode); + } + + /// + /// Closes a file descriptor, so that it no longer refers to any file and may be reused. Any record locks held on the file it was associated with, and owned by + /// the process, are removed (regardless of the file descriptor that was used to obtain the lock). + /// + /// The file descriptor the shall be closed. + /// true on success + /// + /// If is the last file descriptor referring to the underlying open file description, the resources associated with + /// the open file description are freed; if the descriptor was the last reference to a file which has been removed using unlink the file is deleted. + /// + public static bool CloseFileDescriptor(int fileDescriptor) { + if (fileDescriptor != 0) { + return close(fileDescriptor) == 0; + } + return false; + } + #endregion + + #region Private Helpers + private void Dispose(bool disposing) { + if (disposing) { + // free managed here + } + + Trace.Assert(disposing, + string.Format("ERROR: GC finalized a unix file '{0}' with open file descriptor {1} that was not disposed!", + filename, descriptor)); + + if (descriptor != 0) { + // we need to free unmanaged resources to avoid memory leeks + close(descriptor); + descriptor = 0; + filename = null; + } + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.Interop/UnmanagedMemory.cs b/Raspberry.IO.Interop/UnmanagedMemory.cs new file mode 100644 index 0000000..1af4e84 --- /dev/null +++ b/Raspberry.IO.Interop/UnmanagedMemory.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.Interop +{ + /// + /// An memory buffer (reference) to unmanaged memory that can be used for P/Invoke operations. + /// + public class UnmanagedMemory : IMemory + { + #region Fields + private readonly bool owner; + private IntPtr memoryPointer; + private int length; + #endregion + + #region Instance Management + /// + /// Encapsulates the supplied memory address to unmanaged memory. + /// + /// Unmanaged memory address + /// Size in bytes + /// If true the memory will be released if the user calls . + public UnmanagedMemory(IntPtr memoryPointer, int length, bool owner) { + this.memoryPointer = memoryPointer; + this.length = length; + this.owner = owner; + } + + /// + /// Allocates unmanaged memory of the requested size. + /// + /// Memory size in bytes. + public UnmanagedMemory(int length) { + this.length = length; + memoryPointer = Marshal.AllocHGlobal(length); + owner = true; + } + + ~UnmanagedMemory() { + Dispose(false); + } + + /// + /// Free the unmanaged memory. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Free the unmanaged memory. + /// + /// The unmanaged memory will always be released to avoid memory leaks. If you don't want this, don't call this method () in your derived class. + protected virtual void Dispose(bool disposing) { + if (disposing) { + // free managed here + } + + Trace.Assert(disposing, + string.Format("ERROR: GC finalized unmanaged memory of {0} bytes for address {1} that was not disposed!", + length, memoryPointer.ToString("X8"))); + + if (memoryPointer != IntPtr.Zero && owner) { + // free unmanaged memory to avoid memory leeks + Marshal.FreeHGlobal(memoryPointer); + } + memoryPointer = IntPtr.Zero; + length = 0; + } + #endregion + + #region Properties + + /// + /// Pointer to the memory address. + /// + public IntPtr Pointer { + get { return memoryPointer; } + } + + /// + /// Size of the allocated unmanaged memory in bytes. + /// + public int Length { + get { return length; } + } + + /// + /// Indexer, which will allow client code to use [] notation on the class instance itself. + /// + /// Offset to memory + /// Byte at/from the specified position . + public byte this[int index] { + get { return Read(index); } + set { Write(index, value); } + } + + #endregion + + #region Methods + /// + /// Writes at . + /// + /// Offset + /// Data that shall be written. + public void Write(int offset, byte data) { + if (memoryPointer == IntPtr.Zero) { + throw new ObjectDisposedException("Unmanaged memory already disposed."); + } + + if (offset < 0 || offset >= length) { + throw new ArgumentOutOfRangeException("offset"); + } + + Marshal.WriteByte(memoryPointer, offset, data); + } + + /// + /// Reads a byte at . + /// + /// Offset + /// The data. + public byte Read(int offset) { + if (memoryPointer == IntPtr.Zero) { + throw new ObjectDisposedException("Unmanaged memory already disposed."); + } + + if (offset < 0 || offset >= length) { + throw new ArgumentOutOfRangeException("offset"); + } + + return Marshal.ReadByte(memoryPointer, offset); + } + + /// + /// Copies the bytes from to the memory. + /// + /// Source byte array. + /// Copies the data starting from . + /// Copies the data starting at to the memory. + /// Copies bytes. + public void Copy(byte[] source, int sourceIndex, int destinationIndex, int lengthInBytes) { + if (memoryPointer == IntPtr.Zero) { + throw new ObjectDisposedException("Unmanaged memory already disposed."); + } + + if (destinationIndex < 0 || destinationIndex >= length) { + throw new ArgumentOutOfRangeException("destinationIndex"); + } + + if (lengthInBytes < 0 || (destinationIndex + lengthInBytes) > length) { + throw new ArgumentOutOfRangeException("lengthInBytes"); + } + + Marshal.Copy(source, sourceIndex, memoryPointer + destinationIndex, lengthInBytes); + } + + /// + /// Copies data from the memory to the supplied byte array. + /// + /// Copies the data starting from . + /// Destination byte array. + /// Copies the data starting at to the destination byte array. + /// Copies bytes. + public void Copy(int sourceIndex, byte[] destination, int destinationIndex, int lengthInBytes) { + if (memoryPointer == IntPtr.Zero) { + throw new ObjectDisposedException("Unmanaged memory already disposed."); + } + + if (sourceIndex < 0 || sourceIndex >= length) { + throw new ArgumentOutOfRangeException("sourceIndex"); + } + + if (sourceIndex + lengthInBytes >= length) { + throw new ArgumentOutOfRangeException("lengthInBytes"); + } + + Marshal.Copy(memoryPointer + sourceIndex, destination, destinationIndex, lengthInBytes); + } + + /// + /// Returns an enumerator. + /// + /// An enumerator + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + /// + /// Returns an enumerator. + /// + /// An enumerator + public IEnumerator GetEnumerator() { + var tmp = new byte[length]; + Copy(0, tmp, 0, length); + + return ((IEnumerable) tmp) + .GetEnumerator(); + } + + /// + /// Allocates unmanaged memory for and copies its content into it. + /// + /// Structure type + /// The structure that shall be copied into the requested memory buffer. + /// The unmanaged memory buffer containing . + public static UnmanagedMemory CreateAndCopy(T structure) { + var requiredSize = Marshal.SizeOf(structure); + + var memory = new UnmanagedMemory(requiredSize); + Marshal.StructureToPtr(structure, memory.Pointer, false); + + return memory; + } + + /// + /// Allocates unmanaged memory with the size of . + /// + /// Structure type + /// The unmanaged memory buffer of size . + public static UnmanagedMemory CreateFor() { + var requiredSize = Marshal.SizeOf(typeof(T)); + return new UnmanagedMemory(requiredSize); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Endianness.cs b/Raspberry.IO.SerialPeripheralInterface/Endianness.cs new file mode 100644 index 0000000..b956db3 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Endianness.cs @@ -0,0 +1,18 @@ +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Represents the endianness of a SPI communication. + /// + public enum Endianness + { + /// + /// The communication is little endian. + /// + LittleEndian, + + /// + /// The communication is big endian. + /// + BigEndian + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiMode.cs b/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiMode.cs new file mode 100644 index 0000000..a361cff --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiMode.cs @@ -0,0 +1,73 @@ +using System; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// SPI mode + /// + [Flags] + public enum SpiMode : uint + { + /// + /// Clock phase, if set CPHA=1, otherwise CPHA=0. + /// + ClockPhase = Interop.SPI_CPHA, + + /// + /// Clock polarity, if set CPOL=1, otherwise CPOL=0. + /// + ClockPolarity = Interop.SPI_CPOL, + + /// + /// Chip select is a high signal. + /// + ChipSelectActiveHigh = Interop.SPI_CS_HIGH, + + /// + /// The least significant bit comes first. + /// + LeastSignificantBitFirst = Interop.SPI_LSB_FIRST, + + /// + /// Special 3-wire configuration. + /// + ThreeWire = Interop.SPI_3WIRE, + /// + /// Three-wire serial buses + /// + SlaveInOutShared = Interop.SPI_3WIRE, + /// + /// Loopback + /// + Loopback = Interop.SPI_LOOP, + /// + /// Send no chip select signal. + /// + NoChipSelect = Interop.SPI_NO_CS, + /// + /// Slave pulls low to pause. + /// + Ready = Interop.SPI_READY, + /// + /// Slave pulls low to pause. + /// + SlavePullsLowToPause = Interop.SPI_READY, + + /// + /// CPOL=0, CPHA=0 + /// + Mode0 = Interop.SPI_MODE_0, + /// + /// CPOL=0, CPHA=1 + /// + Mode1 = Interop.SPI_MODE_1, + /// + /// CPOL =1, CPHA=0 + /// + Mode2 = Interop.SPI_MODE_2, + /// + /// CPOL=1, CPHA=1 + /// + Mode3 = Interop.SPI_MODE_3 + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiTransferMode.cs b/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiTransferMode.cs new file mode 100644 index 0000000..4583b40 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/EnumTypes/SpiTransferMode.cs @@ -0,0 +1,26 @@ +using System; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Selects if data should be read, written or both. + /// + [Flags] + public enum SpiTransferMode + { + /// + /// Write data to the chip. + /// + Write = 1, + + /// + /// Read data from the chip. + /// + Read = 2, + + /// + /// Write and read data simultaneously. + /// + ReadWrite = 3 + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/ReadonlyTransferBufferException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/ReadonlyTransferBufferException.cs new file mode 100644 index 0000000..b89a163 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/ReadonlyTransferBufferException.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class ReadOnlyTransferBufferException : Exception + { + public ReadOnlyTransferBufferException() {} + public ReadOnlyTransferBufferException(string message) : base(message) {} + public ReadOnlyTransferBufferException(string message, Exception innerException) : base(message, innerException) {} + protected ReadOnlyTransferBufferException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/SendSpiMessageException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SendSpiMessageException.cs new file mode 100644 index 0000000..efdfe0e --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SendSpiMessageException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class SendSpiMessageException : Exception { + public SendSpiMessageException() {} + public SendSpiMessageException(string message) : base(message) {} + public SendSpiMessageException(string message, Exception innerException) : base(message, innerException) {} + protected SendSpiMessageException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetBitsPerWordException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetBitsPerWordException.cs new file mode 100644 index 0000000..62a05a5 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetBitsPerWordException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class SetBitsPerWordException : Exception { + public SetBitsPerWordException() {} + public SetBitsPerWordException(string message) : base(message) {} + public SetBitsPerWordException(string message, Exception innerException) : base(message, innerException) {} + protected SetBitsPerWordException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetMaxSpeedException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetMaxSpeedException.cs new file mode 100644 index 0000000..d339575 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetMaxSpeedException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class SetMaxSpeedException : Exception { + public SetMaxSpeedException() {} + public SetMaxSpeedException(string message) : base(message) {} + public SetMaxSpeedException(string message, Exception innerException) : base(message, innerException) {} + protected SetMaxSpeedException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetSpiModeException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetSpiModeException.cs new file mode 100644 index 0000000..339a9e6 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/SetSpiModeException.cs @@ -0,0 +1,15 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class SetSpiModeException : Exception { + public SetSpiModeException() {} + public SetSpiModeException(string message) : base(message) {} + public SetSpiModeException(string message, Exception innerException) : base(message, innerException) {} + protected SetSpiModeException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Exceptions/WriteOnlyTransferBufferException.cs b/Raspberry.IO.SerialPeripheralInterface/Exceptions/WriteOnlyTransferBufferException.cs new file mode 100644 index 0000000..d209d58 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Exceptions/WriteOnlyTransferBufferException.cs @@ -0,0 +1,16 @@ +using System; +using System.Runtime.Serialization; + +namespace Raspberry.IO.SerialPeripheralInterface +{ +#pragma warning disable 1591 + [Serializable] + public class WriteOnlyTransferBufferException : Exception + { + public WriteOnlyTransferBufferException() {} + public WriteOnlyTransferBufferException(string message) : base(message) {} + public WriteOnlyTransferBufferException(string message, Exception innerException) : base(message, innerException) {} + protected WriteOnlyTransferBufferException(SerializationInfo info, StreamingContext context) : base(info, context) {} + } +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/INativeSpiConnection.cs b/Raspberry.IO.SerialPeripheralInterface/INativeSpiConnection.cs new file mode 100644 index 0000000..7dcef5e --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/INativeSpiConnection.cs @@ -0,0 +1,98 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Native SPI connection that communicates with Linux's userspace SPI driver (e.g. /dev/spidev0.0) using IOCTL. + /// + public interface INativeSpiConnection : IDisposable + { + #region Properties + + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer. + /// + UInt16 Delay { get; } + + /// + /// Maximum clock speed in Hz. + /// + UInt32 MaxSpeed { get; } + + /// + /// SPI mode + /// + SpiMode Mode { get; } + + /// + /// The device's wordsize + /// + byte BitsPerWord { get; } + + #endregion + + #region Methods + + /// + /// Sets the . + /// + /// Delay in µsec. + void SetDelay(UInt16 delayInMicroSeconds); + + /// + /// Sets the maximum clock speed. + /// + /// The speed in Hz + void SetMaxSpeed(UInt32 maxSpeedInHz); + + /// + /// Sets the device's wordsize . + /// + /// Bits per word + void SetBitsPerWord(byte wordSize); + + /// + /// Sets the . + /// + /// SPI mode + void SetSpiMode(SpiMode spiMode); + + /// + /// Creates a transfer buffer of the given and copies the connection settings to it. + /// + /// Memory size in bytes. + /// The transfer mode. + /// The requested transfer buffer. + ISpiTransferBuffer CreateTransferBuffer(int sizeInBytes, SpiTransferMode transferMode); + + /// + /// Creates transfer buffers for . + /// + /// The number of messages to send. + /// Message size in bytes. + /// The transfer mode. + /// The requested transfer buffer collection. + ISpiTransferBufferCollection CreateTransferBufferCollection(int numberOfMessages, int messageSizeInBytes, + SpiTransferMode transferMode); + + /// + /// Starts the SPI data transfer. + /// + /// The transfer buffer that contains data to be send and/or the received data. + /// An that contains the result of the transfer operation. + int Transfer(ISpiTransferBuffer buffer); + + /// + /// Starts the SPI data transfer. + /// + /// The transfer buffers that contain data to be send and/or the received data. + /// An that contains the result of the transfer operation. + int Transfer(ISpiTransferBufferCollection transferBuffers); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/ISpiControlDevice.cs b/Raspberry.IO.SerialPeripheralInterface/ISpiControlDevice.cs new file mode 100644 index 0000000..eac438c --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/ISpiControlDevice.cs @@ -0,0 +1,27 @@ +using System; +using Raspberry.IO.Interop; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A Linux I/O control device that additionally can send/receive SPI data structures. + /// + public interface ISpiControlDevice : IControlDevice + { + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The SPI control data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + int Control(UInt32 request, ref SpiTransferControlStructure data); + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The SPI control data structures to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + int Control(UInt32 request, SpiTransferControlStructure[] data); + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBuffer.cs b/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBuffer.cs new file mode 100644 index 0000000..7bd7e04 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBuffer.cs @@ -0,0 +1,100 @@ +#region References + +using System; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A transfer buffer used to read from / write to the SPI bus. + /// + public interface ISpiTransferBuffer : IDisposable + { + #region Properties + + /// + /// Temporary override of the device's wordsize + /// + /// + /// The bits per word. + /// + byte BitsPerWord { get; set; } + + /// + /// Temporary override of the device's bitrate (in Hz) + /// + /// + /// The speed. + /// + UInt32 Speed { get; set; } + + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer. + /// + /// + /// The delay. + /// + UInt16 Delay { get; set; } + + /// + /// Set to true to deselect device before starting the next transfer. + /// + /// + /// true if device is delected before starting next transfer; otherwise, false. + /// + bool ChipSelectChange { get; set; } + + /// + /// Pad. + /// + /// + /// The pad. + /// + UInt32 Pad { get; set; } + + /// + /// Specifies if the transfer shall read and/or write. + /// + /// + /// The transfer mode. + /// + SpiTransferMode TransferMode { get; } + + /// + /// Length of and buffers, in bytes + /// + /// + /// The length. + /// + int Length { get; } + + /// + /// Holds pointer to userspace buffer with transmit data, or null. If no data is provided, zeroes are shifted out + /// + /// + /// The tx. + /// + IMemory Tx { get; } + + /// + /// Holds pointer to userspace buffer for receive data, or null + /// + /// + /// The rx. + /// + IMemory Rx { get; } + + /// + /// The IOCTL structure that contains control information for a single SPI transfer + /// + /// + /// The control structure. + /// + SpiTransferControlStructure ControlStructure { get; } + + #endregion + + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBufferCollection.cs b/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBufferCollection.cs new file mode 100644 index 0000000..a47aab1 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/ISpiTransferBufferCollection.cs @@ -0,0 +1,42 @@ +#region References + +using System; +using System.Collections.Generic; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A collection of transfer buffers that can be used to read from / write to the SPI bus. + /// + public interface ISpiTransferBufferCollection : IDisposable, IEnumerable + { + #region Properties + + /// + /// Number of structures. + /// + int Length { get; } + + /// + /// Can be used to request a specific from the collection. + /// + /// Index + /// The requested + ISpiTransferBuffer this[int index] { get; } + + #endregion + + #region Methods + + /// + /// A method that returns a specific from the collection. + /// + /// Index + /// The requested + ISpiTransferBuffer Get(int index); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Interop/Interop.cs b/Raspberry.IO.SerialPeripheralInterface/Interop/Interop.cs new file mode 100644 index 0000000..abc6f65 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Interop/Interop.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + // ReSharper disable InconsistentNaming + internal static class Interop + { + public const UInt32 SPI_CPHA = 0x01; + public const UInt32 SPI_CPOL = 0x02; + + public const UInt32 SPI_MODE_0 = (0 | 0); + public const UInt32 SPI_MODE_1 = (0 | SPI_CPHA); + public const UInt32 SPI_MODE_2 = (SPI_CPOL | 0); + public const UInt32 SPI_MODE_3 = (SPI_CPOL | SPI_CPHA); + + public const UInt32 SPI_CS_HIGH = 0x04; + public const UInt32 SPI_LSB_FIRST = 0x08; + public const UInt32 SPI_3WIRE = 0x10; + + public const UInt32 SPI_LOOP = 0x20; + public const UInt32 SPI_NO_CS = 0x40; + public const UInt32 SPI_READY = 0x80; + + public const UInt32 SPI_IOC_MESSAGE_BASE = 0x40006b00; + public const int SPI_IOC_MESSAGE_NUMBER_SHIFT = 16; + + private static readonly int transferMessageSize = Marshal.SizeOf(typeof(SpiTransferControlStructure)); + + internal static UInt32 GetSpiMessageRequest(int numberOfMessages) { + var size = unchecked((UInt32)(transferMessageSize * numberOfMessages)); + return SPI_IOC_MESSAGE_BASE | (size << SPI_IOC_MESSAGE_NUMBER_SHIFT); + } + } + + // ReSharper restore InconsistentNaming +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Interop/SpiTransferControlStructure.cs b/Raspberry.IO.SerialPeripheralInterface/Interop/SpiTransferControlStructure.cs new file mode 100644 index 0000000..c3759d2 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Interop/SpiTransferControlStructure.cs @@ -0,0 +1,60 @@ +#region References + +using System; +using System.Runtime.InteropServices; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A IOCTL structure that describes a single SPI transfer + /// + [StructLayout(LayoutKind.Sequential, Pack = 0)] + public struct SpiTransferControlStructure + { + #region Properties + + /// + /// Holds pointer to userspace buffer with transmit data, or 0. If no data is provided, zeroes are shifted out + /// + public UInt64 Tx; + + /// + /// Holds pointer to userspace buffer for receive data, or 0 + /// + public UInt64 Rx; + + /// + /// Length of and buffers, in bytes + /// + public UInt32 Length; + + /// + /// Temporary override of the device's bitrate (in Hz) + /// + public UInt32 Speed; + + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer + /// + public UInt16 Delay; + + /// + /// Temporary override of the device's wordsize + /// + public Byte BitsPerWord; + + /// + /// Set to true to deselect device before starting the next transfer + /// + public Byte ChipSelectChange; + + /// + /// Pad + /// + public UInt32 Pad; + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/NativeSpiConnection.cs b/Raspberry.IO.SerialPeripheralInterface/NativeSpiConnection.cs new file mode 100644 index 0000000..8135cf9 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/NativeSpiConnection.cs @@ -0,0 +1,252 @@ +#region References + +using System; +using System.Linq; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Native SPI connection that communicates with Linux's userspace SPI driver (e.g. /dev/spidev0.0) using IOCTL. + /// + public class NativeSpiConnection : INativeSpiConnection + { + #region Constants + internal const UInt32 SPI_IOC_RD_MODE = 0x80016b01; + internal const UInt32 SPI_IOC_WR_MODE = 0x40016b01; + internal const UInt32 SPI_IOC_RD_LSB_FIRST = 0x80016b02; + internal const UInt32 SPI_IOC_WR_LSB_FIRST = 0x40016b02; + internal const UInt32 SPI_IOC_RD_BITS_PER_WORD = 0x80016b03; + internal const UInt32 SPI_IOC_WR_BITS_PER_WORD = 0x40016b03; + internal const UInt32 SPI_IOC_RD_MAX_SPEED_HZ = 0x80046b04; + internal const UInt32 SPI_IOC_WR_MAX_SPEED_HZ = 0x40046b04; + #endregion + + #region Fields + private readonly ISpiControlDevice deviceFile; + private UInt16 delay; + private UInt32 maxSpeed; + private UInt32 mode; + private byte bitsPerWord; + #endregion + + #region Instance Management + + /// + /// Creates a new instance of the class. + /// + /// A control device (IOCTL) to the device file (e.g. /dev/spidev0.0). + public NativeSpiConnection(ISpiControlDevice deviceFile) { + this.deviceFile = deviceFile; + } + + /// + /// Creates a new instance of the class and initializes it. + /// + /// A control device (IOCTL) to the device file (e.g. /dev/spidev0.0). + /// Connection settings + public NativeSpiConnection(ISpiControlDevice deviceFile, SpiConnectionSettings settings) + : this(deviceFile) + { + Init(settings); + } + + /// + /// Creates a new instance of the class and initializes it. + /// + /// Full path to the SPI device file (e.g. /dev/spidev0.0). + /// Connection settings + public NativeSpiConnection(string deviceFilePath, SpiConnectionSettings settings) + : this(new SpiControlDevice(new UnixFile(deviceFilePath, UnixFileMode.ReadWrite)), settings) + {} + + /// + /// Creates a new instance of the class. + /// + /// Full path to the SPI device file (e.g. /dev/spidev0.0). + public NativeSpiConnection(string deviceFilePath) + : this(new SpiControlDevice(new UnixFile(deviceFilePath, UnixFileMode.ReadWrite))) + {} + + /// + /// Dispose instance and free all resources. + /// + public void Dispose() { + GC.SuppressFinalize(this); + Dispose(true); + } + + /// + /// Disposes the instance. + /// + /// If true all managed resources will be disposed + protected virtual void Dispose(bool disposing) { + if (disposing) { + deviceFile.Dispose(); + } + } + + #endregion + + #region Properties + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer. + /// + public UInt16 Delay { + get { return delay; } + } + + /// + /// Maximum clock speed in Hz. + /// + public UInt32 MaxSpeed { + get { return maxSpeed; } + } + + /// + /// SPI mode + /// + public SpiMode Mode { + get { return (SpiMode)mode; } + } + + /// + /// The device's wordsize + /// + public byte BitsPerWord { + get { return bitsPerWord; } + } + #endregion + + #region Methods + + /// + /// Sets the . + /// + /// Delay in µsec. + public void SetDelay(UInt16 delayInMicroSeconds) { + delay = delayInMicroSeconds; + } + + /// + /// Sets the maximum clock speed. + /// + /// The speed in Hz + public void SetMaxSpeed(UInt32 maxSpeedInHz) { + maxSpeed = maxSpeedInHz; + deviceFile.Control(SPI_IOC_WR_MAX_SPEED_HZ, ref maxSpeedInHz) + .ThrowOnPInvokeError("Can't set max speed in HZ (SPI_IOC_WR_MAX_SPEED_HZ). Error {1}: {2}"); + deviceFile.Control(SPI_IOC_RD_MAX_SPEED_HZ, ref maxSpeedInHz) + .ThrowOnPInvokeError("Can't set max speed in HZ (SPI_IOC_RD_MAX_SPEED_HZ). Error {1}: {2}"); + } + + /// + /// Sets the device's wordsize . + /// + /// Bits per word + public void SetBitsPerWord(byte wordSize) { + bitsPerWord = wordSize; + deviceFile.Control(SPI_IOC_WR_BITS_PER_WORD, ref wordSize) + .ThrowOnPInvokeError("Can't set bits per word (SPI_IOC_WR_BITS_PER_WORD). Error {1}: {2}"); + deviceFile.Control(SPI_IOC_RD_BITS_PER_WORD, ref wordSize) + .ThrowOnPInvokeError("Can't set bits per word (SPI_IOC_RD_BITS_PER_WORD). Error {1}: {2}"); + } + + /// + /// Sets the . + /// + /// SPI mode + public void SetSpiMode(SpiMode spiMode) { + mode = (UInt32) spiMode; + deviceFile.Control(SPI_IOC_WR_MODE, ref mode) + .ThrowOnPInvokeError("Can't set SPI mode (SPI_IOC_WR_MODE). Error {1}: {2}"); + deviceFile.Control(SPI_IOC_RD_MODE, ref mode) + .ThrowOnPInvokeError("Can't set SPI mode (SPI_IOC_RD_MODE). Error {1}: {2}"); + } + + /// + /// Creates a transfer buffer of the given and copies the connection settings to it. + /// + /// Memory size in bytes. + /// The transfer mode. + /// The requested transfer buffer. + public ISpiTransferBuffer CreateTransferBuffer(int sizeInBytes, SpiTransferMode transferMode) { + return new SpiTransferBuffer(sizeInBytes, transferMode) { + BitsPerWord = bitsPerWord, + Delay = delay, + Speed = maxSpeed + }; + } + + /// + /// Creates transfer buffers for . + /// + /// The number of messages to send. + /// Message size in bytes. + /// The transfer mode. + /// The requested transfer buffer collection. + public ISpiTransferBufferCollection CreateTransferBufferCollection(int numberOfMessages, int messageSizeInBytes, SpiTransferMode transferMode) { + var collection = new SpiTransferBufferCollection(numberOfMessages, messageSizeInBytes, transferMode); + foreach (var transferBuffer in collection) { + transferBuffer.BitsPerWord = bitsPerWord; + transferBuffer.Delay = delay; + transferBuffer.Speed = maxSpeed; + } + return collection; + } + + /// + /// Starts the SPI data transfer. + /// + /// The transfer buffer that contains data to be send and/or the received data. + /// An that contains the result of the transfer operation. + public int Transfer(ISpiTransferBuffer buffer) { + if (buffer == null) { + throw new ArgumentNullException("buffer"); + } + + var request = Interop.GetSpiMessageRequest(1); + var structure = buffer.ControlStructure; + var result = deviceFile.Control(request, ref structure); + + result.ThrowOnPInvokeError("Can't send SPI message. Error {1}: {2}"); + + return result; + } + + /// + /// Starts the SPI data transfer. + /// + /// The transfer buffers that contain data to be send and/or the received data. + /// An that contains the result of the transfer operation. + public int Transfer(ISpiTransferBufferCollection transferBuffers) { + if (transferBuffers == null) { + throw new ArgumentNullException("transferBuffers"); + } + + var request = Interop.GetSpiMessageRequest(transferBuffers.Length); + + var structures = transferBuffers + .Select(buf => buf.ControlStructure) + .ToArray(); + var result = deviceFile.Control(request, structures); + + result.ThrowOnPInvokeError("Can't send SPI messages. Error {1}: {2}"); + + return result; + } + + #endregion + + #region Private Helpers + private void Init(SpiConnectionSettings settings) { + SetSpiMode(settings.Mode); + SetBitsPerWord(settings.BitsPerWord); + SetMaxSpeed(settings.MaxSpeed); + SetDelay(settings.Delay); + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs b/Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..05277c2 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Raspberry.IO.SerialPeripheralInterface")] +[assembly: AssemblyDescription("Raspberry Pi SPI Mono Library")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5548a61b-6677-435f-be41-13aa4f05e994")] + +// Make the internal methods, classes etc. visible to the unit test project. +[assembly: InternalsVisibleTo("Tests.Raspberry.IO.SerialPeripheralInterface")] diff --git a/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj b/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj new file mode 100644 index 0000000..13fa724 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj @@ -0,0 +1,96 @@ + + + + Debug + AnyCPU + {326342E5-0411-40E8-9F2D-563D6B192568} + Library + Properties + Raspberry.IO.SerialPeripheralInterface + Raspberry.IO.SerialPeripheralInterface + v4.0 + 512 + ..\ + + + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + bin\Debug\Raspberry.IO.SerialPeripheralInterface.XML + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Raspberry.IO.SerialPeripheralInterface.XML + + + + $(SolutionDir)packages\Raspberry.System.2.1\lib\net40\Raspberry.System.dll + True + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {D2E41147-5BF6-4109-A497-C76284F3C020} + Raspberry.IO + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + + + + + + \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings b/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings new file mode 100644 index 0000000..ba0505e --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings @@ -0,0 +1,6 @@ + + True + True + True + True + True \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiConnection.cs b/Raspberry.IO.SerialPeripheralInterface/SpiConnection.cs new file mode 100644 index 0000000..556f8b9 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiConnection.cs @@ -0,0 +1,229 @@ +#region References + +using System; +using Raspberry.Timers; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Represents a connection to a SPI device. + /// + public class SpiConnection : IDisposable + { + #region Fields + + private readonly IOutputBinaryPin clockPin; + private readonly IOutputBinaryPin selectSlavePin; + private readonly IInputBinaryPin misoPin; + private readonly IOutputBinaryPin mosiPin; + + private readonly Endianness endianness = Endianness.LittleEndian; + + private static readonly TimeSpan syncDelay = TimeSpan.FromMilliseconds(1); + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// The clock pin. + /// The select slave pin. + /// The miso pin. + /// The mosi pin. + /// The endianness. + public SpiConnection(IOutputBinaryPin clockPin, IOutputBinaryPin selectSlavePin, IInputBinaryPin misoPin, IOutputBinaryPin mosiPin, Endianness endianness) + { + this.clockPin = clockPin; + this.selectSlavePin = selectSlavePin; + this.misoPin = misoPin; + this.mosiPin = mosiPin; + this.endianness = endianness; + + clockPin.Write(false); + selectSlavePin.Write(true); + + if (mosiPin != null) + mosiPin.Write(false); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + Close(); + } + + #endregion + + #region Methods + + /// + /// Closes this instance. + /// + public void Close() + { + clockPin.Dispose(); + selectSlavePin.Dispose(); + if (mosiPin != null) + mosiPin.Dispose(); + if (misoPin != null) + misoPin.Dispose(); + } + + /// + /// Selects the slave device. + /// + /// The slave selection context. + public SpiSlaveSelectionContext SelectSlave() + { + selectSlavePin.Write(false); + return new SpiSlaveSelectionContext(this); + } + + /// + /// Synchronizes the devices. + /// + public void Synchronize() + { + clockPin.Write(true); + Timer.Sleep(syncDelay); + clockPin.Write(false); + } + + /// + /// Writes the specified bit to the device. + /// + /// The data. + public void Write(bool data) + { + if (mosiPin == null) + throw new NotSupportedException("No MOSI pin has been provided"); + + mosiPin.Write(data); + Synchronize(); + } + + /// + /// Writes the specified data. + /// + /// The data. + /// The bit count. + public void Write(byte data, int bitCount) + { + if (bitCount > 8) + throw new ArgumentOutOfRangeException("bitCount", bitCount, "byte data cannot contain more than 8 bits"); + + SafeWrite(data, bitCount); + } + + /// + /// Writes the specified data. + /// + /// The data. + /// The bit count. + public void Write(ushort data, int bitCount) + { + if (bitCount > 16) + throw new ArgumentOutOfRangeException("bitCount", bitCount, "ushort data cannot contain more than 16 bits"); + + SafeWrite(data, bitCount); + } + + /// + /// Writes the specified data. + /// + /// The data. + /// The bit count. + public void Write(uint data, int bitCount) + { + if (bitCount > 32) + throw new ArgumentOutOfRangeException("bitCount", bitCount, "uint data cannot contain more than 32 bits"); + + SafeWrite(data, bitCount); + } + + /// + /// Writes the specified data. + /// + /// The data. + /// The bit count. + public void Write(ulong data, int bitCount) + { + if (bitCount > 64) + throw new ArgumentOutOfRangeException("bitCount", bitCount, "ulong data cannot contain more than 64 bits"); + + SafeWrite(data, bitCount); + } + + /// + /// Reads a bit from the device. + /// + /// The bit status. + public bool Read() + { + if (misoPin == null) + throw new NotSupportedException("No MISO pin has been provided"); + + Synchronize(); + return misoPin.Read(); + } + + /// + /// Reads the specified number of bits from the device. + /// + /// The bit count. + /// The read value. + public ulong Read(int bitCount) + { + if (bitCount > 64) + throw new ArgumentOutOfRangeException("bitCount", bitCount, "ulong data cannot contain more than 64 bits"); + + ulong data = 0; + for (var i = 0; i < bitCount; i++) + { + var index = endianness == Endianness.BigEndian + ? i + : bitCount - 1 - i; + + var bit = Read(); + if (bit) + data |= ((ulong)1 << index); + } + + return data; + } + + #endregion + + #region Internal Methods + + internal void DeselectSlave() + { + selectSlavePin.Write(true); + } + + #endregion + + #region Private Helpers + + private void SafeWrite(ulong data, int bitCount) + { + for (var i = 0; i < bitCount; i++) + { + var index = endianness == Endianness.BigEndian + ? i + : bitCount - 1 - i; + + var bit = data & ((ulong) 1 << index); + Write(bit != 0); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiConnectionSettings.cs b/Raspberry.IO.SerialPeripheralInterface/SpiConnectionSettings.cs new file mode 100644 index 0000000..3afc483 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiConnectionSettings.cs @@ -0,0 +1,48 @@ +using System; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// SPI connection parameters + /// + public class SpiConnectionSettings + { + #region Fields + private uint maxSpeed = 5000000; + private ushort delay; + private byte bitsPerWord = 8; + #endregion + + #region Properties + /// + /// Clock speed in Hz + /// + public UInt32 MaxSpeed { + get { return maxSpeed; } + set { maxSpeed = value; } + } + + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer + /// + public UInt16 Delay { + get { return delay; } + set { delay = value; } + } + + /// + /// The device's word size + /// + public byte BitsPerWord { + get { return bitsPerWord; } + set { bitsPerWord = value; } + } + + /// + /// SPI mode + /// + public SpiMode Mode { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiControlDevice.cs b/Raspberry.IO.SerialPeripheralInterface/SpiControlDevice.cs new file mode 100644 index 0000000..19ba469 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiControlDevice.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.InteropServices; +using Raspberry.IO.Interop; + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A Linux I/O control device that additionally can send/receive SPI data structures. + /// + public class SpiControlDevice : ControlDevice, ISpiControlDevice + { + #region Libc imports + [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] + private static extern int ioctl(int descriptor, UInt32 request, ref SpiTransferControlStructure data); + + [DllImport("libc", EntryPoint = "ioctl", SetLastError = true)] + private static extern int ioctl(int descriptor, UInt32 request, SpiTransferControlStructure[] data); + #endregion + + #region Instance Management + /// + /// Initializes a new instance of the class. + /// + /// A opened special file that can be controlled using ioctl-system calls. + /// will be disposed if the user calls Dispose on this instance. + public SpiControlDevice(IFile file) + :base(file) + {} + + /// + /// Initializes a new instance of the class. + /// + /// A opened special file that can be controlled using ioctl-system calls. + /// If true the supplied will be disposed if the user calls Dispose on this instance. + public SpiControlDevice(IFile file, bool disposeFile) + :base(file, disposeFile){} + #endregion + + #region Methods + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The data to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + public int Control(UInt32 request, ref SpiTransferControlStructure data) { + var result = ioctl(file.Descriptor, request, ref data); + return result; + } + + /// + /// The function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl requests. + /// + /// A device-dependent request code. + /// The SPI control data structures to be transmitted. + /// Usually, on success zero is returned. A few ioctls use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately. + public int Control(UInt32 request, SpiTransferControlStructure[] data) { + var result = ioctl(file.Descriptor, request, data); + return result; + } + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiSlaveSelectionContext.cs b/Raspberry.IO.SerialPeripheralInterface/SpiSlaveSelectionContext.cs new file mode 100644 index 0000000..8bb2d26 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiSlaveSelectionContext.cs @@ -0,0 +1,37 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// Represents a SPI slave selection context. + /// + public class SpiSlaveSelectionContext : IDisposable + { + #region Fields + + private readonly SpiConnection connection; + + #endregion + + #region Instance Management + + internal SpiSlaveSelectionContext(SpiConnection connection) + { + this.connection = connection; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + void IDisposable.Dispose() + { + connection.DeselectSlave(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiTransferBuffer.cs b/Raspberry.IO.SerialPeripheralInterface/SpiTransferBuffer.cs new file mode 100644 index 0000000..2a52447 --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiTransferBuffer.cs @@ -0,0 +1,181 @@ +#region References + +using System; +using Raspberry.IO.Interop; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A transfer buffer used to read from / write to the SPI bus. + /// + public class SpiTransferBuffer : ISpiTransferBuffer + { + #region Fields + private readonly IMemory txBuf; + private readonly IMemory rxBuf; + + private readonly SpiTransferMode mode; + private SpiTransferControlStructure control; + + #endregion + + #region Properties + + /// + /// Temporary override of the device's wordsize + /// + public byte BitsPerWord { + get { return control.BitsPerWord; } + set { control.BitsPerWord = value; } + } + + /// + /// Temporary override of the device's bitrate (in Hz) + /// + public UInt32 Speed { + get { return control.Speed; } + set { control.Speed = value; } + } + + /// + /// If nonzero, how long to delay (in µ seconds) after the last bit transfer before optionally deselecting the device before the next transfer. + /// + public UInt16 Delay { + get { return control.Delay; } + set { control.Delay = value; } + } + + /// + /// Set to true to deselect device before starting the next transfer. + /// + public bool ChipSelectChange { + get { return control.ChipSelectChange == 1; } + set { + control.ChipSelectChange = value + ? (byte) 1 + : (byte) 0; + } + } + + /// + /// Pad + /// + public UInt32 Pad { + get { return control.Pad; } + set { control.Pad = value; } + } + + /// + /// Specifies if the transfer shall read and/or write. + /// + public SpiTransferMode TransferMode { + get { return mode; } + } + + /// + /// Length of and buffers, in bytes + /// + public int Length { + get { return unchecked((int)control.Length); } + } + + /// + /// Holds pointer to userspace buffer with transmit data, or null. If no data is provided, zeroes are shifted out + /// + public IMemory Tx { + get { return txBuf; } + } + + /// + /// Holds pointer to userspace buffer for receive data, or null + /// + public IMemory Rx { + get { return rxBuf; } + } + + /// + /// The IOCTL structure that contains control information for a single SPI transfer + /// + public SpiTransferControlStructure ControlStructure { + get { return control; } + } + + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// Size of data that shall be transmitted. + /// Specifies read and/or write mode. + public SpiTransferBuffer(int lengthInBytes, SpiTransferMode transferMode) { + if (transferMode == 0) { + throw new ArgumentException("An appropriate transfer mode must be specified (read/write)", "transferMode"); + } + + mode = transferMode; + + if ((TransferMode & SpiTransferMode.Write) == SpiTransferMode.Write) { + txBuf = new ManagedMemory(lengthInBytes); + } + if ((TransferMode & SpiTransferMode.Read) == SpiTransferMode.Read) { + rxBuf = new ManagedMemory(lengthInBytes); + } + + var txPtr = ReferenceEquals(Tx, null) + ? 0 + : unchecked((UInt64) Tx.Pointer.ToInt64()); + + var rxPtr = ReferenceEquals(Rx, null) + ? 0 + : unchecked((UInt64) Rx.Pointer.ToInt64()); + + control.Length = unchecked((uint)lengthInBytes); + control.Tx = txPtr; + control.Rx = rxPtr; + } + + /// + /// Finalizer for + /// + ~SpiTransferBuffer() { + Dispose(false); + GC.SuppressFinalize(this); + } + + /// + /// Release all unmanaged memory. + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the instance. + /// + /// The memory will always be released to avoid memory leaks. If you don't want this, don't call this method () in your derived class. + protected virtual void Dispose(bool disposing) { + if (disposing) { + // free managed stuff here + } + + control.Rx = 0; + control.Tx = 0; + control.Length = 0; + + // always free managed/unmanaged memory + if (!ReferenceEquals(txBuf, null)) { + txBuf.Dispose(); + } + if (!ReferenceEquals(rxBuf, null)) { + rxBuf.Dispose(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferCollection.cs b/Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferCollection.cs new file mode 100644 index 0000000..dd40abd --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferCollection.cs @@ -0,0 +1,120 @@ +#region References + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +#endregion + +namespace Raspberry.IO.SerialPeripheralInterface +{ + /// + /// A collection of transfer buffers that can be used to read from / write to the SPI bus. + /// + public class SpiTransferBufferCollection : ISpiTransferBufferCollection + { + #region Fields + private ISpiTransferBuffer[] transferBuffers; + #endregion + + #region Instance Management + + /// + /// Initializes a new instance of the class. + /// + /// Number of tranfer messages + /// Message size in bytes + /// Transfer mode + public SpiTransferBufferCollection(int numberOfMessages, int messageLengthInBytes, SpiTransferMode transferMode) { + if (numberOfMessages <= 0) { + throw new ArgumentOutOfRangeException("numberOfMessages", numberOfMessages, string.Format(CultureInfo.InvariantCulture, "{0} is not a valid number of messages", numberOfMessages)); + } + + transferBuffers = new ISpiTransferBuffer[numberOfMessages]; + for (var i = 0; i < numberOfMessages; i++) { + transferBuffers[i] = new SpiTransferBuffer(messageLengthInBytes, transferMode); + } + } + + /// + /// Finalizer for + /// + ~SpiTransferBufferCollection() { + Dispose(false); + } + + /// + /// Disposes the collection of . + /// + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Dispose the collection of . + /// + /// If true all transfer buffers will be disposed. + protected virtual void Dispose(bool disposing) { + if (disposing) { + foreach (var buffer in transferBuffers) { + buffer.Dispose(); + } + transferBuffers = new ISpiTransferBuffer[0]; + } + } + + #endregion + + #region Properties + + /// + /// Number of structures. + /// + public int Length { + get { return transferBuffers.Length; } + } + + /// + /// Can be used to request a specific from the collection. + /// + /// Index + /// The requested + public ISpiTransferBuffer this[int index] { + get { return transferBuffers[index]; } + } + + #endregion + + #region Methods + + /// + /// A method that returns a specific from the collection. + /// + /// Index + /// The requested + public ISpiTransferBuffer Get(int index) { + return transferBuffers[index]; + } + + /// + /// Returns an enumerator. + /// + /// An enumerator + public IEnumerator GetEnumerator() { + return transferBuffers.OfType().GetEnumerator(); + } + + /// + /// Returns an enumerator. + /// + /// An enumerator + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO.SerialPeripheralInterface/packages.config b/Raspberry.IO.SerialPeripheralInterface/packages.config new file mode 100644 index 0000000..0f101ec --- /dev/null +++ b/Raspberry.IO.SerialPeripheralInterface/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Raspberry.IO/AnalogValue.cs b/Raspberry.IO/AnalogValue.cs new file mode 100644 index 0000000..c5e1694 --- /dev/null +++ b/Raspberry.IO/AnalogValue.cs @@ -0,0 +1,57 @@ +namespace Raspberry.IO +{ + /// + /// Represents an analog value. + /// + public class AnalogValue + { + private decimal value; + private decimal range; + + /// + /// Initializes a new instance of the class. + /// + /// The total range (ie. maximum value). + /// The value. + public AnalogValue(decimal value, decimal range = 1) + { + this.range = range; + this.value = value; + } + + /// + /// Gets or sets the discrete value. + /// + /// + /// The discrete value. + /// + public decimal Value + { + get { return value; } + set { this.value = value; } + } + + /// + /// Gets or sets the total range. + /// + /// + /// The total range, ie. the maximum value. + /// + public decimal Range + { + get { return range; } + set { range = value; } + } + + /// + /// Gets the relative value. + /// + /// + /// The relative value. + /// + public decimal Relative + { + get { return value / range; } + } + } +} \ No newline at end of file diff --git a/Raspberry.IO/BinaryPinExtensionMethods.cs b/Raspberry.IO/BinaryPinExtensionMethods.cs new file mode 100644 index 0000000..22295a1 --- /dev/null +++ b/Raspberry.IO/BinaryPinExtensionMethods.cs @@ -0,0 +1,38 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides extension methods for binary pins. + /// + public static class BinaryPinExtensionMethods + { + #region Methods + + /// + /// Waits for a pin to reach the specified state, then measures the time it remains in this state. + /// + /// The measure pin. + /// if set to true, wait for the pin to be up. + /// The first phase timeout. + /// The second phase timeout. + /// + /// The time the pin remains up, in milliseconds. + /// + public static TimeSpan Time(this IInputBinaryPin pin, bool waitForUp = true, TimeSpan phase1Timeout = new TimeSpan(), TimeSpan phase2Timeout = new TimeSpan()) + { + pin.Wait(waitForUp, phase1Timeout); + + var waitDown = DateTime.UtcNow; + pin.Wait(!waitForUp, phase2Timeout); + + return DateTime.UtcNow - waitDown; + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/ByteExtensionMethods.cs b/Raspberry.IO/ByteExtensionMethods.cs new file mode 100644 index 0000000..c67226d --- /dev/null +++ b/Raspberry.IO/ByteExtensionMethods.cs @@ -0,0 +1,40 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides extension methods for byte and byte arrays. + /// + public static class ByteExtensionMethods + { + #region Methods + + /// + /// Converts a byte array/enumerable to a bit string. + /// + /// bytes to be converted. + /// A bit string + public static string ToBitString(this IEnumerable bytes) + { + var sb = new StringBuilder(255); + foreach (var value in bytes.Select(@byte => Convert.ToString(@byte, 2))) + { + if (value.Length < 8) + { + sb.Append(new string('0', 8 - value.Length)); + } + sb.Append(value); + } + return sb.ToString(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/IInputAnalogPin.cs b/Raspberry.IO/IInputAnalogPin.cs new file mode 100644 index 0000000..ae32205 --- /dev/null +++ b/Raspberry.IO/IInputAnalogPin.cs @@ -0,0 +1,24 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides an interface for input, analog pin. + /// + public interface IInputAnalogPin : IDisposable + { + #region Methods + + /// + /// Reads the value of the pin. + /// + /// The value. + AnalogValue Read(); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/IInputBinaryPin.cs b/Raspberry.IO/IInputBinaryPin.cs new file mode 100644 index 0000000..e879c27 --- /dev/null +++ b/Raspberry.IO/IInputBinaryPin.cs @@ -0,0 +1,32 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides an interface for input, binary pins. + /// + public interface IInputBinaryPin : IDisposable + { + #region Methods + + /// + /// Reads the state of the pin. + /// + /// true if the pin is in high state; otherwise, false. + bool Read(); + + /// + /// Waits for the specified pin to be in the specified state. + /// + /// if set to true waits for the pin to be up. Default value is true. + /// The timeout. Default value is . + /// If timeout is set to , a default timeout is used instead. + void Wait(bool waitForUp = true, TimeSpan timeout = new TimeSpan()); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/IInputOutputBinaryPin.cs b/Raspberry.IO/IInputOutputBinaryPin.cs new file mode 100644 index 0000000..6ac3688 --- /dev/null +++ b/Raspberry.IO/IInputOutputBinaryPin.cs @@ -0,0 +1,18 @@ +namespace Raspberry.IO +{ + /// + /// Provides an interface for bidirectional binary pins. + /// + public interface IInputOutputBinaryPin : IInputBinaryPin, IOutputBinaryPin + { + /// + /// Prepares the pin to act as an input. + /// + void AsInput(); + + /// + /// Prepares the pin to act as an output. + /// + void AsOutput(); + } +} \ No newline at end of file diff --git a/Raspberry.IO/IOutputAnalogPin.cs b/Raspberry.IO/IOutputAnalogPin.cs new file mode 100644 index 0000000..f5cf2fa --- /dev/null +++ b/Raspberry.IO/IOutputAnalogPin.cs @@ -0,0 +1,24 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides an interface for output, analog pin. + /// + public interface IOutputAnalogPin : IDisposable + { + #region Methods + + /// + /// Writes the specified value to the pin. + /// + /// The value. + void Write(AnalogValue value); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/IOutputBinaryPin.cs b/Raspberry.IO/IOutputBinaryPin.cs new file mode 100644 index 0000000..0f1df5a --- /dev/null +++ b/Raspberry.IO/IOutputBinaryPin.cs @@ -0,0 +1,24 @@ +#region References + +using System; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides an interface for output, binary pins. + /// + public interface IOutputBinaryPin : IDisposable + { + #region Methods + + /// + /// Writes the value of the pin. + /// + /// if set to true, pin is set to high state. + void Write(bool state); + + #endregion + } +} \ No newline at end of file diff --git a/Raspberry.IO/Properties/AssemblyInfo.cs b/Raspberry.IO/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..574b283 --- /dev/null +++ b/Raspberry.IO/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Raspberry.IO")] +[assembly: AssemblyDescription("Raspberry Pi IO Components")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cdb59592-d7c3-450e-ad8a-f02902e2ea51")] diff --git a/Raspberry.IO/Raspberry.IO.csproj b/Raspberry.IO/Raspberry.IO.csproj new file mode 100644 index 0000000..e58e755 --- /dev/null +++ b/Raspberry.IO/Raspberry.IO.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Library + Properties + Raspberry.IO + Raspberry.IO + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\Raspberry.IO.XML + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\Raspberry.IO.XML + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + diff --git a/Raspberry.IO/StringExtensionMethods.cs b/Raspberry.IO/StringExtensionMethods.cs new file mode 100644 index 0000000..9cd8e82 --- /dev/null +++ b/Raspberry.IO/StringExtensionMethods.cs @@ -0,0 +1,38 @@ +#region References + +using System; +using System.Linq; + +#endregion + +namespace Raspberry.IO +{ + /// + /// Provides extension methods for strings. + /// + public static class StringExtensionMethods + { + #region Methods + + /// + /// Converts a bit string in MSBF order (most significant bit first) to a byte array. + /// + /// A bit string (e.g. "00101111"). + /// If true the bit string will be prefixed with '0' if it is not divisible by 8. + /// An array starting with the most significant byte. + public static byte[] BitStringToArray(this string bitString, bool prefixWithZero) + { + var requiredPrefixBits = bitString.Length%8; + var @string = (requiredPrefixBits > 0 && prefixWithZero) + ? new string('0', requiredPrefixBits) + bitString + : bitString; + + return Enumerable + .Range(0, @string.Length/8) + .Select(pos => Convert.ToByte(@string.Substring(pos*8, 8), 2)) + .ToArray(); + } + + #endregion + } +} \ No newline at end of file diff --git a/RaspberrySharp.IO.sln b/RaspberrySharp.IO.sln index 3d98397..def0123 100644 --- a/RaspberrySharp.IO.sln +++ b/RaspberrySharp.IO.sln @@ -1,34 +1,211 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO.GeneralPurpose", "Raspberry.IO.GeneralPurpose\Raspberry.IO.GeneralPurpose.csproj", "{281C71ED-C36D-408E-8BAA-75C381DC17E7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpioTest", "GpioTest\GpioTest.csproj", "{5D9D1834-4C4B-4E03-B635-5F205205B6F8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{93E87796-2109-444A-8852-7174C93E6F45}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + Icon.png = Icon.png + Raspberry.IO.GeneralPurpose.nuspec = Raspberry.IO.GeneralPurpose.nuspec + README.md = README.md + SharedAssemblyInfo.cs = SharedAssemblyInfo.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{15ECD485-B3FD-4B6F-8491-7489DFFC89BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.MCP3008", "Tests\Test.Gpio.MCP3008\Test.Gpio.MCP3008.csproj", "{B28253A7-BB93-40F7-B41C-B4AE369174ED}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.MCP4822", "Tests\Test.Gpio.MCP4822\Test.Gpio.MCP4822.csproj", "{54075457-7C1D-4C8F-BE7D-CFCA34F11228}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.HD44780", "Tests\Test.Gpio.HD44780\Test.Gpio.HD44780.csproj", "{6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.HCSR04", "Tests\Test.Gpio.HCSR04\Test.Gpio.HCSR04.csproj", "{0A18AC47-CA6D-442C-A31C-6864F7DDD97D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.Chaser", "Tests\Test.Gpio.Chaser\Test.Gpio.Chaser.csproj", "{5D9D1834-4C4B-4E03-B635-5F205205B6F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.MCP23017", "Tests\Test.Gpio.MCP23017\Test.Gpio.MCP23017.csproj", "{266EDDBC-F741-48F8-83B7-2CD37BACE62B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.WatchPin", "Tests\Test.Gpio.WatchPin\Test.Gpio.WatchPin.csproj", "{60B59A9C-69F6-4CEA-8385-C8542AE2BB98}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.Pca9685", "Tests\Test.Gpio.Pca9685\Test.Gpio.Pca9685.csproj", "{57A001A7-FCF7-45EC-8E25-A390EF044E94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Spi.TLC59711", "Tests\Test.Spi.TLC59711\Test.Spi.TLC59711.csproj", "{AF711872-F931-46A8-AAC0-AFF6FFEF5AE3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Components.PiFaceDigital", "Tests\Test.Components.PiFaceDigital\Test.Components.PiFaceDigital.csproj", "{475CEB21-BA5F-4338-80E2-C6B29766A04A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.DHT11", "Tests\Test.Gpio.DHT11\Test.Gpio.DHT11.csproj", "{63356B6E-47A0-44E8-8B98-456E8579DCD2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Spi.MCP3208", "Tests\Test.Spi.MCP3208\Test.Spi.MCP3208.csproj", "{D4B87926-598E-4EAB-BA75-CD9FB253D450}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Components.Ssd1306", "Tests\Test.Components.Ssd1306\Test.Components.Ssd1306.csproj", "{CAD5F2DD-AB2D-4145-9850-8101343E2AF5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.MCP23008", "Tests\Test.Gpio.MCP23008\Test.Gpio.MCP23008.csproj", "{E401FE2A-7F73-41E7-9347-B51FEDAE71B9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{C39D0CC3-C0F2-4B58-8779-2CCB5B52534C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Raspberry.IO.SerialPeripheralInterface", "UnitTests\Tests.Raspberry.IO.SerialPeripheralInterface\Tests.Raspberry.IO.SerialPeripheralInterface.csproj", "{17A2A965-36FA-4CDB-B2D6-AC69C9E9857F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Raspberry.IO", "UnitTests\Tests.Raspberry.IO\Tests.Raspberry.IO.csproj", "{0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Raspberry.IO.Components", "UnitTests\Tests.Raspberry.IO.Components\Tests.Raspberry.IO.Components.csproj", "{99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.Raspberry.IO.Interop", "UnitTests\Tests.Raspberry.IO.Interop\Tests.Raspberry.IO.Interop.csproj", "{CAF876A0-0FCB-44F7-96F1-53704CB6015F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO.SerialPeripheralInterface", "Raspberry.IO.SerialPeripheralInterface\Raspberry.IO.SerialPeripheralInterface.csproj", "{326342E5-0411-40E8-9F2D-563D6B192568}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO.InterIntegratedCircuit", "Raspberry.IO.InterIntegratedCircuit\Raspberry.IO.InterIntegratedCircuit.csproj", "{63B8403E-BC56-43F9-A045-F61ECC3871F3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO.Components", "Raspberry.IO.Components\Raspberry.IO.Components.csproj", "{8388CFCA-E3DB-43F7-B049-2CB195211CE8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO", "Raspberry.IO\Raspberry.IO.csproj", "{ACE64F17-87E5-43E7-97A0-BDDE19059C61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Raspberry.IO.Interop", "Raspberry.IO.Interop\Raspberry.IO.Interop.csproj", "{689CB6C4-3D23-45DA-8E00-87C28AEA32D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Gpio.Ds1307", "Tests\Test.Gpio.Ds1307\Test.Gpio.Ds1307.csproj", "{E9412139-F9EA-4E39-AB7C-CE775ED06C82}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Debug|x86.ActiveCfg = Debug|Any CPU {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Release|Any CPU.ActiveCfg = Release|Any CPU {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Release|Any CPU.Build.0 = Release|Any CPU - {281C71ED-C36D-408E-8BAA-75C381DC17E7}.Release|x86.ActiveCfg = Release|Any CPU + {B28253A7-BB93-40F7-B41C-B4AE369174ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B28253A7-BB93-40F7-B41C-B4AE369174ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B28253A7-BB93-40F7-B41C-B4AE369174ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B28253A7-BB93-40F7-B41C-B4AE369174ED}.Release|Any CPU.Build.0 = Release|Any CPU + {54075457-7C1D-4C8F-BE7D-CFCA34F11228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54075457-7C1D-4C8F-BE7D-CFCA34F11228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {54075457-7C1D-4C8F-BE7D-CFCA34F11228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54075457-7C1D-4C8F-BE7D-CFCA34F11228}.Release|Any CPU.Build.0 = Release|Any CPU + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC}.Release|Any CPU.Build.0 = Release|Any CPU + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D}.Release|Any CPU.Build.0 = Release|Any CPU {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Debug|x86.ActiveCfg = Debug|x86 - {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Debug|x86.Build.0 = Debug|x86 {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Release|Any CPU.Build.0 = Release|Any CPU - {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Release|x86.ActiveCfg = Release|x86 - {5D9D1834-4C4B-4E03-B635-5F205205B6F8}.Release|x86.Build.0 = Release|x86 + {266EDDBC-F741-48F8-83B7-2CD37BACE62B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {266EDDBC-F741-48F8-83B7-2CD37BACE62B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {266EDDBC-F741-48F8-83B7-2CD37BACE62B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {266EDDBC-F741-48F8-83B7-2CD37BACE62B}.Release|Any CPU.Build.0 = Release|Any CPU + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98}.Release|Any CPU.Build.0 = Release|Any CPU + {57A001A7-FCF7-45EC-8E25-A390EF044E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57A001A7-FCF7-45EC-8E25-A390EF044E94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57A001A7-FCF7-45EC-8E25-A390EF044E94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57A001A7-FCF7-45EC-8E25-A390EF044E94}.Release|Any CPU.Build.0 = Release|Any CPU + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3}.Release|Any CPU.Build.0 = Release|Any CPU + {475CEB21-BA5F-4338-80E2-C6B29766A04A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {475CEB21-BA5F-4338-80E2-C6B29766A04A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {475CEB21-BA5F-4338-80E2-C6B29766A04A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {475CEB21-BA5F-4338-80E2-C6B29766A04A}.Release|Any CPU.Build.0 = Release|Any CPU + {63356B6E-47A0-44E8-8B98-456E8579DCD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63356B6E-47A0-44E8-8B98-456E8579DCD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63356B6E-47A0-44E8-8B98-456E8579DCD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63356B6E-47A0-44E8-8B98-456E8579DCD2}.Release|Any CPU.Build.0 = Release|Any CPU + {D4B87926-598E-4EAB-BA75-CD9FB253D450}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4B87926-598E-4EAB-BA75-CD9FB253D450}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4B87926-598E-4EAB-BA75-CD9FB253D450}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4B87926-598E-4EAB-BA75-CD9FB253D450}.Release|Any CPU.Build.0 = Release|Any CPU + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5}.Release|Any CPU.Build.0 = Release|Any CPU + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9}.Release|Any CPU.Build.0 = Release|Any CPU + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F}.Release|Any CPU.Build.0 = Release|Any CPU + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A}.Release|Any CPU.Build.0 = Release|Any CPU + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B}.Release|Any CPU.Build.0 = Release|Any CPU + {CAF876A0-0FCB-44F7-96F1-53704CB6015F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CAF876A0-0FCB-44F7-96F1-53704CB6015F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CAF876A0-0FCB-44F7-96F1-53704CB6015F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAF876A0-0FCB-44F7-96F1-53704CB6015F}.Release|Any CPU.Build.0 = Release|Any CPU + {326342E5-0411-40E8-9F2D-563D6B192568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {326342E5-0411-40E8-9F2D-563D6B192568}.Debug|Any CPU.Build.0 = Debug|Any CPU + {326342E5-0411-40E8-9F2D-563D6B192568}.Release|Any CPU.ActiveCfg = Release|Any CPU + {326342E5-0411-40E8-9F2D-563D6B192568}.Release|Any CPU.Build.0 = Release|Any CPU + {63B8403E-BC56-43F9-A045-F61ECC3871F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63B8403E-BC56-43F9-A045-F61ECC3871F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63B8403E-BC56-43F9-A045-F61ECC3871F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63B8403E-BC56-43F9-A045-F61ECC3871F3}.Release|Any CPU.Build.0 = Release|Any CPU + {8388CFCA-E3DB-43F7-B049-2CB195211CE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8388CFCA-E3DB-43F7-B049-2CB195211CE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8388CFCA-E3DB-43F7-B049-2CB195211CE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8388CFCA-E3DB-43F7-B049-2CB195211CE8}.Release|Any CPU.Build.0 = Release|Any CPU + {ACE64F17-87E5-43E7-97A0-BDDE19059C61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACE64F17-87E5-43E7-97A0-BDDE19059C61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACE64F17-87E5-43E7-97A0-BDDE19059C61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACE64F17-87E5-43E7-97A0-BDDE19059C61}.Release|Any CPU.Build.0 = Release|Any CPU + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0}.Release|Any CPU.Build.0 = Release|Any CPU + {E9412139-F9EA-4E39-AB7C-CE775ED06C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E9412139-F9EA-4E39-AB7C-CE775ED06C82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E9412139-F9EA-4E39-AB7C-CE775ED06C82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E9412139-F9EA-4E39-AB7C-CE775ED06C82}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} = {93E87796-2109-444A-8852-7174C93E6F45} + {B28253A7-BB93-40F7-B41C-B4AE369174ED} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {54075457-7C1D-4C8F-BE7D-CFCA34F11228} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {5D9D1834-4C4B-4E03-B635-5F205205B6F8} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {266EDDBC-F741-48F8-83B7-2CD37BACE62B} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {57A001A7-FCF7-45EC-8E25-A390EF044E94} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {475CEB21-BA5F-4338-80E2-C6B29766A04A} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {63356B6E-47A0-44E8-8B98-456E8579DCD2} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {D4B87926-598E-4EAB-BA75-CD9FB253D450} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + {C39D0CC3-C0F2-4B58-8779-2CCB5B52534C} = {93E87796-2109-444A-8852-7174C93E6F45} + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F} = {C39D0CC3-C0F2-4B58-8779-2CCB5B52534C} + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A} = {C39D0CC3-C0F2-4B58-8779-2CCB5B52534C} + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B} = {C39D0CC3-C0F2-4B58-8779-2CCB5B52534C} + {CAF876A0-0FCB-44F7-96F1-53704CB6015F} = {C39D0CC3-C0F2-4B58-8779-2CCB5B52534C} + {E9412139-F9EA-4E39-AB7C-CE775ED06C82} = {15ECD485-B3FD-4B6F-8491-7489DFFC89BC} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.FileWidth = 120 + $1.TabsToSpaces = False + $1.inheritsSet = VisualStudio + $1.inheritsScope = text/plain + StartupItem = Raspberry.IO.GeneralPurpose\Raspberry.IO.GeneralPurpose.csproj + EndGlobalSection EndGlobal diff --git a/SharedAssemblyInfo.cs b/SharedAssemblyInfo.cs new file mode 100644 index 0000000..6221bbb --- /dev/null +++ b/SharedAssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyCompany("Raspberry#")] +[assembly: AssemblyProduct("Raspberry.IO")] +[assembly: AssemblyCopyright("www.raspberry-sharp.org, 2012-2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("2.1.0.0")] +[assembly: AssemblyFileVersion("2.1.0.0")] diff --git a/Tests/Test.Components.BiColor24Bargraph/Program.cs b/Tests/Test.Components.BiColor24Bargraph/Program.cs new file mode 100644 index 0000000..5330631 --- /dev/null +++ b/Tests/Test.Components.BiColor24Bargraph/Program.cs @@ -0,0 +1,35 @@ +using System; +using Raspberry.IO.Components.Leds.BiColor24Bargraph; +using Raspberry.IO.InterIntegratedCircuit; +using System.Threading; +using Raspberry.IO.GeneralPurpose; + +namespace Test.Components.BiColor24Bargraph +{ + class MainClass + { + public static void Main (string[] args) + { + using (var i2cDriver = new I2cDriver(ProcessorPin.Pin2, ProcessorPin.Pin3)) + { + I2cDeviceConnection i2c = i2cDriver.Connect(0x70); + + var bargraph = new BiColor24Bargraph(i2c); + + bargraph.Clear(); + + while (true) + { + foreach (BiColor24Bargraph.LEDState state in Enum.GetValues(typeof(BiColor24Bargraph.LEDState))) + { + for (int i=0; i<24;i++) + { + bargraph.SetLed((uint)i, state); + Thread.Sleep(50); + } + } + } + } + } + } +} diff --git a/Tests/Test.Components.PiFaceDigital/App.config b/Tests/Test.Components.PiFaceDigital/App.config new file mode 100644 index 0000000..ecf1837 --- /dev/null +++ b/Tests/Test.Components.PiFaceDigital/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Components.PiFaceDigital/Program.cs b/Tests/Test.Components.PiFaceDigital/Program.cs new file mode 100644 index 0000000..4e082cd --- /dev/null +++ b/Tests/Test.Components.PiFaceDigital/Program.cs @@ -0,0 +1,78 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Raspberry.IO.Components.Devices.PiFaceDigital; + + +namespace Test.Components.PiFaceDigital +{ + class Program + { + private static PiFaceDigitalDevice piFace; + private static bool polling = true; + private static Task t; + + /// + /// Demo App + /// + /// Loops through each output pin 10 times pulsing them on for 1/2 second + /// Inputs are polled and any change reported on the console + /// + static void Main() + { + + piFace = new PiFaceDigitalDevice(); + + + // setup events + foreach (var ip in piFace.InputPins) + { + ip.OnStateChanged += ip_OnStateChanged; + } + + t = Task.Factory.StartNew(() => PollInputs()); + + for (int i = 0; i < 10; i++) + { + for (int pin = 0; pin < 8; pin++) + { + piFace.OutputPins[pin].State = true; + piFace.UpdatePiFaceOutputPins(); + Thread.Sleep(500); + piFace.OutputPins[pin].State = false; + piFace.UpdatePiFaceOutputPins(); + Thread.Sleep(500); + } + } + + //stop polling loop + polling = false; + t.Wait(); + } + + + + + /// + /// Loop polling the inputs at 200ms intervals + /// + private static void PollInputs() + { + while (polling) + { + piFace.PollInputPins(); + Thread.Sleep(200); + } + } + + /// + /// Log any input pin changes + /// + /// + /// + private static void ip_OnStateChanged(object sender, InputPinChangedArgs e) + { + Console.WriteLine("Pin {0} became {1}", e.pin.Id, e.pin.State); + } + } +} diff --git a/Tests/Test.Components.PiFaceDigital/Properties/AssemblyInfo.cs b/Tests/Test.Components.PiFaceDigital/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..848b273 --- /dev/null +++ b/Tests/Test.Components.PiFaceDigital/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Components.PiFaceDigital")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Test.Components.PiFaceDigital")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9e706a1c-5a89-45a4-8dd4-826b2ee03eca")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Test.Components.PiFaceDigital/Test.Components.PiFaceDigital.csproj b/Tests/Test.Components.PiFaceDigital/Test.Components.PiFaceDigital.csproj new file mode 100644 index 0000000..b65d4fe --- /dev/null +++ b/Tests/Test.Components.PiFaceDigital/Test.Components.PiFaceDigital.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {475CEB21-BA5F-4338-80E2-C6B29766A04A} + Exe + Properties + Test.Components.PiFaceDigital + Test.Components.PiFaceDigital + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + + + \ No newline at end of file diff --git a/Tests/Test.Components.Ssd1306/Program.cs b/Tests/Test.Components.Ssd1306/Program.cs new file mode 100644 index 0000000..fbfdd60 --- /dev/null +++ b/Tests/Test.Components.Ssd1306/Program.cs @@ -0,0 +1,92 @@ +using System; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.IO.Components.Displays.Ssd1306; +using System.Threading; +using Raspberry.IO.Components.Displays.Ssd1306.Fonts; + +namespace Test.Components.Ssd1306 +{ + class MainClass + { + public static void Main(string[] args) + { + const byte ssdI2cAddress = 0x3C; + + var sdaPin = ConnectorPin.P1Pin03; + var sclPin = ConnectorPin.P1Pin05; + var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor()); + var ssd1306 = new Ssd1306Connection(driver.Connect(ssdI2cAddress)); + + // Clear the screen and turn the display on + ssd1306.ClearScreen(); + ssd1306.On(); + + // Flash screen + ssd1306.InvertDisplay(); + Thread.Sleep(1000); + ssd1306.NormalDisplay(); + Thread.Sleep(1000); + ssd1306.InvertDisplay(); + Thread.Sleep(1000); + ssd1306.NormalDisplay(); + + // Write "Hello world!" with different fonts + var fontFixed = new Fixed1L(); + var font2L = new Proportional2L(); + var font3L = new Proportional3L(); + ssd1306.GotoXY(0, 0); + ssd1306.DrawText(fontFixed, "Hello World!"); + ssd1306.GotoXY(0, 2); + ssd1306.DrawText(font2L, "Raspberry Pi"); + ssd1306.GotoXY(0, 5); + ssd1306.DrawText(font3L, "Ssd 1306"); + + Thread.Sleep(5000); + + // Draw test image logo + var logo = new byte[]{ + 0x40, + 0xC0, 0xF0, 0xF8, 0x1C, 0x0C, 0x06, 0xF6, 0xFE, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xC0, 0xFE, + 0xFF, 0xFF, 0x7F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, + 0x00, 0x00, 0xC0, 0xF0, 0xF8, 0xFC, 0x7E, 0x1E, 0x0E, 0x07, 0x03, 0x03, 0x03, 0x23, 0x7E, 0x3E, + 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9C, 0x9E, + 0xBE, 0x1C, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x03, 0x00, 0xE0, 0xFF, 0xFF, 0xFF, 0x3F, 0x07, 0x06, 0x06, 0x86, 0xFE, 0xFF, 0xFF, + 0xFF, 0x0F, 0x00, 0xE0, 0xF8, 0xFC, 0xFE, 0x7F, 0x07, 0x03, 0x01, 0x99, 0xFF, 0xFF, 0x10, 0x10, + 0xD8, 0xFE, 0xFF, 0xFF, 0x7F, 0x0F, 0x03, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0x0E, 0x06, 0x03, 0xFF, + 0xFF, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0xE0, 0xF8, 0xFC, 0xFF, 0xDF, 0xC7, 0x41, 0x71, 0x3F, 0x1F, + 0x00, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF8, 0xFC, 0xFC, 0x3C, + 0x00, 0xC0, 0xF0, 0xFC, 0xFE, 0xFF, 0xCF, 0xC3, 0x61, 0x71, 0x3F, 0x0F, 0x00, 0xE0, 0xFF, 0xFF, + 0xFF, 0x3F, 0x07, 0x03, 0x83, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, + 0x1F, 0x00, 0x00, 0xC0, 0xF8, 0xFC, 0xFE, 0xFF, 0xCF, 0xC3, 0x61, 0x39, 0x1F, 0x0F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3C, 0x3F, 0x3F, 0x3F, 0x07, 0x00, 0x00, 0x00, 0x30, 0x3F, 0x3F, 0x3F, 0x1F, + 0x00, 0x00, 0x00, 0x0F, 0x1F, 0x3F, 0x3F, 0x38, 0x38, 0x38, 0x1C, 0x0F, 0x07, 0x01, 0x00, 0x38, + 0x3F, 0x3F, 0x3F, 0x0F, 0x00, 0x00, 0x30, 0x3F, 0x3F, 0x3F, 0x1F, 0x00, 0x00, 0x00, 0x0E, 0x3F, + 0x3F, 0x3F, 0x39, 0x38, 0x18, 0x0E, 0x1F, 0x3F, 0x3F, 0x3F, 0x38, 0x38, 0x38, 0x18, 0x04, 0x00, + 0x00, 0x00, 0x01, 0xE1, 0xF3, 0x73, 0x77, 0x77, 0x67, 0x03, 0xF3, 0xFF, 0xFF, 0x7F, 0x07, 0x00, + 0x00, 0x0F, 0x1F, 0x3F, 0x3F, 0x3C, 0x38, 0x30, 0x38, 0x18, 0x08, 0x04, 0x3E, 0x3F, 0x3F, 0x3F, + 0x07, 0x00, 0x00, 0x00, 0x1F, 0x3F, 0x3F, 0x3F, 0x38, 0x38, 0x1C, 0x0F, 0x3F, 0x3F, 0x3F, 0x3F, + 0x38, 0x18, 0x0C, 0x0F, 0x1F, 0x3F, 0x3F, 0x3C, 0x38, 0x38, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x07, 0x0C, 0x0C, 0x0C, 0x0C, 0x0F, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + ssd1306.ClearScreen(); + ssd1306.GotoXY(0, 3); + ssd1306.DrawImage(logo); + + ((IDisposable)driver).Dispose(); + } + } +} diff --git a/Tests/Test.Components.Ssd1306/Properties/AssemblyInfo.cs b/Tests/Test.Components.Ssd1306/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6c6736d --- /dev/null +++ b/Tests/Test.Components.Ssd1306/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. +[assembly: AssemblyTitle("Test.Components.Ssd1306")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("gene")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". +// The form "{Major}.{Minor}.*" will automatically update the build and revision, +// and "{Major}.{Minor}.{Build}.*" will update just the revision. +[assembly: AssemblyVersion("1.0.*")] +// The following attributes are used to specify the signing key for the assembly, +// if desired. See the Mono documentation for more information about signing. +//[assembly: AssemblyDelaySign(false)] +//[assembly: AssemblyKeyFile("")] + diff --git a/Tests/Test.Components.Ssd1306/Test.Components.Ssd1306.csproj b/Tests/Test.Components.Ssd1306/Test.Components.Ssd1306.csproj new file mode 100644 index 0000000..4f9d9e7 --- /dev/null +++ b/Tests/Test.Components.Ssd1306/Test.Components.Ssd1306.csproj @@ -0,0 +1,51 @@ + + + + Debug + AnyCPU + {CAD5F2DD-AB2D-4145-9850-8101343E2AF5} + Exe + Test.Components.Ssd1306 + Test.Components.Ssd1306 + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + true + + + full + true + bin\Release + prompt + 4 + true + + + + + + + + + + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + \ No newline at end of file diff --git a/GpioTest/Helpers.cs b/Tests/Test.Gpio.Chaser/CommandLineExtensionMethods.cs similarity index 56% rename from GpioTest/Helpers.cs rename to Tests/Test.Gpio.Chaser/CommandLineExtensionMethods.cs index 5ccd1fa..11b8006 100644 --- a/GpioTest/Helpers.cs +++ b/Tests/Test.Gpio.Chaser/CommandLineExtensionMethods.cs @@ -1,12 +1,19 @@ +#region References + using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Raspberry.IO.GeneralPurpose; -namespace GpioTest +#endregion + +namespace Test.Gpio.Chaser { - internal static class Helpers + internal static class CommandLineExtensionMethods { + #region Methods + public static bool GetLoop(this IEnumerable args) { return args.SkipWhile(a => a != "-loop").Any(); @@ -27,22 +34,36 @@ public static int GetSpeed(this IEnumerable args) return args.SkipWhile(a => a != "-speed").Skip(1).Select(int.Parse).DefaultIfEmpty(250).First(); } - public static IConnectionDriver GetDriver(this IEnumerable args) + public static IGpioConnectionDriver GetDriver(this IEnumerable args) { var driverName = args.SkipWhile(a => a != "-driver").Skip(1).DefaultIfEmpty("").First(); + return GetDriver(driverName); + } + + #endregion + + #region Private Helpers + + private static IGpioConnectionDriver GetDriver(string driverName) + { switch (driverName) { + case "default": + return new GpioConnectionDriver(); case "memory": - return new MemoryConnectionDriver(); + return new MemoryGpioConnectionDriver(); case "file": - return new FileConnectionDriver(); + return new FileGpioConnectionDriver(); case "": return null; default: - throw new InvalidOperationException("Unsupported driver"); + throw new ArgumentOutOfRangeException("driverName", driverName, + string.Format(CultureInfo.InvariantCulture, "{0} is not a valid driver name", driverName)); } } + + #endregion } } \ No newline at end of file diff --git a/Tests/Test.Gpio.Chaser/Program.cs b/Tests/Test.Gpio.Chaser/Program.cs new file mode 100644 index 0000000..61a0dbb --- /dev/null +++ b/Tests/Test.Gpio.Chaser/Program.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.GeneralPurpose.Behaviors; + +namespace Test.Gpio.Chaser +{ + /// + /// This is a sample program. Must be modified to match your GPIO project. + /// + class Program + { + static void Main(string[] args) + { + const ConnectorPin led1Pin = ConnectorPin.P1Pin26; + const ConnectorPin led2Pin = ConnectorPin.P1Pin24; + const ConnectorPin led3Pin = ConnectorPin.P1Pin22; + const ConnectorPin led4Pin = ConnectorPin.P1Pin15; + const ConnectorPin led5Pin = ConnectorPin.P1Pin13; + const ConnectorPin led6Pin = ConnectorPin.P1Pin11; + const ConnectorPin buttonPin = ConnectorPin.P1Pin03; + + Console.WriteLine("Chaser Sample: Sample a LED chaser with a switch to change behavior"); + Console.WriteLine(); + Console.WriteLine("\tLed 1: {0}", led1Pin); + Console.WriteLine("\tLed 2: {0}", led2Pin); + Console.WriteLine("\tLed 3: {0}", led3Pin); + Console.WriteLine("\tLed 4: {0}", led4Pin); + Console.WriteLine("\tLed 5: {0}", led5Pin); + Console.WriteLine("\tLed 6: {0}", led6Pin); + Console.WriteLine("\tSwitch: {0}", buttonPin); + Console.WriteLine(); + + var driver = args.GetDriver(); + + // Declare outputs (leds) + var leds = new PinConfiguration[] + { + led1Pin.Output().Name("Led1").Enable(), + led2Pin.Output().Name("Led2"), + led3Pin.Output().Name("Led3").Enable(), + led4Pin.Output().Name("Led4"), + led5Pin.Output().Name("Led5").Enable(), + led6Pin.Output().Name("Led6") + }; + + // Assign a behavior to the leds + var behavior = new ChaserBehavior(leds) + { + Loop = args.GetLoop(), + RoundTrip = args.GetRoundTrip(), + Width = args.GetWidth(), + Interval = TimeSpan.FromMilliseconds(args.GetSpeed()) + }; + + // Alternate behaviors... + /* + var random = new Random(); + var behavior = new PatternBehavior(leds, Enumerable.Range(0, 5).Select(i => random.Next(511))) + { + Loop = Helpers.GetLoop(args), + RoundTrip = Helpers.GetRoundTrip(args), + Interval = Helpers.GetSpeed(args) + };*/ + + /* + var behavior = new BlinkBehavior(leds) + { + Count = args.GetWidth(), + Interval = args.GetSpeed() + };*/ + + // Declare input (switchButton) interacting with the leds behavior + var switchButton = buttonPin.Input() + .Name("Switch") + .Revert() + .Switch() + .Enable() + .OnStatusChanged(b => + { + behavior.RoundTrip = !behavior.RoundTrip; + Console.WriteLine("Button switched {0}", b ? "on" : "off"); + }); + + // Create connection + var settings = new GpioConnectionSettings {Driver = driver}; + + using (var connection = new GpioConnection(settings, leds)) + { + Console.WriteLine("Using {0}, frequency {1:0.##}hz", settings.Driver.GetType().Name, 1000.0/args.GetSpeed()); + + Thread.Sleep(1000); + + connection.Add(switchButton); + connection.Start(behavior); // Starting the behavior automatically registers the pins to the connection, if needed. + + Console.ReadKey(true); + + connection.Stop(behavior); + } + } + } +} diff --git a/Tests/Test.Gpio.Chaser/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.Chaser/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..03611d5 --- /dev/null +++ b/Tests/Test.Gpio.Chaser/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.Chaser")] +[assembly: AssemblyDescription("Raspberry Pi Chaser GPIO Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("80b17b2e-8eac-429c-a817-6c770a44c466")] diff --git a/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.fzz b/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.fzz new file mode 100644 index 0000000..24acee9 Binary files /dev/null and b/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.fzz differ diff --git a/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.png b/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.png new file mode 100644 index 0000000..daa42b5 Binary files /dev/null and b/Tests/Test.Gpio.Chaser/Properties/Test.Gpio.Chaser.png differ diff --git a/GpioTest/GpioTest.csproj b/Tests/Test.Gpio.Chaser/Test.Gpio.Chaser.csproj similarity index 87% rename from GpioTest/GpioTest.csproj rename to Tests/Test.Gpio.Chaser/Test.Gpio.Chaser.csproj index 801a558..c7c6b4e 100644 --- a/GpioTest/GpioTest.csproj +++ b/Tests/Test.Gpio.Chaser/Test.Gpio.Chaser.csproj @@ -3,13 +3,11 @@ Debug x86 - 8.0.30703 - 2.0 {5D9D1834-4C4B-4E03-B635-5F205205B6F8} Exe Properties - GpioTest - GpioTest + Test.Gpio.Chaser + Test.Gpio.Chaser v4.0 @@ -48,6 +46,8 @@ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules true + 4 + false bin\Release\ @@ -63,30 +63,31 @@ ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets true ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + 4 - - - - - - - - - - + + Properties\SharedAssemblyInfo.cs + + - + - + {281C71ED-C36D-408E-8BAA-75C381DC17E7} Raspberry.IO.GeneralPurpose + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.DHT11/packages.config b/Tests/Test.Gpio.DHT11/packages.config new file mode 100644 index 0000000..67e0cf0 --- /dev/null +++ b/Tests/Test.Gpio.DHT11/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.Ds1307/App.config b/Tests/Test.Gpio.Ds1307/App.config new file mode 100644 index 0000000..74ade9d --- /dev/null +++ b/Tests/Test.Gpio.Ds1307/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Tests/Test.Gpio.Ds1307/Program.cs b/Tests/Test.Gpio.Ds1307/Program.cs new file mode 100644 index 0000000..aad5707 --- /dev/null +++ b/Tests/Test.Gpio.Ds1307/Program.cs @@ -0,0 +1,116 @@ +using System; +using System.Threading; +using Raspberry.IO.InterIntegratedCircuit; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.Components.Clocks.Ds1307; + +namespace Test.Gpio.Ds1307 +{ + class Program + { + private static I2cDriver driver; + private static I2cDeviceConnection i2cConnection; + private static Ds1307Connection clock; + + static void Main(string[] args) + { + Console.WriteLine("Ds1307 Test"); + Console.WriteLine("==========="); + + Console.WriteLine("Opening Connection..."); + + try + { + driver = new I2cDriver(ProcessorPin.Pin02, ProcessorPin.Pin03); + i2cConnection = driver.Connect(0x68); + clock = new Ds1307Connection(i2cConnection); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Unable to open connection."); + Console.WriteLine(ex); + Console.WriteLine("Press any key to close."); + Console.Read(); + Environment.Exit(1); + } + + Console.WriteLine("Connection open!"); + + AskForKey(); + } + + public static void AskForKey() + { + Console.ForegroundColor = ConsoleColor.Gray; + Console.WriteLine("A = EnableRtc"); + Console.WriteLine("B = DisableRtc"); + Console.WriteLine("C = GetDate (reads and shows date from the RTC every second)"); + Console.WriteLine("D = SetDate"); + Console.WriteLine("E = ResetToFactoryDefaults"); + Console.WriteLine("F = IsRtcEnabled"); + Console.WriteLine("X = Exit"); + Console.ForegroundColor = ConsoleColor.Cyan; + + while (!Console.KeyAvailable) + { + switch (Console.ReadKey().Key) + { + case ConsoleKey.A: + clock.EnableRtc(); + Console.WriteLine("Clock Enabled"); + break; + case ConsoleKey.B: + clock.DisableRtc(); + Console.WriteLine("Clock Disabled"); + break; + case ConsoleKey.C: + Console.WriteLine("Press any key to stop"); + while (!Console.KeyAvailable) + { + Console.WriteLine(clock.GetDate()); + Thread.Sleep(1000); + } + Console.Read(); + break; + case ConsoleKey.D: + Console.WriteLine("Enter Year "); + int year = int.Parse(Console.ReadLine()); + Console.WriteLine("Enter Month "); + int month = int.Parse(Console.ReadLine()); + Console.WriteLine("Enter Day "); + int day = int.Parse(Console.ReadLine()); + Console.WriteLine("Enter Hour "); + int hour = int.Parse(Console.ReadLine()); + Console.WriteLine("Enter Minutes "); + int minutes = int.Parse(Console.ReadLine()); + Console.WriteLine("Enter Seconds "); + int seconds = int.Parse(Console.ReadLine()); + + DateTime newDateTime = new DateTime(year, month, day, hour, minutes, seconds); + + clock.SetDate(newDateTime); + + Console.WriteLine("Clock set to {0}", newDateTime); + + break; + case ConsoleKey.E: + clock.ResetToFactoryDefaults(); + Console.WriteLine("Clock reset to factory defaults"); + break; + case ConsoleKey.F: + Console.WriteLine("Clock is ticking: {0}", clock.IsRtcEnabled()); + break; + case ConsoleKey.X: + driver.Dispose(); + Environment.Exit(0); + break; + default: + break; + } + + AskForKey(); + } + } + } +} diff --git a/Tests/Test.Gpio.Ds1307/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.Ds1307/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..855642b --- /dev/null +++ b/Tests/Test.Gpio.Ds1307/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.Ds1307")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Test.Gpio.Ds1307")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e9412139-f9ea-4e39-ab7c-ce775ed06c82")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Test.Gpio.Ds1307/Test.Gpio.Ds1307.csproj b/Tests/Test.Gpio.Ds1307/Test.Gpio.Ds1307.csproj new file mode 100644 index 0000000..81f8e63 --- /dev/null +++ b/Tests/Test.Gpio.Ds1307/Test.Gpio.Ds1307.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {E9412139-F9EA-4E39-AB7C-CE775ED06C82} + Exe + Properties + Test.Gpio.Ds1307 + Test.Gpio.Ds1307 + v4.0 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {8388cfca-e3db-43f7-b049-2cb195211ce8} + Raspberry.IO.Components + + + {281c71ed-c36d-408e-8baa-75c381dc17e7} + Raspberry.IO.GeneralPurpose + + + {63b8403e-bc56-43f9-a045-f61ecc3871f3} + Raspberry.IO.InterIntegratedCircuit + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.HCSR04/Program.cs b/Tests/Test.Gpio.HCSR04/Program.cs new file mode 100644 index 0000000..6104ba7 --- /dev/null +++ b/Tests/Test.Gpio.HCSR04/Program.cs @@ -0,0 +1,71 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Raspberry.IO.Components.Sensors.Distance.HcSr04; +using Raspberry.IO.GeneralPurpose; +using Raspberry.Timers; + +#endregion + +namespace Test.Gpio.HCSR04 +{ + internal class Program + { + private static void Main(string[] args) + { + Console.CursorVisible = false; + + const ConnectorPin triggerPin = ConnectorPin.P1Pin21; + const ConnectorPin echoPin = ConnectorPin.P1Pin23; + + Console.WriteLine("HC-SR04 Sample: measure distance"); + Console.WriteLine(); + Console.WriteLine("\tTrigger: {0}", triggerPin); + Console.WriteLine("\tEcho: {0}", echoPin); + Console.WriteLine(); + + var interval = GetInterval(args); + var driver = GpioConnectionSettings.DefaultDriver; + + using (var connection = new HcSr04Connection( + driver.Out(triggerPin.ToProcessor()), + driver.In(echoPin.ToProcessor()))) + { + while (!Console.KeyAvailable) + { + try + { + var distance = connection.GetDistance().Centimeters; + Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:0.0}cm", distance).PadRight(16)); + Console.CursorTop--; + } + catch (TimeoutException e) + { + Console.WriteLine("(Timeout): " + e.Message); + } + + Timer.Sleep(interval); + } + } + + Console.CursorVisible = true; + } + + #region Private Helpers + + private static TimeSpan GetInterval(IEnumerable args) + { + return TimeSpan.FromMilliseconds(args + .SkipWhile(a => a != "-interval") + .Skip(1) + .Select(int.Parse) + .DefaultIfEmpty(100) + .First()); + } + + #endregion + } +} diff --git a/Tests/Test.Gpio.HCSR04/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.HCSR04/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e3fd67d --- /dev/null +++ b/Tests/Test.Gpio.HCSR04/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.HCSR04")] +[assembly: AssemblyDescription("Raspberry Pi HCSR04 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9da308b4-e4c1-4179-bb66-6b5fae3439e0")] diff --git a/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.fzz b/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.fzz new file mode 100644 index 0000000..7314611 Binary files /dev/null and b/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.fzz differ diff --git a/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.png b/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.png new file mode 100644 index 0000000..f2c0325 Binary files /dev/null and b/Tests/Test.Gpio.HCSR04/Properties/Test.Gpio.HCSR04.png differ diff --git a/Tests/Test.Gpio.HCSR04/Test.Gpio.HCSR04.csproj b/Tests/Test.Gpio.HCSR04/Test.Gpio.HCSR04.csproj new file mode 100644 index 0000000..7709dd7 --- /dev/null +++ b/Tests/Test.Gpio.HCSR04/Test.Gpio.HCSR04.csproj @@ -0,0 +1,106 @@ + + + + Debug + x86 + {0A18AC47-CA6D-442C-A31C-6864F7DDD97D} + Exe + Properties + Test.Gpio.HCSR04 + Test.Gpio.HCSR04 + v4.0 + + + 512 + ..\ + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + + + + $(SolutionDir)packages\Raspberry.System.2.1\lib\net40\Raspberry.System.dll + True + + + + + $(SolutionDir)packages\UnitsNet.3.46.1\lib\net35\UnitsNet.dll + True + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + {D2E41147-5BF6-4109-A497-C76284F3C020} + Raspberry.IO + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.HCSR04/packages.config b/Tests/Test.Gpio.HCSR04/packages.config new file mode 100644 index 0000000..67e0cf0 --- /dev/null +++ b/Tests/Test.Gpio.HCSR04/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.HD44780/ConfigurationLoader.cs b/Tests/Test.Gpio.HD44780/ConfigurationLoader.cs new file mode 100644 index 0000000..705dd28 --- /dev/null +++ b/Tests/Test.Gpio.HD44780/ConfigurationLoader.cs @@ -0,0 +1,221 @@ +#region References + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Raspberry.IO; +using Raspberry.IO.Components.Displays.Hd44780; +using Raspberry.IO.Components.Expanders.Mcp23008; +using Raspberry.IO.Components.Expanders.Mcp23017; +using Raspberry.IO.Components.Expanders.Pcf8574; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Test.Gpio.HD44780 +{ + internal static class ConfigurationLoader + { + #region Methods + + public static Hd44780Configuration FromArguments(string[] args) + { + if (args.Contains("mcp23008", StringComparer.InvariantCultureIgnoreCase)) + return LoadMcp23008Configuration(args); + + if (args.Contains("mcp23017", StringComparer.InvariantCultureIgnoreCase)) + return LoadMcp23017Configuration(args); + + if (args.Contains("pcf8574", StringComparer.InvariantCultureIgnoreCase)) + return LoadPcf8574Configuration(args); + + return LoadGpioConfiguration(); + } + + #endregion + + #region Private Helpers + + private static Hd44780Configuration LoadMcp23008Configuration(IEnumerable args) + { + var addressText = args.SkipWhile(s => !String.Equals(s, "mcp23008", StringComparison.InvariantCultureIgnoreCase)).Skip(1).DefaultIfEmpty("0x20").First(); + var address = addressText.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase) + ? Int32.Parse(addressText.Substring(2), NumberStyles.HexNumber) + : Int32.Parse(addressText); + + const Mcp23008Pin registerSelectPin = Mcp23008Pin.Pin1; + const Mcp23008Pin clockPin = Mcp23008Pin.Pin2; + const Mcp23008Pin backlightPin = Mcp23008Pin.Pin7; + + var dataPins = new[] + { + Mcp23008Pin.Pin3, + Mcp23008Pin.Pin4, + Mcp23008Pin.Pin5, + Mcp23008Pin.Pin6 + }; + + Console.WriteLine(); + Console.WriteLine("Using I2C connection over MCP23008 Expander"); + Console.WriteLine("\tRegister Select: {0}", registerSelectPin); + Console.WriteLine("\tClock: {0}", clockPin); + Console.WriteLine("\tData 1: {0}", dataPins[0]); + Console.WriteLine("\tData 2: {0}", dataPins[1]); + Console.WriteLine("\tData 3: {0}", dataPins[2]); + Console.WriteLine("\tData 4: {0}", dataPins[3]); + Console.WriteLine("\tBacklight: {0}", backlightPin); + Console.WriteLine("\tRead/write: GND"); + Console.WriteLine(); + + const ConnectorPin sdaPin = ConnectorPin.P1Pin03; + const ConnectorPin sclPin = ConnectorPin.P1Pin05; + + var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor()) { ClockDivider = 512 }; + var connection = new Mcp23008I2cConnection(driver.Connect(address)); + + var retVal = new Hd44780Configuration(driver) + { + Pins = new Hd44780Pins( + connection.Out(registerSelectPin), + connection.Out(clockPin), + dataPins.Select(pin => (IOutputBinaryPin)connection.Out(pin))) + }; + + retVal.Pins.Backlight = connection.Out(backlightPin); + + return retVal; + } + + private static Hd44780Configuration LoadMcp23017Configuration(IEnumerable args) + { + var addressText = args.SkipWhile(s => !String.Equals(s, "mcp23017", StringComparison.InvariantCultureIgnoreCase)).Skip(1).DefaultIfEmpty("0x20").First(); + var address = addressText.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase) + ? Int32.Parse(addressText.Substring(2), NumberStyles.HexNumber) + : Int32.Parse(addressText); + + const Mcp23017Pin registerSelectPin = Mcp23017Pin.B7; + const Mcp23017Pin clockPin = Mcp23017Pin.B5; + var dataPins = new[] + { + Mcp23017Pin.B1, + Mcp23017Pin.B2, + Mcp23017Pin.B3, + Mcp23017Pin.B4 + }; + + Console.WriteLine(); + Console.WriteLine("Using I2C connection over MCP23017 Expander"); + Console.WriteLine("\tRegister Select: {0}", registerSelectPin); + Console.WriteLine("\tClock: {0}", clockPin); + Console.WriteLine("\tData 1: {0}", dataPins[0]); + Console.WriteLine("\tData 2: {0}", dataPins[1]); + Console.WriteLine("\tData 3: {0}", dataPins[2]); + Console.WriteLine("\tData 4: {0}", dataPins[3]); + Console.WriteLine("\tBacklight: VCC"); + Console.WriteLine("\tRead/write: GND"); + Console.WriteLine(); + + const ConnectorPin sdaPin = ConnectorPin.P1Pin03; + const ConnectorPin sclPin = ConnectorPin.P1Pin05; + + var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor()) { ClockDivider = 512 }; + var connection = new Mcp23017I2cConnection(driver.Connect(address)); + + return new Hd44780Configuration(driver) + { + Pins = new Hd44780Pins( + connection.Out(registerSelectPin), + connection.Out(clockPin), + dataPins.Select(pin => (IOutputBinaryPin)connection.Out(pin))) + }; + } + + private static Hd44780Configuration LoadPcf8574Configuration(IEnumerable args) + { + var addressText = args.SkipWhile(s => !String.Equals(s, "pcf8574", StringComparison.InvariantCultureIgnoreCase)).Skip(1).DefaultIfEmpty("0x20").First(); + var address = addressText.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase) + ? Int32.Parse(addressText.Substring(2), NumberStyles.HexNumber) + : Int32.Parse(addressText); + + const Pcf8574Pin clockPin = Pcf8574Pin.P2; + const Pcf8574Pin readWritePin = Pcf8574Pin.P1; + const Pcf8574Pin registerSelectPin = Pcf8574Pin.P0; + const Pcf8574Pin backlightPin = Pcf8574Pin.P3; + var dataPins = new[] + { + Pcf8574Pin.P4, + Pcf8574Pin.P5, + Pcf8574Pin.P6, + Pcf8574Pin.P7 + }; + + Console.WriteLine(); + Console.WriteLine("Using I2C connection over PCF8574 expander"); + Console.WriteLine("\tRegister Select: {0}", registerSelectPin); + Console.WriteLine("\tClock: {0}", clockPin); + Console.WriteLine("\tData 1: {0}", dataPins[0]); + Console.WriteLine("\tData 2: {0}", dataPins[1]); + Console.WriteLine("\tData 3: {0}", dataPins[2]); + Console.WriteLine("\tData 4: {0}", dataPins[3]); + Console.WriteLine("\tBacklight: {0}", backlightPin); + Console.WriteLine("\tRead/write: {0}", readWritePin); + Console.WriteLine(); + + const ConnectorPin sdaPin = ConnectorPin.P1Pin03; + const ConnectorPin sclPin = ConnectorPin.P1Pin05; + + var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor()) { ClockDivider = 512 }; + var connection = new Pcf8574I2cConnection(driver.Connect(address)); + + return new Hd44780Configuration(driver) + { + Pins = new Hd44780Pins( + connection.Out(registerSelectPin), + connection.Out(clockPin), + dataPins.Select(p => (IOutputBinaryPin)connection.Out(p))) + { + Backlight = connection.Out(backlightPin), + ReadWrite = connection.Out(readWritePin), + } + }; + } + + private static Hd44780Configuration LoadGpioConfiguration() + { + const ConnectorPin registerSelectPin = ConnectorPin.P1Pin22; + const ConnectorPin clockPin = ConnectorPin.P1Pin18; + var dataPins = new[] + { + ConnectorPin.P1Pin16, + ConnectorPin.P1Pin15, + ConnectorPin.P1Pin13, + ConnectorPin.P1Pin11 + }; + + Console.WriteLine(); + Console.WriteLine("Using GPIO connection"); + Console.WriteLine("\tRegister Select: {0}", registerSelectPin); + Console.WriteLine("\tClock: {0}", clockPin); + Console.WriteLine("\tData 1: {0}", dataPins[0]); + Console.WriteLine("\tData 2: {0}", dataPins[1]); + Console.WriteLine("\tData 3: {0}", dataPins[2]); + Console.WriteLine("\tData 4: {0}", dataPins[3]); + Console.WriteLine("\tBacklight: VCC"); + Console.WriteLine("\tRead/write: GND"); + Console.WriteLine(); + + var driver = GpioConnectionSettings.DefaultDriver; + return new Hd44780Configuration + { + Pins = new Hd44780Pins( + driver.Out(registerSelectPin), + driver.Out(clockPin), + dataPins.Select(p => (IOutputBinaryPin)driver.Out(p))) + }; + } + + #endregion + } +} \ No newline at end of file diff --git a/Tests/Test.Gpio.HD44780/Hd44780Configuration.cs b/Tests/Test.Gpio.HD44780/Hd44780Configuration.cs new file mode 100644 index 0000000..24c539b --- /dev/null +++ b/Tests/Test.Gpio.HD44780/Hd44780Configuration.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Raspberry.IO; +using Raspberry.IO.Components.Displays.Hd44780; + +namespace Test.Gpio.HD44780 +{ + internal class Hd44780Configuration : IDisposable + { + private readonly IDisposable driver; + + public Hd44780Configuration(IDisposable driver = null) + { + this.driver = driver; + } + + public void Dispose() + { + if (driver != null) + driver.Dispose(); + } + + public Hd44780Pins Pins; + } +} \ No newline at end of file diff --git a/Tests/Test.Gpio.HD44780/Program.cs b/Tests/Test.Gpio.HD44780/Program.cs new file mode 100644 index 0000000..68a7bed --- /dev/null +++ b/Tests/Test.Gpio.HD44780/Program.cs @@ -0,0 +1,144 @@ +#region References + +using System; +using System.Linq; +using System.Net.NetworkInformation; +using System.Threading; +using Raspberry.IO.Components.Displays.Hd44780; + +#endregion + +namespace Test.Gpio.HD44780 +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("HD-44780 Sample: Display IP configuration on LCD screen"); + + var settings = new Hd44780LcdConnectionSettings + { + ScreenWidth = 20, + ScreenHeight = 2, + }; + + using (var configuration = ConfigurationLoader.FromArguments(args)) + using (var connection = new Hd44780LcdConnection(settings, configuration.Pins)) + { + connection.SetCustomCharacter(1, new byte[] {0x0, 0x0, 0x04, 0xe, 0x1f, 0x0, 0x0}); + connection.SetCustomCharacter(2, new byte[] {0x0, 0x0, 0x1f, 0xe, 0x04, 0x0, 0x0}); + + if (args.Contains("viewMap", StringComparer.InvariantCultureIgnoreCase)) + { + connection.Clear(); + DisplayCharMap(connection); + } + + connection.Clear(); + connection.WriteLine("R# IP Config"); + connection.WriteLine(Environment.OSVersion); + + Thread.Sleep(TimeSpan.FromSeconds(2)); + + var delay = 0m; + while (true) + { + foreach (var t in NetworkInterface.GetAllNetworkInterfaces() + .Where(i => i.NetworkInterfaceType != NetworkInterfaceType.Loopback) + .SelectMany(i => new[] + { + string.Format("{0}: {1}", i.Name, i.OperationalStatus) + + Environment.NewLine + + string.Format("\u0002{0} \u0001{1}", FormatByteCount(i.GetIPv4Statistics().BytesReceived), FormatByteCount(i.GetIPv4Statistics().BytesSent)), + + "IP " + (i.GetIPProperties().UnicastAddresses.Select(a => a.Address.ToString()).FirstOrDefault() ?? "(unassigned)") + + Environment.NewLine + + "MAC " + i.GetPhysicalAddress().ToString() + })) + { + connection.Clear(); + connection.Write(t, delay); + + for (var i = 0; i < 20; i++) + { + if (Console.KeyAvailable) + { + var c = Console.ReadKey(true).Key; + + switch (c) + { + case ConsoleKey.F5: + connection.Clear(); + break; + + case ConsoleKey.F6: + connection.CursorBlinking = !connection.CursorBlinking; + break; + + case ConsoleKey.F7: + connection.CursorEnabled = !connection.CursorEnabled; + break; + + case ConsoleKey.F8: + connection.DisplayEnabled = !connection.DisplayEnabled; + break; + + case ConsoleKey.F9: + connection.Move(-1); + break; + + case ConsoleKey.F10: + connection.Move(1); + break; + + case ConsoleKey.F11: + delay = 50.0m - delay; + break; + + default: + connection.BacklightEnabled = false; + return; + } + } + + Thread.Sleep(TimeSpan.FromSeconds(2d /20)); + } + } + } + } + } + + #region Private Helpers + + private static void DisplayCharMap(Hd44780LcdConnection connection) + { + var idx = 0; + foreach (var group in Hd44780A00Encoding.SupportedCharacters.GroupBy(c => (idx++/40))) + { + var s1 = new string(@group.Take(20).ToArray()); + var s2 = new string(@group.Skip(20).Take(20).ToArray()); + + connection.Clear(); + + connection.WriteLine(s1); + connection.WriteLine(s2); + + Thread.Sleep(2000); + } + } + + private static string FormatByteCount(long byteCount) + { + if (byteCount < 1024) + return string.Format("{0}B", byteCount); + if (byteCount < 1024*1024) + return string.Format("{0:0.0}KB", byteCount/1024.0m); + if (byteCount < 1024*1024*1024) + return string.Format("{0:0.0}MB", byteCount/(1024*1024.0m)); + + return string.Format("{0:0.0}GB", byteCount/(1024*1024*1024.0m)); + } + + #endregion + } +} diff --git a/Tests/Test.Gpio.HD44780/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.HD44780/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..4469337 --- /dev/null +++ b/Tests/Test.Gpio.HD44780/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.HD44780")] +[assembly: AssemblyDescription("Raspberry Pi HD44780 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2d1441fe-8142-4dc1-ae55-b17531eb9acd")] diff --git a/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.fzz b/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.fzz new file mode 100644 index 0000000..52d51d8 Binary files /dev/null and b/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.fzz differ diff --git a/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.png b/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.png new file mode 100644 index 0000000..138d3ce Binary files /dev/null and b/Tests/Test.Gpio.HD44780/Properties/Test.Gpio.HD44780.png differ diff --git a/Tests/Test.Gpio.HD44780/Test.Gpio.HD44780.csproj b/Tests/Test.Gpio.HD44780/Test.Gpio.HD44780.csproj new file mode 100644 index 0000000..9f774c2 --- /dev/null +++ b/Tests/Test.Gpio.HD44780/Test.Gpio.HD44780.csproj @@ -0,0 +1,102 @@ + + + + Debug + x86 + {6B5D38D3-7642-4DF9-A9AA-3AF7D00890BC} + Exe + Properties + Test.Gpio.HD44780 + Test.Gpio.HD44780 + v4.0 + + + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + {D2E41147-5BF6-4109-A497-C76284F3C020} + Raspberry.IO + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP23008/Program.cs b/Tests/Test.Gpio.MCP23008/Program.cs new file mode 100644 index 0000000..106bd6c --- /dev/null +++ b/Tests/Test.Gpio.MCP23008/Program.cs @@ -0,0 +1,40 @@ +#region References + +using System; +using System.Threading; +using Raspberry.IO.Components.Expanders.Mcp23008; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Test.Gpio.MCP23008 +{ + class Program + { + static void Main() + { + const ConnectorPin sdaPin = ConnectorPin.P1Pin03; + const ConnectorPin sclPin = ConnectorPin.P1Pin05; + + Console.WriteLine("MCP-23008 Sample: Switch LED on Pin0 pin"); + Console.WriteLine(); + Console.WriteLine("\tSDA: {0}", sdaPin); + Console.WriteLine("\tSCL: {0}", sclPin); + Console.WriteLine(); + + using (var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor())) + { + var deviceConnection = new Mcp23008I2cConnection(driver.Connect(0x20)); + deviceConnection.SetDirection(Mcp23008Pin.Pin0, Mcp23008PinDirection.Output); + + while (!Console.KeyAvailable) + { + deviceConnection.Toogle(Mcp23008Pin.Pin0); + Thread.Sleep(1000); + } + } + + } + } +} diff --git a/Tests/Test.Gpio.MCP23008/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.MCP23008/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d490767 --- /dev/null +++ b/Tests/Test.Gpio.MCP23008/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.MCP23008")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Test.Gpio.MCP23008")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9951e52d-ee54-4579-8eea-64a2e2e5a638")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Test.Gpio.MCP23008/Test.Gpio.MCP23008.csproj b/Tests/Test.Gpio.MCP23008/Test.Gpio.MCP23008.csproj new file mode 100644 index 0000000..a242b51 --- /dev/null +++ b/Tests/Test.Gpio.MCP23008/Test.Gpio.MCP23008.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {E401FE2A-7F73-41E7-9347-B51FEDAE71B9} + Exe + Properties + Test.Gpio.MCP23008 + Test.Gpio.MCP23008 + v4.0 + 512 + ..\..\ + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + + + + diff --git a/Tests/Test.Gpio.MCP23017/Program.cs b/Tests/Test.Gpio.MCP23017/Program.cs new file mode 100644 index 0000000..06b2734 --- /dev/null +++ b/Tests/Test.Gpio.MCP23017/Program.cs @@ -0,0 +1,39 @@ +#region References + +using System; +using System.Threading; +using Raspberry.IO.Components.Expanders.Mcp23017; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.InterIntegratedCircuit; + +#endregion + +namespace Test.Gpio.MCP23017 +{ + class Program + { + static void Main() + { + const ConnectorPin sdaPin = ConnectorPin.P1Pin03; + const ConnectorPin sclPin = ConnectorPin.P1Pin05; + + Console.WriteLine("MCP-23017 Sample: Switch LED on B0 pin"); + Console.WriteLine(); + Console.WriteLine("\tSDA: {0}", sdaPin); + Console.WriteLine("\tSCL: {0}", sclPin); + Console.WriteLine(); + + using (var driver = new I2cDriver(sdaPin.ToProcessor(), sclPin.ToProcessor())) + { + var deviceConnection = new Mcp23017I2cConnection(driver.Connect(0x20)); + deviceConnection.SetDirection(Mcp23017Pin.B0, Mcp23017PinDirection.Output); + + while (!Console.KeyAvailable) + { + deviceConnection.Toogle(Mcp23017Pin.B0); + Thread.Sleep(1000); + } + } + } + } +} \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP23017/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.MCP23017/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..12ad9bd --- /dev/null +++ b/Tests/Test.Gpio.MCP23017/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.MCP23017")] +[assembly: AssemblyDescription("Raspberry Pi MCP23017 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7f472591-835d-460f-a318-ed6627b6bf97")] diff --git a/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.fzz b/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.fzz new file mode 100644 index 0000000..94edc6a Binary files /dev/null and b/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.fzz differ diff --git a/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.png b/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.png new file mode 100644 index 0000000..fe72d8e Binary files /dev/null and b/Tests/Test.Gpio.MCP23017/Properties/Test.Gpio.MCP23017.png differ diff --git a/Tests/Test.Gpio.MCP23017/Test.Gpio.MCP23017.csproj b/Tests/Test.Gpio.MCP23017/Test.Gpio.MCP23017.csproj new file mode 100644 index 0000000..3347fd2 --- /dev/null +++ b/Tests/Test.Gpio.MCP23017/Test.Gpio.MCP23017.csproj @@ -0,0 +1,94 @@ + + + + Debug + x86 + {266EDDBC-F741-48F8-83B7-2CD37BACE62B} + Exe + Properties + Test.Gpio.MCP23017 + Test.Gpio.MCP23017 + v4.0 + Client + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP3008/Convert.cs b/Tests/Test.Gpio.MCP3008/Convert.cs new file mode 100644 index 0000000..00d662e --- /dev/null +++ b/Tests/Test.Gpio.MCP3008/Convert.cs @@ -0,0 +1,20 @@ +using UnitsNet; + +namespace Test.Gpio.MCP3008 +{ + internal static class Convert + { + #region Methods + + public static decimal ToLux(this ElectricResistance variableResistor) + { + // See http://learn.adafruit.com/photocells/using-a-photocell + // and http://www.emant.com/316002.page + + const decimal luxRatio = 500000; + return luxRatio / (decimal)variableResistor.Ohms; + } + + #endregion + } +} \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP3008/Program.cs b/Tests/Test.Gpio.MCP3008/Program.cs new file mode 100644 index 0000000..cf50362 --- /dev/null +++ b/Tests/Test.Gpio.MCP3008/Program.cs @@ -0,0 +1,73 @@ +#region References + +using System; +using System.Threading; +using Raspberry.IO.Components.Converters.Mcp3008; +using Raspberry.IO.Components.Sensors; +using Raspberry.IO.Components.Sensors.Temperature.Dht; +using Raspberry.IO.Components.Sensors.Temperature.Tmp36; +using Raspberry.IO.GeneralPurpose; +using UnitsNet; + +#endregion + +namespace Test.Gpio.MCP3008 +{ + /// + /// Freely adapted from http://learn.adafruit.com/reading-a-analog-in-and-controlling-audio-volume-with-the-raspberry-pi/overview + /// Connected pins are the same as in the original sample. + /// + class Program + { + static void Main() + { + const ConnectorPin adcClock = ConnectorPin.P1Pin12; + const ConnectorPin adcMiso = ConnectorPin.P1Pin16; + const ConnectorPin adcMosi = ConnectorPin.P1Pin18; + const ConnectorPin adcCs = ConnectorPin.P1Pin22; + + Console.WriteLine("MCP-3008 Sample: Reading temperature on Channel 0 and luminosity on Channel 1"); + Console.WriteLine(); + Console.WriteLine("\tClock: {0}", adcClock); + Console.WriteLine("\tCS: {0}", adcCs); + Console.WriteLine("\tMOSI: {0}", adcMosi); + Console.WriteLine("\tMISO: {0}", adcMiso); + Console.WriteLine(); + + ElectricPotential voltage = ElectricPotential.FromVolts(3.3); + + var driver = new MemoryGpioConnectionDriver(); //GpioConnectionSettings.DefaultDriver; + + using (var adcConnection = new Mcp3008SpiConnection( + driver.Out(adcClock), + driver.Out(adcCs), + driver.In(adcMiso), + driver.Out(adcMosi))) + using (var temperatureConnection = new Tmp36Connection( + adcConnection.In(Mcp3008Channel.Channel0), + voltage)) + using (var lightConnection = new VariableResistiveDividerConnection( + adcConnection.In(Mcp3008Channel.Channel1), + ResistiveDivider.ForLowerResistor(ElectricResistance.FromKiloohms(10)))) + { + Console.CursorVisible = false; + + while (!Console.KeyAvailable) + { + var temperature = temperatureConnection.GetTemperature(); + var resistor = lightConnection.GetResistance(); + var lux = resistor.ToLux(); + + Console.WriteLine("Temperature = {0,5:0.0} °C\tLight = {1,5:0.0} Lux ({2} ohms)", temperature, lux, (int)resistor.Ohms); + + Console.CursorTop--; + + Thread.Sleep(1000); + } + } + + Console.CursorTop++; + Console.CursorVisible = true; + } + } +} diff --git a/Tests/Test.Gpio.MCP3008/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.MCP3008/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..07055ca --- /dev/null +++ b/Tests/Test.Gpio.MCP3008/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.MCP3008")] +[assembly: AssemblyDescription("Raspberry Pi MCP3008 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("72ce3a1e-982d-4a8f-bb3c-422d7c00b816")] diff --git a/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.fzz b/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.fzz new file mode 100644 index 0000000..4f277ae Binary files /dev/null and b/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.fzz differ diff --git a/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.png b/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.png new file mode 100644 index 0000000..1094de2 Binary files /dev/null and b/Tests/Test.Gpio.MCP3008/Properties/Test.Gpio.MCP3008.png differ diff --git a/Tests/Test.Gpio.MCP3008/Test.Gpio.MCP3008.csproj b/Tests/Test.Gpio.MCP3008/Test.Gpio.MCP3008.csproj new file mode 100644 index 0000000..c28ab36 --- /dev/null +++ b/Tests/Test.Gpio.MCP3008/Test.Gpio.MCP3008.csproj @@ -0,0 +1,102 @@ + + + + Debug + x86 + {B28253A7-BB93-40F7-B41C-B4AE369174ED} + Exe + Properties + Test.Gpio.MCP3008 + Test.Gpio.MCP3008 + v4.0 + + + 512 + ..\..\ + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + + + + + + $(SolutionDir)packages\UnitsNet.3.46.1\lib\net35\UnitsNet.dll + True + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP3008/packages.config b/Tests/Test.Gpio.MCP3008/packages.config new file mode 100644 index 0000000..b4e59a6 --- /dev/null +++ b/Tests/Test.Gpio.MCP3008/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.MCP4822/Program.cs b/Tests/Test.Gpio.MCP4822/Program.cs new file mode 100644 index 0000000..139ccde --- /dev/null +++ b/Tests/Test.Gpio.MCP4822/Program.cs @@ -0,0 +1,66 @@ +#region Fields + +using System; +using System.Threading; +using Raspberry.IO; +using Raspberry.IO.Components.Converters.Mcp4822; +using Raspberry.IO.GeneralPurpose; + +#endregion + +namespace Test.Gpio.MCP4822 +{ + /// + /// Freely adapted from http://www.skpang.co.uk/blog/archives/689 + /// Connected pins are custom. + /// + class Program + { + static void Main() + { + const ConnectorPin dacClock = ConnectorPin.P1Pin11; + const ConnectorPin dacCs = ConnectorPin.P1Pin13; + const ConnectorPin dacMosi = ConnectorPin.P1Pin15; + + Console.WriteLine("MCP-4822 Sample: Write a changing value on Channel A"); + Console.WriteLine(); + Console.WriteLine("\tClock: {0}", dacClock); + Console.WriteLine("\tCS: {0}", dacCs); + Console.WriteLine("\tMOSI: {0}", dacMosi); + Console.WriteLine(); + + var driver = GpioConnectionSettings.DefaultDriver; + + using(var clockPin = driver.Out(dacClock)) + using(var csPin = driver.Out(dacCs)) + using(var mosiPin = driver.Out(dacMosi)) + using (var dacConnection = new Mcp4822SpiConnection(clockPin, csPin, mosiPin)) + using (var channel = new Mcp4822OutputAnalogPin(dacConnection, Mcp4822Channel.ChannelA)) + { + const decimal minimum = 0.0001m; + var ticks = minimum; + var up = true; + + while (!Console.KeyAvailable) + { + channel.Write(new AnalogValue(ticks)); + + if (up) + { + ticks *= 2; + if (ticks >= 1) + up = false; + } + else + { + ticks /= 2; + if (ticks <= minimum) + up = true; + } + + Thread.Sleep(100); + } + } + } + } +} diff --git a/Tests/Test.Gpio.MCP4822/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.MCP4822/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..199a35f --- /dev/null +++ b/Tests/Test.Gpio.MCP4822/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.MCP4822")] +[assembly: AssemblyDescription("Raspberry Pi MCP4822 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("81a0aa1c-37b4-429d-9e95-d049372ef9df")] diff --git a/Tests/Test.Gpio.MCP4822/Test.Gpio.MCP4822.csproj b/Tests/Test.Gpio.MCP4822/Test.Gpio.MCP4822.csproj new file mode 100644 index 0000000..e135eb3 --- /dev/null +++ b/Tests/Test.Gpio.MCP4822/Test.Gpio.MCP4822.csproj @@ -0,0 +1,90 @@ + + + + Debug + x86 + {54075457-7C1D-4C8F-BE7D-CFCA34F11228} + Exe + Properties + Test.Gpio.MCP4822 + Test.Gpio.MCP4822 + v4.0 + + + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\Debug\ + DEBUG;TRACE + full + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\Release\ + TRACE + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 4 + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + {D2E41147-5BF6-4109-A497-C76284F3C020} + Raspberry.IO + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.Pca9685/PCA9685Options.cs b/Tests/Test.Gpio.Pca9685/PCA9685Options.cs new file mode 100644 index 0000000..24316e0 --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/PCA9685Options.cs @@ -0,0 +1,51 @@ +#region References + +using System; +using Raspberry.IO.Components.Controllers.Pca9685; +using Raspberry.IO.GeneralPurpose; + +#endregion + +namespace Test.Gpio.PCA9685 +{ + public class Pca9685Options + { + #region Properties + + public PwmChannel Channel { get; set; } + + public UnitsNet.Frequency PwmFrequency { get; set; } + + public int DeviceAddress { get; set; } + + public ConnectorPin SdaPin { get; set; } + + public ConnectorPin SclPin { get; set; } + + public int PwmOn { get; set; } + + public int PwmOff { get; set; } + + public bool ShowHelp { get; set; } + + #endregion + + #region Methods + + public override string ToString() + { + return string.Format( + "Channel={1}{0}SdaPin={3}{0}SclPin={4}{0}DeviceAddress=0x{5:X}{0}PwmFrequency={2}{0}PwmOn={6}{0}PwmOff={7}{0}", + Environment.NewLine, + Channel, + PwmFrequency, + SdaPin, + SclPin, + DeviceAddress, + PwmOn, + PwmOff); + } + + #endregion + } +} \ No newline at end of file diff --git a/Tests/Test.Gpio.Pca9685/Program.cs b/Tests/Test.Gpio.Pca9685/Program.cs new file mode 100644 index 0000000..cee15d4 --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/Program.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using Common.Logging; +using NDesk.Options; +using Raspberry.IO.Components.Controllers.Pca9685; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO.InterIntegratedCircuit; +using UnitsNet; + +namespace Test.Gpio.PCA9685 +{ + /// + /// Demonstrates a connection to the Pca9685 LED controller - used by the Adafruit 16-channel PWM/Servo Shield + /// + /// + /// Ported from https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_PWM_Servo_Driver/Servo_Example.py + /// + class Program + { + private static readonly ILog log = LogManager.GetLogger(); + + static void Main(string[] args) + { + var options = ParseOptions(args); + if (options == null) + return; + + log.Info("-=Pca9685 Sample=-"); + log.Info(m => m("Running {0}", Environment.OSVersion)); + log.Info("Options:" + Environment.NewLine + options); + + var pulse = CalculatePulse(options.PwmFrequency, 50); + log.Info(m => m("Pulse={0}", pulse)); + + if (Environment.OSVersion.Platform != PlatformID.Unix) + { + log.Warn("Windows detected. Exiting"); + return; + } + + log.Info("Connecting..."); + + try + { + using (var driver = new I2cDriver(options.SdaPin.ToProcessor(), options.SclPin.ToProcessor())) + { + log.Info("Creating device..."); + var device = new Pca9685Connection(driver.Connect(options.DeviceAddress)); + + log.Info("Setting frequency..."); + device.SetPwmUpdateRate(options.PwmFrequency); + while (!Console.KeyAvailable) + { + log.Info(m => m("Set channel={0} to {1}", options.Channel, options.PwmOn)); + device.SetPwm(options.Channel, 0, options.PwmOn); + Thread.Sleep(1000); + log.Info(m => m("Set channel={0} to {1}", options.Channel, options.PwmOff)); + device.SetPwm(options.Channel, 0, options.PwmOff); + Thread.Sleep(1000); + } + log.Info("Key pressed. Exiting."); + } + } + catch (InvalidOperationException e) + { + log.Error("Failed to connect? Do you have a Pca9685 IC attached to the i2c line and powered on?", e); + } + } + + #region Private Helpers + + /// + /// Ported but wasn't used in original? Ported from https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code/blob/master/Adafruit_PWM_Servo_Driver/Servo_Example.py + /// Not entirely sure what the result is meant to mean. + /// + private static int CalculatePulse(Frequency frequency, int pulse) + { + const int microSeconds = 1000000; // # 1,000,000 us per second + + var pulseLengthMicroSeconds = microSeconds/(int)frequency.Hertz; // # 60 Hz + log.Info(m => m("{0} uSecs per period", pulseLengthMicroSeconds)); + + var microSecondsPerBit = pulseLengthMicroSeconds/4096; // # 12 bits of resolution + log.Info(m => m("{0} uSecs per bit", microSecondsPerBit)); + + return (pulse*1000)/pulseLengthMicroSeconds; + } + + private static Pca9685Options ParseOptions(IEnumerable arguments) + { + var options = new Pca9685Options + { + SdaPin = ConnectorPin.P1Pin03, + SclPin = ConnectorPin.P1Pin05, + DeviceAddress = 0x40, + PwmFrequency = Frequency.FromHertz(60), + PwmOn = 150, + PwmOff = 600 + }; + + var optionSet = new OptionSet + { + {"c|Channel=", v => options.Channel = (PwmChannel) Enum.Parse(typeof (PwmChannel), v)}, + {"f|PwmFrequency=", v => options.PwmFrequency = Frequency.FromHertz(int.Parse(v))}, + {"b|PwmOn=", v => options.PwmOn = int.Parse(v)}, + {"e|PwmOff=", v => options.PwmOff = int.Parse(v)}, + {"h|?:", v => options.ShowHelp = true} + }; + + optionSet.Parse(arguments); + + if (options.ShowHelp) + { + Console.WriteLine("Options:"); + optionSet.WriteOptionDescriptions(Console.Out); + + return null; + } + + return options; + } + + #endregion + } +} diff --git a/Tests/Test.Gpio.Pca9685/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.Pca9685/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5f479ff --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.Pca9685")] +[assembly: AssemblyDescription("Raspberry Pi Pca9685 Component Sample")] +[assembly: AssemblyConfiguration("")] diff --git a/Tests/Test.Gpio.Pca9685/Test.Gpio.Pca9685.csproj b/Tests/Test.Gpio.Pca9685/Test.Gpio.Pca9685.csproj new file mode 100644 index 0000000..c65a4a2 --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/Test.Gpio.Pca9685.csproj @@ -0,0 +1,90 @@ + + + + + Debug + AnyCPU + {57A001A7-FCF7-45EC-8E25-A390EF044E94} + Exe + Properties + Test.Gpio.PCA9685 + Test.Gpio.PCA9685 + v4.0 + 512 + ..\ + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\Common.Logging.3.3.1\lib\net40\Common.Logging.dll + True + + + $(SolutionDir)packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll + True + + + $(SolutionDir)packages\NDesk.Options.0.2.1\lib\NDesk.Options.dll + + + + + $(SolutionDir)packages\UnitsNet.3.46.1\lib\net35\UnitsNet.dll + True + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {63B8403E-BC56-43F9-A045-F61ECC3871F3} + Raspberry.IO.InterIntegratedCircuit + + + + + Designer + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.Pca9685/app.config b/Tests/Test.Gpio.Pca9685/app.config new file mode 100644 index 0000000..2243207 --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/app.config @@ -0,0 +1,19 @@ + + + + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.Pca9685/packages.config b/Tests/Test.Gpio.Pca9685/packages.config new file mode 100644 index 0000000..c717648 --- /dev/null +++ b/Tests/Test.Gpio.Pca9685/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Tests/Test.Gpio.WatchPin/Program.cs b/Tests/Test.Gpio.WatchPin/Program.cs new file mode 100644 index 0000000..2ae2f84 --- /dev/null +++ b/Tests/Test.Gpio.WatchPin/Program.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using Raspberry.IO.GeneralPurpose; + +namespace Test.Gpio.WatchPin +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("WatchPin Sample: log the state changes on input pin. \nTry to run it in parallel with i.e. Test.Gpio.HCSR501 or to test HCSR04 (IR motion detector) or SW-18020P (shake detector)"); + Console.WriteLine(); + + if (args.Length!=1) + { + PrintUsage(); + return; + } + + var pinname = args[0]; + + //TODO: Allow watch multiple pins simultaneously + //TODO: Allow to specify pin in different ways, ie, by name, by wiringPi, etc. + ConnectorPin userPin; + if (!Enum.TryParse(pinname, true, out userPin)) + { + Console.WriteLine("Could not find pin: "+pinname); + PrintUsage(); + return; + } + + Console.WriteLine("\tWatching Pin: {0}", userPin); + Console.WriteLine(); + Console.WriteLine("Press CTRL-C to stop"); + + var procPin = userPin.ToProcessor(); + + var driver = GpioConnectionSettings.DefaultDriver; + + try + { + driver.Allocate(procPin, PinDirection.Input); + + var isHigh = driver.Read(procPin); + + while (true) + { + var now = DateTime.Now; + + Console.WriteLine(now + "." + now.Millisecond.ToString("000") + ": " + (isHigh?"HIGH":"LOW")); + + driver.Wait(procPin, !isHigh, TimeSpan.FromDays(7)); //TODO: infinite + isHigh = !isHigh; + } + } + finally + { + // Leaves the pin unreleased so that other processes can keep reading + //driver.Release(procPin); + } + } + + private static void PrintUsage() + { + Console.WriteLine("Usage: Test.Gpio.WatchPin [pin]"); + Console.WriteLine("Available pins:"); + Enum.GetNames(typeof(ConnectorPin)).ToList().ForEach(Console.WriteLine); + + Console.WriteLine("//todo allow watch multiple pins simultanously"); + Console.WriteLine("//todo allow to specify pin in diffrent ways, ie, by name, by wiringPi, etc"); + Console.WriteLine("I.e.: sudo mono Test.Gpio.WatchPin.exe P1Pin23"); + Console.WriteLine(); + } + } +} diff --git a/Tests/Test.Gpio.WatchPin/Properties/AssemblyInfo.cs b/Tests/Test.Gpio.WatchPin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9a93a84 --- /dev/null +++ b/Tests/Test.Gpio.WatchPin/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Gpio.WatchPin")] +[assembly: AssemblyDescription("Raspberry Pi WatchPin Test")] +[assembly: AssemblyConfiguration("")] diff --git a/Tests/Test.Gpio.WatchPin/Test.Gpio.WatchPin.csproj b/Tests/Test.Gpio.WatchPin/Test.Gpio.WatchPin.csproj new file mode 100644 index 0000000..6f3740b --- /dev/null +++ b/Tests/Test.Gpio.WatchPin/Test.Gpio.WatchPin.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {60B59A9C-69F6-4CEA-8385-C8542AE2BB98} + Exe + Properties + Test.Gpio.WatchPin + Test.Gpio.WatchPin + v4.5 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + + + \ No newline at end of file diff --git a/Tests/Test.Spi.MCP3208/Program.cs b/Tests/Test.Spi.MCP3208/Program.cs new file mode 100644 index 0000000..46fc7b4 --- /dev/null +++ b/Tests/Test.Spi.MCP3208/Program.cs @@ -0,0 +1,76 @@ +#region References + +using System; +using System.Threading; +using Raspberry.IO.Components.Converters.Mcp3208; +using Raspberry.IO.Components.Sensors; +using Raspberry.IO.Components.Sensors.Temperature.Dht; +using Raspberry.IO.Components.Sensors.Temperature.Tmp36; +using Raspberry.IO.GeneralPurpose; +using Raspberry.IO; + +using System.IO; + +#endregion + +namespace Test.Spi.MCP3208 +{ + /// + /// Modified from Test.Gpio.MCP3208 + /// Connected with standars SPI pins in Raspberry + /// + class Program + { + static void Main() + { + const ConnectorPin adcClock = ConnectorPin.P1Pin23; + const ConnectorPin adcMiso = ConnectorPin.P1Pin21; + const ConnectorPin adcMosi = ConnectorPin.P1Pin19; + const ConnectorPin adcCs = ConnectorPin.P1Pin24; + + Console.Clear(); + + Console.WriteLine("MCP-3208 Sample: Reading ADC points in all channels"); + Console.WriteLine(); + Console.WriteLine("\tClock: {0}", adcClock); + Console.WriteLine("\tCS: {0}", adcCs); + Console.WriteLine("\tMOSI: {0}", adcMosi); + Console.WriteLine("\tMISO: {0}", adcMiso); + Console.WriteLine(); + + var driver = new GpioConnectionDriver(); + + { + Console.CursorVisible = false; + var adcConnection = new Mcp3208SpiConnection( + driver.Out(adcClock), + driver.Out(adcCs), + driver.In(adcMiso), + driver.Out(adcMosi)); + + while (!Console.KeyAvailable) + { + Console.CursorTop = 0; + Console.Clear(); + + Mcp3208Channel chan = Mcp3208Channel.Channel0; + + for (int i = 0; i < 8; i++) + { + AnalogValue p = adcConnection.Read(chan); + decimal points = p.Value; + Console.WriteLine(i.ToString() + " ADC points " + points.ToString()); + using (StreamWriter sw = File.AppendText(".\\prova.txt")) + { + sw.WriteLine(chan.ToString() + " ADC points " + points.ToString()); + } + chan++; // enum increase sends to the next channel + } + Thread.Sleep(500); + } + } + Console.CursorTop++; + Console.CursorVisible = true; + } + } +} \ No newline at end of file diff --git a/Tests/Test.Spi.MCP3208/Properties/AssemblyInfo.cs b/Tests/Test.Spi.MCP3208/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8b07c69 --- /dev/null +++ b/Tests/Test.Spi.MCP3208/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test.Spi.MCP3208")] +[assembly: AssemblyDescription("Raspberry Pi MCP3208 Component Sample")] +[assembly: AssemblyConfiguration("")] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("2e0267cc-028e-4e7a-8ca6-d9cab73f13ed")] + diff --git a/Tests/Test.Spi.MCP3208/Test.Spi.MCP3208.csproj b/Tests/Test.Spi.MCP3208/Test.Spi.MCP3208.csproj new file mode 100644 index 0000000..a2af000 --- /dev/null +++ b/Tests/Test.Spi.MCP3208/Test.Spi.MCP3208.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {D4B87926-598E-4EAB-BA75-CD9FB253D450} + Exe + Properties + Test.Spi.MCP3208 + Test.Spi.MCP3208 + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + + + \ No newline at end of file diff --git a/Tests/Test.Spi.TLC59711/Program.cs b/Tests/Test.Spi.TLC59711/Program.cs new file mode 100644 index 0000000..fb21bb4 --- /dev/null +++ b/Tests/Test.Spi.TLC59711/Program.cs @@ -0,0 +1,104 @@ +using System; +using System.Linq; +using Raspberry.IO.Components.Controllers.Tlc59711; +using Raspberry.IO.SerialPeripheralInterface; + +namespace Test.Spi.TLC59711 +{ + internal class Program + { + private const string DEVICE_FILEPATH = "/dev/spidev0.0"; + private const bool INITIALIZE_WITH_DEFAULT = true; + + private static int numberOfDevices = 1; + private static ushort maxBrightness = (UInt16)Math.Pow(2, 13); + + private static void Main() { + PrintIntroduction(); + AskUserForParameters(); + + using(var deviceConnection = new Tlc59711Connection(new NativeSpiConnection(DEVICE_FILEPATH), INITIALIZE_WITH_DEFAULT, numberOfDevices)) { + var devices = deviceConnection.Devices; + var channels = devices.Channels; + + devices.Blank(false); + deviceConnection.Update(); + + var numberOfChannels = channels.Count; + + var ledBrightness = new ushort[numberOfChannels]; + + // initialize all PWM channels from 0% (channel 0) to 100% (channel n) + var frac = maxBrightness / (numberOfChannels - 1); + for (var i = 1; i < numberOfChannels; i++) { + ledBrightness[i] = (UInt16) (frac * i); + } + + var fadeDirection = Enumerable.Range(1, numberOfChannels) + .Select(channel => 1) + .ToArray(); + + // Fade in/out loop + while (true) { + for (var i = 0; i < numberOfChannels; i++) { + if (ledBrightness[i] == maxBrightness) { + fadeDirection[i] = -1; + } + if (ledBrightness[i] == 0) { + fadeDirection[i] = 1; + } + + channels[i] = ledBrightness[i]; + ledBrightness[i] = (ushort) (ledBrightness[i] + fadeDirection[i]); + } + deviceConnection.Update(); + } + } + } + + private static void AskUserForParameters() { + Console.Clear(); + Console.Write( +@"The program will use {0}. + +How many devices are connected? +The maximum number is limited by your system configuration (Default: 1): ", DEVICE_FILEPATH); + + var numberOfDevicesLine = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(numberOfDevicesLine)) { + numberOfDevices = (int) uint.Parse(numberOfDevicesLine); + } + Console.Write( +@" +Please enter the maximum brightness level +(min: {0}, max: {1}, default: {2}): ", ushort.MinValue, ushort.MaxValue, maxBrightness); + var maxBrightnessLine = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(maxBrightnessLine)) { + maxBrightness = ushort.Parse(maxBrightnessLine); + } + + Console.WriteLine("Press CTRL-C to end."); + } + + private static void PrintIntroduction() { + Console.WriteLine( +@"The program will fade in/out all TLC59711 channels using the system's SPI +driver. Make sure that you have enabled the spi-bcm2708 kernel module. + +In Raspbian, comment out the following line in the file +/etc/modprobe.d/raspi-blacklist.conf: + +#blacklist spi-bcm2708 + +Make sure that your (first) TLC59711 is connected as follows: +MOSI (pin 19) -> DI +SCLK (pin 23) -> CI + +Press CTRL-C to abort or any key to continue. +"); + + Console.ReadKey(); + } + } +} + diff --git a/Tests/Test.Spi.TLC59711/Properties/AssemblyInfo.cs b/Tests/Test.Spi.TLC59711/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ddc5e97 --- /dev/null +++ b/Tests/Test.Spi.TLC59711/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Test.Spi.TLC59711")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Test.Spi.TLC59711")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("eac83688-77bd-40cc-9686-b89a329151dd")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Tests/Test.Spi.TLC59711/Test.Spi.TLC59711.csproj b/Tests/Test.Spi.TLC59711/Test.Spi.TLC59711.csproj new file mode 100644 index 0000000..4e4b7a0 --- /dev/null +++ b/Tests/Test.Spi.TLC59711/Test.Spi.TLC59711.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {AF711872-F931-46A8-AAC0-AFF6FFEF5AE3} + Exe + Properties + Test.Spi.TLC59711 + Test.Spi.TLC59711 + v4.0 + 512 + + + + AnyCPU + true + full + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {281C71ED-C36D-408E-8BAA-75C381DC17E7} + Raspberry.IO.GeneralPurpose + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {326342E5-0411-40E8-9F2D-563D6B192568} + Raspberry.IO.SerialPeripheralInterface + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711DeviceSpecs.cs b/UnitTests/Tests.Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711DeviceSpecs.cs new file mode 100644 index 0000000..af96d2b --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Components/Controllers/Tlc59711/Tlc59711DeviceSpecs.cs @@ -0,0 +1,234 @@ +using System; +using System.Collections; +using System.Diagnostics; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using Raspberry.IO; +using Raspberry.IO.Components.Controllers.Tlc59711; +using Raspberry.IO.Interop; + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming + +namespace Tests.Raspberry.IO.Components.Controllers.Tlc59711.Tlc59711DeviceSpecs +{ + [TestFixture] + public class If_the_user_creates_a_new_device_instance_with_default_settings : Spec + { + private const int COMMAND_SIZE = 28; + private const int NUMBER_OF_CHANNELS = 12; + private const int PWM_WORDSIZE = 2; + // 0x25, OUTTMG = 1, EXTGCK = 0, TMGRST = 1, DSPRPT = 1, BLANK = 1, BCB=0x7f, BCG=0x7f, BCR=0x7f + private const string EXPECTED_SETTINGS = "10010110111111111111111111111111"; + + private IMemory memory; + private ITlc59711Device device; + + protected override void EstablishContext() { + memory = new ManagedMemory(COMMAND_SIZE); + } + + protected override void BecauseOf() { + device = new Tlc59711Device(memory); + } + + [Test] + public void Should_the_device_set_to_BLANK() { + device.Blank.Should().BeTrue(); + } + + [Test] + public void Should_OUTTMG_set_to_TRUE() { + device.ReferenceClockEdge.Should().BeTrue(); + } + + [Test] + public void Should_EXTGCK_set_to_FALSE() { + device.ReferenceClock.Should().BeFalse(); + } + + [Test] + public void Should_TMGRST_set_to_TRUE() { + device.DisplayTimingResetMode.Should().BeTrue(); + } + + [Test] + public void Should_DSPRPT_set_to_TRUE() { + device.DisplayRepeatMode.Should().BeTrue(); + } + + [Test] + public void Should_the_memory_be_correctly_initialized() { + var expected_settings = EXPECTED_SETTINGS.BitStringToArray(true); + memory.Take(4) + .Should() + .ContainInOrder(expected_settings); + } + + [Test] + public void Should_all_channels_set_to_0x0000() { + memory.Skip(4) + .ToArray() + .Should() + .ContainInOrder(new byte[PWM_WORDSIZE * NUMBER_OF_CHANNELS]); + } + + protected override void Cleanup() { + if (!ReferenceEquals(memory, null)) { + memory.Dispose(); + memory = null; + } + } + } + + [TestFixture] + public class If_the_user_creates_a_new_device_instance_using_his_own_settings_settings : Spec + { + private const int COMMAND_SIZE = 28; + // 0x25, OUTTMG = 0, EXTGCK = 1, TMGRST = 0, DSPRPT = 0, BLANK = 0, BCB=0x00, BCG=0x00, BCR=0x00 + private const string EXPECTED_SETTINGS = "10010101000000000000000000000000"; + + private IMemory memory; + private ITlc59711Device device; + private ITlc59711Settings settings; + + protected override void EstablishContext() { + memory = new ManagedMemory(COMMAND_SIZE); + settings = new Tlc59711Settings { + Blank = false, + ReferenceClockEdge = false, + ReferenceClock = true, + DisplayTimingResetMode = false, + DisplayRepeatMode = false, + BrightnessControlB = 0, + BrightnessControlG = 0, + BrightnessControlR = 0 + }; + } + + protected override void BecauseOf() { + device = new Tlc59711Device(memory, settings); + } + + [Test] + public void Should_the_device_set_to_not_BLANK() { + device.Blank.Should().BeFalse(); + } + + [Test] + public void Should_OUTTMG_set_to_FALSE() { + device.ReferenceClockEdge.Should().BeFalse(); + } + + [Test] + public void Should_EXTGCK_set_to_TRUE() { + device.ReferenceClock.Should().BeTrue(); + } + + [Test] + public void Should_TMGRST_set_to_FALSE() { + device.DisplayTimingResetMode.Should().BeFalse(); + } + + [Test] + public void Should_DSPRPT_set_to_FALSE() { + device.DisplayRepeatMode.Should().BeFalse(); + } + + [Test] + public void Should_the_memory_be_correctly_initialized() { + var expected_settings = EXPECTED_SETTINGS.BitStringToArray(true); + memory.Take(4) + .Should() + .ContainInOrder(expected_settings); + } + + protected override void Cleanup() { + if (!ReferenceEquals(memory, null)) { + memory.Dispose(); + memory = null; + } + } + } + + [TestFixture] + public class If_the_user_changes_a_setting_after_initialization : Spec + { + private class TestCase : TestCaseData + { + public TestCase(string description, Action action) + : base(description, action) {} + } + + private const int COMMAND_SIZE = 28; + + private static IEnumerable TestCases { + get { + // MAGIC_WORD, OUTTMG, EXTGCK, TMGRST, DSPRPT, BLANK, BCB, BCG, BCR + yield return new TestCase("do nothing", s => { }) + .Returns("10010110111111111111111111111111"); + yield return new TestCase("Blank", s => s.Blank = false) + .Returns("10010110110111111111111111111111"); + yield return new TestCase("BrightnessControlR", s => s.BrightnessControlR = 0) + .Returns("10010110111111111111111110000000"); + yield return new TestCase("BrightnessControlG", s => s.BrightnessControlG = 0) + .Returns("10010110111111111100000001111111"); + yield return new TestCase("BrightnessControlB", s => s.BrightnessControlB = 0) + .Returns("10010110111000000011111111111111"); + yield return new TestCase("DisplayRepeatMode", s => s.DisplayRepeatMode = false) + .Returns("10010110101111111111111111111111"); + yield return new TestCase("DisplayTimingResetMode", s => s.DisplayTimingResetMode = false) + .Returns("10010110011111111111111111111111"); + yield return new TestCase("ReferenceClock", s => s.ReferenceClock = true) + .Returns("10010111111111111111111111111111"); + yield return new TestCase("ReferenceClockEdge", s => s.ReferenceClockEdge = false) + .Returns("10010100111111111111111111111111"); + } + } + + [Test, TestCaseSource("TestCases")] + public string Should_the_resulting_memory_be_correct(string description, Action action) { + using (var memory = new ManagedMemory(COMMAND_SIZE)) { + var device = new Tlc59711Device(memory); + action(device); + + Debug.Print("Running action on: {0}", description); + return memory + .Take(4) + .ToBitString(); + } + } + } + + [TestFixture] + public class If_the_user_changes_PWM_data_after_initialization : Spec + { + private const int NUMBER_OF_CHANNELS = 12; + private const int PWM_WORDSIZE = 2; + private const int COMMAND_SIZE = 28; + private const UInt16 VALUE = 0x1234; + + private static IEnumerable TestCases { + get { + for (var i = 0; i < NUMBER_OF_CHANNELS; i++) { + yield return new TestCaseData(i, VALUE) + .Returns(new byte[] {0x12, 0x34}); + } + } + } + + [Test,TestCaseSource("TestCases")] + public byte[] Should_the_memory_be_set_to_the_correct_values(int channel, UInt16 value) { + using (var memory = new ManagedMemory(COMMAND_SIZE)) { + var device = new Tlc59711Device(memory); + + device.Channels.Set(channel, value); + return memory + .Skip(4 + (channel * PWM_WORDSIZE)) + .Take(2) + .ToArray(); + } + } + } +} \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Components/Properties/AssemblyInfo.cs b/UnitTests/Tests.Raspberry.IO.Components/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..b1b09ea --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Components/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Tests.Raspberry.IO.Components")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tests.Raspberry.IO.Components")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("be948e15-5643-48a1-a32b-f5b98c6cc831")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/Tests.Raspberry.IO.Components/Tests.Raspberry.IO.Components.csproj b/UnitTests/Tests.Raspberry.IO.Components/Tests.Raspberry.IO.Components.csproj new file mode 100644 index 0000000..418273e --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Components/Tests.Raspberry.IO.Components.csproj @@ -0,0 +1,96 @@ + + + + + Debug + AnyCPU + {99EB3D1A-F0B7-454E-BB50-9B2F5349BC5B} + Library + Properties + Tests.Raspberry.IO.Components + Tests.Raspberry.IO.Components + v4.0 + 512 + + ..\..\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\FakeItEasy.1.25.3\lib\net40\FakeItEasy.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.Core.dll + True + + + $(SolutionDir)packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + {8388CFCA-E3DB-43F7-B049-2CB195211CE8} + Raspberry.IO.Components + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {326342E5-0411-40E8-9F2D-563D6B192568} + Raspberry.IO.SerialPeripheralInterface + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A} + Tests.Raspberry.IO + + + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Components/packages.config b/UnitTests/Tests.Raspberry.IO.Components/packages.config new file mode 100644 index 0000000..3c2106d --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Components/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Interop/MemorySubsetSpecs.cs b/UnitTests/Tests.Raspberry.IO.Interop/MemorySubsetSpecs.cs new file mode 100644 index 0000000..9ec0917 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Interop/MemorySubsetSpecs.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using Raspberry.IO.Interop; + +// ReSharper disable CheckNamespace +// ReSharper disable InconsistentNaming + +namespace Tests.Raspberry.IO.Interop.MemorySubsetSpecs +{ + [TestFixture] + public class If_the_user_tries_to_create_a_subset_that_exceeds_the_memory_boundaries : Spec { + private ManagedMemory managedMemory; + + protected override void EstablishContext() { + managedMemory = new ManagedMemory(10); + } + + private static IEnumerable TestCases { + get { + yield return new TestCaseData(11, 1).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(0, 11).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(-1, 10).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(10, 1).Throws(typeof(ArgumentOutOfRangeException)); + } + } + + [Test,TestCaseSource("TestCases")] + public IMemory Should_it_throw_an_exception(int startOffset, int length) { + return new MemorySubset(managedMemory, startOffset, length, false); + } + + protected override void Cleanup() { + if (managedMemory != null) { + managedMemory.Dispose(); + managedMemory = null; + } + } + } + + [TestFixture] + public class If_the_user_tries_to_copy_data_to_a_memory_subset_exceeding_the_memory_boundaries : Spec { + private ManagedMemory managedMemory; + private MemorySubset subset; + + protected override void EstablishContext() { + managedMemory = new ManagedMemory(10); + subset = new MemorySubset(managedMemory, 2, 4, true); + } + + private static IEnumerable TestCases { + get { + yield return new TestCaseData(4, 1).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(0, 5).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(-1, 2).Throws(typeof(ArgumentOutOfRangeException)); + } + } + + [Test, TestCaseSource("TestCases")] + public void Should_it_throw_an_exception(int startOffset, int length) { + var data = new byte[length]; + subset.Copy(data, 0, startOffset, length); + } + + protected override void Cleanup() { + if (subset != null) { + subset.Dispose(); + subset = null; + } + } + } + + [TestFixture] + public class If_the_user_copies_data_to_a_memory_subset : Spec { + private ManagedMemory managedMemory; + private MemorySubset subset; + + protected override void EstablishContext() { + managedMemory = new ManagedMemory(10); + subset = new MemorySubset(managedMemory, 2, 4, true); + } + + protected override void BecauseOf() { + var data = new byte[] {0x1, 0x2, 0x3, 0x4}; + subset.Copy(data, 0, 0, data.Length); + } + + [Test] + public void Should_the_origin_memory_contain_the_correct_data() { + managedMemory.ToArray().Should().ContainInOrder(new byte[] { + 0x0, 0x0, 0x1, 0x2, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0 + }); + } + + protected override void Cleanup() { + if (subset != null) { + subset.Dispose(); + subset = null; + } + } + } + + [TestFixture] + public class If_the_user_writes_data_to_a_memory_subset : Spec + { + private static IEnumerable TestCases { + get { + yield return new TestCaseData(-1, 0x1).Throws(typeof(ArgumentOutOfRangeException)); + + yield return new TestCaseData(0, 0x1).Returns(new byte[] { + 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }); + + yield return new TestCaseData(1, 0x1).Returns(new byte[] { + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 + }); + + yield return new TestCaseData(2, 0x1).Returns(new byte[] { + 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0 + }); + + yield return new TestCaseData(3, 0x1).Returns(new byte[] { + 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0 + }); + + yield return new TestCaseData(4, 0x1).Throws(typeof(ArgumentOutOfRangeException)); + } + } + + [Test,TestCaseSource("TestCases")] + public byte[] Should_the_origin_memory_contain_the_correct_data_when_using_the_WRITE_method(int startOffset, int value) { + using (var managedMemory = new ManagedMemory(10)) { + using (var subset = new MemorySubset(managedMemory, 2, 4, false)) { + subset.Write(startOffset, (byte) value); + return managedMemory.ToArray(); + } + } + } + + [Test, TestCaseSource("TestCases")] + public byte[] Should_the_origin_memory_contain_the_correct_data_when_using_the_INDEXER_method(int startOffset, int value) { + using (var managedMemory = new ManagedMemory(10)) { + using (var subset = new MemorySubset(managedMemory, 2, 4, false)) { + subset[startOffset] = (byte) value; + return managedMemory.ToArray(); + } + } + } + } + + [TestFixture] + public class If_the_user_reads_data_from_a_memory_subset : Spec { + private ManagedMemory managedMemory; + private readonly byte[] content = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9}; + private MemorySubset subset; + + protected override void EstablishContext() { + managedMemory = new ManagedMemory(10); + managedMemory.Copy(content, 0, 0, 10); + subset = new MemorySubset(managedMemory, 2, 4, true); + } + + private static IEnumerable TestCases { + get { + yield return new TestCaseData(-1).Throws(typeof(ArgumentOutOfRangeException)); + yield return new TestCaseData(0).Returns((byte) 0x2); + yield return new TestCaseData(1).Returns((byte) 0x3); + yield return new TestCaseData(2).Returns((byte) 0x4); + yield return new TestCaseData(3).Returns((byte) 0x5); + yield return new TestCaseData(4).Throws(typeof(ArgumentOutOfRangeException)); + } + } + + [Test,TestCaseSource("TestCases")] + public byte Should_the_result_be_correct_when_using_the_READ_method(int startOffset) { + return subset[startOffset]; + } + + [Test, TestCaseSource("TestCases")] + public byte Should_the_result_be_correct_when_using_the_INDEXER_method(int startOffset) { + return subset[startOffset]; + } + + protected override void Cleanup() { + if (subset != null) { + subset.Dispose(); + subset = null; + } + } + } +} \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Interop/Properties/AssemblyInfo.cs b/UnitTests/Tests.Raspberry.IO.Interop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f54f9d3 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Interop/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Tests.Raspberry.IO.Interop")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tests.Raspberry.IO.Interop")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("1281d04f-7151-40bf-b80e-ebe7c7bfb3a3")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/Tests.Raspberry.IO.Interop/Tests.Raspberry.IO.Interop.csproj b/UnitTests/Tests.Raspberry.IO.Interop/Tests.Raspberry.IO.Interop.csproj new file mode 100644 index 0000000..cd88ef2 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Interop/Tests.Raspberry.IO.Interop.csproj @@ -0,0 +1,88 @@ + + + + + Debug + AnyCPU + {CAF876A0-0FCB-44F7-96F1-53704CB6015F} + Library + Properties + Tests.Raspberry.IO.Interop + Tests.Raspberry.IO.Interop + v4.0 + 512 + + ..\..\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\FakeItEasy.1.25.3\lib\net40\FakeItEasy.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.Core.dll + True + + + $(SolutionDir)packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {ACE64F17-87E5-43E7-97A0-BDDE19059C61} + Raspberry.IO + + + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A} + Tests.Raspberry.IO + + + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.Interop/packages.config b/UnitTests/Tests.Raspberry.IO.Interop/packages.config new file mode 100644 index 0000000..3c2106d --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.Interop/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Interop/InteropSpec.cs b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Interop/InteropSpec.cs new file mode 100644 index 0000000..14fcbf5 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Interop/InteropSpec.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections; +using System.Diagnostics; +using NUnit.Framework; +using Raspberry.IO.SerialPeripheralInterface; + +// ReSharper disable InconsistentNaming +// ReSharper disable once CheckNamespace +namespace Tests.Raspberry.IO.SerialPeripheralInterface.InteropSpec +{ + [TestFixture] + public class If_the_user_requests_the_size_of_N_spi_transfer_messages : Spec { + private static IEnumerable TestCases { + get { + yield return new TestCaseData(0).Returns(Interop.SPI_IOC_MESSAGE_BASE); + yield return new TestCaseData(1).Returns((UInt32)0x40206b00); + yield return new TestCaseData(2).Returns((UInt32)0x40406b00); + yield return new TestCaseData(3).Returns((UInt32)0x40606b00); + } + } + + [Test,TestCaseSource("TestCases")] + public UInt32 Shall_the_result_be_correct(int numberOfMessages) { + var result = Interop.GetSpiMessageRequest(numberOfMessages); + Debug.Print("SPI_IOC_MESSAGE({0}) = {1:x}", numberOfMessages, result); + return result; + } + } +} diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/NativeSpiConnectionSpecs.cs b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/NativeSpiConnectionSpecs.cs new file mode 100644 index 0000000..d799c41 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/NativeSpiConnectionSpecs.cs @@ -0,0 +1,276 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using FakeItEasy; +using FakeItEasy.ExtensionSyntax.Full; +using FluentAssertions; +using NUnit.Framework; +using Raspberry.IO.SerialPeripheralInterface; + +// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming +namespace Tests.Raspberry.IO.SerialPeripheralInterface.NativeSpiConnectionSpecs +{ + [TestFixture] + public class If_the_user_creates_a_native_SPI_connection_instance_providing_appropriate_connection_settings : Spec { + private const int BITS_PER_WORD = 8; + private const int DELAY = 500; + private const SpiMode SPI_MODE = SpiMode.Mode2; + private const int SPEED_IN_HZ = 500000; + + private SpiConnectionSettings settings; + private NativeSpiConnection connection; + private ISpiControlDevice controlDevice; + + protected override void EstablishContext() { + settings = new SpiConnectionSettings { + BitsPerWord = BITS_PER_WORD, + Delay = DELAY, + Mode = SPI_MODE, + MaxSpeed = SPEED_IN_HZ + }; + + controlDevice = A.Fake(); + } + + protected override void BecauseOf() { + connection = new NativeSpiConnection(controlDevice, settings); + } + + [Test] + public void Should_it_write_the_max_speed_in_Hz_to_the_control_device() { + UInt32 speed = SPEED_IN_HZ; + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_WR_MAX_SPEED_HZ, ref speed)) + .MustHaveHappened(Repeated.Exactly.Once); + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_RD_MAX_SPEED_HZ, ref speed)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Test] + public void Should_it_write_the_bits_per_word_to_the_control_device() { + byte bitsPerWord = BITS_PER_WORD; + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_WR_BITS_PER_WORD, ref bitsPerWord)) + .MustHaveHappened(Repeated.Exactly.Once); + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_RD_BITS_PER_WORD, ref bitsPerWord)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Test] + public void Should_it_write_the_spi_mode_to_the_control_device() { + var spiMode = (UInt32)SPI_MODE; + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_WR_MODE, ref spiMode)) + .MustHaveHappened(Repeated.Exactly.Once); + + controlDevice + .CallsTo(device => device.Control(NativeSpiConnection.SPI_IOC_RD_MODE, ref spiMode)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Test] + public void Should_it_set_the_delay() { + connection.Delay.Should().Be(DELAY); + } + + [Test] + public void Should_it_set_the_speed() { + connection.MaxSpeed.Should().Be(SPEED_IN_HZ); + } + + [Test] + public void Should_it_set_the_SPI_mode() { + connection.Mode.Should().Be(SPI_MODE); + } + + [Test] + public void Should_it_set_the_bits_per_word() { + connection.BitsPerWord.Should().Be(BITS_PER_WORD); + } + } + + [TestFixture] + public class If_the_user_requests_a_spi_transfer_buffer : Spec { + private const int BITS_PER_WORD = 16; + private const int DELAY = 500; + private const SpiMode SPI_MODE = SpiMode.Mode2; + private const int SPEED_IN_HZ = 500000; + private const int REQUESTED_SIZE = 100; + + private SpiConnectionSettings settings; + private ISpiControlDevice controlDevice; + private NativeSpiConnection connection; + private ISpiTransferBuffer buffer; + + protected override void EstablishContext() { + settings = new SpiConnectionSettings { + BitsPerWord = BITS_PER_WORD, + Delay = DELAY, + Mode = SPI_MODE, + MaxSpeed = SPEED_IN_HZ + }; + + controlDevice = A.Fake(); + connection = new NativeSpiConnection(controlDevice, settings); + } + + protected override void BecauseOf() { + buffer = connection.CreateTransferBuffer(REQUESTED_SIZE, SpiTransferMode.ReadWrite); + } + + [Test] + public void Should_the_buffer_be_initialized_with_the_connections_wordsize() { + buffer.ControlStructure.BitsPerWord.Should().Be(BITS_PER_WORD); + } + + [Test] + public void Should_the_buffer_be_initialized_with_the_connections_delay() { + buffer.ControlStructure.Delay.Should().Be(DELAY); + } + + [Test] + public void Should_the_buffer_be_initialized_with_the_connections_speed() { + buffer.ControlStructure.Speed.Should().Be(SPEED_IN_HZ); + } + + protected override void Cleanup() { + if (!ReferenceEquals(buffer, null)) { + buffer.Dispose(); + buffer = null; + } + } + } + + [TestFixture] + public class If_the_user_starts_a_single_data_transfer : Spec { + private const int BITS_PER_WORD = 16; + private const int DELAY = 500; + private const int SPEED_IN_HZ = 500000; + private const int IOCTL_PINVOKE_RESULT_CODE = 1; + private const int SPI_IOC_MESSAGE_1 = 0x40206b00; + + private ISpiControlDevice controlDevice; + private NativeSpiConnection connection; + private ISpiTransferBuffer buffer; + private int result; + private SpiTransferControlStructure controlStructure; + + protected override void EstablishContext() { + // SPI control structure we expect to see during the P/Invoke call + controlStructure = new SpiTransferControlStructure { + BitsPerWord = BITS_PER_WORD, + Length = 5, + Delay = DELAY, + ChipSelectChange = 1, + Speed = SPEED_IN_HZ + }; + + controlDevice = A.Fake(); + controlDevice + .CallsTo(device => device.Control(A.Ignored, ref controlStructure)) + .WithAnyArguments() + .Returns(IOCTL_PINVOKE_RESULT_CODE); + + connection = new NativeSpiConnection(controlDevice); + + buffer = A.Fake(); + buffer + .CallsTo(b => b.ControlStructure) + .Returns(controlStructure); + } + + protected override void BecauseOf() { + result = connection.Transfer(buffer); + } + + [Test] + public void Should_the_buffers_control_structure_be_sent_to_the_IOCTL_device() { + controlDevice + .CallsTo(device => device.Control(SPI_IOC_MESSAGE_1, ref controlStructure)) + .MustHaveHappened(Repeated.Exactly.Once); + } + + [Test] + public void Should_it_return_the_pinvoke_result_code() { + result.Should().Be(IOCTL_PINVOKE_RESULT_CODE); + } + } + + [TestFixture] + public class If_the_user_starts_a_multi_data_transfer : Spec + { + private const int BITS_PER_WORD = 16; + private const int DELAY = 500; + private const int SPEED_IN_HZ = 500000; + private const int IOCTL_PINVOKE_RESULT_CODE = 1; + private const int SPI_IOC_MESSAGE_1 = 0x40206b00; + + private ISpiControlDevice controlDevice; + private NativeSpiConnection connection; + private ISpiTransferBufferCollection collection; + private ISpiTransferBuffer buffer; + private int result; + private SpiTransferControlStructure controlStructure; + + protected override void EstablishContext() { + controlDevice = A.Fake(); + controlDevice + .CallsTo(device => device.Control(A.Ignored, A.Ignored)) + .Returns(IOCTL_PINVOKE_RESULT_CODE); + + connection = new NativeSpiConnection(controlDevice); + + // SPI control structure we expect to see during the P/Invoke call + controlStructure = new SpiTransferControlStructure { + BitsPerWord = BITS_PER_WORD, + Length = 5, + Delay = DELAY, + ChipSelectChange = 1, + Speed = SPEED_IN_HZ + }; + + buffer = A.Fake(); + buffer + .CallsTo(b => b.ControlStructure) + .Returns(controlStructure); + + // setup fake collection to return our "prepared" fake buffer + collection = A.Fake(); + collection + .CallsTo(c => c.Length) + .Returns(1); + collection + .CallsTo(c => c.GetEnumerator()) + .ReturnsLazily(call => new List{buffer}.GetEnumerator()); + } + + protected override void BecauseOf() { + result = connection.Transfer(collection); + } + + [Test] + public void Should_the_buffers_control_structure_be_sent_to_the_IOCTL_device() { + controlDevice + .CallsTo(device => device.Control(SPI_IOC_MESSAGE_1, A.That.Matches(s => Predicate(s)))) + .MustHaveHappened(Repeated.Exactly.Once); + } + + private bool Predicate(IEnumerable control_structures) { + return control_structures.Contains(controlStructure); + } + + [Test] + public void Should_it_return_the_pinvoke_result_code() { + result.Should().Be(IOCTL_PINVOKE_RESULT_CODE); + } + } + +} \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..306c84b --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Tests.Raspberry.IO.SerialPeripheralInterface")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tests.Raspberry.IO.SerialPeripheralInterface")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("904914b3-5dcc-4874-9f74-e6ac2d836716")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferSpecs.cs b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferSpecs.cs new file mode 100644 index 0000000..fec7fea --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/SpiTransferBufferSpecs.cs @@ -0,0 +1,247 @@ +using System; +using FluentAssertions; +using NUnit.Framework; +using Raspberry.IO.SerialPeripheralInterface; + +// ReSharper disable once CheckNamespace +// ReSharper disable InconsistentNaming +namespace Tests.Raspberry.IO.SerialPeripheralInterface.SpiTransferBufferSpecs +{ + [TestFixture] + public class If_the_user_creates_an_spi_transfer_buffer_for_transmission_only : Spec { + private const int REQUESTED_SIZE = 500; + private ISpiTransferBuffer buffer; + + protected override void BecauseOf() { + buffer = new SpiTransferBuffer(REQUESTED_SIZE, SpiTransferMode.Write); + } + + [Test] + public void Should_the_structure_contain_a_memory_buffer_for_transmission_data() { + buffer.Tx.Should().NotBeNull(); + } + + [Test] + public void Should_the_structure_contain_no_memory_buffer_to_receive_data() { + buffer.Rx.Should().BeNull(); + } + + [Test] + public void Should_the_buffer_size_be_equal_to_the_requested_size() { + buffer.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_transmission_data_buffer_size_be_equal_to_the_requested_size() { + buffer.Tx.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_transfer_mode_be_write_only() { + buffer.TransferMode.Should().Be(SpiTransferMode.Write); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_TX() { + buffer.ControlStructure.Tx.Should().Be(unchecked((UInt64) buffer.Tx.Pointer.ToInt64())); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_RX() { + buffer.ControlStructure.Rx.Should().Be(0L); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_length() { + buffer.ControlStructure.Length.Should().Be(REQUESTED_SIZE); + } + + protected override void Cleanup() { + if (!ReferenceEquals(buffer, null)) { + buffer.Dispose(); + buffer = null; + } + } + } + + [TestFixture] + public class If_the_user_creates_an_spi_transfer_buffer_for_receive_only : Spec + { + private const int REQUESTED_SIZE = 500; + private ISpiTransferBuffer buffer; + + protected override void BecauseOf() { + buffer = new SpiTransferBuffer(REQUESTED_SIZE, SpiTransferMode.Read); + } + + [Test] + public void Should_the_structure_contain_no_memory_buffer_for_transmission_data() { + buffer.Tx.Should().BeNull(); + } + + [Test] + public void Should_the_structure_contain_a_memory_buffer_to_receive_data() { + buffer.Rx.Should().NotBeNull(); + } + + [Test] + public void Should_the_buffer_size_be_equal_to_the_requested_size() { + buffer.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_receive_data_buffer_size_be_equal_to_the_requested_size() { + buffer.Rx.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_transfer_mode_be_read_only() { + buffer.TransferMode.Should().Be(SpiTransferMode.Read); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_RX() { + buffer.ControlStructure.Rx.Should().Be(unchecked((UInt64)buffer.Rx.Pointer.ToInt64())); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_TX() { + buffer.ControlStructure.Tx.Should().Be(0L); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_length() { + buffer.ControlStructure.Length.Should().Be(REQUESTED_SIZE); + } + + protected override void Cleanup() { + if (!ReferenceEquals(buffer, null)) { + buffer.Dispose(); + buffer = null; + } + } + } + + [TestFixture] + public class If_the_user_creates_an_spi_transfer_buffer_for_transmission_and_receive : Spec + { + private const int REQUESTED_SIZE = 500; + private ISpiTransferBuffer buffer; + + protected override void BecauseOf() { + buffer = new SpiTransferBuffer(REQUESTED_SIZE, SpiTransferMode.ReadWrite); + } + + [Test] + public void Should_the_structure_contain_a_memory_buffer_for_transmission_data() { + buffer.Tx.Should().NotBeNull(); + } + + [Test] + public void Should_the_structure_contain_a_memory_buffer_to_receive_data() { + buffer.Rx.Should().NotBeNull(); + } + + [Test] + public void Should_the_buffer_size_be_equal_to_the_requested_size() { + buffer.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_transmission_data_buffer_size_be_equal_to_the_requested_size() { + buffer.Tx.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_receive_data_buffer_size_be_equal_to_the_requested_size() { + buffer.Rx.Length.Should().Be(REQUESTED_SIZE); + } + + [Test] + public void Should_the_data_buffers_not_have_the_same_memory_address() { + buffer.Rx.Pointer.Should().NotBe(buffer.Tx.Pointer); + } + + [Test] + public void Should_the_transfer_mode_have_set_the_READ_flag() { + buffer.TransferMode.HasFlag(SpiTransferMode.Read).Should().BeTrue(); + } + + [Test] + public void Should_the_transfer_mode_have_set_the_WRITE_flag() { + buffer.TransferMode.HasFlag(SpiTransferMode.Write).Should().BeTrue(); + } + + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_RX() { + buffer.ControlStructure.Rx.Should().Be(unchecked((UInt64)buffer.Rx.Pointer.ToInt64())); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_address_for_TX() { + buffer.ControlStructure.Tx.Should().Be(unchecked((UInt64)buffer.Tx.Pointer.ToInt64())); + } + + [Test] + public void Should_the_transfer_structure_have_the_correct_memory_length() { + buffer.ControlStructure.Length.Should().Be(REQUESTED_SIZE); + } + + protected override void Cleanup() { + if (!ReferenceEquals(buffer, null)) { + buffer.Dispose(); + buffer = null; + } + } + } + + [TestFixture] + public class If_the_user_changes_various_transfer_settings : Spec { + private const int REQUESTED_SIZE = 100; + private const int REQUESTED_BITS_PER_WORD = 16; + private const bool REQUESTED_CHIP_SELECT_CHANGE = true; + private const int REQUESTED_DELAY_IN_USEC = 100; + private const int REQUESTED_SPEED_IN_HZ = 1000000; + private SpiTransferBuffer buffer; + + protected override void EstablishContext() { + buffer = new SpiTransferBuffer(REQUESTED_SIZE, SpiTransferMode.Write); + } + + protected override void BecauseOf() { + buffer.BitsPerWord = REQUESTED_BITS_PER_WORD; + buffer.ChipSelectChange = REQUESTED_CHIP_SELECT_CHANGE; + buffer.Delay = REQUESTED_DELAY_IN_USEC; + buffer.Speed = REQUESTED_SPEED_IN_HZ; + } + + [Test] + public void Should_the_control_structure_have_the_requested_wordsize() { + buffer.ControlStructure.BitsPerWord.Should().Be(REQUESTED_BITS_PER_WORD); + } + + [Test] + public void Should_the_control_structure_have_the_requested_chip_select_change_value() { + // ReSharper disable once UnreachableCode + buffer.ControlStructure.ChipSelectChange.Should().Be(REQUESTED_CHIP_SELECT_CHANGE ? (byte)1 : (byte)0); + } + + [Test] + public void Should_the_control_structure_have_the_requested_delay() { + buffer.ControlStructure.Delay.Should().Be(REQUESTED_DELAY_IN_USEC); + } + + [Test] + public void Should_the_control_structure_have_the_requested_speed() { + buffer.ControlStructure.Speed.Should().Be(REQUESTED_SPEED_IN_HZ); + } + + protected override void Cleanup() { + if (!ReferenceEquals(buffer, null)) { + buffer.Dispose(); + buffer = null; + } + } + } +} \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj new file mode 100644 index 0000000..12ff3b6 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {17A2A965-36FA-4CDB-B2D6-AC69C9E9857F} + Library + Properties + Tests.Raspberry.IO.SerialPeripheralInterface + Tests.Raspberry.IO.SerialPeripheralInterface + v4.0 + 512 + ..\..\ + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + $(SolutionDir)packages\FakeItEasy.1.25.3\lib\net40\FakeItEasy.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.Core.dll + True + + + $(SolutionDir)packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + {689CB6C4-3D23-45DA-8E00-87C28AEA32D0} + Raspberry.IO.Interop + + + {326342E5-0411-40E8-9F2D-563D6B192568} + Raspberry.IO.SerialPeripheralInterface + + + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A} + Tests.Raspberry.IO + + + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings new file mode 100644 index 0000000..65668e8 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/Tests.Raspberry.IO.SerialPeripheralInterface.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/packages.config b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/packages.config new file mode 100644 index 0000000..3c2106d --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO.SerialPeripheralInterface/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO/Properties/AssemblyInfo.cs b/UnitTests/Tests.Raspberry.IO/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5af4fa3 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("Tests.Raspberry.IO")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tests.Raspberry.IO")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("e0f37de3-8576-49c3-aae9-9578d7612264")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/UnitTests/Tests.Raspberry.IO/Spec.cs b/UnitTests/Tests.Raspberry.IO/Spec.cs new file mode 100644 index 0000000..4c35d07 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO/Spec.cs @@ -0,0 +1,53 @@ +using System; +using System.Diagnostics; +using NUnit.Framework; + +namespace Tests.Raspberry.IO +{ + /// + /// Abstract helper class to make nunit tests more readable. + /// + [DebuggerStepThrough, DebuggerNonUserCode] + public class Spec + { + [DebuggerStepThrough] + [TestFixtureSetUp] + public void SetUp() { + EstablishContext(); + BecauseOf(); + } + + [DebuggerStepThrough] + [TestFixtureTearDown] + public void TearDown() { + Cleanup(); + } + + /// + /// Test setup. Place your initialization code here. + /// + [DebuggerStepThrough] + protected virtual void EstablishContext() {} + + /// + /// Test run. Place the tested method / action here. + /// + [DebuggerStepThrough] + protected virtual void BecauseOf() {} + + /// + /// Test clean. Close/delete files, close database connections .. + /// + [DebuggerStepThrough] + protected virtual void Cleanup() {} + + /// + /// Creates an Action delegate. + /// + /// Method the shall be created as delegate. + /// A delegate of type + protected Action Invoking(Action func) { + return func; + } + } +} \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj b/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj new file mode 100644 index 0000000..e213af9 --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj @@ -0,0 +1,75 @@ + + + + + Debug + AnyCPU + {0BB6C3CE-422E-49F2-9165-DEC06AFE1B1A} + Library + Properties + Tests.Raspberry.IO + Tests.Raspberry.IO + v4.0 + 512 + + ..\..\ + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + $(SolutionDir)packages\FakeItEasy.1.25.3\lib\net40\FakeItEasy.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.dll + True + + + $(SolutionDir)packages\FluentAssertions.4.6.3\lib\net40\FluentAssertions.Core.dll + True + + + $(SolutionDir)packages\NUnit.2.6.4\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj.DotSettings b/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj.DotSettings new file mode 100644 index 0000000..95561ee --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO/Tests.Raspberry.IO.csproj.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/UnitTests/Tests.Raspberry.IO/packages.config b/UnitTests/Tests.Raspberry.IO/packages.config new file mode 100644 index 0000000..3c2106d --- /dev/null +++ b/UnitTests/Tests.Raspberry.IO/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..76266c2 --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +powershell -f build.ps1 -configuration release +powershell -f build.ps1 -configuration debug \ No newline at end of file diff --git a/build.common.ps1 b/build.common.ps1 new file mode 100644 index 0000000..fd47175 --- /dev/null +++ b/build.common.ps1 @@ -0,0 +1,90 @@ +$Script:UseWriteHost = $true + +if(!$Global:ColorScheme) { + $Global:ColorScheme = @{ + "Banner"=[ConsoleColor]::Cyan + "RuntimeName"=[ConsoleColor]::Yellow + "Help_Header"=[ConsoleColor]::Yellow + "Help_Switch"=[ConsoleColor]::Green + "Help_Argument"=[ConsoleColor]::Cyan + "Help_Optional"=[ConsoleColor]::Gray + "Help_Command"=[ConsoleColor]::DarkYellow + "Help_Executable"=[ConsoleColor]::DarkYellow + "ParameterName"=[ConsoleColor]::Cyan + "Warning" = [ConsoleColor]::Yellow + } +} + +function _WriteOut { + param( + [Parameter(Mandatory=$false, Position=0, ValueFromPipeline=$true)][string]$msg, + [Parameter(Mandatory=$false)][ConsoleColor]$ForegroundColor, + [Parameter(Mandatory=$false)][ConsoleColor]$BackgroundColor, + [Parameter(Mandatory=$false)][switch]$NoNewLine) + + if($__TestWriteTo) { + $cur = Get-Variable -Name $__TestWriteTo -ValueOnly -Scope Global -ErrorAction SilentlyContinue + $val = $cur + "$msg" + if(!$NoNewLine) { + $val += [Environment]::NewLine + } + Set-Variable -Name $__TestWriteTo -Value $val -Scope Global -Force + return + } + + if(!$Script:UseWriteHost) { + if(!$msg) { + $msg = "" + } + if($NoNewLine) { + [Console]::Write($msg) + } else { + [Console]::WriteLine($msg) + } + } + else { + try { + if(!$ForegroundColor) { + $ForegroundColor = $host.UI.RawUI.ForegroundColor + } + if(!$BackgroundColor) { + $BackgroundColor = $host.UI.RawUI.BackgroundColor + } + + Write-Host $msg -ForegroundColor:$ForegroundColor -BackgroundColor:$BackgroundColor -NoNewLine:$NoNewLine + } catch { + $Script:UseWriteHost = $false + _WriteOut $msg + } + } +} + +function _WriteConfig{ +param( + [Parameter(Mandatory=$true,Position=0)]$name, + [Parameter(Mandatory=$true,Position=1)]$value) + + _WriteOut -NoNewline -ForegroundColor $Global:ColorScheme.ParameterName "${name}: " + _WriteOut "$value" + +} + +function _DownloadNuget{ +param( + [Parameter(Mandatory=$true,Position=0)]$rootPath) + +$sourceNugetExe = "http://nuget.org/nuget.exe" +$targetNugetExe = "$rootPath\nuget.exe" + + +if(!(Test-Path $targetNugetExe )){ + _WriteOut "Downloading nuget to $targetNugetExe" + Invoke-WebRequest $sourceNugetExe -OutFile $targetNugetExe +} +else{ + # _WriteOut "nuget.exe is already present" +} + +Set-Alias nuget $targetNugetExe -Scope Global + +} \ No newline at end of file diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..e9fb1da --- /dev/null +++ b/build.ps1 @@ -0,0 +1,43 @@ +param( + [string]$configuration = "Release" +) + +. ".\build.common.ps1" + +$solutionName = "RaspberrySharp.IO" + +function init { + # Initialization + $global:rootFolder = Split-Path -parent $script:MyInvocation.MyCommand.Path + $global:rootFolder = Join-Path $rootFolder . + $global:packagesFolder = Join-Path $rootFolder packages + $global:outputFolder = Join-Path $rootFolder _artifacts + $global:msbuild = "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" + + _WriteOut -ForegroundColor $ColorScheme.Banner "-= $solutionName Build =-" + _WriteConfig "rootFolder" $rootFolder +} + +function restorePackages{ + _WriteOut -ForegroundColor $ColorScheme.Banner "nuget restore" + + New-Item -Force -ItemType directory -Path $packagesFolder + _DownloadNuget $packagesFolder + nuget restore +} + +function buildSolution{ + + _WriteOut -ForegroundColor $ColorScheme.Banner "Build Solution" + & $msbuild "$rootFolder\$solutionName.sln" /p:Configuration=$configuration + +} + + +init + +restorePackages + +buildSolution + +Write-Host "Build $configuration complete" \ No newline at end of file