Skip to content

Commit

Permalink
Add per-channel user-settable gamma correction (#171)
Browse files Browse the repository at this point in the history
* Pi Zero W v1.1 support added to rpihw.c

* Update rpihw.c (#164)

added raspi Zero W

* Device-tree auto-detect

* Channel gamma support

* Reverted to old detection, added 0x9200c1

* Changed gamma correction to match leds

* Removed dead dt detect code

* Re added "Pi Zero W v1.1", .hwver = 0x9000c1, (#181)

* Added PCM and SPI support. Specifying the corresponding GPIO pin will (#170)

select either PWM, PCM or SPI.
PCM and SPI only support 1 channel.
Increased version to 1.1.0.
Updated README.md

* Fix setting of brightness in go bindings (#174)

Fixes #151

* Added delay to ensure reset time being met with low led counts (#157)

* Fix includes as per #178 (#189)

* clean up doco and strandtest (#187)

* updated documentation on GPIO usage
strandtest now defaults to GRB colour order
added version.h to gitignore

* updated python readme

* more README cleanup

* added notes on spi max transfer size
  • Loading branch information
Gadgetoid authored and jgarff committed Jul 6, 2017
1 parent b57c2b3 commit aee7ccc
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 28 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.sconsign.dblite
version.h
*.pyc
test
newtest
Expand Down
61 changes: 58 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,36 @@ each bit is represented by 3 bits as follows.
Bit 0 - 1 0 0


### Hardware:
### GPIO Usage:

The GPIOs that can be used are limited by the hardware of the Pi and will
vary based on the method used to drive them (PWM, PCM or SPI).
Beware that the GPIO numbers are not the same as the physical pin numbers
on the header.

PWM:
```
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/3B
PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53.
Only 13 is available on the B+/2B/PiZero/3B, on pin 33
```

PCM:
```
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.
```

SPI:
```
SPI0-MOSI is available on GPIOs 10 and 38.
Only GPIO 10 is available on all models.
```


### Power and voltage requirements

WS281X LEDs are generally driven at 5V, which requires that the data
signal be at the same level. Converting the output from a Raspberry
Expand Down Expand Up @@ -52,10 +81,24 @@ reponsibility for damage, harm, or mistakes.

### Running:

- Type 'sudo scons'.
- Type 'sudo ./test' (default uses PWM channel 0).
- That's it. You should see a moving rainbow scroll across the
display.
- More options are available, ./test -h should show them:
```
./test version 1.1.0
Usage: ./test
-h (--help) - this information
-s (--strip) - strip type - rgb, grb, gbr, rgbw
-x (--width) - matrix width (default 8)
-y (--height) - matrix height (default 8)
-d (--dma) - dma channel to use (default 5)
-g (--gpio) - GPIO to use
If omitted, default is 18 (PWM0)
-i (--invert) - invert pin output (pulse LOW)
-c (--clear) - clear matrix on exit.
-v (--version) - version information
```

### Limitations:

Expand All @@ -71,6 +114,14 @@ blacklist the Broadcom audio kernel module by creating a file
If the audio device is still loading after blacklisting, you may also
need to comment it out in the /etc/modules file.

On headless systems you may also need to force audio through hdmi
Edit config.txt and add:

hdmi_force_hotplug=1
hdmi_force_edid_audio=1

A reboot is required for this change to take effect

Some distributions use audio by default, even if nothing is being played.
If audio is needed, you can use a USB audio device instead.

Expand All @@ -84,10 +135,14 @@ uses the PCM hardware, but you can use analog audio.
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.

Many distributions have a maximum SPI transfer of 4096 bytes. This can be
changed in /boot/config.txt
spidev.bufsiz=32768

### 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
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)
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
2 changes: 1 addition & 1 deletion mailbox.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>

#include "mailbox.h"
Expand Down
2 changes: 1 addition & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ void parseargs(int argc, char **argv, ws2811_t *ws2811)
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/3B
PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53.
Only 13 is available on the B+/2B/PiZero/3B, on pin 35
Only 13 is available on the B+/2B/PiZero/3B, on pin 33
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.
Expand Down
9 changes: 9 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# Build

As this is just a python wrapper for the library you must first follow
the build instructions in the parent directory.
When complete, you can build this python wrapper:
```
sudo apt-get install python-dev swig
python ./setup.py build
```

If you are rebuilding after fetching some updated commits, you might need to
remove the build directory first
```
rm -rf ./build
```

# Run a demo

```
Expand Down
14 changes: 9 additions & 5 deletions python/examples/strandtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@

# LED strip configuration:
LED_COUNT = 16 # Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (must support PWM!).
LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!).
#LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 5 # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
LED_STRIP = ws.WS2811_STRIP_GRB # Strip type and colour ordering



# Define functions which animate LEDs in various ways.
Expand Down Expand Up @@ -78,21 +82,21 @@ def theaterChaseRainbow(strip, wait_ms=50):
# Main program logic follows:
if __name__ == '__main__':
# Create NeoPixel object with appropriate configuration.
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP)
# Intialize the library (must be called once before other functions).
strip.begin()

print ('Press Ctrl-C to quit.')
while True:
# Color wipe animations.
print ('Color wipe animations.')
colorWipe(strip, Color(255, 0, 0)) # Red wipe
colorWipe(strip, Color(0, 255, 0)) # Blue wipe
colorWipe(strip, Color(0, 0, 255)) # Green wipe
# Theater chase animations.
print ('Theater chase animations.')
theaterChase(strip, Color(127, 127, 127)) # White theater chase
theaterChase(strip, Color(127, 0, 0)) # Red theater chase
theaterChase(strip, Color( 0, 0, 127)) # Blue theater chase
# Rainbow animations.
print ('Rainbow animations.')
rainbow(strip)
rainbowCycle(strip)
theaterChaseRainbow(strip)
15 changes: 14 additions & 1 deletion rpihw.c
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,20 @@ static const rpi_hw_t rpi_hw_info[] = {
.videocore_base = VIDEOCORE_BASE_RPI,
.desc = "Pi Zero v1.3",
},
{
.hwver = 0x9000c1,
.type = RPI_HWVER_TYPE_PI1,
.periph_base = PERIPH_BASE_RPI,
.videocore_base = VIDEOCORE_BASE_RPI,
.desc = "Pi Zero W v1.1",
},
{
.hwver = 0x9200c1,
.type = RPI_HWVER_TYPE_PI1,
.periph_base = PERIPH_BASE_RPI,
.videocore_base = VIDEOCORE_BASE_RPI,
.desc = "Pi Zero W v1.1",
},

//
// Model A+
Expand Down Expand Up @@ -293,7 +307,6 @@ static const rpi_hw_t rpi_hw_info[] = {

};


const rpi_hw_t *rpi_hw_detect(void)
{
FILE *f = fopen("/proc/cpuinfo", "r");
Expand Down
84 changes: 68 additions & 16 deletions ws2811.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <signal.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include <time.h>

#include "mailbox.h"
#include "clk.h"
Expand All @@ -63,6 +63,9 @@
#define LED_BIT_COUNT(leds, freq) ((leds * LED_COLOURS * 8 * 3) + ((LED_RESET_uS * \
(freq * 3)) / 1000000))

/* Minimum time to wait for reset to occur in microseconds. */
#define LED_RESET_WAIT_TIME 300

// Pad out to the nearest uint32 + 32-bits for idle low/high times the number of channels
#define PWM_BYTE_COUNT(leds, freq) (((((LED_BIT_COUNT(leds, freq) >> 3) & ~0x7) + 4) + 4) * \
RPI_PWM_CHANNELS)
Expand Down Expand Up @@ -111,6 +114,22 @@ typedef struct ws2811_device
int max_count;
} ws2811_device_t;

/**
* Provides monotonic timestamp in microseconds.
*
* @returns Current timestamp in microseconds or 0 on error.
*/
static uint64_t get_microsecond_timestamp()
{
struct timespec t;

if (clock_gettime(CLOCK_MONOTONIC_RAW, &t) != 0) {
return 0;
}

return t.tv_sec * 1000000 + t.tv_nsec / 1000;
}

/**
* Iterate through the channels and find the largest led count.
*
Expand Down Expand Up @@ -590,6 +609,11 @@ void ws2811_cleanup(ws2811_t *ws2811)
free(ws2811->channel[chan].leds);
}
ws2811->channel[chan].leds = NULL;
if (ws2811->channel && ws2811->channel[chan].gamma)
{
free(ws2811->channel[chan].gamma);
}
ws2811->channel[chan].gamma = NULL;
}

if (device->mbox.handle != -1)
Expand Down Expand Up @@ -929,6 +953,13 @@ ws2811_return_t ws2811_init(ws2811_t *ws2811)
channel->strip_type=WS2811_STRIP_RGB;
}

// Set default uncorrected gamma table
channel->gamma = malloc(sizeof(uint8_t) * 256);
int x;
for(x = 0; x < 256; x++){
channel->gamma[x] = x;
}

channel->wshift = (channel->strip_type >> 24) & 0xff;
channel->rshift = (channel->strip_type >> 16) & 0xff;
channel->gshift = (channel->strip_type >> 8) & 0xff;
Expand Down Expand Up @@ -1067,33 +1098,43 @@ ws2811_return_t ws2811_render(ws2811_t *ws2811)
int i, k, l, chan;
unsigned j;
ws2811_return_t ret = WS2811_SUCCESS;
uint32_t protocol_time = 0;

bitpos = (driver_mode == SPI ? 7 : 31);

for (chan = 0; chan < RPI_PWM_CHANNELS; chan++) // Channel
{
ws2811_channel_t *channel = &ws2811->channel[chan];

int wordpos = chan; // PWM & PCM
int bytepos = 0; // SPI
const int scale = (channel->brightness & 0xff) + 1;
const int scale = (channel->brightness & 0xff) + 1;
uint8_t array_size = 3; // Assume 3 color LEDs, RGB

// 1.25µs per bit
const uint32_t channel_protocol_time = channel->count * array_size * 8 * 1.25;

// If our shift mask includes the highest nibble, then we have 4 LEDs, RBGW.
if (channel->strip_type & SK6812_SHIFT_WMASK)
{
array_size = 4;
}

// Only using the channel which takes the longest as both run in parallel
if (channel_protocol_time > protocol_time)
{
protocol_time = channel_protocol_time;
}

for (i = 0; i < channel->count; i++) // Led
{
uint8_t color[] =
{
(((channel->leds[i] >> channel->rshift) & 0xff) * scale) >> 8, // red
(((channel->leds[i] >> channel->gshift) & 0xff) * scale) >> 8, // green
(((channel->leds[i] >> channel->bshift) & 0xff) * scale) >> 8, // blue
(((channel->leds[i] >> channel->wshift) & 0xff) * scale) >> 8, // white
channel->gamma[(((channel->leds[i] >> channel->rshift) & 0xff) * scale) >> 8], // red
channel->gamma[(((channel->leds[i] >> channel->gshift) & 0xff) * scale) >> 8], // green
channel->gamma[(((channel->leds[i] >> channel->bshift) & 0xff) * scale) >> 8], // blue
channel->gamma[(((channel->leds[i] >> channel->wshift) & 0xff) * scale) >> 8], // white
};
uint8_t array_size = 3; // Assume 3 color LEDs, RGB

// If our shift mask includes the highest nibble, then we have 4
// LEDs, RBGW.
if (channel->strip_type & SK6812_SHIFT_WMASK)
{
array_size = 4;
}

for (j = 0; j < array_size; j++) // Color
{
Expand Down Expand Up @@ -1158,15 +1199,26 @@ ws2811_return_t ws2811_render(ws2811_t *ws2811)
return ret;
}

if (ws2811->render_wait_until != 0) {
const uint64_t current_timestamp = get_microsecond_timestamp();

if (ws2811->render_wait_until > current_timestamp) {
usleep(ws2811->render_wait_until - current_timestamp);
}
}

if (driver_mode != SPI)
{
dma_start(ws2811);
}
else if ((ret = spi_transfer(ws2811)) != WS2811_SUCCESS)
else
{
return ret;
ret = spi_transfer(ws2811);
}

// LED_RESET_WAIT_TIME is added to allow enough time for the reset to occur.
ws2811->render_wait_until = get_microsecond_timestamp() + protocol_time + LED_RESET_WAIT_TIME;

return ret;
}

Expand Down
Loading

0 comments on commit aee7ccc

Please sign in to comment.