def enter_calibration_mode(self): self.get_firmware_version() log.info(f"Gemini version: {self.version}") self.get_serial_number() log.info(f"Serial number: {self.serial_number}") self.sysex(SysExCommands.ENTER_CALIBRATION)
def enter_calibration_mode(self): resp = self._sysex(SysExCommands.HELLO, response=True) self.version = bytearray(resp[3:-1]).decode("ascii") log.info(f"Gemini version: {self.version}") resp = self._sysex(SysExCommands.GET_SERIAL_NUMBER, response=True) self.serial_number = teeth.teeth_decode(resp[3:-1]).hex() log.info(f"Serial number: {self.serial_number}")
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 _check_firmware_version(gem): latest_release = git.latest_tag() build_id = gem.get_firmware_version() log.info(f"Firmware build ID: {build_id}") if latest_release in build_id: return log.warning("Firmware is out of date, updating it..") gem.reset_into_bootloader() path = pathlib.Path(fs.wait_for_drive("GEMINIBOOT", timeout=60 * 5)) fs.copyfile("../firmware/build/gemini-firmware.uf2", path / "firmware.uf2") fs.flush(path) time.sleep(3) log.success("Firmware updated!")
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(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 _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()
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()