WS2812b Boblight / TV ambient lighting for Windows...

Tengis

Supreme [H]ardness
Joined
Jun 11, 2003
Messages
6,090
I recently put together a 92 WS2812B LED setup for my home theater. I can get it working in XBMC but I would like to get it going in Windows so I can use it with a plain media player like Media Player Classic.


Has anyone gotten it to work?

I tried following this guide but I cannot get it to work. Here is the code I used for Processing;

Code:
import java.awt.*;
import java.awt.image.*;
import processing.serial.*;
static final int timeout = 5000; // 5 seconds

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], 230400);
    }
    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[1], 115200);
}
/*
// using 12 RGB LEDs
static final int led_num_x = 4;
static final int led_num_y = 4;
static final int leds[][] = new int[][] {
  {1,3}, {0,3}, // Bottom edge, left half
  {0,2}, {0,1}, // Left edge
  {0,0}, {1,0}, {2,0}, {3,0}, // Top edge
  {3,1}, {3,2}, // Right edge
  {3,3}, {2,3}, // Bottom edge, right half
};

*/

// using 72 RGB LEDs
static final int led_num_x = 28;
static final int led_num_y = 18;
static final int leds[][] = new int[][] {
 {27,17},{26,17},{25,17},{24,17},{23,17},{22,17},{21,17},{20,17},{19,17},{18,17},{17,17},{16,17},{15,17},{14,17},{13,17},{12,17},{11,17},{10,17},{9,17},{8,17},{7,17},{6,17},{5,17},{4,17},{3,17},{2,17},{1,17},{00,17}, // Bottom
 {0,17},{0,16},{0,15},{0,14},{0,13},{0,12},{00,11}, {00,10}, {0,9}, {0,8},{00,07},{00,06},{00,05},{00,04},{00,03},{00,02},{00,01},{00,00}, //LEFT
 {00,00},{01,00},{02,00},{03,0},{04,0},{05,0},{06,0},{07,00},{8,0},{9,0},{10,0},{11,0},{12,0},{13,0},{14,0},{15,0},{16,0},{17,0},{18,0},{19,0},{20,0},{21,0},{22,0},{23,0},{24,0},{25,0},{26,0},{27,0},   //UP
{27,0},{27,1},{27,2},{27,3},{27,4},{27,5},{27,6},{27,7},{27,8},{27,9},{27,10},{27,11},{27,12},{27,13},{27,14},{27,15},{27,16},{27,17}, //RIGHT
};

static final short fade = 75;

static final int minBrightness = 120;

// Preview windows
int window_width;
int window_height;
int preview_pixel_width;
int preview_pixel_height;

int[][] pixelOffset = new int[leds.length][256];

// RGB values for each LED
short[][]  ledColor    = new short[leds.length][3],
      prevColor   = new short[leds.length][6];  

byte[][]  gamma       = new byte[256][3];
byte[]    serialData  = new byte[218];
int data_index = 0;

//creates object from java library that lets us take screenshots
Robot bot;

// bounds area for screen capture         
Rectangle dispBounds;

// Monitor Screen information    
GraphicsEnvironment     ge;
GraphicsConfiguration[] gc;
GraphicsDevice[]        gd;

Serial           port;

void setup(){

  int[] x = new int[16];
  int[] y = new int[16];

  // ge - Grasphics Environment
  ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
  // gd - Grasphics Device
  gd = ge.getScreenDevices();
  DisplayMode mode = gd[0].getDisplayMode();
  dispBounds = new Rectangle(0, 0, mode.getWidth(), mode.getHeight());

  // Preview windows
  window_width      = mode.getWidth()/5;
  window_height      = mode.getHeight()/5;
  preview_pixel_width     = window_width/led_num_x;
  preview_pixel_height   = window_height/led_num_y;

  // Preview window size
  size(window_width, window_height);

  //standard Robot class error check
  try   {
    bot = new Robot(gd[0]);
  }
  catch (AWTException e)  {
    println("Robot class not supported by your system!");
    exit();
  }

  float range, step, start;

  for(int i=0; i<leds.length; i++) { // For each LED...

    // Precompute columns, rows of each sampled point for this LED

    // --- for columns -----
    range = (float)dispBounds.width / led_num_x;
    // we only want 256 samples, and 16*16 = 256
    step  = range / 16.0; 
    start = range * (float)leds[i][0] + step * 0.5;

    for(int col=0; col<16; col++) {
      x[col] = (int)(start + step * (float)col);
    }

    // ----- for rows -----
    range = (float)dispBounds.height / led_num_y;
    step  = range / 16.0;
    start = range * (float)leds[i][1] + step * 0.5;

    for(int row=0; row<16; row++) {
      y[row] = (int)(start + step * (float)row);
    }

    // ---- Store sample locations -----

    // Get offset to each pixel within full screen capture
    for(int row=0; row<16; row++) {
      for(int col=0; col<16; col++) {
        pixelOffset[i][row * 16 + col] = y[row] * dispBounds.width + x[col];
      }
    }

  }

  // Open serial port. 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:
  // you can comment it out if you only want to test it without the Arduino
 //port = new Serial(this, Serial.list()[0], 115200);
port = openPort();
  // A special header expected by the Arduino, to identify the beginning of a new bunch data.  
  serialData[0] = 'o';
  serialData[1] = 'z';

}

void draw(){

  //get screenshot into object "screenshot" of class BufferedImage
  BufferedImage screenshot = bot.createScreenCapture(dispBounds);

  // Pass all the ARGB values of every pixel into an array
  int[] screenData = ((DataBufferInt)screenshot.getRaster().getDataBuffer()).getData();

  data_index = 2; // 0, 1 are predefined header

  for(int i=0; i<leds.length; i++) {  // For each LED...

    int r = 0;
    int g = 0;
    int b = 0;

    for(int o=0; o<256; o++)
    {       //ARGB variable with 32 int bytes where    
    int pixel = screenData[ pixelOffset[i][o] ]  ;
  r += pixel & 0x00ff0000;   
  g += pixel & 0x0000ff00;   
  b += pixel & 0x000000ff;     }   
    // Blend new pixel value with the value from the prior frame
    ledColor[i][0]  = (short)(((( r >> 24) & 0xff) * (255 - fade) + prevColor[i][0] * fade) >> 8);
    ledColor[i][1]  = (short)(((( g >> 16) & 0xff) * (255 - fade) + prevColor[i][1] * fade) >> 8);
    ledColor[i][2]  = (short)(((( b >>  8) & 0xff) * (255 - fade) + prevColor[i][2] * fade) >> 8);

    serialData[data_index++] = (byte)ledColor[i][0];
    serialData[data_index++] = (byte)ledColor[i][1];
    serialData[data_index++] = (byte)ledColor[i][2];

    float preview_pixel_left  = (float)dispBounds.width  /5 / led_num_x * leds[i][0] ;
    float preview_pixel_top    = (float)dispBounds.height /5 / led_num_y * leds[i][1] ;

    color rgb = color(ledColor[i][0], ledColor[i][1], ledColor[i][2]);
    fill(rgb);  
    rect(preview_pixel_left, preview_pixel_top, preview_pixel_width, preview_pixel_height);

  }

  if(port != null) {

    // wait for Arduino to send data
    for(;;){

      if(port.available() > 0){
        int inByte = port.read();
        if (inByte == 'y')
          break;
      }

    }
    port.write(serialData); // Issue data to Arduino

  }

  // Benchmark, how are we doing?
  println(frameRate);
  arraycopy(ledColor, 0, prevColor, 0, ledColor.length);
 
In case anyone is interested... a couple of these pics are crap quality.

Finished pic. Made a frame that bolts to the back of the TV.
alight%20(1).jpg


alight%20(2).jpg


alight%20(3).jpg


alight%20(4).jpg


alight%20(5).jpg



Used shoe goo to seal the ends.
alight%20(6).jpg


alight%20(7).jpg


alight%20(8).jpg


alight%20(9).jpg


alight%20(10).jpg


alight%20(11).jpg



Arduino code for my WS2812B that works with Ambibox:

Code:
// Slightly modified Adalight protocol implementation that uses FastLED
// library (http://fastled.io) for driving WS2811/WS2812 led stripe
// Was tested only with Prismatik software from Lightpack project
 
#include "FastLED.h"
 
#define NUM_LEDS 92 // Max LED count
#define LED_PIN 6 // arduino output pin
#define GROUND_PIN 10
#define BRIGHTNESS 90 // maximum brightness
#define SPEED 115200 // virtual serial port speed, must be the same in boblight_config
 
CRGB leds[NUM_LEDS];
uint8_t * ledsRaw = (uint8_t *)leds;
 
// 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_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 = 150000; // 150 seconds
 
void setup()
{
  pinMode(GROUND_PIN, OUTPUT);
  digitalWrite(GROUND_PIN, LOW);
  FastLED.addLeds<WS2811, LED_PIN, BRG>(leds, NUM_LEDS);
 
  // 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;
  int32_t outPos = 0;
 
  Serial.begin(SPEED); // Teensy/32u4 disregards baud rate; is OK!
 
  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) {
        memset(leds, 0,  NUM_LEDS * sizeof(struct CRGB)); //filling Led array by zeroes
        FastLED.show();
        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;
            outPos = 0;
            memset(leds, 0,  NUM_LEDS * sizeof(struct CRGB));
            mode           = MODE_DATA; // 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_DATA:
 
      if(bytesRemaining > 0) {
        if(bytesBuffered > 0) {
          if (outPos < sizeof(leds))
            ledsRaw[outPos++] = buffer[indexOut++];   // Issue next byte
          bytesBuffered--;
          bytesRemaining--;
        }
        // If serial buffer is threatening to underrun, start
        // introducing progressively longer pauses to allow more
        // data to arrive (up to a point).
      } else {
        // End of data -- issue latch:
        startTime  = micros();
        mode       = MODE_HEADER; // Begin next header search
        FastLED.show();
      }
    } // end switch
  } // end for(;;)
}
 
void loop()
{
  // Not used.  See note in setup() function.
}
 
This is really interesting. I'm afraid I can't do anything to help right now because I'm moving soon but I was interested in doing something similar when I get settled in and fix up my basement. Looking at that processing script, I feel like I could cut through alot of the overhead and write something cleaner and closer to the bare metal so it just runs off the frame buffer (i.e. backlighting everywhere in windows).
 
Jwhazel, I actually had issues with that processing code when I upgraded my htpc to Windows 8. Technically I still have the same issue but the new code I am using will "refresh" every time the issue pops up.

That code allows Ambibox to communicate with the Arduino. For whatever reason (I can't figure out why) the LEDs will go insane and start flickering random colors after about a minute of running. I think the issue may stem from the ws2812b not being clocked the same way as ws2801 and everything "comes out of sync"?

Who knows. My programming skills are very basic so my attempts to modify the Arduino sketches has not worked out well.
 
Back
Top