diff --git a/src/devices/Ws28xx/BitmapImageNeo4.cs b/src/devices/Ws28xx/BitmapImageNeo4.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9dae3197f65936aa69d9631e8b9fcfc85e1fc11f
--- /dev/null
+++ b/src/devices/Ws28xx/BitmapImageNeo4.cs
@@ -0,0 +1,63 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Drawing;
+using Iot.Device.Graphics;
+
+namespace Iot.Device.Ws28xx
+{
+ internal class BitmapImageNeo4 : BitmapImage
+ {
+ private const int BytesPerComponent = 3;
+ private const int BytesPerPixel = BytesPerComponent * 4;
+
+ // 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;
+
+ // This field defines the count within the lookup table. The length correlates to the possible values of a single byte.
+ private const int LookupCount = 256;
+
+ private static readonly byte[] _lookup = new byte[LookupCount * BytesPerComponent];
+
+ static BitmapImageNeo4()
+ {
+ for (int i = 0; i < LookupCount; 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));
+ }
+ }
+
+ public BitmapImageNeo4(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)
+ {
+ // Alpha is used as white.
+ 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];
+ Data[offset++] = _lookup[c.A * BytesPerComponent + 0];
+ Data[offset++] = _lookup[c.A * BytesPerComponent + 1];
+ Data[offset++] = _lookup[c.A * BytesPerComponent + 2];
+ }
+ }
+}
diff --git a/src/devices/Ws28xx/README.md b/src/devices/Ws28xx/README.md
index 75fcddbc22b3f5678501b46d1db960ca00a1333b..a4dffcc4ac09d8b6738ff6c6cf79835fdbb97a19 100644
--- a/src/devices/Ws28xx/README.md
+++ b/src/devices/Ws28xx/README.md
@@ -1,19 +1,20 @@
-# Ws28xx LED drivers
+# Ws28xx / SK6812 LED drivers
-This binding allows you to update the RGB LEDs on Ws28xx and based strips and matrices.
+This binding allows you to update the RGB LEDs on Ws28xx / SK6812 and based strips and matrices.
-To see how to use the binding in code, see the [sample](samples/Program.cs).
+To see how to use the binding in code, see the [sample](samples/Ws28xx_Samples/Program.cs).
## Documentation
* WS2812B: [Datasheet](https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf)
* WS2808: [Datasheet](https://datasheetspdf.com/pdf-file/806051/Worldsemi/WS2801/1)
+* SK6812: [Datasheet](https://cdn-shop.adafruit.com/product-files/2757/p2757_SK6812RGBW_REV01.pdf)
* [Neo pixels guide](https://learn.adafruit.com/adafruit-neopixel-uberguide)
* [Neo pixels x8 stick](https://www.adafruit.com/product/1426)
## Board
-### Neo pixels
+### Neo pixels / SK6812
![Raspberry Pi Breadboard diagram](rpi-neo-pixels_bb.png)
@@ -44,7 +45,7 @@ SpiConnectionSettings settings = new(0, 0)
using SpiDevice spi = SpiDevice.Create(settings);
Ws28xx neo = new Ws2808(spi, count);
-//Ws28xx neo = new Ws2812b(spi, Count);
+// Ws28xx neo = new Ws2812b(spi, Count);
while (true)
{
@@ -67,17 +68,27 @@ void Rainbow(Ws28xx neo, int count, int iterations = 1)
}
```
+***Note:***
+
+Using the SK6812 is almost the same, but the alpha channel of the color is used for the white LED. This means that the predefined color definitions (like ```System.Drawing.Color.Red```) will not work correctly as they have the alpha channel set to 255 (0xFF). That will turn the white LED always on. See the [sample](samples/SK6812_Samples/Programs.cs) for the main differences to the above code.
+Because ```System.Drawing.Color``` is a readonly struct, it's not possible to change the any channel directly. In order to correctly set Red, use ```Color.FromArgb(0, 255, 0, 0)```. For setting the white LED, use ```Color.FromArgb(255, 0, 0, 0)```. It's also possible to use an existing definition and remove the white channel like this:
+
+```csharp
+var color = Color.HotPink;
+var newColor = Color.FromArgb(0, color.R, color.G, color.B);
+```
+
## Binding Notes
### Raspberry Pi setup (/boot/config.txt)
-* Make sure spi is enabled
+* Make sure SPI is enabled
```text
dtparam=spi=on
```
-* Make sure SPI don't change speed fix the core clock:
+* To make sure SPI doesn't change speed fix the core clock:
```text
core_freq=250
diff --git a/src/devices/Ws28xx/Sk6812.cs b/src/devices/Ws28xx/Sk6812.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e9c848570d9271e07dad2f5a83021f359c74325
--- /dev/null
+++ b/src/devices/Ws28xx/Sk6812.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Device.Spi;
+
+namespace Iot.Device.Ws28xx
+{
+ ///
+ /// Represents the SK6812 Driver.
+ ///
+ ///
+ public class Sk6812 : Ws28xx
+ {
+ ///
+ /// Gets the default SPI clock frequency.
+ ///
+ ///
+ /// The default SPI clock frequency used for SK6812-LED-Strips.
+ ///
+ /// This frequency defines how fast the data will be put on the line. The SK6812-strip requires a speed of 1.25 kHz.
+ /// A single byte takes 1.2 µs for the transfer, the value for a single LED of 32 bit amounts to 38.4 µs.
+ ///
+ public static int DefaultSpiClockFrequency => 2_400_000;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The spi device.
+ /// The width.
+ /// The height.
+ public Sk6812(SpiDevice spiDevice, int width, int height = 1)
+ : base(spiDevice, new BitmapImageNeo4(width, height))
+ {
+ }
+ }
+}
diff --git a/src/devices/Ws28xx/Ws28xx.sln b/src/devices/Ws28xx/Ws28xx.sln
index db3d3439ba179de59182181a45fc5bab19d942f2..ce3935ac0d5eb70ae1f2e5d1aa3852a3b78e0ae5 100644
--- a/src/devices/Ws28xx/Ws28xx.sln
+++ b/src/devices/Ws28xx/Ws28xx.sln
@@ -1,13 +1,15 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.26124.0
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31729.503
MinimumVisualStudioVersion = 15.0.26124.0
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B2C3D1C3-4F59-4544-982C-E205F522DF27}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ws28xx.Samples", "samples\Ws28xx.Samples.csproj", "{D97F0DC4-777E-4F1A-BF18-A63DA22707ED}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ws28xx.Samples", "samples\Ws28xx_Samples\Ws28xx.Samples.csproj", "{D97F0DC4-777E-4F1A-BF18-A63DA22707ED}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ws28xx", "Ws28xx.csproj", "{D909238E-13C4-45C4-B044-CCBB08B51647}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ws28xx", "Ws28xx.csproj", "{D909238E-13C4-45C4-B044-CCBB08B51647}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sk6812.Samples", "samples\Sk6812_Samples\Sk6812.Samples.csproj", "{76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -18,9 +20,6 @@ Global
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D97F0DC4-777E-4F1A-BF18-A63DA22707ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D97F0DC4-777E-4F1A-BF18-A63DA22707ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
@@ -46,8 +45,27 @@ Global
{D909238E-13C4-45C4-B044-CCBB08B51647}.Release|x64.Build.0 = Release|Any CPU
{D909238E-13C4-45C4-B044-CCBB08B51647}.Release|x86.ActiveCfg = Release|Any CPU
{D909238E-13C4-45C4-B044-CCBB08B51647}.Release|x86.Build.0 = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|x64.Build.0 = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Debug|x86.Build.0 = Debug|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|Any CPU.Build.0 = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|x64.ActiveCfg = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|x64.Build.0 = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|x86.ActiveCfg = Release|Any CPU
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D97F0DC4-777E-4F1A-BF18-A63DA22707ED} = {B2C3D1C3-4F59-4544-982C-E205F522DF27}
+ {76D2B9B1-CBDE-445C-BE6D-B5C5C1754192} = {B2C3D1C3-4F59-4544-982C-E205F522DF27}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F452F127-C7D8-4D14-9484-B23E8F0C3468}
EndGlobalSection
-EndGlobal
\ No newline at end of file
+EndGlobal
diff --git a/src/devices/Ws28xx/samples/Sk6812_Samples/Program.cs b/src/devices/Ws28xx/samples/Sk6812_Samples/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2fb2c656a7fde424c39dbe75e7ce4367dee40d41
--- /dev/null
+++ b/src/devices/Ws28xx/samples/Sk6812_Samples/Program.cs
@@ -0,0 +1,152 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Device.Spi;
+using System.Drawing;
+using System.Threading;
+using Iot.Device.Graphics;
+using Iot.Device.Ws28xx;
+
+// Configure the count of pixels
+const int Count = 8;
+Console.Clear();
+
+SpiConnectionSettings settings = new(0, 0)
+{
+ ClockFrequency = Sk6812.DefaultSpiClockFrequency,
+ Mode = SpiMode.Mode0,
+ DataBitLength = 8
+};
+using SpiDevice spi = SpiDevice.Create(settings);
+
+Sk6812 neo = new Sk6812(spi, Count);
+
+Console.CancelKeyPress += (o, e) =>
+{
+ BitmapImage img = neo.Image;
+ img.Clear();
+ neo.Update();
+ Console.Clear();
+};
+
+while (true)
+{
+ Color4Wipe(neo, Color.White, Count);
+ Color4Wipe(neo, Color.Red, Count);
+ Color4Wipe(neo, Color.Green, Count);
+ Color4Wipe(neo, Color.Blue, Count);
+
+ Theatre4Chase(neo, Color.White, Count);
+ Theatre4Chase(neo, Color.Red, Count);
+ Theatre4Chase(neo, Color.Green, Count);
+ Theatre4Chase(neo, Color.Blue, Count);
+
+ Rainbow4(neo, Count);
+ Rainbow4Cycle(neo, Count);
+ TheaterChase4Rainbow(neo, Count);
+}
+
+void Color4Wipe(Sk6812 neo, Color color, int count)
+{
+ BitmapImage img = neo.Image;
+ for (var i = 0; i < count; i++)
+ {
+ img.SetPixel(i, 0, color);
+ neo.Update();
+ Thread.Sleep(100);
+ }
+}
+
+void Theatre4Chase(Ws28xx neo, Color color, int count, int iterations = 10)
+{
+ BitmapImage img = neo.Image;
+ for (var i = 0; i < iterations; i++)
+ {
+ for (var j = 0; j < 3; j++)
+ {
+ for (var k = 0; k < count; k += 3)
+ {
+ img.SetPixel(j + k, 0, color);
+ }
+
+ neo.Update();
+ Thread.Sleep(100);
+ for (var k = 0; k < count; k += 3)
+ {
+ img.SetPixel(j + k, 0, Color.FromArgb(0, 0, 0, 0));
+ }
+ }
+ }
+}
+
+Color Wheel4(int position)
+{
+ if (position < 85)
+ {
+ return Color.FromArgb(0, position * 3, 255 - position * 3, 0);
+ }
+ else if (position < 170)
+ {
+ position -= 85;
+ return Color.FromArgb(0, 255 - position * 3, 0, position * 3);
+ }
+ else
+ {
+ position -= 170;
+ return Color.FromArgb(0, 0, position * 3, 255 - position * 3);
+ }
+}
+
+void Rainbow4(Sk6812 neo, int count, int iterations = 1)
+{
+ BitmapImage img = neo.Image;
+ for (var i = 0; i < 255 * iterations; i++)
+ {
+ for (var j = 0; j < count; j++)
+ {
+ img.SetPixel(j, 0, Wheel4((i + j) & 255));
+ }
+
+ neo.Update();
+ Thread.Sleep(50);
+ }
+}
+
+void Rainbow4Cycle(Ws28xx neo, int count, int iterations = 1)
+{
+ BitmapImage img = neo.Image;
+ for (var i = 0; i < 255 * iterations; i++)
+ {
+ for (var j = 0; j < count; j++)
+ {
+ img.SetPixel(j, 0, Wheel4(((int)(j * 255 / count) + i) & 255));
+ }
+
+ neo.Update();
+ Thread.Sleep(50);
+ }
+}
+
+void TheaterChase4Rainbow(Ws28xx neo, int count)
+{
+ BitmapImage img = neo.Image;
+ for (var i = 0; i < 255; i++)
+ {
+ for (var j = 0; j < 3; j++)
+ {
+ for (var k = 0; k < count; k += 3)
+ {
+ img.SetPixel(k + j, 0, Wheel4((k + i) % 255));
+ }
+
+ neo.Update();
+ System.Threading.Thread.Sleep(100);
+
+ for (var k = 0; k < count; k += 3)
+ {
+ img.SetPixel(k + j, 0, Color.FromArgb(0, 0, 0, 0));
+ }
+ }
+ }
+}
diff --git a/src/devices/Ws28xx/samples/Sk6812_Samples/Sk6812.Samples.csproj b/src/devices/Ws28xx/samples/Sk6812_Samples/Sk6812.Samples.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..310d97f99f3f09b77075ba250836dcbfd0ff0bfb
--- /dev/null
+++ b/src/devices/Ws28xx/samples/Sk6812_Samples/Sk6812.Samples.csproj
@@ -0,0 +1,9 @@
+
+
+ Exe
+ net5.0
+
+
+
+
+
\ No newline at end of file
diff --git a/src/devices/Ws28xx/samples/Program.cs b/src/devices/Ws28xx/samples/Ws28xx_Samples/Program.cs
similarity index 100%
rename from src/devices/Ws28xx/samples/Program.cs
rename to src/devices/Ws28xx/samples/Ws28xx_Samples/Program.cs
diff --git a/src/devices/Ws28xx/samples/Ws28xx.Samples.csproj b/src/devices/Ws28xx/samples/Ws28xx_Samples/Ws28xx.Samples.csproj
similarity index 75%
rename from src/devices/Ws28xx/samples/Ws28xx.Samples.csproj
rename to src/devices/Ws28xx/samples/Ws28xx_Samples/Ws28xx.Samples.csproj
index 629938ff422f6b063721a3f246365f75f46b4dbc..9f4efbb5f335be5e2aa95f72357bde4396b77068 100644
--- a/src/devices/Ws28xx/samples/Ws28xx.Samples.csproj
+++ b/src/devices/Ws28xx/samples/Ws28xx_Samples/Ws28xx.Samples.csproj
@@ -4,6 +4,6 @@
$(DefaultSampleTfms)
-
+
\ No newline at end of file