Hacker News new | past | comments | ask | show | jobs | submit login
Rust, a Raspberry Pi and Addressable LEDs (tomarrell.com)
119 points by slicedbrandy 11 days ago | hide | past | web | favorite | 31 comments

So I finally got around to writing about a project using the Raspberry Pi to power a panel of WS2812b LED's.

Let me know what you guys think.

Thanks for sharing.

One question about the code, why does the LedPanel struct use a String as a buffer rather than using a Vec<u8>?

edit - I think I see the reason now. Using a string made it easy to print the u8 as a string of bits, which you could then iterate over using chars. This was used to help perform the conversion from bits directly representing colors to the on the wire representation.

That would certainly work as well, and ultimately it does get converted into a Vec<u8>. I did consider just having the buffer as it from the start, but by using a String it was just a little more intuitive when it came to pushing more bits onto the buffer, as well as doing the padding operation at the end. More just to reduce complexity during development than anything I guess.

I may have overlooked some things, but I made a quick PR[0] just to see what this would look like using a Vec<u8>. Feel free to close if you prefer the original.

[0]: https://github.com/tomarrell/rasp-ws2812b/pull/2

Just FYI, since I've implemented something similar in Swift, using the PWM you would have had to implement more or less the same algorithm, i.e. changing the duty cycle of an high frequency signal to generate these "slot-based" patterns that identify the 0s and 1s of the WS281x (there are minimal timing differences between the variants) protocol.

Note: God bless the Oscilloscope, essential for these projects.

Unrelated, but how did you draw your curves/charts? They look fantastic.

Thanks! I drew them using Notability on an iPad, then exported the document as a jpeg and cropped them out.

Really cool! Thanks also for taking the time to take photos and add figures.

Very cool!

We built a similar WS2812b array at university for an embedded systems design project: https://github.com/joe-wright/wiisel

We looked at using a Raspberry Pi but instead settled on an mbed FRDM-KL25Z so we had more control over the signal timing. The trade-off was that the KL25Z only had 16KB memory instead of the Pi's 512MB, so getting everything to fit took a bit of creativity and a few late nights...

For that to work correctly, the piece of code generating the signal should never be interrupted.

To my knowledge, this is not possible with a Raspberry Pi. You should see glitches on the output when your loop generating the 1s and 0s is interrupted.

That may be ok it if you just when to send one static light pattern but the glitches should be more evident if you want to create beautiful, flowing light patterns.

That's why it's better to drive those WS2812b with an Arduino that you code bare metal.

APA102 is strictly better than WS2812B (standard SPI, much less sensitive to timing, way less PWM flicker, similar price/availability).

When you still want bare metal, pick a modern uC like ESP8266 (cheap, 32 bit, integrated WiFi for trivial connectivity) instead of "classic" overpriced, outdated 8/16 bit AVR (Attiny/Atmega) based Arduino hardware.

Arduino the software platform still works great of course, but so does NodeMCU which is the go to stack on ESP8266.

I'd recommend looking at some of the ARM dev boards instead of the ESP8266 for general purpose stuff and if you need the wifi, the ESP32. This is mostly because the ESP8266 is missing a lot of hardware functionality that you'll find in the ESP32 or any arm boards. In particular UARTs (serial ports), I2C, and SPI periphrials that can make working with other things like the APA102 a lot nicer to code for. With the arm chips you usually even can setup the SPI transfers as DMA so that you don't have to actually do any work on the cpu itself to drive the lights.

That said the ESP8266 still has a place because of how cheap you can usually find them. Lowest price arm board that i know of from a manufacturer is the Teensy LC at ~$12USD, but there are cheaper ones out there on ebay and aliexpress. I just can't say anything about how well they work or are supported by anyone.


For the specific purpose of controlling APA102 via wifi, ESP8266 (NodeMCU or similar) seems optimal, as 1x SPI and WiFi are the only peripherals needed and so ESP8266 is a perfect fit hardware-wise, and it's well-supported, extremely beginner friendly (<25 easy lines of code to get these LEDs blinking via WiFi, mouse-click based library download) and cheap.

In general, for intermediate users and up (anyone not lost right away without peripheral libraries from SparkFun/AdaFruit), and unless Wifi is needed, I fully agree on ARM. I'd recommend the STM32 portfolio. These scale really well from learning platform to full on commercial use (with the F0/F1/F4/F7 MCU's, stm32duino/libopencm3/mbed frameworks, Arduino/PlatformIO dev environments, all in order of complexity and/or professionalism). The STM32F* eval boards are nice but pricy; there are fewer universally useful cheap small-formfactor (ie Arduino-like) boards.

I bought a pair of esp8266 off Amazon for about £10 recently; they both look like some sort of knock-off, the PCBs are unbranded and the soldering looks somewhat less than perfect in places - however, both devices function perfectly.

(WIP modular synth sequencer: https://photos.app.goo.gl/PcMqnUThygRG3T8v7 )

I also have since bought a couple of ESP32 and think they are awesome.

Check the regulators on those cheap boards, they're often well under spec and cause brown-out reboots when the wifi gets working.

If you don't use the more power hungry board features they still work fine.

The ESP32 is great. I love this board.

The only thing that is missing is good Rust support.

IIRC, Espressif (the company behind the ESP32) is working on a clang/LLVM port to the Xtensa architecture, which would enable a Rust toolchain.

That would be a dream come true for me, but I've been hearing that for a few years, so I don't have much hope.

They do have an issue for merging their fork back into upstream LLVM in their repo: https://github.com/espressif/llvm-xtensa/issues/8

Not sure how fast that's gonna happen, but one may hope!

Arduino's still have a use, Atmega/Attiny in general, the chips are much cheaper, especially if you don't need connectivity.

Unfortunately this is a "middlebrow dismissal": you know a fact, but not the whole context, and therefore end up claiming that something won't work when in practice it works fine. The code uses the SPI peripheral, it's not generating the 1s and 0s individually but handing a buffer of them over to the OS which will DMA them out.

You do need the buffer to never underrun though, no?

That having been said, if this works reliably, it's fantastic. The ESP32 is pretty great, but controlling WS2812b LEDs from a Pi Zero would be extremely convenient as well.

It only matters if the underrun is in the middle of a message, and the messages are short enough to fit in the buffer whole. I'm sure there are already libraries for driving WS2812b from Pi, such as from Adafruit. Certainly the WS2801 library works.

It’s not very hard to avoid under-runs with a ~125MHz SPI core vs a 1.5GHz quad core cpu.

It’s possible, using the isolcpus kernel command line argument, to prevent the scheduler from managing certain CPU cores.

You can then manually assign a thread to an isolated core, and it will run there without any interruptions from thread switching.

You can also map interrupt handlers away from those isolated CPUs.

There may be architectural things that cause interruptions you can’t mitigate though (like SMIs on Intel).

This uses hardware SPI module running at 3MHz. Those modules are buffered, and as long as there are no underruns, there will no glitches at all.

And the userspace sends entire buffer in one syscall, so those underruns will have to be caused by kernel stall, which usually only happens when buggy device drivers are present.

That seems like the right way to do it, but this piece in the article is a bit unclear:

> Now taking a look at the duties of the PWM signal we need to send, we can approximate the highs and lows to some multiple of 1/3. This means that if we run a MOSI SPI signal at triple the frequency the lights expect, then we can send a single WS2812b "panel bit" using a combination of 3 SPI bits.

OP should consider installing a Xenomai kernel on his Pi, to handle the signal output in hard real time while still providing a nice Linux-based interface to his program.

I think the RPi output ports should be equipped with a buffer. It could work just like the buffered output of audio signals, so if you write reasonably quickly, then there will be no loss in the signal. Of course, there will be some latency, but in many applications (including this one), that will not be a problem.

The article mentions that they attempted bit banging first, but it didn't work precisely for the reasons you mention, which is why they used SPI instead.

You need to use the timer to bit bang - which may require writing a device driver

Source: wrote a software UART for an embedded project

Registration is open for Startup School 2019. Classes start July 22nd.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact