Meow Wolf’s Due Return starfield: EXPOSED!


One of the things I’ve been working on during my protracted absence from my blog is a computer-controlled field of stars for Meow Wolf, a Santa Fe-based artist collective known for their anarcho-utopian-egalitarian mass installations in various places throughout Santa Fe. The typical Meow Wolf show has more stuff by more people crammed into it than could ever really be absorbed.

They have a large show coming up at the Center for Contemporary Art that involves an interdimensional, time-travelling wooden ship, an interactive alien landscape, caverns, a number of performances (with up to sixty characters played by various MWers, I’m told), musical acts, dance parties, and a lounge.

And a 400-light, 48-circuit, computer-controlled field of glowing stars that blankets the whole spectacle, which will be in a perpetual nighttime state. That’s what I’ve been working on, along with sculptor Mat Crimmins.

Technical details? Including code? You bet! Details after the jump.

The design is fairly straightforward: each of the 48 circuits has a brightness value (0-255). The circuits are represented by a 48-byte matrix inside the SRAM of an arduino. Modulating the brightness of each circuit is accomplished through pulse-width modulation. Arduinos don’t have 48 channels of PWM (heck, even the Mega only has 14 channels or something like that) so we fake it by blasting a bank of six shift registers (74HC595s) with serial data, toggling the circuits on and off as quickly as possible.

But, arduinos programmed in wiring are slow as hell. They don’t stand a chance of writing to the shift registers quickly enough to avoid horrifying strobing of the LEDs. So, I assist the arduino with two more microcontrollers, ATtiny2313s in this case, each dedicated to the task of strobing the shift registers quickly enough to do a decent PWM.

The arduino writes out its data to the tinys in parallel, so eight pins per tiny are dedicated to incoming data, plus a clock line, plus a reset line. Both tinys share one clock and reset line, so the total number of pins leaving the arduino is 8 + 8 + 2 or 18. Also, the starfield needs to be able to communicate with a computer over USB, so pins 0 and 1 are dedicated to that. If you’re counting, that’s 20 pins total: every single line on the arduino is occupied. Good thing you can use the analog input pins as digital outs!

Initially, I was dismayed to find that, even wired up to a speedy 20 MHz crystal, the tinys could only manage about 63 “frames” per second. With substantial optimization however I was able to get the speed to around 180 Hz, at which point they don’t strobe so bad. Ultimately I wound up hand-optimizing the new pixel (i.e. the clock line) interrupt in assembler and placing certain critical variables in dedicated registers. This wasn’t critical to PWM performance but I’ve always been bothered by how careless avr-gcc is when it pushes a dozen variables to the stack for interrupt service routines.

The secondary engineering challenge for the starfield was the amount of current the whole thing draws. The math gets scary quick: each super bright LED consumes about 20 milliamps of current. Each star has 3 LEDs, for 60 mA of current draw total. There are 384 stars, capable of consuming 23.04 amps total. That’s nothing to sneeze at! The number for the individual circuits aren’t so bad: 8 stars per circuit leads to 480 mA, which is manageable. But I found out the hard way that common transistors sweat a little under this load. It only takes a few seconds at this load for a common 2N4401 to grow hot to the touch.

The next step up is a power transistor in some kind of upgraded package, like a TO-220 or TO-3. These packages are easily heat sinkable but bigger. But, troublingly, the hFE of these transistors is low. Running the numbers on the shift registers, the 74HC595 that we were already using can only safely source something like 9mA of current — assuming that some of the current will be dissipated by the long cable runs over thin-gauge wire, and it’s not so certain at all that we’d be able to switch the full load on and off. So I went with a darlington pair, the TIP120.

Then I found out that one of the drawbacks of darlington pairs is their low switching speed. So as soon as the order came in I spent some time testing the TIP120s with a function generator and dummy load and found them good to around 100k. Phew. Another non-problem for our application.

Another possibility would have been to use power MOSFETs for the load switching. MOSFETs only need voltage to switch loads on and off — unlike a BJT, which must sink or source a small amount of base current. But my experience with MOSFETS is that they’re fairly delicate, easy to destroy through static electricity or improper handling, and susceptible to noise. I’m sure the design would have been fine with a cheap IRF510 or something, but I went with the darlingtons since I’m an amateur and the budget was too thin to tolerate many mistakes.

The next problem was the power supply. What can put out around 30 amps at 5 volts? Well, professional AC/DC power supplies available from Jameco, for one, but I happened to have a few ATX computer power supplies laying around which are capable of driving that sort of load. But when you compromise, you always wind up having to deal with a whole host of secondary challenges: the ATX power supplies were specced to about 25 amps, which made me nervous. So I decided to use two of them. In fact, not only are half the stars powered by each power supply, but half the controller mainboard as well: the entire project is partitioned in two, straight down to the tinies. So each power supply should only see around 15 amps drawn from the 5V rail, max.

One unforseen drawback to using computer power supplies in this context — or at least the ones I already had — is that they have minimum load requirements. Apparently this is typical of switching power supplies. They need to draw an amp or so to keep the switching regulator happy. So, after much consternation, I found a pile of 20 watt resistors at a local surplus shop, which I mounted to some steel tubing with heatsink paste and placed them close to the power supply fans. Attached to the 12v rails, they get quite warm but the temperature appears to be stable around 130 degrees fahrenheit.

Determining what load was necessary was an interesting experiment. Using an oscilloscope I monitored the 5v and 12v rails under various resistive loads and found that around half an amp from the 12v line removed a significant amount of ripple on the 5v rail, substantially quieting things. This corresponds to about 22 ohms and around 6 or 7 watts of heat dissipation. The preferred requirements could be higher, but I’ve been flying by the seat of my pants for this entire project. Besides, the 5v lines to the circuitry are heavily bypassed which removes quite a bit more of the residual noise, and the power supplies have been able to run for many hours without complaining. So I think it’ll be good to install on Thursday.

Below is the source code for the ATTiny2313 firmware. It may prove interesting for anybody who needs to control a pile of LEDs via fake PWM as I’ve done in this project. The first block is the main body of the code, written in C, then the C header file, then the assembly routines for the interrupts.

Main.c:

/*
 *  main.c
 *  ControllerFirmware
 *
 * Created by Conor Peterson on 3/16/11.
 * https://conorpeterson.wordpress.com
 * robotrobot@gmail.com
 *
 *  This is the firmware for the two ATTiny2313 microcontrollers for the starfield controller board.
 *  The starfield is a matrix of pwm-dimmable LED circuits. The circuits are driven by shift registers.
 *  Each microcontroller handles three shift registers and accepts data from an arduino about what circuits
 *  should be set to what value by an arduino which transmits data to the mcus in parallel.
 *
 *  The parallel protocol is quite simple: the arduino brings the PORTD_IN_CLOCK line high when new data
 *  has been presented at the tiny’s inputs. The tiny stuffs the pixel data into a matrix at some cursor
 *  value, incrementing the cursor by one. Every so often the arduino will being the PORTD_IN_RESET line
 *  high, which resets the cursor back to the home position and clears out the shift registers
 *
 */

#include "main.h"
#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

//Shift register pin/bit assignments on port D
#define PORTD_LATCH         0
#define PORTD_DATA          1  
#define PORTD_IN_RESET      2   //Interrupt 0
#define PORTD_IN_CLOCK      3   //Interrupt 1
#define PORTD_CLOCK         4
#define PORTD_RESET         5
#define PORTD_OUTPUTENABLE  6

#define MAX_PIXEL   24

volatile uint8_t matrix[MAX_PIXEL];         //Stores the visual brightness of each pixel
volatile register uint8_t pwmCounter asm("r4");
volatile register uint8_t writeIndex asm("r3");

int main(void)
{
    //Set up our data direction registers / pull-ups
    DDRD = 0x73;    //0111 0011 — PD2, PD3 as inputs; PD0, PD1, PD4, PD5, PD5 as outputs
    DDRB = 0;       //All inputs
       
    //Clear inputs/outputs. OE is active low, so bring it high while we initialize.
    //(i.e. this next line PREVENTS output and puts the SR into the reset state)
    PORTD = (1 << PORTD_OUTPUTENABLE);
    PORTB = 0;
   
    //Enable int0, int1
    GIMSK = (1 << INT0) | (1 << INT1 );
    //Specify int0, int1 happen on a rising edge
    MCUCR = (1 << ISC11) | (1 << ISC10) | (1 << ISC01) | (1 << ISC00);
   
    initMatrix(matrix, MAX_PIXEL);

    //Prepare the shift registers
    SBI(PORTD, PORTD_LATCH);    //SRs are in reset, so latching should carry the zeroes to the outputs
    asm volatile("nop"  "\n\t" "nop" "\n\t" );
    CBI(PORTD, PORTD_LATCH);
    SBI(PORTD, PORTD_RESET);    //Clear reset condition by bringing the line high
    CBI(PORTD, PORTD_OUTPUTENABLE); //Tie OE low
   
    sei();  //Globally enable interrupts
   
    //Okay, all ready. Let’s go.
    for(;;)
    {
        CBI(PORTD, PORTD_LATCH);
        CBI(PORTD, PORTD_RESET);
        SBI(PORTD, PORTD_RESET);
        writeMatrix();
        SBI(PORTD, PORTD_LATCH);
        //Ensure the shift registers catch the latch
        asm volatile("nop"  "\n\t" "nop" "\n\t" );
    }
   
    return 0;
}

/*
 writeMatrix() writes out the entire matrix and increments the PWM counter.
 Performance is crucial here.
 */

void writeMatrix(void)
{
    uint8_t counter;
    volatile uint8_t *z = matrix;
   
    pwmCounter++;   //Yep, it’ll overflow back to zero.
   
    for( counter = 0; counter < MAX_PIXEL; counter++ )
    {
        CBI(PORTD, PORTD_DATA);
        if( pwmCounter < *z )
            SBI(PORTD, PORTD_DATA);
        SBI(PORTD, PORTD_CLOCK);
        z++;
        CBI(PORTD, PORTD_CLOCK);
    }
}
/*
 Initialize the matrix with some different values to aid in debugging.
 The values packed into the matrix start at 255 (max brightness) and run
 backwards, being halved with every pixel. Afterward the matrix should be
 filled with data that’s both regular and quite distinct, to aid in
 physical circuit debugging.
*/

void initMatrix(volatile uint8_t *m, uint8_t len)
{
    uint8_t counter = 255;
   
    while( len != 0 )
    {
        *m = counter;
        counter >>= 1;
        if( counter == 0 )
            counter = 255;
        m++;
        len;
    }
    pwmCounter = 0;
}

Main.h:

/*
 *  main.h
 *  ControllerFirmware
 *
 * Created by Conor Peterson on 3/16/11.
 * https://conorpeterson.wordpress.com
 * robotrobot@gmail.com
 *
 */

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void);
void writeMatrix(void);
void shiftout(uint8_t data);
void initMatrix(volatile uint8_t *m, uint8_t len);
//ISR( INT0_vect ); Superceded by assembler routine.
//ISR( INT1_vect ); Superceded by assembler routine.

//Macros for clearing and setting individual bits in a register
#define CBI(port, bit) (port) &= ~(1 << (bit))
#define SBI(port, bit) (port) |= (1 << (bit))
 

Interrupt.S:

/*
 * interrupt.S
 * ControllerFirmware
 *
 * Created by Conor Peterson on 3/16/11.
 * http://conorpeterson.wordpress.com
 * robotrobot@gmail.com
 */
 

#include <avr/io.h>

#define rWriteIndex r3
#define cMAX_PIXEL  0x18

#define _SREG       0x3F
#define _PORTD      0x12
#define _PINB       0x16

.arch attiny2313
.global INT0_vect
INT0_vect:              ; int0 – reset the shift registers and write cursor
    push    r18
    in      r18,_SREG
    sbi     _PORTD, 6   ; Set output enable
    cbi     _PORTD, 5   ; Put SRs into reset by bringing reset low
    cbi     _PORTD, 4   ; Clear latch (or, ensure it’s clear)
    sbi     _PORTD, 4   ; Raise latch
    nop                 ; Wait for things to settle… (probably unnecessarily)
    nop
    cbi     _PORTD, 4   ; Clear latch
    sbi     _PORTD, 5   ; Clear reset condition (tie reset high again)
    cbi     _PORTD, 6   ; Clear OE
    eor     rWriteIndex, rWriteIndex        ; Reset write index (which is permanently bound to r3)
    out     _SREG, r18  ; Restore status register
    pop     r18
    reti

.global INT1_vect
INT1_vect:              ; int1 – read data into matrix in parallel
    push    r18
    push    r25
    push    r30
    push    r31
    in      r18, _SREG
    mov     r30, rWriteIndex
    eor     r31, r31
    in      r25, _PINB
    subi    r30, 0xA0   ; adjust for the location of matrix[]
    sbci    r31, 0xFF
    st      Z, r25
    inc     rWriteIndex
    ldi     r25, cMAX_PIXEL ; reusing r25 here since we can’t do a cpi with registers below 15 (why is this?)
    cp      rWriteIndex, r25
    brcs    INT1_RESUME
    eor     rWriteIndex, rWriteIndex
INT1_RESUME:
    out     _SREG, r18
    pop     r31
    pop     r30
    pop     r25
    pop     r18
    reti
   

 

Advertisements
    • Corvas
    • May 16th, 2011

    I wish I could have been there for the production of this stuff. really tremendous work!!!

  1. “Meow Wolfs Due Return starfield: EXPOSED! | Department of New Computings” Window Treatments For Sliding Glass Doors was indeed a very good article and
    I really was indeed extremely glad to read the blog post.
    Regards-Silvia

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s