diff --git a/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj b/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj index 0f9d04901a..62a15a15bb 100644 --- a/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj +++ b/src/Iot.Device.Bindings/Iot.Device.Bindings.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.1 diff --git a/src/devices/Common/Iot/Device/Graphics/BitmapImage.cs b/src/devices/Common/Iot/Device/Graphics/BitmapImage.cs new file mode 100644 index 0000000000..e98ed4cc66 --- /dev/null +++ b/src/devices/Common/Iot/Device/Graphics/BitmapImage.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Drawing; + +namespace Iot.Device.Graphics +{ + public abstract class BitmapImage + { + protected BitmapImage(byte[] data, int width, int height, int stride) + { + _data = data; + Width = width; + Height = height; + Stride = stride; + } + + private readonly byte[] _data; + public Span Data => _data; + public int Width { get; } + public int Height { get; } + public int Stride { get; } + + public abstract void SetPixel(int x, int y, Color c); + + public virtual void Clear(Color c = default) + { + for (int y = 0; y < Height; y++) + { + for (int x = 0; x < Width; x++) + { + SetPixel(x, y, c); + } + } + } + } +} diff --git a/src/devices/Ws2812B/BitmapImageNeo3.cs b/src/devices/Ws2812B/BitmapImageNeo3.cs new file mode 100644 index 0000000000..5bdfe5b415 --- /dev/null +++ b/src/devices/Ws2812B/BitmapImageNeo3.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Iot.Device.Graphics; +using System.Drawing; + +namespace Iot.Device.Ws2812b +{ + /// + /// Special 24bit RGB format for Neo pixel LEDs where each bit is converted to 3 bits. + /// A one is converted to 110, a zero is converted to 100. + /// + public class BitmapImageNeo3 : BitmapImage + { + private const int BytesPerComponent = 3; + private const int BytesPerPixel = BytesPerComponent * 3; + // The Neo Pixels require a 50us delay (all zeros) after. Since Spi freq is not exactly + // as requested 100us is used here with good practical results. 100us @ 2.4Mbps and 8bit + // data means we have to add 30 bytes of zero padding. + private const int ResetDelayInBytes = 30; + + public BitmapImageNeo3(int width, int height) + : base(new byte[width * height * BytesPerPixel + ResetDelayInBytes], width, height, width * BytesPerPixel) + { + } + + public override void SetPixel(int x, int y, Color c) + { + var offset = y * Stride + x * BytesPerPixel; + Data[offset++] = _lookup[c.G * BytesPerComponent + 0]; + Data[offset++] = _lookup[c.G * BytesPerComponent + 1]; + Data[offset++] = _lookup[c.G * BytesPerComponent + 2]; + Data[offset++] = _lookup[c.R * BytesPerComponent + 0]; + Data[offset++] = _lookup[c.R * BytesPerComponent + 1]; + Data[offset++] = _lookup[c.R * BytesPerComponent + 2]; + Data[offset++] = _lookup[c.B * BytesPerComponent + 0]; + Data[offset++] = _lookup[c.B * BytesPerComponent + 1]; + Data[offset++] = _lookup[c.B * BytesPerComponent + 2]; + } + + private static readonly byte[] _lookup = new byte[256 * BytesPerComponent]; + static BitmapImageNeo3() + { + for (int i = 0; i < 256; i++) + { + int data = 0; + for (int j = 7; j >= 0; j--) + { + data = (data << 3) | 0b100 | ((i >> j) << 1) & 2; + } + _lookup[i * BytesPerComponent + 0] = unchecked((byte)(data >> 16)); + _lookup[i * BytesPerComponent + 1] = unchecked((byte)(data >> 8)); + _lookup[i * BytesPerComponent + 2] = unchecked((byte)(data >> 0)); + } + } + } +} diff --git a/src/devices/Ws2812B/README.md b/src/devices/Ws2812B/README.md new file mode 100644 index 0000000000..f9660e77b7 --- /dev/null +++ b/src/devices/Ws2812B/README.md @@ -0,0 +1,27 @@ +# Ws2812b + +## Summary + +This binding allows you to update the RGB LEDs on Ws2812b based strips and matrices. + +To see how to use the binding in code, see the [sample](samples/README.md). + +## Device Family + +* WS2812B: [Datasheet](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf) + +## Binding Notes + +### Raspberry Pi setup (/boot/config.txt) + +* Make sure spi is enabled
+ `dtparam=spi=on` + +* Make sure SPI don't change speed fix the core clock
+ `core_freq=250`
+ `core_freq_min=250` + +## References + +* [Neo pixels guide](https://learn.adafruit.com/adafruit-neopixel-uberguide) +* [Neo pixels x8 stick](https://www.adafruit.com/product/1426) diff --git a/src/devices/Ws2812B/Ws2812B.cs b/src/devices/Ws2812B/Ws2812B.cs new file mode 100644 index 0000000000..71672b5396 --- /dev/null +++ b/src/devices/Ws2812B/Ws2812B.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Device.Spi; + +namespace Iot.Device.Ws2812b +{ + public class Ws2812b + { + private readonly SpiDevice _spiDevice; + public BitmapImageNeo3 Image { get; } + + public Ws2812b(SpiDevice spiDevice, int width, int height = 1) + { + _spiDevice = spiDevice; + _spiDevice.ConnectionSettings.ClockFrequency = 2_400_000; + _spiDevice.ConnectionSettings.Mode = SpiMode.Mode0; + _spiDevice.ConnectionSettings.DataBitLength = 8; + Image = new BitmapImageNeo3(width, height); + } + + public void Update() => _spiDevice.Write(Image.Data); + } +} diff --git a/src/devices/Ws2812B/Ws2812b.csproj b/src/devices/Ws2812B/Ws2812b.csproj new file mode 100644 index 0000000000..80c40400cc --- /dev/null +++ b/src/devices/Ws2812B/Ws2812b.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + + false + latest + + + + + + + + + + diff --git a/src/devices/Ws2812B/samples/README.md b/src/devices/Ws2812B/samples/README.md new file mode 100644 index 0000000000..7a4829f20c --- /dev/null +++ b/src/devices/Ws2812B/samples/README.md @@ -0,0 +1,26 @@ +# Drive Neo pixel strip (x8) from Raspberry Pi + +This [program](Ws2812b.Sample.cs) demonstrates how to use the [Neo pixel binding](../Ws2812b.cs) to drive an 8 Neo Pixel stick from a Raspberry Pi. + +It shows how to set the colors of each LED using `SetPixel` and `Update`. Then it shows how to fade in one of the LEDs in a loop. + +## Run the sample + +```console +cd samples +dotnet build -c release -o out +dotnet out/Ws2812b.Samples.dll +``` + +## Breadboard layout + +The following [fritzing diagram](rpi-neo-pixels.fzz) demonstrates how you should wire your device in order to run the program. It uses the GND, 5V and MOSI pins on the Raspberry Pi. + +![Raspberry Pi Breadboard diagram](rpi-neo-pixels_bb.png) + +## Hardware elements + +The following elements are used in this sample: + +* [Raspberry Pi 3](https://www.adafruit.com/product/3055) +* [Neo pixels x8 stick](https://www.adafruit.com/product/1426) diff --git a/src/devices/Ws2812B/samples/Ws2812b.Sample.cs b/src/devices/Ws2812B/samples/Ws2812b.Sample.cs new file mode 100644 index 0000000000..2135df201a --- /dev/null +++ b/src/devices/Ws2812B/samples/Ws2812b.Sample.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Device.Spi; +using System.Device.Spi.Drivers; +using System.Drawing; + +namespace Iot.Device.Ws2812b.Samples +{ + class Program + { + static void Main() + { + // Create a Neo Pixel x8 stick on spi 0.0 + var spi = new UnixSpiDevice(new SpiConnectionSettings(0, 0)); + var neo = new Ws2812b(spi, 8); + + // Display basic colors for 5 sec + BitmapImageNeo3 img = neo.Image; + img.SetPixel(0, 0, Color.White); + img.SetPixel(1, 0, Color.Red); + img.SetPixel(2, 0, Color.Green); + img.SetPixel(3, 0, Color.Blue); + img.SetPixel(4, 0, Color.Yellow); + img.SetPixel(5, 0, Color.Cyan); + img.SetPixel(6, 0, Color.Magenta); + img.SetPixel(7, 0, Color.FromArgb(unchecked((int)0xffff8000))); + neo.Update(); + System.Threading.Thread.Sleep(5000); + + // Fade in first pixel + byte b = 0; + img.Clear(); + while (true) + { + img.SetPixel(0, 0, Color.FromArgb(0xff, b, b, b)); + neo.Update(); + System.Threading.Thread.Sleep(10); + b++; + } + } + } +} diff --git a/src/devices/Ws2812B/samples/Ws2812b.Samples.csproj b/src/devices/Ws2812B/samples/Ws2812b.Samples.csproj new file mode 100644 index 0000000000..e360eb0f55 --- /dev/null +++ b/src/devices/Ws2812B/samples/Ws2812b.Samples.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/src/devices/Ws2812B/samples/rpi-neo-pixels.fzz b/src/devices/Ws2812B/samples/rpi-neo-pixels.fzz new file mode 100644 index 0000000000..c258a21e93 Binary files /dev/null and b/src/devices/Ws2812B/samples/rpi-neo-pixels.fzz differ diff --git a/src/devices/Ws2812B/samples/rpi-neo-pixels_bb.png b/src/devices/Ws2812B/samples/rpi-neo-pixels_bb.png new file mode 100644 index 0000000000..b8a0d9ed28 Binary files /dev/null and b/src/devices/Ws2812B/samples/rpi-neo-pixels_bb.png differ