def _measure_range(gem, sol_, strategy, sample_count, calibration_points): output = tui.Updateable() bar = tui.Bar() lowest_diff, highest_diff = 0, 0 results = {} with output: for n, (voltage, expected_code) in enumerate(calibration_points.items()): progress = (n + 1) / len(calibration_points) bar.draw( tui.Segment( progress, color=tui.gradient((1.0, 1.0, 1.0), (0.5, 0.5, 1.0), progress), )) log.info(f"{tui.reset}Measuring {voltage:.3f} volts") log.info(f"{tui.reset}expecting: {expected_code}") sol_.send_voltage(voltage, channel=strategy.sol_channel) time.sleep(0.2) samples = [] for s in range(sample_count): samples.append(gem.read_adc(strategy.channel)) result = strategy.post_measure(statistics.mean(samples)) diff = result - expected_code if diff < lowest_diff: lowest_diff = diff if diff > highest_diff: highest_diff = diff if abs(diff) > 300: log.error( f"ADC reading too far out of range. Expected {expected_code}, measured: {result:.1f}, diff: {diff:.1f}" ) log.info(f"{tui.reset}measured: {result:.1f}") log.info( f"{tui.reset}diff: {_color_for_diff(diff)}{diff:.1f}") log.info( f"{tui.reset}lowest diff: {_color_for_diff(lowest_diff)}{lowest_diff:.1f}{tui.reset}, highest_diff: {_color_for_diff(highest_diff)}{highest_diff:.1f}{tui.reset}" ) output.update() results[voltage] = result return results
def run(dry_run=False): castor_calibration_values = {} pollux_calibration_values = {} for filepath in calibration_files: with filepath.open("r") as fh: data = json.load(fh) for key, value in data["castor"].items(): if key not in castor_calibration_values: castor_calibration_values[key] = [value] else: castor_calibration_values[key].append(value) for key, value in data["pollux"].items(): if key not in pollux_calibration_values: pollux_calibration_values[key] = [value] else: pollux_calibration_values[key].append(value) # Print out stats for each calibration point for period in castor_calibration_values.keys(): c_average = statistics.mean(castor_calibration_values[period]) c_stddev = statistics.stdev(castor_calibration_values[period]) p_average = statistics.mean(pollux_calibration_values[period]) p_stddev = statistics.stdev(pollux_calibration_values[period]) c_color = tui.gradient((1.0, 1.0, 1.0), (1.0, 0.0, 0.0), c_stddev / 100) p_color = tui.gradient((1.0, 1.0, 1.0), (1.0, 0.0, 0.0), p_stddev / 100) print( f"Frequency: {_period_reg_to_freq(int(period)):.2f} Hz, period register: {period}:\n" f"- Castor: average: {c_average:.0f}, stddev: {tui.rgb(c_color)}{c_stddev:.0f}{tui.reset}\n" f"- Pollux: average: {p_average:.0f}, stddev: {tui.rgb(p_color)}{p_stddev:.0f}{tui.reset}\n" f"> Difference: {abs(c_average - p_average):.0f}, stddev: {abs(c_stddev - p_stddev):.0f}" ) # Show this data as a bar graph. bar = tui.Bar(width=tui.width()) if c_average < p_average: a_segment = tui.Segment(c_average / 4095, color=(1.0, 0.3, 1.0)) b_segment = tui.Segment((p_average - c_average) / 4095, color=(0.3, 1.0, 1.0)) else: a_segment = tui.Segment(p_average / 4095, color=(0.3, 1.0, 1.0)) b_segment = tui.Segment((c_average - p_average) / 4095, color=(1.0, 0.3, 1.0)) bar.draw(a_segment, b_segment) if dry_run: print("Dry run, not generating new reference calibration.") return # Create a new reference calibration. print("Generating new reference calibration...") with reference_calibration_file.open("w") as fh: fh.write("# This file is generated by libgemini.calibration_stats.") fh.write("castor = {\n") for key, value in castor_calibration_values.items(): fh.write(f" {key}: {int(statistics.mean(value))},\n") fh.write("}\n\n") fh.write("pollux = {\n") for key, value in pollux_calibration_values.items(): fh.write(f" {key}: {int(statistics.mean(value))},\n") fh.write("}\n")
# Published under the standard MIT License. # Full text available at: https://opensource.org/licenses/MIT import argparse import colorsys import dataclasses import json import pathlib import subprocess import typing from wintertools import tui COLUMNS = tui.Columns("<15", ">10", "^5", ">10", ">7") COLUMNS_ALT = tui.Columns("<15", ">10", "<13", ">9") BAR = tui.Bar(width=len(COLUMNS)) FIXED_SEG_COLOR = (255, 158, 221) GRADIENT_START = colorsys.hsv_to_rgb(188 / 360, 0.8, 1.0) GRADIENT_END = colorsys.hsv_to_rgb(0.8, 0.8, 1.0) PLUS_COLOR = (1.0, 1.0, 0.5) MINUS_COLOR = (127, 255, 191) def sizeof_fmt(num, suffix="B"): for unit in ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: if abs(num) < 1024.0: return "%3.2f %s%s" % (num, unit, suffix) num /= 1024.0 return "%.2f %s%s" % (num, "Yi", suffix)
def _calibrate_oscillator(gem, scope, oscillator): bar = tui.Bar() if oscillator == 0: scope_channel = "c1" scope.enable_channel("c1") scope.disable_channel("c2") dac_channel = 0 else: scope_channel = "c2" scope.enable_channel("c2") scope.disable_channel("c1") dac_channel = 2 scope.set_trigger_level(scope_channel, 1) scope.set_cursor_type("Y") scope.set_vertical_cursor(scope_channel, "0V", "3.3V") scope.set_vertical_division(scope_channel, "800mV") scope.set_vertical_offset(scope_channel, "-1.65V") scope.show_measurement(scope_channel, "PKPK") scope.show_measurement(scope_channel, "MAX") scope.set_time_division("10ms") # Wait a moment for the scope to get ready. time.sleep(0.2) last_dac_code = 0 for n, (period, dac_code) in enumerate(period_to_dac_code.items()): progress = n / (len(period_to_dac_code) - 1) if dac_code < last_dac_code: dac_code = last_dac_code # Adjust the oscilloscope's time division as needed. frequency = oscillators.timer_period_to_frequency(period) if frequency > 1200: scope.set_time_division("100us") elif frequency > 700: scope.set_time_division("200us") elif frequency > 400: scope.set_time_division("250us") elif frequency > 250: scope.set_time_division("500us") elif frequency > 120: scope.set_time_division("1ms") elif frequency > 80: scope.set_time_division("2ms") elif frequency > 50: scope.set_time_division("5ms") else: scope.set_time_division("10ms") bar.draw( tui.Segment(progress, color=tui.gradient(start_color, end_color, progress)), ) log.info(f"Calibrating ramp for {frequency=:.2f} Hz {period=}") gem.set_period(oscillator, period) _wait_for_frequency(scope, frequency) calibrated_code = _manual_seek(gem, dac_channel, dac_code) period_to_dac_code[period] = calibrated_code magnitude = _measure_max(scope, scope_channel) log.success( f"Calibrated to {calibrated_code} ({oscillators.charge_code_to_volts(calibrated_code)} volts), magnitude: {magnitude:.2f} volts" ) last_dac_code = calibrated_code return period_to_dac_code.copy()
def run(save): # Oscilloscope setup. log.info("Configuring oscilloscope...") resource_manager = visa.ResourceManager("@ivi") scope = oscilloscope.Oscilloscope(resource_manager) scope.reset() scope.enable_bandwidth_limit() scope.set_intensity(50, 100) # Enable both channels initially so that it's clear if the programming # board isn't connecting to the POGO pins. scope.set_time_division("10ms") scope.enable_channel("c1") scope.enable_channel("c2") scope.set_vertical_cursor("c1", 0, 3.3) scope.set_vertical_cursor("c2", 0, 3.3) scope.set_vertical_division("c1", "800mV") scope.set_vertical_division("c2", "800mV") scope.set_vertical_offset("c1", -1.65) scope.set_vertical_offset("c2", -1.65) # Gemini setup log.info("Connecting to Gemini...") gem = gemini.Gemini() gem.enter_calibration_mode() input( "Connect PROBE ONE to RAMP A\nConnect PROBE TWO to RAMP B\n> press enter to start." ) # Calibrate both oscillators log.section("Calibrating Castor...", depth=2) castor_calibration = _calibrate_oscillator(gem, scope, 0) lowest_voltage = oscillators.charge_code_to_volts( min(castor_calibration.values())) highest_voltage = oscillators.charge_code_to_volts( max(castor_calibration.values())) log.success( f"\nCalibrated:\n- Lowest: {lowest_voltage:.2f}v\n- Highest: {highest_voltage:.2f}v\n" ) log.section("Calibrating Pollux...", depth=2) pollux_calibration = _calibrate_oscillator(gem, scope, 1) lowest_voltage = oscillators.charge_code_to_volts( min(pollux_calibration.values())) highest_voltage = oscillators.charge_code_to_volts( max(pollux_calibration.values())) log.success( f"\nCalibrated:\n- Lowest: {lowest_voltage:.2f}v\n- Highest: {highest_voltage:.2f}v\n" ) log.section("Saving calibration table...", depth=2) local_copy = pathlib.Path( "calibrations") / f"{gem.serial_number}.ramp.json" local_copy.parent.mkdir(parents=True, exist_ok=True) with local_copy.open("w") as fh: data = { "castor": castor_calibration, "pollux": pollux_calibration, } json.dump(data, fh) log.success(f"Saved local copy to {local_copy}") if save: output = tui.Updateable() bar = tui.Bar() log.info("Sending LUT values to device...") with output: for n, timer_period in enumerate(castor_calibration.keys()): progress = n / (len(castor_calibration.values()) - 1) bar.draw( tui.Segment( progress, color=tui.gradient(start_color, end_color, progress), ), ) output.update() castor_code = castor_calibration[timer_period] pollux_code = pollux_calibration[timer_period] gem.write_lut_entry(n, timer_period, castor_code, pollux_code) log.debug( f"Set LUT entry {n} to {timer_period=}, {castor_code=}, {pollux_code=}." ) log.info("Committing LUT to NVM...") gem.write_lut() checksum = 0 for dac_code in castor_calibration.values(): checksum ^= dac_code log.success(f"Calibration table written, checksum: {checksum:04x}") else: log.warning("Dry run enabled, calibration table not saved to device.") gem.close() print("") log.success("Done!")
def _calibrate_oscillator(gem, scope, oscillator): bar = tui.Bar() output = tui.Updateable() if oscillator == 0: scope_channel = "c1" scope.enable_channel("c1") scope.disable_channel("c2") dac_channel = 0 else: scope_channel = "c2" scope.enable_channel("c2") scope.disable_channel("c1") dac_channel = 2 scope.set_trigger_level(scope_channel, 1) scope.show_measurement(scope_channel, "PKPK") scope.show_measurement(scope_channel, "MAX") scope.set_time_division("10ms") # Wait a moment for the scope to get ready. time.sleep(0.2) # Start with the lowest note and manually calibrate it. This will be used # as the reference for other notes. lowest_period = list(period_to_dac_code.keys())[0] start_code = period_to_dac_code[lowest_period] gem.set_period(oscillator, lowest_period) time.sleep(0.2) print( "Calibrate first note: use up and down to change DAC code, press enter to accept." ) with output: while True: gem.set_dac(dac_channel, start_code, 0) print( f"DAC code: {start_code}, voltage: {oscillators.charge_code_to_volts(start_code)}" ) output.update() key = keyboard.read() if key == keyboard.UP: start_code += 1 elif key == keyboard.DOWN: start_code -= 1 elif key == keyboard.ENTER: break period_to_dac_code[lowest_period] = start_code target_voltage = _measure_max(scope, scope_channel) log.info( f"Calibrated {oscillators.timer_period_to_frequency(lowest_period)} to {start_code} with magnitude of {target_voltage}." ) last_dac_code = start_code for n, (period, dac_code) in enumerate(period_to_dac_code.items()): progress = n / (len(period_to_dac_code) - 1) if dac_code < last_dac_code: dac_code = last_dac_code # Adjust the oscilloscope's time division as needed. frequency = oscillators.timer_period_to_frequency(period) if frequency > 500: scope.set_time_division("250us") elif frequency > 300: scope.set_time_division("500us") elif frequency > 150: scope.set_time_division("1ms") elif frequency > 50: scope.set_time_division("5ms") else: scope.set_time_division("10ms") bar.draw( tui.Segment(progress, color=tui.gradient(start_color, end_color, progress)), ) print(f"Frequency: {frequency:.2f} Hz, Period: {period}") period_to_dac_code[period] = last_dac_code = _seek_voltage_on_channel( gem, scope, oscillator, period, dac_code, target_voltage ) return period_to_dac_code.copy()