Skip to content

Instantly share code, notes, and snippets.

@blindman2k
Created May 14, 2014 17:42
Show Gist options
  • Save blindman2k/cfaf8cff1da87e6d9c83 to your computer and use it in GitHub Desktop.
Save blindman2k/cfaf8cff1da87e6d9c83 to your computer and use it in GitHub Desktop.
This is a new Neopixel class designed specifically for rectangular X/Y coordinate systems but still works fine for linear strips. Note the function names have changed.
class NeoPixels {
// when instantiated, the neopixel 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 - which makes the class faster.
bits = null;
// Like bits, this blob holds the waveform to send the color [0,0,0], to clear pixels faster
clearblob = null;
// private variables passed into the constructor
spi = null; // imp SPI interface (pre-configured)
width = null; // the maximum X dimension
height = null; // the maximum Y dimension
frameSize = null; // number of pixels per frame (height x width)
frame = null; // a blob to hold the current frame buffer
canvas = null; // 2d array holding the next buffer to be drawn
snapshot = null; // holds a copy of the canvas for quick drawing of a background/template
cache = null; // holds the hsl2rgb cache
// _spi - A configured spi (MSB_FIRST, 7.5MHz)
// _width - X pixels wide
// _height - Y pixels high
constructor(_spi, _width, _height = 1) {
// This class uses SPI to emulate the newpixels' 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
const SPICLK = 7500; // kHz
const ZERO = 0xC0;
const ONE = 0xF8;
const BYTESPERPIXEL = 24;
spi = _spi;
width = _width;
height= _height;
spi.configure(MSB_FIRST, SPICLK);
frameSize = width * height;
frame = blob(frameSize*BYTESPERPIXEL + 1);
clearblob = blob(BYTESPERPIXEL);
cache = {};
// prepare the bits array and the clearblob blob
initialize();
// Blank the screen
clear();
write();
}
// fill the array of representative 1-wire waveforms.
// done by the constructor at instantiation.
function initialize() {
// fill the bits array first
bits = array(256);
for (local i = 0; i < 256; i++) {
local valblob = blob(BYTESPERPIXEL / 3);
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;
}
// now fill the clearblob
for(local j = 0; j < BYTESPERPIXEL; j++) {
clearblob.writen(ZERO, 'b');
}
// finally, prepare the canvas
canvas = array(width);
for (local x = 0; x < width; x++) {
canvas[x] = array(height);
for (local y = 0; y < height; y++) {
canvas[x][y] = null;
}
}
}
// draw a single pixel onto the canvas
function drawPixel(x, y, color) { // or pdp
if (x >= 0 && x < width && y >= 0 && y < height) {
canvas[x][y] = color;
}
}
// draw a box or line on the canvas
function drawBox(x1, y1, x2, y2, color) { // or pdb
// Swap the coordinates to its always drawing uphill
if (x2 < x1) { local x3 = x1; x1 = x2; x2 = x3 }
if (y2 < y1) { local y3 = y1; y1 = y2; y2 = y3 }
for (local x = x1; x <= x2; x++) {
for (local y = y1; y <= y2; y++) {
if (x >= 0 && x < width && y >= 0 && y < height) {
canvas[x][y] = color;
}
}
}
}
// wipes the canvas to a single colour (or black)
function clear(color = null) { // or pcf
for (local x = 0; x < width; x++) {
for (local y = 0; y < height; y++) {
canvas[x][y] = color;
}
}
}
// sends the canvas to the neopixels
function write() {
local color, x, y, yy, alt = true;
local fwb = frame.writeblob.bindenv(frame);
frame.seek(0);
for (x = 0; x < width; x++) {
alt = !alt;
for (y = 0; y < height; y++) {
// Alternate direction of every alternate row
yy = alt ? (height - y - 1) : y;
color = canvas[x][yy];
if (color) {
fwb(bits[color[1]]);
fwb(bits[color[0]]);
fwb(bits[color[2]]);
} else {
fwb(clearblob);
}
}
}
frame.writen(0, 'b'); // Drive MOSI low
// All done. Send.
spi.write(frame);
}
// stores the current canvas for future fast use
function storeSnapshot() {
snapshot = array(width);
for (local x = 0; x < width; x++) {
snapshot[x] = clone canvas[x];
}
}
// restores a snapshot as the primary canvas
function restoreSnapshot() {
if (snapshot) {
for (local x = 0; x < width; x++) {
canvas[x] = clone snapshot[x];
}
return true;
} else {
return false;
}
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 255] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
function hsl2rgb(h, s, l, _cache=false) {
local cachekey = format("%02X%02X%02X", h, s, l);
if (cachekey in cache) return cache[cachekey];
local hue2rgb = function(p, q, t) {
if (t < 0.0) t += 1.0;
if (t > 1.0) t -= 1.0;
if (t < 0.16666666) return p + (q - p) * 6.0 * t;
if (t < 0.5) return q;
if (t < 0.66666666) return p + (q - p) * (0.66666666 - t) * 6.0;
return p;
}
local r = 0.0;
local g = 0.0;
local b = 0.0;
h /= 255.0;
s /= 255.0;
l /= 255.0;
if (s == 0) {
r = g = b = l; // achromatic
} else if (l == 0) {
r = g = b = l; // off
} else {
local q = l < 0.5 ? l * (1.0 + s) : l + s - l * s;
local p = 2.0 * l - q;
r = hue2rgb(p, q, h + 0.33333333);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 0.33333333);
}
local rgb = [math.floor(r*255.0), math.floor(g*255.0), math.floor(b*255.0)];
if (_cache) cache[cachekey] <- rgb;
return rgb;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment