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