|
| 1 | +# Copyright (c) 2021 Alethea Katherine Flowers. |
| 2 | +# Published under the standard MIT License. |
| 3 | +# Full text available at: https://opensource.org/licenses/MIT |
| 4 | + |
| 5 | +import json |
| 6 | +import pathlib |
| 7 | +import time |
| 8 | + |
| 9 | +from wintertools.print import print |
| 10 | +from wintertools import oscilloscope, reportcard |
| 11 | + |
| 12 | +from libgemini import gemini, oscillators |
| 13 | + |
| 14 | +SCOPE_SETTLE_TIME = 4 |
| 15 | +OSCILLATOR = 0 |
| 16 | +TEST_NOTE_FREQ = 880 |
| 17 | +MAX_DEVIATION = 0.85 |
| 18 | + |
| 19 | +def get_firmware_and_serial(): |
| 20 | + print("# Firmware & serial") |
| 21 | + |
| 22 | + gem = gemini.Gemini.get() |
| 23 | + fw_version = gem.get_firmware_version() |
| 24 | + serial = gem.get_serial_number() |
| 25 | + |
| 26 | + print(f"Firmware version: {fw_version}") |
| 27 | + print(f"Serial number: {serial}") |
| 28 | + |
| 29 | + REPORT.ulid = serial |
| 30 | + REPORT.sections.append( |
| 31 | + reportcard.Section( |
| 32 | + name="Firmware", |
| 33 | + items=[ |
| 34 | + reportcard.LabelValueItem( |
| 35 | + label="Version", value=fw_version, class_="stack" |
| 36 | + ), |
| 37 | + reportcard.LabelValueItem( |
| 38 | + label="Serial number", value=serial, class_="stack" |
| 39 | + ), |
| 40 | + ], |
| 41 | + ) |
| 42 | + ) |
| 43 | + |
| 44 | +def run(): |
| 45 | + # Calibrates the tuning by indirectly measuring the actual frequency of the |
| 46 | + # SAMD21's internal 8 MHz oscillator. |
| 47 | + |
| 48 | + input("\nConnect a scope to Castor Output (Left hand side). Then turn up the SAW output on Castor (left hand side) and press enter....") |
| 49 | + gem = gemini.Gemini.get() |
| 50 | + print(f"Changing Gemini note to {TEST_NOTE_FREQ} Hz") |
| 51 | + gem.enter_calibration_mode() |
| 52 | + gem.set_osc8m_freq(8_000_000) |
| 53 | + gem.set_frequency(OSCILLATOR, TEST_NOTE_FREQ) |
| 54 | + gem.set_dac(2048, 2048, 2048, 2048) |
| 55 | + |
| 56 | + print(f"Waiting {SCOPE_SETTLE_TIME}s for scope to settle") |
| 57 | + time.sleep(SCOPE_SETTLE_TIME) |
| 58 | + |
| 59 | + # measured_note_freq = scope.get_freq(SCOPE_CHANNEL) |
| 60 | + measured_note_freq = float(input("\nEnter Measured Frequency from scope....")) |
| 61 | + |
| 62 | + print(f"Measured note frequency: {measured_note_freq} Hz") |
| 63 | + |
| 64 | + period = oscillators.frequency_to_timer_period(880) |
| 65 | + measured_clock_freq = round((period + 1) * measured_note_freq) |
| 66 | + |
| 67 | + print(f"Measured clock frequency: {measured_clock_freq:,.0f} Hz") |
| 68 | + |
| 69 | + print("Re-measuring with adjusted clock") |
| 70 | + |
| 71 | + gem.set_osc8m_freq(measured_clock_freq) |
| 72 | + gem.set_frequency(OSCILLATOR, TEST_NOTE_FREQ) |
| 73 | + |
| 74 | + print(f"Waiting {SCOPE_SETTLE_TIME}s for scope to settle") |
| 75 | + time.sleep(SCOPE_SETTLE_TIME) |
| 76 | + |
| 77 | + # post_measured_note_freq = scope.get_freq(SCOPE_CHANNEL) |
| 78 | + post_measured_note_freq = float(input("\nEnter Measured Frequency from scope....")) |
| 79 | + |
| 80 | + passed = abs(TEST_NOTE_FREQ - post_measured_note_freq) < MAX_DEVIATION |
| 81 | + |
| 82 | + print() |
| 83 | + if passed: |
| 84 | + print.success() |
| 85 | + else: |
| 86 | + print.failure() |
| 87 | + print(f"Calibration failed: post_zero_code < 20 or post_max_code < 4075") |
| 88 | + print() |
| 89 | + |
| 90 | + print( |
| 91 | + f"{'✓' if passed else '!!'} Re-measured note frequency: {post_measured_note_freq:.0f} Hz" |
| 92 | + ) |
| 93 | + |
| 94 | + if passed: |
| 95 | + settings = gem.read_settings() |
| 96 | + settings.osc8m_freq = round(measured_clock_freq) |
| 97 | + gem.save_settings(settings) |
| 98 | + print() |
| 99 | + print("✓ Saved to device NVM") |
| 100 | + |
| 101 | + local_copy = pathlib.Path("calibrations") / f"{gem.get_serial_number()}.clock.json" |
| 102 | + local_copy.parent.mkdir(parents=True, exist_ok=True) |
| 103 | + |
| 104 | + with local_copy.open("w") as fh: |
| 105 | + data = { |
| 106 | + "expected_note_frequency": TEST_NOTE_FREQ, |
| 107 | + "measured_note_frequency": measured_note_freq, |
| 108 | + "measured_clock_frequency": measured_clock_freq, |
| 109 | + } |
| 110 | + json.dump(data, fh) |
| 111 | + print(f"[italic]Saved to {local_copy}[/]") |
| 112 | + |
| 113 | + return reportcard.Section( |
| 114 | + name="Tuning", |
| 115 | + items=[ |
| 116 | + reportcard.PassFailItem(label="8 MHz clock", value=passed), |
| 117 | + reportcard.LabelValueItem( |
| 118 | + label="Measured clock", |
| 119 | + value=f"{measured_clock_freq:0,.0f} Hz", |
| 120 | + ), |
| 121 | + reportcard.LabelValueItem( |
| 122 | + label="Measured note", value=f"{measured_note_freq:.3f} Hz" |
| 123 | + ), |
| 124 | + reportcard.LabelValueItem( |
| 125 | + label="Adjusted note", |
| 126 | + value=f"{post_measured_note_freq:0.3f} Hz", |
| 127 | + ), |
| 128 | + reportcard.LabelValueItem( |
| 129 | + label="Tuning error", |
| 130 | + value=f"{abs(TEST_NOTE_FREQ - post_measured_note_freq):0.4f} Hz", |
| 131 | + ), |
| 132 | + ], |
| 133 | + ) |
| 134 | + |
| 135 | + |
| 136 | +if __name__ == "__main__": |
| 137 | + |
| 138 | + print() |
| 139 | + print("> This script calibrates the 8kHz clock by connecting a scope to the Castor output (Left hand side)") |
| 140 | + print("!! Please confirm the following are true, then press ENTER to continue:") |
| 141 | + print("* This machine connected to the main board USB port") |
| 142 | + print("* There is not a drive visible named GEMINIBOOT. If so, please power cycle the main board") |
| 143 | + input() |
| 144 | + |
| 145 | + REPORT = reportcard.Report(name="Castor & Pollux") |
| 146 | + get_firmware_and_serial() |
| 147 | + print() |
| 148 | + |
| 149 | + REPORT.sections.append(run()) |
| 150 | + print(REPORT) |
0 commit comments