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")
Beispiel #3
0
# 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)

Beispiel #4
0
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()
Beispiel #5
0
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()