#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/io.h>
-#include <avr/wdt.h>
#include <util/delay.h>
const regs_t reg;
const uint8_t pin;
volatile uint8_t overtemp;
- volatile bool shutdown;
+ volatile uint16_t shutdown_flag;
} load_t;
load_t loads[2] = {
- {LOAD1_REG, LOAD1_PIN, 0, false},
- {LOAD2_REG, LOAD2_PIN, 0, false},
+ {LOAD1_REG, LOAD1_PIN, 0, LOAD1_SHUTDOWN_FLAG},
+ {LOAD2_REG, LOAD2_PIN, 0, LOAD2_SHUTDOWN_FLAG},
};
static volatile uint16_t reg_index[NUM_REGS] = {0};
static volatile uint64_t time = 0; // ms
static volatile uint8_t motor_overload = 0;
-static volatile bool shunt_overload = false;
static volatile float shunt_joules = 0;
static volatile float vnom = 0;
-void i2c_ack() {TWSCRB = (1 << TWCMD1) | (1 << TWCMD0);}
-void i2c_nack() {TWSCRB = (1 << TWAA) | (1 << TWCMD1) | (1 << TWCMD0);}
+static void shutdown();
+
+
+static uint16_t flags_get(uint16_t flags) {return regs[FLAGS_REG] & flags;}
+static void flags_clear(uint16_t flags) {regs[FLAGS_REG] &= ~flags;}
+
+
+static void flags_set(uint16_t flags) {
+ regs[FLAGS_REG] |= flags;
+ if (flags & FATAL_FLAGS) shutdown();
+}
+
+
+static void flags(uint16_t flags, bool enable) {
+ if (enable) flags_set(flags);
+ else flags_clear(flags);
+}
+
+
+static void i2c_ack() {TWSCRB = (1 << TWCMD1) | (1 << TWCMD0);}
+static void i2c_nack() {TWSCRB = (1 << TWAA) | (1 << TWCMD1) | (1 << TWCMD0);}
ISR(TWI_SLAVE_vect) {
}
-static float get_total_current() {
- cli();
- float current =
- regs[MOTOR_REG] + regs[VDD_REG] + regs[LOAD1_REG] + regs[LOAD2_REG];
- sei();
-
- return current / 100;
-}
-
-
static float get_reg(int reg) {
cli();
float value = regs[reg];
static void update_shunt() {
+ if (flags_get(POWER_SHUTDOWN_FLAG)) return;
+
static float joules = SHUNT_JOULES; // Power disipation budget
// Add power dissipation credit for the 1ms that elapsed
joules += SHUNT_JOULES_PER_MS;
if (SHUNT_JOULES < joules) joules = SHUNT_JOULES; // Max
- if (joules < shunt_joules) shunt_overload = true;
+ if (joules < shunt_joules) flags_set(SHUNT_OVERLOAD_FLAG);
else joules -= shunt_joules; // Subtract power dissipated
}
-static void update_shunt_power(float vout, float vnom) {
+static void update_shunt_power() {
+ if (flags_get(POWER_SHUTDOWN_FLAG)) return;
+
+ float vout = get_reg(VOUT_REG);
+
if (vnom + SHUNT_MIN_V < vout) {
// Compute joules shunted this cycle: J = V^2 / RT
shunt_joules = vout * vout / (SHUNT_OHMS * 1000.0);
- IO_DDR_SET(SHUNT_PIN); // Enable
- IO_PORT_CLR(SHUNT_PIN); // Lo
+ IO_PORT_CLR(SHUNT_PIN); // Enable (lo)
} else {
shunt_joules = 0;
- IO_DDR_CLR(SHUNT_PIN); // Disable
+ IO_PORT_SET(SHUNT_PIN); // Disable (hi)
}
}
}
-static void check_load(load_t *load) {
- if (load->shutdown) return;
-
- bool overtemp = CURRENT_OVERTEMP * 100 < regs[load->reg];
- if (overtemp) {
- if (++load->overtemp == LOAD_OVERTEMP_MAX) {
- load->shutdown = true;
- IO_PORT_CLR(load->pin); // Lo
- IO_DDR_SET(load->pin); // Output
- }
- } else if (load->overtemp) load->overtemp--;
-}
-
-
ISR(TIMER0_OVF_vect) {
static uint8_t tick = 0;
}
-static void delay_ms(uint16_t ms) {
- uint64_t start = time;
- while (time < start + ms) continue;
-}
-
-
-inline static uint16_t convert_voltage(uint16_t sample) {
+static uint16_t convert_voltage(uint16_t sample) {
return sample * (VOLTAGE_REF / 1024.0 *
(VOLTAGE_REF_R1 + VOLTAGE_REF_R2) / VOLTAGE_REF_R2 * 100);
}
-inline static uint16_t convert_current(uint16_t sample) {
+static uint16_t convert_current(uint16_t sample) {
return sample * (VOLTAGE_REF / 1024.0 * CURRENT_REF_MUL);
}
-inline static void update_current(int reg, uint16_t sample) {
+static void update_current(int reg, uint16_t sample) {
reg_avg[reg][reg_index[reg]] = convert_current(sample);
if (++reg_index[reg] == BUCKETS) reg_index[reg] = 0;
sum += reg_avg[reg][i];
regs[reg] = sum >> AVG_SCALE;
+
+ // Check total current
+ uint16_t total_current =
+ regs[MOTOR_REG] + regs[VDD_REG] + regs[LOAD1_REG] + regs[LOAD2_REG];
+ if (CURRENT_MAX * 100 < total_current) flags_set(OVER_CURRENT_FLAG);
}
-static void read_conversion(uint8_t ch) {
- uint16_t data = ADC;
+static void update_vin(uint16_t sample) {
+ uint16_t vin = regs[VIN_REG] = convert_voltage(sample);
- switch (ch) {
- case TEMP_ADC: regs[TEMP_REG] = data; break; // in Kelvin
- case VIN_ADC: regs[VIN_REG] = convert_voltage(data); break;
-
- case VOUT_ADC:
- regs[VOUT_REG] = convert_voltage(data);
- update_shunt_power(regs[VOUT_REG] / 100.0, vnom);
- break;
-
- case CS1_ADC: {
- update_current(MOTOR_REG, data);
- bool overtemp = CURRENT_OVERTEMP * 100 < regs[MOTOR_REG];
-
- if (overtemp) {
- if (motor_overload < MOTOR_SHUTDOWN_THRESH) motor_overload++;
- } else if (motor_overload != MOTOR_SHUTDOWN_THRESH && motor_overload)
- motor_overload--;
- break;
- }
+ // Check voltage
+ if (vin < (VOLTAGE_MIN * 100)) flags_set(UNDER_VOLTAGE_FLAG);
+ if ((VOLTAGE_MAX * 100) < vin) flags_set(OVER_VOLTAGE_FLAG);
+}
- case CS2_ADC: update_current(VDD_REG, data); break;
- case CS3_ADC:
- update_current(LOAD2_REG, data);
- check_load(&loads[1]);
- break;
+static void update_vout(uint16_t sample) {
+ uint16_t vout = regs[VOUT_REG] = convert_voltage(sample);
- case CS4_ADC:
- update_current(LOAD1_REG, data);
- check_load(&loads[0]);
- break;
+ update_shunt_power();
+
+ // Check voltage
+ if ((VOLTAGE_MAX * 100) < vout) flags_set(OVER_VOLTAGE_FLAG);
+ flags(MOTOR_UNDER_VOLTAGE_FLAG,
+ vout < (VOLTAGE_MIN * 100) && !flags_get(POWER_SHUTDOWN_FLAG));
+}
+
+
+static void update_motor_current(uint16_t sample) {
+ update_current(MOTOR_REG, sample);
+
+ if (flags_get(MOTOR_OVERLOAD_FLAG)) return;
+
+ bool overtemp = CURRENT_OVERTEMP * 100 < regs[MOTOR_REG];
+
+ if (overtemp) {
+ if (motor_overload < MOTOR_SHUTDOWN_THRESH) motor_overload++;
+ if (motor_overload == MOTOR_SHUTDOWN_THRESH) flags_set(MOTOR_OVERLOAD_FLAG);
+
+ } else if (motor_overload != MOTOR_SHUTDOWN_THRESH && motor_overload)
+ motor_overload--;
+}
+
+
+static void load_shutdown(load_t *load) {
+ flags_set(load->shutdown_flag);
+ IO_PORT_CLR(load->pin); // Lo
+ IO_DDR_SET(load->pin); // Output
+}
+
+
+static void update_load_current(load_t *load, uint16_t sample) {
+ update_current(load->reg, sample);
+
+ if (flags_get(load->shutdown_flag)) return;
+
+ bool overtemp = CURRENT_OVERTEMP * 100 < regs[load->reg];
+
+ if (overtemp) {
+ if (++load->overtemp == LOAD_OVERTEMP_MAX) load_shutdown(load);
+ } else if (load->overtemp) load->overtemp--;
+}
+
+
+static void read_conversion(uint8_t ch) {
+ uint16_t sample = ADC;
+
+ switch (ch) {
+ case TEMP_ADC: regs[TEMP_REG] = sample; break; // in Kelvin
+ case VIN_ADC: update_vin(sample); break;
+ case VOUT_ADC: update_vout(sample); break;
+ case CS1_ADC: update_motor_current(sample); break;
+ case CS2_ADC: update_current(VDD_REG, sample); break;
+ case CS3_ADC: update_load_current(&loads[1], sample); break;
+ case CS4_ADC: update_load_current(&loads[0], sample); break;
}
}
float vlast = 0;
while (settle < VOLTAGE_SETTLE_COUNT) {
- wdt_reset();
- delay_ms(VOLTAGE_SETTLE_PERIOD);
-
- float vin = get_reg(VIN_REG);
+ _delay_ms(VOLTAGE_SETTLE_PERIOD);
// Check that voltage is with in range and settled
+ float vin = get_reg(VIN_REG);
if (VOLTAGE_MIN < vin && vin < VOLTAGE_MAX &&
is_within(vlast, vin, VOLTAGE_SETTLE_TOLERANCE)) settle++;
else settle = 0;
static void charge_caps() {
- uint64_t now = time;
-
+ IO_PORT_SET(SHUNT_PIN); // Disable shunt (hi)
IO_PORT_SET(MOTOR_PIN); // Motor voltage on
- while (time < now + CAP_CHARGE_TIME) continue;
+ _delay_ms(CAP_CHARGE_TIME);
}
cli();
// CPU Clock, disable CKOUT
- CCP = 0xd8;
+ CCP = 0xd8;
CLKSR = (1 << CSTR) | (1 << CKOUT_IO) | 0b0010; // 8Mhz internal clock
- CCP = 0xd8;
+ CCP = 0xd8;
CLKPR = 0; // div 1
while (!((1 << 7) & CLKSR)) continue; // Wait for clock to stabilize
IO_DDR_CLR(LOAD1_PIN); // Tri-state
IO_DDR_CLR(LOAD2_PIN); // Tri-state
IO_PUE_SET(PWR_RESET); // Pull up reset line
+ IO_PORT_CLR(SHUNT_PIN); // Enable shunt
+ IO_DDR_SET(SHUNT_PIN); // Output
// Disable digital IO on ADC lines
DIDR0 = (1 << ADC4D) | (1 << ADC3D) | (1 << ADC2D) | (1 << ADC1D) |
// ADC internal 1.1v, enable, with interrupt, prescale 64
// Note, a conversion takes ~200uS
- ADMUX = (1 << REFS1) | (0 << REFS0);
+ ADMUX = (1 << REFS1) | (0 << REFS0);
ADCSRA = (1 << ADEN) | (1 << ADIE) |
(1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
ADCSRB = 0;
// Timer 0 (Fast PWM, clk/1)
TCCR0A = (1 << WGM01) | (1 << WGM00);
TCCR0B = (0 << WGM02) | (0 << CS02) | (0 << CS01) | (1 << CS00);
- TIMSK = 1 << TOIE0; // Enable overflow interrupt
+ TIMSK = 1 << TOIE0; // Enable overflow interrupt
// I2C, enable, enable address/stop interrupt
TWSCRA = (1 << TWEN) | (1 << TWASIE) | (1 << TWDIE);
- TWSA = I2C_ADDR << 1;
- TWSAM = I2C_MASK << 1;
+ TWSA = I2C_ADDR << 1;
+ TWSAM = I2C_MASK << 1;
sei();
}
static void shutdown() {
- // Disable outputs
- IO_DDR_CLR(MOTOR_PIN); // Input
- IO_PORT_CLR(LOAD1_PIN); // Lo
- IO_PORT_CLR(LOAD2_PIN); // Lo
- IO_DDR_SET(LOAD1_PIN); // Output
- IO_DDR_SET(LOAD2_PIN); // Output
+ if (flags_get(POWER_SHUTDOWN_FLAG)) return;
+ flags_set(POWER_SHUTDOWN_FLAG);
+
+ // Disable loads
+ load_shutdown(&loads[0]);
+ load_shutdown(&loads[1]);
+
+ // Motor power off
+ IO_PORT_CLR(MOTOR_PIN); // Lo
+
+ // Turn shunt on
+ IO_PORT_CLR(SHUNT_PIN); // Lo
}
-static uint16_t validate_measurements() {
+static void validate_measurements() {
const float max_voltage = 0.99 * convert_voltage(0x3ff);
const float max_current = 0.99 * convert_current(0x3ff);
- uint16_t flags = 0;
-
- if (max_voltage < regs[VOUT_REG]) flags |= MOTOR_VOLTAGE_SENSE_ERROR_FLAG;
- if (max_current < regs[MOTOR_REG]) flags |= MOTOR_CURRENT_SENSE_ERROR_FLAG;
- if (max_current < regs[LOAD1_REG]) flags |= LOAD1_SENSE_ERROR_FLAG;
- if (max_current < regs[LOAD2_REG]) flags |= LOAD2_SENSE_ERROR_FLAG;
- if (max_current < regs[VDD_REG]) flags |= VDD_CURRENT_SENSE_ERROR_FLAG;
- return flags ? SENSE_ERROR_FLAG | flags : 0;
+ if (max_voltage < regs[VOUT_REG]) flags_set(MOTOR_VOLTAGE_SENSE_ERROR_FLAG);
+ if (max_current < regs[MOTOR_REG]) flags_set(MOTOR_CURRENT_SENSE_ERROR_FLAG);
+ if (max_current < regs[LOAD1_REG]) flags_set(LOAD1_SENSE_ERROR_FLAG);
+ if (max_current < regs[LOAD2_REG]) flags_set(LOAD2_SENSE_ERROR_FLAG);
+ if (max_current < regs[VDD_REG]) flags_set(VDD_CURRENT_SENSE_ERROR_FLAG);
+ if (flags_get(SENSE_ERROR_FLAGS)) flags_set(SENSE_ERROR_FLAG);
}
int main() {
- wdt_enable(WDTO_8S);
-
regs[VERSION_REG] = VERSION;
init();
adc_conversion(); // Start ADC
validate_input_voltage();
charge_caps();
- uint16_t fatal = validate_measurements();
-
- while (true) {
- wdt_reset();
+ validate_measurements();
- float vin = get_reg(VIN_REG);
- float vout = get_reg(VOUT_REG);
-
- //update_shunt_power(vout, vnom);
-
- // Fatal conditions
- if (vin < VOLTAGE_MIN) fatal |= UNDER_VOLTAGE_FLAG;
- if (VOLTAGE_MAX < vin || VOLTAGE_MAX < vout) fatal |= OVER_VOLTAGE_FLAG;
- if (CURRENT_MAX < get_total_current()) fatal |= OVER_CURRENT_FLAG;
- if (shunt_overload) fatal |= SHUNT_OVERLOAD_FLAG;
- if (motor_overload == MOTOR_SHUTDOWN_THRESH) fatal |= MOTOR_OVERLOAD_FLAG;
- if (fatal) shutdown();
-
- // Nonfatal conditions
- uint16_t nonfatal = 0;
- if (loads[0].shutdown) nonfatal |= LOAD1_SHUTDOWN_FLAG;
- if (loads[1].shutdown) nonfatal |= LOAD2_SHUTDOWN_FLAG;
- if (vout < VOLTAGE_MIN) nonfatal |= MOTOR_UNDER_VOLTAGE_FLAG;
-
- // Update flags
- regs[FLAGS_REG] = fatal | nonfatal;
- }
+ while (true) continue;
return 0;
}