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