First commit!
This commit is contained in:
commit
f43a8086e1
|
@ -0,0 +1,185 @@
|
||||||
|
// Arduino "bridge" code between host computer and WS2801-based digital
|
||||||
|
// RGB LED pixels (e.g. Adafruit product ID #322). Intended for use
|
||||||
|
// with USB-native boards such as Teensy or Adafruit 32u4 Breakout;
|
||||||
|
// works on normal serial Arduinos, but throughput is severely limited.
|
||||||
|
// LED data is streamed, not buffered, making this suitable for larger
|
||||||
|
// installations (e.g. video wall, etc.) than could otherwise be held
|
||||||
|
// in the Arduino's limited RAM.
|
||||||
|
|
||||||
|
// Some effort is put into avoiding buffer underruns (where the output
|
||||||
|
// side becomes starved of data). The WS2801 latch protocol, being
|
||||||
|
// delay-based, could be inadvertently triggered if the USB bus or CPU
|
||||||
|
// is swamped with other tasks. This code buffers incoming serial data
|
||||||
|
// and introduces intentional pauses if there's a threat of the buffer
|
||||||
|
// draining prematurely. The cost of this complexity is somewhat
|
||||||
|
// reduced throughput, the gain is that most visual glitches are
|
||||||
|
// avoided (though ultimately a function of the load on the USB bus and
|
||||||
|
// host CPU, and out of our control).
|
||||||
|
|
||||||
|
// LED data and clock lines are connected to the Arduino's SPI output.
|
||||||
|
// On traditional Arduino boards, SPI data out is digital pin 11 and
|
||||||
|
// clock is digital pin 13. On both Teensy and the 32u4 Breakout,
|
||||||
|
// data out is pin B2, clock is B1. LEDs should be externally
|
||||||
|
// powered -- trying to run any more than just a few off the Arduino's
|
||||||
|
// 5V line is generally a Bad Idea. LED ground should also be
|
||||||
|
// connected to Arduino ground.
|
||||||
|
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
// LED pin for Adafruit 32u4 Breakout Board:
|
||||||
|
//#define LED_DDR DDRE
|
||||||
|
//#define LED_PORT PORTE
|
||||||
|
//#define LED_PIN _BV(PORTE6)
|
||||||
|
// LED pin for Teensy:
|
||||||
|
//#define LED_DDR DDRD
|
||||||
|
//#define LED_PORT PORTD
|
||||||
|
//#define LED_PIN _BV(PORTD6)
|
||||||
|
// LED pin for Arduino:
|
||||||
|
#define LED_DDR DDRB
|
||||||
|
#define LED_PORT PORTB
|
||||||
|
#define LED_PIN _BV(PORTB5)
|
||||||
|
|
||||||
|
// A 'magic word' (along with LED count & checksum) precedes each block
|
||||||
|
// of LED data; this assists the microcontroller in syncing up with the
|
||||||
|
// host-side software and properly issuing the latch (host I/O is
|
||||||
|
// likely buffered, making usleep() unreliable for latch). You may see
|
||||||
|
// an initial glitchy frame or two until the two come into alignment.
|
||||||
|
// The magic word can be whatever sequence you like, but each character
|
||||||
|
// should be unique, and frequent pixel values like 0 and 255 are
|
||||||
|
// avoided -- fewer false positives. The host software will need to
|
||||||
|
// generate a compatible header: immediately following the magic word
|
||||||
|
// are three bytes: a 16-bit count of the number of LEDs (high byte
|
||||||
|
// first) followed by a simple checksum value (high byte XOR low byte
|
||||||
|
// XOR 0x55). LED data follows, 3 bytes per LED, in order R, G, B,
|
||||||
|
// where 0 = off and 255 = max brightness.
|
||||||
|
|
||||||
|
static const uint8_t magic[] = {'A','d','a'};
|
||||||
|
#define MAGICSIZE sizeof(magic)
|
||||||
|
#define HEADERSIZE (MAGICSIZE + 3)
|
||||||
|
|
||||||
|
#define MODE_HEADER 0
|
||||||
|
#define MODE_HOLD 1
|
||||||
|
#define MODE_DATA 2
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
// Dirty trick: the circular buffer for serial data is 256 bytes,
|
||||||
|
// and the "in" and "out" indices are unsigned 8-bit types -- this
|
||||||
|
// much simplifies the cases where in/out need to "wrap around" the
|
||||||
|
// beginning/end of the buffer. Otherwise there'd be a ton of bit-
|
||||||
|
// masking and/or conditional code every time one of these indices
|
||||||
|
// needs to change, slowing things down tremendously.
|
||||||
|
uint8_t
|
||||||
|
buffer[256],
|
||||||
|
indexIn = 0,
|
||||||
|
indexOut = 0,
|
||||||
|
mode = MODE_HEADER,
|
||||||
|
hi, lo, chk, i, spiFlag;
|
||||||
|
int16_t
|
||||||
|
bytesBuffered = 0,
|
||||||
|
c;
|
||||||
|
int32_t
|
||||||
|
bytesRemaining;
|
||||||
|
unsigned long
|
||||||
|
t = 0;
|
||||||
|
|
||||||
|
LED_DDR |= LED_PIN; // Enable output for LED
|
||||||
|
LED_PORT &= ~LED_PIN; // LED off
|
||||||
|
|
||||||
|
Serial.begin(115200); // Teensy/32u4 disregards baud rate; is OK!
|
||||||
|
|
||||||
|
SPI.begin();
|
||||||
|
SPI.setBitOrder(MSBFIRST);
|
||||||
|
SPI.setDataMode(SPI_MODE0);
|
||||||
|
SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz
|
||||||
|
// WS2801 datasheet recommends max SPI clock of 2 MHz, and 50 Ohm
|
||||||
|
// resistors on SPI lines for impedance matching. In practice and
|
||||||
|
// at short distances, 2 MHz seemed to work reliably enough without
|
||||||
|
// resistors, and 4 MHz was possible with a 220 Ohm resistor on the
|
||||||
|
// SPI clock line only. Your mileage may vary. Experiment!
|
||||||
|
// SPI.setClockDivider(SPI_CLOCK_DIV4); // 4 MHz
|
||||||
|
|
||||||
|
// loop() is avoided as even that small bit of function overhead
|
||||||
|
// has a measurable impact on this code's overall throughput.
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
|
||||||
|
// Implementation is a simple finite-state machine.
|
||||||
|
// Regardless of mode, check for serial input each time:
|
||||||
|
if((bytesBuffered < 256) && ((c = Serial.read()) >= 0)) {
|
||||||
|
buffer[indexIn++] = c;
|
||||||
|
bytesBuffered++;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(mode) {
|
||||||
|
|
||||||
|
case MODE_HEADER:
|
||||||
|
|
||||||
|
// In header-seeking mode. Is there enough data to check?
|
||||||
|
if(bytesBuffered >= HEADERSIZE) {
|
||||||
|
// Indeed. Check for a 'magic word' match.
|
||||||
|
for(i=0; (i<MAGICSIZE) && (buffer[indexOut++] == magic[i++]););
|
||||||
|
if(i == MAGICSIZE) {
|
||||||
|
// Magic word matches. Now how about the checksum?
|
||||||
|
hi = buffer[indexOut++];
|
||||||
|
lo = buffer[indexOut++];
|
||||||
|
chk = buffer[indexOut++];
|
||||||
|
if(chk == (hi ^ lo ^ 0x55)) {
|
||||||
|
// Checksum looks valid. Get 16-bit LED count, add 1
|
||||||
|
// (# LEDs is always > 0) and multiply by 3 for R,G,B.
|
||||||
|
bytesRemaining = 3L * (256L * (long)hi + (long)lo + 1L);
|
||||||
|
bytesBuffered -= 3;
|
||||||
|
mode = MODE_HOLD; // Proceed to latch wait mode
|
||||||
|
spiFlag = 0; // No data out yet
|
||||||
|
} else {
|
||||||
|
// Checksum didn't match; search resumes after magic word.
|
||||||
|
indexOut -= 3; // Rewind
|
||||||
|
}
|
||||||
|
} // else no header match. Resume at first mismatched byte.
|
||||||
|
bytesBuffered -= i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MODE_HOLD:
|
||||||
|
|
||||||
|
// Ostensibly "waiting for the latch from the prior frame
|
||||||
|
// to complete" mode, but may also revert to this mode when
|
||||||
|
// underrun prevention necessitates a delay.
|
||||||
|
|
||||||
|
if(micros() < t) break; // Still holding; continue buffering.
|
||||||
|
|
||||||
|
// Latch/delay complete. Advance to data-issuing mode...
|
||||||
|
LED_PORT &= ~LED_PIN; // LED off
|
||||||
|
mode = MODE_DATA; // ...and fall through (no break):
|
||||||
|
|
||||||
|
case MODE_DATA:
|
||||||
|
|
||||||
|
while(spiFlag && !(SPSR & _BV(SPIF))); // Wait for prior byte
|
||||||
|
if(bytesRemaining > 0) {
|
||||||
|
if(bytesBuffered > 0) {
|
||||||
|
SPDR = buffer[indexOut++]; // Issue next byte
|
||||||
|
bytesBuffered--;
|
||||||
|
bytesRemaining--;
|
||||||
|
spiFlag = 1;
|
||||||
|
}
|
||||||
|
// If serial buffer is threatening to underrun, start
|
||||||
|
// introducing progressively longer pauses to allow more
|
||||||
|
// data to arrive (up to a point).
|
||||||
|
if((bytesBuffered < 32) && (bytesRemaining > bytesBuffered)) {
|
||||||
|
mode = MODE_HOLD;
|
||||||
|
t = micros() + 60 + (32 - bytesBuffered) * 20;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// End of data -- issue latch:
|
||||||
|
t = micros() + 1000; // Latch duration = 1000 uS
|
||||||
|
LED_PORT |= LED_PIN; // LED on
|
||||||
|
mode = MODE_HEADER; // Begin next header search
|
||||||
|
}
|
||||||
|
} // end switch
|
||||||
|
} // end for(;;)
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop()
|
||||||
|
{
|
||||||
|
// Not used. See note in setup() function.
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
EXECS = colorswirl
|
||||||
|
|
||||||
|
all: $(EXECS)
|
||||||
|
|
||||||
|
colorswirl: colorswirl.c
|
||||||
|
cc -O2 colorswirl.c -lm -o colorswirl
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(EXECS) *.o
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
"Colorswirl" LED demo. This is the host PC-side code written in C;
|
||||||
|
intended for use with a USB-connected Arduino microcontroller running the
|
||||||
|
accompanying LED streaming code. Requires one strand of Digital RGB LED
|
||||||
|
Pixels (Adafruit product ID #322, specifically the newer WS2801-based type,
|
||||||
|
strand of 25) and a 5 Volt power supply (such as Adafruit #276). You may
|
||||||
|
need to adapt the code and the hardware arrangement for your specific
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
This is a command-line program. It expects a single parameter, which is
|
||||||
|
the serial port device name, e.g.:
|
||||||
|
|
||||||
|
./colorswirl /dev/tty.usbserial-A60049KO
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#define N_LEDS 25 // Max of 65536
|
||||||
|
|
||||||
|
int main(int argc,char *argv[])
|
||||||
|
{
|
||||||
|
int fd, i, bytesToGo, bytesSent, totalBytesSent = 0,
|
||||||
|
frame = 0, hue1, hue2, brightness;
|
||||||
|
unsigned char buffer[6 + (N_LEDS * 3)], // Header + 3 bytes per LED
|
||||||
|
lo, r, g, b;
|
||||||
|
double sine1, sine2;
|
||||||
|
time_t t, start, prev;
|
||||||
|
struct termios tty;
|
||||||
|
|
||||||
|
if(argc < 2) {
|
||||||
|
(void)printf("Usage: %s device\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if((fd = open(argv[1],O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) {
|
||||||
|
(void)printf("Can't open device '%s'.\n", argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial port config swiped from RXTX library (rxtx.qbang.org):
|
||||||
|
tcgetattr(fd, &tty);
|
||||||
|
tty.c_iflag = INPCK;
|
||||||
|
tty.c_lflag = 0;
|
||||||
|
tty.c_oflag = 0;
|
||||||
|
tty.c_cflag = CREAD | CS8 | CLOCAL;
|
||||||
|
tty.c_cc[ VMIN ] = 0;
|
||||||
|
tty.c_cc[ VTIME ] = 0;
|
||||||
|
cfsetispeed(&tty, B115200);
|
||||||
|
cfsetospeed(&tty, B115200);
|
||||||
|
tcsetattr(fd, TCSANOW, &tty);
|
||||||
|
|
||||||
|
bzero(buffer, sizeof(buffer)); // Clear LED buffer
|
||||||
|
|
||||||
|
// Header only needs to be initialized once, not
|
||||||
|
// inside rendering loop -- number of LEDs is constant:
|
||||||
|
buffer[0] = 'A'; // Magic word
|
||||||
|
buffer[1] = 'd';
|
||||||
|
buffer[2] = 'a';
|
||||||
|
buffer[3] = (N_LEDS - 1) >> 8; // LED count high byte
|
||||||
|
buffer[4] = (N_LEDS - 1) & 0xff; // LED count low byte
|
||||||
|
buffer[5] = buffer[3] ^ buffer[4] ^ 0x55; // Checksum
|
||||||
|
|
||||||
|
sine1 = 0.0;
|
||||||
|
hue1 = 0;
|
||||||
|
prev = start = time(NULL); // For bandwidth statistics
|
||||||
|
|
||||||
|
for(;;) {
|
||||||
|
sine2 = sine1;
|
||||||
|
hue2 = hue1;
|
||||||
|
|
||||||
|
// Start at position 6, after the LED header/magic word
|
||||||
|
for(i = 6; i < sizeof(buffer); ) {
|
||||||
|
// Fixed-point hue-to-RGB conversion. 'hue2' is an
|
||||||
|
// integer in the range of 0 to 1535, where 0 = red,
|
||||||
|
// 256 = yellow, 512 = green, etc. The high byte
|
||||||
|
// (0-5) corresponds to the sextant within the color
|
||||||
|
// wheel, while the low byte (0-255) is the
|
||||||
|
// fractional part between primary/secondary colors.
|
||||||
|
lo = hue2 & 255;
|
||||||
|
switch((hue2 >> 8) % 6) {
|
||||||
|
case 0:
|
||||||
|
r = 255;
|
||||||
|
g = lo;
|
||||||
|
b = 0;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
r = 255 - lo;
|
||||||
|
g = 255;
|
||||||
|
b = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
r = 0;
|
||||||
|
g = 255;
|
||||||
|
b = lo;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
r = 0;
|
||||||
|
g = 255 - lo;
|
||||||
|
b = 255;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
r = lo;
|
||||||
|
g = 0;
|
||||||
|
b = 255;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
r = 255;
|
||||||
|
g = 0;
|
||||||
|
b = 255 - lo;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting hue is multiplied by brightness in the
|
||||||
|
// range of 0 to 255 (0 = off, 255 = brightest).
|
||||||
|
// Gamma corrrection (the 'pow' function here) adjusts
|
||||||
|
// the brightness to be more perceptually linear.
|
||||||
|
brightness = (int)(pow(0.5+sin(sine2)*0.5,3.0)*255.0);
|
||||||
|
buffer[i++] = (r * brightness) / 255;
|
||||||
|
buffer[i++] = (g * brightness) / 255;
|
||||||
|
buffer[i++] = (b * brightness) / 255;
|
||||||
|
|
||||||
|
// Each pixel is offset in both hue and brightness
|
||||||
|
hue2 += 40;
|
||||||
|
sine2 += 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slowly rotate hue and brightness in opposite directions
|
||||||
|
hue1 = (hue1 + 5) % 1536;
|
||||||
|
sine1 -= .03;
|
||||||
|
|
||||||
|
// Issue color data to LEDs. Each OS is fussy in different
|
||||||
|
// ways about serial output. This arrangement of drain-and-
|
||||||
|
// write-loop seems to be the most relable across platforms:
|
||||||
|
tcdrain(fd);
|
||||||
|
for(bytesSent=0, bytesToGo=sizeof(buffer); bytesToGo > 0;) {
|
||||||
|
if((i=write(fd,&buffer[bytesSent],bytesToGo)) > 0) {
|
||||||
|
bytesToGo -= i;
|
||||||
|
bytesSent += i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Keep track of byte and frame counts for statistics
|
||||||
|
totalBytesSent += sizeof(buffer);
|
||||||
|
frame++;
|
||||||
|
|
||||||
|
// Update statistics once per second
|
||||||
|
if((t = time(NULL)) != prev) {
|
||||||
|
(void)printf(
|
||||||
|
"Average frames/sec: %d, bytes/sec: %d\n",
|
||||||
|
(int)((float)frame / (float)(t - start)),
|
||||||
|
(int)((float)totalBytesSent / (float)(t - start)));
|
||||||
|
prev = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// "Adalight" is a do-it-yourself facsimile of the Philips Ambilight concept
|
||||||
|
// for desktop computers and home theater PCs. This is the host PC-side code
|
||||||
|
// written in Processing; intended for use with a USB-connected Arduino
|
||||||
|
// microcontroller running the accompanying LED streaming code. Requires one
|
||||||
|
// strand of Digital RGB LED Pixels (Adafruit product ID #322, specifically
|
||||||
|
// the newer WS2801-based type, strand of 25) and a 5 Volt power supply (such
|
||||||
|
// as Adafruit #276). You may need to adapt the code and the hardware
|
||||||
|
// arrangement for your specific display configuration.
|
||||||
|
// Screen capture adapted from code by Cedrik Kiefer (processing.org forum)
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import processing.serial.*;
|
||||||
|
|
||||||
|
// This array contains the 2D image coordinates corresponding to each pixel
|
||||||
|
// in the LED strand, which forms a ring around the perimeter of the screen
|
||||||
|
// (with a one pixel gap at the bottom to accommodate the monitor stand).
|
||||||
|
|
||||||
|
static final int coord[][] = new int[][] {
|
||||||
|
{3,5}, {2,5}, {1,5}, {0,5}, // Bottom edge, left half
|
||||||
|
{0,4}, {0,3}, {0,2}, {0,1}, // Left edge
|
||||||
|
{0,0}, {1,0}, {2,0}, {3,0}, {4,0}, {5,0}, {6,0}, {7,0}, {8,0}, // Top edge
|
||||||
|
{8,1}, {8,2}, {8,3}, {8,4}, // Right edge
|
||||||
|
{8,5}, {7,5}, {6,5}, {5,5} // Bottom edge, right half
|
||||||
|
};
|
||||||
|
|
||||||
|
static final int arrayWidth = 9, // Width of Adalight array, in LED pixels
|
||||||
|
arrayHeight = 6, // Height of Adalight array, in LED pixels
|
||||||
|
imgScale = 20, // Size of displayed preview
|
||||||
|
samples = 20, // Samples (per axis) when down-scaling
|
||||||
|
s2 = samples * samples;
|
||||||
|
|
||||||
|
byte[] buffer = new byte[6 + coord.length * 3];
|
||||||
|
byte[][] gamma = new byte[256][3];
|
||||||
|
GraphicsDevice[] gs;
|
||||||
|
PImage preview = createImage(arrayWidth, arrayHeight, RGB);
|
||||||
|
Rectangle bounds;
|
||||||
|
Serial port;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
GraphicsEnvironment ge;
|
||||||
|
DisplayMode mode;
|
||||||
|
int i;
|
||||||
|
float f;
|
||||||
|
|
||||||
|
port = new Serial(this, Serial.list()[0], 115200);
|
||||||
|
|
||||||
|
size(arrayWidth * imgScale, arrayHeight * imgScale, JAVA2D);
|
||||||
|
|
||||||
|
// Initialize capture code for full screen dimensions:
|
||||||
|
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
gs = ge.getScreenDevices();
|
||||||
|
mode = gs[0].getDisplayMode();
|
||||||
|
bounds = new Rectangle(0, 0, screen.width, screen.height);
|
||||||
|
|
||||||
|
// A special header / magic word is expected by the corresponding LED
|
||||||
|
// streaming code running on the Arduino. This only needs to be initialized
|
||||||
|
// once (not in draw() loop) because the number of LEDs remains constant:
|
||||||
|
buffer[0] = 'A'; // Magic word
|
||||||
|
buffer[1] = 'd';
|
||||||
|
buffer[2] = 'a';
|
||||||
|
buffer[3] = byte((coord.length - 1) >> 8); // LED count high byte
|
||||||
|
buffer[4] = byte((coord.length - 1) & 0xff); // LED count low byte
|
||||||
|
buffer[5] = byte(buffer[3] ^ buffer[4] ^ 0x55); // Checksum
|
||||||
|
|
||||||
|
// Pre-compute gamma correction table for LED brightness levels:
|
||||||
|
for(i = 0; i < 256; i++) {
|
||||||
|
f = pow(float(i) / 255.0, 2.8);
|
||||||
|
gamma[i][0] = byte(f * 255.0);
|
||||||
|
gamma[i][1] = byte(f * 240.0);
|
||||||
|
gamma[i][2] = byte(f * 220.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw () {
|
||||||
|
BufferedImage desktop;
|
||||||
|
PImage screenShot;
|
||||||
|
int i, j, c;
|
||||||
|
|
||||||
|
// Capture screen
|
||||||
|
try {
|
||||||
|
desktop = new Robot(gs[0]).createScreenCapture(bounds);
|
||||||
|
}
|
||||||
|
catch(AWTException e) {
|
||||||
|
System.err.println("Screen capture failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
screenShot = new PImage(desktop); // Convert Image to PImage
|
||||||
|
screenShot.loadPixels(); // Make pixel array readable
|
||||||
|
|
||||||
|
// Downsample blocks of interest into LED output buffer:
|
||||||
|
preview.loadPixels(); // Also display in preview image
|
||||||
|
j = 6; // Data follows LED header / magic word
|
||||||
|
for(i = 0; i < coord.length; i++) { // For each LED...
|
||||||
|
c = block(screenShot, coord[i][0], coord[i][1]);
|
||||||
|
buffer[j++] = gamma[(c >> 16) & 0xff][0];
|
||||||
|
buffer[j++] = gamma[(c >> 8) & 0xff][1];
|
||||||
|
buffer[j++] = gamma[ c & 0xff][2];
|
||||||
|
preview.pixels[coord[i][1] * arrayWidth + coord[i][0]] = c;
|
||||||
|
}
|
||||||
|
preview.updatePixels();
|
||||||
|
|
||||||
|
// Show preview image
|
||||||
|
scale(imgScale);
|
||||||
|
image(preview,0,0);
|
||||||
|
println(frameRate);
|
||||||
|
|
||||||
|
port.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method computes a single pixel value filtered down from a rectangular
|
||||||
|
// section of the screen. While it would seem tempting to use the native
|
||||||
|
// image scaling in Processing, in practice this didn't look very good -- the
|
||||||
|
// extreme downsampling, coupled with the native interpolation mode, results
|
||||||
|
// in excessive scintillation with video content. An alternate approach
|
||||||
|
// using the Java AWT AreaAveragingScaleFilter filter produces wonderfully
|
||||||
|
// smooth results, but is too slow for filtering full-screen video. So
|
||||||
|
// instead, a "manual" downsampling method is used here. In the interest of
|
||||||
|
// speed, it doesn't actually sample every pixel within a block, just a 20x20
|
||||||
|
// grid...the results still look reasonably smooth and are handled quickly
|
||||||
|
// enough for video. Scaling the full screen image also wastes a lot of
|
||||||
|
// cycles on center pixels that are never used for the LED output; this
|
||||||
|
// method gets called only for perimeter pixels. Even then, you may want to
|
||||||
|
// set your monitor for a lower resolution before running this sketch.
|
||||||
|
|
||||||
|
color block(PImage image, int x, int y) {
|
||||||
|
int c, r, g, b, row, col, rowOffset;
|
||||||
|
float startX, curX, curY, incX, incY;
|
||||||
|
|
||||||
|
startX = float(screen.width / arrayWidth ) *
|
||||||
|
(float(x) + (0.5 / float(samples)));
|
||||||
|
curY = float(screen.height / arrayHeight) *
|
||||||
|
(float(y) + (0.5 / float(samples)));
|
||||||
|
incX = float(screen.width / arrayWidth ) / float(samples);
|
||||||
|
incY = float(screen.height / arrayHeight) / float(samples);
|
||||||
|
|
||||||
|
r = g = b = 0;
|
||||||
|
for(row = 0; row < samples; row++) {
|
||||||
|
rowOffset = int(curY) * screen.width;
|
||||||
|
curX = startX;
|
||||||
|
for(col = 0; col < samples; col++) {
|
||||||
|
c = image.pixels[rowOffset + int(curX)];
|
||||||
|
r += (c >> 16) & 0xff;
|
||||||
|
g += (c >> 8) & 0xff;
|
||||||
|
b += c & 0xff;
|
||||||
|
curX += incX;
|
||||||
|
}
|
||||||
|
curY += incY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return color(r / s2, g / s2, b / s2);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
// "Colorswirl" LED demo. This is the host PC-side code written in
|
||||||
|
// Processing; intended for use with a USB-connected Arduino microcontroller
|
||||||
|
// running the accompanying LED streaming code. Requires one strand of
|
||||||
|
// Digital RGB LED Pixels (Adafruit product ID #322, specifically the newer
|
||||||
|
// WS2801-based type, strand of 25) and a 5 Volt power supply (such as
|
||||||
|
// Adafruit #276). You may need to adapt the code and the hardware
|
||||||
|
// arrangement for your specific configuration.
|
||||||
|
|
||||||
|
import processing.serial.*;
|
||||||
|
|
||||||
|
int N_LEDS = 25; // Max of 65536
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[6 + N_LEDS * 3];
|
||||||
|
Serial myPort;
|
||||||
|
int i, hue1, hue2, bright, lo, r, g, b, t, prev, frame = 0;
|
||||||
|
long totalBytesSent = 0;
|
||||||
|
float sine1, sine2;
|
||||||
|
|
||||||
|
noLoop();
|
||||||
|
|
||||||
|
// Assumes the Arduino is the first/only serial device. If this is not the
|
||||||
|
// case, change the device index here. println(Serial.list()); can be used
|
||||||
|
// to get a list of available serial devices.
|
||||||
|
myPort = new Serial(this, Serial.list()[0], 115200);
|
||||||
|
|
||||||
|
// A special header / magic word is expected by the corresponding LED
|
||||||
|
// streaming code running on the Arduino. This only needs to be initialized
|
||||||
|
// once because the number of LEDs remains constant:
|
||||||
|
buffer[0] = 'A'; // Magic word
|
||||||
|
buffer[1] = 'd';
|
||||||
|
buffer[2] = 'a';
|
||||||
|
buffer[3] = byte((N_LEDS - 1) >> 8); // LED count high byte
|
||||||
|
buffer[4] = byte((N_LEDS - 1) & 0xff); // LED count low byte
|
||||||
|
buffer[5] = byte(buffer[3] ^ buffer[4] ^ 0x55); // Checksum
|
||||||
|
|
||||||
|
sine1 = 0.0;
|
||||||
|
hue1 = 0;
|
||||||
|
prev = second(); // For bandwidth statistics
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
sine2 = sine1;
|
||||||
|
hue2 = hue1;
|
||||||
|
|
||||||
|
// Start at position 6, after the LED header/magic word
|
||||||
|
for (i = 6; i < buffer.length; ) {
|
||||||
|
// Fixed-point hue-to-RGB conversion. 'hue2' is an integer in the
|
||||||
|
// range of 0 to 1535, where 0 = red, 256 = yellow, 512 = green, etc.
|
||||||
|
// The high byte (0-5) corresponds to the sextant within the color
|
||||||
|
// wheel, while the low byte (0-255) is the fractional part between
|
||||||
|
// the primary/secondary colors.
|
||||||
|
lo = hue2 & 255;
|
||||||
|
switch((hue2 >> 8) % 6) {
|
||||||
|
case 0:
|
||||||
|
r = 255;
|
||||||
|
g = lo;
|
||||||
|
b = 0;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
r = 255 - lo;
|
||||||
|
g = 255;
|
||||||
|
b = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
r = 0;
|
||||||
|
g = 255;
|
||||||
|
b = lo;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
r = 0;
|
||||||
|
g = 255 - lo;
|
||||||
|
b = 255;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
r = lo;
|
||||||
|
g = 0;
|
||||||
|
b = 255;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
r = 255;
|
||||||
|
g = 0;
|
||||||
|
b = 255 - lo;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resulting hue is multiplied by brightness in the range of 0 to 255
|
||||||
|
// (0 = off, 255 = brightest). Gamma corrrection (the 'pow' function
|
||||||
|
// here) adjusts the brightness to be more perceptually linear.
|
||||||
|
bright = int(pow(0.5 + sin(sine2) * 0.5, 2.8) * 255.0);
|
||||||
|
buffer[i++] = byte((r * bright) / 255);
|
||||||
|
buffer[i++] = byte((g * bright) / 255);
|
||||||
|
buffer[i++] = byte((b * bright) / 255);
|
||||||
|
|
||||||
|
// Each pixel is slightly offset in both hue and brightness
|
||||||
|
hue2 += 40;
|
||||||
|
sine2 += 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slowly rotate hue and brightness in opposite directions
|
||||||
|
hue1 = (hue1 + 4) % 1536;
|
||||||
|
sine1 -= .03;
|
||||||
|
|
||||||
|
// Issue color data to LEDs and keep track of the byte and frame counts
|
||||||
|
myPort.write(buffer);
|
||||||
|
totalBytesSent += buffer.length;
|
||||||
|
frame++;
|
||||||
|
|
||||||
|
// Update statistics once per second
|
||||||
|
if ((t = second()) != prev) {
|
||||||
|
print("Average frames/sec: ");
|
||||||
|
print(int((float)frame / (float)millis() * 1000.0));
|
||||||
|
print(", bytes/sec: ");
|
||||||
|
println(int((float)totalBytesSent / (float)millis() * 1000.0));
|
||||||
|
prev = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue