-
PROGRAM=lights
-SRC=lights.c
+SRC=main.c logging.c adc.c pwm.c tmr.c pwmled.c
OBJ=$(SRC:.c=.o)
+
MCU=attiny861a
# AVRDUDE_MCU=$(MCU)
AVRDUDE_MCU=attiny861
AVRDUDE_PROGRAMMER=usbasp
-CFLAGS=-Os -mmcu=$(MCU)
+CFLAGS=-Wall -Os -mmcu=$(MCU) -DUSE_LOGGING=1 -DF_CPU=1000000UL
LDFLAGS=
AVRDUDE_FLAGS= -p$(AVRDUDE_MCU) -c $(AVRDUDE_PROGRAMMER)
objdump: $(PROGRAM).elf
$(OBJDUMP) --disassemble $<
-
.PRECIOUS : $(OBJ) $(PROGRAM).elf
%.hex: %.elf
%.elf: $(OBJ)
$(CC) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS)
-%.o: %.c
+%.o: %.c lights.h Makefile
$(CC) -c $(CFLAGS) $< -o $@
-%.s: %.c
+%.s: %.c lights.h Makefile
$(CC) -S -c $(CFLAGS) $< -o $@
%.o: %.S
--- /dev/null
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#include "lights.h"
+
+static unsigned char adc_mux[] = { // pwmleds should be first
+ // 0: pwmled 0: 1.1V, ADC3 (PA4), single-ended
+ _BV(REFS1) | _BV(MUX1) | _BV(MUX0),
+ // 1: pwmled 1: 1.1V, ADC0,1 (PA0,1), gain 1 or 8
+ _BV(REFS1) | _BV(MUX3) | _BV(MUX2),
+ // 2: pwmled 2: 1.1V, ADC2,1 (PA2,1), gain 1 or 8
+ _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0),
+ // 3: ambient light: 1.1V, ADC4 (PA5), single-ended
+ _BV(REFS1) | _BV(MUX2),
+ // 4: batt voltage: 1.1V, ADC5 (PA6), single-ended
+ _BV(REFS1) | _BV(MUX2) | _BV(MUX0),
+};
+
+#define LAST_ADC (sizeof(adc_mux)/sizeof(char))
+volatile static unsigned char current_adc = LAST_ADC;
+
+static void start_next_adc()
+{
+ while (current_adc > 0) {
+ --current_adc;
+
+ // test if current_adc should be measured
+ if (current_adc < N_PWMLEDS && pwmled_is_on(current_adc))
+ goto found;
+ // TODO ambient light, battery sense, etc.
+ }
+
+ // all ADCs have been handled
+ current_adc = LAST_ADC;
+ return;
+found:
+ // ADCSRB |= _BV(GSEL); // gain 8 or 32
+ ADMUX = adc_mux[current_adc]; // set up mux, start one-shot conversion
+ ADCSRA |= _BV(ADSC);
+}
+
+void init_adc()
+{
+ 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);
+
+ ADCSRA |= _BV(ADSC);
+
+ /* Do first conversion and drop the result */
+ while ((ADCSRA & _BV(ADIF)) == 0)
+ ;
+ ADCSRA |= _BV(ADIF); // clear the IRQ flag
+
+ ADCSRA |= _BV(ADIE); // enable IRQ
+}
+
+ISR(ADC_vect) { // IRQ handler
+ uint16_t adcval = ADCW;
+
+ if (current_adc < N_PWMLEDS)
+ pwmled_adc(current_adc, adcval);
+ // TODO ambient light, battery sense, etc.
+
+ start_next_adc();
+}
+
+void timer_start_adcs()
+{
+ if (current_adc == LAST_ADC) // Don't start if in progress
+ start_next_adc();
+}
+
+++ /dev/null
-#include <avr/io.h>
-#include <avr/eeprom.h>
-#include <util/delay.h>
-#include <avr/sleep.h>
-#include <avr/interrupt.h>
-
-volatile uint16_t adcval;
-unsigned char led_is_on = 0;
-volatile unsigned char adccount = 0;
-
-typedef struct {
- unsigned char pwmval, expected;
-} led_level_t;
-
-led_level_t led_modes[2] = {
- { 0x50, 0x38 },
- { 0x38, 0x04 },
-};
-
-unsigned char led_mode = 0;
-unsigned char led_mode_changed = 0;
-
-static void inline led_on()
-{
- DDRB |= _BV( PB5 );
- PORTA |= _BV( PA0 );
- led_is_on = 1;
-}
-
-static void inline led_off()
-{
- led_is_on = 0;
- DDRB &= ~_BV( PB5 );
- PORTA &=~ _BV( PA0 );
-// ADCSRA &= ~(_BV(ADIE) | _BV(ADIF));
-}
-
-/* ------------ Logging/Debugging ----------- */
-
-unsigned char debug_state EEMEM;
-
-static void inline debug_setstate(unsigned char val)
-{
- eeprom_write_byte(&debug_state, val);
-}
-
-#define LOG_BUFFER 64
-uint16_t log_buffer[LOG_BUFFER] EEMEM;
-unsigned char log_buffer_count;
-uint16_t log_buffer2[LOG_BUFFER];
-volatile unsigned char stop = 0;
-
-static void inline init_log()
-{
- debug_setstate(1);
- log_buffer_count = 0;
-}
-
-static void log_word(uint16_t word) {
- if (log_buffer_count >= LOG_BUFFER)
- return;
-
- // eeprom_write_word(&log_buffer[log_buffer_count], word);
- log_buffer2[log_buffer_count] = word;
- log_buffer_count++;
-
- if (log_buffer_count == LOG_BUFFER) {
- unsigned char i;
- for (i=0; i < LOG_BUFFER; i++) {
- eeprom_write_word(&log_buffer[i],
- log_buffer2[i]);
- }
- debug_setstate(0x42);
- }
-}
-
-/* ------------ Timer ----------- */
-
-volatile uint16_t jiffies = 0;
-
-static void inline init_tmr()
-{
- TCCR0A = _BV(WGM00);
- TCCR0B = _BV(CS02) | _BV(CS00); // 1 kHz
- OCR0A = 12; // 100 Hz
- TIMSK |= _BV(OCIE0A);
- DDRA |= _BV( PA0 );
-
- jiffies = 0;
-}
-
-static void inline tmr_handler()
-{
- unsigned char c = jiffies & 0x0F;
- unsigned char c1 = jiffies & 0x7F;
-
- ++jiffies;
-
-#if 0
- if (c == 1)
- led_on();
-
- if (c == 9)
- led_off();
-#endif
-
- if (c == 0x02 || c == 0x08)
- led_on();
-
- if (c == 0x05 || c == 0x0b)
- led_off();
-
-#if 1
- if (c1 == 0x10) {
- led_mode = 0;
- led_mode_changed = 1;
- OCR1D = led_modes[led_mode].pwmval;
- } else if (c1 == 0x70) {
- led_mode = 1;
- led_mode_changed = 1;
- OCR1D = led_modes[led_mode].pwmval;
- }
-#endif
-
- ADCSRA |= _BV(ADSC);
-#if 0
- if (led_is_on && adcval != 0xFFEE) {
- adcval = 0xFFEE;
-
- ADCSRA |= _BV(ADIF) | _BV(ADIE) | _BV(ADSC);
- }
-#endif
-}
-
-ISR(TIMER0_COMPA_vect)
-{
- tmr_handler();
-}
-
-/* ------------ PWM ----------- */
-
-static void inline init_pwm()
-{
- /* Async clock */
- PLLCSR = _BV(LSM) | _BV(PLLE);
- _delay_ms(1);
- while (PLLCSR & _BV(PLOCK) == 0)
- ;
- PLLCSR |= _BV(PCKE);
-
- TCCR1C = _BV(COM1D0) | _BV(COM1D1) | _BV(PWM1D);
- TCCR1A = _BV(COM1A0) | _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(PWM1A) | _BV(PWM1B);
- // TCCR1B = 0x80| _BV(CS13) | _BV(CS11);
- TCCR1B = _BV(7) // PWM1X: PWM inversion mode
- | _BV(CS10) // no clock prescaling
- ;
- OCR1C = 0xFF; // TOP value
- OCR1D = OCR1B = OCR1A = 0;
- // OCR1D = 0;
- DDRB |= _BV( PB5 );
- PORTB &= ~(_BV( PB5 ) | _BV( PB1 ) | _BV( PB3 ));
-
- // led_off();
- // TIMSK |= _BV(TOIE1);
-}
-
-#if 0
-static void inline pwm_handler()
-{
- // TIMSK &= ~_BV(TOIE1);
- // OCR1D = pwmval;
-}
-
-ISR(TIMER1_OVF_vect)
-{
- pwm_handler();
-}
-#endif
-
-/* ------------ A/D Converter ----------- */
-
-static void inline init_adc()
-{
- ADCSRA = _BV(ADEN) // enable
- | _BV(ADPS1) | _BV(ADPS0) // CLK/8 = 125 kHz
- // | _BV(ADPS2) // CLK/16 = 62.5 kHz
- ;
- ADMUX = _BV(REFS1) // 1.1V internal reference
- | _BV(MUX4)|_BV(MUX3) // port ADC5-6, gain 1
- ;
- // ADCSRB = _BV(REFS2);
- ADCSRB |= _BV(GSEL);
- // Disable digital input on all bits used by ADC
- DIDR0 = _BV(ADC5D)|_BV(ADC6D) | _BV(AREFD);
- ADCSRA |= _BV(ADIE);
-}
-
-static void inline adc_handler()
-{
- uint16_t new_pwm = led_modes[led_mode].pwmval;
- uint16_t old_pwm = new_pwm;
- uint16_t adc_exp = led_modes[led_mode].expected;
-
- adcval = ADCW;
- adccount++;
-
- // log_word(((adcval & 0x3FC) << 6) | pwmval);
-
- if (!led_is_on)
- return;
-
- // ADCSRA &= ~(_BV(ADIE) | _BV(ADIF));
-
- if (led_mode_changed) {
- led_mode_changed = 0;
- goto set_pwm;
- }
-
- log_word(((adcval & 0xFF) << 8) | old_pwm);
-
- if (2*adcval > 5*adc_exp) { // >2.5x expected, lower significantly
- new_pwm = 2*old_pwm/3;
- } else if (3*adcval > 4*adc_exp) { // >1.33x expected, lower a bit
- new_pwm = old_pwm - 1;
- } else if (4*adcval < 3*adc_exp) { // 0.75x expected, raise a bit
- new_pwm = old_pwm + 1;
- }
-
- if (new_pwm > 0x60) { // odpojeno?
- new_pwm = 0x60;
- }
- if (new_pwm < 2) { // zkrat?
- new_pwm = 2;
- }
-
-set_pwm:
- if (new_pwm != old_pwm) {
- led_modes[led_mode].pwmval = new_pwm;
- OCR1D = new_pwm;
- }
- // ADCSRA |= _BV(ADSC);
-}
-
-ISR(ADC_vect)
-{
- adc_handler();
-}
-
-int main(void)
-{
- _delay_ms(1500);
- init_log();
-
- init_pwm();
- init_adc();
- init_tmr();
-
- led_on();
- debug_setstate(3);
-
- sei();
- while (1)
- ; // sleep_mode();
-
-#if 0
- while (1) {
- PORTA |= _BV( PA0 );
- _delay_ms(200);
- PORTA &=~ _BV( PA0 );
- _delay_ms(200);
- }
-#endif
-}
--- /dev/null
+#ifndef LIGHTS_H__
+#define LIGHTS_H__ 1
+
+#define N_LEDS 5
+#define N_PWMLEDS 3
+#define N_PWMLED_MODES 4
+
+/* logging.c */
+#ifdef USE_LOGGING
+void log_set_state(unsigned char val);
+void log_init();
+void log_flush();
+void log_byte(unsigned char byte);
+void log_word(uint16_t word);
+#else
+void inline log_set_state(unsigned char val) { }
+void inline log_init() { }
+void inline log_flush() { }
+void inline log_byte(unsigned char byte) { }
+void inline log_word(uint16_t word) { }
+#endif
+
+/* adc.c */
+void init_adc();
+void timer_start_adcs();
+
+/* pwm.c */
+void init_pwm();
+void pwm_on(unsigned char n);
+void pwm_off(unsigned char n);
+void pwm_set(unsigned char n, unsigned char stride);
+
+/* tmr.c */
+extern volatile uint16_t jiffies;
+void init_tmr();
+
+/* pwmled.c */
+void pwmled_init();
+void pwmled_adc(unsigned char n, uint16_t adcval);
+void pwmled_set_mode(unsigned char n, unsigned char mode);
+unsigned char pwmled_is_on(unsigned char n);
+
+
+#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_state EEMEM;
+static unsigned char log_buffer_ee[LOG_BUFFER] EEMEM;
+static unsigned char log_buffer_count;
+static unsigned char log_buffer[LOG_BUFFER];
+
+void log_set_state(unsigned char val)
+{
+ eeprom_write_byte(&log_state, val);
+}
+
+void log_init()
+{
+ log_set_state(1);
+ log_buffer_count = 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;
+
+ log_buffer_count = LOG_BUFFER;
+ for (i=0; i < LOG_BUFFER; i++) {
+ eeprom_write_byte(&log_buffer_ee[i],
+ log_buffer[i]);
+ }
+ log_set_state(0x42);
+}
+
+#endif
+
--- /dev/null
+#include <avr/io.h>
+#include <util/delay.h>
+#include <avr/sleep.h>
+#include <avr/interrupt.h>
+
+#include "lights.h"
+
+int main(void)
+{
+ _delay_ms(1500);
+ log_init();
+
+ init_pwm();
+ init_adc();
+ init_tmr();
+
+ pwmled_init();
+
+ log_set_state(3);
+
+ sei();
+#if 1
+ while (1)
+ ; // 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 "lights.h"
+
+typedef struct {
+ unsigned char mode: 3;
+ unsigned char duration: 5;
+} pattern_t;
+
+static unsigned char led_counters[N_LEDS];
+static pattern_t *led_patterns[N_LEDS];
+
+#define PATTERN_END { 0, 0 }
+pattern_t off_pattern[] = {
+ { 0, 5 },
+ PATTERN_END
+};
+
+pattern_t blink_pattern[] = {
+ { 1, 5 },
+ { 0, 5 },
+ PATTERN_END
+};
+
+void pattern_init()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_LEDS; i++) {
+ led_counters[i] = 0;
+ led_patterns[i] = off_pattern;
+ }
+}
+
+void patterns_next_tick()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_LEDS; i++) {
+ if (led_counters[i] == 0) {
+ led_patterns[i]++;
+ if (led_patterns[i]->duration == 0) { // END
+ led_patterns[i] = pattern_select(i);
+ }
+ led_counters[i] = led_patterns[i]->duration;
+ if (led_patterns[i]->mode == 0) {
+ led_off(i);
+ } else {
+ led_set_level(i, led_patterns[i]->mode - 1);
+ }
+ }
+
+ led_counters[i]--;
+ }
+}
+
--- /dev/null
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include <util/delay.h>
+
+#include "lights.h"
+
+void init_pwm()
+{
+ /* Async clock */
+ PLLCSR = _BV(LSM) | _BV(PLLE);
+
+ /* Synchronize to the phase lock */
+ _delay_ms(1);
+ while ((PLLCSR & _BV(PLOCK)) == 0)
+ ;
+ PLLCSR |= _BV(PCKE);
+
+ TCCR1C = _BV(COM1D0) | _BV(COM1D1) | _BV(PWM1D);
+ TCCR1A = _BV(COM1A0) | _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1) | _BV(PWM1A) | _BV(PWM1B);
+ TCCR1B = _BV(7) // PWM1X: PWM inversion mode
+ | _BV(CS10) // no clock prescaling
+ ;
+ OCR1C = 0xFF; // TOP value
+
+ OCR1D = 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 pwm_on(unsigned char n)
+{
+ switch (n) {
+ case 0: DDRB |= _BV(PB1); break;
+ case 1: DDRB |= _BV(PB3); break;
+ case 2: DDRB |= _BV(PB5); break;
+ }
+}
+
+void pwm_off(unsigned char n)
+{
+ switch (n) {
+ case 0: DDRB &= ~_BV(PB1); break;
+ case 1: DDRB &= ~_BV(PB3); break;
+ case 2: DDRB &= ~_BV(PB5); break;
+ }
+}
+
+void pwm_set(unsigned char n, unsigned char stride)
+{
+ switch (n) {
+ case 0: OCR1A = stride; break;
+ case 1: OCR1B = stride; break;
+ case 2: OCR1D = stride; break;
+ }
+}
+
+#if 0
+static void inline pwm_handler()
+{
+ OCR1A = pwmval[0];
+ OCR1B = pwmval[1];
+ OCR1D = pwmval[2];
+ TIMSK &= ~_BV(TOIE1);
+}
+
+ISR(TIMER1_OVF_vect)
+{
+ pwm_handler();
+}
+#endif
+
--- /dev/null
+#include <avr/io.h>
+
+#include "lights.h"
+
+static unsigned char pwm_vals[N_PWMLEDS*N_PWMLED_MODES];
+static unsigned char adc_vals[N_PWMLEDS*N_PWMLED_MODES] = {
+ /* pwmled0 */
+ 0x04, 0x14, 0x24, 0x38,
+ /* pwmled1 */
+ 0x04, 0x14, 0x24, 0x38,
+ /* pwmled2 */
+ 0x04, 0x14, 0x24, 0x38,
+};
+
+static unsigned char pwmled_state[N_PWMLEDS];
+#define ST_DISABLED 0
+#define ST_PROBING 1
+#define ST_OFF 2
+#define ST_ON 3
+
+static unsigned char pwmled_mode[N_PWMLEDS];
+
+static unsigned char pwm_probes[N_PWMLEDS];
+
+static void start_probing(unsigned char n)
+{
+ pwmled_state[n] = ST_PROBING;
+ pwm_set(n, 0);
+ pwm_on(n);
+ pwm_probes[n] = 0;
+}
+
+void pwmled_init()
+{
+ unsigned char i;
+
+ for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++)
+ pwm_vals[i] = 0;
+
+ for (i = 0; i < N_PWMLEDS; i++) {
+ start_probing(i);
+ }
+}
+
+unsigned char pwmled_is_on(unsigned char n)
+{
+ unsigned char st = pwmled_state[n];
+ if (st == ST_PROBING || st == ST_ON)
+ return 1;
+ else
+ return 0;
+}
+
+static void inline probing_adc(unsigned char n, uint16_t adcval)
+{
+ unsigned char need_bigger = 0, i;
+ unsigned char *pwm_p = &pwm_vals[n*N_PWMLED_MODES];
+ unsigned char *adc_p = &adc_vals[n*N_PWMLED_MODES];
+ unsigned char pwm = pwm_probes[n];
+
+#if 0
+ log_byte(n);
+ log_byte(0xF4);
+ log_word(adcval);
+#endif
+
+#if 0
+ if (pwm == 0 && adcval > 0) { // non-zero voltage with zero PWM?
+ pwmled_state[n] = ST_DISABLED;
+ log_byte(n);
+ log_byte(0xF0);
+ log_word(adcval);
+ return;
+ }
+#endif
+
+ for (i = 0; i < N_PWMLED_MODES; i++, pwm_p++, adc_p++) {
+ uint16_t adc = *adc_p;
+ if (adc >= adcval) {
+ *pwm_p = pwm;
+ need_bigger = 1;
+ }
+ }
+
+#if 0
+ if ((n == 1 && pwm > 0x35) || adcval != 0) {
+ log_byte(n);
+ log_byte(0xF3);
+ log_byte(pwm);
+ log_word(adcval);
+ }
+#endif
+
+ if (!need_bigger) { // successfully probed
+ pwm_off(n);
+ // pwm_set(n, 0);
+ pwmled_state[n] = ST_OFF;
+ log_byte(n);
+ log_byte(0xF1);
+
+ return;
+ }
+
+ if (pwm >= 0x60) { // over the maximum!
+ pwmled_state[n] = ST_DISABLED;
+ log_byte(n);
+ log_byte(0xF2);
+ pwm_off(n);
+ // pwm_set(n, 0);
+ return;
+ }
+
+ // try to increase
+ pwm++;
+ pwm_probes[n] = pwm;
+ pwm_set(n, pwm);
+}
+
+// Feedback loop
+static void inline on_adc(unsigned char n, uint16_t adcval)
+{
+#if 0
+ uint16_t new_pwm = led_modes[led_mode].pwmval;
+ uint16_t old_pwm = new_pwm;
+ uint16_t adc_exp = led_modes[led_mode].expected;
+
+ log_word(((adcval & 0xFF) << 8) | old_pwm);
+
+ if (2*adcval > 5*adc_exp) { // >2.5x expected, lower significantly
+ new_pwm = 2*old_pwm/3;
+ } else if (3*adcval > 4*adc_exp) { // >1.33x expected, lower a bit
+ new_pwm = old_pwm - 1;
+ } else if (4*adcval < 3*adc_exp) { // 0.75x expected, raise a bit
+ new_pwm = old_pwm + 1;
+ }
+
+ if (new_pwm > 0x60) { // odpojeno?
+ new_pwm = 0x60;
+ }
+ if (new_pwm < 2) { // zkrat?
+ new_pwm = 2;
+ }
+
+set_pwm:
+ if (new_pwm != old_pwm) {
+ led_modes[led_mode].pwmval = new_pwm;
+ OCR1D = new_pwm;
+ }
+ // ADCSRA |= _BV(ADSC);
+#endif
+}
+
+void pwmled_adc(unsigned char n, uint16_t adcval)
+{
+ unsigned char i, probing;
+ switch (pwmled_state[n]) {
+ case ST_PROBING:
+ probing_adc(n, adcval);
+
+ probing = 0;
+ for (i = 0; i < N_PWMLEDS; i++)
+ if (pwmled_state[i] == ST_PROBING)
+ probing = 1;
+
+ if (!probing) {
+ for (i = 0; i < N_PWMLEDS; i++)
+ log_byte(pwmled_state[i]);
+
+ for (i = 0; i < N_PWMLEDS*N_PWMLED_MODES; i++)
+ log_byte(pwm_vals[i]);
+ log_flush();
+ }
+
+ return;
+ case ST_ON:
+ on_adc(n, adcval);
+ return;
+ // WTF am I doing in this function then?
+ }
+}
--- /dev/null
+#include <avr/io.h>
+#include <avr/interrupt.h>
+
+#include "lights.h"
+
+volatile uint16_t jiffies;
+
+void init_tmr()
+{
+ TCCR0A = _BV(WGM00);
+ TCCR0B = _BV(CS02) | _BV(CS00); // CLK/1024 = 1 kHz
+ OCR0A = 10; // 100 Hz
+ TIMSK |= _BV(OCIE0A);
+
+ jiffies = 0;
+}
+
+ISR(TIMER0_COMPA_vect)
+{
+ ++jiffies;
+
+ // patterns_next_tick();
+ timer_start_adcs();
+}
+