Home

Bicycle light control, this time for sure, for sure (maybe).

February 2, 2014

This is the plan, anyhow. N-1, I designed long ago, finally built, and with the exception of broken wires, a confuso (OVERFLOW vs OVERVOLTAGE), and a units error (4000Hz “ticks” versus 1Hz “seconds”), it appears to work. The only remaining mystery is why not all the zero crossings are detected; I went so far as to determine that the cause is on the analog side, and I think before I worry too much about it I will check to see if it matters, or if I can fix it in software.

So, why do I think this one is better? First, it is not sensitive to the LED load; it just runs whatever you put there, pulling the right amount of current form the hub. Second, it has a switchable doubler, so the lights come on at low speeds, but then the doubler turns off at high speeds for more efficiency and less need to limit current. Third, you can do whatever the heck you want with timing and flashing patterns. Fourth, on the advice of Wiley, it uses screw-down terminals, which is a good thing (most first controller just died, from a corroded+broken wire) and allows easier repairs in the field. Fifth, it has “everything”; it can be reprogrammed in place, it can supply at least a half-amp of 5V current for phone charging, it has an optional input for solar panels if that’s what you want, it has a shunt for overvoltage protection, and it has a switched optional input for batteries (or capacitors) for a standlight.

The prototype (which proved most of this would work) has several flaws; among them, I was still suffering from the delusion that I could stick it in side a frame tube. That’s not such an awesome idea. Making it in one compact lump allows all the capacitors to be lashed down tight, allows the screw terminals, and reduces the need for extra wires. The whole thing is relative compact — 5cm by 10cm, with a bit of the BuckPuck sticking off the end, and the capacitors piled about 3cm high.

Tiny84OpAmpBuck

And there’s a wee bit of software….

/*
 * main.c
 *
 *  Created on: Jun 16, 2013
 *      Author: dr2chase
 */

#include <stdint.h>
#include <avr/sfr_defs.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 8000000UL        // Sets up the default speed for delay.h
#include <util/delay.h>
#include <avr/sleep.h>
#include <avr/power.h>

#include "/Users/dr2chase/simavr/simavr/sim/avr/avr_mcu_section.h"

#define X8 1

uint32_t expand(uint16_t x) {
    uint8_t exp = x >> 12;
    if (exp <= 1)
        return x;
    {
        uint32_t mantissa = (x & 4095) + (exp == 0 ? 0 : 4096);
        return mantissa << (exp - 1);
    }
}

uint16_t contract(uint32_t x) {
    if (x <= 8192)
        return x;
    {
        // x is <= 8191 << 14
        uint32_t y = x;
        uint8_t exp = 0;
        uint16_t frac;
        uint16_t mask;
        while (y > 8191) {
            y = y >> 1;
            exp++;
        }
        /* rounding; nearest, zero if tie.
         examine the "fraction" bits below
         the mantissa.
         */
        mask = (1 << exp) - 1;
        frac = x & mask;
        frac += mask >> 1;      // frac += .5 - ulp
        frac += (x >> exp) & 1; // add low bit of mantissa; rounds up if odd
        frac = frac >> exp;     // lose the fraction bits, keep the potential 1
        y = y + frac;
        /* Hack for exponent correct.
         Implicit 1 bit is added to exponent.
         If rounding overflowed y to 8192,
         that adds two to the exponent,
         which is what we want.
         */
        return (exp << 12) + y;
    }
}

uint8_t l2p1(uint8_t x) {
    uint8_t t = 0;
    uint8_t y = x >> 4;
    if (y != 0) {
        t += 4;
        x = y;
    }
    y = x >> 2;
    if (y != 0) {
        t += 2;
        x = y;
    }
    y = x >> 1;
    if (y != 0) {
        t += 1;
        x = y;
    }
    if (x != 0)
        t++;

    return t;
}

#define N_IMMEDIATE 16
uint16_t do_now[N_IMMEDIATE];
uint16_t n_immediate;

#define N_EVENTS 64
struct heap {
    uint32_t next_time;
    uint16_t times[N_EVENTS];
    uint8_t events[N_EVENTS];
    uint8_t last;

} h;

void adjust_child(uint8_t child_i, uint32_t adjust) {
    h.times[child_i] = contract(adjust + expand(h.times[child_i]));
}

void adjust_children(uint8_t node, uint32_t adjust) {
    node <<= 1;
    adjust_child(node, adjust);
    adjust_child(node + 1, adjust);
}

void immediate_insert(uint16_t what) {
    if (n_immediate < N_IMMEDIATE) {
        do_now[n_immediate++] = what;
        return;
    }
}

void heap_insert(uint32_t at, uint16_t what) {
    int l = h.last;
    if (l == 1) {
        h.next_time = at;
        h.last = 2;
        h.events[1] = what;
        h.times[1] = 0; // special case
    } else {
        int s = l2p1(l);
        // Make at, what be after first item in heap.
        // Swap if necessary.
        uint32_t delta = h.next_time;
        s--;
        uint8_t i = l >> s;
        while (1) {
            if (at < delta) {
                uint16_t s = h.events[i];
                h.events[i] = what;
                what = s;

                if (i == 1)
                    h.next_time = at;
                else
                    h.times[i] = contract(at);

                at = delta - at;
                adjust_children(i, at);
            } else {
                at -= delta;
            }
            if (s <= 1)
                break;
            s--;
            i = l >> s;
            delta = expand(h.times[i]);
        }

        h.events[l] = what;
        h.times[l] = contract(at);
        h.last = l + 1;
    }
}

uint16_t heap_remove(void) {
    uint8_t l = h.last - 1;
    if (l < 2) {
        if (l != 1) {
            return 0;
        } else {
            h.last = 1;
            h.next_time = 0;
            return h.events[1];
        }
    }
    uint16_t result = h.events[1];

    uint8_t s = l2p1(l);

    // Reconstitute scheduled time
    uint32_t at = 0;
    while (s > 0) {
        s--;
        at += expand(h.times[l >> s]);
    }

    uint16_t what = h.events[l];

    h.last = l;
    uint8_t i = 1;

    uint8_t ii0 = i << 1;

    while (ii0 < l) {
        uint8_t ii1 = ii0 + 1;
        // Note stores to ii1 are legal, since ii0 is less than l.

        uint32_t t0 = expand(h.times[ii0]);
        uint32_t t1 = ii1 < l ? expand(h.times[ii1]) : UINT32_MAX;

        if (at <= t0 && at <= t1) {
            /* at <= c1, c2 */
            t0 -= at;
            t1 -= at;
            h.times[ii0] = contract(t0);
            h.times[ii1] = contract(t1);
            break;
        }

        /* Smaller bubbles up */
        if (t0 > t1) {
            // Swap roles to make t0 smaller
            uint32_t tt = t0;
            t0 = t1;
            t1 = tt;

            uint8_t iit = ii0;
            ii0 = ii1;
            ii1 = iit;
        }

        h.times[i] = h.times[ii0];
        h.events[i] = h.events[ii0];
        t1 -= t0;
        h.times[ii1] = contract(t1);
        at -= t0;
        i = ii0;
        ii0 = i << 1;
    }

    if (i == 1) {
        h.next_time = at;
    } else {
        h.times[i] = contract(at);
        h.next_time = expand(h.times[1]);
        h.times[1] = 0;
    }
    h.events[i] = what;

    return result;
}

// Called with interrupts disabled.
uint16_t immed_empty(void) {
    return (n_immediate == 0);
}

uint16_t heap_cond_remove(void) {
    // Check for any fresh interrupt work.
    cli();
    if (n_immediate > 0) {
        uint16_t x = do_now[--n_immediate];
        sei();
        return x;
    }
    sei();

    // The heap proper is not touched from interrupts;
    // minimize interrupt blocking times.
    if (h.next_time == 0)
        return heap_remove();
    return 0;
}

void heap_init(void) {
    h.last = 1;
    h.next_time = 0;
}

/*
 * ups = 1000000 // microseconds per second
 * C = 2.1       // circumference in meters
 * N = 28        // zeroes per revolution
 * m_per_z = C/N // meters traveled per zero
 *
 * observed_delta_at_mps(x, a) = ups * a * m_per_z^2 / (x ^ 3 + x * a * m_per_z)
 * observed_u_at_mps(x)        = m_per_z * ups / x
 * observed_u_at_mps(x)^3      = m_per_z^3 * ups^3 / x^3
 * observed_u^3 / observed_delta = a * m_per_z * ups^2 (roughly)
 *                               = 1 * 0.075 * 1,000,000,000,000
 *                               = 2^36.126
 *
 * Doubling the accumulation time doubles observed_u,
 * but quadruples observed delta
 * with the result that cube>>36 divided by  delta
 * increases by a factor of 2 for the same acceleration.
 * Hence, for sample window 2^i, the comparison to make is
 * cube>>36 vs observed_delta << i
 *
 * 0.447 mps = 1 mph
 * mps_from_mph(x) = 0.447 * x
 */
#define OVERFLOW 0xffffffffL
uint32_t cube_rshift36(uint32_t x) {
    char big = 0;
    while (x >= 0x10000) {
        big++;
        x = x >> 4;
    }

    if (big == 3)
        return OVERFLOW;

    uint8_t a = x >> 8;
    uint8_t b = x;

// To compute a full cube from 8-bit:
//    (aaH, aaL) = a * a = (24, 16)
//    (bbH, bbL) = b * b = (8, 0)
//    (aaHaH, aaHaL) = aaH * a = (40, 32)
//    (aaLaH, aaLaL) = aaL * a = (32, 24)
//    (aaHbH, aaHbL) = aaH * b = (32, 24)

//    (aaLbH, aaLbL) = aaL * b = (24, 16)
//    (bbHaH, bbHaL) = bbH * a = (24, 16)

//    (bbLaH, bbLaL) = bbL * a = (16, 8)
//    (bbHbH, bbHnL) = bbH * b = (16, 8)
//    (bbLbH, bbLbL) = bbL * b = (8, 0)
//
// Then do *3 as necessary.
// Error is less than one part in 1024.
//
// If big = 0, then result is bits 47-36
// If big = 1, then result is bits 47-24 (note 24 bits!)
// If big = 2, then result is bits 47-12 (note 36 bits -- too many)
// Return 0xffffffff for overflow.
    uint16_t aaHaaL = (uint16_t) a * a;
    uint16_t bbHbbL = (uint16_t) b * b;
    uint8_t aaH = aaHaaL >> 8;
    uint8_t aaL = aaHaaL;
    uint8_t bbH = bbHbbL >> 8;
    uint8_t bbL = bbHbbL;

    uint32_t aaHaHaaHaL = (uint16_t) aaH * a; // 47-32
    uint32_t aaLaHaaLaL = (uint16_t) aaL * a; // 39-24
    uint32_t aaHbHaaHbL = (uint16_t) aaH * b; // 39-24

    if (big == 0) { // 47-36
        uint8_t bit = (uint8_t) (((aaLaHaaLaL & 4095) + 3 * (aaHbHaaHbL & 4095)
                + ((aaHaHaaHaL & 15) << 8)) >> 12);
        return (aaHaHaaHaL >> 4) + (aaLaHaaLaL >> 12) + 3 * (aaHbHaaHbL >> 12)
                + bit;
    }

    uint32_t aaLbHaaLbL = (uint16_t) aaL * b; // 31-16
    uint32_t bbHaHbbHaL = (uint16_t) bbH * a; // 31-16

    uint32_t bbLaHbbLaL = (uint16_t) bbL * a; // 23-8
    uint32_t bbHbHbbHbL = (uint16_t) bbH * b; // 23-8

    uint32_t bbLbHbbLbL = (uint16_t) bbL * b; // 15-0

    if (big == 1) { // 47-24 (24 bits of shift remaining)
        uint32_t partial = (3 * bbLaHbbLaL + bbHbHbbHbL + (bbLbHbbLbL >> 8))
                >> 8;
        partial = (partial + 3 * (aaLbHaaLbL + bbHaHbbHaL)) >> 8;
        partial += (aaHaHaaHaL << 8) + aaLaHaaLaL + 3 * aaHbHaaHbL;
        return partial;
    }

    if (big == 2) { // 47-12 -- 36 bits, can overflow.
                    // 12 bits of shift remaining.

        uint32_t partial = (aaHaHaaHaL << 8) + (aaLaHaaLaL) + 3 * (aaHbHaaHbL);

        if (partial >> 19 != 0)
            return OVERFLOW;

        partial = partial << 12;

        return partial + 3 * ((aaLbHaaLbL << 4) + (bbHaHbbHaL << 4))
                + (bbLbHbbLbL >> 12);
    }

    return OVERFLOW;
}

// State bits
#define OFF 0
#define STOPPED 1
#define ROLLING 2
#define DOUBLER_OFF 4
#define BATTERY_ON 8
#define ACCEL_PLUS 16
#define ACCEL_MINUS 32
#define OVERVOLTAGE 64
volatile uint8_t state;

// Events from scheduling queue
#define EVENT_TEST1 1
#define EVENT_TEST2 2
#define EVENT_TEST3 3
#define EVENT_ZERO     4
#define EVENT_ADC_DONE 5
#define EVENT_CHECK_STOPPED 6
#define EVENT_VERIFY_STOPPED 7
#define EVENT_SANITY_CHECK 8

#define BATTERY_ON_PIN _BV(PD3)
#define DOUBLER_OFF_PIN _BV(PD2)

#define SEC 4000
#define MIN 240000

#define COUNTER_TOP 250

volatile int16_t inc_C = 0;  // to add to C at next timer interrupt.
volatile int32_t C = 0;      // microseconds currently pending
volatile int32_t tz = 0;    // microseconds between last zero crossings
volatile uint16_t crossings; // number of crossings recently observed
volatile uint32_t ticks_since_crossing; // 250 uS ticks since last zero crossing.

ISR(ANALOG_COMP_vect) {
    uint8_t c = TCNT1L;
    crossings++;
    if (TIFR1 & _BV(ICF1)) {
        tz = C + COUNTER_TOP + c;
        C = -250; // + 250 - c + 250 = 250 - c
    } else {
        tz = C + c;
        C = 0;    // + 250 - c = 250 - c
    }
    inc_C = 250 - c;
    immediate_insert(EVENT_ZERO);
    ticks_since_crossing = 0;
    if (state & DOUBLER_OFF) {
        PORTD |= DOUBLER_OFF_PIN;
    } else {
        PORTD &= ~DOUBLER_OFF_PIN;
    }
    PORTB ^= _BV(PB1); // debugging
}

volatile uint16_t ticks;
ISR(TIMER1_OVF_vect) {
    ticks++;
    ticks_since_crossing++;
    C += inc_C;
    inc_C = COUNTER_TOP;
}

void pwm_init(void) {
    /*
     * No clock scaling (1MHz) (CS12:10 = 001b)
     * Fast PWM, top = ICR1 (WGM13:10 = 1110b)
     * Top = 250.
     */
    /* Note option of scaling by 8 (CS11) and
     * counting up to 32 (or 31), and setting
     * power levels to 6 and 12.
     */
    ICR1 = COUNTER_TOP;
    OCR1B = 50; // 20% of 250.
    TCCR1A = _BV(COM1B1) | _BV(WGM11);
#ifdef X8
    TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11); // CS11 for /8
#else
            TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // CS11 for /8
#endif
    TIFR1 = _BV(TOV1);
    TIMSK1 = _BV(TOIE1); // Enable overflow interrupts @ 4kHz
}

#define ADMUX_REF_AREF  _BV(REFS0)
#define ADMUX_REF_1_1  (_BV(REFS0) | _BV(REFS1))

#define ADMUX_5_times_I  (0 | ADMUX_REF_AREF)
#define  ADMUX_v_over_7  (5 | ADMUX_REF_AREF)
#define  ADMUX_temp      (8 | ADMUX_REF_1_1)

/* Analog configuration -- 1M/16 (= 62.5kHz), Vcc ref, from PB2
 * adps2:0 = log adc clock scale.
 * 10 bits requires 50kHz-200kHz
 * 8M/64 => 125kHz, requires PS2 and PS1
 *
 */
#ifdef X8
const uint8_t adcsra_enable = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2)
        | (1 << ADPS1);
const uint8_t adcsra_start = (1 << ADSC) | (1 << ADEN) | (1 << ADIE)
        | (1 << ADPS2) | (1 << ADPS1);
#else
const uint8_t adcsra_enable = (1<<ADEN)|(1<<ADIE)|(1<<ADPS2);
const uint8_t adcsra_start = (1<<ADSC)|(1<<ADEN)|(1<<ADIE)|(1<<ADPS2);
#endif
const uint8_t adcsrb = 0;

#define N_ADC_SOURCES 3
#define ADC_SRC_5I 0
#define ADC_SRC_Vo7 1
#define ADC_SRC_temp 2

uint8_t adc_sources[N_ADC_SOURCES];
uint16_t adc_results[N_ADC_SOURCES];
uint8_t which_adc = 0;

void start_adc(void) {
    ADMUX = adc_sources[which_adc];
    ADCSRA = adcsra_start;
}

void adc_init(void) {
    adc_sources[ADC_SRC_5I] = ADMUX_5_times_I;
    adc_sources[ADC_SRC_Vo7] = ADMUX_v_over_7;
    adc_sources[ADC_SRC_temp] = ADMUX_temp;

    ADCSRA = adcsra_enable;
    ADCSRB = adcsrb;
    DIDR0 = _BV(ADC5D) | _BV(ADC0D); /* disable digital input on B0,5 */

    start_adc();
}

/* Completed analog to digital conversion, move to next measurement
 * or signal done if last one was completed.
 */
ISR(ADC_vect) {
    uint8_t adcl = ADCL;
    uint8_t adch = ADCH;
    uint16_t result = ((uint16_t) adch << 8) + adcl;
    adc_results[which_adc++] = result;
    if (which_adc < N_ADC_SOURCES)
        start_adc();
    else {
        which_adc = 0;
        immediate_insert(EVENT_ADC_DONE);
    }
}


#define LO 50
#define HI 100
// Current light level (250=1A, 50 = 200mA, 100 = 400mA)
uint8_t level = 25;
uint8_t fudge; // adjusted according to voltage

struct flash {
    uint16_t step_lo; // time at each level when towards lo
    uint16_t step_hi; // time at each level when towards hi
    uint16_t step_flash; // time at flash, 0 means no flash
    uint16_t flash_count; // time between flashes
    uint8_t lo; // lowest level, then turn around and go up
    uint8_t hi; // highest level then turn around and go down
    uint8_t flash; // magnitude of flash (zero, e.g.)
};

struct flash stopped_lo = { SEC >> 5, SEC >> 5, SEC >> 7, 2 * SEC, 35, 40, 0 };
struct flash accel_lo = { SEC >> 5, SEC >> 5, SEC >> 6, SEC / 4, 35, 40, 0};
struct flash running_lo = { SEC >> 5, SEC >> 5, 0, SEC / 2, 35, 40, 0};

struct flash stopped_hi = { SEC >> 5, SEC >> 5, SEC >> 7, SEC*2, 70, 80, 255 };
struct flash accel_hi = { SEC >> 5, SEC >> 5, SEC >> 6, SEC / 4, 70, 80, 255};
struct flash running_hi = { SEC >> 5, SEC >> 5, 0, SEC / 2, 70, 80, 0};

struct flash full_on = { SEC >> 6, SEC >> 6, SEC >> 5, SEC, 255, 255, 255 };

// Vary slowly between 4 and 10%, flash off every half second, for 1/128 second
struct flash low = { SEC >> 4, SEC >> 4, SEC >> 7, SEC / 2, 10, 25, 0 };

struct flash low_no_flash = { SEC >> 4, SEC >> 4, 0, SEC / 2, 10, 25, 0 };
struct flash low_rare_flash = { SEC >> 3, SEC >> 3, SEC >> 6, 4 * SEC, 10, 25,
        255 };

struct flash low_flash = { SEC >> 3, SEC >> 3, SEC >> 7, SEC/8, 10, 25,
        255 };

struct flash * flashes[] = {
        & low,
        & low,
        & stopped_lo,
        & stopped_hi,
        & accel_lo,
        & accel_hi,
        & running_lo,
        & running_hi
};

uint8_t flash_i = 0;
#define FLASH_STOP_INDEX 2
#define FLASH_ACCEL_INDEX 4
#define FLASH_RUN_INDEX 6

struct flash * current_flash = & low;

int32_t flash_count = SEC;
uint8_t do_flash(uint16_t step, uint16_t event) {
    if (current_flash->step_flash == 0)
        return 0;
    flash_count -= step;
    if (flash_count <= 0) {
        OCR1B = current_flash->flash;
        heap_insert(current_flash->step_flash, event);
        flash_count = current_flash->flash_count;
        return 1;
    } else
        return 0;
}

// zctr is a rapidly overflowing index of zero-crossing events used to help
// accumulate an averaged time between zeroes for speed and acceleration
// estimation.
uint8_t zctr;
uint32_t A[8];
uint32_t B[8];

// Critical voltages.  32 should never be exceeded.
uint16_t V_TOO_HI = 30 * 1024 / 35; // Dump power
uint16_t V_TOO_LO = 9 * 1024 / 35; // Enable battery
uint16_t V_DBLR_OFF = 27 * 1024 / 35; // Disable doubler
uint16_t V_DBLR_ON = 12 * 1024 / 35; // Enable doubler
uint16_t V_FUDGE_1 = 8 * 1024 / 35; // Fudge threshold
uint16_t V_FUDGE_2 = 7 * 1024 / 35; // Fudge threshold

// Record the ticks at battery-on,
// reduce that slightly and use that
// as the trigger to turn the battery off.
uint32_t battery_off_again = 33000; // about 5mph

// Sample index for battery off -- log2 of # of zeroes
#define TBZ_SAMPLING_LOG 4

// If this gets too large, we decide that we are stopped.
int8_t check_stopped = 0;
// Require 8 zero crossings in one second to unstop.
#define ZEROES_TO_UNSTOP 8
int8_t stopped = 0;

int32_t standlight_countdown = 0;

void set_flash(void) {
    if ((state & OVERVOLTAGE) == 0) {
        current_flash = flashes[flash_i];
    }
}

uint16_t record;

void process(uint16_t event) {
    switch (event) {
    case EVENT_TEST1:
        record |= 1<<EVENT_TEST1;
        if (do_flash(current_flash->step_hi, EVENT_TEST1)) {
        } else {
            OCR1B = level; // % of 250.
            uint8_t hi = current_flash->hi;
            hi = hi>>fudge;
            if (level < hi) {
                level++;
                heap_insert(current_flash->step_hi, EVENT_TEST1);
            } else {
                heap_insert(current_flash->step_hi, EVENT_TEST2);
            }
        }
        break;

    case EVENT_TEST2:
        record |= 1<<EVENT_TEST2;
        if (do_flash(current_flash->step_lo, EVENT_TEST2)) {
        } else {
            OCR1B = level; // % of 250.
            uint8_t lo = current_flash->lo;
            lo = lo>>fudge;
            if (level > lo) {
                level--;
                heap_insert(current_flash->step_lo, EVENT_TEST2);
            } else {
                heap_insert(current_flash->step_lo, EVENT_TEST1);
            }
        }
        break;

    case EVENT_TEST3:
        record |= 1<<EVENT_TEST3;
        flash_i++;
        if (flash_i >= 6) flash_i = 0;
        current_flash = flashes[flash_i];
        heap_insert((uint32_t)SEC*10L, EVENT_TEST3);
        break;

    case EVENT_ADC_DONE: {
        record |= 1<<EVENT_ADC_DONE;
        uint16_t voltage = adc_results[ADC_SRC_Vo7]; // 1024 = 35V
        uint16_t current = adc_results[ADC_SRC_5I];  // 1024 = 1A
        uint16_t temp = adc_results[ADC_SRC_temp];  // 1024 = ??
        start_adc();
        // ADC runs till the end, but does not monkey with the battery
        // unless wheel turns are detected.
        if (voltage >= V_FUDGE_1) {
            fudge = 0;
        } else if (voltage >= V_FUDGE_2) {
            // V_FUDGE is about 450
            fudge = 1;
        } else {
            fudge = 2;
        }
        if (state != OFF) {
            if (voltage > V_TOO_HI) {
                if ((state & OVERVOLTAGE) == 0) {
                    state |= OVERVOLTAGE;
                }
                // dump power, overvoltage
                current_flash = &full_on;
                level = 255;
                OCR1B = level;
            } else {
                if ((state & OVERVOLTAGE) != 0) {
                    // quit dumping power.
                    state &= ~OVERVOLTAGE;
                    current_flash = flashes[flash_i];
                }
                if (voltage < V_TOO_LO) {
                    // power too low, battery on
                    // record ticks between zeroes at 16-zero sampling, times 7/8
                    // battery_off_again = (A[TBZ_SAMPLING_LOG] * 7) >> 3;
                    cli();
                    PORTD |= BATTERY_ON_PIN;
                    sei();
                    state |= BATTERY_ON;
                }
                if (voltage > V_DBLR_OFF) {
                    // turn doubler off, increase goal current
                    // PORTD |= DOUBLER_OFF_PIN; // do this at zero crossing.
                    flash_i |= 1;
                    current_flash = flashes[flash_i];
                    state |= DOUBLER_OFF;
                } else if (voltage < V_DBLR_ON) {
                    // turn doubler on, decrease goal current
                    // PORTD &= ~DOUBLER_OFF_PIN; // Do this at zero crossing.
                    flash_i &= ~1;
                    current_flash = flashes[flash_i];
                    state &= ~DOUBLER_OFF;
                }
            }
        }
    }
        break;

    case EVENT_ZERO: {
        record |= 1 << EVENT_ZERO;
        int32_t local_tz = tz; // uS since last zero.

        if (stopped && local_tz < 250 * (SEC>>5) ) // We get noise.  Ignore it.
            break;

        check_stopped = 0;

        // Update running averages
        // A[i] = sum of most recent 2**i times between zeroes.
        // B[i] = sum of previous 2**i times between zeroes.
        // distances correspond to multiples of 7.5cm
        // e.g., 0 -> 7.5, 1 -> 15, 2 -> 30, 3 -> 60, 4 -> 120
        // and 7 -> 960cm = 9.6m = about 31 feet.
        uint8_t zctrp = zctr + 1;
        uint8_t flips = zctr ^ zctrp;
        zctr = zctrp;
        B[0] = A[0];
        A[0] = local_tz;
        flips >>= 1;
        int i = 0;
        while (flips != 0) {
            int ip1 = i + 1;
            B[ip1] = A[ip1];
            A[ip1] = A[i] + B[i];
            flips >>= 1;
            i = ip1;
        }

        if (stopped) {
            // If stopped, leave the lights alone.
            // Enough zero-crossing will lead to an unstopping.
            if (--stopped <= 0) {
                // TODO this is probably a good place to look for weird
                // power-down artifacts.  Also think about how we would
                // figure times if many zero-crossing occurred in a hurry.
                state = (state & ~STOPPED) | ROLLING;
            }
        } else {
            // i = max flipped bit.
            // See if speed has increased past battery disabling level.
            // Note possible interaction with overvoltage; if we check for
            // persistent overvoltage, we might enable the battery (it will
            // sink the overvoltage).
            if (i >= TBZ_SAMPLING_LOG && (state & BATTERY_ON)
                    && A[TBZ_SAMPLING_LOG] < battery_off_again) {
                // turn battery off.
                state &= ~BATTERY_ON;
                cli();
                PORTD &= ~BATTERY_ON_PIN;
                sei();
            }
            /*
             * Abs(A[i] - B[i]) gives diff of 2^i zeroes at 2^(i+1) distance
             * Speed in A gives clue as to i to probe.
             *
             * difference (A-B) quadruples, time (A) doubles but is cubed.
             * cube>>36 the time, shift << i the diff, compare.
             * If diff is larger, then we have acceleration of about .1g.
             * Do not bother to try this until a time is found that is between
             * 2^15 and 2^16.
             */
            int j = 0;
            uint32_t aj;
            for (j = 0; j < i; j++) {
                // Looking for 32-64 ms of sample.
                aj = A[j];
                if (aj >= 0x8000)
                    break;
            }
            {
                uint32_t cubeshift = cube_rshift36(aj);
                int32_t diff = B[j] - aj; // positive means increased speed
                uint8_t accel_state = ACCEL_PLUS;
                if (diff < 0) {
                    diff = -diff;
                    accel_state = ACCEL_MINUS;
                }
                diff = diff << i;
                if (diff > cubeshift) {
                    // more than about .1G acceleration detected.
                    if (0 == (accel_state & state)) {
                        // new state
                        cli();
                        state = (state & ~(ACCEL_PLUS | ACCEL_MINUS))
                                | accel_state;
                        sei();
                        flash_i = (flash_i & 1) | FLASH_ACCEL_INDEX;
                        set_flash();
                    }
                } else {
                    // no acceleration
                    uint8_t old_accel = state & (ACCEL_PLUS | ACCEL_MINUS);
                    if (old_accel != 0) {
                        cli();
                        state = (state & ~(ACCEL_PLUS | ACCEL_MINUS));
                        sei();
                        // Call the new state running, even though it might
                        // soon be "stopped".
                        flash_i = (flash_i & 1) | FLASH_RUN_INDEX;
                        set_flash();
                    }
                }

            }
        }
    }
    break;

    case EVENT_CHECK_STOPPED: {
        record |= 1 << EVENT_CHECK_STOPPED;
        // Possibly transition to stopped state and turn battery on.
        if (check_stopped++ > 0) {
            // Reset if enough zeroes pass.
            state = (state & ~(ROLLING|ACCEL_PLUS|ACCEL_MINUS)) | STOPPED;
            flash_i = FLASH_STOP_INDEX | (flash_i & 1);
            set_flash();
            stopped = ZEROES_TO_UNSTOP;
            standlight_countdown = 60; // seconds, DOH.
            heap_insert(SEC, EVENT_VERIFY_STOPPED);
        } else {
            state = (state & ~STOPPED) | ROLLING;
            heap_insert(SEC, EVENT_CHECK_STOPPED);
        }
    }
        break;

    case EVENT_VERIFY_STOPPED: {
        record |= 1 << EVENT_VERIFY_STOPPED;
        // We've been stopped, see if we are still stopped.
        if (stopped) {
            // we are still stopped
            stopped = ZEROES_TO_UNSTOP; // reset
            // See if we have been stopped so long we turn off the lights.
            if (--standlight_countdown <= 0) {
                // No surprise allowed here, and turning out the lights is not
                // performance or latency-critical.
                cli();
                state = OFF;
                flash_i = 0;
                current_flash = &low_flash;
                PORTD &= ~BATTERY_ON_PIN;
                stopped = 100; // make it much harder.
                standlight_countdown = MIN; // reset in case of restart.
                sei();
            }
            // Possibly transition out of stopped state or turn battery off.
            heap_insert(SEC, EVENT_VERIFY_STOPPED);
        } else {
            // not stopped -- the wheel moved enough
            // to decrement stopped to zero revert to former behavior.
            heap_insert(SEC, EVENT_CHECK_STOPPED);
        }
    }
        break;

    case EVENT_SANITY_CHECK:
        heap_insert(SEC  * 2, EVENT_SANITY_CHECK);
        uint16_t local_record = record;
        record = 0;
        if (record & (1 << EVENT_ADC_DONE) == 0) {
            adc_init();
        }
        if (record & (1 << EVENT_ZERO) == 0) {
            stopped = 1;
        }
        if (record & ((1 << EVENT_CHECK_STOPPED)|(1 << EVENT_VERIFY_STOPPED)) == 0) {
            heap_insert(SEC/16, EVENT_CHECK_STOPPED);
        }
        break;
    }
}

/*
 * Set clock prescaler to 2-to-the-log_scale,
 * 0 <= log_scale <= 16.
 * Assumes interrupts are disabled.
 */
void set_prescalar(unsigned char log_scale) {
    log_scale &= 15;
    CLKPR = _BV(CLKPCE);
    CLKPR = log_scale;
}

int main(void) {
    /*
     * Begin with no interrupts.
     */
    cli();
#ifdef X8
    set_prescalar(0);
#endif
    /* Initialize control outputs.
     * PD1 - power override.
     *       hi = on, lo = off-ish, hi-impedance normally.
     * PD2 - hi = doubler off
     * PD3 - hi = battery on
     * PB2/OC1B - PWM setting goal current (2V/4V)
     *            should be 20% duty cycle for 2V, 40% for 4V
     */

    /* Default is no override (tristate), no doubler on, no battery.
     * Do this ASAP.
     */
    PORTD = 0;
    DDRD = _BV(PD2) | _BV(PD3);
    PORTB = 0;
    DDRB = _BV(PB2) | _BV(PB1);

    /* Shutdown unneeded stuff. */
    PRR = _BV(PRTWI) | _BV(PRTIM2) | _BV(PRTIM0) | _BV(PRSPI) | _BV(PRUSART0);

    /*
     * Prepare event heap for other initializations, which can trigger
     * events.
     */
    heap_init();

    /* Initialize analog comparator input. */
    DIDR1 = _BV(AIN0D) | _BV(AIN1D);
    ACSR = _BV(ACIE); /* Enable analog compare interrupts */

    /* Initialize analog to digital. */
    adc_init();

    /* Initialize PWM/Timer. */
    pwm_init();

    // heap_insert(SEC / 16, EVENT_TEST1);
    // heap_insert((uint32_t)SEC*10L, EVENT_TEST3);
    stopped = 1;
    state = STOPPED;
    level = 50;
    current_flash = &low;
    flash_i = 0;

    heap_insert(SEC / 2 - SEC/16, EVENT_TEST1);
    heap_insert(SEC + SEC/16, EVENT_CHECK_STOPPED);
    heap_insert(SEC  * 2 + SEC/8, EVENT_SANITY_CHECK);

    while (1) {
        cli();
        if (ticks == 0 && immed_empty()) {
            // Sleep only if no work to do.
            sleep_enable()
            ;
            sei();
            // enable interrupts
            sleep_cpu()
            ;
            sleep_disable()
            ;
            // could wake up, interrupts could occur here.
            cli();
        }
        // interrupts blocked, read + update ticks
        uint16_t ticks_local = ticks;
        ticks = 0;
        sei();

        uint16_t event = heap_cond_remove();
        while (ticks_local > 0 || event > 0) {
            if (event > 0) {
                // Process event;
                process(event);
                // Next event if any.
            } else {
                ticks_local--;
                if (h.next_time > 0)
                    h.next_time--;
            }
            event = heap_cond_remove();
        }
    }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: