Close

The RPI SPI1 I/O Tower and WS2812b

A project log for Tron Identity Disc upgrade.1

Upgrade a Tron Identity Disc with: RPI Zero, WS2812b LEDs, Sound, Gyro/Accel, USB storage/networking/charging, Wi-Fi, haptic feedback...

dan-rogahnDan Rogahn 03/05/2017 at 11:180 Comments

First: Many Thanks to Tim's Blog for great work on Understanding the WS2812.

Short version is, I now have a basic & good enough WS2812b SPI driver in Python, working with the RPI SPI1. But it was pretty rough. I ran into some really weird issues...

I'm packing 2 LED bits into 6 SPI hardware bits (padded to 8 SPI bits) at around 2.4 - 2.8 MHz.

LED 0 bit = 0b100 = 0x4
LED 1 bit = 0b110 = 0x6
LED 0x7F = 0x46, 0x66, 0x66, 0x66

Efficiency might prefer 5 into 15 bits (or 10 into 30). But apparently the spi_bcm2835aux driver doesn't handle any bit length besides 8. The SpiDev doc says 8-16, and Broadcom docs say 1-24/32 bits.
(I looked at SpiDev source, it looks like it is trying to pass the bit length along)

SPI0 seemed to work great - it has DMA (but this project will be using SPI0 for SDIO).

Switching to SPI1, the problems start... SPI1 does not have DMA.
The first write to SPI1 after opening the SPI1 device was always corrupt. Temporarily fixed by padding the start with zeros.
Adding multiple updates: There were a lot of glitches, a few per second.

But a HUGE problem was: after 5-6 seconds (sometimes less) of the script running, the LEDs would get stuck on bright white. Not the whole strip, just the ones I'm controlling. Nothing changes it: number of LEDs driven, update frequency, open/close the SPI device in the update loop, checked voltages, twiddled timings.... Only re-running the program resets the LEDs for another several seconds.
And SPI0 had the same problem!

I had to sleep on it.

A sleep(4) at the start of the script (before opening the spi device) would give me 2 seconds of good LEDs -- so somehow, seems related to program runtime.
Replacing the sleep in the update with a busy loop was a successful, but inefficient, workaround.
Nice/Priority didn't change much (maybe made it worse).

I looked into CPU speed

sudo cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
The frequency ramps up to load python, and then dials back several seconds later under the low load.
The default govenor (which controls the frequency) is "ondemand": PI idles at 700MHz and goes to 1G at load.
Updating 9 LEDs at about 30 fps (with sleep) takes about 0.9% cpu. (not much demand)

Switching to "performance" (always 1GHz) fixes just about everything: the 6 sec problem, the first write, less glitches (about 1 per 10 sec).

echo performance | sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
But it's not the frequcy change that's the problem -- it's the actual frequency.
Switching to "userspace" govenor, so I can set a static frequency "scaling_setspeed" -- 700000 is bad, but 700001 is OK.
That might be an SPI driver timing bug.

Tweaking:

It seems that spi.xfer() takes slightly longer when glitches occur -- presumably due to a context switch.
(writebytes() seems to always take the same time)

Using that as a filter, the script can immediately re-send the LED data -- theoretically reducing the visible glitches (but some still get through). It doesn't seem to get worse under high cpu load (tested with "stress -c 1")

The current result is approximately one glitch every 20-30 sec -- but there can be bursts of glitches for unknown reasons. (For now, just think of of it like the brightness variation in the original Tron -- or like Max Headroom glitches).

Discussions