Configure sample rate more precisely

This commit is contained in:
Michael Ossmann
2026-03-01 10:02:51 -05:00
parent bb670f9af7
commit 08964c7fb7
5 changed files with 145 additions and 229 deletions

View File

@@ -190,7 +190,7 @@ bool fpga_if_xcvr_selftest(void)
max2831_set_frequency(&max283x, 2500000000);
// Capture 1: 4 Msps, tone at 0.5 MHz, narrowband filter OFF
sample_rate_frac_set(4000000, 1);
sample_rate_set(4ULL * FP_ONE_MHZ, true);
delay_us_at_mhz(1000, 204);
if (rx_samples(num_samples, 2000000) == -1) {
timeout = true;
@@ -213,7 +213,7 @@ bool fpga_if_xcvr_selftest(void)
// Capture 3: 20 Msps, tone at 5 MHz, narrowband filter OFF
fpga_set_tx_nco_pstep(&fpga, 255);
sample_rate_frac_set(20000000, 1);
sample_rate_set(20ULL * FP_ONE_MHZ, true);
narrowband_filter_set(0);
delay_us_at_mhz(1000, 204);
if (rx_samples(num_samples, 2000000) == -1) {
@@ -236,7 +236,7 @@ bool fpga_if_xcvr_selftest(void)
&selftest.xcvr_measurements[3]);
// Restore default settings.
sample_rate_set(10000000);
sample_rate_set(10ULL * FP_ONE_MHZ, true);
rf_path_set_direction(&rf_path, RF_PATH_DIRECTION_OFF);
narrowband_filter_set(0);
fpga_init(&fpga);

View File

@@ -446,16 +446,26 @@ static uint32_t gcd(uint32_t u, uint32_t v)
return u << s;
}
bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
/*
* Configure clock generator to produce sample clock in units of 1/(2**24) Hz.
* Can be called with program=false for a dry run that returns the resultant
* frequency without actually configuring the clock generator.
*
* The clock generator output frequency is:
*
* fs = 128 * vco / (512 + p1 + p2/p3))
*
* where p1, p2, and p3 are register values.
*
* For more information see:
* https://www.pa3fwm.nl/technotes/tn42a-si5351-programming.html
*/
fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program)
{
const uint64_t VCO_FREQ = 800 * 1000 * 1000; /* 800 MHz */
uint32_t MSx_P1, MSx_P2, MSx_P3;
uint32_t a, b, c;
uint32_t rem;
/* Round to the nearest Hz for display. */
uint32_t rate_hz = (rate_num + (rate_denom >> 1)) / rate_denom;
hackrf_ui()->set_sample_rate(rate_hz);
const fp_40_24_t vco = 800 * FP_ONE_MHZ;
uint64_t p1, p2, p3;
uint64_t n, d, q;
fp_40_24_t remainder, resultant_rate;
/*
* First double the sample rate so that we can produce a clock at twice
@@ -463,36 +473,72 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
* and it is divided by two in an output divider to produce the actual
* AFE clock.
*/
rate_num *= 2;
fp_40_24_t rate = sample_rate * 2;
/* Find best config */
a = (VCO_FREQ * rate_denom) / rate_num;
p1 = ((128 * vco) / rate) - 512;
if (vco % rate) {
/*
* Compute numerator (p2) for denominator (p3) matching
* fixed-point type.
*/
n = (128 * vco) - (rate * (p1 + 512));
d = rate / FP_ONE_HZ;
n += (d / 2);
p2 = n / d;
p3 = 1 << 24;
rem = (VCO_FREQ * rate_denom) - (a * rate_num);
/* Reduce fraction. */
uint32_t g = gcd(p2, p3);
p2 /= g;
p3 /= g;
if (!rem) {
/* Integer mode */
b = 0;
c = 1;
} else {
/* Fractional */
uint32_t g = gcd(rem, rate_num);
rem /= g;
rate_num /= g;
if (rate_num < (1 << 20)) {
/* Perfect match */
b = rem;
c = rate_num;
} else {
/* Approximate */
c = (1 << 20) - 1;
b = ((uint64_t) c * (uint64_t) rem) / rate_num;
g = gcd(b, c);
b /= g;
c /= g;
/* Convert fraction to valid denominator. */
const uint64_t p3_max = 0xfffff;
if (p3 > p3_max) {
p2 *= p3_max;
p2 += (p3 / 2);
p2 /= p3;
p3 = p3_max;
}
/* Roll over to next p1 to enable integer mode. */
if (p2 >= p3) {
p1++;
p2 = 0;
}
} else {
p2 = 0;
}
/* Maximum: (128 * 2048) - 512 */
if (p1 > 0x3fe00) {
p1 = 0x3fe00;
p2 = 0;
}
if (p2 == 0) {
/* Use unity denominator for integer mode. */
p3 = 1;
n = (vco * 128);
d = (p1 + 512);
n += (d / 2);
resultant_rate = n / d;
} else {
const uint64_t vco_hz = vco / FP_ONE_HZ;
n = p3 * vco_hz * 128;
d = p3 * (p1 + 512) + p2;
const uint64_t rate_hz = n / d;
remainder = (n - (d * rate_hz)) * FP_ONE_HZ;
remainder += (d / 2);
q = remainder / d;
resultant_rate = (rate_hz * FP_ONE_HZ) + q;
}
/* Return MCU sample rate, not AFE clock rate. */
resultant_rate = (resultant_rate + 1) / 2;
if (!program) {
return resultant_rate;
}
bool streaming = sgpio_cpld_stream_is_enabled(&sgpio_config);
@@ -501,103 +547,13 @@ bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom)
sgpio_cpld_stream_disable(&sgpio_config);
}
/* Can we enable integer mode ? */
if (a & 0x1 || b) {
/* Integer mode can be enabled if p1 is even and p2 is zero. */
if (p1 & 0x1 || p2) {
si5351c_set_int_mode(&clock_gen, 0, 0);
} else {
si5351c_set_int_mode(&clock_gen, 0, 1);
}
/* Final MS values */
MSx_P1 = 128 * a + (128 * b / c) - 512;
MSx_P2 = (128 * b) % c;
MSx_P3 = c;
#ifndef PRALINE
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
* On HackRF One r9 all sample clocks are externally derived
* from MS1/CLK1 operating at twice the sample rate.
*/
si5351c_configure_multisynth(&clock_gen, 1, MSx_P1, MSx_P2, MSx_P3, 0);
} else {
/*
* On other platforms the clock generator produces three
* different sample clocks, all derived from multisynth 0.
*/
/* MS0/CLK0 is the source for the MAX5864/CPLD (CODEC_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, MSx_P1, MSx_P2, MSx_P3, 1);
/* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */
si5351c_configure_multisynth(&clock_gen, 1, 0, 0, 0, 0); //p1 doesn't matter
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter
}
#else
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, MSx_P1, MSx_P2, MSx_P3, 1);
#endif
if (streaming) {
sgpio_cpld_stream_enable(&sgpio_config);
}
return true;
}
bool sample_rate_set(const uint32_t sample_rate_hz)
{
uint32_t p1 = 4608;
uint32_t p2 = 0;
uint32_t p3 = 0;
switch (sample_rate_hz) {
case 8000000:
p1 = SI_INTDIV(50); // 800MHz / 50 = 16 MHz (SGPIO), 8 MHz (codec)
break;
case 9216000:
// 43.40277777777778: a = 43; b = 29; c = 72
p1 = 5043;
p2 = 40;
p3 = 72;
break;
case 10000000:
p1 = SI_INTDIV(40); // 800MHz / 40 = 20 MHz (SGPIO), 10 MHz (codec)
break;
case 12288000:
// 32.552083333333336: a = 32; b = 159; c = 288
p1 = 3654;
p2 = 192;
p3 = 288;
break;
case 12500000:
p1 = SI_INTDIV(32); // 800MHz / 32 = 25 MHz (SGPIO), 12.5 MHz (codec)
break;
case 16000000:
p1 = SI_INTDIV(25); // 800MHz / 25 = 32 MHz (SGPIO), 16 MHz (codec)
break;
case 18432000:
// 21.70138888889: a = 21; b = 101; c = 144
p1 = 2265;
p2 = 112;
p3 = 144;
break;
case 20000000:
p1 = SI_INTDIV(20); // 800MHz / 20 = 40 MHz (SGPIO), 20 MHz (codec)
break;
default:
return false;
}
#ifndef PRALINE
if (detected_platform() == BOARD_ID_HACKRF1_R9) {
/*
@@ -614,32 +570,21 @@ bool sample_rate_set(const uint32_t sample_rate_hz)
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
/* MS0/CLK1 is the source for the CPLD (CODEC_X2_CLK). */
si5351c_configure_multisynth(
&clock_gen,
1,
p1,
0,
1,
0); //p1 doesn't matter
si5351c_configure_multisynth(&clock_gen, 1, 0, 0, 0, 0); //p1 doesn't matter
/* MS0/CLK2 is the source for SGPIO (CODEC_X2_CLK) */
si5351c_configure_multisynth(
&clock_gen,
2,
p1,
0,
1,
0); //p1 doesn't matter
si5351c_configure_multisynth(&clock_gen, 2, 0, 0, 0, 0); //p1 doesn't matter
}
#else
/* MS0/CLK0 is the source for the MAX5864/FPGA (AFE_CLK). */
si5351c_configure_multisynth(&clock_gen, 0, p1, p2, p3, 1);
/* MS0/CLK1 is the source for SCT_CLK (CODEC_X2_CLK). */
si5351c_configure_multisynth(&clock_gen, 1, p1, p2, p3, 0);
#endif
return true;
if (streaming) {
sgpio_cpld_stream_enable(&sgpio_config);
}
return resultant_rate;
}
/*
@@ -926,7 +871,7 @@ void clock_gen_init(void)
/* MS7/CLK7 is unused. */
/* Set to 10 MHz, the common rate between Jawbreaker and HackRF One. */
sample_rate_set(10000000);
sample_rate_set(10ULL * FP_ONE_MHZ, true);
si5351c_set_clock_source(&clock_gen, PLL_SOURCE_XTAL);
// soft reset

View File

@@ -45,6 +45,7 @@ extern "C" {
#include "cpld_jtag.h"
#include "ice40_spi.h"
#include "fpga.h"
#include "fixed_point.h"
/*
* SCU PinMux
@@ -435,8 +436,7 @@ void enable_1v8_power(void);
void disable_1v8_power(void);
#endif
bool sample_rate_frac_set(uint32_t rate_num, uint32_t rate_denom);
bool sample_rate_set(const uint32_t sampling_rate_hz);
fp_40_24_t sample_rate_set(const fp_40_24_t sample_rate, const bool program);
clock_source_t activate_best_clock_source(void);

View File

@@ -28,6 +28,7 @@
#include "platform_detect.h"
#include "radio.h"
#include "fixed_point.h"
#include "hackrf_ui.h"
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define MAX(x, y) ((x) > (y) ? (x) : (y))
@@ -119,46 +120,6 @@ static bool radio_update_direction(radio_t* const radio, uint64_t* bank)
return true;
}
/*
* Convert sample rate in units of 1/(2**24) Hz to fraction.
*/
static inline uint64_t frac_sample_rate(const fp_40_24_t rate)
{
uint64_t num = rate;
uint64_t denom = FP_ONE_HZ;
while ((num & 1) == 0) {
num >>= 1;
denom >>= 1;
if (denom == 1) {
break;
}
}
return (denom << 32) | (num & 0xffffffff);
}
static inline uint32_t numerator(const uint64_t frac)
{
return frac & 0xffffffff;
}
static inline uint32_t denominator(const uint64_t frac)
{
return frac >> 32;
}
/*
* Convert fractional sample rate to units of 1/(2**24) Hz.
*/
static inline fp_40_24_t round_sample_rate(const uint64_t frac)
{
uint64_t num = (uint64_t) numerator(frac) * FP_ONE_HZ;
uint32_t denom = denominator(frac);
if (denom == 0) {
denom = 1;
}
return (num + (denom >> 1)) / denom;
}
#define MAX_AFE_RATE_HZ (40000000UL)
static inline uint8_t compute_resample_log(
@@ -191,50 +152,47 @@ static fp_40_24_t applied_afe_rate = RADIO_UNSET;
static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
{
fp_40_24_t rate;
uint64_t frac, previous_n;
fp_40_24_t rate, afe_rate;
uint64_t previous_n;
uint8_t n = 0;
bool new_afe_rate = false;
bool new_rate = false;
bool new_n = false;
const uint64_t requested_frac = bank[RADIO_SAMPLE_RATE_FRAC];
const uint64_t requested_rate = bank[RADIO_SAMPLE_RATE];
const uint64_t requested_n = bank[RADIO_RESAMPLE_RX];
if (requested_rate != RADIO_UNSET) {
rate = MIN(requested_rate, MAX_MCU_RATE);
rate = MAX(rate, MIN_MCU_RATE);
frac = frac_sample_rate(rate);
} else if (requested_frac != RADIO_UNSET) {
frac = requested_frac;
if (round_sample_rate(frac) > MAX_MCU_RATE) {
frac = frac_sample_rate(MAX_MCU_RATE);
}
if (round_sample_rate(frac) < MIN_MCU_RATE) {
frac = frac_sample_rate(MIN_MCU_RATE);
}
rate = round_sample_rate(frac);
} else {
rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE];
if (rate == RADIO_UNSET) {
rate = DEFAULT_MCU_RATE;
}
frac = RADIO_UNSET;
}
const uint64_t previous_opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE];
uint64_t opmode = bank[RADIO_OPMODE];
if (opmode == RADIO_UNSET) {
opmode = radio->config[RADIO_BANK_APPLIED][RADIO_OPMODE];
opmode = previous_opmode;
}
switch (previous_opmode) {
case TRANSCEIVER_MODE_TX:
case TRANSCEIVER_MODE_SS:
previous_n = radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX];
break;
default:
previous_n = radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX];
}
switch (opmode) {
case TRANSCEIVER_MODE_TX:
case TRANSCEIVER_MODE_SS:
previous_n = radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX];
if (n != previous_n) {
if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX]) {
#ifdef PRALINE
fpga_set_tx_interpolation_ratio(&fpga, n);
#endif
radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_TX] = n;
new_rate = true;
}
break;
default:
@@ -243,32 +201,33 @@ static bool radio_update_sample_rate(radio_t* const radio, uint64_t* bank)
* spectrum inversion bug with TX interpolation.
*/
n = compute_resample_log(rate / FP_ONE_HZ, requested_n);
previous_n = radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX];
if (n != previous_n) {
if (n != radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX]) {
#ifdef PRALINE
fpga_set_rx_decimation_ratio(&fpga, n);
#endif
radio->config[RADIO_BANK_APPLIED][RADIO_RESAMPLE_RX] = n;
new_rate = true;
}
}
new_n = (n != previous_n);
fp_40_24_t afe_rate = rate << n;
fp_40_24_t previous_afe_rate = RADIO_UNSET;
if (previous_n != RADIO_UNSET) {
previous_afe_rate = radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE]
<< previous_n;
}
if (afe_rate != previous_afe_rate) {
sample_rate_frac_set(numerator(frac) << n, denominator(frac));
afe_rate = rate << n;
afe_rate = sample_rate_set(afe_rate, false);
new_afe_rate = (afe_rate != applied_afe_rate);
if (new_afe_rate) {
afe_rate = sample_rate_set(afe_rate, true);
applied_afe_rate = afe_rate;
radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE_FRAC] = frac;
}
rate = afe_rate >> n;
new_rate = (rate != radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE]);
if (new_rate) {
radio->config[RADIO_BANK_APPLIED][RADIO_SAMPLE_RATE] = rate;
new_rate = true;
/* Round to the nearest Hz for display. */
const uint32_t rate_hz = (rate + (FP_ONE_HZ >> 1)) / FP_ONE_HZ;
hackrf_ui()->set_sample_rate(rate_hz);
}
return new_rate;
return (new_afe_rate || new_rate || new_n);
}
#define DEFAULT_RF (2450ULL * FP_ONE_MHZ)
@@ -691,8 +650,8 @@ bool radio_update(radio_t* const radio)
bool dc = false;
if ((dirty &
((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_SAMPLE_RATE_FRAC) |
(1 << RADIO_RESAMPLE_TX) | (1 << RADIO_RESAMPLE_RX))) ||
((1 << RADIO_SAMPLE_RATE) | (1 << RADIO_RESAMPLE_TX) |
(1 << RADIO_RESAMPLE_RX))) ||
((detected_platform() == BOARD_ID_PRALINE) &&
(dirty & (1 << RADIO_OPMODE)))) {
rate = radio_update_sample_rate(radio, &tmp_bank[0]);

View File

@@ -166,6 +166,18 @@ usb_request_status_t usb_vendor_request_set_freq_explicit(
return USB_REQUEST_STATUS_OK;
}
/*
* Convert fractional sample rate to units of 1/(2**24) Hz.
*/
static inline fp_40_24_t round_sample_rate(uint64_t num, uint32_t denom)
{
num *= FP_ONE_HZ;
if (denom == 0) {
denom = 1;
}
return (num + (denom >> 1)) / denom;
}
usb_request_status_t usb_vendor_request_set_sample_rate_frac(
usb_endpoint_t* const endpoint,
const usb_transfer_stage_t stage)
@@ -179,9 +191,9 @@ usb_request_status_t usb_vendor_request_set_sample_rate_frac(
NULL);
} else if (stage == USB_TRANSFER_STAGE_DATA) {
uint32_t numerator = set_sample_r_params.freq_hz;
uint64_t denominator = set_sample_r_params.divider;
uint64_t value = (denominator << 32) | numerator;
radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_SAMPLE_RATE_FRAC, value);
uint32_t denominator = set_sample_r_params.divider;
uint64_t value = round_sample_rate(numerator, denominator);
radio_reg_write(&radio, RADIO_BANK_ACTIVE, RADIO_SAMPLE_RATE, value);
usb_transfer_schedule_ack(endpoint->in);
}
return USB_REQUEST_STATUS_OK;