--- /dev/null
+
+PROGRAM=rgbstring
+SRC=version.c main.c logging.c serial.c
+OBJ=$(SRC:.c=.o)
+
+
+MCU=attiny45
+AVRDUDE_MCU=$(MCU)
+AVRDUDE_PROGRAMMER=usbasp
+
+CFLAGS=-Wall -Os -mmcu=$(MCU) -DUSE_LOGGING=1 -DF_CPU=8000000UL -std=gnu99
+LDFLAGS=
+AVRDUDE_FLAGS= -p$(AVRDUDE_MCU) -c $(AVRDUDE_PROGRAMMER) -B10
+
+FORMAT=ihex
+
+CC=avr-gcc
+OBJCOPY=avr-objcopy
+OBJDUMP=avr-objdump
+AVRDUDE=avrdude
+
+all: $(PROGRAM).hex $(PROGRAM).eep
+
+program: $(PROGRAM).hex $(PROGRAM).eep
+ $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(PROGRAM).hex:i -U eeprom:w:$(PROGRAM).eep:i
+
+program_flash: $(PROGRAM).hex
+ $(AVRDUDE) $(AVRDUDE_FLAGS) -U flash:w:$(PROGRAM).hex:i
+
+program_eeprom: $(PROGRAM).eep
+ $(AVRDUDE) $(AVRDUDE_FLAGS) eeprom:w:$(PROGRAM).eep:i
+
+dump_eeprom:
+ $(AVRDUDE) $(AVRDUDE_FLAGS) -U eeprom:r:eeprom.raw:r
+ od -tx1 eeprom.raw
+
+objdump: $(PROGRAM).elf
+ $(OBJDUMP) --disassemble $<
+
+.PRECIOUS : $(OBJ) $(PROGRAM).elf
+
+%.hex: %.elf
+ $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@
+
+%.eep: %.elf
+ $(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
+ --change-section-lma .eeprom=0 -O $(FORMAT) $< $@
+
+%.elf: $(OBJ)
+ $(CC) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS)
+
+%.o: %.c rgbstring.h Makefile
+ $(CC) -c $(CFLAGS) $< -o $@
+
+%.s: %.c rgbstring.h Makefile
+ $(CC) -S -c $(CFLAGS) $< -o $@
+
+%.o: %.S
+ $(CC) -c $(CFLAGS) $< -o $@
+
+clean:
+ rm -f $(PROGRAM).hex $(PROGRAM).eep $(PROGRAM).elf *.o *.s eeprom.raw
+
+version.c:
+ ./version.pl > version.c
+
+.PHONY: all clean dump_eeprom program program_flash program_eeprom objdump version.c
+
--- /dev/null
+
+Controller for the string of RGB LEDs
+
+CPU: ATtiny45
+
+Bill of materials:
+
+C3 10uF ceramic
+U1 ATtiny45-20SU
+
+Pin-out:
+
+PB0 header:
+ 2: Button 1
+PB1 header:
+ 2: DATA
+PB2 header:
+ 2: CLK
+PB3 header:
+ 2: Button 2
+PB4 header:
+ 2: Button 3
+
+
+
--- /dev/null
+#ifdef USE_LOGGING
+
+#include <avr/io.h>
+#include <avr/eeprom.h>
+
+#include "rgbstring.h"
+
+#define LOG_BUFFER 64
+static unsigned char log_buffer_ee[LOG_BUFFER] EEMEM;
+static unsigned char log_buffer_count;
+static unsigned char log_buffer[LOG_BUFFER];
+static unsigned char log_state EEMEM;
+/* Upper 4 bits are reset count, lower 4 bits are reset reason from MCUSR */
+static unsigned char reboot_count EEMEM = 0;
+static unsigned char can_write_eeprom = 0;
+static uint16_t flushed_end;
+
+void log_set_state(unsigned char val)
+{
+ if (can_write_eeprom)
+ eeprom_write_byte(&log_state, val);
+}
+
+void init_log()
+{
+ unsigned char r_count;
+
+ r_count = eeprom_read_byte(&reboot_count);
+ r_count >>= 4;
+
+ if (r_count < 5) {
+ r_count++;
+ eeprom_write_byte(&reboot_count,
+ (r_count << 4) | (MCUSR & 0xF));
+ MCUSR = 0;
+ can_write_eeprom = 1;
+ } else {
+ //eeprom_write_byte(&log_state, 0xFF);
+ can_write_eeprom = 0;
+ }
+
+ log_set_state(1);
+ log_buffer_count = 0;
+ flushed_end = 0;
+}
+
+void log_byte(unsigned char byte) {
+ if (log_buffer_count >= LOG_BUFFER)
+ return;
+
+ // eeprom_write_word(&log_buffer[log_buffer_count], word);
+ log_buffer[log_buffer_count++] = byte;
+
+ if (log_buffer_count == LOG_BUFFER)
+ log_flush();
+}
+
+void log_word(uint16_t word) {
+ log_byte(word & 0xFF);
+ log_byte(word >> 8);
+}
+
+void log_flush() {
+ unsigned char i;
+
+ if (!can_write_eeprom)
+ return;
+
+ for (i=flushed_end; i < log_buffer_count; i++) {
+ eeprom_write_byte(&log_buffer_ee[i],
+ log_buffer[i]);
+ }
+
+ flushed_end = i;
+
+ if (flushed_end == LOG_BUFFER)
+ log_set_state(0x42);
+}
+
+#endif
+
--- /dev/null
+#include <avr/io.h>
+#include <util/delay.h>
+#include <avr/interrupt.h>
+
+#include "rgbstring.h"
+
+static volatile uint16_t jiffies;
+
+// #define CHRISTMAS_TREE 1
+
+#define rgb_return(r, g, b) do { send_rgb((r), (g), (b)); return 1; } while(0)
+
+#define VERT_SIZE 47
+
+/* RNG from ADC noise */
+static unsigned char rand_pool[8], rand_pool_off, prev_bit, rand_pool_out;
+
+static void init_rng()
+{
+ ADCSRA |= _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1); // enable, clk/64
+ ADMUX = _BV(REFS1) | _BV(MUX0) | _BV(MUX3); // 1.1V, PB5:PB5, gain 20
+ DIDR0 = _BV(ADC0D);
+ ADCSRA |= _BV(ADIE) | _BV(ADSC);
+ rand_pool_off = 0;
+ prev_bit = 0;
+}
+
+static unsigned char rand() {
+ unsigned char rv = 0;
+
+ rv = rand_pool[rand_pool_out];
+ rand_pool_out++;
+ if (rand_pool_out >= sizeof(rand_pool))
+ rand_pool_out = 0;
+
+ return rv;
+}
+
+ISR(ADC_vect) {
+ ADCSRA |= _BV(ADSC);
+ jiffies++;
+ if ((rand_pool_off & 1) == 0) { // first bit of the pair
+ prev_bit = ADCW & 1;
+ rand_pool_off++;
+ } else {
+ unsigned char bit = ADCW & 1;
+ if (bit == prev_bit) { // whitening fail: try again
+ rand_pool_off--;
+ return;
+ }
+
+ if (bit) {
+ rand_pool[rand_pool_off >> 4]
+ ^= 1 << ((rand_pool_off >> 1) & 7);
+ }
+
+ rand_pool_off++;
+ if (rand_pool_off >= 16*sizeof(rand_pool))
+ rand_pool_off = 0;
+ }
+}
+
+static unsigned int slow_dim[] = {
+ 255, 27, 7, 2,
+};
+
+static void fill_color(unsigned char r, unsigned char g, unsigned char b)
+{
+ unsigned char i;
+
+ for (i = 0; i < STRIP_SIZE; i++)
+ send_rgb(r, g, b);
+
+ end_frame();
+}
+
+unsigned int state;
+
+static void do_buttons()
+{
+ static uint8_t prev_buttons = _BV(PB0) | _BV(PB3) | _BV(PB4);
+ static uint16_t prev_len = 0;
+
+ uint8_t buttons = PINB & (_BV(PB0) | _BV(PB3) | _BV(PB4));
+
+ if (prev_buttons == buttons) {
+ prev_len++;
+ return;
+ }
+
+ // was change
+ if (prev_len < 3 || (buttons != (_BV(PB0) | _BV(PB3) | _BV(PB4)))) {
+ prev_buttons = buttons;
+ prev_len = 0;
+ return;
+ }
+
+ if ((prev_buttons & _BV(PB0)) == 0) {
+ if (state)
+ state--;
+ } else if ((prev_buttons & _BV(PB3)) == 0) {
+ if (state < 5)
+ state++;
+ } else if ((prev_buttons & _BV(PB4)) == 0) {
+ state = 1;
+ }
+
+ prev_buttons = buttons;
+ prev_len = 0;
+}
+
+int main(void)
+{
+
+ init_log();
+ init_rng();
+ init_serial();
+
+ _delay_ms(3000/8); // wait for a bit and then increase the CPU clock
+ CLKPR = _BV(CLKPCE);
+ CLKPR = 0;
+
+ PORTB |= _BV(PB0) | _BV(PB3) | _BV(PB4); // pull-ups for buttons
+
+ state = 0;
+
+ sei();
+
+ while (1) {
+ unsigned char i;
+ static unsigned char c = 28;
+
+ do_buttons();
+
+ switch (state) {
+ case 0:
+ zero_frame();
+ break;
+ case 1:
+ i = 0;
+ while (i < STRIP_SIZE) {
+ send_rgb(4, 0, 0);
+ send_rgb(4, 1, 0);
+ send_rgb(0, 2, 0);
+ send_rgb(0, 1, 1);
+ send_rgb(0, 0, 2);
+ send_rgb(4, 0, 2);
+ i += 6;
+ }
+ end_frame();
+ break;
+ case 2:
+ if ((jiffies & 0x1ff) == 0) {
+ c++;
+ if (c >= 30)
+ c = 0;
+ }
+
+ for (i = 0; i < STRIP_SIZE; i++) {
+ unsigned char x = c; // + i / 2;
+ if (x >= 30)
+ x %= 30;
+ if (x < 10) {
+ send_rgb(8*(10-x), x, 0);
+ } else if (x < 20) {
+ send_rgb(0, 20-x, x-10);
+ } else {
+ send_rgb(8*(x-20), 0, 30-x);
+ }
+ }
+ end_frame();
+ break;
+ case 3:
+ fill_color(32, 4, 8);
+ break;
+ case 4:
+ fill_color(255, 92, 92);
+ break;
+ case 5:
+ fill_color(255, 255, 255);
+ break;
+ default:
+ { unsigned char light;
+
+ light = slow_dim[sizeof(slow_dim)/sizeof(slow_dim[0]) - state];
+ fill_color(4*light, light, 2*light);
+ }
+ break;
+ }
+ }
+}
+
--- /dev/null
+#ifndef LIGHTS_H__
+#define LIGHTS_H__ 1
+
+#define N_PWMLED_MODES 3
+
+/* logging.c */
+#ifdef USE_LOGGING
+void init_log();
+void log_set_state(unsigned char val);
+void log_flush();
+void log_byte(unsigned char byte);
+void log_word(uint16_t word);
+#else
+void inline init_log() { }
+void inline log_set_state(unsigned char val) { }
+void inline log_flush() { }
+void inline log_byte(unsigned char byte) { }
+void inline log_word(uint16_t word) { }
+#endif
+
+/* serial.c */
+#define STRIP_SIZE 10
+void init_serial();
+void zero_frame();
+void end_frame();
+void send_rgb(unsigned char r, unsigned char g, unsigned char b);
+
+#endif /* !LIGHTS_H__ */
+
--- /dev/null
+#include <avr/io.h>
+#include <util/delay.h>
+#include <avr/interrupt.h>
+
+#include "rgbstring.h"
+
+void init_serial()
+{
+ PORTB &= ~(_BV(PB1) | _BV(PB2));
+ DDRB |= _BV(PB1) | _BV(PB2);
+
+#if 0
+ TCCR0A = _BV(WGM01) | _BV(WGM00);
+ TCCR0B = _BV(WGM02) | _BV(CS00);
+ OCR0A = 2;
+#endif
+
+ zero_frame();
+}
+
+static void send_byte(unsigned char b)
+{
+ unsigned char i, mask;
+
+#if 0
+ USIDR = b;
+ USISR = _BV(USIOIF);
+ USICR = _BV(USIWM0) | _BV(USICS0);
+
+ while (!(USISR & _BV(USIOIF)))
+ ;
+#endif
+
+#if 0
+ USIDR = b;
+ USISR = _BV(USIOIF);
+
+ while ( (USISR & _BV(USIOIF)) == 0 ) {
+ USICR = _BV(USIWM0) | _BV(USICS1) | _BV(USICLK);
+ USICR = _BV(USIWM0) | _BV(USICS1) | _BV(USICLK) | _BV(USITC);
+ }
+#endif
+
+#if 0
+ for (i = 0; i < 8; i++) {
+ USICR = _BV(USIWM0) | _BV(USITC);
+ USICR = _BV(USIWM0) | _BV(USITC) | _BV(USICLK);
+ }
+#endif
+
+#if 1
+ for (i = 0; i < 8; i++) {
+ PORTB &= ~_BV(PB2); // clock low
+ if (b & 0x80) // data bit on or off
+ PORTB |= _BV(PB1);
+ else
+ PORTB &= ~_BV(PB1);
+ b <<= 1;
+ PORTB |= _BV(PB2); // clock high
+ }
+#endif
+}
+
+void end_frame()
+{
+ PORTB &= ~_BV(PB2); // clock low
+ _delay_us(1000);
+}
+
+void send_rgb(unsigned char r, unsigned char g, unsigned char b)
+{
+ send_byte(r);
+ send_byte(g);
+ send_byte(b);
+}
+
+
+void zero_frame()
+{
+ unsigned char i;
+
+ for (i = 0; i < STRIP_SIZE; i++) {
+ send_rgb(0, 0, 0);
+ }
+
+ end_frame();
+}
+
--- /dev/null
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX qw(strftime);
+
+my $git = `git rev-parse --short HEAD`;
+chomp $git;
+
+my $now = strftime('%Y%m%d', localtime(time));
+
+print <<EOF;
+/* DO NOT EDIT - GENERATED BY $0 */
+
+#include <avr/eeprom.h>
+
+unsigned char version[] EEMEM = {
+EOF
+
+print hex2c($git, "git revision");
+print hex2c($now, "date");
+
+print "};\n\n/* EOF - this file has not been truncated */\n\n";
+
+sub hex2c {
+ my ($data, $comment) = @_;
+
+ my $data1 = $data;
+ $data1 .= '0' if (length($data1) & 1 == 1);
+ $data1 =~ s/(..)/0x$1, /g;
+ return "\t$data1 /* $comment $data */\n";
+}
+