After playing with the Arduino and the LPD8806 LED strip for a while I hooked it up to the Arduino Ethernet (Arduino Uno with EthernetShield will also do) to build my own backlight for the iMac.
This is the part, which runs on the Arduino and accepts UDP datagram packages, parses them and does control the LPD8806 LED strip. I have a 1m with 32 LEDS, but that could be adapted.
// Includes #include "LPD8806.h" #include "SPI.h" #include <Ethernet.h> #include <EthernetUdp.h> // Setup the Ethernet to receive UDP packets byte mac[] = { 0x90, 0xA2, 0xDA, 0x0E, 0x0D, 0x48 }; IPAddress ip(192,168,2,2); unsigned int localPort=8888; EthernetUDP Udp; char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; char ReplyBuffer[] = "ACK\n"; char Reset[] = "Reset\n"; // Memory for the color #define RRR 0 #define GGG 1 #define BBB 2 uint32_t currentColor; uint32_t oldColor; byte cColor[3]; byte oColor[3]; // Setup the LEDstrup int dataPin = 2; int clockPin = 3; // Set the first variable to the NUMBER of pixels. 32 = 32 pixels in a row // The LED strips are 32 LEDs per meter but you can extend/cut the strip LPD8806 strip = LPD8806(32, dataPin, clockPin); // function prototypes, do not remove these! void colorChase(uint32_t c, uint8_t wait); void colorWipe(uint32_t c, uint8_t wait); void dither(uint32_t c, uint8_t wait); void scanner(uint8_t r, uint8_t g, uint8_t b, uint8_t wait); void wave(uint32_t c, int cycles, uint8_t wait); void rainbowCycle(uint8_t wait); uint32_t Wheel(uint16_t WheelPos); void setup() { // Start up the LED strip strip.begin(); // Update the strip, to start they are all 'off' strip.show(); colorChase(strip.Color(127,127,127),10); colorChase(0,0); Ethernet.begin(mac,ip); Udp.begin(localPort); cColor[RRR] = cColor[GGG] = cColor[BBB] = 0; oColor[RRR] = oColor[GGG] = oColor[BBB] = 0; currentColor = 0; oldColor = 0; } void loop() { int packetSize = Udp.parsePacket(); if (packetSize && Udp.available()) { Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); if (packetBuffer[0] == 'c') { // Format: cRRRGGGBB oldColor=currentColor; for (int i=0;i<3;i++) { oColor[i] = cColor[i]; } cColor[RRR] = (100 * (packetBuffer[1] - '0')) + (10 * (packetBuffer[2] - '0')) + (packetBuffer[3] - '0'); cColor[GGG] = (100 * (packetBuffer[4] - '0')) + (10 * (packetBuffer[5] - '0')) + (packetBuffer[6] - '0'); cColor[BBB] = (100 * (packetBuffer[7] - '0')) + (10 * (packetBuffer[8] - '0')) + (packetBuffer[9] - '0'); for(int i=0;i<3;i++) { cColor[i] = (cColor[i] < 0) ? 0 : cColor[i]; cColor[i] = (cColor[i] > 127) ? 127 : cColor[i]; } currentColor = strip.Color(cColor[RRR],cColor[GGG],cColor[BBB]); } if (packetBuffer[0] == 'p') { byte Pos = (100 * (packetBuffer[1] - '0')) + (10 * (packetBuffer[2] - '0')) + (packetBuffer[3] - '0'); Pos = (Pos < 0 ) ? 0 : Pos; Pos = (Pos > strip.numPixels()) ? strip.numPixels() : Pos; strip.setPixelColor(Pos, cColor[RRR], cColor[GGG], cColor[BBB]); } if (packetBuffer[0] == 'S') { strip.show(); delay(10); } if (packetBuffer[0] == 's') { scanner(cColor[RRR], cColor[GGG], cColor[BBB],30); } if (packetBuffer[0] == 'w') { colorWipe(currentColor,30); } if (packetBuffer[0] == 'C') { colorChase(currentColor,30); } if (packetBuffer[0] == 'r') { rainbowCycle(0); } if (packetBuffer[0] == 'd') { dither(currentColor, 30); } if (packetBuffer[0] == 't') { byte diffColor[3]; for(int i=0; i<3; i++) { diffColor[i] = (cColor[i] - oColor[i]) / 10; } for (int i=1; i<11; i++) { dither(strip.Color(oColor[RRR]+i*diffColor[RRR],oColor[GGG]+i*diffColor[GGG],oColor[BBB]+i*diffColor[BBB]),5); } dither(currentColor, 30); } if (packetBuffer[0] == 'R') { oldColor=currentColor; currentColor = 0; for (int i=0;i<3;i++) { oColor[i] = cColor[i]; cColor[i] = 0; } colorChase(0,0); Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());Udp.write(Reset);Udp.endPacket(); } Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());Udp.write(ReplyBuffer);Udp.endPacket(); //Udp.flush(); delay(10); } } // Cycle through the color wheel, equally spaced around the belt void rainbowCycle(uint8_t wait) { uint16_t i, j; for (j=0; j < 384 * 5; j++) { // 5 cycles of all 384 colors in the wheel for (i=0; i < strip.numPixels(); i++) { // tricky math! we use each pixel as a fraction of the full 384-color // wheel (thats the i / strip.numPixels() part) // Then add in j which makes the colors go around per pixel // the % 384 is to make the wheel cycle around strip.setPixelColor(i, Wheel(((i * 384 / strip.numPixels()) + j) % 384)); } strip.show(); // write all the pixels out delay(wait); } } // fill the dots one after the other with said color // good for testing purposes void colorWipe(uint32_t c, uint8_t wait) { int i; for (i=0; i < strip.numPixels(); i++) { strip.setPixelColor(i, c); strip.show(); delay(wait); } } // Chase a dot down the strip // good for testing purposes void colorChase(uint32_t c, uint8_t wait) { int i; for (i=0; i < strip.numPixels(); i++) { strip.setPixelColor(i, 0); // turn all pixels off } for (i=0; i < strip.numPixels(); i++) { strip.setPixelColor(i, c); // set one pixel strip.show(); // refresh strip display delay(wait); // hold image for a moment strip.setPixelColor(i, 0); // erase pixel (but don't refresh yet) } strip.show(); // for last erased pixel } // An "ordered dither" fills every pixel in a sequence that looks // sparkly and almost random, but actually follows a specific order. void dither(uint32_t c, uint8_t wait) { // Determine highest bit needed to represent pixel index int hiBit = 0; int n = strip.numPixels() - 1; for(int bit=1; bit < 0x8000; bit <<= 1) { if(n & bit) hiBit = bit; } int bit, reverse; for(int i=0; i<(hiBit << 1); i++) { // Reverse the bits in i to create ordered dither: reverse = 0; for(bit=1; bit <= hiBit; bit <<= 1) { reverse <<= 1; if(i & bit) reverse |= 1; } strip.setPixelColor(reverse, c); strip.show(); delay(wait); } delay(250); // Hold image for 1/4 sec } // "Larson scanner" = Cylon/KITT bouncing light effect void scanner(uint8_t r, uint8_t g, uint8_t b, uint8_t wait) { int i, j, pos, dir; pos = 0; dir = 1; for(i=0; i<((strip.numPixels()-1) * 8); i++) { // Draw 5 pixels centered on pos. setPixelColor() will clip // any pixels off the ends of the strip, no worries there. // we'll make the colors dimmer at the edges for a nice pulse // look strip.setPixelColor(pos - 2, strip.Color(r/4, g/4, b/4)); strip.setPixelColor(pos - 1, strip.Color(r/2, g/2, b/2)); strip.setPixelColor(pos, strip.Color(r, g, b)); strip.setPixelColor(pos + 1, strip.Color(r/2, g/2, b/2)); strip.setPixelColor(pos + 2, strip.Color(r/4, g/4, b/4)); strip.show(); delay(wait); // If we wanted to be sneaky we could erase just the tail end // pixel, but it's much easier just to erase the whole thing // and draw a new one next time. for(j=-2; j<= 2; j++) strip.setPixelColor(pos+j, strip.Color(0,0,0)); // Bounce off ends of strip pos += dir; if(pos < 0) { pos = 1; dir = -dir; } else if(pos >= strip.numPixels()) { pos = strip.numPixels() - 2; dir = -dir; } } } // Sine wave effect #define PI 3.14159265 void wave(uint32_t c, int cycles, uint8_t wait) { float y; byte r, g, b, r2, g2, b2; // Need to decompose color into its r, g, b elements g = (c >> 16) & 0x7f; r = (c >> 8) & 0x7f; b = c & 0x7f; for(int x=0; x<(strip.numPixels()*5); x++) { for(int i=0; i<strip.numPixels(); i++) { y = sin(PI * (float)cycles * (float)(x + i) / (float)strip.numPixels()); if(y >= 0.0) { // Peaks of sine wave are white y = 1.0 - y; // Translate Y to 0.0 (top) to 1.0 (center) r2 = 127 - (byte)((float)(127 - r) * y); g2 = 127 - (byte)((float)(127 - g) * y); b2 = 127 - (byte)((float)(127 - b) * y); } else { // Troughs of sine wave are black y += 1.0; // Translate Y to 0.0 (bottom) to 1.0 (center) r2 = (byte)((float)r * y); g2 = (byte)((float)g * y); b2 = (byte)((float)b * y); } strip.setPixelColor(i, r2, g2, b2); } strip.show(); delay(wait); } } /* Helper functions */ //Input a value 0 to 384 to get a color value. //The colours are a transition r - g - b - back to r uint32_t Wheel(uint16_t WheelPos) { byte r, g, b; switch(WheelPos / 128) { case 0: r = 127 - WheelPos % 128; // red down g = WheelPos % 128; // green up b = 0; // blue off break; case 1: g = 127 - WheelPos % 128; // green down b = WheelPos % 128; // blue up r = 0; // red off break; case 2: b = 127 - WheelPos % 128; // blue down r = WheelPos % 128; // red up g = 0; // green off break; } return(strip.Color(r,g,b)); }
This is the php script, which does convert a screenshot to a strip image, which gets send to the Arduino for display
<?php // // Screen background glow for a 32 LED Belt based on LDP8806 // // (C) 2013 -mat- filid brandy, brandy@klammeraffe.org // // Debugging error_reporting(E_ALL | E_STRICT); define('DEBUG', FALSE); // How to reach the Arduino Ethernet (or Arduino Uno with EthernetShield) // This used UDP to send the data across define('HOSTIP', '192.168.2.2'); define('PORT', 8888); // Where to find the screenshot define('SCREENSHOT','/Users/brandy/screenshot.png'); // Handle sending the data to the Arduino function w($socket,$msg) { if (DEBUG) print_r($msg." "); socket_sendto($socket,$msg,strlen($msg),0,HOSTIP,PORT); $buffer = ''; $i = 0; // Wait for ACK or a timeout do { if (!@socket_recvfrom($socket, $buffer, 516, 0, $remotehost, $remoteport)) { print_r(socket_strerror(socket_last_error())." "); $i=100; } else { if (DEBUG) print_r($buffer); $i++; } } while( ('ACK'."\n" != $buffer) && ($i < 40) ); // If we ran into a timeout, cancel the whole program if ($i > 40) { print_r("Timeout\n"); $msg = 'R'; socket_sendto($socket,$msg,strlen($msg),0,HOSTIP,PORT); exit; } } // Read the screenshot and prepare the temp buffers $screenshot = imagecreatefrompng(SCREENSHOT); $pixelated = imagecreatetruecolor(16,8); $strip = imagecreatetruecolor(32,1); // 1. Pixlate the original screenshot with $width/32 x $width/32 blocks $width = imagesx($screenshot); $height = imagesy($screenshot); imagefilter($screenshot, IMG_FILTER_PIXELATE,($width/32),true); // 2. Resize the pixelated screenshot into a 16x8 size imagecopyresampled($pixelated,$screenshot,0,0,0,0,16,8,$width,$height); /* TTTTTTTTTTTTTTTT L R L R L R L R => LLLLLLLLTTTTTTTTTTTTTTTTRRRRRRRR L R L R L R L R */ // 3. Generate the strip image, which will be send to the Arduino // 3.1 the 8 pixel of left and right for ($y=0;$y<8;$y++) { $c = imagecolorat($pixelated,0,$y); imagesetpixel($strip,8-$y,0,$c); $c = imagecolorat($pixelated,15,$y); imagesetpixel($strip,$y+24,0,$c); } // 3.2 the 16 pixel of top for ($x=0;$x<16;$x++) { $c = imagecolorat($pixelated,$x,0); imagesetpixel($strip,$x+8,0,$c); } if (DEBUG) { // Save the interim steps imagepng($pixelated,'resized.png'); imagepng($screenshot,'pixelated.png'); imagepng($strip,'strip.png'); } // Open the UDP socket with 2 seconds timeout $socket = socket_create(AF_INET,SOCK_DGRAM,SOL_UDP); socket_set_option($socket,SOL_SOCKET, SO_RCVTIMEO, array("sec"=>2, "usec"=>0)); // Read the pixels of the strip and send them to the Arduino $width=32; for($x=0;$x<$width;$x++) { $rgb = imagecolorat($strip,$x,0); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8 ) & 0XFF; $b = $rgb & 0xFF; // Adjust from [0-255] to [0-127] $r = $r >> 2; $g = $g >> 2; $b = $b >> 2; // Send color $msg = sprintf("c%03d%03d%03d",$r,$g,$b); w($socket,$msg); // Send pixel location $msg = sprintf("p%03d",$x); w($socket,$msg); } // Send display the change w($socket,'S'); // Close the UDP socket socket_close($socket);
This is how you get (a still slow) backlight running on an iMac. This could be easily adapted to Linux or Wndows.
while [ 1==1] do screencapture -T0 screenshot.png php Screenshow.php done
And here are some pictures of the installation:
This is the process from screenshot to pixelated to the strip version:
Cheers,
-mat-
Recent Comments