626Pilot / RaspberryPi-NeoPixel-WS2812

Library for driving WS2812 pixels (also known as "NeoPixels" when sold by Adafruit) from a Raspberry Pi.
132 stars 26 forks source link

Refactor for inclusion in Hyperion #2

Closed tvdzwan closed 10 years ago

tvdzwan commented 10 years ago

I have reviewed the code and I have to admit that it is some dense code. Kudos for getting it to work. I have tried compiling and running and everything seems to work (I only do not have any leds attached because I need to re-solder some connections for that).

To use it in Hyperion the code requires some thorough refactoring though. Hyperion is written in C++ and I like to keep it like that ;-). What are you thoughts about this? How do you see the cooperation between RaspberryPi-NeoPixel-WS2812 and Hyperion?

626Pilot commented 10 years ago

I think the way to go on that would be to turn the program into a userspace FIFO daemon, like ServoBlaster, so you would write color triplets to /dev/something and they would show up. This would mean you don't have to worry about licensing (this program is GPL2 because of what it's based on) or making your users run Hyperion as root (which this program can't do without). You also wouldn't have to wrap the code in classes to make it "proper" C++.

My own plan is to code in C#, talking to this program either through a native wrapper or through the FIFO. If you require some integration help, I'll answer whatever questions you have to the best of my abilities.

tvdzwan commented 10 years ago

We have been discussing a generic device that would simply write the colors to a file (or a pipe). This could also be used in this case.

On the other hand I would like to include the code in Hyperion, allowing users to simply install Hyperion and 'go go go'.

626Pilot commented 10 years ago

You could write an installer script that downloads the FIFO daemon from GitHub and enables it at boot through /etc/rc.local or similar. To use the code in Hyperion is OK from my standpoint (in that I don't object), although I can't change the license (since the code is adapted from others' work), and you would have to run Hyperion as root. If it runs as a daemon, you don't have to care about the license or what privileges it has.

I have to resume work on some other projects, so I won't get around to making this into a FIFO daemon for a little while.

tvdzwan commented 10 years ago

I finally got around to re-soldering some wires. Unfortunately the setup does not work. And before you blame my soldering skills, I also tested them on an arduino and they are working ;-). Could it be that we are not using the same leds? Are you using the ws2812b or just the ws2812 (they can be recognised by the number of pins on the led plus controller. 4 for the ws2812b and 6 for the ws2812)? They might require different timing of the signal, but I could not directly deduce the signal timing from the code.

626Pilot commented 10 years ago

The SMT chips have six legs. As far as I'm aware there are the ws2811 chips (requiring a 400KHz signal) and the ws2812 chips (requiring 800KHz.) The 2812s are said to be compatible with WS2811 timing, but the data sheet from the manufacturer says one thing and the docs on the Adafruit site - https://learn.adafruit.com/adafruit-neopixel-uberguide - says there's a math error in the datasheet.

One thing I've read on the forums is that some of the '12 LED rings will run fine at 5V power and 3.3V signal, but others are "grumpy" and won't - they need a level shifter to convert the 3.3V signal to 5V (or maybe just a transistor?) My LED ring, bought a couple months ago, can run at 5V power and 3.3V signal. However, I also ran it at 3.3V power + signal and it was fine. Have you tried running yours at 3.3V power? Which pin is the data line on, and do you have resistor on that pin, and if so what's the resistance? Adafruit suggests a weak resistor (on the order of 100-400 ohms I think) to protect the first pixel from ringing in the signal. Do you have an electrolytic capacitor across the power lines, and if so, what is the capacitance?

tvdzwan commented 10 years ago

The differences are little bit more complicated. The ws2812 is basically three leds (green, red, blue) with the ws2811 chip on top of them, making for a single component. The ws2811 chip can be used in normal (400kb/s) or high speed (800kb/s) mode. The ws2812 has preselected one of these modes (I thought it could be either). The ws2812b is always the high speed. I am still looking to see how the data rate is controlled in the code. Do you know how the output datarate is configured? Note that the required signal is at least 3 times higher then the required datarate (3 bits are the minimum to make a single output bit).

For the hardware setup: I have no extra components, no resistor or capacitor. As said, I have tested the leds using an arduino and they worked fine. I will have a look and try and find a resistor and capacitor but I am less then hopeful it will help. Next week, if I can find the time at work, I will hook up a scope and check the output signal.

626Pilot commented 10 years ago

The capacitor is to smooth the LEDs. They can fluctuate in brightness at high output without a cap. The resistor is to protect the first pixel from being destroyed by ringing as the logic transitions between high and low. The rings will run without either component.

Data is assembled bitwise into a waveform such that 1=high-high-low and 0=high-low-low, so one wire bit takes 3 RAM bits. (You can see this in the show() function.) The data rate is controlled by the clock divisor, currently 400. That's what decides how fast the pulses are sent. This goes through the RAM bits once every 400nS, so it takes 1,200nS to send one wire bit. The resulting signal is 833.333KHz. (The pulses for T1H, T1L, T0H, and T0L have a tolerance of +-150nS, so even though 800KHz is specified, 833,333KHz is fine. It also makes the code a lot simpler, as we can use symmetric timeslices for all the pulses.)

tvdzwan commented 10 years ago

I got it 'working'. I mis interpreted the pin 18 as pin number 18 instead of GPIO18. This made something happen on the lights. However the colors do not yet match up neatly. There seems to be some misalignment in the PWM-buffer, which I have not been able to pin point. If you set the brightness to 100% (leave the colors as provided) and then send: RGB(255,0,0) => Led is purple RGB(1,0,0) => Led is blue RGB(0,1,0) => Led is red All bits seem to have shifted to the left (also across leds). If I use the dumpPWMBuffer function, the output is not what I would have expected. Hopefully I have some more time this weekend to investigate the cause of the issue.

BTW: There is probably a much more efficient way for translating a single byte to a PWM of 3 bytes. I am thinking along the lines of a simple lookup table.

626Pilot commented 10 years ago

It sounds like you have some different revision of neopixels than I do. When did you order them? Did they come from Adafruit? I got mine from them a couple months ago. Maybe they take data in RGB order? There are some ones that do that according to the Adafruit Arduino library.

The code has been running the demo on my Raspberry Pi since I first published it, and it never misses a frame or gets anything misaligned. If your pixels are responding strangely, I would doubt it's an issue with the PWM buffer, but rather how the bits are being encoded. Take a look at line 1140, where it decodes the color into its component bits:

colorBits = ((unsigned int)LEDBuffer[i].r << 8) | ((unsigned int)LEDBuffer[i].g << 16) | LEDBuffer[i].b;

It encodes in GRB order. Try this instead:

colorBits = ((unsigned int)LEDBuffer[i].r << 16) | ((unsigned int)LEDBuffer[i].g << 8) | LEDBuffer[i].b;

If it doesn't work, at least what it does differently might produce some insight.

tvdzwan commented 10 years ago

I finally got somethings working consistently. The byte order of the colors is not the issue. My theory is that there seems to be a short signal before the actual start of the encoded PWM signal. This would trigger the major-bit of the first led (with a zero) and consequently swap all the rest of the data by one. I still have to confirm this by using a scope. It could be that my leds are slightly more sensitive or that adding a resistor in the data line makes a difference and that is why you do no experience this problem. After hooking up a scope somewhere next week I will test with an extra resistor in the dataline. A workaround/solution is starting the PWM at low signal for the first few samples.

626Pilot commented 10 years ago

Looks like this code is up and running in Hyperion. Cheers!