Skip to content

Commit

Permalink
Merge pull request #6 from jgarff/master
Browse files Browse the repository at this point in the history
merge latest updates from upstream
  • Loading branch information
penfold42 authored May 21, 2017
2 parents 7fea619 + a183970 commit f580777
Show file tree
Hide file tree
Showing 11 changed files with 937 additions and 140 deletions.
79 changes: 55 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
rpi_ws281x
==========

Userspace Raspberry Pi PWM library for WS281X LEDs
Userspace Raspberry Pi library for controlling WS281X LEDs.
This includes WS2812 and SK6812RGB RGB LEDs
Preliminary support is now included for SK6812RGBW LEDs (yes, RGB + W)
The LEDs can be controlled by either the PWM (2 independent channels)
or PCM controller (1 channel) or the SPI interfacei (1 channel).

### Background:

The BCM2835 in the Raspberry Pi has a PWM module that is well suited to
driving individually controllable WS281X LEDs. Using the DMA, PWM FIFO,
and serial mode in the PWM, it's possible to control almost any number
of WS281X LEDs in a chain connected to the PWM output pin.

This library and test program set the clock rate of the PWM controller to
3X the desired output frequency and creates a bit pattern in RAM from an
array of colors where each bit is represented by 3 bits for the PWM
controller as follows.
The BCM2835 in the Raspberry Pi has both a PWM and a PCM module that
are well suited to driving individually controllable WS281X LEDs.
Using the DMA, PWM or PCM FIFO, and serial mode in the PWM, it's
possible to control almost any number of WS281X LEDs in a chain connected
to the appropirate output pin.
For SPI the Raspbian spidev driver is used (/dev/spi0.0).
This library and test program set the clock rate to 3X the desired output
frequency and creates a bit pattern in RAM from an array of colors where
each bit is represented by 3 bits as follows.

Bit 1 - 1 1 0
Bit 0 - 1 0 0
Expand All @@ -31,9 +33,10 @@ It is possible to run the LEDs from a 3.3V - 3.6V power source, and
connect the GPIO directly at a cost of brightness, but this isn't
recommended.

The test program is designed to drive a 8x8 grid of LEDs from Adafruit
(http://www.adafruit.com/products/1487). Please see the Adafruit
website for more information.
The test program is designed to drive a 8x8 grid of LEDs e.g.from
Adafruit (http://www.adafruit.com/products/1487) or Pimoroni
(https://shop.pimoroni.com/products/unicorn-hat).
Please see the Adafruit and Pimoroni websites for more information.

Know what you're doing with the hardware and electricity. I take no
reponsibility for damage, harm, or mistakes.
Expand All @@ -50,16 +53,18 @@ reponsibility for damage, harm, or mistakes.
### Running:

- Type 'sudo scons'.
- Type 'sudo ./test'.
- Type 'sudo ./test' (default uses PWM channel 0).
- That's it. You should see a moving rainbow scroll across the
display.

### Limitations:

Since this library and the onboard Raspberry Pi audio both use the PWM,
they cannot be used together. You will need to blacklist the Broadcom
audio kernel module by creating a file /etc/modprobe.d/snd-blacklist.conf
with
#### PWM

Since this library and the onboard Raspberry Pi audio
both use the PWM, they cannot be used together. You will need to
blacklist the Broadcom audio kernel module by creating a file
/etc/modprobe.d/snd-blacklist.conf with

blacklist snd_bcm2835

Expand All @@ -69,17 +74,43 @@ need to comment it out in the /etc/modules file.
Some distributions use audio by default, even if nothing is being played.
If audio is needed, you can use a USB audio device instead.

#### PCM

When using PCM you cannot use digital audio devices which use I2S since I2S
uses the PCM hardware, but you can use analog audio.

#### SPI

When using SPI the ledstring is the only device which can be connected to
the SPI bus. Both digital (I2S/PCM) and analog (PWM) audio can be used.

### Comparison PWM/PCM/SPI

Both PWM and PCM use DMA transfer to output the control signal for the LEDs.
The max size of a DMA transfer is 65536 bytes. SInce each LED needs 12 bytes
(4 colors, 8 symbols per color, 3 bits per symbol) this means you can
control approximately 5400 LEDs for a single strand in PCM and 2700 LEDs per string
for PWM (Only PWM can control 2 independent strings simultaneously)
SPI uses the SPI device driver in the kernel. For transfers larger than
96 bytes the kernel driver also uses DMA.
Of course there are practical limits on power and signal quality. These will
be more constraining in practice than the theoretical limits above.

When controlling a LED string of 240 LEDs the CPU load on the original Pi 2 (BCM2836) are:
PWM 5%
PCM 5%
SPI 1%

### Usage:

The API is very simple. Make sure to create and initialize the ws2811_t
structure as seen in main.c. From there it can be initialized
by calling ws2811_init(). LEDs are changed by modifying the color in
the .led[index] array and calling ws2811_render(). The rest is handled
by the library, which creates the DMA memory and starts the DMA/PWM.
the .led[index] array and calling ws2811_render().
The rest is handled by the library, which either creates the DMA memory and
starts the DMA for PWM and PCM or prepares the SPI transfer buffer and sends
it out on the MISO pin.

Make sure to hook a signal handler for SIGKILL to do cleanup. From the
handler make sure to call ws2811_fini(). It'll make sure that the DMA
is finished before program execution stops.

That's it. Have fun. This was a fun little weekend project. I hope
you find it useful.
is finished before program execution stops and cleans up after itself.
1 change: 1 addition & 0 deletions SConscript
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ lib_srcs = Split('''
mailbox.c
ws2811.c
pwm.c
pcm.c
dma.c
rpihw.c
''')
Expand Down
41 changes: 23 additions & 18 deletions clk.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,27 +33,32 @@

typedef struct {
uint32_t ctl;
#define CM_PWM_CTL_PASSWD (0x5a << 24)
#define CM_PWM_CTL_MASH(val) ((val & 0x3) << 9)
#define CM_PWM_CTL_FLIP (1 << 8)
#define CM_PWM_CTL_BUSY (1 << 7)
#define CM_PWM_CTL_KILL (1 << 5)
#define CM_PWM_CTL_ENAB (1 << 4)
#define CM_PWM_CTL_SRC_GND (0 << 0)
#define CM_PWM_CTL_SRC_OSC (1 << 0)
#define CM_PWM_CTL_SRC_TSTDBG0 (2 << 0)
#define CM_PWM_CTL_SRC_TSTDBG1 (3 << 0)
#define CM_PWM_CTL_SRC_PLLA (4 << 0)
#define CM_PWM_CTL_SRC_PLLC (5 << 0)
#define CM_PWM_CTL_SRC_PLLD (6 << 0)
#define CM_PWM_CTL_SRC_HDMIAUX (7 << 0)
#define CM_CLK_CTL_PASSWD (0x5a << 24)
#define CM_CLK_CTL_MASH(val) ((val & 0x3) << 9)
#define CM_CLK_CTL_FLIP (1 << 8)
#define CM_CLK_CTL_BUSY (1 << 7)
#define CM_CLK_CTL_KILL (1 << 5)
#define CM_CLK_CTL_ENAB (1 << 4)
#define CM_CLK_CTL_SRC_GND (0 << 0)
#define CM_CLK_CTL_SRC_OSC (1 << 0)
#define CM_CLK_CTL_SRC_TSTDBG0 (2 << 0)
#define CM_CLK_CTL_SRC_TSTDBG1 (3 << 0)
#define CM_CLK_CTL_SRC_PLLA (4 << 0)
#define CM_CLK_CTL_SRC_PLLC (5 << 0)
#define CM_CLK_CTL_SRC_PLLD (6 << 0)
#define CM_CLK_CTL_SRC_HDMIAUX (7 << 0)
uint32_t div;
#define CM_PWM_DIV_PASSWD (0x5a << 24)
#define CM_PWM_DIV_DIVI(val) ((val & 0xfff) << 12)
#define CM_PWM_DIV_DIVF(val) ((val & 0xfff) << 0)
} __attribute__((packed, aligned(4))) cm_pwm_t;
#define CM_CLK_DIV_PASSWD (0x5a << 24)
#define CM_CLK_DIV_DIVI(val) ((val & 0xfff) << 12)
#define CM_CLK_DIV_DIVF(val) ((val & 0xfff) << 0)
} __attribute__((packed, aligned(4))) cm_clk_t;


/*
* PWM and PCM clock offsets from https://www.scribd.com/doc/127599939/BCM2835-Audio-clocks
*
*/
#define CM_PCM_OFFSET (0x00101098)
#define CM_PWM_OFFSET (0x001010a0)


Expand Down
2 changes: 1 addition & 1 deletion golang/ws2811/ws2811.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
func Init(gpioPin int, ledCount int, brightness int) error {
C.ledstring.channel[0].gpionum = C.int(gpioPin)
C.ledstring.channel[0].count = C.int(ledCount)
C.ledstring.channel[0].brightness = C.int(brightness)
C.ledstring.channel[0].brightness = C.uint8_t(brightness)
res := int(C.ws2811_init(&C.ledstring))
if res == 0 {
return nil
Expand Down
67 changes: 34 additions & 33 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
*/


static char VERSION[] = "testing...";
static char VERSION[] = "XX.YY.ZZ";

#include <stdint.h>
#include <stdio.h>
Expand All @@ -53,19 +53,19 @@ static char VERSION[] = "testing...";
#include "ws2811.h"


#define ARRAY_SIZE(stuff) (sizeof(stuff) / sizeof(stuff[0]))
#define ARRAY_SIZE(stuff) (sizeof(stuff) / sizeof(stuff[0]))

// defaults for cmdline options
#define TARGET_FREQ WS2811_TARGET_FREQ
#define GPIO_PIN 18
#define DMA 5
#define STRIP_TYPE WS2811_STRIP_RGB // WS2812/SK6812RGB integrated chip+leds
//#define STRIP_TYPE WS2811_STRIP_GBR // WS2812/SK6812RGB integrated chip+leds
//#define STRIP_TYPE SK6812_STRIP_RGBW // SK6812RGBW (NOT SK6812RGB)
#define TARGET_FREQ WS2811_TARGET_FREQ
#define GPIO_PIN 18
#define DMA 5
//#define STRIP_TYPE WS2811_STRIP_RGB // WS2812/SK6812RGB integrated chip+leds
#define STRIP_TYPE WS2811_STRIP_GBR // WS2812/SK6812RGB integrated chip+leds
//#define STRIP_TYPE SK6812_STRIP_RGBW // SK6812RGBW (NOT SK6812RGB)

#define WIDTH 8
#define HEIGHT 8
#define LED_COUNT (WIDTH * HEIGHT)
#define WIDTH 8
#define HEIGHT 8
#define LED_COUNT (WIDTH * HEIGHT)

int width = WIDTH;
int height = HEIGHT;
Expand Down Expand Up @@ -122,7 +122,9 @@ void matrix_raise(void)
{
for (x = 0; x < width; x++)
{
matrix[y * width + x] = matrix[(y + 1)*width + x];
// This is for the 8x8 Pimoroni Unicorn-HAT where the LEDS in subsequent
// rows are arranged in opposite directions
matrix[y * width + x] = matrix[(y + 1)*width + width - x - 1];
}
}
}
Expand Down Expand Up @@ -178,11 +180,11 @@ void matrix_bottom(void)
dotspos[i] = 0;
}

if (ledstring.channel[0].strip_type == SK6812_STRIP_RGBW) {
matrix[dotspos[i] + (height - 1) * width] = dotcolors_rgbw[i];
} else {
matrix[dotspos[i] + (height - 1) * width] = dotcolors[i];
}
if (ledstring.channel[0].strip_type == SK6812_STRIP_RGBW) {
matrix[dotspos[i] + (height - 1) * width] = dotcolors_rgbw[i];
} else {
matrix[dotspos[i] + (height - 1) * width] = dotcolors[i];
}
}
}

Expand Down Expand Up @@ -245,8 +247,8 @@ void parseargs(int argc, char **argv, ws2811_t *ws2811)
"-x (--width) - matrix width (default 8)\n"
"-y (--height) - matrix height (default 8)\n"
"-d (--dma) - dma channel to use (default 5)\n"
"-g (--gpio) - GPIO to use must be one of 12,18,40,52\n"
" If omitted, default is 18\n"
"-g (--gpio) - GPIO to use\n"
" If omitted, default is 18 (PWM0)\n"
"-i (--invert) - invert pin output (pulse LOW)\n"
"-c (--clear) - clear matrix on exit.\n"
"-v (--version) - version information\n"
Expand All @@ -260,23 +262,20 @@ void parseargs(int argc, char **argv, ws2811_t *ws2811)
if (optarg) {
int gpio = atoi(optarg);
/*
https://www.raspberrypi.org/forums/viewtopic.php?f=91&t=105044
PWM0, which can be set to use GPIOs 12, 18, 40, and 52.
Only 12 (pin 32) and 18 (pin 12) are available on the B+/2B
Only 12 (pin 32) and 18 (pin 12) are available on the B+/2B/3B
PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53.
Only 13 is available on the B+/2B, on pin 35
Only 13 is available on the B+/2B/PiZero/3B, on pin 35
PCM_DOUT, which can be set to use GPIOs 21 and 31.
Only 21 is available on the B+/2B/PiZero/3B, on pin 40.
SPI0-MOSI is available on GPIOs 10 and 38.
Only GPIO 10 is available on all models.
The library checks if the specified gpio is available
on the specific model (from model B rev 1 till 3B)
*/
switch (gpio) {
case 12:
case 18:
case 40:
case 52:
ws2811->channel[0].gpionum = gpio;
break;
default:
printf ("gpio %d doesnt support PWM0\n",gpio);
exit (-1);
}
ws2811->channel[0].gpionum = gpio;
}
break;

Expand Down Expand Up @@ -376,6 +375,8 @@ int main(int argc, char *argv[])
{
ws2811_return_t ret;

sprintf(VERSION, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO);

parseargs(argc, argv, &ledstring);

matrix = malloc(sizeof(ws2811_led_t) * width * height);
Expand Down
Loading

0 comments on commit f580777

Please sign in to comment.