dotnet / iot

This repo includes .NET Core implementations for various IoT boards, chips, displays and PCBs.
MIT License
2.15k stars 581 forks source link

WS2812b (WS28xx?) does not work on Raspberry Pi 4B - bad colors #1901

Open maloo opened 2 years ago

maloo commented 2 years ago

Describe the bug

Colors are not what they should on Raspberry Pi 4.

It seems Raspberry Pi 4 (more?) introduce a one (1.5?) cycle delay between bytes. This mess up the timing of BitmapNeo3 format that send SPI 3bits per Neo pixel bit. 0b110 for one and 0b100 for zero. So when sending Red:

    0b110_110_11, 0b0_110_110_1, 0b10_110_110,
    0b100_100_10, 0b0_100_100_1, 0b00_100_100,
    0b100_100_10, 0b0_100_100_1, 0b00_100_100,

the 3rd and 6th triplet gets split over a byte boundary. The 0b110 become 0b1x10 and the x-delay is sent as 0, so we get 0b1010 instead of 0b110. This is out of spec and most likely interpreted by Neo as one or two 0s instead of a 1.

A workaround is to utilize the inter byte delay as a hardcoded 0 and for example send the following to get red:

    0b110_110_11, 0b110_110_11, 0b110_110_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,

I'm not sure if the driver in Linux can be tweeked to skip the inter byte delay. There is a .pad in the ioctl struct, but I'm not sure if the kernel driver use it. It does not seem like dotnet IoT use it which should mean it is set to 0. I have not tested any other HW, just the Raspberry Pi 4B

Steps to reproduce

Run WS2812b sample and hardcode color to red.

Expected behavior

Red color on Nep pixel

Actual behavior

Orangish color on No pixel

Versions used

dotnet 6 - but probably irrelevant.

joperezr commented 2 years ago

Thanks for logging the issue @maloo. We have been getting some reports lately about the colors on this driver, and I believe @Ellerbach was going to take a look to see what was happening. @Ellerbach can you please also take a look at this one?

jesseryoung commented 2 years ago

Could this be related to https://github.com/dotnet/iot/issues/1328?

HumJ0218 commented 2 years ago

The color issues with the WS2812B have always existed, so I considered making a new version of the driver #1761. But because of the problem of how to define the pixel color format, this PR has been on hold. If you're in a hurry to solve this issue, consider using my code to solve the problem for the time being.

maloo commented 2 years ago

The specialized formats of neo via SPi will never be supported by major Gfx libs like image sharp. So you will always need a conversion step from these to the neo native formats. A span of bytes is probably the best common layer between apis as this can be created from native and dotnet memory of all sorts. If you render in for example ImageSharp 24bit and then add a conversion to BitmapImage you probably solve your rendering. As for the Neo3 format, that might need to be updated to support Pi4s where there is a delay in between bytes. Do you know if the same delay exists in Pi2B etc? I have been using this driver for long time on Pi3B+ without issue (at fixed 250MHz core clock) and now with the above fix on Pi4B. I've used 1x and 8x Neo.

krwq commented 1 year ago

[Triage] nano framework might have a fix for this. @Ellerbach to provide more info

maloo commented 1 year ago

Let me know if you want the fix I described above. It is used in production on both Pi3B+ and Pi4B+. But if there is a way to get rid of the delay between bytes, that would be better.

krwq commented 6 months ago

[Triage] Hey @maloo, sorry for late response, if your offer is still valid we'd be happy to take a fix 😄

maloo commented 6 months ago

I'm a bit busy at the moment, can I just upload the file here? Or post the relevant part of the code?

pgrawehr commented 6 months ago

Sure, if you think you know how to fix it, we're happy for anything you can provide.

maloo commented 6 months ago

Can't seem to find the updated driver file, but I found some code I use on RaspPi 4s.

TL;DR Align data so each bit (hi-lo) is sent as 3 bits, assume an implicit 0 between each byte (the delay between bytes in Pi4), so 100 = 0, 110 = 1, then add 00 at the end to pad.

Half bright colors (0b0111_1111 => 0b100_110_11, 0b110_110_11, 0b110_110_00)

var red = new byte[] {
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
};

var green = new byte[] {
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
};

var blue = new byte[] {
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
};

var cyan = new byte[] {
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
};

var magenta = new byte[] {
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
};

var black = new byte[] {
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
};

var yellow = new byte[] {
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_110_11, 0b110_110_11, 0b110_110_00,
    0b100_100_10, 0b100_100_10, 0b100_100_00,
};

      SpiConnectionSettings settings = new(0, 0) {
          ClockFrequency = 2_400_000,
          Mode = SpiMode.Mode0,
          DataBitLength = 8
      };
      using SpiDevice spi = SpiDevice.Create(settings);
      spi.Write(black);