-
Notifications
You must be signed in to change notification settings - Fork 13
/
cal.py
executable file
·152 lines (128 loc) · 5.21 KB
/
cal.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/bin/python3
# I/O calibration utility for EURORACK-PMOD
#
# INPUT calibration process:
# 1. Compile gateware and program FPGA with:
# - UART_SAMPLE_TRANSMITTER
# - UART_SAMPLE_TRANSMIT_RAW_ADC
# 2. Connect +/- 5V source to all INPUTS
# 3. Run `sudo ./cal.py input`
# 4. Supply 5V, wait for values to settle, hold 'p' to capture
# 5. Supply -5V, wait for values to settle, hold 'n' to capture
# 6. At this point you can try other voltages to make sure the calibration is good
# by looking at the 'back-calculated' values using the generated calibration.
# 7. Press 'x', copy the calibration string to the cal hex file.
# (Input cal is first 8 values, output cal is last 8 values)
#
# OUTPUT calibration process:
# 1. Recompile gateware with input calibration (from above!) and program FPGA with:
# - UART_SAMPLE_TRANSMITTER
# - OUTPUT_CALIBRATION
# - [important!] undef UART_SAMPLE_TRANSMIT_RAW_ADC (this step needs cal samples)
# 2. Loop back all outputs to inputs (1->1, 2->2, ...)
# 3. Run `sudo ./cal.py output`
# 4. Wait for values to settle, hold 'p' to capture
# 5. Hold uButton, wait for values to settle, hold 'n' to capture
# (the uButton switches between the output emitting +/- signals)
# 7. Press 'x', copy the calibration string to the cal hex file.
# (Input cal is first 8 values, output cal is last 8 values)
#
# Note: if you check the output calibration with a multimeter, make sure
# to add a 100K load unless you calibrate with the CAL_OPEN_LOAD option below.
import serial
import sys
import os
import time
import numpy as np
import keyboard
SERIAL_PORT = '/dev/ttyUSB1'
# Input calibration is aiming for N counts per volt
COUNT_PER_VOLT = 4000
# Number of bits in multiply constant for input calibration
MP_N_BITS = 10
# Calibrate outputs such that they are correct if they are driving
# an open load (i.e a multimeter). Normally, the 1K output impedance
# should be driving a 100K input impedance causing a small droop.
# Set this to False for the latter case.
CAL_OPEN_LOAD = True
INPUT_CAL = True if 'input' in sys.argv[1] else False
def twos_comp(val, bits):
"""compute the 2's complement of int value val"""
if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255
val = val - (1 << bits) # compute negative value
return val # return positive value as is
ser = serial.Serial(SERIAL_PORT, 115200)
ch_avg = np.zeros(4)
p5v_avg = np.zeros(4)
n5v_avg = np.zeros(4)
while True:
ser.flushInput()
raw = ser.read(100)
raw = raw[raw.find(b'CH0'):]
print("INPUT" if INPUT_CAL else "OUTPUT", "calibration")
print()
ix = 0
ch_tc_values = np.zeros(4)
while ix < len(raw):
item = raw[ix:ix+5]
if not item.startswith(b'CH') or ix > 15:
break
channel = int(item[2]) - ord('0')
msb = item[3]
lsb = item[4]
value = (msb << 8) | lsb
value_tc = twos_comp(value, 16)
alpha = 0.1
ch_avg[channel] = alpha*value_tc + (1-alpha)*ch_avg[channel]
print(channel, hex(value), value_tc, int(ch_avg[channel]))
ch_tc_values[channel] = value_tc
ix = ix + 5
if keyboard.is_pressed('p'):
p5v_avg = np.copy(ch_avg)
if keyboard.is_pressed('n'):
n5v_avg = np.copy(ch_avg)
print()
print("+5v captured:", p5v_avg)
print("-5v captured:", n5v_avg)
shift_constant = None
mp_constant = None
if INPUT_CAL:
shift_constant = -(n5v_avg + p5v_avg)/2.
mp_constant = 2**MP_N_BITS * COUNT_PER_VOLT * 10./(n5v_avg-p5v_avg)
print("shift_constant:", shift_constant)
print("mp_constant:", mp_constant)
else:
range_constant = (p5v_avg - n5v_avg) / (COUNT_PER_VOLT * 10.)
if CAL_OPEN_LOAD:
# Tweak range constant to remove effect of 100K load impedance.
# (in all cases it is assumed the device is connected in loopback
# mode, all this does is tweak the constants emitted)
range_constant = range_constant * (101./100.)
print("range_constant:", range_constant)
mp_constant = 2**MP_N_BITS / range_constant
print("mp_constant:", mp_constant)
shift_constant = (n5v_avg + p5v_avg)/2.
shift_constant = shift_constant * range_constant
print("shift_constant:", shift_constant)
print()
cal_string = None
if np.isfinite(shift_constant).all() and np.isfinite(mp_constant).all():
cal_string = f"@0000000{'0' if INPUT_CAL else '8'} "
for i in range(4):
cal_string = cal_string + hex(int(shift_constant[i])).replace('0x','') + ' '
cal_string = cal_string + hex(int(mp_constant[i])).replace('0x','') + ' '
print(cal_string)
print()
print("Back-calculated channel values:")
for i in range(4):
back_calc = int(int(mp_constant[i])*
(-ch_tc_values[i]-int(shift_constant[i]))) >> MP_N_BITS
print(i, ch_tc_values[i], "->", back_calc, "(", float(back_calc)/COUNT_PER_VOLT, "V )")
else:
cal_string = None
print("Constants not finite, could not generate calibration string")
print()
if keyboard.is_pressed('x'):
break
time.sleep(0.1)
os.system('clear')