Removed other Adalight files
This fork is now detatched from the main Adalight repo. The other Arduino host code and Processing files can be downloaded from github.com/adafruit/Adalight
This commit is contained in:
parent
10d5ac4b54
commit
6b25599580
|
@ -1,246 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
#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
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
{
|
|
||||||
// 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,
|
|
||||||
hold = 0,
|
|
||||||
c;
|
|
||||||
int32_t
|
|
||||||
bytesRemaining;
|
|
||||||
unsigned long
|
|
||||||
startTime,
|
|
||||||
lastByteTime,
|
|
||||||
lastAckTime,
|
|
||||||
t;
|
|
||||||
|
|
||||||
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_DIV16); // 1 MHz max, else flicker
|
|
||||||
|
|
||||||
// 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 red,
|
|
||||||
// green, 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[] = { 0, 0, 0, 255, 0, 0 };
|
|
||||||
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)); );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delay(1); // One millisecond pause = latch
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.print("Ada\n"); // Send ACK string to host
|
|
||||||
|
|
||||||
startTime = micros();
|
|
||||||
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(;;) {
|
|
||||||
|
|
||||||
// Implementation is a simple finite-state machine.
|
|
||||||
// Regardless of mode, check for serial input each time:
|
|
||||||
t = millis();
|
|
||||||
if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) {
|
|
||||||
buffer[indexIn++] = c;
|
|
||||||
bytesBuffered++;
|
|
||||||
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=0; !(SPSR & _BV(SPIF)); );
|
|
||||||
}
|
|
||||||
delay(1); // One millisecond pause = latch
|
|
||||||
lastByteTime = t; // Reset counter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
spiFlag = 0; // No data out yet
|
|
||||||
mode = MODE_HOLD; // Proceed to latch wait mode
|
|
||||||
} 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() - startTime) < hold) break; // Still holding; keep 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)) {
|
|
||||||
startTime = micros();
|
|
||||||
hold = 100 + (32 - bytesBuffered) * 10;
|
|
||||||
mode = MODE_HOLD;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// End of data -- issue latch:
|
|
||||||
startTime = micros();
|
|
||||||
hold = 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.
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
// This is a pared-down version of the LEDstream sketch specifically
|
|
||||||
// for Circuit Playground. It is NOT a generic solution to NeoPixel
|
|
||||||
// support with Adalight! The NeoPixel library disables interrupts
|
|
||||||
// while issuing data...but Serial transfers depend on interrupts.
|
|
||||||
// This code works (or appears to work, it hasn't been extensively
|
|
||||||
// battle-tested) only because of the finite number of pixels (10)
|
|
||||||
// on the Circuit Playground board. With 10 NeoPixels, interrupts are
|
|
||||||
// off for about 300 microseconds...but if the incoming data rate is
|
|
||||||
// sufficiently limited (<= 60 FPS or so with the given number of
|
|
||||||
// pixels), things seem OK, no data is missed. Balancing act!
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
#include "Adafruit_CircuitPlayground.h"
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
static const unsigned long serialTimeout = 15000; // 15 seconds
|
|
||||||
static unsigned long lastByteTime;
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
CircuitPlayground.begin();
|
|
||||||
CircuitPlayground.setBrightness(255); // LEDs full blast!
|
|
||||||
CircuitPlayground.strip.clear();
|
|
||||||
CircuitPlayground.strip.show();
|
|
||||||
|
|
||||||
Serial.begin(38400);
|
|
||||||
|
|
||||||
lastByteTime = millis(); // Initialize timers
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 no data received for an extended time, turn off all LEDs.
|
|
||||||
if((t - lastByteTime) > serialTimeout) {
|
|
||||||
CircuitPlayground.strip.clear();
|
|
||||||
CircuitPlayground.strip.show();
|
|
||||||
lastByteTime = t; // Reset counter
|
|
||||||
bytesBuffered = 0; // Clear serial buffer
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
uint8_t i, hi, lo, byteNum;
|
|
||||||
int c;
|
|
||||||
long nLEDs, pixelNum;
|
|
||||||
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 = t; // Reset timeout counter
|
|
||||||
} else { // No data, check for timeout...
|
|
||||||
if(timeout(t, 10000) == true) return; // Start over
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = 256L * (long)hi + (long)lo + 1L;
|
|
||||||
bytesBuffered = 0; // Clear serial buffer
|
|
||||||
byteNum = 0;
|
|
||||||
|
|
||||||
// DATA-FORWARDING BLOCK: move bytes from serial input to NeoPixels.
|
|
||||||
|
|
||||||
for(pixelNum = 0; pixelNum < nLEDs; ) { // While more LED data is expected...
|
|
||||||
t = millis();
|
|
||||||
if((c = Serial.read()) >= 0) { // Successful read?
|
|
||||||
lastByteTime = t; // Reset timeout counters
|
|
||||||
buffer[byteNum++] = c; // Store in data buffer
|
|
||||||
if(byteNum == 3) { // Have a full LED's worth?
|
|
||||||
CircuitPlayground.strip.setPixelColor(pixelNum++,
|
|
||||||
buffer[0], buffer[1], buffer[2]);
|
|
||||||
byteNum = 0;
|
|
||||||
}
|
|
||||||
} else { // No data, check for timeout...
|
|
||||||
if(timeout(t, nLEDs) == true) return; // Start over
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CircuitPlayground.strip.show();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,250 +0,0 @@
|
||||||
// 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 (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!
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
#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() {
|
|
||||||
byte c;
|
|
||||||
int i, p;
|
|
||||||
|
|
||||||
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.
|
|
||||||
// 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 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. 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
|
|
||||||
while(remaining > 0) { // While more LED data is expected...
|
|
||||||
t = millis();
|
|
||||||
if((c = Serial.read()) >= 0) { // Successful read?
|
|
||||||
lastByteTime = lastAckTime = t; // Reset timeout counters
|
|
||||||
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
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
EXECS = colorswirl
|
|
||||||
|
|
||||||
all: $(EXECS)
|
|
||||||
|
|
||||||
colorswirl: colorswirl.c
|
|
||||||
cc -O2 colorswirl.c -lm -o colorswirl
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(EXECS) *.o
|
|
181
C/colorswirl.c
181
C/colorswirl.c
|
@ -1,181 +0,0 @@
|
||||||
/*
|
|
||||||
"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
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
|
@ -1,430 +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 LED streaming code. Requires one
|
|
||||||
// or more strands 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)
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
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,9,6} // Screen 0, 9 LEDs across, 6 LEDs down
|
|
||||||
//,{1,9,6} // Screen 1, also 9 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,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}, // Top edge
|
|
||||||
{0,5,0}, {0,6,0}, {0,7,0}, {0,8,0}, // More top edge
|
|
||||||
{0,8,1}, {0,8,2}, {0,8,3}, {0,8,4}, // Right edge
|
|
||||||
{0,8,5}, {0,7,5}, {0,6,5}, {0,5,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,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}, // Top edge
|
|
||||||
{1,5,0}, {1,6,0}, {1,7,0}, {1,8,0}, // More top edge
|
|
||||||
{1,8,1}, {1,8,2}, {1,8,3}, {1,8,4}, // Right edge
|
|
||||||
{1,8,5}, {1,7,5}, {1,6,5}, {1,5,5} // Bottom edge, right half
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
// GLOBAL VARIABLES ---- You probably won't need to modify any of this -------
|
|
||||||
|
|
||||||
byte[] serialData = new byte[6 + leds.length * 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);
|
|
||||||
noSmooth();
|
|
||||||
|
|
||||||
// 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:
|
|
||||||
serialData[0] = 'A'; // Magic word
|
|
||||||
serialData[1] = 'd';
|
|
||||||
serialData[2] = 'a';
|
|
||||||
serialData[3] = (byte)((leds.length - 1) >> 8); // LED count high byte
|
|
||||||
serialData[4] = (byte)((leds.length - 1) & 0xff); // LED count low byte
|
|
||||||
serialData[5] = (byte)(serialData[3] ^ serialData[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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
j = 6; // Serial led data follows header / magic word
|
|
||||||
|
|
||||||
// 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=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][0]][0];
|
|
||||||
serialData[j++] = gamma[ledColor[i][1]][1];
|
|
||||||
serialData[j++] = gamma[ledColor[i][2]][2];
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
// Fill serialData (after header) with 0's, and issue to Arduino...
|
|
||||||
// Arrays.fill(serialData, 6, serialData.length, (byte)0);
|
|
||||||
java.util.Arrays.fill(serialData, 6, serialData.length, (byte)0);
|
|
||||||
if(port != null) port.write(serialData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,300 +0,0 @@
|
||||||
// IMPORTANT: change 'serialPortIndex' to make this work on your system.
|
|
||||||
|
|
||||||
// This is a slightly pared-down version of Adalight specifically for
|
|
||||||
// Circuit Playground, configured for a single screen and 10 LEDs.
|
|
||||||
|
|
||||||
// "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 Circuit
|
|
||||||
// Playground microcontroller running the accompanying LED streaming code.
|
|
||||||
// Screen capture adapted from code by Cedrik Kiefer (processing.org forum)
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.*;
|
|
||||||
import processing.serial.*;
|
|
||||||
|
|
||||||
// CONFIGURABLE PROGRAM CONSTANTS --------------------------------------------
|
|
||||||
|
|
||||||
// This selects from the list of serial devices connected to the system.
|
|
||||||
// Use print(Serial.list()); to get a list of ports. Then, counting from 0,
|
|
||||||
// set this value to the index corresponding to the Circuit Playground port:
|
|
||||||
|
|
||||||
static final byte serialPortIndex = 2;
|
|
||||||
|
|
||||||
// For multi-screen systems, set this to the index (counting from 0) of the
|
|
||||||
// display which will have ambient lighting:
|
|
||||||
|
|
||||||
static final byte screenNumber = 0;
|
|
||||||
|
|
||||||
// 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 = 100;
|
|
||||||
|
|
||||||
// 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 = 60;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// PER-LED INFORMATION -------------------------------------------------------
|
|
||||||
|
|
||||||
// The Circuit Playground version of Adalight operates on a fixed 5x5 grid
|
|
||||||
// encompassing the full display. 10 elements from this grid correspond to
|
|
||||||
// the 10 NeoPixels on the Circuit Playground board. The following array
|
|
||||||
// contains the 2D coordinates of each NeoPixel within that 5x5 grid (0,0 is
|
|
||||||
// top left); board assumed facing away from display, with USB at bottom:
|
|
||||||
// .4.5.
|
|
||||||
// 3...6
|
|
||||||
// 2...7
|
|
||||||
// 1...8
|
|
||||||
// .0.9.
|
|
||||||
|
|
||||||
static final int leds[][] = new int[][] {
|
|
||||||
{1,4}, {0,3}, {0,2}, {0,1}, {1,0},
|
|
||||||
{3,0}, {4,1}, {4,2}, {4,3}, {3,4}
|
|
||||||
};
|
|
||||||
|
|
||||||
// GLOBAL VARIABLES ---- You probably won't need to modify any of this -------
|
|
||||||
|
|
||||||
byte serialData[] = new byte[6 + leds.length * 3],
|
|
||||||
gamma[][] = new byte[256][3];
|
|
||||||
short[][] ledColor = new short[leds.length][3],
|
|
||||||
prevColor = new short[leds.length][3];
|
|
||||||
Robot bot;
|
|
||||||
Rectangle dispBounds, ledBounds[];
|
|
||||||
int pixelOffset[][] = new int[leds.length][256],
|
|
||||||
screenData[];
|
|
||||||
PImage preview;
|
|
||||||
Serial port;
|
|
||||||
|
|
||||||
// INITIALIZATION ------------------------------------------------------------
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
GraphicsEnvironment ge;
|
|
||||||
GraphicsConfiguration[] gc;
|
|
||||||
GraphicsDevice[] gd;
|
|
||||||
int i, row, col;
|
|
||||||
int[] x = new int[16], y = new int[16];
|
|
||||||
float f, range, step, start;
|
|
||||||
|
|
||||||
this.registerMethod("dispose", this);
|
|
||||||
print(Serial.list()); // Show list of serial devices/ports
|
|
||||||
// Open serial port. Change serialPortIndex in the globals to
|
|
||||||
// select a different port:
|
|
||||||
port = new Serial(this, Serial.list()[serialPortIndex], 38400);
|
|
||||||
|
|
||||||
// Initialize screen capture code for the display's dimensions.
|
|
||||||
if(useFullScreenCaps == false) ledBounds = new Rectangle[leds.length];
|
|
||||||
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
||||||
gd = ge.getScreenDevices();
|
|
||||||
|
|
||||||
try {
|
|
||||||
bot = new Robot(gd[screenNumber]);
|
|
||||||
}
|
|
||||||
catch(AWTException e) {
|
|
||||||
System.out.println("new Robot() failed");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
gc = gd[screenNumber].getConfigurations();
|
|
||||||
dispBounds = gc[0].getBounds();
|
|
||||||
dispBounds.x = dispBounds.y = 0;
|
|
||||||
preview = createImage(5, 5, RGB);
|
|
||||||
preview.loadPixels();
|
|
||||||
|
|
||||||
// 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...
|
|
||||||
// Precompute columns, rows of each sampled point for this LED
|
|
||||||
range = (float)dispBounds.width / 5.0;
|
|
||||||
step = range / 16.0;
|
|
||||||
start = range * (float)leds[i][0] + step * 0.5;
|
|
||||||
for(col=0; col<16; col++) x[col] = (int)(start + step * (float)col);
|
|
||||||
range = (float)dispBounds.height / 5.0;
|
|
||||||
step = range / 16.0;
|
|
||||||
start = range * (float)leds[i][1] + 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.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
size(200, 200, JAVA2D); // Preview window for 5x5 grid at 40X scale
|
|
||||||
noSmooth();
|
|
||||||
|
|
||||||
// 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:
|
|
||||||
serialData[0] = 'A'; // Magic word
|
|
||||||
serialData[1] = 'd';
|
|
||||||
serialData[2] = 'a';
|
|
||||||
serialData[3] = (byte)((leds.length - 1) >> 8); // LED count high byte
|
|
||||||
serialData[4] = (byte)((leds.length - 1) & 0xff); // LED count low byte
|
|
||||||
serialData[5] = (byte)(serialData[3] ^ serialData[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 + 0.5);
|
|
||||||
gamma[i][1] = (byte)(f * 240.0 + 0.5);
|
|
||||||
gamma[i][2] = (byte)(f * 220.0 + 0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PER_FRAME PROCESSING ------------------------------------------------------
|
|
||||||
|
|
||||||
void draw () {
|
|
||||||
BufferedImage img;
|
|
||||||
int i, j, o, c, weight, rb, g, sum, deficit, s2;
|
|
||||||
int[] pxls, offs;
|
|
||||||
|
|
||||||
if(useFullScreenCaps == true ) {
|
|
||||||
img = bot.createScreenCapture(dispBounds);
|
|
||||||
// Get location of source pixel data
|
|
||||||
screenData =
|
|
||||||
((DataBufferInt)img.getRaster().getDataBuffer()).getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
weight = 257 - fade; // 'Weighting factor' for new frame vs. old
|
|
||||||
j = 6; // Serial led data follows header / magic word
|
|
||||||
|
|
||||||
// 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=0; i<leds.length; i++) { // For each LED...
|
|
||||||
if(useFullScreenCaps == true) {
|
|
||||||
// Get location of source data from prior full-screen capture:
|
|
||||||
pxls = screenData;
|
|
||||||
} else {
|
|
||||||
// Capture section of screen (LED bounds rect) and locate data::
|
|
||||||
img = bot.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][0]][0];
|
|
||||||
serialData[j++] = gamma[ledColor[i][1]][1];
|
|
||||||
serialData[j++] = gamma[ledColor[i][2]][2];
|
|
||||||
// Update pixels in preview image
|
|
||||||
preview.pixels[leds[i][1] * 5 + leds[i][0]] = 0xFF000000 |
|
|
||||||
(ledColor[i][0] << 16) | (ledColor[i][1] << 8) | ledColor[i][2];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(port != null) port.write(serialData); // Issue data to Arduino
|
|
||||||
|
|
||||||
// Show live preview image
|
|
||||||
preview.updatePixels();
|
|
||||||
scale(40);
|
|
||||||
image(preview, 0, 0);
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
// Fill serialData (after header) with 0's, and issue to Arduino...
|
|
||||||
java.util.Arrays.fill(serialData, 6, serialData.length, (byte)0);
|
|
||||||
if(port != null) port.write(serialData);
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
// "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.
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
// This file is part of Adalight.
|
|
||||||
|
|
||||||
// Adalight 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.
|
|
||||||
|
|
||||||
// Adalight 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 Adalight. If not, see
|
|
||||||
// <http://www.gnu.org/licenses/>.
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue