osmo-e1-hardware/firmware/ice40-riscv/icE1usb/gpsdo.c

323 lines
7.4 KiB
C

/*
* 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"
#include "ice1usb_proto.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);
void
gpsdo_get_status(struct e1usb_gpsdo_status *status)
{
const uint8_t state_map[] = {
[STATE_DISABLED] = ICE1USB_GPSDO_STATE_DISABLED,
[STATE_HOLD_OVER] = ICE1USB_GPSDO_STATE_HOLD_OVER,
[STATE_TUNE_COARSE] = ICE1USB_GPSDO_STATE_TUNE_COARSE,
[STATE_TUNE_FINE] = ICE1USB_GPSDO_STATE_TUNE_FINE,
};
const uint8_t ant_map[] = {
[ANT_UNKNOWN] = ICE1USB_GPSDO_ANT_UNKNOWN,
[ANT_OK] = ICE1USB_GPSDO_ANT_OK,
[ANT_OPEN] = ICE1USB_GPSDO_ANT_OPEN,
[ANT_SHORT] = ICE1USB_GPSDO_ANT_SHORT,
};
status->state = state_map[g_gpsdo.state];
status->antenna_status = ant_map[gps_antenna_status()];
status->valid_fix = gps_has_valid_fix();
status->mode = (g_gpsdo.state == STATE_DISABLED) ? ICE1USB_GPSDO_MODE_DISABLED : ICE1USB_GPSDO_MODE_AUTO;
status->tune.coarse = g_gpsdo.tune.coarse;
status->tune.fine = g_gpsdo.tune.fine;
status->freq_est = g_gpsdo.meas.last;
}
void
gpsdo_enable(bool enable)
{
if (!enable)
g_gpsdo.state = STATE_DISABLED;
else if (g_gpsdo.state == STATE_DISABLED)
g_gpsdo.state = STATE_HOLD_OVER;
}
bool
gpsdo_enabled(void)
{
return g_gpsdo.state != STATE_DISABLED;
}
void
gpsdo_set_tune(uint16_t coarse, uint16_t fine)
{
/* Set value */
g_gpsdo.tune.coarse = coarse;
g_gpsdo.tune.fine = fine;
pdm_set(PDM_CLK_HI, true, g_gpsdo.tune.coarse, false);
pdm_set(PDM_CLK_LO, true, g_gpsdo.tune.fine, false);
/* If we were in 'fine' mode, reset to coarse */
if (g_gpsdo.state == STATE_TUNE_FINE)
_gpsdo_coarse_start();
}
void
gpsdo_get_tune(uint16_t *coarse, uint16_t *fine)
{
*coarse = g_gpsdo.tune.coarse;
*fine = g_gpsdo.tune.fine;
}
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];
}