Showing posts with label electronics. Show all posts
Showing posts with label electronics. Show all posts

20130319

Design note: "Charlieplexing" LED matrices: Pin savings of Charlieplexing, easy assembly of multiplexed LED modules.


Charlieplexing ( named after Charlie Allen ) is a great way to save pins on a microcontroller. Since each pin can be either high, low, or off ( 'high impedance' ), and LEDs conduct in only one direction, one can place two LEDs for for each unique pair of microcontroller pins. This also works with other devices, like buttons, but you can only place one for each pair of pins since they conduct in both directions*.

However, a recent foray into designing with charlieplexing revealed a drawback: soldering a zillion discrete LEDs is time consuming and not for everyone. It is easier to use LED modules, which have LEDs already wired up, and are designed to be driven by multiplexing. For an N by M multiplexed grid you need N+M driving pins. However, for an N by M charlieplexed grid you need only K pins where K(K-1)=NM **. However, there is often a way to charlieplex LED matrices to save pins without increasing assembly difficulty.

Thinking about charlieplexed grids

One might ask: how the hell am I supposed to keep track of all the possible combinations used in Charlieplexing? Since each pin can be either high (positive, or anode) or low (negative, or cathode), we can draw a K by K grid for K pins, where the cases where a pin acts as an anode are on one axis, and as a cathode, the other. Along the diagonal you have sites where a pin is supposed to act as both an anode and a cathode -- these are forbidden, and are blacked out. Here is an example grid for 16 pins:



Placing modules

I can now place components on this grid to fill it up. Say I have an 8x8 LED matrix with 8 cathodes and 8 anodes. All I have to do is find an 8x8 space large enough to hold it somewhere on the grid. For example, two 8x8 LED matrices fit into this 16 grid:



Another common size for LED matrices is 5x7. We can fit two of them on 12 pins like so:



Now it gets fun. It's ok for components to wrap around the sides. We can fit four 5x7 ( for a 10x14 pixel game board perhaps? ) matrices on 16 pins like this:



We can fit six 5x7 matrices on 18 pins ( for a 10x21 pixel game board perhaps? Large enough for original tetris! ). Eight 5x7 matrices fit on 20 pins. 8x8 matrices are a little more clunky, but you can still fit 3 of them onto 20 pins or 4 of them onto 22 pins ( 22 pins also fits 10 5x7 arrays ). We leave these last three as exercises. ( solutions 1 2 3 4)



To demonstrate that this approach does in fact work, I rigged up a little game of life on four 8x8 modules running on 22 pins on an AtMega328. After correcting for a problem with the brightness related to the PORTC pins sourcing less current, the display is quite functional -- the scanning is not visible and all lights are of equal brightness. I scan the lights one at a time, but only spend time on those that are on. (The variable frame rate is from the video processing -- the actual device is quite smooth)



Other packaged LED modules can be laid out similarly. 7 segment displays ( 8 with decimal point ) come packaged in "common cathode" and "common anode" configurations, which would be represented as a column of 8 cells, or a row of 8 cells, respectively. Often, four 7-segment displays ( 8 with decimal ) are packaged at once in a multiplexed manner -- these would be represented as a 4x8 or 8x4 block on our grid, depending on whether they were common anode or cathode. RGB LEDs also come packaged in common cathode and anode configurations. For example, here is how one could charlieplex 14 common anode RGB LEDs on 7 pins:

Hardware note: don't blow it up

When driving LEDs with multiplexing or charlieplexing, it is not uncommon to omit current limiting resistors. Since the grid is scanned, only a few LEDs will be on at once, and all LEDs spend most of their time off. If the supply voltage lies between the typical forward voltage, and the peak instantaneous voltage, we can figure out the largest acceptable duty cycle and enforce this in software. However, now one must ensure that software glitches do not cause the array scanning to stall, or that LEDs can survive a period of elevated forward voltage.

Microcontrollers will have a maximum safe current per IO pin. Sometimes, you can rely on the microcontroller to limit current to this level. Other times, attempting to force more than the maximum rating through a pin will damage the microcontroller. You can ensure that this never happens in software by never turning on more LEDs than a single IO pin can handle. Or, you can use tri-state drivers. If your microcontroller limits over-current, you can probably turn on as many LEDs as you want at once, but they will dim exponentially with the reduction in current per LED.

Combining devices

There is nothing stopping us from combining different types of LED modules, or LEDs and buttons, in our grid. However, buttons conduct in both the forward and backward direction, so they occupy both the anode-cathode and cathode-anode positions for any pair of pins. I represent this as a black and white pair of buttons in the grid drawing. For example, one could get an acceptable calculator with 6 display digits and 21 buttons onto 10 pins if you use a mix of common-cathode and common-anode 7-segment displays like so:



You could probably get a pretty decent mini-game using the space left over from Charlie-muxing four 5x7 modules on 16 pins. There is enough room to fit 17 buttons and 6 7-segment displays (shown as earth-tone strips below):



For the grand finale, we revisit the six 5x7 modules on 18 pins. Apart from giving us a grid large enough to hold classic Tetris, we also have room for 18 buttons, 6 7-segment displays (shown as earth-tone strips below), with 12 single-LED spots left over -- all on 18 pins. On an AtMega, this would leave 5 IO pins free -- enough room to fit a serial interface, piezo speaker, and crystal oscillator. Programming, however, would be a challenge.***

Hardware note: combining different LED colors in one grid

There are problems with combining different LEDs in one grid. If two LEDs with different forward voltages are placed on the same, say, cathode, then the one with the lower forward voltage can hog all the current, and the other LED won't light. I have found that ensuring in software that LEDs with mixed forward voltages are never illuminated simultaneously solves this problem.

Also ensure that your largest forward voltage is smaller than twice the lowest forward voltage. For example, if you try to drive a 3.6V white LED in a matrix that contains 1.8V red LEDs, the current may decide take a shortcut through two red LEDs rather than the white LED. However, it may be possible to ensure that there are no such paths by design. You must ensure that for every 3.6V forward path from pin A to B, there are no two 1.8V forward paths AC and CB for any C.

Driving software

Saving microcontroller pins and soldering time in well and good, but programming for these grids can be a real challenge! Here are some practices ( for AVR ) that I have found useful.
  • Overclock the processor. Most AVRs are configured to 1MHz by default, but can run up to 8MHz even without an external crystal. The AVR fuse calculator is a godsend. Test the program first without overclocking, then raise the clock rate. Ensure that the power supply voltage is high enough for the selected clock rate. If things get dire and you need more speed, you can tweak the OSCCAL register as well.
  • Prototype driver code on a device that can be removed and replaced if necessary. Repeated messing with the fuses to tweak the clock risks bricking the AVR. It's a shame when you have bricked AVR soldered in a TQFP package.
  • Row-scan the grid. If this places too much current on the IO pins, break each row into smaller pieces that are safe. If too many LEDs are lit on a row and they appear dim, adjust the time the row is displayed to compensate.
  • Store the LED state vector in the format that you will use to scan. Write set() and get() methods to access and manipulate this state that maps the structure of the charliplexing grid onto the logical structure of your displays. Scanning code is hard enough to get fast and correct without worrying about the abstract logical arrangement of the LED grid.
  • Use a single timer interrupt to do all of the scanning. Having multiple recurring timer interrupts along with a main loop can create interesting interference and beat effects in the LED matrix that are hard to debug.
  • If there are buttons and LEDs on the same grid, switch to polling the buttons every so often at a fixed interval, and write there state into volatile memory that other threads can query.
  • If your display is sparse ( e.g. a game of life ) you can skip sections that aren't illuminated to get a higher effective refresh rate. If your display is very sparse, and you have a lot of memory to spare, you can even scan LEDs one at a time.

Conclusion

This document outlines how to drive many LED modules from a limited number of microcontroller pins. The savings in part cost and assembly time are offset by increased code complexity. These design practices would be useful for someone who enjoys coding puzzles, or gets a kick out of making microcontrollers do way more than they are supposed to. They could also be useful for reducing part costs and assembly time for mass produced devices, where the additional time in driver development is offset by the savings in production. I originally worked through these notes when considering how to build easy-to-assemble LED marquee kits, but as I have no means to produce such kits, nor easy mechanism for selling them, I am leaving the notes up here for general benefit.

_______________________________________________
*If you place a diode in series with a button you can place two buttons for each unique pair of pins. One can make this diode a LED to create a button-press indicator.

**For those interested, K*(K-1)=N*M solves to K = ceil[ ( 1 + sqrt( 1+4NM ) ) / 2]

***I've tried this with an 8x16 grid using a maximally overclocked AtMega. It is tricky. To avoid beat effects, sound, display, and button polling are handled with the same timer interrupt. The music is intentionally restricted to notes that can be rendered with low clock resolution. Some day i may even write this up.

_______________________________________________

Driving software example: Game of Life on a 16x16 grid

Due to popular demand I've gone through and commented the source code for the game of life demo. There are probably some things that I could have done better, but hopefully it will be a good place to start.
/*
Game of Life charliplexing 4 8x8 LED Matrix demo.
Designed for AtMega

_______________________________________________________________________________
# to compile and upload using avr-gcc and avrdude:

# compile
avr-gcc -Os -mmcu=atmega328p ./display.c -o a.o
# grab the text and data sections and pack them into a binary
avr-objcopy -j .text -j .data -O binary a.o a.bin 
# check that the binary is small enough to fit!
du -b ./a.bin
# upload using the avr ISP MKII. In this case, 
# it is located at /dev/ttyUSB1, but you would change that argument
# to reflect whichever device your programmer has been mounted as
avrdude -c avrispmkII -p m328p -B20 -P /dev/ttyUSB1 -U flash:w:a.bin

Hardware notes
===============================================================================

DDRx  : 1 = output, 0 = input
PORTx : output buffer
PINx  : digital input buffer ( writes set pullups )
                          ______
         !RESET     PC6 -|  U  |- PC5
                    PD0 -|     |- PC4
                    PD1 -|     |- PC3
                    PD2 -|     |- PC2
                    PD3 -|  m  |- PC1
                    PD4 -|  *  |- PC0
                    VCC -|  8  |- GND
                    GND -|     |- AREF
                    PB6 -|     |- AVCC
                    PB7 -|     |- PB5   SCK  ( yellow )
                    PD5 -|     |- PB4   MISO ( green )
                    PD6 -|     |- PB3   MOSI ( blue )
                    PD7 -|     |- PB2
                    PB0 -|_____|- PB1

        Programmer pinout, 6 pin:
                
        6 MISO +-+  VCC 3
        5 SCK  + + MOSI 2 
        4 RST  +-+  GND 1
        
        Programmer pinout, 6 pin, linear:
                
        6 MISO +  
        5 SCK  + 
        4 RST  +  
        VCC 3  +
        MOSI 2 +
        GND 1  +

        Programmer pinout, 10 pin:
                
        3 vcc  +-+   MOSI 2
               + +    
               + +]  RST  4 
               + +   SCK  5 
        1 gnd  +-+   MISO 6
        
    PORT : write to here to set output
    DDR  : write to here to set IO. 1 for output.
    PIN  : digital input

thanks to http://brownsofa.org/blog/archives/215 for explaining timer interrupts
*/

// avr io gives us common pin definitions, 
// and avr interrupt gives us definitions for setting interrupts. 
// ( we will use one timer interrupt to update the display )
#include <avr/io.h>
#include <avr/interrupt.h>

// Basic definitions. Our display is 16 pixels wide and 16 pixels high
// This is set in N
// There are 16*16=256 lights total, this is set in NN
// We also use NOP to control timing sometimes, we use an assembly wrapper
// reflected in the macro "NOP"
#define N 16
#define NN ((N)*(N))
#define NOP __asm__("nop\n\t")

// I use two different sets of buffers for display data. Part of this is 
// Laziness -- it does consume a lot of memory and wouldn't fly on say an
// AtMega8. But it makes the code a little easier to understand. 
// First, I store the display pixels in a "raster-life" format. Here, 
// Each rown of the display is stored in a sequence of integers, and 
// If a pixel is on, then the bit corresponding to that pixel is set to 1,
// Otherwise 0.
// These displays are used to run the game of life logic because it is 
// straightforward to read and write pixel data. 
// We use 16 bit integers because this makes more sense for a 16x16 array
// But this is an abstraction and 8 or 32 bit integers would work just as well.
// We use a double buffering strategy. So, we declare two buffers, and then
// also make two indirect pointers to these buffers. When we want to update
// the display state, we write into the "buff" pointer, while reading the
// previous game state from the "disp" pointer. Then, when we are ready to
// show the next frame, these pointers will be flipped.
#define BUFFLEN 16
uint16_t b0[BUFFLEN];
uint16_t b1[BUFFLEN];
uint16_t *buff=&b0[0];
uint16_t *disp=&b1[0];

// To actually scan the display, I use a sparse format. When we scan the lights
// we turn them on one at a time. This can make the display very dim if we
// have to turn on all of the lights. However, for something like the Game of
// Life, only a few lights are ever on at the same time. If we only worry 
// about scanning the lights that are on, then our display is much brighter.
// However, it is important that the code that scans the display is very
// fast, so that we can run it thousands of times per second so that there is
// no visible flicker to the human eye. So, we prepare a list of which lights
// are on ahead of time. Then, the display scanning code only has to refer to
// this list, which is rapid.
// We use a double buffering strategy here. The active list of lights that 
// are on is stored in the "lightList" variable. This is the one that the 
// display scanning code acually uses. When we want to prepare a new list,
// we write it into the lightBuff variable, then flip it once it is ready. 
// I am somewhat wasteful here and allocate enough space to store two lists
// that might contain all 256 LEDs. I suspect there are ways to use less 
// space by being clever, but I can't think of anything at the moment. 
uint8_t ll0[NN],ll1[NN];
volatile uint8_t *lightList = &ll0[0];
volatile uint8_t *lightBuff = &ll1[0];
volatile uint16_t lighted = 0;

// This is a simple random number generator. It is not very good, and will
// produce the same sequence of numbers each time the AtMega is turned on,
// but it will siffice for this demonstration.
uint32_t rng = 6719;
uint8_t rintn()
{
    rng = ((uint64_t)rng * 279470273UL) % 4294967291UL;
    return rng&0xff;
}

// To simplify changing the pin states of the AtMega, I group the three ports
// PORTB PORTC and PORTD into one logical port and then set all three with
// one function call. 
void setDDR(uint32_t ddr) 
{
    DDRB = ddr & 0xff;
    ddr >>= 8;
    DDRD = ddr & 0xff;
    ddr >>= 8;
    DDRC = ddr & 0xff;
}
void setPort(uint32_t pins)
{
    PORTB = pins & 0xff;
    pins >>= 8;
    PORTD = pins & 0xff;
    pins >>= 8;
    PORTC = pins & 0xff;
}

// Spin around NOP for a while -- a quick and lazy way to control timing
void delay(uint32_t n)  { while (n--) NOP;}

// Read and write functions for the display data. 
// This abstracts away the bit packing and unpacking.
// First argument: one of the display buffers ( either disp or buff )
// Second argument: the pixel index. We use row-major indexing, so 
// for example, the first row will count up indecies 0..15, then 
// the second row will start at index 16 .. 31 and so on and so forth.
// For the set method, the third argument is 0 or 1 -- 1 for "on" and 0 for
// "off"
uint8_t get(uint16_t *b,uint16_t i) { return (b[i>>4]>>(i&15))&1;}
uint8_t set(uint16_t *b,uint16_t i,uint8_t v) { if (get(b,i)!=v) b[i>>4]^=1<<(i&15);}

// The game of life wraps around at the edges. To abstract away some of the
// difficulty, these previous and next functions will return the previous
// or next row or column, automatically handlign wrapping around the edges.
// For example, the next column after column 15 is column 0. The previous
// row to row 0 is row 15.
uint8_t prev(uint8_t x) { return (x>0?x:N)-1; }
uint8_t next(uint8_t x) { return x<N-1?x+1:0; }

// These are parameters to configure the Game of Life. Sometimes the game
// gets stuck. To poke it, I randomly drop in some primative shapes. 
// I have stored these shapes in a bit-packed representation here.
// Also, the TIMEOUT variable tells us how long to let the game stay "stuck"
// before we try to add a new life-form to it. The variable shownFor counts
// how many frames the game has been stuck.
#define glider  0xCE
#define genesis 0x5E
#define bomb    0x5D
#define TIMEOUT 7
uint8_t shownFor;

// These are some macros to make coding the Game of Life a little easier. 
// When we update the game, we are always reading from the current state, 
// which is stored in the "disp" display buffer, and we are always 
// writing the next state into the "buff" display buffer. So, we can 
// simplify the Get and Set functions for readability.
#define getLifeRaster(i)    get(disp,i)
#define setLifeRaster(i,v)  set(buff,i,(v))
// We store the display data in a bit-vector which is row-major ordered. 
// The bit vector is indexed by a single number from 0 to 255, but we want 
// to think about the Game of Life in terms of rows and columns. So, 
// this macro just wraps conversion of row and column numbers into an index
// for readability
#define rc2i(r,c)           ((r)*N+(c))
// Finally, combine the index macro with the get and set macros to 
// create macros for reading and writing game state based on the row and
// column numbers. 
#define getLife(r,c)        getLifeRaster(rc2i(r,c))
#define setLife(r,c,v)      setLifeRaster(rc2i(r,c),v)

// One step in the Game of Life is to count the number of neighbors that 
// are "on". This function computes part of that: it counts the number of 
// neighbors that are "on" at the current position (r,c) and also the
// row above and below. This is not a Macro because the Game of Life 
// implementation here was ported from an implementation with much less
// flash memory. When you make a macro, that code is expanded with simple
// text substitution before it even gets to the compiler. This means that
// each time we "call" a macro a bunch of code gets included. Somtimes this
// can be optimized out, but not always. By making this a function, we help
// the compiler understand that each "call" can be computed using the same
// code, and this resulted in a smaller binary. TLDR: esoteric design choice
// for size optimization, IGNORE.
uint8_t columnCount(r,c) {
    return getLife(prev(r),c) + getLife(r,c) + getLife(next(r),c);
}

// We store the game state in raster-like buffers of pixel data, but we
// scan the display one light at a time based on a list of which lights are
// on. This function converts from the raster-like representation to the
// list reprentation. Once we have prepared a new frame of the game, we call
// this function to create a new list of "on" lights for the display scanning
// code. We also flip the pixel and light-list buffers here. 
void flipBuffers() 
{
    // flip the pixel buffers
    uint16_t *temp = buff;
    buff = disp;
    disp = temp;
    
    // scan the new pixel buffer for lit pixels and add them to the list
    // of lighted pixels for sparse scanning
    uint16_t r,c,i;
    uint16_t lightCount = 0;
    for (i=0;i<NN;i++)
    {
        if (get(disp,i))
        {
            lightBuff[lightCount]=i;
            lightList[lightCount]=i;
            lightCount++;
        }
    }
    
    //swap the sparse scanning data buffers
    lighted = lighted<lightCount?lighted:lightCount;
    
    volatile uint8_t *ltemp = lightBuff;
    lightBuff = lightList;
    lightList = ltemp;
    
    lighted = lightCount;
}

// pin definitions. We have wired up four different 8x8 LED matrices to
// The AtMega. I have kept the anodes and cathodes contiguous, so that
// For each array, the anodes and the cathodes use a sequential number of 
// Pins. The way I have set up my logical pin numbers, Pins 0..7 are PORTB
// Pins 8..15 are PORTD, and then the rest are PORTC. These arrays store
// The anode or cathode sequence starts for each of the 4 matrices.
const uint8_t cmap[4]  = { 21,9, 7,17};
const uint8_t amap[4]  = { 12,1,15, 4};

// I didn't know which anode/cathode was which when I wired up the arrays.
// LED matrix pinouts can be idiosyncratic like that. However, I did wire up
// each of the arrays in a consistent manner. So, I can fix this by applying
// a permutation in software. These arrays store the pin permuations for 
// the anodes and cathodes.
const uint8_t aperm[N] = {0,2,3,1,7,4,6,5};
const uint8_t cperm[N] = {4,3,1,5,0,6,7,2};

// This function handles the display scanning -- it is called from a timer
// interrupt. The variable scanI keeps track of which light we're currently
// displaying. The variable PWM keeps track of state so that we can turn some
// lights on longer than others. Each time a timer interrupt occurs, this
// code changes which light is displayed. Lights are only shown one at a time.
// The timer interrupt changes them fast enough that they appear to be all
// Illuminated simultaneously.
volatile uint8_t scanI = 0;
volatile uint8_t pwm   = 0;
ISR(TIMER0_COMPA_vect) 
{
    // PWM would allow us to vary the brightness by leaving some lights
    // on longer. Here, I just use it to correct for a problem with PORTC.
    // For some reason LEDs on PORTC are more dim than they should be. This 
    // is probably because I am driving the LEDs without current limiting 
    // resistors, and so LEDs on other ports are drawing more current than
    // I expected ( more than 40mA ). Thus, these LEDs are brighter than the
    // ones driven using PORTC. It may have something to do with PORTC also
    // being capable of analog input? Anyway, its unclear if this is bad but
    // this little PWM script seems to fix it. Also, I intentionally waste
    // CPU cycles using a delay loop so that the PWM branch takes as long
    // as the light-scanning branch -- this is so that the fraction of time
    // taken by the display scanning interrupt routine is roughtly constant.
    // This is important because if I returned early from the PWM branch, 
    // The extra CPU cycles would be used by the game logic and the game
    // frame rate would vary. A more correct way to do this is to wait after
    // each frame of the game of life has been computer, based on timer
    // interrupts. But just adding the delay here was easier for me.
    if (pwm)
    {
        pwm--;
        delay(110);
        return;
    }
    
    // We advance to the next light. If for some reason all of the lights are
    // off we return immediately. Technically, there are race conditions
    // with the flipBuffers() function, but the errors are not catestrophic
    // We only focus on lights that are on, and keep track of how many lights
    // are on in the "ligted" variable. 
    scanI++;
    if (!lighted) return;
    scanI%=lighted;
    
    // We look up which light should be on, which is stored in the lightList.
    // The lower 4 bits contain the column index, and the upper four bits 
    // contain the row index. Our display consists of 4 8x8 arrays. These are
    // laid out with a charlieplexing pattern. So, we do a test to see which
    // array our light at row, columb (r,c) should be in -- that is what the
    // variable 'b' stored. Then we can lookup which anode and cathode we 
    // should use to turn the light on, now that we know which array to use.
    uint8_t light = lightList[scanI];
    uint8_t r = light>>4;
    uint8_t c = light&0x0f;
    uint8_t b = (r<8?0:1)+(c<8?0:2);
    
    // Looking up which anode and cathode to use seems strange at first.
    // For each array, I've wired up the anodes and cathodes to be contiguous
    // So that, for example, the fourth array, the cathodes will start at 
    // logical pin 17, and proceed to logical pin 25. Except there are only
    // 21 pins, so it wraps around to the beginnign again rather than counting
    // Up to 24. So, we get the starting in, an count up from there, and use
    // Modulo to wrap around to the beginning again. 
    // Finally, I did not know which cathode or anode was which when I wired
    // up the arrays -- LED matrix pinouts can be rather idiosyncratic like 
    // that. So! The anodes and cathods were all out of order. So I have to
    // look up a permutation to get the right one for our specific row and
    // column. 
    uint8_t an = (amap[b]+aperm[r%8]+21)%22;
    uint8_t ct = (cmap[b]+cperm[c%8]+21)%22;

    // Now that we know which pins to use to turn on the light, we can 
    // actually set the pin state to achieve this. First we turn all the 
    // pins to high impedence ("Off" or "input mode"). This is because, to
    // set the right pin, we have to changes pins in PORTA, PORTB, and PORTC.
    // As far as I know, there is no way to change all three of these 
    // simultanously -- you have to do them one at a time. This means that
    // in the process of changing the pin states we might accidentally turn on
    // some lights we didn't mean to. To work around this, first turn off all
    // the pins, then set the pin state, then turn back on only the pins that
    // we need to use
    setDDR(0);
    setPort(1UL<<an);
    setDDR((1UL<<an)|(1UL<<ct));
    
    // If we are driving the anodes using PORTC, we use a brightness correction
    if (an>=16) pwm = 1;
}

int main()
{
    // I happen to be using the internal RC oscillator, which has an unknown
    // frequency. Setting OSCCAL to 0xff means "run the internal RC oscillator
    // "FAST". OSCCAL is supposed to be used for calirating the RC oscillator 
    // so that it runs at a known frequency, but here I just max out the 
    // calibration variable. Another way to do this is to change the fuses so 
    // that     // the RC oscillator runs faster -- but this works too. 
    OSCCAL=0xFF;

    // I want to configure a timer interrupt to chan the display.
    // I determined these values using the AtMega datasheet, and don't exactly
    // remember what they all mean. 
    // I know that we turn on timer interrupt 0 and set the prescaler 
    // (in TCCR0B) to something -- probably "as fast as you can". 
    // Then, we set it to "compare to counter" mode. This means that the
    // counter will incremement, and when it gets to OCR0A ( set here to 180 )
    // it will call our display scanning code. This lets you tweak how often
    // the display is scanned. If it too slow, you will see flickering. If
    // it is too fast, there will not be enough CPU cycles left over to run
    // the game logic.
    TIMSK0 = 2;  // Timer CompA interupt 1
    TCCR0B = 2;  // speed
    TCCR0A = 2;  // CTC mode
    OCR0A  = 180;// period
    
    // Initialize the game to a random state, and update the lightList to
    // reflect this.
    uint8_t r,c;
    for (r=0;r<16;r++) buff[r] = rintn();
    flipBuffers();
    sei();
    
    // Run the Game of Life
    // There are some tedious optimizations here. 
    // To compute the next frame in the Game of Life, one has to sum up
    // the numbr of "live" or "on" neighbors around each cell.
    // To optimize this, we keep a running count. 
    // When we want to count the number of "live" cells at position (r,c),
    // We look at how many cells were alive around position (r,c-1). Then, we 
    // Subtract the cells from column c-2 and add the cells from column c+1.
    // One could also keep a running count along the columns, but this
    // would require more intermediate state and the additional memory lookups
    // consume the benefit in this application. 
    // As we update the game, we also keep track of whetehr a cell changes. 
    // If cells are not changing, we make note of this, and if the game stays
    // stuck for a bit, then we add in new life-forms to keep things
    // interesting. 
    // One neat thing about double-buffering the game state is that the 
    // state from TWO frames ago is available in the output buffer, at least,
    // until we write the next frame over it. So we can take advantage of this
    // and also detect when the game gets stuck in a 2-cycle.
    while (1) 
    {
        uint8_t changed = 0;
        uint8_t k=0;
        for (r=0; r<N; r++)
        {
            uint8_t previous = columnCount(r,N-1);
            uint8_t current  = columnCount(r,0);
            uint8_t neighbor = previous+current;
            for (c=0; c<N; c++)
            {
                uint8_t cell = getLife(r,c);
                uint8_t upcoming = columnCount(r,next(c));
                neighbor += upcoming;
                uint8_t new = cell? (neighbor+1>>1)==2:neighbor==3;
                neighbor -= previous;
                previous = current ;
                current  = upcoming;
                changed |= new!=cell && new!=get(buff,rc2i(r,c));
                k += new;
                setLife(r,c,new);
            }
        }

        uint8_t l=0;
        if (!(k&&rintn())) l=genesis;
        if (!changed && shownFor++>TIMEOUT) l=genesis;
        if (l) {
            uint8_t r = rintn();
            uint8_t q = rintn();
            uint8_t a = rintn()&1;
            uint8_t b = rintn()&1;
            uint8_t i,j;
            for (i=0;i<3;i++)
            {    
                uint8_t c = q;
                for (j=0;j<3;j++) 
                {
                    setLife(r,c,(l&0x80)>>7);
                    l <<= 1;
                    c = a?next(c):prev(c);
                }
                r = b?next(r):prev(r);
            }    
            shownFor = 0;
        }
        flipBuffers();
    }
} 


20130206

DIY TinyMarquee: an Attiny24 based scrolling marquee

This project uses charlieplexing and software serial to push the capabilities of the AtTiny24. The source code and design files are up on a github repository.

Other DIY LED marquee projects may be of interest, including a large one hand-routed on protoboard, one made from Christmas lights, a network enabled display, one using ping-pong balls, and one implemented in the rear window of a car.



A terminal stock ticker

The python module ystockquote pulls stock prices and other information from Yahoo finance, "get_price(stockname)". Here is a short python script that downloads and formats stock prices and writes them to a local file.

Bitmap fonts 

We need to translate text into a format that we can send to a scrolling marquee. We send raw pixels, so that we can adjust fonts and graphics without re-loading the firmware. Columns of pixel data are sent as 5-bit integers over serial. I used Gimp to draw a font and this Jython script to convert it into bit-packed integers representing columns of pixels for each letter.*


To test converting text to the bitmap font, the script "scroll" takes the scraped data and scrolls it across a simulated marquee in the terminal ( there is also the short "terminal_marquee" script which scrolls indefinitely, but updates intermittently in the background )

Hardware

The hardware consists of 90 3mm LEDs arranged in a 5x18 grid. These are driven by 10 IO lines of an AtTiny24. The remaining free IO line is used to poll and listen for serial data.

There is also a 10K pull-up resistor on the AtTiny's reset pin, and a 0.1μF decoupling capacitor near the power pins. The surface mount AtTiny bridges one row of the LED pins, a bit unusual but this allows a compact layout with through-hole LEDs. The marquee gets power and data from a USB to TTL serial adapter.


Charlieplexing

Charlieplexing is a way to drive tons of LEDs form only a few pins. Since LEDs only light up when current is passed in one direction, you can place two LEDs for every unique pair of IO lines at your disposal. This lets you drive N*(N-1) LEDs from N IO pins.**

PCB design

While it is possible to wire up the grid of LEDs by hand, I would not recommend it. Instead of going through this tedium, you can design a custom board and get it professionally fabricated. I use the free version of Eagle Cad to design and prepare boards for manufacture. A full Eagle tutorial is beyond the scope of this writeup, but numerous tutorials can be found elsewhere online. The Eagle design files for this project can be found here.




Exporting gerber files for board fabrication

One you have finalized a board, you need to prepare design files for fabrication. PCB designs get exported to so called "Gerber" files, which are like the PDFs of circuit board design. Once you have these files you can send them off to a fab house for production. My favorite tutorial for this is on Hackaday.

For one-off boards, BatchPCB is the go-to place. For small runs, consider Advanced Circuits or Seeed studio's Fusion PCB service. For larger runs (more than 30), depending on board size, Goldphoenix is the place to go. Depending on which service you choose, you should get boards in a few days to five weeks. I used Seeed because it is relatively cheap, and it took about a month to ship to the US.

Part sourcing 

For cheap LEDs I use Ebay. For all other components ( especially the AVR microcontollers ), I source from Mouser or Digikey. For a low cost USB to Serial adaptor, look for "USB To RS232 TTL PL2303HX" on Ebay . These are cheaper than, say, an FTDI cable from Sparkfun, and have worked great for me. I'd hoped to save a few bucks by using charlieplexing and the AtTiny14 in the design -- the total cost of each board, shipping and USB-TTL converter included, from a lot of 10, is about $7.50:
10   boards     $30   
1000 LEDs       $10   
10   AtTiny     $14   
50   Resistors  $ 1   
10   Capacitors $ 1   
10   Headers    $ 1   
10   USB-TTL    $18   
-------------------   
                $75   
       /10      $ 7.50

Cleaning the boards 

After you're finished soldering, remove any solder flux adhered to the board. Apart from being unsightly, flux can be conductive and corrosive and damage the board over time. Hackaday has a good tutorial on this. We filled an old jar with 90% Isopropanol, dunked the boards in there, and shook them around for a while -- it worked wonderfully.

Software serial 

The Attiny24 does not have hardware support for serial ( UART ), so we'll have to make a software implementation. For information about the RS-232 communication protocol, see the wiki. I used polling at twice the serial rate (4800Hz) to monitor incoming serial data, which is works for transmitting five bit packets. Further details about the firmware can be found in the C source file.

Compiling with avr-gcc 

I never remember the commands to compile and upload firmware, so here are the commands for future reference.

#compile source to a file a.o targeting the AtTiny24
avr-gcc -Os -mmcu=attiny24 ./display_serial.c -o a.o 

#extract the text and data sections to make a binary for the AVR
avr-objcopy -j .text -j .data -O binary a.o a.bin 

# check the size ( this should be smaller than the amount of available flash )
du -b ./a.bin

# upload the binary to ( in this case ) the AtTiny24 
avrdude -c avrispmkII -p t24 -B4 -P /dev/ttyUSB1 -U flash:w:a.bin 


_____________________________

*If you don't have Jython or a working JVM installed, it may be easier for you to re-enter the font as text data and write a short conversion routine in python.

**If you're familiar with multiplexing there's a simple way to conceptualize board layout for charlieplexing. When you multiplex an NxN grid of LEDs, you use N IO lines to control power to the (-) ends of the LEDs, and N IO lines to control power to the (+) ends of the LEDs.  To go from multiplexing to charlieplexing, note that microcontroller pins can take on three sates : Low (-), High (+), and Off ("high impedence"). Each IO line can serve as both a (+) line and a (-) line. What happens if we use the same N pins to drive both the (-) and the (+) a multiplexed display? Everything works fine, as long as we use the "off" state to stop current. One problem: there some LEDs along the diagonal of the matrix that have their (+) and (-) driven by the same IO pin -- there is no way to make these light up. But, no worries, since we are laying out our own board, we can just delete these LEDs, or connect their (+) terminals to a reserved IO pin to regain control of them! Charlieplexed PCB design can be relatively simple: lay out a grid of LEDs as if you were to multiplex them, but connect the N anodes to the N cathodes, and either delete the N LEDs that end up being connected to the same IO line at both ends, or wire these up to a separate IO pin to finish things off.


20111003

Goggles Kits for VIA are Go !

Alright kids, as promised, flicker hallucination visor kits for VIA are packed and ready to go! We should have about 30 kits available at Assemble on Wednesday October 5, 4-7PM. There will also be drawdio kits and LED illuminated kites. The Visor kit is a sexy remix of the brain machine kit, which retails for $35. The Visor also cost that much to produce, but we're still looking for last minute sponsors to subsidize the Assemble workshop and bring that cost down a bit. The remainder of the kits ( about 50 in total ) will arrive by Saturday and will be available at the main event. Assembly instructions and additional information are hosted at www.treehovse.blogspot.com. If there are any extra kits, we've reserved a booth at the Pittsburgh Mini Maker Faire to make them available there. Let me know if you would like me to reserve you a kit. There also might be a very limited number of pre-built kits available, time permitting. By the way, this most excellent Visor cartoon has been brought to you by Austin Redwood.


20110919

Soon : Goggles kits for VIA Pittsburgh


WeAlone has teamed up with members of ReplayMyPlay to produce a simplified soldering kit version of the trip visor, complete with professionally fabricated circuit boards and laser etched artwork. This kit is tentatively scheduled for distribution at the Assemble hackerspace in Pittsburgh, in association with the VIA-Pgh electronic arts festival, Saturday October 8, and at the Pittsburgh Mini-MakerFaire later in October. The kit design and part sourcing are nearly complete, stay tuned for more details.


20110206

Project : Atomic Sun

Progress and innovation let us build a world that departs increasingly from the environment for which we evolved. To resolve the mismatch between our genetic disposition and the world we build, we must either adapt our environment or adapt ourselves. Winters are pretty dark up here, some days I'm not sure the sun even rises. So, I built this lamp. Its on a timer, and functions to keep the circadian rhythm intact.
These are instructions for building a very bright lamp with 20 bulbs and a truncated icosahedral core. Development set me back about $120 total ( bulbs included ), but you should be able to build this for as little as $40 not including the light-bulbs or cost of plastic.
Parts :
Materials:
  • electrical tape
  • super-glue ( I used Gorilla brand )
Tools:
  • Pliers
  • 3D printer
  • razor knife
  • wire cutters
  • wire strippers
  • Phillip's head screwdriver
Assembly:
First print out the indicated quantity of all printed parts.
More detailed assembly instructions for the lamp socket brackets can be found on the thingiverse page. Trim the bracket until the black socket rests flush inside. This is important, since we need the hexagonal cover plate to bond to both the bracket and the socket for a good fit.
The orientation of the socket within the bracket will matter later. The socket has a wide ridge. Align this ridge with a side of the bracket for 10 pieces. Align the ridge with a corner of the bracket for the other 10. Aligning randomly also works, as long as you don't align all sockets so that the wide parts face a side.
Print out 12 pentagonal pieces. All pieces have extra plastic to stabilize the hinge while printing. This can be removed easily with a razor knife.
Perform a test assembly with just the hexagonal pieces. Leave out the pentagons for now since they are hard to remove once assembled. Ensure that all light sockets fit properly and don't collide. You may have to experiment, rotating and swapping between pieces, to get everything to fit well. If all else fails you can tap apart one of the brackets and re-orient it.
Carefully unfold your test assembly into an as-linear-as-possible planar arrangement like below. The exact arrangement doesn't really matter, just so long as there isn't too much branching.
The lamp sockets clip onto 12 to 14 gauge electrical wire. The only 12 gauge wire I could find had too thick of insulation to work with these sockets. I used 16 gauge wire instead, which just barely works. Using scissors or a knife, separate one end of the lamp cord. Protect the ends with electrical tape. Starting at the far end, clamp the sockets to the cable in turn. The sockets are difficult to close, so I had to use pliers to get enough force.

Before you get excited and attach the plug to test everything, slide on the pentagonal hook piece over the cable. The top of the printed piece should be facing away from the assembly, toward the plug. I neglected to do this, and had to dis-assemble my plug to add this piece.
To assemble the plug, use needle-nose pliers to remove the orange stopper from the front of the plug. Remove the prongs. Thread the lamp cord through. Split and strip about 13mm from the end of each wire. Wrap the exposed wire around the bolts attached to the prongs, and tighten the bolts well. Replace the prongs and stopper.

Test each of your sockets. Turn everything over and plug in some lightbulbs. I did it the dangerous way by adding and removing bulbs ( I only had 2 at the time ) while the thing was plugged in. People that don't want to die should un-plug the setup while moving the bulbs. Better yet, order the bulbs with the rest of your parts and put them all in at once to test.
The next step is tricky. Unplug the setup and remove the bulbs. Turn over the setup. You are going to need to fold the pieces back into the polyhedral shape. The lamp cord is inflexible and resists folding, but bending each joint beforehand helps. Adding in the pentagons while folding provides more stability. As the polyhedron becomes more complete, it becomes more difficult to add pieces. If you're having trouble getting a hinge to mate, pry up slightly the side that is already in the polyhedron. The hinges come together more easily if pushed together from the side, rather than if pushed down from above.
When it was all done, the compressed cable overpowered the super-glue on a couple brackets, thankfully this mistake is easily fixed with more super-glue and some patience. You should end up with an object that looks more than a little bit like the detonation mechanism for an atomic bomb. The final assembly is very strong and the hinges will hold together without additional glue.
The last piece you'll insert is the one that contains the power cord and the rope or chain for hanging the lamp. I would attach rope or chain before you add this piece. Don't use polypropylene rope like I did, it doesn't hold knots. A chain would look nicer anyway.
Thats it. You're done. Hang the lamp somewhere, insert bulbs, and power up your own miniature sun.


20100920

Project, preliminary


Keegan and I are working on a new project. It should look something like this. We have the electrical stuff worked out, but may need some assistance with the mechanical aspects. The basic idea is to make a conveyor belt of phosphorescent fabric, then write things on it using a bar of ultraviolet LEDs.

/>


20100902

Drop Day Octahedron

I was digging through the archives and I found some documentation for the CCFL Octahedron project from Drop Day that was never published. We don't have photos of the build process, just a text description and the source code, as well as pictures of the finished project in action. This should be good for inspiration and perhaps source code for a similar project, if not total duplication.

Physical construction

Each edge of the octahedron structure is a 2-foot piece of wooden dowel rod. A hole is drilled perpendicular to each rod at each end. The rods at each vertex are held together with a double loop of fishing wire through these holes. Rods and wire are spraypainted black.

The lights are cold cathode flourescent bulbs, as sold for lighting computer cases. A pair of bulbs are attached to the outside of each edge with cable ties. Each pair is powered through an inverter attached to the inside of the edge. I've left some spare bulbs in the elevator closet in alley 3. A pair of bulbs together with inverter can be purchased online for about $8.

A power cable runs from each inverter to a common vertex where they are bundled together and exit through one side. Each of the twelve cables is dual-conductor 18-gauge (?) speaker wire. One end terminates in a connector accepted by the inverter; the other is permanently soldered to the control board.

The control board is powered through a long three-conductor ribbon cable terminating in a Molex 8981 male connector, which will interface with a standard ATX computer power supply. (To make an ATX power supply turn on without a motherboard present, one must connect the ~PS_ON signal on the motherboard connector, normally green, to ground.)


Electronics

The control board is built around an ATtiny2313 processor, two CD4094BC 8-bit shift registers, and three ULN2068 quad-channel Darlington switch arrays. The processor is clocked by an external 16 MHz crystal. The fuse extended and high bytes are left at factory default. The fuse low byte is programmed to 0xFF: no prescalar, no clock output, external crystal, slow start-up. The shift registers are cascaded to act as a single 16-bit serial-in, parallel-out register. The processor has three outputs to the shift register: Data (pin PB3), Clock (pin PB2), and Latch (pin PB4). A single bit is loaded by setting Data high or low, setting Clock high for 4 processor cycles (250 ns), then bringing Clock low again. This is repeated 16 times, once per bit of a 16-bit word, with the least-significant bit first. After all 16 bits are loaded, the Latch pin is strobed high for 4 cycles, at which point the shift registers will present the 16 bits on their output pins.

12 of these 16 bits are wired to inputs of the Darlington switch arrays. When a switch-array input is at logic high, the corresponding output will sink current to ground, illuminating the corresponding lights. The unused bits of the shift register are (from LSB = 0) bits 6, 7, B, and F.

The power supply to the control board consists of ground, +5 V, and +12 V. The 12 V supply is wired directly to the positive side of each inverter's DC input. The negative side of each inverter's input sinks through a Darlington switch. When all bulbs are on, the system will draw about 5 amps on the 12 V line. It has an inline fuse holder loaded with a 10 amp fuse. The 5 V line is used for powering the logic chips only and should draw minimal current.

.. you see how much effort I'm putting here : screen shot of the monospace text figure

hdr2 is used to program the processor via a standard AVR programmer, such as the AVR ISP mkII. From top to bottom as oriented above, the pins are:

MOSI, MISO, SCK, VCC, ~RESET, GND

hdr1 is currently unused but is connected to the circuit. From top to bottom:

VCC, GND, not connected, PD4, PD5

This could be used to communicate with another board via a 2-wire serial protocol.

The push buttons will short PB5 or PB6 to ground when pressed. The switch will short PD6. Neither is used by the current software.


Software

See code at the end of this post. The software implements a few modes and switches between them. It includes a very simplistic random number generator (which is not random at all because the seed is hard-coded).


Bugs and quirks

Sometimes, especially when starting, the system will go into a bad state such as freezing or strobing synchronously in an unappealing fashion. I seem to have fixed this by adding another filtering capacitor on the 12 V rail, but the problem may return.

Sometimes after switching off the ATX power supply it will not turn back on for a number of minutes. Perhaps a large resistor between power and ground would fix this problem by acting as a stabilization load.

Many aspects of the circuit, such as the use of shift registers, an external crystal, and extra buttons and switches, are historical or arbitrary. Where the speaker wire meets the board it can short against unintended pins of the ULN2068 in ways that are not visually obvious. This can result in all four channels on that chip turning on when only one is activated in software.

You can pulse-width modulate a light channel in software, but it will not dim the light. Instead, it will light up part of the tube from one end, which looks cool and is probably very bad for the bulb.


Replacing the control board

The control board is a crufty prototype and it may be desired to replace it with something nicer: either a better-produced custom board or a totally off-the-shelf controller. Essentially all that is required is to switch on and off with some reasonable speed the sinking of ~ 500 mA current on each of 12 channels. This could be accomplished with any number of devices: Darlington pairs, power FETs, solid-state relays, etc. Optical isolation of the control signal may improve robustness. Many microcontrollers can provide the 12 I/O pins directly, so external shift registers are unnecessary.


Software


Here is the source code associated with the Octahedron. First, build-and-upload.sh, the bash commands used to compile and upload the program to the AVR.


#!/bin/sh -e


# build with avr-gcc suite
avr-g++ -o octahedron.bin -mmcu=attiny2313 -Wall -Winline -save-temps -fverbose-asm -Os octahedron.cpp
avr-size octahedron.bin
avr-objcopy -j .text -j .data -O ihex octahedron.bin octahedron.hex

# upload using AVR ISP mkII
avrdude -p t2313 -P usb -c avrispmkII -U flash:w:octahedron.hex


This is the straight up AVR C++. You'll need an in system programmer to upload it.



#define F_CPU 16000000L

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

typedef uint8_t byte;

// a shitty "random" number generator
uint16_t xrand() __attribute__ ((noinline));
uint16_t xrand() {
static uint16_t y = 3;
y ^= (y << 13);
y ^= (y >> 9);
return y ^= (y << 7);
}

// delay by an amount known at runtime
void delay_ms(uint16_t ms) {
while (ms-- > 0) _delay_ms(1);
}


// Define pins for the shift registers
#define SREG_PORT PORTB
#define SREG_DDR DDRB
#define SREG_LCH (1 << 4) // latch, high to set
#define SREG_DAT (1 << 3) // data
#define SREG_CLK (1 << 2) // clock on rising edge

// Define pins for the buttons and switch
#define UI_PORT PORTB
#define UI_DDR DDRB
#define UI_PIN PINB
#define UI_BTN_A (1 << 6)
#define UI_BTN_B (1 << 5)

#define UI_BTN_A_DOWN (!(UI_PIN & UI_BTN_A))
#define UI_BTN_B_DOWN (!(UI_PIN & UI_BTN_B))

#define UI_SW_PORT PORTD
#define UI_SW_DDR DDRD
#define UI_SW_PIN PIND
#define UI_SW (1 << 6)
#define UI_SW_EN (DARK_PIN & DARK_SW)

// Init the shift registers
inline void sreg_init() {
SREG_DDR |= (SREG_LCH | SREG_DAT | SREG_CLK);
}

// Optional delay when setting pins for the
// shift registers.
// At 5V they will handle at least 12 MHz.
inline void sreg_delay() {
asm volatile ("nop");
asm volatile ("nop");
asm volatile ("nop");
asm volatile ("nop");
}

// Shift a bit into the shift registers.
void sreg_shift_bit(bool x) {
if (x)
SREG_PORT |= SREG_DAT;
else
SREG_PORT &= ~SREG_DAT;

sreg_delay();
SREG_PORT |= SREG_CLK;
sreg_delay();
SREG_PORT &= ~SREG_CLK;
}

// A full shift-register write.
// Shift 16 bits, then latch.
void sreg_write(uint16_t x) {
for (byte i=0; i<16; i++) {
sreg_shift_bit(x & 1);
x >>= 1;
}

sreg_delay();
SREG_PORT |= SREG_LCH;
sreg_delay();
SREG_PORT &= ~SREG_LCH;
}

// The bits corresponding to wired-up channels are:
// FEDC BA98 7654 3210
// XXX XXX XX XXXX
// This function maps 12 contig. bits onto these.
uint16_t lights_to_channels(uint16_t x) {
return (x & 0x003F) // 0000 0011 1111
| ((x & 0x01C0) << 2) // 0001 1100 0000
| ((x & 0x0E00) << 3); // 1110 0000 0000
}

// Set which lights are on according to a 12-bit value.
uint16_t lights = 0;
void set_lights(uint16_t new_lights) {
static uint16_t state = 0;
lights = new_lights;
uint16_t new_state = lights_to_channels(lights);
uint16_t mask = 0;

//if (new_state == state) return;

for (byte i=0; i<16; i++) {
mask = (mask << 1) | 1;
sreg_write((state & (~mask)) | (new_state & mask));
_delay_us(500);
}

state = new_state;
}


/*
inline void ui_init() {
// pull up both buttons and the switch
UI_DDR &= ~(UI_BTN_A | UI_BTN_B);
UI_PORT |= (UI_BTN_A | UI_BTN_B);

UI_SW_DDR &= ~UI_SW;
UI_SW_PORT |= UI_SW;
}

inline void ui_debounce() {
_delay_ms(50);
}
*/

#define L_R1 0x400
#define L_G1 0x002
#define L_B1 0x004

#define L_R2 0x008
#define L_G2 0x040
#define L_B2 0x010

#define L_R3 0x100
#define L_G3 0x020
#define L_B3 0x080

#define L_R4 0x001
#define L_G4 0x200
#define L_B4 0x800

/// RGBRBGGBRGRB
/// BRGR BGGB RBGR
/// 0101 0000 1001 = 0x509
/// 0010 0110 0010 = 0x262
/// 1000 1001 0100 = 0x894

#define ALL_R (L_R1 | L_R2 | L_R3 | L_R4)
#define ALL_G (L_G1 | L_G2 | L_G3 | L_G4)
#define ALL_B (L_B1 | L_B2 | L_B3 | L_B4)

#define ALL_LIGHTS (ALL_R | ALL_B | ALL_G)

const uint16_t tris[8] PROGMEM = {
L_R2 | L_G2 | L_B2,
L_R1 | L_G3 | L_B2,
L_R3 | L_G3 | L_B3,
L_R4 | L_G2 | L_B3,
L_R3 | L_G1 | L_B4,
L_R4 | L_G4 | L_B4,
L_R2 | L_G4 | L_B1,
L_R1 | L_G1 | L_B1
};

const uint16_t squares[3] PROGMEM = {
ALL_R, ALL_G, ALL_B
};

/// modes

uint16_t rand_mask() {
return (1 << (xrand() % 12));
}

void noise_fast() {
uint16_t len = 300 + (xrand() % 300);
for (uint16_t t = 0; t < len; t++) {
set_lights(lights ^ rand_mask());
_delay_ms(60);
}
}

void fade_down() {
while (lights != 0) {
set_lights(lights & (~rand_mask()));
_delay_ms(1);
}
}

void fade_up() {
while (lights != ALL_LIGHTS) {
set_lights(lights | rand_mask());
_delay_ms(1);
}
}

void updown_fast() {
uint16_t len = 20 + (xrand() % 20);
for (uint16_t t = 0; t < len; t++) {
fade_up();
fade_down();
}
}

void rgb() {
uint16_t len = 150 + (xrand() % 100);
for (uint16_t t = 0; t < len; t++) {
for (byte i=0; i<3; i++) {
set_lights(pgm_read_dword(&squares[i]));
_delay_ms(120);
}
}
}

void tri_rand() {
uint16_t len = 300 + (xrand() % 700);
for (uint16_t t = 0; t < len; t++) {
set_lights(pgm_read_dword(&(tris[xrand() % 8])));
_delay_ms(60);
}
}

/* causes reset?
void tri_strobe() {
uint16_t len = 100 + (xrand() % 300);
for (uint16_t t = 0; t < len; t++) {
set_lights(pgm_read_dword(&(tris[xrand() % 8])));
_delay_ms(30);
set_lights(0);
delay_ms(xrand() % 200);
}
}
*/

/*
void test() {
for (byte i=0; i<12; i++) {
set_lights(1 << i);
delay_ms(1000);
}
set_lights(0);
delay_ms(5000);
}*/

int main() {

sreg_init();
//ui_init();

set_lights(0);

for (;;) {
rgb();
updown_fast();
rgb();
noise_fast();
rgb();
tri_rand();
}

}




20100529

Do it Yourself : Trip Visor

Thanks to a dumpster diving friend I had come into possesion of a pair of welding goggles, as well as some white translucent plexiglass. So, the old hallucination goggles project was adapted for this new, awesome, rugged, form factor.

Step 1 : electronics :
I chose to use an Arduino Pro-mini, two RGB LEDs for the goggles, and a 4 character multiplexed 7 segment display.

Step 2 : draw a circuit board. I like to use a generic drawing program so I can put graphics in the copper pattern. Some PCB drawing software will also allow this, although not all PCB fabrication services will do custom graphics. I skipped out on the current limiting resistors for the blue and green channels, since the supply voltage is actually lower than the LED driving voltage. I still needed them for the red though. This kind of design is very general, as you just need to get some LEDs to blink in a controlled manner. The circuit looks like this :



Next, transfer pattern, etch, drill, clean. It is easier to wire this up on a breadboard and then transfer it to a radioshack protoboard, to save on the hassle of making your own board. Here is a tutorial that I loosely followed, and here is a previous post where I practiced the technique, and below is the finished result. I made the traces from the Arduino to the LED display a too narrow, and unless you're careful this design requires clean up after etching.


I was wrong to try to drive this circuit from coin cell batteries. These batteries do not put out enough current to drive the LEDs. I worked around this by adding a 2xAAA battery pack to the interior of the goggles. If you copy my design, bear this in mind and adjust accordingly.


My friend laser cut white plexiglass to replace the tinted glass of the welding goggles. This is a square cut that could also be accomplished with a saw. These goggles have a slot for a piece of plexiglass on the inside, and another piece on the outside, with 7mm clearance in-between, so it is straightforward to sandwich the LEDs between two sheets of plexiglass to create diffused light. Position the LEDs approximately in the center of the visual field in each eye, so that when you look at them through a pane of the white plexiglass, they line up as if they were one diffuse source. I used standard connectors for indicator lights and power buttons in PC cases to connect the LEDs, and a rocker switch from an old Ikea lamp, to finish off the connections.


This is more durable than past designs, since it doesn't have a separate part for the driving hardware and the goggles, connected by a failure prone cable. Reminds me of this. I can actually toss this one around without breaking it.



20100228

Drop-Day 2010 Tech

I figured I'd start writing this up on the return flight from Drop Day, so I'm typing here at an odd, cramped, angle from my flight back from LA to Pittsburgh.
Drop-Day was a fine production, a victory for both hobbyist physical computing, and the forces of democratic freedom. I was impressed with the stark giant white cube dance floor with the, as Biff describes, lovecraftian monolith as a centerpiece. Something about the smaller size and the fog machine made people actually want to dance this year. The sensory rooms were excellent, although some of the code running the party never got off the ground. There is something uniquely appealing about a party that crashes, and requires rewriting of computer code and recompiliation on the fly. In addition to projected visuals ( Kanada, Perceptron, Cortex, Live and recorded video feeds, and other trippy renderings ), we had a few Alumni constructed blinkylights. Keegan completed a most excellent glowing octahedron, Suresh completed a rather nice modification of a commercial lamp, and I constructed several more pairs of goggles. I have spent most of my time travelling ( and very little sleeping ) this weekend, and it was well worth it. However, I doubt I'll be traveling back any time in the next five years. Others travel from much further away (London, Fairbanks) to go to this party, which should give you an idea of how important this party is to Dabney alumni.
RGB controlled diffuse illumination lamp :
Suresh successfully modified a modern style diffuse diffuse illumination lamp for controllable RGB color. He even designed and ordered a custom multi-layer board for the thing. I will try to track him down and see if designs and photographs are available anywhere.
CCFL octahedron :
This project was a wire-frame octahedron, approximately two feet on each edge. An octahedron can be viewed as 3 intersecting squares, once for each of the x, y, z, axes. In this design, each axis was assigned a specific color. The octahedron was constructed using two standard cold cathode fluorescent lighting tubes per edge, driven by black-box driving hardware that is powered by 12V DC. 12V is switched to the various edge drivers using darlington arrays controlled by an AtTiny2123(?), with 12V pulled from a modified desktop computer power supply. The skeleton of the octahedron itself was build by cutting wooden dowels to size, drilling a hole through each end, and joining the ends with zip-ties. The lights and driving hardware was also secured to the skeleton via zip ties. A great effect of the hue rotation on tie-dye style patterns is to cause the location of edges to appear to shift as the color changes and alternatively illuminates different parts of the pattern.
Revised goggles :
The goggles you see in these photographs still use the same old LEDs in ping-pong ball design, stripped down and controlled by an AtTiny13a. I would not recommend this design, as technically the chip is unable to source more than 60mA, where the goggles may require up to 120mA. Offhand the AtMega(4,8,16)8 chips are the only ones I can think of that can source sufficient current, and since they can hold more elaborate programs might be a better choice for future designs. Additionally, although the AVR micro-controllers can function at a range of voltages, the nonlinear V-I curve of the LEDs means that attempts to balance the white-point using resistors must be in the context of a well defined voltage ( preferably a constant 20mA current source, but that takes up board space ). Additionally, I was surprised that the internal resistance of coin-cell Cr2023 batteries limits them to approximately 0.3mA continuous draw. Although the much higher mean current draw of ~20mA for the goggles can be supported, this will cause the battery voltage to drop during operation and the LED white-point to drift. Eventually the voltage falls below the operating voltage of the AtTiny. The coin-cells will recover after about ~30 minutes of rebound. We're still seeing some problems with party-durability but hopefully refining the PCB board design and construction can improve on this. Building the goggles is incredibly annoying and I doubt I shall be constructing any more by the old methods for some time. I'm still a bit baffled as to how someone magically managed to repair solder connections and rebuild the connector on one of the goggles in the middle of the party, but ... thats Dabney house for you.
Laser Spirographs and Monolith-Monitor tower with EL wire :
I don't have good documentation on this at the moment, other than this system crashed a lot during the party, but was still super awesome.
Thanks to everyone who made this happen, it was great to see you all again.