mirror of
https://github.com/fairwaves/UHD-Fairwaves.git
synced 2025-10-23 07:42:00 +00:00
260 lines
5.5 KiB
C
260 lines
5.5 KiB
C
/*
|
|
* Copyright 2012 Sylvain Munaut <tnt@246tNt.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* peripheral headers */
|
|
#include "u2_init.h"
|
|
#include "pic.h"
|
|
#include "spi.h"
|
|
#include "time64.h"
|
|
|
|
#include "memory_map.h"
|
|
|
|
/* printf headers */
|
|
#include "nonstdio.h"
|
|
|
|
/* standard headers */
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
|
|
static int gpsdo_debug = 0;
|
|
|
|
|
|
/* DAC */
|
|
/* ---- */
|
|
|
|
#define DAC_BITS 12
|
|
|
|
static uint16_t dac_value; /* Current DAC value */
|
|
|
|
static int
|
|
_set_vctcxo_dac(uint16_t v)
|
|
{
|
|
if (gpsdo_debug) printf("DAC: %d\n", v);
|
|
dac_value = v;
|
|
return spi_transact(
|
|
SPI_TXRX, SPI_SS_DAC,
|
|
v & ((1 << DAC_BITS) - 1),
|
|
16, SPI_PUSH_RISE | SPI_LATCH_RISE
|
|
);
|
|
}
|
|
|
|
static uint16_t
|
|
_get_vctcxo_dac(void)
|
|
{
|
|
return dac_value;
|
|
}
|
|
|
|
/* PID */
|
|
/* --- */
|
|
|
|
#define PID_SCALE_SHIFT 7
|
|
|
|
#define PID_TARGET 52000000 /* 52 MHz */
|
|
#define PID_MAX_ERR 10000 /* 10 kHz off max */
|
|
#define PID_MAX_DEV ((1 << (DAC_BITS-1)) - 1)
|
|
#define PID_MID_VAL (1 << (DAC_BITS-1)) /* 2048 - the middle value of the DAC */
|
|
|
|
#define MAX_INT ((1<<31)-1)
|
|
|
|
typedef struct {
|
|
/* Loop constants */
|
|
int16_t Pk;
|
|
int16_t Ik;
|
|
int16_t Dk;
|
|
|
|
int32_t max_error;
|
|
int32_t max_sum_error;
|
|
|
|
/* Previous value */
|
|
int32_t val_prev;
|
|
|
|
/* State */
|
|
int32_t err_sum;
|
|
} pid_data_t;
|
|
|
|
|
|
static pid_data_t g_pid;
|
|
|
|
static void
|
|
_gpsdo_pid_init(void)
|
|
{
|
|
/* Configure loop */
|
|
g_pid.Pk = 64;
|
|
g_pid.Ik = 16;
|
|
g_pid.Dk = 256; /* Seems high but we LPF PID input so d is dampened */
|
|
|
|
/* Reset state */
|
|
g_pid.val_prev = PID_TARGET;
|
|
g_pid.err_sum = 0;
|
|
}
|
|
|
|
static void
|
|
_gpsdo_pid_step(int32_t val)
|
|
{
|
|
int32_t error;
|
|
int32_t p_term, d_term, i_term;
|
|
int32_t tot;
|
|
|
|
/* Compute error */
|
|
error = PID_TARGET - val;
|
|
|
|
if (error > PID_MAX_ERR)
|
|
error = PID_MAX_ERR;
|
|
else if (error < -PID_MAX_ERR)
|
|
error = -PID_MAX_ERR;
|
|
|
|
/* Compute P term */
|
|
p_term = error * g_pid.Pk;
|
|
|
|
/* Compute I term */
|
|
g_pid.err_sum += error;
|
|
i_term = g_pid.err_sum * g_pid.Ik;
|
|
|
|
/* Compute D term */
|
|
d_term = (g_pid.val_prev - val) * g_pid.Dk;
|
|
g_pid.val_prev = val;
|
|
|
|
/* Final value */
|
|
tot = (p_term + i_term + d_term) >> PID_SCALE_SHIFT;
|
|
|
|
if (tot > PID_MAX_DEV)
|
|
tot = PID_MAX_DEV;
|
|
else if (tot < -PID_MAX_DEV)
|
|
tot = -PID_MAX_DEV;
|
|
|
|
if (gpsdo_debug) printf("GPSDO: correction = %d (P=%d, I=%d, D=%d)>>%d limit +-%d\n", tot, p_term, i_term, d_term, PID_SCALE_SHIFT, PID_MAX_DEV);
|
|
|
|
/* Update DAC */
|
|
_set_vctcxo_dac( PID_MID_VAL + tot );
|
|
}
|
|
|
|
|
|
|
|
/* Driver */
|
|
/* ------ */
|
|
|
|
/* Extra bits of precision for g_val_lpf */
|
|
#define VAL_LPF_PRECISION 3
|
|
#define VAL_LPF_INIT_VALUE (PID_TARGET<<VAL_LPF_PRECISION)
|
|
|
|
static uint32_t g_val_lpf = VAL_LPF_INIT_VALUE;
|
|
static uint32_t g_prev_secs = 0;
|
|
static uint32_t g_prev_ticks = 0;
|
|
static uint32_t g_last_calc_freq = 0; /* Last calculated VCTCXO frequency */
|
|
|
|
static void
|
|
_gpsdo_irq_handler(unsigned irq)
|
|
{
|
|
if (gpsdo_regs->csr & GPSDO_CSR_RDY)
|
|
{
|
|
/* Counter value */
|
|
uint32_t val = gpsdo_regs->cnt;
|
|
/* Read the current wall time */
|
|
uint32_t cur_secs, cur_ticks;
|
|
time64_read(&cur_secs, &cur_ticks);
|
|
|
|
/* Next request */
|
|
gpsdo_regs->csr = GPSDO_CSR_REQ;
|
|
|
|
if (gpsdo_debug) printf("GPSDO: Counter = %u @ %u sec %u ticks\n", val, cur_secs, cur_ticks);
|
|
|
|
/* Check validity of value */
|
|
if (abs(val - PID_TARGET) < 100000)
|
|
{
|
|
/* Save calculated frequency */
|
|
g_last_calc_freq = val;
|
|
|
|
/* LPF the value */
|
|
/* Integer overlow warning! */
|
|
/* This works for val ~= 52M, but don't try to use it with much larger values - it will overflow */
|
|
g_val_lpf = (g_val_lpf * 7 + (val<<VAL_LPF_PRECISION) + 4) >> 3;
|
|
if (gpsdo_debug) printf("GPSDO: Filtered counter = %u + %u/8\n",
|
|
(g_val_lpf>>VAL_LPF_PRECISION), (g_val_lpf&((1<<VAL_LPF_PRECISION)-1)));
|
|
|
|
/* Update PID */
|
|
_gpsdo_pid_step(g_val_lpf>>VAL_LPF_PRECISION);
|
|
}
|
|
|
|
/* Save the current wall time */
|
|
g_prev_secs = cur_secs;
|
|
g_prev_ticks = cur_ticks;
|
|
}
|
|
}
|
|
|
|
void
|
|
gpsdo_init(void)
|
|
{
|
|
/* Set last saved freq to an invalid value */
|
|
g_last_calc_freq = 0;
|
|
|
|
/* Set the DAC to mid value */
|
|
_set_vctcxo_dac( PID_MID_VAL );
|
|
|
|
/* Register IRQ handler */
|
|
pic_register_handler(IRQ_GPSDO, _gpsdo_irq_handler);
|
|
|
|
/* Init PID */
|
|
_gpsdo_pid_init();
|
|
|
|
/* Start request */
|
|
gpsdo_regs->csr = GPSDO_CSR_REQ;
|
|
|
|
/* Save the current wall time.
|
|
* We can use it to estimate time to lock */
|
|
time64_read(&g_prev_secs, &g_prev_ticks);
|
|
}
|
|
|
|
void gpsdo_set_debug(int level)
|
|
{
|
|
gpsdo_debug = level;
|
|
}
|
|
|
|
void gpsdo_set_dac(uint16_t v)
|
|
{
|
|
/* Reset PID */
|
|
_gpsdo_pid_init();
|
|
/* Set the DAC value */
|
|
_set_vctcxo_dac(v);
|
|
}
|
|
|
|
uint16_t gpsdo_get_dac(void)
|
|
{
|
|
return _get_vctcxo_dac();
|
|
}
|
|
|
|
uint32_t gpsdo_get_last_freq(void)
|
|
{
|
|
return g_last_calc_freq;
|
|
}
|
|
|
|
uint32_t gpsdo_get_lpf_freq(void)
|
|
{
|
|
return g_val_lpf;
|
|
}
|
|
|
|
uint32_t gpsdo_get_last_pps_secs(void)
|
|
{
|
|
return g_prev_secs;
|
|
}
|
|
|
|
uint32_t gpsdo_get_last_pps_ticks(void)
|
|
{
|
|
return g_prev_ticks;
|
|
}
|