Controlling WS2812 device (RGB LED strip)
I have this WS2812 RGB LED strip (aka NeoPixels), and WS2812 requires fairly strict timing. The problem is that high-level Python code cannot really switch the output level precisely enough, so I need to find some way how to make sure no other process can interrupt while sending out the WS2812 data.
I've seen examples how to talk to WS2812 devices via SPI, using bitbanging. An on-board timer chip takes care for timing, and firmware can push the stream of bytes to the port while hardware makes sure the bits flow out at perfect rate.
If I could build a low-level command line utility that holds all interrupts, streams the data passed via parameter, and returns control to OS, that would be something I could call from my Python code. I'll be happy for any suggestions how that could be achieved.
Unfortunately the SPI pins do not have breakouts on the Omega since it is used to communicate with the flash chip.
Maybe you can bit-bang using regular GPIOs? The Omega has General Purpose and Watchdog timers that you should be able to use to get the timing down.
However, an easier option might be to use the Arduino Dock since the ATmega can very precisely control its output timing with probably less hassle than the above. Then you can use the Omega to send the Arduino chip instructions on what colors to set and the Arduino chip will do all of the low-level stuff.
Both methods are cool though! Let me know how it goes!
I'd first like to go with minimal hardware. Also, the comms with Arduino would go via sertial tx/rx pins, and if there are many LEDs to control, the throughput might become problem. With 300 LEDs at update speed 60 times a second, there are 3 * 300 * 60 * 8 = 432000 bits/second to transfer.
I am a bit at loss where to start, with using a digital GPIO pin for bit-banging. You mention General Purpose and Watchdog timers. Can you, please, guide me to some C++ code that properly inits those timers and interrupts and writes to the output port?
@Pavils-Jurjans While I can't go in to long detail here (largely because I am not an expert in this) I would suggest you look at the Posix Threads library. This is available on the Omega and with this it is possible to created threaded code with timers.
If you are not familiar with Posix Threads, a couple of pointers that may help get you started:
Also a couple of references using Posix Thread based timers:
I am currently using Posix Threads with usleep calls to provide some timing capabilities.
However, my general preference is to use Java which has easy to use built in Classes for running timers and threads. You can run java on the Omega using Jamvm- I am writing Java wrappers to access the Omega GPIO so that I can stick with just using Java (see https://community.onion.io/topic/36/java-on-omega-with-wrappers)
@Pavils-Jurjans I've only done some light research into the Timers, don't have any code as yet. We are planning to create a NeoPixel library but that's sometime down the road.
I would say, try to experiment with the Posix Threads that @Kit-Bishop mentioned. I'll try to do some more research into the timers and get back to you in a few days.
Ok, I think I will move on with first driving the strip from Arduino Micro, connected to Omega via serial. That will get me started sooner, as I see the "hard way" requires some extra tinkering without guaranteed results too soon. But the lights must go up or it will be too late. :)
Once that fallback setup will be working, I will have the safety for tinkering with lower level Linux code where I don't feel so much at home.
Thanks for suggestions, and I will report back my results!
@Pavils-Jurjans Have you looked into how it has been attempted on a RaspPi? There is this tutorial on Adafruit that uses a library that is using PWM and DMA. Might give you some ideas. https://learn.adafruit.com/neopixels-on-raspberry-pi/overview
Was going to have a look at neopixels, have used the library mentioned to power a few neopixels from a RasPi for a project. In my project I had a powered hub so just took a 5v USB feed for the power.
@Sawyer-McBride We are working on a neopixel library. We've been wanting to release it earlier so people can decorate christmas trees with it, lol. It'll be finished soon! Stay tuned!
@Boken-Lin Now this im interested in! My wife has been looking at LED door weaths but I said I wanted to make one, possibly with an Arduino....but why not the omega! Get it in a "production" environment :smirk:
Ok, here's an update. To get the feels, here's a photo of my kids messing with LED strip color config, using their smartphones. Competing with their programming, how cool is that.
The device-tech stack:
- 150 pixel RGB LED strip (similar to this one)
- Arduino Micro talks WS2812 to the strip
- Onion Omega talks to Arduino over TTY serial (custom protocol)
- Custom Python module for talking the custom protocol over serial connection
- Python script using BaseHTTPServer to allow the strip control over webservice
- A web client (ie smartphone) where the interface is opened
The LED strip, Arduino and Omega are all powered from one 5V 10A 50W power converter.
Ideally, I'd like to top the stack with a Blockly -enabled coding tool that allows writing scripts for controlling the strip.
@Pavils-Jurjans Nice! We are working on getting Scratch to work with the Omega as a Console App. Blockly is very nice as well. It shouldn't be too different to add to the Console.
Scratch is a great way to introduce kids to programming, indeed. But unfortunately it is Adobe Flash based, and the trend is that Flash is bad (mainly due to security issues, but also because there are new better open standards like HTML5 to replace it), and it is being phased out. From what I can find, there appears to be no serious plan to migrate the Scratch platform to HTML5 right now. So, please consider twice before investing too much effort in this.
@Pavils-Jurjans That's a good suggestion. To keep the console future-friendly, it would make sense to put a higher priority on Blockly. We will get started on getting Blockly to work on the Console shortly.
There's another problem what I encountered with Blockly - it's the storage space. Even fairly minimal package is above 1MB. In my case, when I have Python installed, it was too much for the 16MB storage space.
@Pavils-Jurjans It might be necessary to extend the storage of the Omega with rootfs. For more information please check out this wiki: https://wiki.onion.io/Tutorials/Using-USB-Storage-as-Rootfs.
@administrators Theres still no way to control ws2812 leds without needing an arduino too?
@Joseph-Sammarco We are still working on that. It's a bit harder than we thought, but we want it just as badly as you do, so we won't stop until it's done :)
@Pavils-Jurjans Did you see the post on hackaday, where some guy figured out a way to send precisely timed VGA signals to the led strip? It was pretty dope, and, if there is a way to send analog frequencies in the same manner over any combo of pins, it'd be interesting to simplify what you are doing to get it under the time constraints needed.
Alternatively: precise digital timing could work.
I found this driver just now, in a second round of googling the topic in the evening - as my hardware is in the office, I could not try it yet.
But before, I already spent the afternoon experimenting with bitbanging the WS2812 timing on the Onion and measuring the results on a scope.
With interrupts disabled, the timing (350nS pulse for a 0 bit, 700nS for a 1 bit) can be met quite easily, especially when you know that the specified pause time between bit pulses (600/800nS) is a minimum, not an exact requirement, contrary to what the worldsemi datasheet says. The pause length must only be kept well below the reset time of 50µS.
However, I found keeping the pauses reliably below 50µS is not possible without blocking the interrupts during the entire WS2812 chain update, which amounts to multiple milliseconds for an average 200 LED chain. Doing so will certainly ruin the accuracy of hrtimers etc. But apparently, one can usually get away with that - the ws2812_draiveris driver from github implements it that way.
I'll try that driver tomorrow, however I'm a bit reluctant to actually using it, because I have other drivers relying on hrtimer accuracy in the 500µS range. Keeping IRQs blocked for so long is certainly not good kernel programming practice.
On the other hand, are there any better options? To meet the timing without blocking IRQs, we'd need a DMA based solution. Maybe i2s or SLIC can be misused to produce the WS2812 timing somehow?