Skip to content

Commit

Permalink
Merge pull request #11 from electricimp/develop
Browse files Browse the repository at this point in the history
Updated to support all imps
  • Loading branch information
betzrhodes authored Jan 31, 2017
2 parents 2bdbd92 + 8b41731 commit 27d2419
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 75 deletions.
48 changes: 16 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# WS2812 v2.0.2
# WS2812 3.0.0

This class allows the imp to drive WS2812 and WS2812B LEDs. The WS2812 is an all-in-one RGB LED with integrated shift register and constant-current driver. The parts are daisy-chained, and a proprietary one-wire protocol is used to send data to the chain of LEDs. Each pixel is individually addressable and this allows the part to be used for a wide range of effects animations.

Expand All @@ -9,18 +9,18 @@ Some example hardware that uses the WS2812 or WS2812B:
* [30 LED - 1m strip](http://www.adafruit.com/products/1376)
* [NeoPixel Stick](http://www.adafruit.com/products/1426)

**To add this library to your project, add `#require "WS2812.class.nut:2.0.2"` to the top of your device code.**

You can view the library’s source code on [GitHub](https://github.com/electricimp/ws2812/tree/v2.0.2).
**To add this library to your project, add** `#require "WS2812.class.nut:3.0.0"` **to the top of your device code.**

## Hardware

WS2812s require a 5V power supply and logic, and each pixel can draw up to 60mA when displaying white in full brightness, so be sure to size your power supply appropriatly. Undersized power supplies (lower voltages and/or insufficent current) can cause glitches and/or failure to produce and light at all.
WS2812s require a 5V power supply and logic, and each pixel can draw up to 60mA when displaying white in full brightness, so be sure to size your power supply appropriately. Undersized power supplies (lower voltages and/or insufficient current) can cause glitches and/or failure to produce and light at all.

Because WS2812s require 5V logic, you will need to shift your logic level to 5V. A sample circuit can be found below using Adafruit’s [4-channel Bi-directional Logic Level Converter](http://www.adafruit.com/products/757):

![WS2812 Circuit](./circuit.png)

**Warning** We do not recommend using the imp005 with WS2812s. Unlike the imp001, imp002, imp003 and imp004m, the imp005 does not use DMA for SPI data transfers. Instead, each byte is written out individually, and this means there will always be a small gap between each byte. As a result, the LEDs may not work as expected and the performance of other operations, such as Agent/Device communications, are blocked when the *draw()* method is called.

## Class Usage

All public methods in the WS2812 class return `this`, allowing you to easily chain multiple commands together:
Expand All @@ -33,54 +33,38 @@ pixels
.draw();
```

### Constructor: WS2812(spi, frameSize, [draw])
### Constructor: WS2812(*spi, numberOfPixels[, draw]*)

Instantiate the class with a pre-configured SPI object and the number of pixels that are connected. The SPI object must be configured at 7500kHz and have the *MSB_FIRST* flag set:
Instantiate the class with an imp SPI object and the number of pixels that are connected. The SPI object will be configured by the constructor. An optional third parameter can be set to control whether the class will draw an empty frame on initialization. The default value is `true`.

```squirrel
#require "ws2812.class.nut:2.0.2"
#require "ws2812.class.nut:3.0.0"
// Configure the SPI bus
// Select the SPI bus
spi <- hardware.spi257;
spi.configure(MSB_FIRST, 7500);
// Instantiate LED array with 5 pixels
pixels <- WS2812(spi, 5);
```

An optional third parameter can be set to control whether the class will draw an empty frame on initialization. The default value is `true`.

## Class Methods

### configure()

Rather than pass a preconfigured SPI object to the constructor, you can pass an unconfigured SPI object, and have the *configure()* method automatically configure the SPI object for you.

**NOTE:** If you are using the *configure* method, you **must** pass `false` the the *draw* parameter of the constructor:

```squirrel
#require "ws2812.class.nut:2.0.2"
// Create and configure an LED array with 5 pixels:
pixels <- WS2812(hardware.spi257, 5, false).configure();
```

### set(*index, color*)

The *set* method changes the color of a particular pixel in the frame buffer. The color is passed as as an array of three integers between 0 and 255 representing `[red, green, blue]`.
The *set()* method changes the color of a particular pixel in the frame buffer. The color is passed as as an array of three integers between 0 and 255 representing `[red, green, blue]`.

NOTE: The *set* method does not output the changes to the pixel strip. After setting up the frame, you must call `draw` (see below) to output the frame to the strip.
**Note** The *set()* method does not output the changes to the pixel strip. After setting up the frame, you must call *draw()* (see below) to output the frame to the strip.

```squirrel
// Set and draw a pixel
pixels.set(0, [127,0,0]).draw();
```

### fill(*color, [start], [end]*)
### fill(*color[, start][, end]*)

The *fill* methods sets all pixels in the specified range to the desired color. If no range is selected, the entire frame will be filled with the specified color.
The *fill()* method sets all pixels in the specified range to the desired color. If no range is selected, the entire frame will be filled with the specified color.

NOTE: The *fill* method does not output the changes to the pixel strip. After setting up the frame, you must call `draw` (see below) to output the frame to the strip.
**Note** The *fill()* method does not output the changes to the pixel strip. After setting up the frame, you must call *draw()* (see below) to output the frame to the strip.

```squirrel
// Turn all LEDs off
Expand All @@ -92,13 +76,13 @@ pixels.fill([0,0,0]).draw();
// and the other half blue
pixels
.fill([100,0,0], 0, 2)
.fill([0,0,100], 3, 4);
.fill([0,0,100], 3, 4)
.draw();
```

### draw()

The *draw* method draws writes the current frame to the pixel array (see examples above).
The *draw()* method draws writes the current frame to the pixel array (see examples above).

## License

Expand Down
146 changes: 105 additions & 41 deletions WS2812.class.nut
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,58 @@
// http://opensource.org/licenses/MIT

class WS2812 {
// This class uses SPI to emulate the WS2812s' one-wire protocol.
// This requires one byte per bit to send data at 7.5 MHz via SPI.
// These consts define the "waveform" to represent a zero or one

static VERSION = [2,0,2];
static VERSION = "3.0.0";

static ERROR_005 = "Use of this imp module is not advisable.";

// This class uses SPI to emulate the WS2812s' one-wire protocol.
// The ideal speed for WS2812 LEDs is 6400 MHz via SPI.

// The closest Imp001 & Imp002 supported SPI datarate is 7500 MHz
// Imp004m supported SPI datarate is 6000 MHz
// Imp005 supported SPI datarate is 6400 MHz
// These consts define the "waveform" to represent a zero or one
static ZERO = 0xC0;
static ONE = 0xF8;
static BYTES_PER_PIXEL = 24;

// The closest Imp003 supported SPI datarate is 9 MHz.
// These consts define the "waveform" to represent a zero-zero, zero-one, one-zero, one-one.
static ZERO_ZERO = "\xE0\x0E\x00";
static ZERO_ONE = "\xE0\x0F\xC0";
static ONE_ZERO = "\xFC\x0E\x00";
static ONE_ONE = "\xFC\x0F\xC0";
static IMP3_BYTES_PER_PIXEL = 36;

// When instantiated, the WS2812 class will fill this array with blobs to
// represent the waveforms to send the numbers 0 to 255. This allows the
// blobs to be copied in directly, instead of being built for each pixel.

static _bits = array(256, null);

// Private variables passed into the constructor

_spi = null; // imp SPI interface (pre-configured)
_spi = null; // imp SPI interface
_frameSize = null; // number of pixels per frame
_frame = null; // a blob to hold the current frame
_bytes_per_pixel = null; // number of bytes per pixel

// Parameters:
// spi A pre-configured SPI bus (MSB_FIRST, 7500)
// spi A SPI bus
// frameSize Number of Pixels per frame
// _draw Whether or not to initially draw a blank frame
constructor(spiBus, frameSize, _draw = true) {
// spiBus must be configured
_spi = spiBus;
local impType = _getImpType();
_configureSPI(spiBus, impType);

_frameSize = frameSize;
_frame = blob(_frameSize * BYTES_PER_PIXEL + 1);
_frame[_frameSize * BYTES_PER_PIXEL] = 0;
_frame = blob(_frameSize * _bytes_per_pixel + 1);
_frame[_frameSize * _bytes_per_pixel] = 0;

// Used in constructing the _bits array
local bytesPerColor = BYTES_PER_PIXEL / 3;
local bytesPerColor = _bytes_per_pixel / 3;

// Fill the _bits array if required
// (Multiple instance of WS2812 will only initialize it once)
if (_bits[0] == null) {
for (local i = 0; i < 256; i++) {
local valblob = blob(bytesPerColor);
valblob.writen((i & 0x80) ? ONE:ZERO,'b');
valblob.writen((i & 0x40) ? ONE:ZERO,'b');
valblob.writen((i & 0x20) ? ONE:ZERO,'b');
valblob.writen((i & 0x10) ? ONE:ZERO,'b');
valblob.writen((i & 0x08) ? ONE:ZERO,'b');
valblob.writen((i & 0x04) ? ONE:ZERO,'b');
valblob.writen((i & 0x02) ? ONE:ZERO,'b');
valblob.writen((i & 0x01) ? ONE:ZERO,'b');
_bits[i] = valblob;
}
}
if (_bits[0] == null) _fillBitsArray(impType, bytesPerColor);

// Clear the pixel buffer
fill([0,0,0]);
Expand All @@ -66,15 +65,6 @@ class WS2812 {
}
}

// Configures the SPI Bus
//
// NOTE: If using the configure method, you *must* pass `false` to the
// _draw parameter in the constructor (or else an error will be thrown)
function configure() {
_spi.configure(MSB_FIRST, 7500);
return this;
}

// Sets a pixel in the buffer
// index - the index of the pixel (0 <= index < _frameSize)
// color - [r,g,b] (0 <= r,g,b <= 255)
Expand All @@ -84,7 +74,7 @@ class WS2812 {
index = _checkRange(index);
color = _checkColorRange(color);

_frame.seek(index * BYTES_PER_PIXEL);
_frame.seek(index * _bytes_per_pixel);

// Create a blob for the color
// Red and green are swapped for some reason, so swizzle them back
Expand Down Expand Up @@ -118,13 +108,13 @@ class WS2812 {

// Create a blob for the color
// Red and green are swapped for some reason, so swizzle them back
local colorBlob = blob(BYTES_PER_PIXEL);
local colorBlob = blob(_bytes_per_pixel);
colorBlob.writeblob(_bits[color[1]]);
colorBlob.writeblob(_bits[color[0]]);
colorBlob.writeblob(_bits[color[2]]);

// Write the color blob to each pixel in the fill
_frame.seek(start*BYTES_PER_PIXEL);
_frame.seek(start*_bytes_per_pixel);
for (local index = start; index <= end; index++) {
_frame.writeblob(colorBlob);
}
Expand All @@ -140,6 +130,9 @@ class WS2812 {
return this;
}

// Private functions
// --------------------------------------------------

function _checkRange(index) {
if (index < 0) index = 0;
if (index >= _frameSize) index = _frameSize - 1;
Expand All @@ -153,4 +146,75 @@ class WS2812 {
}
return colors
}
}

function _getImpType() {
local env = imp.environment();
if (env == ENVIRONMENT_CARD) {
return 1;
}
if (env == ENVIRONMENT_MODULE) {
return hardware.getdeviceid().slice(0,1).tointeger();
}
}

function _configureSPI(spiBus, impType) {
_spi = spiBus;
switch (impType) {
case 1:
// same as 002 config
case 2:
_bytes_per_pixel = BYTES_PER_PIXEL;
_spi.configure(MSB_FIRST, 7500);
break;
case 3:
_bytes_per_pixel = IMP3_BYTES_PER_PIXEL;
_spi.configure(MSB_FIRST, 9000);
break;
case 4:
_bytes_per_pixel = BYTES_PER_PIXEL;
_spi.configure(MSB_FIRST, 6000);
break;
case 5:
server.error(ERROR_005);
_bytes_per_pixel = BYTES_PER_PIXEL;
// Note: to see the actual rate log actualRate
// Passing in 6000 actually sets datarate to 6400
local actualRate = _spi.configure(MSB_FIRST, 6000);
// server.log(actual Rate)
break;
}
}

function _fillBitsArray(impType, bytesPerColor) {
if (impType == 3) {
for (local i = 0; i < 256; i++) {
local valblob = blob(bytesPerColor);
valblob.writestring(_getNumber((i /64) % 4));
valblob.writestring(_getNumber((i /16) % 4));
valblob.writestring(_getNumber((i /4) % 4));
valblob.writestring(_getNumber(i % 4));
_bits[i] = valblob;
}
} else {
for (local i = 0; i < 256; i++) {
local valblob = blob(bytesPerColor);
valblob.writen((i & 0x80) ? ONE:ZERO,'b');
valblob.writen((i & 0x40) ? ONE:ZERO,'b');
valblob.writen((i & 0x20) ? ONE:ZERO,'b');
valblob.writen((i & 0x10) ? ONE:ZERO,'b');
valblob.writen((i & 0x08) ? ONE:ZERO,'b');
valblob.writen((i & 0x04) ? ONE:ZERO,'b');
valblob.writen((i & 0x02) ? ONE:ZERO,'b');
valblob.writen((i & 0x01) ? ONE:ZERO,'b');
_bits[i] = valblob;
}
}
}

function _getNumber(num) {
if(num == 0) return ZERO_ZERO;
if(num == 1) return ZERO_ONE;
if(num == 2) return ONE_ZERO;
if(num == 3) return ONE_ONE;
}
}
Binary file modified circuit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions examples/neoweather/neoweather.device.nut
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#require "ws2812.class.nut:2.0.2"
#require "ws2812.class.nut:3.0.0"

class NeoWeather extends WS2812 {

Expand Down Expand Up @@ -487,7 +487,6 @@ agent.on("seteffect", function(val) {
const NUMPIXELS = 24;

spi <- hardware.spi257;
spi.configure(MSB_FIRST, 7500);
display <- NeoWeather(spi, NUMPIXELS);

server.log("Ready, running impOS "+imp.getsoftwareversion());
Expand Down

0 comments on commit 27d2419

Please sign in to comment.