]> www.fi.muni.cz Git - tinyboard.git/commitdiff
Imported firmware from Project Bike Lights
authorJan "Yenya" Kasprzak <kas@fi.muni.cz>
Thu, 25 Apr 2013 13:35:57 +0000 (15:35 +0200)
committerJan "Yenya" Kasprzak <kas@fi.muni.cz>
Thu, 25 Apr 2013 13:35:57 +0000 (15:35 +0200)
These are firmware source code files imported verbatim from
the Project Bike Lights, as of commit
c30006aaf666f7cff3a6ab949c613c2f8cc6163b:

http://www.fi.muni.cz/~kas/git/?p=bike-lights.git;a=tree;f=firmware;hb=c30006aaf666f7cff3a6ab949c613c2f8cc6163b

These will not run directly on Tinyboard - I just want to have
documented all the changes I plan to make against these source code
files (and possibly to merge some future patches into both projects).

projects/step-up/Makefile [new file with mode: 0644]
projects/step-up/adc.c [new file with mode: 0644]
projects/step-up/lights.h [new file with mode: 0644]
projects/step-up/logging.c [new file with mode: 0644]
projects/step-up/main.c [new file with mode: 0644]
projects/step-up/pattern.c [new file with mode: 0644]
projects/step-up/pwm.c [new file with mode: 0644]
projects/step-up/pwmled.c [new file with mode: 0644]

diff --git a/projects/step-up/Makefile b/projects/step-up/Makefile
new file mode 100644 (file)
index 0000000..1b0113e
--- /dev/null
@@ -0,0 +1,67 @@
+
+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
+
diff --git a/projects/step-up/adc.c b/projects/step-up/adc.c
new file mode 100644 (file)
index 0000000..ef71477
--- /dev/null
@@ -0,0 +1,256 @@
+#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();
+}
+
diff --git a/projects/step-up/lights.h b/projects/step-up/lights.h
new file mode 100644 (file)
index 0000000..772613a
--- /dev/null
@@ -0,0 +1,117 @@
+#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__ */
+
diff --git a/projects/step-up/logging.c b/projects/step-up/logging.c
new file mode 100644 (file)
index 0000000..2b8d242
--- /dev/null
@@ -0,0 +1,81 @@
+#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
+
diff --git a/projects/step-up/main.c b/projects/step-up/main.c
new file mode 100644 (file)
index 0000000..90bea63
--- /dev/null
@@ -0,0 +1,92 @@
+#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
+}
diff --git a/projects/step-up/pattern.c b/projects/step-up/pattern.c
new file mode 100644 (file)
index 0000000..55b64cc
--- /dev/null
@@ -0,0 +1,199 @@
+#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);
+               }
+
+       }
+}
+
diff --git a/projects/step-up/pwm.c b/projects/step-up/pwm.c
new file mode 100644 (file)
index 0000000..b472273
--- /dev/null
@@ -0,0 +1,144 @@
+#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);
+}
+
diff --git a/projects/step-up/pwmled.c b/projects/step-up/pwmled.c
new file mode 100644 (file)
index 0000000..9aa6d80
--- /dev/null
@@ -0,0 +1,244 @@
+#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);
+}
+