// Fading cellular automaton.  Inspired by George Dyson's work showing
// elementary-rule-110 executions on the IAS machine.  Intended to run
// on Limor Fried's minipov2 computer, in which the PORTB of an
// ATtiny2313 is connected to 8 LEDs in a row, but will probably run
// on any AVR microcontroller if you hook PORTB up to something
// interesting.

// Uses 288 bytes of flash (144 instructions) when compiled as
// follows:

// avr-gcc -c -I. -g -Os -funsigned-char -funsigned-bitfields
// -fpack-struct -fshort-enums -Wall -Wstrict-prototypes
// -DF_CPU=8000000 -Wa,-adhlns=fadeca.lst -mmcu=attiny2313 -std=gnu99
// fadeca.c -o fadeca.o

// avr-gcc -I. -g -Os -funsigned-char -funsigned-bitfields
// -fpack-struct -fshort-enums -Wall -Wstrict-prototypes
// -DF_CPU=8000000 -Wa,-adhlns=fadeca.o -mmcu=attiny2313 -std=gnu99
// fadeca.o --output fadeca.elf -Wl,-Map=.map,--cref

// avr-objcopy -O ihex -R .eeprom fadeca.elf fadeca.hex

#include <avr/io.h>      // this contains all the IO port definitions

// delta-sigma encoding has far less flicker than ordinary PWM.

// The inner loop here is 6 instructions or 9 instructions, depending
// on which path you take, so the oversampling rate should be about
// 1MHz.  But I can still occasionally see a little bit of flicker
// when I wave the thing in front of my face fast enough, presumably
// when it's on a very low duty cycle like 1/256 (which is actually
// about 1/160 due to the inequality in inner loop sizes mentioned
// earlier).  This means there's about 0.2ms between LED flashes at
// the lowest brightness, which is about a twentieth of an inch if I'm
// moving the minipov2 past my nose at 15MPH.  Which is in about the
// range of what I see.  It still seems absolutely incredible that my
// eye can detect a 1-microsecond pulse of light, so maybe I've
// misconstrued something here...

void deltasigma(unsigned char brightness, unsigned char pattern) {
    unsigned char ii, oldtimer;
    short error = 0;   // Apparently you need 9 bits to track the
                       // error for an 8-bit brightness... :(
    for (ii = 0; ii < 40; ii++) {
        oldtimer = 0;
        do {
            if (error >= 0) {
                PORTB = pattern;
                error += 256 - brightness;
            } else {
                PORTB = 0;
                error -= brightness;
            }
            oldtimer++;
        } while (oldtimer);
    }
}

// execute an elementary CA rule
unsigned char rule(unsigned char rule, unsigned char prevgen) {
    unsigned char ii;
    unsigned char rv = 0;
    unsigned char bitno;
    // Each time around the loop, we rotate the previous and next
    // generation by one bit.  avr-gcc is apparently smart enough to
    // turn this into rotate-rights, but this inner loop is still 34
    // instructions :(  Maybe I'll fix that when I understand AVR
    // assembly better.
    for (ii = 8; ii--;) {
        bitno = (prevgen & 7);
        rv = (((rv >> 1)      | ((rv & 1) << 7)) |
              ((rule & (1 << bitno)) >> bitno));
        prevgen = ((prevgen >> 1) | ((prevgen & 1) << 7));
    }
    return rv;
}

int main(void) {
    unsigned char bright;
    unsigned char pattern = 1;

    DDRB = 0xFF;        // set port B to output only

    while (1) {
        // Apparently if your brightness is 180/256, 181/256 doesn't
        // look that different.  So we increase by 1/16 of the current
        // brightness, plus 1 in case the current brightness is 15 or
        // less.
        for (bright = 1; bright < 200; bright += 1 + (bright >> 4)) {
            deltasigma(bright, pattern);
        }
        for (bright = 200; bright > 0; bright -= 1 + (bright >> 4)) {
            deltasigma(bright, pattern);
        }
        pattern = rule(110, pattern);
    }
}

Reply via email to