From c8cd7ce46ce2dbb6a67e6d50051720dd849176ef Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 18 Aug 2023 12:09:49 +0200 Subject: [PATCH] Added helper script for pll parameter test --- scripts/calc_pll_errpr.py | 128 ++++++++++++++++++++++++++++++++++++++ scripts/ssb_filter.py | 37 +++++++++++ 2 files changed, 165 insertions(+) create mode 100644 scripts/calc_pll_errpr.py create mode 100644 scripts/ssb_filter.py diff --git a/scripts/calc_pll_errpr.py b/scripts/calc_pll_errpr.py new file mode 100644 index 0000000..24fec49 --- /dev/null +++ b/scripts/calc_pll_errpr.py @@ -0,0 +1,128 @@ +#!/bin/env python3 + +from fractions import Fraction +from math import floor, nan + +import matplotlib.pyplot as plt +import numpy as np + + +BANDS = [ + (1_810_000, 2_000_000), + (3_500_000, 3_800_000), + (5_351_500, 5_366_500), + (7_000_000, 7_200_000), + (10_100_000, 10_150_000), + (14_000_000, 14_350_000), + (18_068_000, 18_168_000), + (21_000_000, 21_450_000), + (24_890_000, 24_990_000), + (28_000_000, 29_700_000) +] + + +XTAL_FREQ = 25_000_000 + +TARGET_PHASE = 90.0 + +# Official limit is 600MHz, however https://rfzero.net/tutorials/si5351a/ claims that 420Mhz is also okay +PLL_MIN = 420_000_000 +PLL_MAX = 900_000_000 + + +def calc_pll_params(in_freq, out_freq): + MAX_DENOM = 0x0FFFFF + + divider = Fraction(floor(in_freq), floor(out_freq)) + divider = divider.limit_denominator(MAX_DENOM) + + a = floor(divider.numerator / divider.denominator) + b = divider.numerator % divider.denominator + c = divider.denominator + + return a, b, c + + +def calc_error(freq, offset): + phase_offset_sec = 1.0 / freq * TARGET_PHASE / 360.0 + target_pll_freq = offset / (4 * phase_offset_sec) + + if target_pll_freq >= PLL_MIN and target_pll_freq <= PLL_MAX: + #print("\tVCO target for %d: %f" % (offset, target_pll_freq / 10**6)) + + pll_a, pll_b, pll_c = calc_pll_params(XTAL_FREQ, target_pll_freq) + pll_actual_freq = XTAL_FREQ / (pll_a + pll_b/pll_c) + + #print("\tVCO actual for %d: %f" % (offset, target_pll_freq / 10**6)) + + ms_a, ms_b, ms_c = calc_pll_params(target_pll_freq, freq) + ms_actual_freq = pll_actual_freq / (ms_a + ms_b/ms_c) + + err = ms_actual_freq - freq + #print("\tError: %f" % (ms_actual_freq - freq)) + + return err, target_pll_freq + + return None, None + + +def main(): + freqs = [] + errs = [] + pll_freqs = [] + phases = [] + + for start, end in BANDS: + print("Band %d - %d" % (start, end)) + + min_err = None + min_err_phase = None + for phase in range(1,128): + err_sum = 0 + for freq in range(start, end, 100): + err, pll_freq = calc_error(freq, phase) + if pll_freq is None: + break + err_sum += err + + if min_err is None or err_sum <= min_err: + min_err = err_sum + min_err_phase = phase + + if min_err_phase is None: + print("\tNo suitable PLL freq found for %d" % freq) + continue + + print("Minimal error phase offset for band: %d" % (min_err_phase)) + + for freq in range(start, end, 1): + err, pll_freq = calc_error(freq, min_err_phase) + if pll_freq is None: + print("\tNo suitable PLL freq found for %d" % freq) + break + + freqs += [freq] + pll_freqs += [pll_freq] + phases += [min_err_phase] + errs += [err] + + freqs += [nan] + pll_freqs += [nan] + phases += [nan] + errs += [nan] + + + freqs = np.array(freqs) / 10**6 + pll_freqs = np.array(pll_freqs) / 10**6 + phases = np.array(phases) + errs = np.abs(np.array(errs)) + + fig, (ax1, ax2, ax3) = plt.subplots(3, 1) + ax1.plot(freqs, phases) + ax2.plot(freqs, pll_freqs) + ax3.plot(freqs, errs) + plt.show() + + +if __name__ == '__main__': + main() diff --git a/scripts/ssb_filter.py b/scripts/ssb_filter.py new file mode 100644 index 0000000..a31fdac --- /dev/null +++ b/scripts/ssb_filter.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 + +from scipy import signal +import matplotlib.pyplot as plt +import numpy as np + + +def main(): + fs = 6400.0 + coeffs = signal.firls(63, (0, 1150, 1200, fs/2), (1, 1, 0, 0), fs=fs) + + freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512) + freqs, response = signal.freqz(coeffs, worN=freq_space) + response = 10 * np.log(abs(response)) + plt.plot(fs/2*freqs/(np.pi), response) + plt.grid() + plt.show() + + f0 = (1200 + 50) / fs + complex_coeffs = [] + for n in range(0, len(coeffs)): + complex_coeffs += [coeffs[n] * np.exp(1j * 2 * np.pi * f0 * n)] + complex_coeffs = np.array(complex_coeffs) + + print(complex_coeffs) + + freq_space = np.linspace(-fs/2 / (fs/2)*np.pi, fs/2 / (fs/2)*np.pi, 512) + freqs, response = signal.freqz(complex_coeffs, worN=freq_space) + response = 10 * np.log(abs(response)) + plt.plot(fs/2*freqs/(np.pi), response) + plt.grid() + plt.show() + + + +if __name__ == '__main__': + main() \ No newline at end of file