From f43a8086e13642d0bf09abde2c6cc7758def0022 Mon Sep 17 00:00:00 2001 From: Ladyada Date: Tue, 4 Oct 2011 13:09:09 -0400 Subject: [PATCH] First commit! --- Arduino/LEDstream/LEDstream.pde | 185 +++++++++++++++++++++++++++ C/Makefile | 9 ++ C/colorswirl.c | 163 +++++++++++++++++++++++ Processing/Adalight/Adalight.pde | 153 ++++++++++++++++++++++ Processing/Colorswirl/Colorswirl.pde | 123 ++++++++++++++++++ README.txt | 0 6 files changed, 633 insertions(+) create mode 100644 Arduino/LEDstream/LEDstream.pde create mode 100644 C/Makefile create mode 100644 C/colorswirl.c create mode 100644 Processing/Adalight/Adalight.pde create mode 100644 Processing/Colorswirl/Colorswirl.pde create mode 100644 README.txt diff --git a/Arduino/LEDstream/LEDstream.pde b/Arduino/LEDstream/LEDstream.pde new file mode 100644 index 0000000..de1dafd --- /dev/null +++ b/Arduino/LEDstream/LEDstream.pde @@ -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 + +// 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 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. +} diff --git a/C/Makefile b/C/Makefile new file mode 100644 index 0000000..7d40df7 --- /dev/null +++ b/C/Makefile @@ -0,0 +1,9 @@ +EXECS = colorswirl + +all: $(EXECS) + +colorswirl: colorswirl.c + cc -O2 colorswirl.c -lm -o colorswirl + +clean: + rm -f $(EXECS) *.o diff --git a/C/colorswirl.c b/C/colorswirl.c new file mode 100644 index 0000000..f868a1c --- /dev/null +++ b/C/colorswirl.c @@ -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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/Processing/Adalight/Adalight.pde b/Processing/Adalight/Adalight.pde new file mode 100644 index 0000000..d69c30b --- /dev/null +++ b/Processing/Adalight/Adalight.pde @@ -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); +} + diff --git a/Processing/Colorswirl/Colorswirl.pde b/Processing/Colorswirl/Colorswirl.pde new file mode 100644 index 0000000..abf0f20 --- /dev/null +++ b/Processing/Colorswirl/Colorswirl.pde @@ -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() +{ +} + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e69de29