diff --git a/experimental/.gitignore b/experimental/.gitignore index 9b783e6..eebcf65 100644 --- a/experimental/.gitignore +++ b/experimental/.gitignore @@ -1 +1,3 @@ -calibrations/ \ No newline at end of file +calibrations/ +bootloader.bin +firmware.uf2 diff --git a/experimental/calibrate.py b/experimental/calibrate.py index a761630..216a995 100644 --- a/experimental/calibrate.py +++ b/experimental/calibrate.py @@ -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: @@ -45,7 +47,7 @@ def read_voltage(self): class Sol: - VERBOSE = False + VERBOSE = True DAC_SETTLING_TIME = 0.1 def __init__(self): @@ -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")) @@ -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 @@ -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 = {") @@ -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.") \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/experimental/calibration_cpy_code.py b/experimental/calibration_cpy_code.py index afae20f..beb7145 100644 --- a/experimental/calibration_cpy_code.py +++ b/experimental/calibration_cpy_code.py @@ -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" diff --git a/experimental/deploy.sh b/experimental/deploy.sh deleted file mode 100644 index de78776..0000000 --- a/experimental/deploy.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -cd "$(dirname "$0")"/.. -find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete -rm -rf deploy-bundle -mkdir -p deploy-bundle deploy-bundle/lib deploy-bundle/examples -cp -r /mnt/c/Users/jjram/Desktop/neopixel.mpy deploy-bundle/lib -cp -r firmware/winterbloom_sol deploy-bundle/lib -cp -r firmware/lib/adafruit_circuitpython_busdevice/adafruit_bus_device deploy-bundle/lib/ -cp -r firmware/lib/winterbloom_ad5689/winterbloom_ad5689.py deploy-bundle/lib/ -cp -r firmware/lib/winterbloom_voltageio/winterbloom_voltageio.py deploy-bundle/lib -cp -r firmware/lib/winterbloom_smolmidi/winterbloom_smolmidi.py deploy-bundle/lib -cp -r examples/*.py deploy-bundle/examples -cp -r examples/1_default.py deploy-bundle/code.py \ No newline at end of file