Permalink
Browse files

LEDstream for LPD8806 now compatible with stock WS2801 version

  • Loading branch information...
1 parent 7dec765 commit d22622a4022d5c41a0b5bc69a78079b5c1feb3e5 @PaintYourDragon PaintYourDragon committed Jan 7, 2012
Showing with 193 additions and 476 deletions.
  1. +193 −62 Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde
  2. +0 −414 Processing/Adalight_LPD8806/Adalight_LPD8806.pde
View
255 Arduino/LEDstream_LPD8806/LEDstream_LPD8806.pde
@@ -1,39 +1,65 @@
-// 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.
+// Arduino bridge code between host computer and LPD8806-based digital
+// addressable RGB LEDs (e.g. Adafruit product ID #306). LED data is
+// streamed, not buffered, making this suitable for larger installations
+// (e.g. video wall, etc.) than could otherwise be contained within the
+// Arduino's limited RAM. Intended for use with USB-native boards such
+// as Teensy or Adafruit 32u4 Breakout; also works on normal serial
+// Arduinos (Uno, etc.), but speed will be limited by the serial port.
// 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.
+// On traditional Arduino boards (e.g. Uno), 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. On Arduino Mega, 51=data, 52=clock.
+// 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.
+
+// Elsewhere, the WS2801 version of this code was specifically designed
+// to avoid buffer underrun conditions...the WS2801 pixels automatically
+// latch when the data stream stops for 500 microseconds or more, whether
+// intentional or not. The LPD8806 pixels are fundamentally different --
+// the latch condition is indicated within the data stream, not by pausing
+// the clock -- and buffer underruns are therefore a non-issue. In theory
+// it would seem this could allow the code to be much simpler and faster
+// (there's no need to sync up with a start-of-frame header), but in
+// practice the difference was not as pronounced as expected -- such code
+// soon ran up against a USB throughput limit anyway. So, rather than
+// break compatibility in the quest for speed that will never materialize,
+// this code instead follows the same header format as the WS2801 version.
+// This allows the same host-side code (e.g. Adalight, Adavision, etc.)
+// to run with either type of LED pixels. Huzzah!
#include <SPI.h>
+// A 'magic word' precedes each block of LED data; this assists the
+// microcontroller in syncing up with the host-side software and latching
+// frames at the correct time. You may see an initial glitchy frame or
+// two until the two come into alignment. 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. LPD8806 pixels only have 7-bit
+// brightness control, so each value is divided by two; the 8-bit format
+// is used to maintain compatibility with the protocol set forth by the
+// WS2801 streaming code (those LEDs use 8-bit values).
+static const uint8_t magic[] = { 'A','d','a' };
+#define MAGICSIZE sizeof(magic)
+#define HEADERSIZE (MAGICSIZE + 3)
+static uint8_t
+ buffer[HEADERSIZE], // Serial input buffer
+ bytesBuffered = 0; // Amount of data in buffer
+
// 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
+static unsigned long lastByteTime, lastAckTime;
void setup() {
- int i, c;
- unsigned long
- lastByteTime,
- lastAckTime,
- t;
+ byte c;
+ int i, p;
- Serial.begin(115200); // 32u4 ignores BPS, runs full speed
+ Serial.begin(115200); // 32u4 will ignore BPS and run full speed
// SPI is run at 2 MHz. LPD8806 can run much faster,
// but unshielded wiring is susceptible to interference.
@@ -43,59 +69,164 @@ void setup() {
SPI.setDataMode(SPI_MODE0);
SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz
+ // Issue dummy byte to "prime" the SPI bus. This later simplifies
+ // the task of doing useful work during SPI transfers. Rather than
+ // the usual issue-and-wait-loop, code can instead wait-and-issue --
+ // with other operations occurring between transfers, the wait is
+ // then shortened or eliminated. The SPSR register is read-only,
+ // so this flag can't be forced -- SOMETHING must be issued.
+ SPDR = 0;
+
+ // Issue initial latch to LEDs. This flushes any undefined data that
+ // may exist on powerup, and prepares the LEDs to receive the first
+ // frame of data. Actual number of LEDs isn't known yet (this arrives
+ // later in frame header packets), so just latch a large number:
+ latch(10000);
+
// 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)); );
+ // wiring between the Arduino and LEDs is correct. Again not knowing
+ // the actual number of LEDs, this writes data for an arbitrarily
+ // large number (10K). If wiring is correct, LEDs will all light
+ // red, green, blue on startup, then off. Once you're confident
+ // everything is working end-to-end, it's OK to comment this out and
+ // re-upload the sketch to the Arduino.
+ const uint8_t testColor[] = { 0x80, 0x80, 0xff, 0x80, 0x80, 0x80 },
+ testOffset[] = { 1, 2, 0, 3 };
+ for(c=0; c<4; c++) { // for each test sequence color...
+ for(p=0; p<10000; p++) { // for each pixel...
+ for(i=0; i<3; i++) { // for each R,G,B...
+ while(!(SPSR & _BV(SPIF))); // Wait for prior byte out
+ SPDR = testColor[testOffset[c] + i]; // Issue next byte
}
}
- for(c=0; c<400; c++) {
- for(SPDR=0; !(SPSR & _BV(SPIF)); );
+ latch(10000);
+ if(c < 3) delay(250);
+ }
+
+ Serial.print("Ada\n"); // Send ACK string to host
+ lastByteTime = lastAckTime = millis(); // Initialize timers
+}
+
+// Program flow is simpler than the WS2801 code. No need for a state
+// machine...instead, software just alternates between two conditions:
+// a header-seeking mode (looking for the 'magic word' at the start
+// of each frame of data), and a data-forwarding mode (moving bytes
+// from serial input to SPI output). A proper data stream will
+// consist only of alternating valid headers and valid data, so the
+// loop() function is simply divided into these two parts, and repeats
+// forever.
+
+// LPD8806 pixels expect colors in G,R,B order vs. WS2801's R,G,B.
+// This is used to shuffle things around later.
+static const uint8_t byteOrder[] = { 2, 0, 1 };
+
+void loop() {
+ uint8_t i, hi, lo, byteNum;
+ int c;
+ long nLEDs, remaining;
+ unsigned long t;
+
+ // HEADER-SEEKING BLOCK: locate 'magic word' at start of frame.
+
+ // If any data in serial buffer, shift it down to starting position.
+ for(i=0; i<bytesBuffered; i++)
+ buffer[i] = buffer[HEADERSIZE - bytesBuffered + i];
+
+ // Read bytes from serial input until there's a full header's worth.
+ while(bytesBuffered < HEADERSIZE) {
+ t = millis();
+ if((c = Serial.read()) >= 0) { // Data received?
+ buffer[bytesBuffered++] = c; // Store in buffer
+ lastByteTime = lastAckTime = t; // Reset timeout counters
+ } else { // No data, check for timeout...
+ if(timeout(t, 10000) == true) return; // Start over
}
}
- Serial.print("Ada\n"); // Send ACK string to host
- SPDR = 0; // Dummy byte out to "prime" the SPI status register
+ // Have a header's worth of data. Check for 'magic word' match.
+ for(i=0; i<MAGICSIZE; i++) {
+ if(buffer[i] != magic[i]) { // No match...
+ if(i == 0) bytesBuffered -= 1; // resume search at next char
+ else bytesBuffered -= i; // resume at non-matching char
+ return;
+ }
+ }
- lastByteTime = lastAckTime = millis();
+ // Magic word matches. Now how about the checksum?
+ hi = buffer[MAGICSIZE];
+ lo = buffer[MAGICSIZE + 1];
+ if(buffer[MAGICSIZE + 2] != (hi ^ lo ^ 0x55)) {
+ bytesBuffered -= MAGICSIZE; // No match, resume after magic word
+ return;
+ }
+
+ // Checksum appears valid. Get 16-bit LED count, add 1 (nLEDs always > 0)
+ nLEDs = remaining = 256L * (long)hi + (long)lo + 1L;
+ bytesBuffered = 0; // Clear serial buffer
+ byteNum = 0;
+
+ // DATA-FORWARDING BLOCK: move bytes from serial input to SPI output.
- // loop() is avoided as even that small bit of function overhead
- // has a measurable impact on this code's overall throughput.
+ // Unfortunately can't just forward bytes directly. The data order is
+ // different on LPD8806 (G,R,B), so bytes are buffered in groups of 3
+ // and issued in the revised order.
- for(;;) {
+ while(remaining > 0) { // While more LED data is expected...
t = millis();
- if((c = Serial.read()) >= 0) {
- while(!(SPSR & (1<<SPIF))); // Wait for prior SPI byte out
- SPDR = c; // Issue new SPI byte out
+ if((c = Serial.read()) >= 0) { // Successful read?
lastByteTime = lastAckTime = t; // Reset timeout counters
- } else {
- // No data received. If this persists, send an ACK packet
- // to host once every second to alert it to our presence.
- if((t - lastAckTime) > 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)); );
+ buffer[byteNum++] = c; // Store in data buffer
+ if(byteNum == 3) { // Have a full LED's worth?
+ while(byteNum > 0) { // Issue data in LPD8806 order...
+ i = 0x80 | (buffer[byteOrder[--byteNum]] >> 1);
+ while(!(SPSR & _BV(SPIF))); // Wait for prior byte out
+ SPDR = i; // Issue new byte
}
- lastByteTime = t; // Reset counter
+ remaining--;
}
+ } else { // No data, check for timeout...
+ if(timeout(t, nLEDs) == true) return; // Start over
}
}
+
+ // Normal end of data. Issue latch, return to header-seeking mode.
+ latch(nLEDs);
}
-void loop() {
- // Not used. See note in setup() function.
+static void latch(int n) { // Pass # of LEDs
+ n = ((n + 63) / 64) * 3; // Convert to latch length (bytes)
+ while(n--) { // For each latch byte...
+ while(!(SPSR & _BV(SPIF))); // Wait for prior byte out
+ SPDR = 0; // Issue next byte
+ }
}
+
+// Function is called when no pending serial data is available.
+static boolean timeout(
+ unsigned long t, // Current time, milliseconds
+ int nLEDs) { // Number of LEDs
+
+ // If condition persists, send an ACK packet to host once every
+ // second to alert it to our presence.
+ if((t - lastAckTime) > 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) {
+ long bytes = nLEDs * 3L;
+ latch(nLEDs); // Latch any partial/incomplete data in strand
+ while(bytes--) { // Issue all new data to turn off strand
+ while(!(SPSR & _BV(SPIF))); // Wait for prior byte out
+ SPDR = 0x80; // Issue next byte (0x80 = LED off)
+ }
+ latch(nLEDs); // Latch 'all off' data
+ lastByteTime = t; // Reset counter
+ bytesBuffered = 0; // Clear serial buffer
+ return true;
+ }
+
+ return false; // No timeout
+}
+
View
414 Processing/Adalight_LPD8806/Adalight_LPD8806.pde
@@ -1,414 +0,0 @@
-// "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<nDisplays; d++) { // For each display...
- try {
- bot[d] = new Robot(gd[displays[d][0]]);
- }
- catch(AWTException e) {
- System.out.println("new Robot() failed");
- continue;
- }
- gc = gd[displays[d][0]].getConfigurations();
- dispBounds[d] = gc[0].getBounds();
- dispBounds[d].x = dispBounds[d].y = 0;
- preview[d] = createImage(displays[d][1], displays[d][2], RGB);
- preview[d].loadPixels();
- totalWidth += displays[d][1];
- if(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<leds.length; i++) { // For each LED...
- d = leds[i][0]; // Corresponding display index
-
- // Precompute columns, rows of each sampled point for this LED
- range = (float)dispBounds[d].width / (float)displays[d][1];
- step = range / 16.0;
- start = range * (float)leds[i][1] + step * 0.5;
- for(col=0; col<16; col++) x[col] = (int)(start + step * (float)col);
- range = (float)dispBounds[d].height / (float)displays[d][2];
- step = range / 16.0;
- start = range * (float)leds[i][2] + step * 0.5;
- for(row=0; row<16; row++) y[row] = (int)(start + step * (float)row);
-
- if(useFullScreenCaps == true) {
- // Get offset to each pixel within full screen capture
- for(row=0; row<16; row++) {
- for(col=0; col<16; col++) {
- pixelOffset[i][row * 16 + col] =
- y[row] * dispBounds[d].width + x[col];
- }
- }
- } else {
- // Calc min bounding rect for LED, get offset to each pixel within
- ledBounds[i] = new Rectangle(x[0], y[0], x[15]-x[0]+1, y[15]-y[0]+1);
- for(row=0; row<16; row++) {
- for(col=0; col<16; col++) {
- pixelOffset[i][row * 16 + col] =
- (y[row] - y[0]) * ledBounds[i].width + x[col] - x[0];
- }
- }
- }
- }
-
- for(i=0; i<prevColor.length; i++) {
- prevColor[i][0] = prevColor[i][1] = prevColor[i][2] =
- minBrightness / 3;
- }
-
- // Preview window shows all screens side-by-side
- size(totalWidth * pixelSize, maxHeight * pixelSize, JAVA2D);
-
- // The "gamma" table actually does three things: applies gamma
- // correction to input colors to produce a more perceptually linear
- // output range, reduces 8-bit inputs to 7-bit outputs, and sets the
- // high bit as required by the LPD8806 LED data protocol.
- for(i=0; i<256; i++) {
- f = pow((float)i / 255.0, 2.8);
- gamma[i][0] = (byte)(0x80 | (int)(0.5 + f * 127.0)); // Adjust these numbers
- gamma[i][1] = (byte)(0x80 | (int)(0.5 + f * 127.0)); // if color balance seems
- gamma[i][2] = (byte)(0x80 | (int)(0.5 + f * 127.0)); // out of whack.
- }
-}
-
-// Open and return serial connection to Arduino running LEDstream code. This
-// attempts to open and read from each serial device on the system, until the
-// matching "Ada\n" acknowledgement string is found. Due to the serial
-// timeout, if you have multiple serial devices/ports and the Arduino is late
-// in the list, this can take seemingly forever...so if you KNOW the Arduino
-// will always be on a specific port (e.g. "COM6"), you might want to comment
-// out most of this to bypass the checks and instead just open that port
-// directly! (Modify last line in this method with the serial port name.)
-
-Serial openPort() {
- String[] ports;
- String ack;
- int i, start;
- Serial s;
-
- ports = Serial.list(); // List of all serial ports/devices on system.
-
- for(i=0; i<ports.length; i++) { // For each serial port...
- System.out.format("Trying serial port %s\n",ports[i]);
- try {
- s = new Serial(this, ports[i], 115200);
- }
- catch(Exception e) {
- // Can't open port, probably in use by other software.
- continue;
- }
- // Port open...watch for acknowledgement string...
- start = millis();
- while((millis() - start) < timeout) {
- if((s.available() >= 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<nDisplays; d++) {
- img = bot[d].createScreenCapture(dispBounds[d]);
- // Get location of source pixel data
- screenData[d] =
- ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
- }
- }
-
- weight = 257 - fade; // 'Weighting factor' for new frame vs. old
-
- // This 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/Java, in practice this didn't look very
- // good -- either too pixelated or too blurry, no happy medium. So
- // instead, a "manual" downsampling is done here. In the interest of
- // speed, it doesn't actually sample every pixel within a block, just
- // a selection of 256 pixels spaced within the block...the results still
- // look reasonably smooth and are handled quickly enough for video.
-
- for(i=j=0; i<leds.length; i++) { // For each LED...
- d = leds[i][0]; // Corresponding display index
- if(useFullScreenCaps == true) {
- // Get location of source data from prior full-screen capture:
- pxls = screenData[d];
- } else {
- // Capture section of screen (LED bounds rect) and locate data::
- img = bot[d].createScreenCapture(ledBounds[i]);
- pxls = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
- }
- offs = pixelOffset[i];
- rb = g = 0;
- for(o=0; o<256; o++) {
- c = pxls[offs[o]];
- rb += c & 0x00ff00ff; // Bit trickery: R+B can accumulate in one var
- g += c & 0x0000ff00;
- }
-
- // Blend new pixel value with the value from the prior frame
- ledColor[i][0] = (short)((((rb >> 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; i=j) {
-// j = i + 255;
-// if(j > serialData.length) j = serialData.length;
-// port.write(Arrays.copyOfRange(serialData,i,j));
-// }
- }
-
- // Show live preview image(s)
- scale(pixelSize);
- for(i=d=0; d<nDisplays; d++) {
- preview[d].updatePixels();
- image(preview[d], i, 0);
- i += displays[d][1] + 1;
- }
-
- println(frameRate); // How are we doing?
-
- // Copy LED color data to prior frame array for next pass
- arraycopy(ledColor, 0, prevColor, 0, ledColor.length);
-}
-
-
-// CLEANUP -------------------------------------------------------------------
-
-// The DisposeHandler is called on program exit (but before the Serial library
-// is shutdown), in order to turn off the LEDs (reportedly more reliable than
-// stop()). Seems to work for the window close box and escape key exit, but
-// not the 'Quit' menu option. Thanks to phi.lho in the Processing forums.
-
-public class DisposeHandler {
- DisposeHandler(PApplet pa) {
- pa.registerDispose(this);
- }
- public void dispose() {
- if(port != null) {
- Arrays.fill(serialData, 0, serialData.length - latchLen, (byte)0x80);
- port.write(serialData);
- }
- }
-}
-

0 comments on commit d22622a

Please sign in to comment.