Christmas Lights!

jwatte's picture

Adafruit has some cool, bendable LED strips. You can buy up to 4 meters of them in a continuous strip, with 30, 60, or 144 LEDs per meter. You send the color to each of the LEDs as 24-bit RGB, using a serial bus (single-ended SPI) and a simple protocol.

The protocol for these DotStar LEDs wasn't documented, so I had to figure it out from the sample code. It's pretty easy:

Start by sending four 0 bytes.
Then, send each LEDs value as 0xff, B, G, R (one byte each)
Then, send four 0xFF bytes.

Apparently, these LEDs can kick it up to 30 MHz, although they're 5V devices, so a Raspberry Pi won't quite be able to drive the bus without a level converter of some sort (and the converter needs to keep a 30 MHz Manchester-coded signal (plain high/low) in reasonable shape.

But the order came with a free, bonus AdaFruit Trinket! The Trinket is kind-of cute; it's a fairly small circuit board, with a ATTiny85 microcontroller, a USB Micro connector, and a reset button. The microcontroller bit-bangs the USB bus to talk the 1.5 Mbps lowest-speed USB 1.1 protocol to run a bootloader that lets you easily download code from avrdude or the Arduino IDE. If you already have a serial programmer laying around, this isn't much interesting, except it saves board space because you don't need the ISP header, which is surprisingly big (bigger than the microcontroller chip itself.)

Anyway, I took one of these strands, wired them to the freebie Trinket, and wrote a little Arduino sketch/program to drive it in four different kinds of displays, and strung the lights into my Christmas tree. Unfortunately, the tree is quite big, so 4m of strand only reaches half-way up when draped in the middle of the tree.

Here's the code:

#include <avr/io.h>
#include <avr/power.h>
 
 
void write_spi(unsigned char val) {
  #define BIT() \
  if (val & 0x80) { \
    PORTB |= 2; \
  } else { \
    PORTB &= ~2; \
  }  \
  PORTB |= 4; \
  PORTB &= ~4; \
  val <<= 1;
  BIT();
  BIT();
  BIT();
  BIT();
  BIT();
  BIT();
  BIT();
  BIT();
}
 
unsigned char buf[3];
 
void write_values(void (*func)(int i , unsigned char *)) {
  write_spi(0);
  write_spi(0);
  write_spi(0);
  write_spi(0);
  for (int i = 0; i != 240; ++i) {
    func(i, buf);
    write_spi(0xff);
    write_spi(buf[0]);
    write_spi(buf[1]);
    write_spi(buf[2]);
  }
  write_spi(0xff);
  write_spi(0xff);
  write_spi(0xff);
  write_spi(0xff);
}
 
unsigned char state = 0;
unsigned char bstate[240];
 
 
void init_bright() {
  state = 0;
}
 
void iterate_bright() {
  state = (state + 1) & 0xff;
}
 
unsigned char brightval(unsigned char ch) {
  if (ch > 160) return 0;
  if (ch < 80) {
    return ch * 3;
  }
  return (160 - ch) * 3;
}
 
void generate_bright(int i, unsigned char *b) {
  unsigned char s = (state - i) & 0xff;
  b[0] = brightval(s);
  b[1] = brightval(s-80);
  b[2] = brightval(s+80);
}
 
void init_sparkles() {
  state = 0;
}
 
void iterate_sparkles() {
  state = rand() & 0xff;
  delay(100);
}
 
void generate_sparkles(int i, unsigned char * b) {
  unsigned char v = 0;
  if (i == state) {
    v = 0xff;
  }
  b[0] = b[1] = b[2] = v;
}
 
unsigned char glint_rand_color() {
  while (true) {
    unsigned char ret = rand() & 0xe0;
    if (ret) {
      return ret | 1 | 0x80;
    }
  }
}
 
void init_glint() {
  memset(bstate, 0, sizeof(bstate));
  int n = 10;
  while (n > 0) {
    unsigned char ch = rand() & 0xff;
    if (ch >= 240) {
      continue;
    }
    if (bstate[ch]) {
      continue;
    }
    bstate[ch] = glint_rand_color() | (rand() & 0x1e);
    --n;
  }
}
 
void iterate_glint() {
  for (int i = 0; i != 240; ++i) {
    if (!bstate[i]) {
      continue;
    }
    bstate[i] += 1;
    if (!(bstate[i] & 0x1f)) {
      bstate[i] = 0;
      while (true) {
        unsigned char q = rand() & 0xff;
        if (q >= 240) {
          continue;
        }
        if (bstate[q]) {
          continue;
        }
        bstate[q] = glint_rand_color();
        break;
      }
    }
  }
  delay(100);
}
 
void generate_glint(int i, unsigned char *b) {
  unsigned char ch = bstate[i];
  b[0] = b[1] = b[2] = 0;
  if (!ch) {
    return;
  }
  unsigned char v = ch;
  if (v & 0x10) {
    v = 0xf - (v & 0xf);
  } else {
    v = (v & 0xf);
  }
  v = v | (v << 4);
  if (ch & 0x80) {
    b[0] = v;
  }
  if (ch & 0x40) {
    b[1] = v >> 1;
  }
  if (ch & 0x20) {
    b[2] = v >> 2;
  }
}
 
void init_runner() {
  memset(bstate, 0, sizeof(bstate));
  for (int i = 0; i != 7; ++i) {
    bstate[i * 32] = glint_rand_color() | 0xf;
  }
}
 
void iterate_runner() {
  unsigned char ch = bstate[239];
  memmove(&bstate[1], &bstate[0], 239);
  bstate[0] = ch ? glint_rand_color() | 0xf : 0;
  delay(30);
}
 
void generate_runner(int i, unsigned char *b) {
  return generate_glint(i, b);
}
 
#define NUM_METASTATES 4
unsigned char metastate = 3;
unsigned long next = 0;
unsigned long lastms;
 
void reinit_metastate() {
  switch (metastate) {
    default: metastate = 0; /* fallthrough */ next = 1000;
    case 0: init_bright(); next = 15000; break;
    case 1: init_sparkles(); next = 30000; break;
    case 2: init_glint(); next = 120000; break;
    case 3: init_runner(); next = 180000; break;
  }
  lastms = millis();
}
 
void switch_metastate() {
  unsigned char old = metastate;
  while (old == metastate) {
    metastate = (long)(rand() & 0x7fff) * NUM_METASTATES / 32768;
  }
  reinit_metastate();
}
 
void setup() {
  if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
  // put your setup code here, to run once:
  PORTB &= ~0x6;
  DDRB |= 0x6;
  USISR = 0;
  USICR = 0; //(1 << USIWM0);
  reinit_metastate();
}
 
void loop() {
  switch (metastate) {
    case 0:
      iterate_bright();
      write_values(generate_bright);
      break;
    case 1:
      iterate_sparkles();
      write_values(generate_sparkles);
      break;
    case 2:
      iterate_glint();
      write_values(generate_glint);
      break;
    case 3:
      iterate_runner();
      write_values(generate_runner);
      break;
    default:
      switch_metastate();
      break;
  }
  unsigned long d = millis();
  unsigned long q = d - lastms;
  lastms = d;
  if (q >= next) {
    switch_metastate();
  } else {
    next -= q;
  }
}