From e76b6432985f19d9c28085c7af0b0afcafae0f7e Mon Sep 17 00:00:00 2001 From: Sylvain Munaut Date: Wed, 12 Jan 2022 22:41:01 +0100 Subject: [PATCH] icE1usb fw: Add GPSDO VCXO trimming loop Rather basic and not super well tested, use with caution Signed-off-by: Sylvain Munaut Change-Id: I5f9ce6621492be967d6a44d31f270e107f3ef686 --- firmware/ice40-riscv/icE1usb/Makefile | 2 + firmware/ice40-riscv/icE1usb/fw_app.c | 11 +- firmware/ice40-riscv/icE1usb/gpsdo.c | 254 ++++++++++++++++++++++++++ firmware/ice40-riscv/icE1usb/gpsdo.h | 16 ++ 4 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 firmware/ice40-riscv/icE1usb/gpsdo.c create mode 100644 firmware/ice40-riscv/icE1usb/gpsdo.h diff --git a/firmware/ice40-riscv/icE1usb/Makefile b/firmware/ice40-riscv/icE1usb/Makefile index a6e9138..25c65d1 100644 --- a/firmware/ice40-riscv/icE1usb/Makefile +++ b/firmware/ice40-riscv/icE1usb/Makefile @@ -49,6 +49,7 @@ HEADERS_app=\ config.h \ e1.h \ gps.h \ + gpsdo.h \ ice1usb_proto.h \ misc.h \ usb_desc_ids.h \ @@ -62,6 +63,7 @@ SOURCES_app=\ e1.c \ fw_app.c \ gps.c \ + gpsdo.c \ misc.c \ usb_desc_app.c \ usb_dev.c \ diff --git a/firmware/ice40-riscv/icE1usb/fw_app.c b/firmware/ice40-riscv/icE1usb/fw_app.c index 78da8b6..c1fe295 100644 --- a/firmware/ice40-riscv/icE1usb/fw_app.c +++ b/firmware/ice40-riscv/icE1usb/fw_app.c @@ -15,6 +15,7 @@ #include "console.h" #include "e1.h" #include "gps.h" +#include "gpsdo.h" #include "led.h" #include "misc.h" #include "mini-printf.h" @@ -94,12 +95,13 @@ void main() pdm_set(PDM_E1_RX1, true, 128 + d, false); #endif - /* Setup clock tuning */ - pdm_set(PDM_CLK_HI, true, 2048, false); - pdm_set(PDM_CLK_LO, false, 0, false); - /* GPS init */ gps_init(); +#if defined(BOARD_ICE1USB_PROTO_ICEBREAKER) || defined(BOARD_ICE1USB_PROTO_BITSY) + gpsdo_init(VCTXO_TAITIEN_VT40); +#else + gpsdo_init(VCTXO_SITIME_SIT3808_E); +#endif /* Enable USB directly */ usb_init(&app_stack_desc); @@ -170,6 +172,7 @@ void main() /* GPS poll */ gps_poll(); + gpsdo_poll(); usb_gps_poll(); } } diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.c b/firmware/ice40-riscv/icE1usb/gpsdo.c new file mode 100644 index 0000000..ca9b806 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/gpsdo.c @@ -0,0 +1,254 @@ +/* + * gpsdo.c + * + * Copyright (C) 2019-2022 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "console.h" +#include "gps.h" +#include "gpsdo.h" +#include "misc.h" + + +struct { + /* Current tuning */ + struct { + uint16_t coarse; + uint16_t fine; + } tune; + + /* Measurements */ + struct { + uint32_t tick_prev; /* Previous tick value */ + uint32_t last; /* Last measurement */ + int skip; /* Number of measurement left to discard */ + int invalid; /* Number of consecutive invalid measurements */ + } meas; + + /* FSM */ + enum { + STATE_DISABLED, /* Forced to manual */ + STATE_HOLD_OVER, /* GPS invalid data, hold */ + STATE_TUNE_COARSE, /* Adjust coarse tuning until we're +- 3 Hz */ + STATE_TUNE_FINE, /* Fine tracking */ + } state; + + /* Coarse tuning */ + struct { + int vctxo_sensitivity; + int step; + } coarse; + + /* Fine tracking */ + struct { + int acc; + int div; + } fine; + +} g_gpsdo; + + +/* VCXTO sensitivity vs 'coarse' count for fast initial acquisition */ + /* + * Note that the spec if often a guaranteed minimum range and goes + * from ~0.1V to 3.2V instead of 0-3.3V so actual sensitivity is + * higher than the "theoritical value". We boost it by ~ 10% here. + */ +static const int +vctxo_sensitivity[] = { + /* +- 50 ppm pull range => ~ 0.75 Hz / hi-count (set to 0.85) */ + [VCTXO_TAITIEN_VT40] = 300, + + /* +- 100 ppm pull range => ~ 1.50 Hz / hi-count (set to 1.6) */ + [VCTXO_SITIME_SIT3808_E] = 160, +}; + +/* Tuning params */ +#define TARGET 30720000 +#define MAX_DEV_VALID 1000 +#define MAX_DEV_FINE 3 +#define MAX_INVALID 5 + + +static void +_gpsdo_coarse_start(void) +{ + /* Set the state */ + g_gpsdo.state = STATE_TUNE_COARSE; + + /* Skip a few measurements to be safe */ + g_gpsdo.meas.skip = 3; + + /* Reset coarse tuning state */ + g_gpsdo.coarse.step = 0; + + /* Put the fine adjust back in the middle point */ + g_gpsdo.tune.coarse += ((int)g_gpsdo.tune.fine - 2048) >> 6; + g_gpsdo.tune.fine = 2048; + + pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false); + pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false); +} + +static void +_gpsdo_fine_start(void) +{ + /* Set the state */ + g_gpsdo.state = STATE_TUNE_FINE; + + /* Reset the long term error tracking */ + g_gpsdo.fine.acc = 0; + g_gpsdo.fine.div = 0; +} + +static void +_gpsdo_coarse_tune(uint32_t tick_diff) +{ + int freq_diff = (int)tick_diff - TARGET; + + /* Is the measurement good enough to switch to fine ? */ + if ((freq_diff > -MAX_DEV_FINE) && (freq_diff < MAX_DEV_FINE)) { + _gpsdo_fine_start(); + return; + } + + /* Estimate correction and apply it */ + g_gpsdo.tune.coarse -= (freq_diff * g_gpsdo.coarse.vctxo_sensitivity) >> 8; + pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false); + + /* Skip next measurement */ + g_gpsdo.meas.skip = 1; + + /* Debug */ +#ifdef GPSDO_DEBUG + printf("[+] GPSDO Coarse: err=%d tune=%d\n", freq_diff, g_gpsdo.tune.coarse); +#endif +} + +static void +_gpsdo_fine_track(uint32_t tick_diff) +{ + int freq_diff = (int)tick_diff - TARGET; + uint16_t tune; + + /* Did we deviate too much ? */ + if ((freq_diff < -2*MAX_DEV_FINE) && (freq_diff > 2*MAX_DEV_FINE)) { + _gpsdo_coarse_start(); + return; + } + + /* Accumulate long term error */ + g_gpsdo.fine.acc += freq_diff; + + /* Update fine tuning value */ + if (((g_gpsdo.fine.acc >= 0) && (freq_diff > 0)) || + ((g_gpsdo.fine.acc <= 0) && (freq_diff < 0))) + { + g_gpsdo.tune.fine -= freq_diff; + } + + if (g_gpsdo.fine.acc) { + if (++g_gpsdo.fine.div > 10) { + g_gpsdo.fine.div = 0; + if (g_gpsdo.fine.acc > 0) + g_gpsdo.tune.fine--; + else if (g_gpsdo.fine.acc < 0) + g_gpsdo.tune.fine++; + } + } else { + g_gpsdo.fine.div = 0; + } + + /* Apply value with a bias from long term accumulator */ + tune = g_gpsdo.tune.fine - (g_gpsdo.fine.acc / 2); + pdm_set(PDM_CLK_LO, true, tune, false); + + /* Debug */ +#ifdef GPSDO_DEBUG + printf("[+] GPSDO Fine: err=%d acc=%d tune=%d\n", freq_diff, g_gpsdo.fine.acc, g_gpsdo.tune.fine); +#endif +} + + +void +gpsdo_poll(void) +{ + uint32_t tick_now, tick_diff; + bool valid; + + /* Get current tick and check if there was a PPS and estimate frequency */ + tick_now = time_pps_read(); + + if (tick_now == g_gpsdo.meas.tick_prev) + return; + + g_gpsdo.meas.last = tick_diff = tick_now - g_gpsdo.meas.tick_prev; + g_gpsdo.meas.tick_prev = tick_now; + + /* If we're currently discarding samples, skip it */ + if (g_gpsdo.meas.skip) { + g_gpsdo.meas.skip--; + return; + } + + /* If we're disabled, nothing else to do */ + if (g_gpsdo.state == STATE_DISABLED) + return; + + /* Check GPS state */ + if (!gps_has_valid_fix()) { + /* No GPS fix, go to hold-over */ + g_gpsdo.state = STATE_HOLD_OVER; + return; + } + + /* Check measurement plausibility */ + valid = (tick_diff > (TARGET - MAX_DEV_VALID)) && (tick_diff < (TARGET + MAX_DEV_VALID)); + + if (valid) { + /* If we're in hold-over, switch to active */ + if (g_gpsdo.state == STATE_HOLD_OVER) { + _gpsdo_coarse_start(); + return; + } + } else { + /* Count invalid measurements and if too many of + * them, we go back to hold-over */ + if (++g_gpsdo.meas.invalid >= MAX_INVALID) { + g_gpsdo.state = STATE_HOLD_OVER; + return; + } + } + + g_gpsdo.meas.invalid = 0; + + /* If we reach here, we have a valid fix, valid measurement and + * we're not in hold-over or disabled. Feed the correct loop */ + if (g_gpsdo.state == STATE_TUNE_COARSE) + _gpsdo_coarse_tune(tick_diff); + else if (g_gpsdo.state == STATE_TUNE_FINE) + _gpsdo_fine_track(tick_diff); +} + + +void +gpsdo_init(enum gpsdo_vctxo_model vctxo) +{ + /* State */ + memset(&g_gpsdo, 0x00, sizeof(g_gpsdo)); + + /* Default tune to middle range */ + g_gpsdo.tune.coarse = 2048; + g_gpsdo.tune.fine = 2048; + + pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false); + pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false); + + /* Initial state and config */ + g_gpsdo.state = STATE_HOLD_OVER; + g_gpsdo.coarse.vctxo_sensitivity = vctxo_sensitivity[vctxo]; +} diff --git a/firmware/ice40-riscv/icE1usb/gpsdo.h b/firmware/ice40-riscv/icE1usb/gpsdo.h new file mode 100644 index 0000000..181e4b9 --- /dev/null +++ b/firmware/ice40-riscv/icE1usb/gpsdo.h @@ -0,0 +1,16 @@ +/* + * gpsdo.h + * + * Copyright (C) 2019-2022 Sylvain Munaut + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +enum gpsdo_vctxo_model { + VCTXO_TAITIEN_VT40 = 0, /* VTEUALJANF-30.720000 */ + VCTXO_SITIME_SIT3808_E = 1, /* SIT3808AI-D2-33EE-30.720000T */ +}; + +void gpsdo_poll(void); +void gpsdo_init(enum gpsdo_vctxo_model vctxo);