// 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);
}
}