/LEDscape

Beagle Bone Black cape and firmware for driving a large number of WS281x LED strips.

Primary LanguageC

Overview

LEDscape is a library and service for controlling individually addressable LEDs from a Beagle Bone Black using the onboard PRUs. It currently supports WS281x (WS2811, WS2812, WS2812b), WS2801 and initial support for DMX. It is designed to be used with level-shifter capes like those produced by RGB-123.

Background

LEDscape was originally written by Trammell Hudson (http://trmm.net/Category:LEDscape) for controlling WS2811-based LEDs. Since his original work, his version (https://github.com/osresearch/LEDscape) has been repurposed to drive a different type of LED panel (e.g. http://www.adafruit.com/products/420).

This version of the library was forked from his original WS2811 work. Various improvements have been made in the attempt to make an accessible and powerful LED driver based on the BBB. Many thanks to Trammell for his excellent work in scaffolding the BBB and PRUs for driving LEDs.

WARNING

This code works with the PRU units on the Beagle Bone and can easily cause hard crashes. It is still being debugged and developed. Be careful hot-plugging things into the headers -- it is possible to damage the pin drivers and cause problems in the ARM, especially if there are +5V signals involved.

Installation and Usage - Command Line

It is necessary to have access to a shell onto the Beaglebone Black using serial, ethernet, or USB connections.
Examples on how to do this can be found at BeagleBoard.org or at Adafruit's Learning Site.

To use LEDscape, download it to your BeagleBone Black by connecting the BBB to the internet via ethernet and cloning this github repository. Before LEDscape will function, you will need to replace the device tree file, load the
uio_pruss, and reboot by executing the commands listed below via the cmd line.

Angstrom - RevB

git clone git://github.com/Yona-Appletree/LEDscape
cd LEDscape
cp /boot/am335x-boneblack.dtb{,.preledscape_bk}
cp am335x-boneblack.dtb /boot/
modprobe uio_pruss
vi /boot/uboot/uEnv.txt
reboot
sudo ./install-service.sh

Debian - RevC (2014-04-23)

git clone git://github.com/Yona-Appletree/LEDscape
cd LEDscape
cp /boot/uboot/dtbs/am335x-boneblack.dtb{,.preledscape_bk}
cp am335x-boneblack.dtb /boot/uboot/dtbs/
modprobe uio_pruss
vi /boot/uboot/uEnv.txt 
reboot

Debian - RevC (2015-03-01)

git clone git://github.com/Yona-Appletree/LEDscape
cd LEDscape
cp /boot/dtbs/3.8.13-bone70/am335x-boneblack.dtb{,.preledscape_bk}
cp am335x-boneblack.dtb /boot/dtbs/3.8.13-bone70/
modprobe uio_pruss	
vi /boot/uEnv.txt
reboot

After rebooting you will need to enter the LEDscape folder and compile the LEDscape code.

cd LEDscape
make
sudo ./install-service.sh

Note: Locating the am335x-boneblack.dtb file:

  • Older BBB have the file in /boot;
  • Some distros (e.g. Arch) keep these files in /boot/dtbs;
  • The Debian distribution keeps the file in /boot/uboot/dtbs (when mounted over USB, the /boot/uboot directory is read-only from the BBB and you need to do the file operations from the host system.

Disabling HDMI

For LEDscape to run properly you'll have to disable the HDMI "cape" on the BeagleBone Black.

Mount the FAT32 partition, either through linux on the BeagleBone or by plugging the USB into a computer, modify 'uEnv.txt' by changing:

Using vi

vi /boot/uboot/uEnv.txt

Angstrom - RevB

capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

It should read something like

optargs=quiet drm.debug=7 capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

Debian - RevB

##Disable HDMI
#cape_disable=capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

Change to

##Disable HDMI
cape_disable=capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

Save and Reboot the BeagleBone Black.

Open Pixel Control Server

Setup

Once you have LEDscape sending data to your pixels, you will probably want to use the opc-server server which accepts Open Pixel Control data and passes it on to LEDscape. There is an systemd service file built to run LEDscape from it's home directory. Simple install/uninstall scripts are provided:

sudo ./install-service.sh

If you would prefer to run the receiver startup script without adding it as a service:

sudo ./run-ledscape

Configuration

By default LEDscape is configured for strings of 256 WS2811 pixels, accepting OPC data on port 7890. You can adjust this by editing run-ledscape and editing the parameters to opc-server

Data Format

The opc-server server accepts data on OPC channel 0. It expects the data for each LED strip concatonated together. This is done because LEDscape requires that data for all strips be present at once before flushing data data out to the LEDs.

opc-server supports both TCP and UDP data packets. The TCP port is specified with --tcp-port <port> and the UDP port with --udp-port <port>. Entering 0 for a port number will disable that server.

Note that if using the UDP server, opc-server will limit the number of pixels to 21835, or 454 pixels per port if using all 48 ports.

Output Modes

LEDscape is capable of outputting several types of signal. By default, a ws2811-compatible signal is generated. The output mode can be specified with the --mode <mode-id> parameter. A list of available modes and their descriptions can be obtained by running opc-server -h.

Frame Rates for WS2812 Leds

512 per channel ~= 060 fps
256 per channel ~= 120 fps
128 per channel ~= 240 fps
064 per channel ~= 400 fps

Pin Mappings

Each output mode of LEDscape is compatible with several different pin mappings. These pin-mappings are declared in pru/mappings as json files and each contain information about the mapping. They can be provided to LEDscape with the --mapping <mapping-id> parameter, where <mapping-id> is the filename of the json file without it's extension.

The mappings are designed for use with various different cape configurations to simplify the hardware designed. Additional mappings can be created by adding new json files to the pru/mappings directory and rebuilding.

A human-readable pinout for a mapping can be generated by running

node pru/pinmap.js --mapping <mapping-id>

Multi-Pin Channels

Some mappings, such as ws2801, use multiple pins to output each channel. In these cases, fewer than the full 48 channels are available. In the case of ws2801, each channel uses two pins, DATA on the first pin and CLOCK on the next. Only 24 channels of output are available and to reduce CPU usage, opc-server should be called with --strip-count 24 or lower.

Output Features

opc-server supports Fadecandy-inspired temporal dithering and interpolation to enhance the smoothness of the output data. By default, it will apply a luminance curve, interpolate and dither input data at the highest framerate possible with the given number of LEDs.

These options can be configured by command-line switches that are documented in the help output from opc-server -h.

To disable all signal enhancements, use opc-server -lut

Invocation Examples

Configuration opc-server Invocation
32 strips, 64 pixels, ws2811 ./opc-server --strip-count 32 --count 64 --mode ws281x
24 strips, 512 pixels, ws2801 ./opc-server --strip-count 24 --count 512 --mode ws2801
8 outputs, 170 pixels, dmx ./opc-server --strip-count 8 --count 170 --mode dmx

JSON Configuration

Use the command below to create and execute the JSON configuration

./opc-server --config ws281x-config.json --mapping rgb-123-v2 --mode ws281x --count 64 --strip-count 48

With this JSON configured it can be called again by issuing the command

./opc-server --config ws281x-config.json

Processing Example

LEDscape provides versions of the FadeCandy processing examples modified to work better with LEDscape in the processing directory. Clone this repo on a computer and run these sketches, edited to point at your BBB hostname or ip address after starting opc-server or installing the system service.

Hardware Tips

Connecting the LEDs to the correct pins and level-shifting the voltages to 5v can be quite complex when using many output ports of the BBB.

While there may be others, RGB123 makes an excellent 24/48 pin cape designed specifically for this version of LEDscape: 24 pin or 48 pin

If you do not use a cape, refer to the pin mapping section below and remember that the BBB outputs data at 3.3v. If you run your LEDs at 5v (which most are), you will need to use a level-shifter of some sort. Adafruit has a decent one which works well. For custom circuit boards we recommend the TI SN74LV245.

API

ledscape.h defines the API. The key components are:

ledscape_t * ledscape_init(unsigned num_pixels)
ledscape_frame_t * ledscape_frame(ledscape_t*, unsigned frame_num);
ledscape_draw(ledscape_t*, unsigned frame_num);
unsigned ledscape_wait(ledscape_t*)

You can double buffer like this:

const int num_pixels = 256;
ledscape_t * const leds = ledscape_init(num_pixels);

unsigned i = 0;
while (1)
{
	// Alternate frame buffers on each draw command
	const unsigned frame_num = i++ % 2;
	ledscape_frame_t * const frame
		= ledscape_frame(leds, frame_num);

	render(frame);

	// wait for the previous frame to finish;
	ledscape_wait(leds);
	ledscape_draw(leds, frame_num);
}

ledscape_close(leds);

The 24-bit RGB data to be displayed is laid out with BRGA format, since that is how it will be translated during the clock out from the PRU. The frame buffer is stored as a "strip-major" array of pixels.

typedef struct {
	uint8_t b;
	uint8_t r;
	uint8_t g;
	uint8_t a;
} __attribute__((__packed__)) ledscape_pixel_t;

typedef struct {
	ledscape_pixel_t strip[32];
} __attribute__((__packed__)) ledscape_frame_t;

Low level API

If you want to poke at the PRU directly, there is a command structure shared in PRU DRAM that holds a pointer to the current frame buffer, the length in pixels, a command byte and a response byte. Once the PRU has cleared the command byte you are free to re-write the dma address or number of pixels.

typedef struct
{
	// in the DDR shared with the PRU
	const uintptr_t pixels_dma;

	// Length in pixels of the longest LED strip.
	unsigned num_pixels;

	// write 1 to start, 0xFF to abort. will be cleared when started
	volatile unsigned command;

	// will have a non-zero response written when done
	volatile unsigned response;
} __attribute__((__packed__)) ws281x_command_t;

Reference