/*------------------------------------------------------------------------
Python module to control Adafruit Dot Star addressable RGB LEDs.
This has some Known Issues(tm):
It's modeled after the Adafruit_DotStar Arduino library (C++), for
better or for worse. The idea is that the majority of existing Arduino
code for DotStar & NeoPixel LEDs can then port over with less fuss.
As such, it's less "Python-like" than it could (and perhaps should)
be...for example, RGB colors might be more elegantly expressed as
tuples or something other than the packed 32-bit integers used here.
Also, it does not have 100% feature parity with that library...e.g.
getPixels() is missing here, and this code allows changing the SPI
bitrate (Arduino lib does not).
There's no doc strings yet.
The library can use either hardware SPI or "bitbang" output....but one
must be careful in the latter case not to overlap the SPI GPIO pins...
once they're set as bitbang outputs by this code, they're no longer
usable for SPI even after the code exits (and not just by this library;
subsequent runs, other code, etc. all are locked out of SPI, only fix
seems to be a reboot). The library checks for an exact overlap between
the requested bitbang data & clock pins and the hardware SPI pins, and
will switch over to hardware SPI in that case...but partial overlaps
(just the data -or- clock pin, or if their positions are swapped) are
not protected.
Written by Phil Burgess for Adafruit Industries.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing products
from Adafruit!
------------------------------------------------------------------------
This file is part of the Adafruit Dot Star library.
Adafruit Dot Star is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
Adafruit Dot Star is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with NeoPixel. If not, see .
------------------------------------------------------------------------*/
#include
#include
#include
#include
#include
// From GPIO example code by Dom and Gert van Loo on elinux.org:
#define BCM2708_PERI_BASE 0x20000000
#define GPIO_BASE (BCM2708_PERI_BASE + 0x200000)
#define BLOCK_SIZE (4*1024)
#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
#define OUT_GPIO(g) *(gpio+((g)/10)) |= (1<<(((g)%10)*3))
#define SPI_MOSI_PIN 10
#define SPI_CLK_PIN 11
static volatile unsigned
*gpio = NULL, // Memory-mapped GPIO peripheral
*gpioSet, // Write bitmask of GPIO pins to set here
*gpioClr; // Write bitmask of GPIO pins to clear here
// SPI transfer operation setup. These are only used w/hardware SPI
// and LEDs at full brightness (or raw write); other conditions require
// per-byte processing. Explained further in the show() method.
static uint8_t header[] = { 0x00, 0x00, 0x00, 0x00 };
static struct spi_ioc_transfer xfer[3] = {
{ .rx_buf = 0,
.len = sizeof(header),
.delay_usecs = 0,
.bits_per_word = 8,
.cs_change = 0 },
{ .rx_buf = 0,
.delay_usecs = 0,
.bits_per_word = 8,
.cs_change = 0 },
{ .rx_buf = 0,
.len = 0,
.delay_usecs = 0,
.bits_per_word = 8,
.cs_change = 0 }
};
// Bitbang requires throttle on clock set/clear to avoid outpacing strip
#define TINYDELAY { volatile uint8_t x=2; while(x--); }
typedef struct { // Python object for DotStar strip
PyObject_HEAD
uint32_t numLEDs, // Number of pixels in strip
footerLen, // Length of the footer
dataMask, // Data pin bitmask if using bitbang SPI
clockMask, // Clock pin bitmask if bitbang SPI
bitrate; // SPI clock speed if using hardware SPI
int fd; // File descriptor if using hardware SPI
uint8_t *pixels, // -> pixel data
*pbuf, // -> pixel buffer for faster SPI writes
*footer, // -> footer (number of pixels as bits minimum)
dataPin, // Data pin # if bitbang SPI
clockPin, // Clock pin # if bitbang SPI
brightness[3]; // Global brightness setting
} DotStarObject;
// Allocate new DotStar object. There's a few ways this can be called:
// x = Adafruit_DotStar(nleds, datapin, clockpin) Bitbang output
// x = Adafruit_DotStar(nleds, bitrate) Use hardware SPI @ bitrate
// x = Adafruit_DotStar(nleds) Hardware SPI @ default rate
// x = Adafruit_DotStar() 0 LEDs, HW SPI, default rate
// 0 LEDs is valid, but one must then pass a properly-sized and -rendered
// bytearray to the show() method.
static PyObject *DotStar_new(
PyTypeObject *type, PyObject *arg, PyObject *kw) {
DotStarObject *self = NULL;
uint8_t *pixels = NULL, *pbuf = NULL, *footer = NULL, dPin = 0xFF, cPin = 0xFF;
uint32_t n_pixels = 0, bitrate = 8000000, footer_len;
switch(PyTuple_Size(arg)) {
case 3: // Pixel count, data pin, clock pin
if(!PyArg_ParseTuple(arg, "Ibb", &n_pixels, &dPin, &cPin))
return NULL;
// If pins happen to correspond to hardware SPI data and
// clock, hardware SPI is used instead. Because reasons.
if((dPin == SPI_MOSI_PIN) && (cPin == SPI_CLK_PIN))
dPin = cPin = 0xFF;
break;
case 2: // Pixel count, hardware SPI bitrate
if(!PyArg_ParseTuple(arg, "II", &n_pixels, &bitrate))
return NULL;
break;
case 1: // Pixel count (hardware SPI w/default bitrate)
if(!PyArg_ParseTuple(arg, "I", &n_pixels)) return NULL;
break;
case 0: // No LED buffer (raw writes only), default SPI bitrate
break;
default: // Unexpected number of arguments
return NULL;
}
if((!n_pixels) || ((pixels = (uint8_t *)malloc(n_pixels * 4)))) {
if( dPin == 0xFF && cPin == 0xFF ) {
pbuf = (uint8_t *)malloc(n_pixels * 4);
}
// Dynamically build the correct sized footer
footer_len = (n_pixels / 8) + ((n_pixels % 8) != 0) + 1;
if (footer_len < 4 ) footer_len = 4;
if (footer = (uint8_t *)malloc(footer_len)) {
memset(footer, 0xff, footer_len);
}
if (footer && (self = (DotStarObject *)type->tp_alloc(type, 0))) {
self->numLEDs = n_pixels;
self->footerLen = footer_len;
self->dataMask = 0;
self->clockMask = 0;
self->bitrate = 8000000;
self->fd = -1;
self->pixels = pixels; // NULL if 0 pixels
self->pbuf = pbuf; // Only allocated if in SPI mode
self->footer = footer; // Dynamically allocated footer
self->dataPin = dPin;
self->clockPin = cPin;
self->brightness[0] = self->brightness[1] = self->brightness[2] = 0;
Py_INCREF(self);
} else {
if(pixels) free(pixels);
if(footer) free(footer);
if(pbuf) free(pbuf);
}
}
Py_INCREF(self);
return (PyObject *)self;
}
// Initialize DotStar object
static int DotStar_init(DotStarObject *self, PyObject *arg) {
uint8_t *ptr;
uint32_t i;
// Set first byte of each 4-byte pixel to 0xFF, rest to 0x00 (off)
for(ptr = self->pixels, i=0; inumLEDs; i++) {
*ptr++ = 0xFF; *ptr++ = 0x00; *ptr++ = 0x00; *ptr++ = 0x00;
}
return 0;
}
// Initialize pins/SPI for output
static PyObject *begin(DotStarObject *self) {
if(self->dataPin == 0xFF) { // Use hardware SPI
if((self->fd = open("/dev/spidev0.0", O_RDWR)) < 0) {
printf("Can't open /dev/spidev0.0 (try 'sudo')\n");
return NULL;
}
uint8_t mode = SPI_MODE_0 | SPI_NO_CS;
ioctl(self->fd, SPI_IOC_WR_MODE, &mode);
// The actual data rate may be less than requested.
// Hardware SPI speed is a function of the system core
// frequency and the smallest power-of-two prescaler
// that will not exceed the requested rate.
// e.g. 8 MHz request: 250 MHz / 32 = 7.8125 MHz.
ioctl(self->fd, SPI_IOC_WR_MAX_SPEED_HZ, self->bitrate);
xfer[0].tx_buf = (unsigned long)header;
xfer[0].speed_hz = xfer[1].speed_hz = xfer[2].speed_hz =
self->bitrate;
} else { // Use bitbang "soft" SPI (any 2 pins)
if(gpio == NULL) { // First time accessing GPIO?
int fd;
if((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
printf("Can't open /dev/mem (try 'sudo')\n");
return NULL;
}
gpio = (volatile unsigned *)mmap( // Memory-map I/O
NULL, // Any adddress will do
BLOCK_SIZE, // Mapped block length
PROT_READ|PROT_WRITE, // Enable read+write
MAP_SHARED, // Shared w/other processes
fd, // File to map
GPIO_BASE); // Offset to GPIO registers
close(fd); // Not needed after mmap()
if(gpio == MAP_FAILED) {
err("Can't mmap()");
return NULL;
}
gpioSet = &gpio[7];
gpioClr = &gpio[10];
}
self->dataMask = 1 << self->dataPin;
self->clockMask = 1 << self->clockPin;
// Set 2 pins as outputs. Must use INP before OUT.
INP_GPIO(self->dataPin); OUT_GPIO(self->dataPin);
INP_GPIO(self->clockPin); OUT_GPIO(self->clockPin);
*gpioClr = self->dataMask | self->clockMask; // data+clock LOW
}
Py_INCREF(Py_None);
return Py_None;
}
// Set strip data to 'off' (just clears buffer, does not write to strip)
static PyObject *clear(DotStarObject *self) {
uint8_t *ptr;
uint32_t i;
for(ptr = self->pixels, i=0; inumLEDs; i++, ptr += 4) {
ptr[1] = 0x00; ptr[2] = 0x00; ptr[3] = 0x00;
}
Py_INCREF(Py_None);
return Py_None;
}
// Set global strip brightness. This does not have an immediate effect;
// must be followed by a call to show(). Not a fan of this...for various
// reasons I think it's better handled in one's application, but it's here
// for parity with the Arduino NeoPixel library.
static PyObject *setBrightness(DotStarObject *self, PyObject *arg) {
uint8_t br, bg, bb;
switch(PyTuple_Size(arg)) {
case 3:
if(!PyArg_ParseTuple(arg, "bbb", &br, &bg, &bb)) return NULL;
break;
case 1: // Single brightness
if(!PyArg_ParseTuple(arg, "b", &br)) return NULL;
bg = br;
bb = br;
break;
default:
return NULL;
}
// Stored brightness value is different than what's passed. This
// optimizes the actual scaling math later, allowing a fast multiply
// and taking the MSB. 'brightness' is a uint8_t, adding 1 here may
// (intentionally) roll over...so 0 = max brightness (color values
// are interpreted literally; no scaling), 1 = min brightness (off),
// 255 = just below max brightness.
self->brightness[0] = br + 1;
self->brightness[1] = bg + 1;
self->brightness[2] = bb + 1;
Py_INCREF(Py_None);
return Py_None;
}
// Valid syntaxes:
// x.setPixelColor(index, red, green, blue)
// x.setPixelColor(index, 0x00RRGGBB)
static PyObject *setPixelColor(DotStarObject *self, PyObject *arg) {
uint32_t i, v;
uint8_t r, g, b;
switch(PyTuple_Size(arg)) {
case 4: // Index, r, g, b
if(!PyArg_ParseTuple(arg, "Ibbb", &i, &r, &g, &b))
return NULL;
break;
case 2: // Index, value
if(!PyArg_ParseTuple(arg, "II", &i, &v))
return NULL;
r = v >> 16;
g = v >> 8;
b = v;
break;
default:
return NULL;
}
if(i < self->numLEDs) {
uint8_t *ptr = &self->pixels[i * 4 + 1];
*ptr++ = g; // Data internally is stored
*ptr++ = b; // in GBR order; it's what
*ptr++ = r; // the strip expects.
}
Py_INCREF(Py_None);
return Py_None;
}
// Private method. Writes pixel data without brightness scaling.
static void raw_write(DotStarObject *self, uint8_t *ptr, uint32_t len) {
if(self->fd >= 0) { // Hardware SPI
xfer[2].tx_buf = (unsigned long)self->footer;
xfer[2].len = self->footerLen;
xfer[1].tx_buf = (unsigned long)ptr;
xfer[1].len = len;
// All that spi_ioc_transfer struct stuff earlier in
// the code is so we can use this single ioctl to concat
// the header/data/footer into one operation:
(void)ioctl(self->fd, SPI_IOC_MESSAGE(3), xfer);
} else if(self->dataMask) { // Bitbang
unsigned char byte, bit;
*gpioClr = self->dataMask;
for(bit=0; bit<32; bit++) { // Header
*gpioSet = self->clockMask; TINYDELAY
*gpioClr = self->clockMask; TINYDELAY
}
while(len--) {
byte = *ptr++;
for(bit = 0x80; bit; bit >>= 1) {
if(byte & bit) *gpioSet = self->dataMask;
else *gpioClr = self->dataMask;
*gpioSet = self->clockMask; TINYDELAY
*gpioClr = self->clockMask; TINYDELAY
}
}
*gpioClr = self->dataMask;
for(bit=0; bit<32; bit++) { // Footer
*gpioSet = self->clockMask; TINYDELAY
*gpioClr = self->clockMask; TINYDELAY
}
}
}
// Issue data to strip. Optional arg = raw bytearray to issue to strip
// (else object's pixel buffer is used). If passing raw data, it must
// be in strip-ready format (4 bytes/pixel, 0xFF/B/G/R) and no brightness
// scaling is performed...it's all about speed (for POV & stuff).
static PyObject *show(DotStarObject *self, PyObject *arg) {
if(PyTuple_Size(arg) == 1) { // Raw bytearray passed
Py_buffer buf;
if(!PyArg_ParseTuple(arg, "s*", &buf)) return NULL;
raw_write(self, buf.buf, buf.len);
PyBuffer_Release(&buf);
} else { // Write object's pixel buffer
if(self->brightness[0] == 0 && self->brightness[1] == 0 && self->brightness[2] == 0) { // Send raw (no scaling)
raw_write(self, self->pixels, self->numLEDs * 4);
} else { // Adjust brightness during write
uint32_t i;
uint8_t *ptr = self->pixels;
if(self->fd >= 0) { // Hardware SPI
if(self->pbuf) {
uint8_t *buf = self->pbuf;
for(i=0; inumLEDs; ++i) {
*buf++ = 0xFF; ++ptr;
*buf++ = (*ptr++ * ((uint16_t)self->brightness[1])) >> 8;
*buf++ = (*ptr++ * ((uint16_t)self->brightness[2])) >> 8;
*buf++ = (*ptr++ * ((uint16_t)self->brightness[0])) >> 8;
}
raw_write(self, self->pbuf, self->numLEDs * 4);
} else {
uint8_t x[4];
x[0] = 0xFF;
write(self->fd, &header, sizeof(header));
for(i=0; inumLEDs; i++, ptr += 4) {
x[1] = (ptr[1] * ((uint16_t)self->brightness[1])) >> 8;
x[2] = (ptr[2] * ((uint16_t)self->brightness[2])) >> 8;
x[3] = (ptr[3] * ((uint16_t)self->brightness[0])) >> 8;
write(self->fd, &x, sizeof(x));
}
write(self->fd, self->footer, self->footerLen);
}
} else if(self->dataMask) {
uint32_t word, bit;
*gpioClr = self->dataMask;
for(bit=0; bit<32; bit++) { // Header
*gpioSet = self->clockMask; TINYDELAY
*gpioClr = self->clockMask; TINYDELAY
}
for(i=0; inumLEDs; i++, ptr += 4) {
word = 0xFF000000 |
(((ptr[1] * ((uint16_t)self->brightness[1])) & 0xFF00) << 8) |
( (ptr[2] * ((uint16_t)self->brightness[2])) & 0xFF00 ) |
( (ptr[3] * ((uint16_t)self->brightness[0])) >> 8);
for(bit = 0x80000000; bit; bit >>= 1) {
if(word & bit)
*gpioSet = self->dataMask;
else
*gpioClr = self->dataMask;
*gpioSet = self->clockMask;
TINYDELAY
*gpioClr = self->clockMask;
TINYDELAY
}
}
*gpioClr = self->dataMask;
for(bit=0; bitfooterLen*8; bit++) { // Footer
*gpioSet = self->clockMask; TINYDELAY
*gpioClr = self->clockMask; TINYDELAY
}
}
}
}
Py_INCREF(Py_None);
return Py_None;
}
// Given separate R, G, B, return a packed 32-bit color.
// Meh, mostly here for parity w/Arduino library.
static PyObject *Color(DotStarObject *self, PyObject *arg) {
uint8_t r, g, b;
PyObject *result;
if(!PyArg_ParseTuple(arg, "bbb", &r, &g, &b)) return NULL;
result = Py_BuildValue("I", (r << 16) | (b << 8) | g);
Py_INCREF(result);
return result;
}
// Return color of previously-set pixel (as packed 32-bit value)
static PyObject *getPixelColor(DotStarObject *self, PyObject *arg) {
uint32_t i;
uint8_t r=0, g=0, b=0;
PyObject *result;
if(!PyArg_ParseTuple(arg, "I", &i)) return NULL;
if(i < self->numLEDs) {
uint8_t *ptr = &self->pixels[i * 4 + 1];
b = *ptr++; g = *ptr++; r = *ptr++;
}
result = Py_BuildValue("I", (r << 16) | (g << 8) | b);
Py_INCREF(result);
return result;
}
// Return strip length
static PyObject *numPixels(DotStarObject *self) {
PyObject *result = Py_BuildValue("I", self->numLEDs);
Py_INCREF(result);
return result;
}
// Return strip brightness
static PyObject *getBrightness(DotStarObject *self) {
PyObject *result = Py_BuildValue("H", (uint8_t)(self->brightness[0] - 1));
Py_INCREF(result);
return result;
}
// DON'T USE THIS. One of those "parity with Arduino library" methods,
// but current'y doesn't work (and might never). Supposed to return strip's
// pixel buffer, but doesn't seem to be an easy way to do this in Python 2.X.
// That's okay -- instead of 'raw' access to a strip's previously-allocated
// buffer, a Python program can instead allocate its own buffer and pass this
// to the show() method, basically achieving the same thing and then some.
static PyObject *getPixels(DotStarObject *self) {
PyObject *result = Py_BuildValue("s#",
self->pixels, self->numLEDs * 4);
Py_INCREF(result);
return result;
}
static PyObject *_close(DotStarObject *self) {
if(self->fd) {
close(self->fd);
self->fd = -1;
} else {
INP_GPIO(self->dataPin);
INP_GPIO(self->clockPin);
self->dataMask = 0;
self->clockMask = 0;
}
Py_INCREF(Py_None);
return Py_None;
}
static void DotStar_dealloc(DotStarObject *self) {
_close(self);
if(self->pixels) free(self->pixels);
if(self->footer) free(self->footer);
if(self->pbuf) free(self->pbuf);
self->ob_type->tp_free((PyObject *)self);
}
// Method names are silly and inconsistent, but following NeoPixel
// and prior libraries, which formed through centuries of accretion.
static PyMethodDef methods[] = {
{ "begin" , (PyCFunction)begin , METH_NOARGS , NULL },
{ "clear" , (PyCFunction)clear , METH_NOARGS , NULL },
{ "setBrightness", (PyCFunction)setBrightness, METH_VARARGS, NULL },
{ "setPixelColor", (PyCFunction)setPixelColor, METH_VARARGS, NULL },
{ "show" , (PyCFunction)show , METH_VARARGS, NULL },
{ "Color" , (PyCFunction)Color , METH_VARARGS, NULL },
{ "getPixelColor", (PyCFunction)getPixelColor, METH_VARARGS, NULL },
{ "numPixels" , (PyCFunction)numPixels , METH_NOARGS , NULL },
{ "getBrightness", (PyCFunction)getBrightness, METH_NOARGS , NULL },
{ "getPixels" , (PyCFunction)getBrightness, METH_NOARGS , NULL },
{ "close" , (PyCFunction)_close , METH_NOARGS , NULL },
{ NULL, NULL, 0, NULL }
};
static PyTypeObject DotStarObjectType = {
PyObject_HEAD_INIT(NULL)
0, // ob_size (not used, always set to 0)
"dotstar.Adafruit_DotStar", // tp_name (module name, object name)
sizeof(DotStarObject), // tp_basicsize
0, // tp_itemsize
(destructor)DotStar_dealloc, // tp_dealloc
0, // tp_print
0, // tp_getattr
0, // tp_setattr
0, // tp_compare
0, // tp_repr
0, // tp_as_number
0, // tp_as_sequence
0, // tp_as_mapping
0, // tp_hash
0, // tp_call
0, // tp_str
0, // tp_getattro
0, // tp_setattro
0, // tp_as_buffer
Py_TPFLAGS_DEFAULT, // tp_flags
0, // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
methods, // tp_methods
0, // tp_members
0, // tp_getset
0, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
(initproc)DotStar_init, // tp_init
0, // tp_alloc
DotStar_new, // tp_new
0, // tp_free
};
PyMODINIT_FUNC initdotstar(void) { // Module initialization function
PyObject* m;
if((m = Py_InitModule("dotstar", methods)) &&
(PyType_Ready(&DotStarObjectType) >= 0)) {
Py_INCREF(&DotStarObjectType);
PyModule_AddObject(m, "Adafruit_DotStar",
(PyObject *)&DotStarObjectType);
}
}