icE1usb fw: Add GPSDO VCXO trimming loop
Rather basic and not super well tested, use with caution Signed-off-by: Sylvain Munaut <tnt@246tNt.com> Change-Id: I5f9ce6621492be967d6a44d31f270e107f3ef686
This commit is contained in:
parent
70c10f05cf
commit
e76b643298
|
@ -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 \
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* gpsdo.c
|
||||
*
|
||||
* Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#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];
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* gpsdo.h
|
||||
*
|
||||
* Copyright (C) 2019-2022 Sylvain Munaut <tnt@246tNt.com>
|
||||
* 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);
|
Loading…
Reference in New Issue