
On this page
In this beginner-friendly guide, you’ll learn how to control a WS2812B (aka “NeoPixel”) LED light strip with a Raspberry Pi using .NET. We’ll enable SPI on the Pi, wire the strip correctly (including best practices like a level shifter and a series resistor), and write a small .NET 9 app using the Iot.Device.Bindings library to animate colors. We’ll also publish the app as a self-contained linux-arm64 binary so no .NET runtime is needed on the Pi.
Hardware
- Raspberry Pi, I’m using a Raspberry Pi 4 B ; Pi 5 also works
- It’s recommended to cool your Raspberry Pi , especially for long runs
- A WS2812B LED strip (I’m using this one )
Hardware Connection
- WS2812B strips usually expose 3 pins: DIN (data), 5V (power), and GND (ground).
- Connect the strip DIN to Raspberry Pi SPI0 MOSI via a level shifter and a 330–470 Ω series resistor.
- Connect the strip 5V to a dedicated 5V power supply.
- Connect strip GND and Pi GND together (common ground is mandatory).
I am using the Raspberry Pi 4, which means I have to connect
- Data (DIN) → GPIO10 (MOSI, physical pin 19)
- 5V → the Pi’s 5V pin
- GND → common ground with Pi (any GND pin)
Depending on the light strip, you also need an additional power supply to provide sufficient current. Please check the data sheet.
Raspberry Configuration
- Flash Raspberry Pi OS (64‑bit recommended) using Raspberry Pi Imager.
- Update packages:
1sudo apt update && sudo apt full-upgrade -y
2sudo reboot
- Enable SPI:
1sudo raspi-config
2# Interface Options -> SPI -> Enable
3sudo reboot
On newer Raspberry Pi OS versions (Bookworm), you can also ensure SPI is enabled via config:
1# edit the correct config file for your OS
2sudo nano /boot/firmware/config.txt # Bookworm
3
4# ensure this line exists (uncomment or add)
5dtparam=spi=on
Give your user access to SPI without sudo (log out and in afterwards):
1sudo usermod -aG spi $USER
WS2812B Configuration
- WS2812B LEDs require very precise timing. When driven via the Raspberry Pi SPI “trick”, the library encodes bits so SPI can approximate the WS2812B waveform. If the GPU core frequency changes, timing can drift.
- To keep timings stable, lock the GPU core frequency:
1sudo nano /boot/firmware/config.txt # Bookworm
2
3# add or update these lines
4[all]
5core_freq=500
6core_freq_min=500
If core_freq isn’t fixed, animations may flicker, show wrong colors, or fail intermittently because the effective SPI timing changes when the GPU scales frequencies.
Implementation
- We’ll create a .NET 9 console app, add the
Iot.Device.Bindingspackage (which includesIot.Device.Ws28xx). We’ll publish a self-contained linux-arm64 binary so the Pi doesn’t need a .NET runtime.
Create the app on your dev machine:
1dotnet new console -n Ws2812Demo
2cd Ws2812Demo
3dotnet add package Iot.Device.Bindings
Update Ws2812Demo.csproj to target .NET 9:
1<Project Sdk="Microsoft.NET.Sdk">
2 <PropertyGroup>
3 <OutputType>Exe</OutputType>
4 <TargetFramework>net9.0</TargetFramework>
5 <ImplicitUsings>enable</ImplicitUsings>
6 <Nullable>enable</Nullable>
7 </PropertyGroup>
8
9 <!-- set build properties for the pi -->
10 <PropertyGroup Label="Build">
11 <RuntimeIdentifiers>linux-arm64</RuntimeIdentifiers>
12 <PublishReadyToRun>false</PublishReadyToRun>
13 <PublishReadyToRunShowWarnings>false</PublishReadyToRunShowWarnings>
14 <PublishTrimmed>false</PublishTrimmed>
15 </PropertyGroup>
16
17 <ItemGroup>
18 <PackageReference Include="Iot.Device.Bindings" Version="4.0.1" />
19 </ItemGroup>
20</Project>
Replace Program.cs with this minimal demo that cycles colors every second. Adjust ledCount to your strip length.
1using System;
2using System.Device.Spi;
3using System.Threading;
4using Iot.Device.Ws28xx;
5using SixLabors.ImageSharp;
6using SixLabors.ImageSharp.PixelFormats;
7
8// Configure SPI0 CE0 (spidev0.0) at ~2.4 MHz (commonly used for WS2812B over SPI)
9// I'm using an 800 kbps LED strip, which requires these settings. Please check your datasheet!
10SpiConnectionSettings settings = new SpiConnectionSettings(0, 0)
11{
12 ClockFrequency = 2_400_000,
13 Mode = SpiMode.Mode0
14};
15
16using SpiDevice spi = SpiDevice.Create(settings);
17int ledCount = 30; // <-- set to your number of LEDs
18Ws2812b neo = new Ws2812b(spi, ledCount);
19
20// The driver exposes an Image<Rgb24> with width = ledCount, height = 1
21Image<Rgb24> img = neo.Image;
22
23Rgb24[] colors = new[]
24{
25 new Rgb24(255, 0, 0), // red
26 new Rgb24(0, 255, 0), // green
27 new Rgb24(0, 0, 255), // blue
28 new Rgb24(255, 255, 255) // white
29};
30
31Console.CancelKeyPress += (s, e) =>
32{
33 // Clear on exit
34 img.Mutate(c => c.Clear(new Rgb24(0, 0, 0)));
35 neo.Update();
36};
37
38int colorIndex = 0;
39while (true)
40{
41 // random color
42 Rgb24 current = colors[colorIndex % colors.Length];
43
44 // Set all pixels to the same color
45 img.Mutate(c => c.Clear(current));
46 neo.Update();
47
48 Console.WriteLine($"Showing: R{current.R} G{current.G} B{current.B}");
49 Thread.Sleep(1000);
50 colorIndex++;
51}
Note: If you update LED pixels very quickly, add a
Thread.Sleep(1)after the update. This introduces a tiny delay of about 50 µs but keeps the colors stable. Without it, many LED strips can’t keep up with the fast updates.
Publish a self-contained linux-arm64 binary (no .NET runtime needed on the Pi):
1dotnet publish -c Release -r linux-arm64 \
2 --self-contained true \
3 -p:PublishSingleFile=true \
4 -p:PublishTrimmed=false
Notes:
- Use
linux-armif your Pi OS is 32-bit. - We disable trimming because ImageSharp uses reflection.
Copy to the Pi and run (adjust host and path):
1scp bin/Release/net9.0/linux-arm64/publish/Ws2812Demo pi@raspberrypi:/home/pi/ws2812/Ws2812Demo
2ssh pi@raspberrypi
3chmod +x /home/pi/ws2812/Ws2812Demo
4cd /home/pi/ws2812
5./Ws2812Demo
If you see permission errors for SPI, ensure SPI is enabled and your user is in the spi group (see configuration section).
Power and wiring best practices
Sizing power correctly and protecting the data line will save you hours of debugging:
- Current budget: assume up to ~60 mA per LED at full white (R+G+B = 255). Example: 60 LEDs → ~3.6 A. Real usage is often lower, but size your 5 V supply with headroom.
- Inject power at both ends for long strips and use adequate wire gauge. Add a large electrolytic capacitor (e.g., 1000 µF, 6.3 V or higher) across 5 V and GND near the strip start.
- Put a 330–470 Ω series resistor in the DIN line close to the strip to damp edges.
- Observe arrow/direction on the strip: connect the Pi to DIN (not DOUT), and keep leads short.
- Common ground: always connect the Pi’s GND to the strip’s GND.
Logic level shifting (3.3 V → 5 V)
Most WS2812B strips expect a 5 V logic-high. Driving from the Pi’s 3.3 V sometimes works but isn’t guaranteed. Use a proper logic-level shifter with fast edges, such as:
- 74AHCT125, 74HCT14, or SN74HCT245 (powered at 5 V; input from Pi is 3.3 V)
- Avoid generic MOSFET I2C level shifters for this data path; they’re too slow for the WS2812B waveform.
Feed the shifter from the same 5 V as the strip, and keep the data wire short from the shifter to the first LED.
Animations and brightness control
You can control brightness by scaling color values before writing to the image. Here’s a tiny helper you can drop below the demo:
1static Rgb24 Scale(Rgb24 c, float brightness)
2{
3 brightness = Math.Clamp(brightness, 0f, 1f);
4 return new Rgb24(
5 (byte)MathF.Round(c.R * brightness),
6 (byte)MathF.Round(c.G * brightness),
7 (byte)MathF.Round(c.B * brightness));
8}
9
10// Example usage inside the loop
11float brightness = 0.2f; // 20% overall brightness
12Rgb24 current = colors[colorIndex % colors.Length];
13img.Mutate(x => x.Clear(Scale(current, brightness)));
14neo.Update();
Simple effects are easy: fill, wipe, chase, gradients, or a rotating rainbow. For more elaborate animations, render into img per frame and throttle updates with a small Thread.Sleep(1) to help latching.
Troubleshooting
- Flicker or wrong colors: lock
core_freq/core_freq_min(see above), verify SPI mode/frequency, keep wires short, and use a series resistor. - Nothing lights: check strip direction (DIN), common ground, and that your power supply can deliver enough current.
- Works at low brightness but glitches at white: power supply droop–thicker wires and power injection help.
- Random first LED behavior: add the large capacitor across 5 V/GND near the strip and ensure the level shifter is used.
What’s next
If you’re building more than a demo, consider making your code testable and adding unit tests around your animation/orchestration logic. See the follow-up article: /blog/2025/08/14/raspberry-ws2812b-lightstrip-dotnet-unit-testing/.
Conclusion
WS2812B LEDs are sensitive to timing. If your Pi’s GPU core frequency changes, you may see flicker or incorrect colors–locking core_freq and core_freq_min helps keep SPI timing stable. Also, treat power seriously: size the 5V supply for your total LEDs, add a large capacitor across 5V/GND, and use a series resistor on the data line.
Most strips expect a 5V data signal; the Pi outputs 3.3V. While 3.3V sometimes works, it’s not guaranteed. A proper 3.3V→5V logic level shifter makes the setup robust.
With SPI enabled, stable core frequency, and the .NET driver in place, you can build rich animations and effects entirely in C#.
Have fun lighting things up!

Comments