--- /dev/null
+
+PROGRAM=lights
+SRC=main.c logging.c adc.c pwm.c tmr.c pwmled.c gpio.c ambient.c pattern.c \
+ buttons.c battery.c control.c
+OBJ=$(SRC:.c=.o)
+
+
+MCU=attiny861a
+# AVRDUDE_MCU=$(MCU)
+AVRDUDE_MCU=attiny861
+AVRDUDE_PROGRAMMER=usbasp
+
+CFLAGS=-Wall -Os -mmcu=$(MCU) -DUSE_LOGGING=1 -DF_CPU=1000000UL -std=gnu99
+LDFLAGS=
+AVRDUDE_FLAGS= -p$(AVRDUDE_MCU) -c $(AVRDUDE_PROGRAMMER)
+
+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 lights.h Makefile
+ $(CC) -c $(CFLAGS) $< -o $@
+
+%.s: %.c lights.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
+
+.PHONY: all clean dump_eeprom program program_flash program_eeprom objdump
+
--- /dev/null
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#include "lights.h"
+
+#define AMBIENT_ADC N_PWMLEDS
+#define BATTERY_ADC (N_PWMLEDS + 1)
+#define ADC1_GAIN20 (N_PWMLEDS + 2)
+#define BUTTON_ADC (N_PWMLEDS + 3)
+#define ZERO_ADC (N_PWMLEDS + 4)
+
+#define NUM_ADCS ZERO_ADC
+
+struct {
+ unsigned char read_zero_log : 2;
+ unsigned char read_drop_log : 2;
+ unsigned char read_keep_log : 4;
+} adc_params[NUM_ADCS] = {
+ { 0, 1, PWMLED_ADC_SHIFT }, // pwmled 1
+ { 0, 1, PWMLED_ADC_SHIFT }, // pwmled 2
+ { 0, 1, PWMLED_ADC_SHIFT }, // pwmled 3
+ { 0, 1, AMBIENT_ADC_SHIFT }, // ambient
+ { 0, 1, 0 }, // battery
+ { 0, 1, 0 }, // gain20
+ { 0, 1, 0 }, // buttons
+};
+
+volatile static unsigned char current_adc, current_slow_adc;
+static uint16_t adc_sum, zero_count, drop_count, read_count, n_reads_log;
+#define ADC1_GAIN20_OFFSET_SHIFT 6
+static uint16_t adc1_gain20_offset;
+
+
+static void setup_mux(unsigned char n)
+{
+ /* ADC numbering: PWM LEDs first, then others, zero at the end */
+ switch (n) {
+ case 0: // pwmled 1: 1.1V, ADC0,1 (PA0,1), gain 20
+ ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX1) | _BV(MUX0);
+ break;
+ case 1: // pwmled 2: 1.1V, ADC2,1 (PA2,1), gain 20
+ ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
+ break;
+ case 2: // pwmled 3: 1.1V, ADC4 (PA5), single-ended
+ ADMUX = _BV(REFS1) | _BV(MUX2);
+ break;
+ case AMBIENT_ADC: // ambient light: 1.1V, ADC5 (PA6), single-ended
+ ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX0);
+ break;
+ case BATTERY_ADC: // batt voltage: 1.1V, ADC6 (PA7), single-ended
+ ADMUX = _BV(REFS1) | _BV(MUX2) | _BV(MUX1);
+ break;
+ case ADC1_GAIN20: // gain stage offset: 1.1V, ADC1,1, gain 20
+ ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
+ break;
+ case BUTTON_ADC: // buttons: 1.1V, ADC3, single-ended
+ PORTA |= _BV(PA3); // +5V to the voltage splitter
+ ADMUX = _BV(REFS1) | _BV(MUX1) | _BV(MUX0);
+ break;
+ case ZERO_ADC: // zero: 1.1V, ADC1 (PA1), single-ended
+ ADMUX = _BV(REFS1) | _BV(MUX0);
+ break;
+ }
+}
+
+static void start_next_adc()
+{
+ if (current_adc == 0) {
+ if (current_slow_adc > N_PWMLEDS) {
+ // read one of the non-PWMLED ADCs
+ current_adc = --current_slow_adc;
+ } else {
+ // no more non-PWMLEDs to do, start with PWMLEDs
+ current_adc = N_PWMLEDS-1;
+ }
+ } else if (current_adc >= N_PWMLEDS) {
+ // one of the non-PWMLED ADCs just finished, skip to PWMLEDs.
+ current_adc = N_PWMLEDS-1;
+ } else {
+ // next PWMLED
+ current_adc--;
+ }
+
+#if 0
+ log_byte(0x90 + current_adc); // debug ADC switching
+#endif
+
+ adc_sum = 0;
+ // we use the last iteration of zero_count to set up the MUX
+ // to its final destination, hence the "1 +" below:
+ if (adc_params[current_adc].read_zero_log)
+ zero_count = 1 + (1 << (adc_params[current_adc].read_zero_log-1));
+ else
+ zero_count = 1;
+
+ if (adc_params[current_adc].read_drop_log)
+ drop_count = 1 << (adc_params[current_adc].read_drop_log - 1);
+ else
+ drop_count = 0;
+
+ read_count = 1 << adc_params[current_adc].read_keep_log;
+ n_reads_log = adc_params[current_adc].read_keep_log;
+
+ // set up mux, start one-shot conversion
+ if (zero_count > 1)
+ setup_mux(ZERO_ADC);
+ else
+ setup_mux(current_adc);
+
+ ADCSRA |= _BV(ADSC);
+}
+
+void timer_start_slow_adcs()
+{
+ if (current_slow_adc > N_PWMLEDS) { // Don't start if in progress
+ log_byte(0x80 + current_slow_adc);
+ } else {
+ current_slow_adc = NUM_ADCS;
+ // TODO: kick the watchdog here
+ }
+}
+
+/*
+ * Single synchronous ADC conversion.
+ * Has to be called with IRQs disabled (or with the ADC IRQ disabled).
+ */
+static uint16_t read_adc_sync()
+{
+ uint16_t rv;
+
+ ADCSRA |= _BV(ADSC); // start the conversion
+
+ // wait for the conversion to finish
+ while((ADCSRA & _BV(ADIF)) == 0)
+ ;
+
+ rv = ADCW;
+ ADCSRA |= _BV(ADIF); // clear the IRQ flag
+
+ return rv;
+}
+
+void init_adc()
+{
+ unsigned char i;
+ current_slow_adc = NUM_ADCS;
+ current_adc = 0;
+
+ ADCSRA = _BV(ADEN) // enable
+ | _BV(ADPS1) | _BV(ADPS0) // CLK/8 = 125 kHz
+ // | _BV(ADPS2) // CLK/16 = 62.5 kHz
+ ;
+ // ADCSRB |= _BV(GSEL); // gain 8 or 32
+
+ // Disable digital input on all bits used by ADC
+ DIDR0 = _BV(ADC0D) | _BV(ADC1D) | _BV(ADC2D) | _BV(ADC3D)
+ | _BV(ADC4D) | _BV(ADC5D) | _BV(ADC6D);
+
+ // 1.1V, ADC1,1, gain 20
+ ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX0);
+
+ /* Do first conversion and drop the result */
+ read_adc_sync();
+
+ adc1_gain20_offset = 0;
+
+ for (i = 0; i < (1 << ADC1_GAIN20_OFFSET_SHIFT); i++) {
+ adc1_gain20_offset += read_adc_sync()
+ - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
+ }
+
+ ADCSRA |= _BV(ADIE); // enable IRQ
+
+ start_next_adc();
+}
+
+void susp_adc()
+{
+ ADCSRA = 0;
+ DIDR0 = 0;
+}
+
+static void adc1_gain20_adc(uint16_t adcsum)
+{
+ // running average
+ adc1_gain20_offset += adcsum
+ - (adc1_gain20_offset >> ADC1_GAIN20_OFFSET_SHIFT);
+}
+
+ISR(ADC_vect) { // IRQ handler
+ uint16_t adcval = ADCW;
+
+ if (zero_count) {
+ if (zero_count > 1) {
+ ADCSRA |= _BV(ADSC);
+ zero_count--;
+ return;
+ } else {
+ setup_mux(current_adc);
+ zero_count = 0;
+ /* fall through */
+ }
+ }
+
+ if (drop_count) {
+ ADCSRA |= _BV(ADSC); // drop this one, start the next
+ drop_count--;
+ return;
+ }
+
+ if (read_count) {
+ ADCSRA |= _BV(ADSC);
+ adc_sum += adcval;
+ read_count--;
+ return;
+ }
+
+ /*
+ * Now we have performed read_count measurements and have them
+ * in adc_sum.
+ */
+
+ // For inputs with gain, subtract the measured gain stage offset
+ if (current_adc < 2) {
+ uint16_t offset = adc1_gain20_offset
+ >> (ADC1_GAIN20_OFFSET_SHIFT - n_reads_log);
+
+ if (adc_sum > offset)
+ adc_sum -= offset;
+ else
+ adc_sum = 0;
+ }
+
+ switch (current_adc) {
+ case 0:
+ case 1:
+ case 2:
+ pwmled_adc(current_adc, adc_sum);
+ break;
+ case AMBIENT_ADC:
+ ambient_adc(adc_sum);
+ break;
+ case BATTERY_ADC:
+ battery_adc(adc_sum);
+ break;
+ case BUTTON_ADC:
+ button_adc(adc_sum);
+ break;
+ case ADC1_GAIN20:
+ adc1_gain20_adc(adcval);
+ break;
+ }
+
+ start_next_adc();
+}
+
--- /dev/null
+#ifndef LIGHTS_H__
+#define LIGHTS_H__ 1
+
+#define TESTING_FW 1
+
+#define N_LEDS 7
+#define N_PWMLEDS 3
+#define N_PWMLED_MODES 4
+
+#define N_BUTTONS 2
+
+/* 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
+
+/* adc.c */
+#define PWMLED_ADC_SHIFT 1 /* 1<<1 measurements per single callback */
+void init_adc();
+void susp_adc();
+void timer_start_slow_adcs();
+
+/* pwm.c */
+/*
+ * The real Timer/Counter 1 frequency should not be too close to the
+ * A/D converter frequency (125 kHz). Note that this is not the Top
+ * value of T/C 1, it is shifted by PWM_STEP_SHIFT as described in pwm.c
+ */
+#define PWM_MAX 0x780
+void init_pwm();
+void susp_pwm();
+void pwm_off(unsigned char n);
+void pwm_set(unsigned char n, uint16_t stride);
+void pwm_timer();
+
+/* tmr.c */
+extern volatile uint16_t jiffies;
+void init_tmr();
+void susp_tmr();
+
+/* pwmled.c */
+void init_pwmled();
+void pwmled_adc(unsigned char n, uint16_t adcval);
+void pwmled_set_mode(unsigned char n, unsigned char mode);
+
+/* gpio.c */
+void init_gpio();
+void susp_gpio();
+void gpio_set(unsigned char n, unsigned char on);
+
+/* ambient.c */
+#define AMBIENT_ADC_SHIFT 0 /* 1 measurement per callback */
+void init_ambient();
+extern volatile unsigned char ambient_zone;
+void ambient_adc(uint16_t adc_val);
+
+/* pattern.c */
+typedef struct {
+ unsigned char mode: 3;
+ unsigned char duration: 5;
+} pattern_t;
+
+#define PATTERN_END { 0, 0 }
+void init_pattern();
+void patterns_next_tick();
+void led_set_pattern(unsigned char led, pattern_t *pattern);
+pattern_t *number_pattern(unsigned char num, unsigned char inv);
+void pattern_reload();
+
+/* buttons.c */
+#define MAX_USER_PARAMS 3
+void init_buttons();
+void susp_buttons();
+void timer_check_buttons();
+void button_adc(uint16_t adcval);
+unsigned char get_user_param(unsigned char param);
+unsigned char buttons_wait_for_release();
+unsigned char buttons_setup_in_progress();
+pattern_t *buttons_setup_status0_pattern_select();
+pattern_t *buttons_setup_status1_pattern_select();
+
+/* battery.c */
+extern volatile unsigned char battery_critical;
+void battery_adc();
+void init_battery();
+unsigned char battery_gauge();
+
+/* control.c */
+extern pattern_t on1_pattern[];
+
+void init_control();
+void brake_on();
+void brake_off();
+void toggle_dim_mode();
+void set_panic_mode();
+pattern_t *pwmled0_pattern_select();
+pattern_t *pwmled1_pattern_select();
+pattern_t *pwmled2_pattern_select();
+pattern_t *status_led_pattern_select();
+pattern_t *illumination_led_pattern_select();
+pattern_t *laser_pattern_select();
+
+/* main.c */
+void power_down();
+
+#endif /* !LIGHTS_H__ */
+
--- /dev/null
+#ifdef USE_LOGGING
+
+#include <avr/io.h>
+#include <avr/eeprom.h>
+
+#include "lights.h"
+
+#define LOG_BUFFER 128
+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/sleep.h>
+#include <avr/interrupt.h>
+#include <avr/power.h>
+#include <avr/wdt.h>
+
+#include "lights.h"
+
+static void hw_setup()
+{
+ wdt_enable(WDTO_1S);
+
+ init_battery();
+ init_pwm();
+ init_adc();
+ init_tmr();
+ init_buttons();
+
+ init_pwmled();
+ init_gpio();
+ init_ambient();
+ init_pattern();
+ init_control();
+
+ set_sleep_mode(SLEEP_MODE_IDLE);
+}
+
+static void hw_suspend()
+{
+ susp_pwm();
+ susp_adc();
+ susp_tmr();
+ susp_gpio();
+ susp_buttons();
+
+ wdt_disable();
+}
+
+void power_down()
+{
+ hw_suspend();
+
+ do {
+ // G'night
+ set_sleep_mode(SLEEP_MODE_PWR_DOWN);
+ sleep_enable();
+ sleep_bod_disable();
+ sei();
+ sleep_cpu();
+
+ // G'morning
+ cli();
+ sleep_disable();
+
+ // allow wakeup by long button-press only
+ } while (!buttons_wait_for_release());
+
+ // ok, so I will wake up
+ hw_setup();
+}
+
+int main(void)
+{
+ init_log();
+
+ power_usi_disable(); // Once for lifetime
+ ACSRA |= _BV(ACD); // disable analog comparator
+
+ log_set_state(3);
+
+ hw_setup();
+ power_down();
+
+ sei();
+#if 1
+ while (1) {
+ wdt_reset();
+ sleep_mode();
+ }
+#endif
+
+#if 0
+ DDRB |= _BV(PB2);
+ while (1) {
+ PORTB |= _BV( PB2 );
+ _delay_ms(200);
+ PORTB &=~ _BV( PB2 );
+ _delay_ms(200);
+ }
+#endif
+}
--- /dev/null
+#include <avr/io.h>
+#include <stdlib.h> // for NULL
+
+#include "lights.h"
+
+static unsigned char led_counters[N_LEDS];
+static pattern_t *led_patterns[N_LEDS];
+
+static pattern_t boot_pattern[] = {
+ { 1, 0x6 },
+ { 0, 0x6 },
+ { 1, 0x3 },
+ { 0, 0x3 },
+ { 1, 0x2 },
+ { 0, 0x2 },
+ { 1, 0x1 },
+ { 0, 0x1 },
+ { 1, 0x1 },
+ { 0, 0x1 },
+ { 1, 0x1 },
+ { 0, 0x1 },
+ { 1, 0x1 },
+ { 0, 0x1 },
+ { 1, 0x10 },
+ { 0, 0x10 },
+ PATTERN_END
+};
+
+static pattern_t pattern_num[] = {
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 10 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 9 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 8 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 7 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 6 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 5 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 4 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 3 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 2 */
+ { 0, 0x5 },
+ { 1, 0x1 }, /* 1 */
+ { 0, 0xF },
+ PATTERN_END
+};
+
+static pattern_t pattern_invnum[] = {
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 10 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 9 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 8 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 7 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 6 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 5 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 4 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 3 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 2 */
+ { 1, 0x5 },
+ { 0, 0x1 }, /* 1 */
+ { 1, 0xF },
+ PATTERN_END
+};
+
+pattern_t off_pattern[] = {
+ { 0, 0x1 },
+ PATTERN_END
+};
+
+static void led_set_mode(unsigned char n, unsigned char mode)
+{
+ if (n < N_PWMLEDS) {
+ pwmled_set_mode(n, mode);
+ } else if (n < N_LEDS) {
+ gpio_set(n - N_PWMLEDS, mode);
+ }
+}
+
+void led_set_pattern(unsigned char n, pattern_t *pattern)
+{
+ if (!pattern)
+ pattern = off_pattern;
+
+ led_patterns[n] = pattern;
+
+ led_counters[n] = pattern->duration;
+ led_set_mode(n, pattern->mode);
+}
+
+void init_pattern()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_LEDS; i++)
+ led_set_pattern(i, NULL);
+
+ led_set_pattern(N_PWMLEDS+1, boot_pattern);
+}
+
+pattern_t *number_pattern(unsigned char num, unsigned char inv)
+{
+ if (num >= 10)
+ num = 10;
+
+ if (inv) {
+ return pattern_invnum
+ + sizeof(pattern_invnum)/sizeof(pattern_t)
+ - 2 - 2*num;
+ } else {
+ return pattern_num
+ + sizeof(pattern_num)/sizeof(pattern_t)
+ - 2 - 2*num;
+ }
+}
+
+static pattern_t *pattern_select(unsigned char n)
+{
+ switch(n) {
+ case 0: return pwmled0_pattern_select();
+ case 1: return pwmled1_pattern_select();
+ case 2: return pwmled2_pattern_select();
+ case 3: return status_led_pattern_select();
+ case 4: return illumination_led_pattern_select();
+ case 6: return laser_pattern_select();
+ default: return NULL;
+ }
+}
+
+void pattern_reload()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_LEDS; i++)
+ led_set_pattern(i, pattern_select(i));
+}
+
+static void inline pattern_finished(unsigned char n)
+{
+ unsigned char i;
+
+ led_patterns[n] = NULL;
+
+ if (n < N_PWMLEDS) {
+ for (i = 0; i < N_PWMLEDS; i++)
+ if (led_patterns[i])
+ return;
+
+ /* all pwmleds finished; restart them */
+ for (i = 0; i < N_PWMLEDS; i++)
+ led_set_pattern(i, pattern_select(i));
+ } else if (n == 3) {
+ if (!led_patterns[4])
+ led_set_pattern(4, pattern_select(4));
+ } else if (n == 4) {
+ if (!led_patterns[3])
+ led_set_pattern(3, pattern_select(3));
+ } else {
+ led_set_pattern(n, pattern_select(n));
+ }
+}
+
+void patterns_next_tick()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_LEDS; i++) {
+ if (!led_patterns[i]) {
+ pattern_finished(i);
+ continue;
+ }
+
+ if (--led_counters[i] == 0) {
+ pattern_t *p = led_patterns[i];
+ p++;
+ if (p->duration == 0) { // END
+ /* Keep the last state, wait for others */
+ pattern_finished(i);
+ continue;
+ }
+ led_set_pattern(i, p);
+ }
+
+ }
+}
+
--- /dev/null
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/delay.h>
+#include <util/atomic.h>
+
+#include "lights.h"
+
+#define PWM_STEP_SHIFT 2 /* sub-LSB precision */
+#define PWM_TOP (((PWM_MAX) + (4 << (PWM_STEP_SHIFT))) >> (PWM_STEP_SHIFT))
+#if PWM_TOP > 0x3FF
+#error PWM_TOP too high
+#endif
+
+static uint16_t pwm[N_PWMLEDS];
+static volatile unsigned char step;
+
+static void enable_pll()
+{
+ /* Async clock */
+ PLLCSR = _BV(PLLE);
+
+ /* Synchronize to the phase lock */
+ _delay_us(100);
+ while ((PLLCSR & _BV(PLOCK)) == 0)
+ ;
+ PLLCSR |= _BV(PCKE);
+}
+
+void init_pwm()
+{
+ int i;
+
+ step = 0;
+
+ for (i = 0; i < N_PWMLEDS; i++)
+ pwm[i] = 0;
+
+ enable_pll();
+
+ // PWM channel D is inverted, ...
+ TCCR1C = _BV(COM1D1) | _BV(COM1D0) | _BV(PWM1D);
+ // PWM channels A and B are not
+ TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(PWM1A) | _BV(PWM1B);
+ TCCR1D = 0;
+ TCCR1B = _BV(CS10); // no clock prescaling
+
+ TC1H = PWM_TOP >> 8;
+ OCR1C = PWM_TOP & 0xFF; // TOP value
+
+ TC1H = PWM_TOP >> 8; // PWM3 is inverted
+ OCR1D = PWM_TOP & 0xFF;
+
+ TC1H = 0x00;
+ OCR1B = OCR1A = 0; // initial stride is 0
+
+ DDRB &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 )); // tristate it
+ PORTB &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 )); // set to zero
+}
+
+void susp_pwm()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_PWMLEDS; i++)
+ pwm[i] = 0;
+
+ DDRB &= ~(_BV( PB1 ) | _BV( PB3 ) | _BV( PB5 ));
+ TCCR1D = TCCR1C = TCCR1B = TCCR1A = 0;
+ TIMSK = 0;
+ TIFR = 0;
+
+ PLLCSR &= ~(_BV(PLLE) | _BV(PCKE));
+}
+
+void pwm_off(unsigned char n)
+{
+ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+ pwm[n] = 0;
+
+ switch (n) {
+ case 0: DDRB &= ~_BV(PB1); break;
+ case 1: DDRB &= ~_BV(PB3); break;
+ case 2: DDRB &= ~_BV(PB5); break;
+ }
+ }
+}
+
+static void pwm_update_hw(unsigned char n)
+{
+ unsigned char hi, lo;
+ uint16_t stride = (pwm[n] + step) >> PWM_STEP_SHIFT;
+
+ if (n == 2)
+ stride = PWM_TOP - stride;
+
+ hi = stride >> 8;
+ lo = stride & 0xFF;
+
+ switch (n) {
+ case 0:
+ TC1H = hi;
+ OCR1A = lo;
+ break;
+ case 1:
+ TC1H = hi;
+ OCR1B = lo;
+ break;
+ case 2:
+ TC1H = hi;
+ OCR1D = lo;
+ break;
+ }
+}
+
+void pwm_set(unsigned char n, uint16_t stride)
+{
+ if (stride > PWM_MAX)
+ stride = PWM_MAX;
+
+ ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
+ pwm[n] = stride;
+
+ pwm_update_hw(n);
+
+ switch(n) {
+ case 0: DDRB |= _BV(PB1); break;
+ case 1: DDRB |= _BV(PB3); break;
+ case 2: DDRB |= _BV(PB5); break;
+ }
+ }
+}
+
+void pwm_timer()
+{
+ unsigned char i;
+
+ if (++step >= (1 << PWM_STEP_SHIFT))
+ step = 0;
+
+ for (i = 0; i < N_PWMLEDS; i++)
+ if (pwm[i])
+ pwm_update_hw(i);
+}
+
--- /dev/null
+#include <avr/io.h>
+
+#include "lights.h"
+
+typedef struct {
+ uint16_t target, pwm;
+ int16_t err_sum;
+ unsigned char mode, state;
+ union {
+ unsigned char probe_steady, mode_changed;
+ };
+ uint16_t mode_pwm[N_PWMLED_MODES];
+ int16_t err_sums[N_PWMLED_MODES];
+} pwmled_t;
+
+pwmled_t pwmleds[N_PWMLEDS];
+
+#define PWMLED2_TESTING_WITH_350MA_LED
+
+#define SENSE_MOHM 33 /* 0.033 Ohm */
+/*
+ * Voltage in uV at ADC reading == 1 is 1100/gain/1024
+ * ADC module returns sum of 1 << PWMLED_ADC_SHIFT measurements
+ * Voltage in uV measured is current in mA * sense resistance in mOhm
+ */
+#define MA_GAIN_TO_ADC(ma, gain) ((uint16_t) \
+ ((uint32_t)(ma) \
+ * (SENSE_MOHM) \
+ * (1 << (PWMLED_ADC_SHIFT)) \
+ * 1024 \
+ / (1100000/(gain))))
+
+static uint16_t adc_max[N_PWMLEDS] = {
+#ifdef TESTING_FW
+ MA_GAIN_TO_ADC( 400, 20),
+ MA_GAIN_TO_ADC( 30, 20),
+ MA_GAIN_TO_ADC( 800, 1)
+#else
+ MA_GAIN_TO_ADC( 900, 20),
+ MA_GAIN_TO_ADC( 30, 20),
+ MA_GAIN_TO_ADC(2500, 1)
+#endif
+};
+
+static uint16_t adc_vals[N_PWMLEDS*N_PWMLED_MODES] = {
+#ifdef TESTING_FW
+ /* pwmled0 */
+ MA_GAIN_TO_ADC( 50, 20),
+ MA_GAIN_TO_ADC( 100, 20),
+ MA_GAIN_TO_ADC( 200, 20),
+ MA_GAIN_TO_ADC( 350, 20),
+ /* pwmled1 */
+ MA_GAIN_TO_ADC( 5, 20),
+ MA_GAIN_TO_ADC( 10, 20),
+ MA_GAIN_TO_ADC( 15, 20),
+ MA_GAIN_TO_ADC( 20, 20),
+ /* pwmled2 */
+ MA_GAIN_TO_ADC( 50, 1),
+ MA_GAIN_TO_ADC( 80, 1),
+ MA_GAIN_TO_ADC( 150, 1),
+ MA_GAIN_TO_ADC( 200, 1)
+#else
+ /* pwmled0 */
+ MA_GAIN_TO_ADC( 50, 20),
+ MA_GAIN_TO_ADC( 100, 20),
+ MA_GAIN_TO_ADC( 200, 20),
+ MA_GAIN_TO_ADC( 350, 20),
+ /* pwmled1 */
+ MA_GAIN_TO_ADC( 5, 20),
+ MA_GAIN_TO_ADC( 10, 20),
+ MA_GAIN_TO_ADC( 18, 20),
+ MA_GAIN_TO_ADC( 23, 20),
+ /* pwmled2 */
+ MA_GAIN_TO_ADC( 150, 1),
+ MA_GAIN_TO_ADC( 300, 1),
+ MA_GAIN_TO_ADC( 800, 1),
+ MA_GAIN_TO_ADC(1500, 1)
+#endif
+};
+
+#define ST_DISABLED 0
+#define ST_OFF 1
+#define ST_PROBING 2
+#define ST_ON 3
+// The above are constructed so that the following work:
+#define ST_IS_ON(s) ((s) & 0x02)
+#define ST_CAN_SET_MODE(s) ((s) & 0x01)
+
+void init_pwmled()
+{
+ unsigned char i, j;
+
+ for (i = 0; i < N_PWMLEDS; i++) {
+ pwmled_t *led = pwmleds + i;
+ led->err_sum = 0;
+ led->target = adc_vals[i*N_PWMLED_MODES];
+ led->pwm = 0;
+ led->mode = 1;
+ led->state = ST_PROBING;
+ led->probe_steady = 0;
+
+ for (j = 0; j < N_PWMLED_MODES; j++) {
+ led->mode_pwm[j] = 0;
+ led->err_sums[j] = 0;
+ }
+ }
+}
+
+void pwmled_set_mode(unsigned char n, unsigned char mode)
+{
+ pwmled_t *led = pwmleds + n;
+
+ if (!ST_CAN_SET_MODE(led->state))
+ return;
+
+ if (led->mode) { // save the previous state
+ led->mode_pwm[led->mode - 1] = led->pwm;
+ led->err_sums[led->mode - 1] = led->err_sum;
+ }
+
+ led->mode = mode;
+
+ if (mode > 0 && mode <= N_PWMLED_MODES) {
+ led->target = adc_vals[n*N_PWMLED_MODES + mode - 1];
+ led->state = ST_ON;
+ led->pwm = led->mode_pwm[mode - 1];
+ led->err_sum = led->err_sums[mode - 1];
+ led->mode_changed = 1;
+ pwm_set(n, led->pwm);
+ } else {
+ led->state = ST_OFF;
+ pwm_off(n);
+ }
+}
+
+#define PWMLED_PROBE_STEADY_COUNT 10
+
+static inline unsigned char pwmled_probed_ok(unsigned char n, uint16_t old_pwm)
+{
+ pwmled_t *led = pwmleds + n;
+
+ if (led->pwm == old_pwm) {
+ if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT)
+ led->probe_steady++;
+ } else {
+ led->probe_steady = 0;
+ }
+
+ if (led->probe_steady < PWMLED_PROBE_STEADY_COUNT
+ && old_pwm <= led->pwm)
+ return 0;
+
+ // probed OK
+ led->mode_pwm[led->mode - 1] = led->pwm;
+ led->err_sums[led->mode - 1] = 0;
+
+ // next mode to probe?
+ if (led->mode < N_PWMLED_MODES) {
+ led->probe_steady = 0;
+ led->err_sum = 0;
+
+ led->mode++;
+ led->target = adc_vals[n*N_PWMLED_MODES+led->mode-1];
+
+ return 0;
+ } else {
+ unsigned char i;
+
+ led->state = ST_OFF;
+ pwm_off(n);
+
+ log_byte(0xF0);
+ log_byte(n);
+ log_word(jiffies);
+
+ for (i = 0; i < N_PWMLED_MODES; i++)
+ log_word(led->mode_pwm[i]);
+
+ log_flush();
+
+ pattern_reload();
+
+ return 1;
+ }
+}
+
+static inline void pwmled_err(unsigned char n)
+{
+ pwmleds[n].state = ST_DISABLED;
+ pwm_off(n);
+
+ log_byte(0xF1);
+ log_byte(n);
+ log_word(jiffies);
+ log_flush();
+}
+
+
+void pwmled_adc(unsigned char n, uint16_t adcval)
+{
+ pwmled_t *led = pwmleds + n;
+ uint16_t old_pwm;
+ int32_t sum;
+ unsigned char shift;
+
+ if (!ST_IS_ON(led->state))
+ return;
+
+ if (led->state == ST_ON && led->mode_changed) {
+ led->mode_changed--;
+ return;
+ }
+ // FIXME: test for maximum adcval value (adc_max[n])
+
+ old_pwm = led->pwm;
+
+ shift = led->state == ST_PROBING ? 3 : 8;
+
+ sum = ((int32_t)led->pwm << shift)
+ + led->err_sum + led->target - adcval;
+
+ if (sum < 0)
+ sum = 0;
+
+ led->pwm = sum >> shift;
+ sum -= led->pwm << shift;
+ led->err_sum = sum;
+
+ if (led->pwm >= PWM_MAX
+ || (n == 1 && led->pwm > PWM_MAX/2 && adcval < 0x08)) {
+ pwmled_err(n);
+ return;
+ }
+
+ if (led->state == ST_PROBING)
+ if (pwmled_probed_ok(n, old_pwm))
+ return;
+
+ if (led->pwm == old_pwm)
+ return;
+
+ pwm_set(n, led->pwm);
+}
+