From 7dec765eed5a046a3f2fcc7fa7ab65bf5a199261 Mon Sep 17 00:00:00 2001 From: Paint Your Dragon Date: Thu, 15 Dec 2011 22:54:27 -0800 Subject: [PATCH] Added Arduino+Processing sketches for LPD8806 strips --- .../LEDstream_LPD8806/LEDstream_LPD8806.pde | 101 +++++ .../Adalight_LPD8806/Adalight_LPD8806.pde | 414 ++++++++++++++++++ 2 files changed, 515 insertions(+) create mode 100644 Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde create mode 100644 Processing/Adalight_LPD8806/Adalight_LPD8806.pde diff --git a/Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde b/Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde new file mode 100644 index 0000000..5ceba50 --- /dev/null +++ b/Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde @@ -0,0 +1,101 @@ +// Arduino "bridge" code between host computer and LPD8806-based digital +// addressable RGB LEDs (e.g. Adafruit product ID #306). 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. + +// The LPD8806 latch condition is indicated through the data protocol, +// not through a pause in the data clock as with the WS2801. Buffer +// underruns are thus a non-issue and the code can be vastly simpler. +// Data is merely routed from serial in to SPI out. + +// 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 + +// If no serial data is received for a while, the LEDs are shut off +// automatically. This avoids the annoying "stuck pixel" look when +// quitting LED display programs on the host computer. +static const unsigned long serialTimeout = 15000; // 15 seconds + +void setup() { + int i, c; + unsigned long + lastByteTime, + lastAckTime, + t; + + Serial.begin(115200); // 32u4 ignores BPS, runs full speed + + // SPI is run at 2 MHz. LPD8806 can run much faster, + // but unshielded wiring is susceptible to interference. + // Feel free to experiment with other divider ratios. + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz + + // Issue test pattern to LEDs on startup. This helps verify that + // wiring between the Arduino and LEDs is correct. Not knowing the + // actual number of LEDs connected, this sets all of them (well, up + // to the first 25,000, so as not to be TOO time consuming) to green, + // red, blue, then off. Once you're confident everything is working + // end-to-end, it's OK to comment this out and reprogram the Arduino. + uint8_t testcolor[] = { 0x80, 0x80, 0x80, 0xff, 0x80, 0x80 }; + for(char n=3; n>=0; n--) { + for(c=0; c<25000; c++) { + for(i=0; i<3; i++) { + for(SPDR = testcolor[n + i]; !(SPSR & _BV(SPIF)); ); + } + } + for(c=0; c<400; c++) { + for(SPDR=0; !(SPSR & _BV(SPIF)); ); + } + } + + Serial.print("Ada\n"); // Send ACK string to host + SPDR = 0; // Dummy byte out to "prime" the SPI status register + + lastByteTime = lastAckTime = millis(); + + // loop() is avoided as even that small bit of function overhead + // has a measurable impact on this code's overall throughput. + + for(;;) { + t = millis(); + if((c = Serial.read()) >= 0) { + while(!(SPSR & (1< 1000) { + Serial.print("Ada\n"); // Send ACK string to host + lastAckTime = t; // Reset counter + } + // If no data received for an extended time, turn off all LEDs. + if((t - lastByteTime) > serialTimeout) { + for(c=0; c<32767; c++) { + for(SPDR=0x80; !(SPSR & _BV(SPIF)); ); + } + for(c=0; c<512; c++) { + for(SPDR=0; !(SPSR & _BV(SPIF)); ); + } + lastByteTime = t; // Reset counter + } + } + } +} + +void loop() { + // Not used. See note in setup() function. +} diff --git a/Processing/Adalight_LPD8806/Adalight_LPD8806.pde b/Processing/Adalight_LPD8806/Adalight_LPD8806.pde new file mode 100644 index 0000000..b24aefe --- /dev/null +++ b/Processing/Adalight_LPD8806/Adalight_LPD8806.pde @@ -0,0 +1,414 @@ +// "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 LPD8806 (NOT WS2801) LED +// streaming code. Requires one or more strips of Digital Addressable RGB +// LEDs (Adafruit product ID #306, 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.*; + +// CONFIGURABLE PROGRAM CONSTANTS -------------------------------------------- + +// Minimum LED brightness; some users prefer a small amount of backlighting +// at all times, regardless of screen content. Higher values are brighter, +// or set to 0 to disable this feature. + +static final short minBrightness = 120; + +// LED transition speed; it's sometimes distracting if LEDs instantaneously +// track screen contents (such as during bright flashing sequences), so this +// feature enables a gradual fade to each new LED state. Higher numbers yield +// slower transitions (max of 255), or set to 0 to disable this feature +// (immediate transition of all LEDs). + +static final short fade = 75; + +// Pixel size for the live preview image. + +static final int pixelSize = 20; + +// Depending on many factors, it may be faster either to capture full +// screens and process only the pixels needed, or to capture multiple +// smaller sub-blocks bounding each region to be processed. Try both, +// look at the reported frame rates in the Processing output console, +// and run with whichever works best for you. + +static final boolean useFullScreenCaps = true; + +// Serial device timeout (in milliseconds), for locating Arduino device +// running the corresponding LEDstream code. See notes later in the code... +// in some situations you may want to entirely comment out that block. + +static final int timeout = 5000; // 5 seconds + +// PER-DISPLAY INFORMATION --------------------------------------------------- + +// This array contains details for each display that the software will +// process. If you have screen(s) attached that are not among those being +// "Adalighted," they should not be in this list. Each triplet in this +// array represents one display. The first number is the system screen +// number...typically the "primary" display on most systems is identified +// as screen #1, but since arrays are indexed from zero, use 0 to indicate +// the first screen, 1 to indicate the second screen, and so forth. This +// is the ONLY place system screen numbers are used...ANY subsequent +// references to displays are an index into this list, NOT necessarily the +// same as the system screen number. For example, if you have a three- +// screen setup and are illuminating only the third display, use '2' for +// the screen number here...and then, in subsequent section, '0' will be +// used to refer to the first/only display in this list. +// The second and third numbers of each triplet represent the width and +// height of a grid of LED pixels attached to the perimeter of this display. +// For example, '9,6' = 9 LEDs across, 6 LEDs down. + +static final int displays[][] = new int[][] { + {0,12,6} // Screen 0, 12 LEDs across, 6 LEDs down +//,{1,12,6} // Screen 1, also 12 LEDs across and 6 LEDs down +}; + +// PER-LED INFORMATION ------------------------------------------------------- + +// This array contains the 2D coordinates corresponding to each pixel in the +// LED strand, in the order that they're connected (i.e. the first element +// here belongs to the first LED in the strand, second element is the second +// LED, and so forth). Each triplet in this array consists of a display +// number (an index into the display array above, NOT necessarily the same as +// the system screen number) and an X and Y coordinate specified in the grid +// units given for that display. {0,0,0} is the top-left corner of the first +// display in the array. +// For our example purposes, the coordinate list below forms a ring around +// the perimeter of a single screen, with a one pixel gap at the bottom to +// accommodate a monitor stand. Modify this to match your own setup: + +static final int leds[][] = new int[][] { + {0, 5,5}, {0, 4,5}, {0, 3,5}, {0, 2,5}, {0, 1,5}, {0, 0,5}, // Bottom edge, left half + {0, 0,4}, {0, 0,3}, {0, 0,2}, {0, 0,1}, // Left edge + {0, 0,0}, {0, 1,0}, {0, 2,0}, {0, 3,0}, {0, 4,0}, {0, 5,0}, // Top edge, left half + {0, 6,0}, {0, 7,0}, {0, 8,0}, {0, 9,0}, {0,10,0}, {0,11,0}, // Top edge, right half + {0,11,1}, {0,11,2}, {0,11,3}, {0,11,4}, // Right edge + {0,11,5}, {0,10,5}, {0, 9,5}, {0, 8,5}, {0, 7,5}, {0, 6,5}, // Bottom edge, right half + +/* Hypothetical second display has the same arrangement as the first. + But you might not want both displays completely ringed with LEDs; + the screens might be positioned where they share an edge in common. +, {1, 5,5}, {1, 4,5}, {1, 3,5}, {1, 2,5}, {1, 1,5}, {1, 0,5}, // Bottom edge, left half + {1, 0,4}, {1, 0,3}, {1, 0,2}, {1, 0,1}, // Left edge + {1, 0,0}, {1, 1,0}, {1, 2,0}, {1, 3,0}, {1, 4,0}, {1, 5,0}, // Top edge, left half + {1, 6,0}, {1, 7,0}, {1, 8,0}, {1, 9,0}, {1,10,0}, {1,11,0}, // Top edge, right half + {1,11,1}, {1,11,2}, {1,11,3}, {1,11,4}, // Right edge + {1,11,5}, {1,10,5}, {1, 9,5}, {1, 8,5}, {1, 7,5}, {1, 6,5}, // Bottom edge, right half +*/ +}; + +// GLOBAL VARIABLES ---- You probably won't need to modify any of this ------- + +static final int latchLen = (leds.length + 63) / 64; +byte[] serialData = new byte[(leds.length + latchLen) * 3]; +short[][] ledColor = new short[leds.length][3], + prevColor = new short[leds.length][3]; +byte[][] gamma = new byte[256][3]; +int nDisplays = displays.length; +Robot[] bot = new Robot[displays.length]; +Rectangle[] dispBounds = new Rectangle[displays.length], + ledBounds; // Alloc'd only if per-LED captures +int[][] pixelOffset = new int[leds.length][256], + screenData; // Alloc'd only if full-screen captures +PImage[] preview = new PImage[displays.length]; +Serial port; +DisposeHandler dh; // For disabling LEDs on exit + +// INITIALIZATION ------------------------------------------------------------ + +void setup() { + GraphicsEnvironment ge; + GraphicsConfiguration[] gc; + GraphicsDevice[] gd; + int d, i, totalWidth, maxHeight, row, col, rowOffset; + int[] x = new int[16], y = new int[16]; + float f, range, step, start; + + dh = new DisposeHandler(this); // Init DisposeHandler ASAP + + // Open serial port. As written here, this assumes the Arduino is the + // first/only serial device on the system. If that's not the case, + // change "Serial.list()[0]" to the name of the port to be used: + port = new Serial(this, Serial.list()[0], 115200); + // Alternately, in certain situations the following line can be used + // to detect the Arduino automatically. But this works ONLY with SOME + // Arduino boards and versions of Processing! This is so convoluted + // to explain, it's easier just to test it yourself and see whether + // it works...if not, leave it commented out and use the prior port- + // opening technique. + // port = openPort(); + // And finally, to test the software alone without an Arduino connected, + // don't open a port...just comment out the serial lines above. + + // Initialize screen capture code for each display's dimensions. + dispBounds = new Rectangle[displays.length]; + if(useFullScreenCaps == true) { + screenData = new int[displays.length][]; + // ledBounds[] not used + } else { + ledBounds = new Rectangle[leds.length]; + // screenData[][] not used + } + ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + gd = ge.getScreenDevices(); + if(nDisplays > gd.length) nDisplays = gd.length; + totalWidth = maxHeight = 0; + for(d=0; d 0) totalWidth++; + if(displays[d][2] > maxHeight) maxHeight = displays[d][2]; + } + + // Precompute locations of every pixel to read when downsampling. + // Saves a bunch of math on each frame, at the expense of a chunk + // of RAM. Number of samples is now fixed at 256; this allows for + // some crazy optimizations in the downsampling code. + for(i=0; i= 4) && + ((ack = s.readString()) != null) && + ack.contains("Ada\n")) { + return s; // Got it! + } + } + // Connection timed out. Close port and move on to the next. + s.stop(); + } + + // Didn't locate a device returning the acknowledgment string. + // Maybe it's out there but running the old LEDstream code, which + // didn't have the ACK. Can't say for sure, so we'll take our + // changes with the first/only serial device out there... + return new Serial(this, ports[0], 115200); +} + + +// PER-FRAME PROCESSING ------------------------------------------------------ + +void draw () { + BufferedImage img; + int d, i, j, o, c, weight, rb, g, sum, deficit, s2; + int[] pxls, offs; + + if(useFullScreenCaps == true ) { + // Capture each screen in the displays array. + for(d=0; d> 24) & 0xff) * weight + + prevColor[i][0] * fade) >> 8); + ledColor[i][1] = (short)(((( g >> 16) & 0xff) * weight + + prevColor[i][1] * fade) >> 8); + ledColor[i][2] = (short)((((rb >> 8) & 0xff) * weight + + prevColor[i][2] * fade) >> 8); + + // Boost pixels that fall below the minimum brightness + sum = ledColor[i][0] + ledColor[i][1] + ledColor[i][2]; + if(sum < minBrightness) { + if(sum == 0) { // To avoid divide-by-zero + deficit = minBrightness / 3; // Spread equally to R,G,B + ledColor[i][0] += deficit; + ledColor[i][1] += deficit; + ledColor[i][2] += deficit; + } else { + deficit = minBrightness - sum; + s2 = sum * 2; + // Spread the "brightness deficit" back into R,G,B in proportion to + // their individual contribition to that deficit. Rather than simply + // boosting all pixels at the low end, this allows deep (but saturated) + // colors to stay saturated...they don't "pink out." + ledColor[i][0] += deficit * (sum - ledColor[i][0]) / s2; + ledColor[i][1] += deficit * (sum - ledColor[i][1]) / s2; + ledColor[i][2] += deficit * (sum - ledColor[i][2]) / s2; + } + } + + // Apply gamma curve and place in serial output buffer + serialData[j++] = gamma[ledColor[i][1]][1]; // G + serialData[j++] = gamma[ledColor[i][0]][0]; // R + serialData[j++] = gamma[ledColor[i][2]][2]; // B + // Update pixels in preview image + preview[d].pixels[leds[i][2] * displays[d][1] + leds[i][1]] = + (ledColor[i][0] << 16) | (ledColor[i][1] << 8) | ledColor[i][2]; + } + + if(port != null) { + port.write(serialData); // Issue data to Arduino + // You *might* need to comment out the above line and use + // the following code instead. Long writes fail for some + // unknown reason. RXTX lib? Processing? Java? OS? Hardware? +// for(i=0; i serialData.length) j = serialData.length; +// port.write(Arrays.copyOfRange(serialData,i,j)); +// } + } + + // Show live preview image(s) + scale(pixelSize); + for(i=d=0; d