Commit 8c49bb10 authored by Anders Blomdell's avatar Anders Blomdell
Browse files

Preliminary calibration of china-io finished

parent 49421414
*/avr/compiled
*/linux/compiled
*/matlab/*.autosave
*~
\ No newline at end of file
*~
__pycache__
\ No newline at end of file
......@@ -20,6 +20,9 @@ all:
make -C $${d} $@ ; \
done
%.LINK:
make -C $$(./tools/find_avr_targets $*) $@
%.LOAD:
make -C $$(./tools/find_avr_targets $*) $@
......
......@@ -7,7 +7,7 @@ china-io.FUSE=--wr_fuse_l=0x1f --wr_fuse_h=0xd9 --wr_fuse_e=0xff
china-io.FUSE_L=0x1f
china-io.FUSE_H=0xd9
china-io.C=china-io
china-io.H=../lib/serialio i2c_master
china-io.H=../../lib/avr/serialio i2c_master
china-io-test.ARCH=avr
china-io-test.CHIP=atmega32
......
#include <avr/eeprom.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <inttypes.h>
#include "i2c_master.h"
#include "serialio.h"
/*
* Serial I/O assignments:
*
* AI 0 -- Analog In 0
* AI 1 -- Analog In 1
* AI 2 -- Analog In 2
* AI 3 -- Analog In 3
*
* AO 0 -- Analog Out 0
* AO 1 -- Analog Out 1
*
*/
/*
* Used I/O pins
*
* PB4 SPI SS / ADS8694 SPI CS
* PB5 Programming MOSI / ADS8694 SPI SDI
* PB6 Programming MISO / ADS8694 SPI SDO
* PB7 Programming SCK / ADS8694 SPI SCLK
*
* PC0 I2C SCL
* PC1 I2C SDA
*
* PD0 Serial In
* PD1 Serial Out
*
*/
#define DDR_SPI DDRB
#define DD_MISO PB6
#define DD_MOSI PB5
#define DD_SCK PB7
#define DD_SS PB4
#define PORT_SPI PORTB
static volatile uint8_t serial_readchannels;
static volatile uint8_t serial_writechannels;
static volatile uint8_t serial_readconfig;
static volatile struct dac {
uint16_t value[2];
} dac;
static volatile struct adc {
uint32_t value[4];
} adc;
static volatile struct calibration {
uint8_t not_valid; // Non-zero if eeprom not written
struct {
uint16_t offset;
int16_t min;
int16_t max;
} channel[2];
} calibration;
static uint8_t ADS8694_W(
uint8_t length,
const __flash uint8_t *data)
{
DDR_SPI |= (1<<DD_MOSI) | (1<<DD_SCK) | (1<<DD_SS);
SPCR &= (1<<SPIE);
SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPHA);
SPSR = (1<<SPI2X);
PORT_SPI |= (1<<DD_MISO); // Pull up
PORT_SPI &= ~(1<<DD_SS);
for (uint8_t i = 0 ; i < length ; i++) {
SPDR = data[i];
while ((SPSR & (1<<SPIF)) == 0) {}
}
uint8_t dummy = SPDR; // Clear SPIF flag
PORT_SPI |= (1<<DD_SS);
return dummy;
}
/*
* DAC8574 4-channel D/A convert
*/
#define DAC8574ADDR 0x4c
static volatile struct dac8574 {
union dac8574_channel {
uint8_t bytes[3];
struct {
uint8_t control;
uint8_t data[2];
} reg;
} a, b, c, d;
} dac8574 = {
.a={ .reg={ .control=0x10 } },
.b={ .reg={ .control=0x12 } },
.c={ .reg={ .control=0x14 } },
.d={ .reg={ .control=0x16 } },
};
static void dac8574_set_chan(
volatile union dac8574_channel *channel,
int value)
{
channel->reg.data[0] = (value & 0xff00) >> 8;
channel->reg.data[1] = (value & 0x00ff);
}
static const __flash struct i2c_transcation write_all[] = {
I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.a.bytes),
I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.b.bytes),
I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.c.bytes),
I2C_WRITE_RAM(DAC8574ADDR, 3, dac8574.d.bytes),
I2C_END()
};
static void handle_calibration(uint32_t value)
{
unsigned char pos = value & 0x7f;
unsigned char sign = (value >> 7) & 0x01;
int32_t v = value >> 8;
if (sign) {
v = -v;
}
switch (pos) {
case 0: calibration.channel[0].offset = v; break;
case 1: calibration.channel[1].offset = v; break;
case 2: calibration.channel[0].min = v; break;
case 3: calibration.channel[0].max = v; break;
case 4: calibration.channel[1].min = v; break;
case 5: calibration.channel[1].max = v; break;
case 6: eeprom_write_block((void*)&calibration, 0, sizeof(calibration)); break;
}
}
SIGNAL(USART_RXC_vect)
{
char ch = UDR;
switch (serialio_RXC(ch)) {
case serialio_clearbit: {
// Ignored
} break;
case serialio_setbit: {
// Ignored
} break;
case serialio_pollbit: {
// Ignored
} break;
case serialio_pollchannel: {
if (serialio_channel < 4) {
serial_readchannels |= (1<<serialio_channel);
} else if (serialio_channel == 31) {
serial_readconfig = 1;
}
} break;
case serialio_setchannel: {
if (serialio_channel < 2) {
dac.value[serialio_channel] = serialio_value;
serial_writechannels |= (1<<serialio_channel);
} else if (serialio_channel == 31) {
handle_calibration(serialio_value);
}
} break;
case serialio_error: {
} break;
case serialio_more: {
} break;
}
}
SIGNAL(TIMER1_COMPA_vect)
{
unsigned char data[5];
SPCR = (1<<SPE) | (1<<MSTR) | (1<< CPHA);
PORT_SPI &= ~(1<<DD_SS); // Chip select
SPDR = 0x00; // NOP
for (int i = 0 ; i < 5 ; i++) {
while ((SPSR & 0x80) == 0) { }
data[i] = SPDR;
if (i < 4) {
// More to read
SPDR = 0;
}
}
PORT_SPI |= (1<<DD_SS); // Chip deselect
union {
int32_t i;
unsigned char b[4];
} v;
uint8_t chan;
v.i = 0;
v.b[2] = data[2];
v.b[1] = data[3];
v.b[0] = data[4];
chan = (v.i>>2) & 0xf;
adc.value[chan] = (v.i>>6);
}
static uint32_t conf_millivolt(uint32_t value)
{
if (value < 0) {
return CONF_NEGATIVE_MILLIVOLT(value);
} else {
return CONF_POSITIVE_MILLIVOLT(value);
}
}
int main()
{
eeprom_read_block((void*)&calibration, 0, sizeof(calibration));
if (calibration.not_valid) {
calibration.not_valid = 0;
calibration.channel[0].offset = 0x8000;
calibration.channel[0].min = -10000;
calibration.channel[0].max = 10000;
calibration.channel[1].offset = 0x8000;
calibration.channel[1].min = -10000;
calibration.channel[1].max = 10000;
}
/* Setup serial port */
UCSRA = 0x00; // USART:
UCSRB = 0x98; // USART: RxIntEnable|RxEnable|TxEnable
UCSRC = 0x86; // USART: 8bit, no parity
UBRRH = 0; // USART: 115200 @ 14.7456MHz
UBRRL = 7; // USART: 115200 @ 14.7456MHz
/* Setup timer1 */
TCCR1A = 0x00; // Normal port mode (no output)
TCCR1B = 0x09; // Clock / 1
OCR1A = 0x400; // 14.7456MHz / 1024 = 14.4kHz
TIMSK = 0x10; // Enable Timer1 compare A interrupts
/* Setup I2C (TWI), used by DAC8574 */
TWBR = 0x0a; // Max allowed master speed
TWSR = 0x00;
/* Setup DAC8574 (SPI setup is done in ADS8694_W) */
static const __flash uint8_t RST[] = { 0x85, 0x00, 0x00 };
static const __flash uint8_t SEQUENCE[] = { (0x01<<1)|0x01, 0x0f, 0x00 };
static const __flash uint8_t FEATURES[] = { (0x03<<1)|0x01, 0x03, 0x00 };
static const __flash uint8_t AUTO_RST[] = { 0xa0, 0x00, 0x00, 0x00, 0x00 };
ADS8694_W(3, RST);
ADS8694_W(3, SEQUENCE);
ADS8694_W(3, FEATURES);
ADS8694_W(5, AUTO_RST);
/* Setup serialio communication */
serialio_init();
serial_readchannels = 0;
serial_readconfig = 0;
/* Setup DAC values */
dac.value[0] = 0x8000;
dac.value[1] = 0x8000;
sei(); // Global interrupt enable
unsigned char readchannels = 0;
while (1) {
unsigned char config;
struct adc local_adc;
cli(); // Global interrupt disable
if (readchannels == 0) {
readchannels = serial_readchannels;
serial_readchannels = 0;
}
config = serial_readconfig;
serial_readconfig = 0;
local_adc = adc;
sei(); // Global interrupt enable
if (readchannels & 0x01) {
serialio_putchannel(0, local_adc.value[0]);
readchannels &= ~0x01;
} else if (readchannels & 0x02) {
serialio_putchannel(1, local_adc.value[1]);
readchannels &= ~0x02;
} else if (readchannels & 0x04) {
serialio_putchannel(2, local_adc.value[2]);
readchannels &= ~0x04;
} else if (readchannels & 0x08) {
serialio_putchannel(3, local_adc.value[3]);
readchannels &= ~0x08;
}
if (i2c_idle()) {
cli();
dac8574_set_chan(&dac8574.a, calibration.channel[0].offset);
dac8574_set_chan(&dac8574.b, calibration.channel[0].offset);
dac8574_set_chan(&dac8574.c, dac.value[0]);
dac8574_set_chan(&dac8574.d, dac.value[1]);
sei();
i2c_start(write_all);
}
if (config) {
CONF_ANALOG_IN(0, CONF_RESOLUTION(18)); // Analog In 0
CONF_ANALOG_IN(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(1, CONF_RESOLUTION(18)); // Analog In 1
CONF_ANALOG_IN(1, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(1, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(2, CONF_RESOLUTION(18)); // Analog In 2
CONF_ANALOG_IN(2, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(2, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(3, CONF_RESOLUTION(18)); // Analog In 3
CONF_ANALOG_IN(3, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10000)));
CONF_ANALOG_IN(3, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10000)));
CONF_ANALOG_OUT(0, CONF_RESOLUTION(16)); // Analog Out 0
//CONF_ANALOG_OUT(0, CONF_MIN(CONF_NEGATIVE_MILLIVOLT(10000)));
//CONF_ANALOG_OUT(0, CONF_MAX(CONF_POSITIVE_MILLIVOLT(10000)));
CONF_ANALOG_OUT(0, CONF_MIN(conf_millivolt(calibration.channel[0].min)));
CONF_ANALOG_OUT(0, CONF_MAX(conf_millivolt(calibration.channel[0].max)));
CONF_ANALOG_OUT(1, CONF_RESOLUTION(16)); // Analog Out 1
CONF_ANALOG_OUT(1, CONF_MIN(conf_millivolt(calibration.channel[1].min)));
CONF_ANALOG_OUT(1, CONF_MAX(conf_millivolt(calibration.channel[1].max)));
CONF_END();
}
}
}
#!/usr/bin/python3
import sys
import time
import numpy
import traceback
from serialio import SerialIO
import math
class Welford:
def __init__(self, value):
self.k = 0
self.M = 0
self.S = 0
self.update(value)
pass
def update(self, value):
self.k += 1
newM = self.M + (value - self.M) / self.k
newS = self.S + (value - self.M) * (value - newM)
self.M = newM
self.S = newS
def mean(self):
return self.M
def stddev(self):
if self.k==1:
return 0
return math.sqrt(self.S/(self.k-1))
def __repr__(self):
return "%f +- %f" % (self.mean(), self.stddev())
class ChinaIO:
ANALOG_OUT_0 = 0
ANALOG_OUT_1 = 1
AIN_STEP = 20/(1<<18) # 20V, 18 bit resolution
AOUT_STEP = 20/(1<<16) # 20V, 18 bit resolution
def __init__(self, port):
self.io = SerialIO(port)
self.action_msg = ""
self.status_msg = ""
pass
def close(self):
self.io.setchannel(self.ANALOG_OUT_0, 0x8000)
self.io.setchannel(self.ANALOG_OUT_1, 0x8000)
def measure(self, analogIn, N=100, message=''):
time.sleep(0.1)
stat = {}
for b in range(N):
print(message, '[', end='')
for i,c in analogIn.items():
value = (self.io.getchannel(i) - (1<<17)) * self.AIN_STEP
if not i in stat:
stat[i] = Welford(value)
pass
else:
stat[i].update(value)
pass
print('%s ' % stat[i], end='')
pass
print(']', end='\r')
pass
print()
return [ stat[i] for i in sorted(stat.keys()) ]
def setCalibration(self, index, value):
if value > 0:
self.io.setchannel(31, (value << 8) | index & 0x3f)
pass
else:
self.io.setchannel(31, (-value << 8) | 0x80 | index & 0x7f)
pass
pass
def setVoltage(self, channel, value):
self.io.setchannel(channel, int(value/ self.AOUT_STEP + (1<<15)))
def expect(self, analogIn, ref, expect, N, message):
for i,r in zip(range(100), ref):
if r != None:
self.setVoltage(i, r)
pass
pass
time.sleep(0.1) # Let values settle
sample = self.measure(analogIn, N=N, message=message)
for e,v in zip(expect, sample):
if e != None and (v.mean() < e[0] or e[1] < v.mean()):
print(" Expected:", expect)
return False
pass
return True
def calibrate(self):
# Calibration steps:
# 1. Sanity check returned channels
# 2. Set offset calibration to default value
# 3. Read values from grounded inputs
# 4. Connect AOut0 -> AIn0,AIn2 (+9V)
# 5. Connect AOut1 -> AIn1,AIn3 (-9V)
# 6. Zero adjust
# 7. Determine min and max values
# Sanity check
analogIn = self.io.analogIn()
analogOut = self.io.analogOut()
if len(analogIn) != 4:
raise Exception('Did not find 4 analogIn channels')
if len(analogOut) != 2:
raise Exception('Did not find 2 analogOut channels')
for i,c in analogIn.items():
if c.bits != 18:
raise Exception('analogIn[%d] is not 18 bit (%d)' % (i, c.bits))
pass
# Set offset calibration to default value
# self.io.setchannel(31, (0x8000 << 8) | 0x00)
# self.io.setchannel(31, (0x8000 << 8) | 0x00)
self.setCalibration(0, 0x8000)
self.setCalibration(1, 0x8000)
# Read values from grounded inputs
while not self.expect(analogIn,
[ -9, 9 ],
[ (-0.1, 0.1), (-0.1, 0.1),
(-0.1, 0.1), (-0.1, 0.1) ],
N=500,
message='Ground all input pins'):
break
pass
# Connect AOut0 -> AIn0,AIn2 (+9V)
while not self.expect(analogIn,
[ 9, None ],
[ (8.7,9.3), None,
(8.7,9.3), None ],
N=500,
message='Connect: AOut0 -> AIn0, AIn2'):
pass
# Connect AOut1 -> AIn1,AIn3 (-9V)
while not self.expect(analogIn,
[ None, -9 ],
[ None, (-9.3,-8.7),
None, (-9.3,-8.7) ],
N=500,
message='Connect: AOut1 -> AIn1, AIn3'):
pass
# Zero adjust
while not self.expect(analogIn,
[ 0, 0 ],
[ (-0.1, 0.1), (-0.1, 0.1),
(-0.1, 0.1),(-0.1, 0.1) ],
N=500,
message='Calibrating zero'):
pass
# 5. Zero adjust
v0 = 0x8000
v1 = 0x8000
delta = 0x4000
self.io.setchannel(0, 0x8000)
self.io.setchannel(1, 0x8000)
self.setCalibration(0, v0)
self.setCalibration(1, v1)
sample = self.measure(analogIn, N=100, message='Sampling baseline')
def nextv(oldv, delta, sample):
if sample.mean() > 0:
return oldv + delta
else:
return oldv - delta
pass
candidate = [[], []]
while delta >= 1:
v0 = nextv(v0, delta, sample[0])
v1 = nextv(v1, delta, sample[1])
self.setCalibration(0, v0)
self.setCalibration(1, v1)
sample = self.measure(analogIn, N=200,
message='Zero (v0=%x, v1=%x)' % (v0, v1))
candidate[0].append([v0, sample[0]])
candidate[1].append([v1, sample[1]])
delta = delta // 2
pass
best = [ sorted(candidate[0], key=lambda x: abs(x[1].mean())),
sorted(candidate[1], key=lambda x: abs(x[1].mean())) ]
print("Done %x %x : %x %x" % (v0, v1, best[0][0][0], best[1][0][0]))
v0 = best[0][0][0]
v1 = best[0][0][0]
self.setCalibration(0, v0)
self.setCalibration(1, v1)
# Determine min and max values
self.setVoltage(0, -9)
self.setVoltage(1, -9)
min_sample = self.measure(analogIn, N=200, message='Min (-9V)')
self.setVoltage(0, 9)
self.setVoltage(1, 9)
max_sample = self.measure(analogIn, N=200, message='Max (+9V)')
print("0: Min: %f, Max: %f" % (min_sample[0].mean() / 9 * 10,
max_sample[0].mean() / 9 * 10))
print("1: Min: %f, Max: %f" % (min_sample[1].mean() / 9 * 10,
max_sample[1].mean() / 9 * 10))
def millivolt(v):
return int(v * 1000)
self.setCalibration(2, millivolt(min_sample[0].mean() / 9 * 10))
self.setCalibration(3, millivolt(max_sample[0].mean() / 9 * 10))
self.setCalibration(4, millivolt(min_sample[1].mean() / 9 * 10))
self.setCalibration(5, millivolt(max_sample[1].mean() / 9 * 10))
self.setCalibration(6, 0);
pass
def write_calibration(self, index, value):
if value < 0:
tmp = (int(-value * 1000) << 8) | 0x80 | index
else:
tmp = (int(value * 1000) << 8) | index
print("%d %8x" % (index, tmp))
self.io.setchannel(31, tmp, 0xffffffff)
if __name__ == "__main__":
io = ChinaIO(sys.argv[1])
try:
io.calibrate()
except:
traceback.print_exc()