Verison 1.0.3 Release
Based on Buildbotics 0.4.14
This commit is contained in:
3
src/pwr/.gitignore
vendored
Normal file
3
src/pwr/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
/build
|
||||
/*.elf
|
||||
/*.hex
|
||||
116
src/pwr/Makefile
Normal file
116
src/pwr/Makefile
Normal file
@@ -0,0 +1,116 @@
|
||||
# Makefile for the project Bulidbotics firmware
|
||||
PROJECT = bbctrl-pwr-firmware
|
||||
MCU = attiny1634
|
||||
CLOCK = 8000000
|
||||
|
||||
# Compile flags
|
||||
CC = avr-gcc
|
||||
CPP = avr-g++
|
||||
COMMON = -mmcu=$(MCU)
|
||||
CFLAGS += $(COMMON)
|
||||
CFLAGS += -Wall -Werror
|
||||
CFLAGS += -std=gnu99 -DF_CPU=$(CLOCK)UL -O3
|
||||
CFLAGS += -funsigned-bitfields -fpack-struct -fshort-enums -funsigned-char
|
||||
CFLAGS += -MD -MP -MT $@ -MF build/dep/$(@F).d
|
||||
CFLAGS += -I.
|
||||
|
||||
# Linker flags
|
||||
LDFLAGS += $(COMMON) -Wl,-u,vfprintf -lprintf_flt -lm
|
||||
LIBS += -lm
|
||||
|
||||
# Programming flags
|
||||
ifndef (PROGRAMMER)
|
||||
PROGRAMMER = avrispmkII
|
||||
#PROGRAMMER = jtag3pdi
|
||||
endif
|
||||
PDEV = usb
|
||||
AVRDUDE_OPTS = -c $(PROGRAMMER) -p $(MCU) -P $(PDEV)
|
||||
|
||||
FUSE_EX=0xff
|
||||
FUSE_HI=0xdf
|
||||
FUSE_LO=0x62
|
||||
|
||||
# SRC
|
||||
SRC = $(wildcard *.c)
|
||||
OBJ = $(patsubst %.c,build/%.o,$(SRC))
|
||||
|
||||
# Build
|
||||
all: $(PROJECT).hex size
|
||||
|
||||
# Compile
|
||||
build/%.o: %.c
|
||||
@mkdir -p $(shell dirname $@)
|
||||
$(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
build/%.o: %.S
|
||||
@mkdir -p $(shell dirname $@)
|
||||
$(CC) $(INCLUDES) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
# Link
|
||||
$(PROJECT).elf: $(OBJ)
|
||||
$(CC) $(LDFLAGS) $(OBJ) $(LIBS) -o $@
|
||||
|
||||
%.hex: %.elf
|
||||
avr-objcopy -O ihex -R .fuse -R .lock -R .signature $< $@
|
||||
|
||||
%.lss: %.elf
|
||||
avr-objdump -h -S $< > $@
|
||||
|
||||
_size:
|
||||
@for X in A B C; do\
|
||||
echo '****************************************************************' ;\
|
||||
avr-size -$$X --mcu=$(MCU) $(SIZE_TARGET) ;\
|
||||
done
|
||||
|
||||
size: $(PROJECT).elf
|
||||
@$(MAKE) SIZE_TARGET=$< _size
|
||||
|
||||
# Program
|
||||
init:
|
||||
$(MAKE) erase
|
||||
-$(MAKE) fuses
|
||||
$(MAKE) program
|
||||
|
||||
reset:
|
||||
avrdude $(AVRDUDE_OPTS)
|
||||
|
||||
erase:
|
||||
avrdude $(AVRDUDE_OPTS) -e
|
||||
|
||||
program: $(PROJECT).hex
|
||||
avrdude $(AVRDUDE_OPTS) -U flash:w:$(PROJECT).hex:i
|
||||
|
||||
verify: $(PROJECT).hex
|
||||
avrdude $(AVRDUDE_OPTS) -U flash:v:$(PROJECT).hex:i
|
||||
|
||||
fuses:
|
||||
avrdude $(AVRDUDE_OPTS) -U efuse:w:$(FUSE_EX):m -U lfuse:w:$(FUSE_LO):m \
|
||||
-U hfuse:w:$(FUSE_HI):m
|
||||
|
||||
read_fuses:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U efuse:r:-:h -U lfuse:r:-:h -U hfuse:r:-:h
|
||||
|
||||
signature:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U signature:r:-:h
|
||||
|
||||
prodsig:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U prodsig:r:-:h
|
||||
|
||||
usersig:
|
||||
avrdude $(AVRDUDE_OPTS) -q -q -U usersig:r:-:h
|
||||
|
||||
info:
|
||||
avrdude $(AVRDUDE_OPTS) -v
|
||||
|
||||
# Clean
|
||||
tidy:
|
||||
rm -f $(shell find -name \*~ -o -name \#\*)
|
||||
|
||||
clean: tidy
|
||||
rm -rf $(PROJECT).elf $(PROJECT).hex $(PROJECT).lss $(PROJECT).map build
|
||||
|
||||
.PHONY: tidy clean size all reset erase program fuses read_fuses prodsig
|
||||
.PHONY: signature usersig
|
||||
|
||||
# Dependencies
|
||||
-include $(shell mkdir -p build/dep) $(wildcard build/dep/*)
|
||||
164
src/pwr/config.h
Normal file
164
src/pwr/config.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "pins.h"
|
||||
|
||||
|
||||
#define VERSION 6
|
||||
|
||||
|
||||
// Pins
|
||||
enum {
|
||||
AREF_PIN = PORT_A << 3,
|
||||
PA1_PIN, // NC
|
||||
PA2_PIN, // NC
|
||||
CS1_PIN,
|
||||
CS2_PIN,
|
||||
CS3_PIN,
|
||||
CS4_PIN,
|
||||
VOUT_REF_PIN,
|
||||
|
||||
VIN_REF_PIN = PORT_B << 3,
|
||||
PWR_MOSI_PIN,
|
||||
PWR_MISO_PIN,
|
||||
SHUNT_PIN,
|
||||
|
||||
MOTOR_PIN = PORT_C << 3, // IN1
|
||||
PWR_SCK_PIN,
|
||||
PC2_PIN, // NC
|
||||
PWR_RESET,
|
||||
LOAD2_PIN, // IN3
|
||||
LOAD1_PIN, // IN4
|
||||
};
|
||||
|
||||
|
||||
// ADC channels
|
||||
enum {
|
||||
CS1_ADC, // Motor current
|
||||
CS2_ADC, // Vdd current
|
||||
CS3_ADC, // Load 2 current
|
||||
CS4_ADC, // Load 1 current
|
||||
VOUT_ADC, // Motor voltage
|
||||
VIN_ADC, // Input voltage
|
||||
NC6_ADC,
|
||||
NC7_ADC,
|
||||
NC8_ADC,
|
||||
NC9_ADC,
|
||||
NC10_ADC,
|
||||
NC11_ADC,
|
||||
NC12_ADC,
|
||||
NC13_ADC,
|
||||
TEMP_ADC, // Temperature
|
||||
};
|
||||
|
||||
|
||||
#define SHUNT_FAIL_VOLTAGE 5
|
||||
#define CAP_CHARGE_TIME 20 // ms
|
||||
#define VOLTAGE_MIN 11
|
||||
#define VOLTAGE_MAX 39
|
||||
#define CURRENT_MAX 25
|
||||
#define CURRENT_OVERTEMP 19 // Should read ~21A but > 11.86A is error
|
||||
#define LOAD_OVERTEMP_MAX 10
|
||||
#define MOTOR_SHUTDOWN_THRESH 15
|
||||
#define VOLTAGE_SETTLE_COUNT 5
|
||||
#define VOLTAGE_SETTLE_PERIOD 20 // ms
|
||||
#define VOLTAGE_SETTLE_TOLERANCE 0.01
|
||||
#define VOLTAGE_EXP 0.01
|
||||
|
||||
#define SHUNT_WATTS 5
|
||||
#define SHUNT_OHMS 5.1
|
||||
#define SHUNT_PERIOD 65000 // ms
|
||||
#define SHUNT_JOULES 25 // Power per shunt period
|
||||
#define SHUNT_JOULES_PER_MS ((float)SHUNT_JOULES / SHUNT_PERIOD)
|
||||
#define SHUNT_MIN_V 2
|
||||
|
||||
#define VOLTAGE_REF 1.1
|
||||
#define VOLTAGE_REF_R1 37400
|
||||
#define VOLTAGE_REF_R2 1000
|
||||
#define CURRENT_REF_R2 137
|
||||
#define CURRENT_REF_MUL (100.0 * 2700 / CURRENT_REF_R2) // 2700 from datasheet
|
||||
|
||||
#define REG_SCALE 100
|
||||
#define AVG_SCALE 3
|
||||
#define BUCKETS (1 << AVG_SCALE)
|
||||
|
||||
// Addresses 0x60 to 0x67
|
||||
#define I2C_ADDR 0x60
|
||||
#define I2C_MASK 0b00001111
|
||||
|
||||
#define I2C_ERROR_BM (1 << TWBE)
|
||||
#define I2C_DATA_INT_BM (1 << TWDIF)
|
||||
#define I2C_READ_BM (1 << TWDIR)
|
||||
#define I2C_ADDRESS_STOP_INT_BM (1 << TWASIF)
|
||||
#define I2C_ADDRESS_MATCH_BM (1 << TWAS)
|
||||
|
||||
|
||||
typedef enum {
|
||||
TEMP_REG,
|
||||
VIN_REG,
|
||||
VOUT_REG,
|
||||
MOTOR_REG,
|
||||
LOAD1_REG,
|
||||
LOAD2_REG,
|
||||
VDD_REG,
|
||||
FLAGS_REG,
|
||||
VERSION_REG,
|
||||
NUM_REGS
|
||||
} regs_t;
|
||||
|
||||
|
||||
enum {
|
||||
// Fatal
|
||||
UNDER_VOLTAGE_FLAG = 1 << 0,
|
||||
OVER_VOLTAGE_FLAG = 1 << 1,
|
||||
OVER_CURRENT_FLAG = 1 << 2,
|
||||
SENSE_ERROR_FLAG = 1 << 3,
|
||||
SHUNT_OVERLOAD_FLAG = 1 << 4,
|
||||
MOTOR_OVERLOAD_FLAG = 1 << 5,
|
||||
|
||||
// Non fatal
|
||||
LOAD1_SHUTDOWN_FLAG = 1 << 6,
|
||||
LOAD2_SHUTDOWN_FLAG = 1 << 7,
|
||||
MOTOR_UNDER_VOLTAGE_FLAG = 1 << 8,
|
||||
SHUNT_ERROR_FLAG = 1 << 15,
|
||||
|
||||
// Sense errors
|
||||
MOTOR_VOLTAGE_SENSE_ERROR_FLAG = 1 << 9,
|
||||
MOTOR_CURRENT_SENSE_ERROR_FLAG = 1 << 10,
|
||||
LOAD1_SENSE_ERROR_FLAG = 1 << 11,
|
||||
LOAD2_SENSE_ERROR_FLAG = 1 << 12,
|
||||
VDD_CURRENT_SENSE_ERROR_FLAG = 1 << 13,
|
||||
|
||||
// State flags
|
||||
POWER_SHUTDOWN_FLAG = 1 << 14,
|
||||
};
|
||||
|
||||
|
||||
#define FATAL_FLAGS 0x003f
|
||||
#define SENSE_ERROR_FLAGS 0x3e00
|
||||
486
src/pwr/main.c
Normal file
486
src/pwr/main.c
Normal file
@@ -0,0 +1,486 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <avr/io.h>
|
||||
|
||||
#include <util/delay.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
volatile uint16_t value;
|
||||
volatile uint16_t raw;
|
||||
volatile uint16_t buckets[BUCKETS];
|
||||
volatile uint8_t index;
|
||||
volatile uint8_t fill;
|
||||
volatile uint32_t sum;
|
||||
} reg_t;
|
||||
|
||||
|
||||
typedef struct {
|
||||
const regs_t reg;
|
||||
const uint8_t pin;
|
||||
volatile uint8_t overtemp;
|
||||
volatile uint16_t shutdown_flag;
|
||||
} load_t;
|
||||
|
||||
|
||||
load_t loads[2] = {
|
||||
{LOAD1_REG, LOAD1_PIN, 0, LOAD1_SHUTDOWN_FLAG},
|
||||
{LOAD2_REG, LOAD2_PIN, 0, LOAD2_SHUTDOWN_FLAG},
|
||||
};
|
||||
|
||||
|
||||
static const uint8_t ch_schedule[] = {
|
||||
TEMP_ADC, VOUT_ADC,
|
||||
VIN_ADC, VOUT_ADC,
|
||||
CS1_ADC, VOUT_ADC,
|
||||
CS2_ADC, VOUT_ADC,
|
||||
CS3_ADC, VOUT_ADC,
|
||||
CS4_ADC, VOUT_ADC,
|
||||
};
|
||||
|
||||
|
||||
static reg_t regs[NUM_REGS] = {{0}};
|
||||
static volatile uint64_t time = 0; // ms
|
||||
static volatile uint8_t motor_overload = 0;
|
||||
static volatile float shunt_joules = 0;
|
||||
static volatile bool initialized = false;
|
||||
static volatile float vnom = 0;
|
||||
|
||||
|
||||
void delay(uint16_t ms) {
|
||||
uint64_t end = time + ms;
|
||||
while (time < end) continue;
|
||||
}
|
||||
|
||||
|
||||
static void shutdown();
|
||||
|
||||
|
||||
static uint16_t flags_get(uint16_t flags) {
|
||||
return regs[FLAGS_REG].value & flags;
|
||||
}
|
||||
|
||||
|
||||
static void flags_clear(uint16_t flags) {regs[FLAGS_REG].value &= ~flags;}
|
||||
|
||||
|
||||
static void flags_set(uint16_t flags) {
|
||||
regs[FLAGS_REG].value |= 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 uint8_t byte = 0;
|
||||
static uint16_t reg;
|
||||
|
||||
// Stretch clock longer to work around RPi bug
|
||||
// See https://github.com/raspberrypi/linux/issues/254
|
||||
_delay_us(10); // Must use software delay while in interrupt
|
||||
|
||||
uint8_t status = TWSSRA;
|
||||
|
||||
if (status & I2C_DATA_INT_BM) {
|
||||
if (status & I2C_READ_BM) {
|
||||
// Send response
|
||||
if (byte < 2) {
|
||||
TWSD = byte++ ? reg >> 8 : reg;
|
||||
i2c_ack();
|
||||
|
||||
} else i2c_nack();
|
||||
|
||||
} else i2c_ack(); // Write ignore
|
||||
|
||||
} else if (status & I2C_ADDRESS_STOP_INT_BM) {
|
||||
if (status & I2C_ADDRESS_MATCH_BM) {
|
||||
// Read address
|
||||
uint8_t addr = (TWSD >> 1) & I2C_MASK;
|
||||
|
||||
if (addr < NUM_REGS) {
|
||||
i2c_ack();
|
||||
reg = regs[addr].value;
|
||||
byte = 0;
|
||||
|
||||
} else i2c_nack();
|
||||
|
||||
} else TWSCRB = (1 << TWCMD1) | (0 << TWCMD0); // Stop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static float get_reg(int reg) {
|
||||
uint8_t sreg = SREG;
|
||||
cli();
|
||||
float value = regs[reg].value;
|
||||
SREG = sreg;
|
||||
|
||||
return value / REG_SCALE;
|
||||
}
|
||||
|
||||
|
||||
static void update_shunt() {
|
||||
if (!initialized) 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) flags_set(SHUNT_OVERLOAD_FLAG);
|
||||
else joules -= shunt_joules; // Subtract power dissipated
|
||||
}
|
||||
|
||||
|
||||
static void update_shunt_power() {
|
||||
if (!initialized) 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_PORT_CLR(SHUNT_PIN); // Enable (lo)
|
||||
|
||||
} else {
|
||||
shunt_joules = 0;
|
||||
IO_PORT_SET(SHUNT_PIN); // Disable (hi)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void measure_nominal_voltage() {
|
||||
float vin = regs[VIN_REG].raw / REG_SCALE;
|
||||
|
||||
if (vnom < VOLTAGE_MIN) vnom = vin;
|
||||
else vnom = vnom * (1 - VOLTAGE_EXP) + vin * VOLTAGE_EXP;
|
||||
}
|
||||
|
||||
|
||||
ISR(TIMER0_OVF_vect) {
|
||||
static uint8_t tick = 0;
|
||||
|
||||
if (++tick == 31) {
|
||||
time++;
|
||||
tick = 0;
|
||||
|
||||
update_shunt(); // Every 1ms
|
||||
if (!(time & 7)) measure_nominal_voltage(); // Every 8ms
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static uint16_t average_reg(int index, uint16_t sample) {
|
||||
reg_t *reg = ®s[index];
|
||||
|
||||
reg->raw = sample;
|
||||
reg->sum -= reg->buckets[reg->index];
|
||||
reg->sum += sample;
|
||||
reg->buckets[reg->index] = sample;
|
||||
if (++reg->index == BUCKETS) reg->index = 0;
|
||||
|
||||
if (reg->fill < BUCKETS) {
|
||||
reg->fill++;
|
||||
reg->value = reg->sum / reg->fill;
|
||||
|
||||
} else reg->value = reg->sum >> AVG_SCALE;
|
||||
|
||||
return reg->value;
|
||||
}
|
||||
|
||||
|
||||
static uint16_t convert_voltage(uint16_t sample) {
|
||||
return
|
||||
sample * (VOLTAGE_REF / 1024.0 *
|
||||
(VOLTAGE_REF_R1 + VOLTAGE_REF_R2) / VOLTAGE_REF_R2 * REG_SCALE);
|
||||
}
|
||||
|
||||
|
||||
static uint16_t convert_current(uint16_t sample) {
|
||||
return sample * (VOLTAGE_REF / 1024.0 * CURRENT_REF_MUL);
|
||||
}
|
||||
|
||||
|
||||
static void update_current(int reg, uint16_t sample) {
|
||||
average_reg(reg, convert_current(sample));
|
||||
|
||||
// Check total current
|
||||
if (!initialized) return;
|
||||
uint16_t total_current =
|
||||
regs[MOTOR_REG].value + regs[VDD_REG].value + regs[LOAD1_REG].value +
|
||||
regs[LOAD2_REG].value;
|
||||
if (CURRENT_MAX * REG_SCALE < total_current) flags_set(OVER_CURRENT_FLAG);
|
||||
}
|
||||
|
||||
|
||||
static void update_vin(uint16_t sample) {
|
||||
uint16_t vin = average_reg(VIN_REG, convert_voltage(sample));
|
||||
|
||||
// Check voltage
|
||||
if (!initialized) return;
|
||||
if (vin < (VOLTAGE_MIN * REG_SCALE)) flags_set(UNDER_VOLTAGE_FLAG);
|
||||
if ((VOLTAGE_MAX * REG_SCALE) < vin) flags_set(OVER_VOLTAGE_FLAG);
|
||||
}
|
||||
|
||||
|
||||
static void update_vout(uint16_t sample) {
|
||||
uint16_t vout = average_reg(VOUT_REG, convert_voltage(sample));
|
||||
|
||||
update_shunt_power();
|
||||
|
||||
// Check voltage
|
||||
if (!initialized) return;
|
||||
if ((VOLTAGE_MAX * REG_SCALE) < vout) flags_set(OVER_VOLTAGE_FLAG);
|
||||
flags(MOTOR_UNDER_VOLTAGE_FLAG,
|
||||
vout < (VOLTAGE_MIN * REG_SCALE) && !flags_get(POWER_SHUTDOWN_FLAG));
|
||||
}
|
||||
|
||||
|
||||
static void update_motor_current(uint16_t sample) {
|
||||
update_current(MOTOR_REG, sample);
|
||||
|
||||
// Check overtemp and motor overload
|
||||
if (!initialized) return;
|
||||
bool overtemp = CURRENT_OVERTEMP * REG_SCALE < regs[MOTOR_REG].value;
|
||||
|
||||
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) {
|
||||
if (!flags_get(POWER_SHUTDOWN_FLAG)) 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 (!initialized || flags_get(load->shutdown_flag)) return;
|
||||
|
||||
bool overtemp = CURRENT_OVERTEMP * REG_SCALE < regs[load->reg].value;
|
||||
|
||||
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].value = 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void adc_conversion() {
|
||||
static int i = 0;
|
||||
|
||||
read_conversion(ch_schedule[i]);
|
||||
if (++i == sizeof(ch_schedule)) i = 0;
|
||||
|
||||
// Start next conversion
|
||||
ADMUX = (ADMUX & 0xf0) | ch_schedule[i];
|
||||
ADCSRA |= 1 << ADSC;
|
||||
}
|
||||
|
||||
|
||||
ISR(ADC_vect) {adc_conversion();}
|
||||
|
||||
|
||||
static bool is_within(float a, float b, float tolerance) {
|
||||
return a * (1 - tolerance) < b && b < a * (1 + tolerance);
|
||||
}
|
||||
|
||||
|
||||
static void validate_input_voltage() {
|
||||
int settle = 0;
|
||||
float vlast = 0;
|
||||
|
||||
while (settle < VOLTAGE_SETTLE_COUNT) {
|
||||
delay(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;
|
||||
|
||||
vlast = vin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void charge_caps() {
|
||||
IO_PORT_SET(SHUNT_PIN); // Disable shunt (hi)
|
||||
IO_PORT_SET(MOTOR_PIN); // Motor voltage on
|
||||
delay(CAP_CHARGE_TIME);
|
||||
}
|
||||
|
||||
|
||||
static void shunt_test() {
|
||||
charge_caps();
|
||||
|
||||
// Discharge caps
|
||||
IO_PORT_CLR(MOTOR_PIN); // Motor voltage off
|
||||
IO_PORT_CLR(SHUNT_PIN); // Enable shunt (lo)
|
||||
delay(CAP_CHARGE_TIME);
|
||||
|
||||
if (SHUNT_FAIL_VOLTAGE < get_reg(VOUT_REG)) flags_set(SHUNT_ERROR_FLAG);
|
||||
}
|
||||
|
||||
|
||||
void init() {
|
||||
cli();
|
||||
|
||||
// CPU Clock, disable CKOUT
|
||||
CCP = 0xd8;
|
||||
CLKSR = (1 << CSTR) | (1 << CKOUT_IO) | 0b0010; // 8Mhz internal clock
|
||||
CCP = 0xd8;
|
||||
CLKPR = 0; // div 1
|
||||
while (!((1 << 7) & CLKSR)) continue; // Wait for clock to stabilize
|
||||
|
||||
// Power reduction
|
||||
PRR = (0 << PRADC) | (1 << PRUSART0) | (1 << PRUSART1) | (1 << PRUSI) |
|
||||
(0 << PRTIM0) | (0 << PRTIM1) | (0 << PRTWI);
|
||||
|
||||
// IO
|
||||
IO_PORT_CLR(MOTOR_PIN); // Motor voltage off
|
||||
IO_DDR_SET(MOTOR_PIN); // Output
|
||||
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) |
|
||||
(1 << ADC0D) | (1 << AREFD);
|
||||
DIDR1 = (1 << ADC5D);
|
||||
|
||||
// ADC internal 1.1v, enable, with interrupt, prescale 64
|
||||
// Note, a conversion takes ~200uS
|
||||
ADMUX = (1 << REFS1) | (0 << REFS0);
|
||||
ADCSRA = (1 << ADEN) | (1 << ADIE) |
|
||||
(1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
|
||||
ADCSRB = 0;
|
||||
|
||||
// Timer 0, normal, clk/1
|
||||
TCCR0A = (0 << WGM01) | (0 << WGM00);
|
||||
TCCR0B = (0 << WGM02) | (0 << CS02) | (0 << CS01) | (1 << CS00);
|
||||
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;
|
||||
|
||||
sei();
|
||||
}
|
||||
|
||||
|
||||
static void shutdown() {
|
||||
if (flags_get(POWER_SHUTDOWN_FLAG)) return;
|
||||
flags_set(POWER_SHUTDOWN_FLAG);
|
||||
initialized = false;
|
||||
|
||||
// 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 void validate_measurements() {
|
||||
const float max_voltage = 0.99 * convert_voltage(0x3ff);
|
||||
const float max_current = 0.99 * convert_current(0x3ff);
|
||||
|
||||
if (max_voltage < regs[VOUT_REG].value)
|
||||
flags_set(MOTOR_VOLTAGE_SENSE_ERROR_FLAG);
|
||||
if (max_current < regs[MOTOR_REG].value)
|
||||
flags_set(MOTOR_CURRENT_SENSE_ERROR_FLAG);
|
||||
if (max_current < regs[LOAD1_REG].value)
|
||||
flags_set(LOAD1_SENSE_ERROR_FLAG);
|
||||
if (max_current < regs[LOAD2_REG].value)
|
||||
flags_set(LOAD2_SENSE_ERROR_FLAG);
|
||||
if (max_current < regs[VDD_REG].value)
|
||||
flags_set(VDD_CURRENT_SENSE_ERROR_FLAG);
|
||||
if (flags_get(SENSE_ERROR_FLAGS)) flags_set(SENSE_ERROR_FLAG);
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
regs[VERSION_REG].value = VERSION;
|
||||
|
||||
init();
|
||||
adc_conversion(); // Start ADC
|
||||
validate_input_voltage();
|
||||
shunt_test();
|
||||
charge_caps();
|
||||
validate_measurements();
|
||||
initialized = true;
|
||||
|
||||
while (true) continue;
|
||||
|
||||
return 0;
|
||||
}
|
||||
31
src/pwr/pins.c
Normal file
31
src/pwr/pins.c
Normal file
@@ -0,0 +1,31 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#include "pins.h"
|
||||
|
||||
|
||||
uint16_t io_base[] = {0xf, 0xb, 0x7};
|
||||
63
src/pwr/pins.h
Normal file
63
src/pwr/pins.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/******************************************************************************\
|
||||
|
||||
This file is part of the Buildbotics firmware.
|
||||
|
||||
Copyright (c) 2015 - 2018, Buildbotics LLC
|
||||
All rights reserved.
|
||||
|
||||
This file ("the software") is free software: you can redistribute it
|
||||
and/or modify it under the terms of the GNU General Public License,
|
||||
version 2 as published by the Free Software Foundation. You should
|
||||
have received a copy of the GNU General Public License, version 2
|
||||
along with the software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
The software is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with the software. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
For information regarding this software email:
|
||||
"Joseph Coffland" <joseph@buildbotics.com>
|
||||
|
||||
\******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
enum {PORT_A = 1, PORT_B, PORT_C};
|
||||
enum {IO_REG_PIN, IO_REG_DDR, IO_REG_PORT, IO_REG_PUE};
|
||||
|
||||
#define IO_REG(PIN, REG) _SFR_IO8(io_base[((PIN) >> 3) - 1] + (REG))
|
||||
#define BM(PIN) (1 << ((PIN) & 7))
|
||||
|
||||
#include <avr/io.h>
|
||||
|
||||
extern uint16_t io_base[];
|
||||
|
||||
#define IO_REG_GET(PIN, REG) (!!(IO_REG(PIN, REG) & BM(PIN)))
|
||||
#define IO_REG_SET(PIN, REG) do {IO_REG(PIN, REG) |= BM(PIN);} while (0)
|
||||
#define IO_REG_CLR(PIN, REG) do {IO_REG(PIN, REG) &= ~BM(PIN);} while (0)
|
||||
#define IO_REG_TGL(PIN, REG) do {IO_REG(PIN, REG) ^= BM(PIN);} while (0)
|
||||
|
||||
#define IO_PIN_GET(PIN) IO_REG_GET(PIN, IO_REG_PIN)
|
||||
#define IO_PIN_SET(PIN) IO_REG_SET(PIN, IO_REG_PIN)
|
||||
#define IO_PIN_CLR(PIN) IO_REG_CLR(PIN, IO_REG_PIN)
|
||||
#define IO_PIN_TGL(PIN) IO_REG_TGL(PIN, IO_REG_PIN)
|
||||
|
||||
#define IO_DDR_GET(PIN) IO_REG_GET(PIN, IO_REG_DDR)
|
||||
#define IO_DDR_SET(PIN) IO_REG_SET(PIN, IO_REG_DDR)
|
||||
#define IO_DDR_CLR(PIN) IO_REG_CLR(PIN, IO_REG_DDR)
|
||||
#define IO_DDR_TGL(PIN) IO_REG_TGL(PIN, IO_REG_DDR)
|
||||
|
||||
#define IO_PORT_GET(PIN) IO_REG_GET(PIN, IO_REG_PORT)
|
||||
#define IO_PORT_SET(PIN) IO_REG_SET(PIN, IO_REG_PORT)
|
||||
#define IO_PORT_CLR(PIN) IO_REG_CLR(PIN, IO_REG_PORT)
|
||||
#define IO_PORT_TGL(PIN) IO_REG_TGL(PIN, IO_REG_PORT)
|
||||
|
||||
#define IO_PUE_GET(PIN) IO_REG_GET(PIN, IO_REG_PUE)
|
||||
#define IO_PUE_SET(PIN) IO_REG_SET(PIN, IO_REG_PUE)
|
||||
#define IO_PUE_CLR(PIN) IO_REG_CLR(PIN, IO_REG_PUE)
|
||||
#define IO_PUE_TGL(PIN) IO_REG_TGL(PIN, IO_REG_PUE)
|
||||
Reference in New Issue
Block a user