def main(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument( "--stages", type=str, nargs="*", default=["firmware", "ramp", "adc", "afe"], help="Select which setup stages to run.", ) args = parser.parse_args() if "firmware" in args.stages: program_firmware() if "erase_nvm" in args.stages: erase_nvm() if "ramp" in args.stages: run_ramp_calibration() if "adc" in args.stages: run_adc_calibration() if "afe" in args.stages: run_afe_calibration() log.section("Soft-resetting") gem = gemini.Gemini() gem.soft_reset() log.success("Finished")
def run(adc_resolution, invert, adc_channel, save): if invert: high_expected = 0 low_expected = adc_resolution - 1 else: high_expected = adc_resolution - 1 low_expected = 0 gem = gemini.Gemini() gem.enter_calibration_mode() # Knob calibration is done with error correction enabled. gem.enable_adc_error_correction() input(f"Set knob for channel {adc_channel} all the way CCW and press enter.") samples = [] for s in range(512): samples.append(gem.read_adc(adc_channel)) low_measured = statistics.mean(samples) log.info(f"> Measured {low_measured}, expected {low_expected}") input(f"Set knob for channel {adc_channel} all the way CW and press enter.") samples = [] for s in range(512): samples.append(gem.read_adc(adc_channel)) high_measured = statistics.mean(samples) log.info(f"> Measured {high_measured}, expected {high_expected}") gain_error = (high_expected - low_expected) / (high_measured - low_measured) offset_error = (low_measured * gain_error) - low_expected log.success(f"Knob gain error: {gain_error:.3f}, offset error: {offset_error:.1f}") local_copy = pathlib.Path("calibrations") / f"{gem.serial_number}.knob.json" local_copy.parent.mkdir(parents=True, exist_ok=True) with local_copy.open("w") as fh: json.dump({"gain_error": gain_error, "offset_error": offset_error}, fh) log.success(f"Saved local copy to {local_copy}") if save: settings = gem.read_settings() settings.knob_gain_corr = gain_error settings.knob_offset_corr = offset_error gem.save_settings(settings) log.success("Saved to NVM.") else: log.warning("Dry run, not saved to NVM.") log.success("Done!") gem.close()
def erase_nvm(): log.section("Erasing NVM") gem = gemini.Gemini() gem.erase_lut() log.success("Erased ramp look-up-table.") gem.reset_settings() log.success("Erased user settings.") gem.close()
def run(calibration_points, sample_count, adc_range, adc_resolution, invert, adc_channel, save): voltages = [ n / calibration_points * adc_range for n in range(calibration_points + 1) ] expected_codes = [ int(voltages[n] / adc_range * (adc_resolution - 1)) for n in range(calibration_points + 1) ] if invert: expected_codes = [adc_resolution - 1 - code for code in expected_codes] measured_codes = [] gem = gemini.Gemini() sol_ = sol.Sol() gem.enter_calibration_mode() gem.disable_adc_error_correction() sol_.send_voltage(0) for n in range(calibration_points + 1): voltage = n / calibration_points * adc_range print(f"Measuring {voltage:.3f}, expecting {expected_codes[n]}.") sol_.send_voltage(voltage) time.sleep(0.2) samples = [] for s in range(sample_count): samples.append(gem.read_adc(adc_channel)) result = statistics.mean(samples) print(f"Got {result:.1f}, diff {result - expected_codes[n]:.1f}") measured_codes.append(result) gain_error = adc_errors.calculate_avg_gain_error(expected_codes, measured_codes) offset_error = adc_errors.calculate_avg_offset_error(expected_codes, measured_codes, gain_error) print(f"Measured: Gain: {gain_error:.3f}, Offset: {offset_error:.1f}") corrected = adc_errors.apply_correction(measured_codes, gain_error, offset_error) corrected_gain_error = adc_errors.calculate_avg_gain_error(expected_codes, corrected) corrected_offset_error = adc_errors.calculate_avg_offset_error(expected_codes, corrected, corrected_gain_error) print(f"Corrected: Gain: {corrected_gain_error:.3f}, Offset: {corrected_offset_error:.1f}") if save: gem.set_adc_gain_error(gain_error) gem.set_adc_offset_error(int(offset_error)) print("Saved to NVM.") else: print("Dry run, not saved to NVM.") gem.enable_adc_error_correction()
def run(adc_resolution, invert, adc_channel, save): if invert: high_expected = 0 low_expected = adc_resolution - 1 else: high_expected = adc_resolution - 1 low_expected = 0 gem = gemini.Gemini() gem.enter_calibration_mode() # Knob calibration is done with error correction enabled. gem.enable_adc_error_correction() input( f"Set knob for channel {adc_channel} all the way CCW and press enter.") samples = [] for s in range(128): samples.append(gem.read_adc(adc_channel)) low_measured = statistics.mean(samples) print(f"> Measured {low_measured}, expected {low_expected}") input( f"Set knob for channel {adc_channel} all the way CW and press enter.") samples = [] for s in range(128): samples.append(gem.read_adc(adc_channel)) high_measured = statistics.mean(samples) print(f"> Measured {high_measured}, expected {high_expected}") gain_error = (high_expected - low_expected) / (high_measured - low_measured) offset_error = (low_measured * gain_error) - low_expected print( f"Knob gain error: {gain_error:.3f}, offset error: {offset_error:.1f}") if save: gain_f16 = int(gain_error * 0x10000) offset_f16 = int(offset_error * 0x10000) settings = gem.read_settings() settings.knob_gain_corr = gain_f16 settings.knob_offset_corr = offset_f16 gem.save_settings(settings) print("Saved to NVM.") else: print("Dry run, not saved to NVM.")
def run(save): # Oscilloscope setup. resource_manager = visa.ResourceManager("@ivi") scope = oscilloscope.Oscilloscope(resource_manager) scope.reset() scope.set_vertical_division("c1", 2) scope.set_vertical_cursor("c1", 0, 3.3) scope.set_trigger_level("c1", 1) # Gemini setup gem = gemini.Gemini() gem.enter_calibration_mode() # Calibrate both oscillators print("--------- Calibrating Castor ---------") input("Connect to RAMP A and press enter to start.") castor_calibration = _calibrate_oscillator(gem, scope, 0) lowest_voltage = _code_to_volts(min(castor_calibration.values())) highest_voltage = _code_to_volts(max(castor_calibration.values())) print(f"Lowest voltage: {lowest_voltage:.2f}, Highest: {highest_voltage:.2f}") print("--------- Calibrating Pollux ---------") input("Connect to RAMP B and press enter to start.") pollux_calibration = _calibrate_oscillator(gem, scope, 1) lowest_voltage = _code_to_volts(min(castor_calibration.values())) highest_voltage = _code_to_volts(max(castor_calibration.values())) print(f"Lowest voltage: {lowest_voltage:.2f}, Highest: {highest_voltage:.2f}") if save: print("--------- Saving calibration table ---------") for o, table in enumerate([castor_calibration, pollux_calibration]): for n, dac_code in enumerate(table.values()): print(f"> Set oscillator {o} entry {n} to {dac_code}.") gem.write_lut_entry(n, o, dac_code) print("Writing LUT to NVM") gem.write_lut() checksum = 0 for dac_code in castor_calibration.values(): checksum ^= dac_code print(f"Calibration table written, checksum: {checksum:04x}") else: print("WARNING: Dry run enabled, calibration table not saved.") gem.close() print("Done")
def main(stats=False): gem = gemini.Gemini() _check_firmware_version(gem) settings = gem.read_settings() log.info(settings) gem.enable_monitor() output = tui.Updateable(clear_all=False) seen_states = make_seen_states() with output: while True: update = gem.monitor() track_states(update, seen_states) draw(update, seen_states, stats=stats) output.update()
def run( num_calibration_points, sample_count, strategy, save, ): if strategy == "adc": strategy = DirectADCStrategy() elif strategy == "afe": strategy = ThroughAFEStrategy() else: raise ValueError(f"Unknonw strategy {strategy}.") # Create the list of calibration points and expected codes. voltages = [ n / num_calibration_points * strategy.range_ for n in range(num_calibration_points + 1) ] expected_codes = [ int(voltages[n] / strategy.range_ * (strategy.resolution - 1)) for n in range(num_calibration_points + 1) ] if strategy.invert: expected_codes = [ strategy.resolution - 1 - code for code in expected_codes ] calibration_points = dict(zip(voltages, expected_codes)) gem = gemini.Gemini() sol_ = sol.Sol() sol._setup() sol_.send_voltage(0, channel=strategy.sol_channel) gem.enter_calibration_mode() strategy.setup(gem) measured = _measure_range(gem, sol_, strategy, sample_count, calibration_points) gain_error = adc_errors.calculate_avg_gain_error(expected_codes, list(measured.values())) offset_error = adc_errors.calculate_avg_offset_error( expected_codes, list(measured.values()), gain_error) log.success(f"Measured gain={gain_error:.3f}, offset={offset_error:.1f}") strategy.finish(gem) local_copy = pathlib.Path("calibrations") / strategy.file_name(gem) local_copy.parent.mkdir(parents=True, exist_ok=True) with local_copy.open("w") as fh: json.dump({"gain_error": gain_error, "offset_error": offset_error}, fh) log.info(f"Saved local copy to {local_copy}") if save: strategy.save(gem, gain_error, offset_error) log.success("Saved to NVM.") else: log.warning("Dry run, not saving to NVM.") return # Test out the new calibration log.info("Taking measurements with new calibration...") gem.enable_adc_error_correction() measured = _measure_range(gem, sol_, strategy, sample_count, calibration_points) gain_error = adc_errors.calculate_avg_gain_error(expected_codes, list(measured.values())) offset_error = adc_errors.calculate_avg_offset_error( expected_codes, list(measured.values()), gain_error) log.info(f"Remeasured gain={gain_error:.3f}, offset={offset_error:.1f}") log.success("Done") gem.close()
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_time_division("10ms") 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() # Calibrate both oscillators log.section("Calibrating Castor...", depth=2) input( "Connect PROBE ONE to RAMP A\nConnect PROBE TWO to RAMP B\n> press enter to start." ) castor_calibration = _calibrate_oscillator(gem, scope, 0) lowest_voltage = _code_to_volts(min(castor_calibration.values())) highest_voltage = _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 = _code_to_volts(min(castor_calibration.values())) highest_voltage = _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("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...") for o, table in enumerate([castor_calibration, pollux_calibration]): for n, dac_code in enumerate(table.values()): with output: progress = n / (len(table.values()) - 1) bar.draw( output, tui.Segment( progress, color=tui.gradient(start_color, end_color, progress), ), ) log.debug(f"Set oscillator {o} entry {n} to {dac_code}.") gem.write_lut_entry(n, o, dac_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 main(): start = time.monotonic() gem = gemini.Gemini() gem.enable_monitor() column_size = max(tui.width() / 10, 12) columns = tui.Columns(*[f">{column_size}"] * 4) output = tui.Updateable() with output: while True: update = gem.monitor() columns.draw(output, "", tui.underline, "Castor", "Pollux") columns.draw( output, "CV In │", color_range_cv(update.castor_pitch_cv, 0.0, 6.0), f"{update.castor_pitch_cv:.3f}v", color_range_cv(update.pollux_pitch_cv, 0.0, 6.0), f"{update.pollux_pitch_cv:.3f}v", ) columns.draw( output, "CV Knob │", color_range_bipolar(update.castor_pitch_knob, -1.0, 1.0), f"{update.castor_pitch_knob:+.3f}v", color_range_bipolar(update.pollux_pitch_knob, -1.0, 1.0), f"{update.pollux_pitch_knob:+.3f}v", ) columns.draw( output, "PW In │", color_range(update.castor_pulse_width_cv, 0, 4095), f"{update.castor_pulse_width_cv / 4095 * 100:.0f}%", color_range(update.pollux_pulse_width_cv, 0, 4095), f"{update.pollux_pulse_width_cv / 4095 * 100:.0f}%", ) columns.draw( output, tui.underline, "PW Knob │", color_range(update.castor_pulse_width_knob, 0, 4095), f"{update.castor_pulse_width_knob / 4095 * 100:.0f}%", color_range(update.pollux_pulse_width_knob, 0, 4095), f"{update.pollux_pulse_width_knob / 4095 * 100:.0f}%", ) columns.draw( output, "LFO │", color_range(update.lfo_intensity, 0, 1.0), f"{update.lfo_intensity * 100:.0f}%", ) columns.draw( output, "Button │", color_range_bipolar(int(update.button_state), 0, 1.0), update.button_state, ) columns.draw( output, "Loop time │", f"{update.loop_time}", ) columns.draw( output, "LED time │", f"{update.animation_time}", ) columns.draw( output, "ADC time │", f"{update.sample_time}", ) columns.draw( output, "Runtime │", f"{time.monotonic() - start:.0f}", ) output.update()
import statistics import time import IPython import pyvisa import wintertools.oscilloscope from libgemini import fallback_calibration, gemini, oscillators, reference_calibration _visa_resources_mgr = pyvisa.ResourceManager("@ivi") gem = gemini.Gemini() def set_oscillators_to_note(note, calibration=reference_calibration): freq = oscillators.midi_note_to_frequency(note) period = oscillators.frequency_to_timer_period(freq) charge_code_castor = oscillators.calibrated_charge_code_for_period( period, calibration.castor) charge_code_pollux = oscillators.calibrated_charge_code_for_period( period, calibration.pollux) print( f"Note: {note}, Freq: {freq}, Charge codes: {charge_code_castor}, {charge_code_pollux}" ) gem.set_period(0, period) gem.set_dac(0, charge_code_castor, 0) time.sleep(0.1) # Needed so the DAC has time to update EEPROM gem.set_period(1, period) gem.set_dac(2, charge_code_pollux, 0)
def run( calibration_points, sample_count, adc_range, adc_resolution, invert, adc_channel, save, ): voltages = [ n / calibration_points * adc_range for n in range(calibration_points + 1) ] expected_codes = [ int(voltages[n] / adc_range * (adc_resolution - 1)) for n in range(calibration_points + 1) ] if invert: expected_codes = [adc_resolution - 1 - code for code in expected_codes] measured_codes = [] gem = gemini.Gemini() sol_ = sol.Sol() gem.enter_calibration_mode() gem.disable_adc_error_correction() sol_.send_voltage(0) for n in range(calibration_points + 1): expected = expected_codes[n] voltage = n / calibration_points * adc_range log.info(f"Measuring {voltage:.3f}, expecting {expected}.") sol_.send_voltage(voltage) time.sleep(0.1) samples = [] for s in range(sample_count): samples.append(gem.read_adc(adc_channel)) result = statistics.mean(samples) diff = result - expected_codes[n] if abs(diff) > 100: log.error( "ADC reading too far out of range. Expected {expected}, measured: {result:.1f}, diff: {diff:.1f}" ) log.info(f"Measured {result:.1f}, diff {diff:.1f}") measured_codes.append(result) gain_error = adc_errors.calculate_avg_gain_error(expected_codes, measured_codes) offset_error = adc_errors.calculate_avg_offset_error( expected_codes, measured_codes, gain_error ) log.info(f"Measured: Gain: {gain_error:.3f}, Offset: {offset_error:.1f}") corrected = adc_errors.apply_correction(measured_codes, gain_error, offset_error) corrected_gain_error = adc_errors.calculate_avg_gain_error( expected_codes, corrected ) corrected_offset_error = adc_errors.calculate_avg_offset_error( expected_codes, corrected, corrected_gain_error ) log.success( f"Expected after correction: Gain: {corrected_gain_error:.3f}, Offset: {corrected_offset_error:.1f}" ) local_copy = pathlib.Path("calibrations") / f"{gem.serial_number}.adc.json" local_copy.parent.mkdir(parents=True, exist_ok=True) with local_copy.open("w") as fh: json.dump({"gain_error": gain_error, "offset_error": offset_error}, fh) log.info(f"Saved local copy to {local_copy}") if save: gem.set_adc_gain_error(gain_error) gem.set_adc_offset_error(int(offset_error)) log.success("Saved to NVM.") else: log.warning("Dry run, not saved to NVM.") gem.enable_adc_error_correction() # Test out the new calibrated ADC log.info("Taking measurements with new calibration...") measured_codes = [] for n in range(calibration_points + 1): voltage = n / calibration_points * adc_range log.debug(f"Measuring {voltage:.3f}, expecting {expected_codes[n]}.") sol_.send_voltage(voltage) time.sleep(0.1) samples = [] for s in range(sample_count): samples.append(gem.read_adc(adc_channel)) result = statistics.mean(samples) log.info(f"Measured {result:.1f}, diff {result - expected_codes[n]:.1f}") if abs(diff) > 50: log.error( "ADC reading too far out of range. Expected {expected}, measured: {result:.1f}, diff: {diff:.1f}" ) measured_codes.append(result) gain_error = adc_errors.calculate_avg_gain_error(expected_codes, measured_codes) offset_error = adc_errors.calculate_avg_offset_error( expected_codes, measured_codes, gain_error ) log.success( f"Measured, corrected: Gain: {gain_error:.3f}, Offset: {offset_error:.1f}" ) log.success("Done") gem.close()
def run(save): # Gemini setup log.info("Connecting to Gemini...") gem = gemini.Gemini() gem.enter_calibration_mode() initial_period, initial_dac_code = next(iter(period_to_dac_code.items())) time.sleep(0.1) gem.set_period(0, initial_period) gem.set_dac(0, initial_dac_code, 0) time.sleep(0.1) gem.set_period(1, initial_period) gem.set_dac(2, initial_dac_code, 0) # 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.set_vertical_cursor("c1", 0, 3.3) scope.set_vertical_cursor("c2", 0, 3.3) scope.enable_channel("c1") scope.enable_channel("c2") 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) 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 run(save): # Oscilloscope setup. resource_manager = visa.ResourceManager("@ivi") scope = oscilloscope.Oscilloscope(resource_manager) scope.reset() scope.set_vertical_division("c1", 2) scope.set_vertical_cursor("c1", 0, 3.3) scope.set_trigger_level("c1", 1) # Gemini setup gem = gemini.Gemini() gem.enter_calibration_mode() # Calibrate both oscillators print("--------- Calibrating Castor ---------") input("Connect to RAMP A and press enter to start.") castor_calibration = _calibrate_oscillator(gem, scope, 0) lowest_voltage = _code_to_volts(min(castor_calibration.values())) highest_voltage = _code_to_volts(max(castor_calibration.values())) print( f"Lowest voltage: {lowest_voltage:.2f}, Highest: {highest_voltage:.2f}" ) print("--------- Calibrating Pollux ---------") input("Connect to RAMP B and press enter to start.") pollux_calibration = _calibrate_oscillator(gem, scope, 1) lowest_voltage = _code_to_volts(min(castor_calibration.values())) highest_voltage = _code_to_volts(max(castor_calibration.values())) print( f"Lowest voltage: {lowest_voltage:.2f}, Highest: {highest_voltage:.2f}" ) print("--------- Saving calibration table ---------") 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) print(f"Saved local copy to {local_copy}") if save: for o, table in enumerate([castor_calibration, pollux_calibration]): for n, dac_code in enumerate(table.values()): print(f"> Set oscillator {o} entry {n} to {dac_code}.") gem.write_lut_entry(n, o, dac_code) print("Writing LUT to NVM") gem.write_lut() checksum = 0 for dac_code in castor_calibration.values(): checksum ^= dac_code print(f"Calibration table written, checksum: {checksum:04x}") else: print( "WARNING: Dry run enabled, calibration table not saved to device.") gem.close() print("Done")