Skip to content

Commit

Permalink
Finish factory setup script
Browse files Browse the repository at this point in the history
  • Loading branch information
theacodes committed May 22, 2020
1 parent 8a0f687 commit 2b40b1f
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 107 deletions.
4 changes: 3 additions & 1 deletion experimental/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
calibrations/
calibrations/
bootloader.bin
firmware.uf2
182 changes: 89 additions & 93 deletions experimental/calibrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
import io
import subprocess

import utils


METER_RESOURCE_NAME = "USB0::0x05E6::0x6500::04450405::INSTR"
SOL_USB_DEVICE_ID = "239A:8062"
METER_AVERAGE_COUNT = 1 # 50 for production
METER_AVERAGE_COUNT = 20 # 50 for production
MILIVOLTS_PER_CODE = 13 / (2**16) * 1000

class Meter:
Expand Down Expand Up @@ -45,7 +47,7 @@ def read_voltage(self):


class Sol:
VERBOSE = False
VERBOSE = True
DAC_SETTLING_TIME = 0.1

def __init__(self):
Expand All @@ -59,20 +61,12 @@ def _connect(self):
timeout=1)

def reset(self):
time.sleep(2)
self.port.write(b'\x03')
time.sleep(2)
self.port.write(b'\x04')
while True:
for n in range(10):
line = self.port.readline().decode("utf-8").strip()
if self.VERBOSE:
print("Sol: ", line)
if line == "ready":
break
if line.startswith("Press any key to enter the REPL. Use CTRL-D to reload."):
time.sleep(1)
self.port.write(b'\x04')
pass

def call(self, expr):
self.port.write(f"{expr}\r\n".encode("utf-8"))
Expand All @@ -87,6 +81,9 @@ def call(self, expr):
if line.startswith("Traceback"):
error = self.port.read(size=500).decode("utf-8")
raise RuntimeError(f"Error while interacting with Sol: {error}")
if line.startswith("NameError"): # The repl didn't pick up the command.
error = self.port.read(size=500).decode("utf-8")
return call(self, expr)

if not line.startswith(expr):
output += line
Expand All @@ -112,35 +109,27 @@ def write_calibration_to_nvm(self, calibration_data):


def find_circuitpython_drive():
drives = win32api.GetLogicalDriveStrings()
drives = drives.split('\000')[:-1]
for drive in drives:
info = win32api.GetVolumeInformation(drive)
if info[0] == "CIRCUITPY":
return drive
raise RuntimeError("No circuitpython drive found.")


def copyfile(src, dst):
# shutil can be a little wonky, so do this manually.
with open(src, "r") as fh:
contents = fh.read()

with open(dst, "w") as fh:
fh.write(contents)
fh.flush()

drive, _ = os.path.splitdrive(dst)
subprocess.run(["sync", drive], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return utils.find_drive_by_name("CIRCUITPY")


def copy_calibration_script(circuitpython_drive):
print(f"Copying calibration script to {circuitpython_drive}")
copyfile(
if not os.path.exists(os.path.join(circuitpython_drive, "code-bak.py")):
utils.copyfile(
os.path.join(circuitpython_drive, "code.py"),
os.path.join(circuitpython_drive, "code-bak.py"))
utils.copyfile(
os.path.join(os.path.dirname(__file__), "calibration_cpy_code.py"),
os.path.join(circuitpython_drive, "code.py"))


def restore_code_py(circuitpython_drive):
utils.copyfile(
os.path.join(circuitpython_drive, "code-bak.py"),
os.path.join(circuitpython_drive, "code.py"))
os.unlink(os.path.join(circuitpython_drive, "code-bak.py"))


def generate_calibration_file(channel_calibrations):
buf = io.StringIO()
buf.write("calibration = {")
Expand All @@ -150,77 +139,84 @@ def generate_calibration_file(channel_calibrations):
return buf.getvalue()


circuitpython_drive = find_circuitpython_drive()
resource_manager = visa.ResourceManager("@ni")
meter = Meter(resource_manager)
sol = Sol()
copy_calibration_script(circuitpython_drive)
time.sleep(5) # Wait a few second for circuitpython to maybe reload.
sol.reset()
cpu_id = sol.get_cpu_id()

print(f"Sol CPU ID: {cpu_id}")

channel_calibrations = {}
channel_voltages = {}

try:
for channel in ("a", "b", "c", "d"):
print(f"========= Channel {channel} =========")
input(f"Connect to channel {channel}, press enter when ready.")
calibration_values = {}
channel_voltages[channel] = {}
def main():
circuitpython_drive = find_circuitpython_drive()
resource_manager = visa.ResourceManager("@ni")
meter = Meter(resource_manager)
sol = Sol()
copy_calibration_script(circuitpython_drive)
time.sleep(3) # Wait a few second for circuitpython to maybe reload.
sol.reset()
cpu_id = sol.get_cpu_id()

print(f"Sol CPU ID: {cpu_id}")

channel_calibrations = {}
channel_voltages = {}

try:
for channel in ("a", "b", "c", "d"):
print(f"========= Channel {channel} =========")
input(f"Connect to channel {channel}, press enter when ready.")
calibration_values = {}
channel_voltages[channel] = {}

for step in range(16):
dac_code = int((2**16 - 1) * step / 15)
sol.set_dac(channel, dac_code)
voltage = meter.read_voltage()
calibration_values[voltage] = dac_code
print(f"DAC code: {dac_code}, Voltage: {voltage}")

sol.set_calibration(channel, calibration_values)
channel_calibrations[channel] = calibration_values

for desired_voltage in range(-5, 9):
sol.set_voltage(channel, desired_voltage)
measured_voltage = meter.read_voltage()
channel_voltages[channel][desired_voltage] = measured_voltage
print(f"Desired voltage: {desired_voltage}, Measured voltage: {measured_voltage}")

for step in range(16):
dac_code = int((2**16 - 1) * step / 15)
sol.set_dac(channel, dac_code)
voltage = meter.read_voltage()
calibration_values[voltage] = dac_code
print(f"DAC code: {dac_code}, Voltage: {voltage}")

calibration_file_contents = generate_calibration_file(channel_calibrations)
sol.write_calibration_to_nvm(calibration_file_contents)

sol.set_calibration(channel, calibration_values)
channel_calibrations[channel] = calibration_values
finally:
meter.close()

for desired_voltage in range(-5, 9):
sol.set_voltage(channel, desired_voltage)
measured_voltage = meter.read_voltage()
channel_voltages[channel][desired_voltage] = measured_voltage
print(f"Desired voltage: {desired_voltage}, Measured voltage: {measured_voltage}")
print("========= Stats =========")
for channel, voltages in channel_voltages.items():
if not voltages:
continue


calibration_file_contents = generate_calibration_file(channel_calibrations)
sol.write_calibration_to_nvm(calibration_file_contents)
print(f"Channel {channel}:")
differences = [abs(desired - measured) for desired, measured in voltages.items()]
avg = statistics.mean(differences) * 1000
dev = statistics.stdev(differences) * 1000
worst = max(differences) * 1000
best = min(differences) * 1000
print(f"Average: {avg:.3f} mV ({avg / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Std. dev: {dev:.3f} mV ({dev / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Worst: {worst:.3f} mV ({worst / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Best: {best:.3f} mV ({best / MILIVOLTS_PER_CODE:.0f} lsb)")

finally:
meter.close()

print("========= Stats =========")
for channel, voltages in channel_voltages.items():
if not voltages:
continue
print(f"Saving calibration to calibrations/{cpu_id} and {circuitpython_drive}")

print(f"Channel {channel}:")
differences = [abs(desired - measured) for desired, measured in voltages.items()]
avg = statistics.mean(differences) * 1000
dev = statistics.stdev(differences) * 1000
worst = max(differences) * 1000
best = min(differences) * 1000
print(f"Average: {avg:.3f} mV ({avg / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Std. dev: {dev:.3f} mV ({dev / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Worst: {worst:.3f} mV ({worst / MILIVOLTS_PER_CODE:.0f} lsb)")
print(f"Best: {best:.3f} mV ({best / MILIVOLTS_PER_CODE:.0f} lsb)")
calibration_file_path = os.path.join(os.path.dirname(__file__), 'calibrations', f'{cpu_id}.py')

with open(calibration_file_path, "w") as fh:
fh.write("# This is generated by the factory when assembling your\n")
fh.write("# device. Do not remove or change this.\n\n")
fh.write(calibration_file_contents)
fh.flush()

print(f"Saving calibration to calibrations/{cpu_id} and {circuitpython_drive}")
utils.copyfile(calibration_file_path, os.path.join(circuitpython_drive, "calibration.py"))

calibration_file_path = os.path.join(os.path.dirname(__file__), 'calibrations', f'{cpu_id}.py')
restore_code_py(circuitpython_drive)

with open(calibration_file_path, "w") as fh:
fh.write("# This is generated by the factory when assembling your\n")
fh.write("# device. Do not remove or change this.\n\n")
fh.write(calibration_file_contents)
fh.flush()
print("Done.")

copyfile(calibration_file_path, os.path.join(circuitpython_drive, "calibration.py"))

print("Done.")
if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions experimental/calibration_cpy_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ def get_cpu_id():
print(''.join('{:02x}'.format(x) for x in microcontroller.cpu.uid))


# This is a dumb hack but it gets around a weird bug with calling the first function
# from the calibration program. :shrug:
et_cpu_id = get_cpu_id


def write_calibration_to_nvm(calibration_data):
calibration_data = calibration_data.encode("utf-8")
microcontroller.nvm[0:2] = b"\x69\x69"
Expand Down
13 changes: 0 additions & 13 deletions experimental/deploy.sh

This file was deleted.

0 comments on commit 2b40b1f

Please sign in to comment.