First commit!

This commit is contained in:
Ladyada 2011-10-04 13:09:09 -04:00
commit f43a8086e1
6 changed files with 633 additions and 0 deletions

View File

@ -0,0 +1,185 @@
// Arduino "bridge" code between host computer and WS2801-based digital
// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use
// with USB-native boards such as Teensy or Adafruit 32u4 Breakout;
// works on normal serial Arduinos, but throughput is severely limited.
// LED data is streamed, not buffered, making this suitable for larger
// installations (e.g. video wall, etc.) than could otherwise be held
// in the Arduino's limited RAM.
// Some effort is put into avoiding buffer underruns (where the output
// side becomes starved of data). The WS2801 latch protocol, being
// delay-based, could be inadvertently triggered if the USB bus or CPU
// is swamped with other tasks. This code buffers incoming serial data
// and introduces intentional pauses if there's a threat of the buffer
// draining prematurely. The cost of this complexity is somewhat
// reduced throughput, the gain is that most visual glitches are
// avoided (though ultimately a function of the load on the USB bus and
// host CPU, and out of our control).
// LED data and clock lines are connected to the Arduino's SPI output.
// On traditional Arduino boards, SPI data out is digital pin 11 and
// clock is digital pin 13. On both Teensy and the 32u4 Breakout,
// data out is pin B2, clock is B1. LEDs should be externally
// powered -- trying to run any more than just a few off the Arduino's
// 5V line is generally a Bad Idea. LED ground should also be
// connected to Arduino ground.
#include <SPI.h>
// LED pin for Adafruit 32u4 Breakout Board:
//#define LED_DDR DDRE
//#define LED_PORT PORTE
//#define LED_PIN _BV(PORTE6)
// LED pin for Teensy:
//#define LED_DDR DDRD
//#define LED_PORT PORTD
//#define LED_PIN _BV(PORTD6)
// LED pin for Arduino:
#define LED_DDR DDRB
#define LED_PORT PORTB
#define LED_PIN _BV(PORTB5)
// A 'magic word' (along with LED count & checksum) precedes each block
// of LED data; this assists the microcontroller in syncing up with the
// host-side software and properly issuing the latch (host I/O is
// likely buffered, making usleep() unreliable for latch). You may see
// an initial glitchy frame or two until the two come into alignment.
// The magic word can be whatever sequence you like, but each character
// should be unique, and frequent pixel values like 0 and 255 are
// avoided -- fewer false positives. The host software will need to
// generate a compatible header: immediately following the magic word
// are three bytes: a 16-bit count of the number of LEDs (high byte
// first) followed by a simple checksum value (high byte XOR low byte
// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B,
// where 0 = off and 255 = max brightness.
static const uint8_t magic[] = {'A','d','a'};
#define MAGICSIZE sizeof(magic)
#define HEADERSIZE (MAGICSIZE + 3)
#define MODE_HEADER 0
#define MODE_HOLD 1
#define MODE_DATA 2
void setup()
{
// Dirty trick: the circular buffer for serial data is 256 bytes,
// and the "in" and "out" indices are unsigned 8-bit types -- this
// much simplifies the cases where in/out need to "wrap around" the
// beginning/end of the buffer. Otherwise there'd be a ton of bit-
// masking and/or conditional code every time one of these indices
// needs to change, slowing things down tremendously.
uint8_t
buffer[256],
indexIn = 0,
indexOut = 0,
mode = MODE_HEADER,
hi, lo, chk, i, spiFlag;
int16_t
bytesBuffered = 0,
c;
int32_t
bytesRemaining;
unsigned long
t = 0;
LED_DDR |= LED_PIN; // Enable output for LED
LED_PORT &= ~LED_PIN; // LED off
Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK!
SPI.begin();
SPI.setBitOrder(MSBFIRST);
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz
// WS2801 datasheet recommends max SPI clock of 2 MHz, and 50 Ohm
// resistors on SPI lines for impedance matching. In practice and
// at short distances, 2 MHz seemed to work reliably enough without
// resistors, and 4 MHz was possible with a 220 Ohm resistor on the
// SPI clock line only. Your mileage may vary. Experiment!
// SPI.setClockDivider(SPI_CLOCK_DIV4); // 4 MHz
// loop() is avoided as even that small bit of function overhead
// has a measurable impact on this code's overall throughput.
for(;;) {
// Implementation is a simple finite-state machine.
// Regardless of mode, check for serial input each time:
if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) {
buffer[indexIn++] = c;
bytesBuffered++;
}
switch(mode) {
case MODE_HEADER:
// In header-seeking mode. Is there enough data to check?
if(bytesBuffered >= HEADERSIZE) {
// Indeed. Check for a 'magic word' match.
for(i=0; (i<MAGICSIZE) && (buffer[indexOut++] == magic[i++]););
if(i == MAGICSIZE) {
// Magic word matches. Now how about the checksum?
hi = buffer[indexOut++];
lo = buffer[indexOut++];
chk = buffer[indexOut++];
if(chk == (hi ^ lo ^ 0x55)) {
// Checksum looks valid. Get 16-bit LED count, add 1
// (# LEDs is always > 0) and multiply by 3 for R,G,B.
bytesRemaining = 3L * (256L * (long)hi + (long)lo + 1L);
bytesBuffered -= 3;
mode = MODE_HOLD; // Proceed to latch wait mode
spiFlag = 0; // No data out yet
} else {
// Checksum didn't match; search resumes after magic word.
indexOut -= 3; // Rewind
}
} // else no header match. Resume at first mismatched byte.
bytesBuffered -= i;
}
break;
case MODE_HOLD:
// Ostensibly "waiting for the latch from the prior frame
// to complete" mode, but may also revert to this mode when
// underrun prevention necessitates a delay.
if(micros() < t) break; // Still holding; continue buffering.
// Latch/delay complete. Advance to data-issuing mode...
LED_PORT &= ~LED_PIN; // LED off
mode = MODE_DATA; // ...and fall through (no break):
case MODE_DATA:
while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte
if(bytesRemaining > 0) {
if(bytesBuffered > 0) {
SPDR = buffer[indexOut++]; // Issue next byte
bytesBuffered--;
bytesRemaining--;
spiFlag = 1;
}
// If serial buffer is threatening to underrun, start
// introducing progressively longer pauses to allow more
// data to arrive (up to a point).
if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) {
mode = MODE_HOLD;
t = micros() + 60 + (32 - bytesBuffered) * 20;
}
} else {
// End of data -- issue latch:
t = micros() + 1000; // Latch duration = 1000 uS
LED_PORT |= LED_PIN; // LED on
mode = MODE_HEADER; // Begin next header search
}
} // end switch
} // end for(;;)
}
void loop()
{
// Not used. See note in setup() function.
}

9
C/Makefile Normal file
View File

@ -0,0 +1,9 @@
EXECS = colorswirl
all: $(EXECS)
colorswirl: colorswirl.c
cc -O2 colorswirl.c -lm -o colorswirl
clean:
rm -f $(EXECS) *.o

163
C/colorswirl.c Normal file
View File

@ -0,0 +1,163 @@
/*
"Colorswirl" LED demo. This is the host PC-side code written in C;
intended for use with a USB-connected Arduino microcontroller running the
accompanying LED streaming code. Requires one strand of Digital RGB LED
Pixels (Adafruit product ID #322, specifically the newer WS2801-based type,
strand of 25) and a 5 Volt power supply (such as Adafruit #276). You may
need to adapt the code and the hardware arrangement for your specific
configuration.
This is a command-line program. It expects a single parameter, which is
the serial port device name, e.g.:
./colorswirl /dev/tty.usbserial-A60049KO
*/
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#include <time.h>
#include <math.h>
#define N_LEDS 25 // Max of 65536
int main(int argc,char *argv[])
{
int fd, i, bytesToGo, bytesSent, totalBytesSent = 0,
frame = 0, hue1, hue2, brightness;
unsigned char buffer[6 + (N_LEDS * 3)], // Header + 3 bytes per LED
lo, r, g, b;
double sine1, sine2;
time_t t, start, prev;
struct termios tty;
if(argc < 2) {
(void)printf("Usage: %s device\n", argv[0]);
return 1;
}
if((fd = open(argv[1],O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
(void)printf("Can't open device '%s'.\n", argv[1]);
return 1;
}
// Serial port config swiped from RXTX library (rxtx.qbang.org):
tcgetattr(fd, &tty);
tty.c_iflag = INPCK;
tty.c_lflag = 0;
tty.c_oflag = 0;
tty.c_cflag = CREAD | CS8 | CLOCAL;
tty.c_cc[ VMIN ] = 0;
tty.c_cc[ VTIME ] = 0;
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
tcsetattr(fd, TCSANOW, &tty);
bzero(buffer, sizeof(buffer)); // Clear LED buffer
// Header only needs to be initialized once, not
// inside rendering loop -- number of LEDs is constant:
buffer[0] = 'A'; // Magic word
buffer[1] = 'd';
buffer[2] = 'a';
buffer[3] = (N_LEDS - 1) >> 8; // LED count high byte
buffer[4] = (N_LEDS - 1) & 0xff; // LED count low byte
buffer[5] = buffer[3] ^ buffer[4] ^ 0x55; // Checksum
sine1 = 0.0;
hue1 = 0;
prev = start = time(NULL); // For bandwidth statistics
for(;;) {
sine2 = sine1;
hue2 = hue1;
// Start at position 6, after the LED header/magic word
for(i = 6; i < sizeof(buffer); ) {
// Fixed-point hue-to-RGB conversion. 'hue2' is an
// integer in the range of 0 to 1535, where 0 = red,
// 256 = yellow, 512 = green, etc. The high byte
// (0-5) corresponds to the sextant within the color
// wheel, while the low byte (0-255) is the
// fractional part between primary/secondary colors.
lo = hue2 & 255;
switch((hue2 >> 8) % 6) {
case 0:
r = 255;
g = lo;
b = 0;
break;
case 1:
r = 255 - lo;
g = 255;
b = 0;
break;
case 2:
r = 0;
g = 255;
b = lo;
break;
case 3:
r = 0;
g = 255 - lo;
b = 255;
break;
case 4:
r = lo;
g = 0;
b = 255;
break;
case 5:
r = 255;
g = 0;
b = 255 - lo;
break;
}
// Resulting hue is multiplied by brightness in the
// range of 0 to 255 (0 = off, 255 = brightest).
// Gamma corrrection (the 'pow' function here) adjusts
// the brightness to be more perceptually linear.
brightness = (int)(pow(0.5+sin(sine2)*0.5,3.0)*255.0);
buffer[i++] = (r * brightness) / 255;
buffer[i++] = (g * brightness) / 255;
buffer[i++] = (b * brightness) / 255;
// Each pixel is offset in both hue and brightness
hue2 += 40;
sine2 += 0.3;
}
// Slowly rotate hue and brightness in opposite directions
hue1 = (hue1 + 5) % 1536;
sine1 -= .03;
// Issue color data to LEDs. Each OS is fussy in different
// ways about serial output. This arrangement of drain-and-
// write-loop seems to be the most relable across platforms:
tcdrain(fd);
for(bytesSent=0, bytesToGo=sizeof(buffer); bytesToGo > 0;) {
if((i=write(fd,&buffer[bytesSent],bytesToGo)) > 0) {
bytesToGo -= i;
bytesSent += i;
}
}
// Keep track of byte and frame counts for statistics
totalBytesSent += sizeof(buffer);
frame++;
// Update statistics once per second
if((t = time(NULL)) != prev) {
(void)printf(
"Average frames/sec: %d, bytes/sec: %d\n",
(int)((float)frame / (float)(t - start)),
(int)((float)totalBytesSent / (float)(t - start)));
prev = t;
}
}
close(fd);
return 0;
}

View File

@ -0,0 +1,153 @@
// "Adalight" is a do-it-yourself facsimile of the Philips Ambilight concept
// for desktop computers and home theater PCs. This is the host PC-side code
// written in Processing; intended for use with a USB-connected Arduino
// microcontroller running the accompanying LED streaming code. Requires one
// strand of Digital RGB LED Pixels (Adafruit product ID #322, specifically
// the newer WS2801-based type, strand of 25) and a 5 Volt power supply (such
// as Adafruit #276). You may need to adapt the code and the hardware
// arrangement for your specific display configuration.
// Screen capture adapted from code by Cedrik Kiefer (processing.org forum)
import java.awt.*;
import java.awt.image.*;
import processing.serial.*;
// This array contains the 2D image coordinates corresponding to each pixel
// in the LED strand, which forms a ring around the perimeter of the screen
// (with a one pixel gap at the bottom to accommodate the monitor stand).
static final int coord[][] = new int[][] {
{3,5}, {2,5}, {1,5}, {0,5}, // Bottom edge, left half
{0,4}, {0,3}, {0,2}, {0,1}, // Left edge
{0,0}, {1,0}, {2,0}, {3,0}, {4,0}, {5,0}, {6,0}, {7,0}, {8,0}, // Top edge
{8,1}, {8,2}, {8,3}, {8,4}, // Right edge
{8,5}, {7,5}, {6,5}, {5,5} // Bottom edge, right half
};
static final int arrayWidth = 9, // Width of Adalight array, in LED pixels
arrayHeight = 6, // Height of Adalight array, in LED pixels
imgScale = 20, // Size of displayed preview
samples = 20, // Samples (per axis) when down-scaling
s2 = samples * samples;
byte[] buffer = new byte[6 + coord.length * 3];
byte[][] gamma = new byte[256][3];
GraphicsDevice[] gs;
PImage preview = createImage(arrayWidth, arrayHeight, RGB);
Rectangle bounds;
Serial port;
void setup() {
GraphicsEnvironment ge;
DisplayMode mode;
int i;
float f;
port = new Serial(this, Serial.list()[0], 115200);
size(arrayWidth * imgScale, arrayHeight * imgScale, JAVA2D);
// Initialize capture code for full screen dimensions:
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
gs = ge.getScreenDevices();
mode = gs[0].getDisplayMode();
bounds = new Rectangle(0, 0, screen.width, screen.height);
// A special header / magic word is expected by the corresponding LED
// streaming code running on the Arduino. This only needs to be initialized
// once (not in draw() loop) because the number of LEDs remains constant:
buffer[0] = 'A'; // Magic word
buffer[1] = 'd';
buffer[2] = 'a';
buffer[3] = byte((coord.length - 1) >> 8); // LED count high byte
buffer[4] = byte((coord.length - 1) & 0xff); // LED count low byte
buffer[5] = byte(buffer[3] ^ buffer[4] ^ 0x55); // Checksum
// Pre-compute gamma correction table for LED brightness levels:
for(i = 0; i < 256; i++) {
f = pow(float(i) / 255.0, 2.8);
gamma[i][0] = byte(f * 255.0);
gamma[i][1] = byte(f * 240.0);
gamma[i][2] = byte(f * 220.0);
}
}
void draw () {
BufferedImage desktop;
PImage screenShot;
int i, j, c;
// Capture screen
try {
desktop = new Robot(gs[0]).createScreenCapture(bounds);
}
catch(AWTException e) {
System.err.println("Screen capture failed.");
return;
}
screenShot = new PImage(desktop); // Convert Image to PImage
screenShot.loadPixels(); // Make pixel array readable
// Downsample blocks of interest into LED output buffer:
preview.loadPixels(); // Also display in preview image
j = 6; // Data follows LED header / magic word
for(i = 0; i < coord.length; i++) { // For each LED...
c = block(screenShot, coord[i][0], coord[i][1]);
buffer[j++] = gamma[(c >> 16) & 0xff][0];
buffer[j++] = gamma[(c >> 8) & 0xff][1];
buffer[j++] = gamma[ c & 0xff][2];
preview.pixels[coord[i][1] * arrayWidth + coord[i][0]] = c;
}
preview.updatePixels();
// Show preview image
scale(imgScale);
image(preview,0,0);
println(frameRate);
port.write(buffer);
}
// This method computes a single pixel value filtered down from a rectangular
// section of the screen. While it would seem tempting to use the native
// image scaling in Processing, in practice this didn't look very good -- the
// extreme downsampling, coupled with the native interpolation mode, results
// in excessive scintillation with video content. An alternate approach
// using the Java AWT AreaAveragingScaleFilter filter produces wonderfully
// smooth results, but is too slow for filtering full-screen video. So
// instead, a "manual" downsampling method is used here. In the interest of
// speed, it doesn't actually sample every pixel within a block, just a 20x20
// grid...the results still look reasonably smooth and are handled quickly
// enough for video. Scaling the full screen image also wastes a lot of
// cycles on center pixels that are never used for the LED output; this
// method gets called only for perimeter pixels. Even then, you may want to
// set your monitor for a lower resolution before running this sketch.
color block(PImage image, int x, int y) {
int c, r, g, b, row, col, rowOffset;
float startX, curX, curY, incX, incY;
startX = float(screen.width / arrayWidth ) *
(float(x) + (0.5 / float(samples)));
curY = float(screen.height / arrayHeight) *
(float(y) + (0.5 / float(samples)));
incX = float(screen.width / arrayWidth ) / float(samples);
incY = float(screen.height / arrayHeight) / float(samples);
r = g = b = 0;
for(row = 0; row < samples; row++) {
rowOffset = int(curY) * screen.width;
curX = startX;
for(col = 0; col < samples; col++) {
c = image.pixels[rowOffset + int(curX)];
r += (c >> 16) & 0xff;
g += (c >> 8) & 0xff;
b += c & 0xff;
curX += incX;
}
curY += incY;
}
return color(r / s2, g / s2, b / s2);
}

View File

@ -0,0 +1,123 @@
// "Colorswirl" LED demo. This is the host PC-side code written in
// Processing; intended for use with a USB-connected Arduino microcontroller
// running the accompanying LED streaming code. Requires one strand of
// Digital RGB LED Pixels (Adafruit product ID #322, specifically the newer
// WS2801-based type, strand of 25) and a 5 Volt power supply (such as
// Adafruit #276). You may need to adapt the code and the hardware
// arrangement for your specific configuration.
import processing.serial.*;
int N_LEDS = 25; // Max of 65536
void setup()
{
byte[] buffer = new byte[6 + N_LEDS * 3];
Serial myPort;
int i, hue1, hue2, bright, lo, r, g, b, t, prev, frame = 0;
long totalBytesSent = 0;
float sine1, sine2;
noLoop();
// Assumes the Arduino is the first/only serial device. If this is not the
// case, change the device index here. println(Serial.list()); can be used
// to get a list of available serial devices.
myPort = new Serial(this, Serial.list()[0], 115200);
// A special header / magic word is expected by the corresponding LED
// streaming code running on the Arduino. This only needs to be initialized
// once because the number of LEDs remains constant:
buffer[0] = 'A'; // Magic word
buffer[1] = 'd';
buffer[2] = 'a';
buffer[3] = byte((N_LEDS - 1) >> 8); // LED count high byte
buffer[4] = byte((N_LEDS - 1) & 0xff); // LED count low byte
buffer[5] = byte(buffer[3] ^ buffer[4] ^ 0x55); // Checksum
sine1 = 0.0;
hue1 = 0;
prev = second(); // For bandwidth statistics
for (;;) {
sine2 = sine1;
hue2 = hue1;
// Start at position 6, after the LED header/magic word
for (i = 6; i < buffer.length; ) {
// Fixed-point hue-to-RGB conversion. 'hue2' is an integer in the
// range of 0 to 1535, where 0 = red, 256 = yellow, 512 = green, etc.
// The high byte (0-5) corresponds to the sextant within the color
// wheel, while the low byte (0-255) is the fractional part between
// the primary/secondary colors.
lo = hue2 & 255;
switch((hue2 >> 8) % 6) {
case 0:
r = 255;
g = lo;
b = 0;
break;
case 1:
r = 255 - lo;
g = 255;
b = 0;
break;
case 2:
r = 0;
g = 255;
b = lo;
break;
case 3:
r = 0;
g = 255 - lo;
b = 255;
break;
case 4:
r = lo;
g = 0;
b = 255;
break;
default:
r = 255;
g = 0;
b = 255 - lo;
break;
}
// Resulting hue is multiplied by brightness in the range of 0 to 255
// (0 = off, 255 = brightest). Gamma corrrection (the 'pow' function
// here) adjusts the brightness to be more perceptually linear.
bright = int(pow(0.5 + sin(sine2) * 0.5, 2.8) * 255.0);
buffer[i++] = byte((r * bright) / 255);
buffer[i++] = byte((g * bright) / 255);
buffer[i++] = byte((b * bright) / 255);
// Each pixel is slightly offset in both hue and brightness
hue2 += 40;
sine2 += 0.3;
}
// Slowly rotate hue and brightness in opposite directions
hue1 = (hue1 + 4) % 1536;
sine1 -= .03;
// Issue color data to LEDs and keep track of the byte and frame counts
myPort.write(buffer);
totalBytesSent += buffer.length;
frame++;
// Update statistics once per second
if ((t = second()) != prev) {
print("Average frames/sec: ");
print(int((float)frame / (float)millis() * 1000.0));
print(", bytes/sec: ");
println(int((float)totalBytesSent / (float)millis() * 1000.0));
prev = t;
}
}
}
void draw()
{
}

0
README.txt Normal file
View File