Lock TX to RX frequency for clients

Central nodes (“base stations”) do no longer do coarse frequency estimation,
but do only use the preamble + PLL for fine offset tracking.
This commit is contained in:
Thomas Kolb 2024-03-31 22:23:04 +02:00
parent 4f4128fbd3
commit 93fd8aebbc
5 changed files with 82 additions and 8 deletions

View file

@ -1,6 +1,7 @@
#include <liquid/liquid.h> #include <liquid/liquid.h>
#include <assert.h> #include <assert.h>
#include <math.h>
#include "correlator.h" #include "correlator.h"
#include "preamble.h" #include "preamble.h"
@ -8,6 +9,7 @@
#include "whitening.h" #include "whitening.h"
#include "config.h" #include "config.h"
#include "options.h"
#include "rx.h" #include "rx.h"
@ -48,7 +50,7 @@ static void update_nco_pll(nco_crcf nco, float phase_error, float bw)
} }
static bool acquire_preamble(layer1_rx_t *rx, const float complex sample) static bool acquire_preamble(layer1_rx_t *rx, const float complex sample, bool do_coarse_freq_est)
{ {
static float complex freq_est_history[FREQ_EST_L]; static float complex freq_est_history[FREQ_EST_L];
static unsigned int freq_est_history_write_idx = 0; static unsigned int freq_est_history_write_idx = 0;
@ -90,7 +92,7 @@ static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
freq_est_history_write_idx = 0; freq_est_history_write_idx = 0;
freq_est_holdoff_samples = FREQ_EST_L; freq_est_holdoff_samples = FREQ_EST_L;
return true; // preamble found! return true; // preamble found!
} else { } else if(do_coarse_freq_est) {
// preamble not found. // preamble not found.
//DEBUG_LOG("Preamble not found: %.3f < %.3f\n", cabsf(corr_out), 0.5f * preamble_get_symbol_count()); //DEBUG_LOG("Preamble not found: %.3f < %.3f\n", cabsf(corr_out), 0.5f * preamble_get_symbol_count());
@ -140,9 +142,9 @@ static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
freq_est_history[freq_est_history_write_idx] = sample; freq_est_history[freq_est_history_write_idx] = sample;
freq_est_history_write_idx++; freq_est_history_write_idx++;
freq_est_history_write_idx %= FREQ_EST_L; freq_est_history_write_idx %= FREQ_EST_L;
return false; // preamble not found
} }
return false; // preamble not found
} }
@ -172,8 +174,16 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
// Mix the input signal with the carrier NCO, which oscillates at the // Mix the input signal with the carrier NCO, which oscillates at the
// frequency coarsly estimated so far. // frequency coarsly estimated so far.
float complex mixed_sample; float complex mixed_sample;
nco_crcf_step(rx->carrier_coarse_nco);
nco_crcf_mix_down(rx->carrier_coarse_nco, agc_out, &mixed_sample); if(is_central_node) {
// central nodes do not do coarse frequency tracking, as the clients
// shall lock onto the centrals carrier frequency and therefore not much
// frequency deviation is expected.
mixed_sample = agc_out;
} else {
nco_crcf_step(rx->carrier_coarse_nco);
nco_crcf_mix_down(rx->carrier_coarse_nco, agc_out, &mixed_sample);
}
samples2dump[nsamples2dump++] = mixed_sample; samples2dump[nsamples2dump++] = mixed_sample;
@ -183,7 +193,6 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
symsync_crcf_execute(rx->symsync, &mixed_sample, 1, &symsync_out, &out_len); symsync_crcf_execute(rx->symsync, &mixed_sample, 1, &symsync_out, &out_len);
if(out_len != 0) { if(out_len != 0) {
switch(rx->state) { switch(rx->state) {
// Try to acquire packets by synchronizing the frequency // Try to acquire packets by synchronizing the frequency
// (symbol-independent search) and correlating the preamble. // (symbol-independent search) and correlating the preamble.
@ -196,7 +205,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
nco_crcf_set_phase(rx->carrier_coarse_nco, 0.0f); nco_crcf_set_phase(rx->carrier_coarse_nco, 0.0f);
} }
if(acquire_preamble(rx, symsync_out)) { if(acquire_preamble(rx, symsync_out, !is_central_node)) {
// Preamble found and frequency corrected! // Preamble found and frequency corrected!
rx->callback(RX_EVT_PREAMBLE_FOUND, NULL, 0); rx->callback(RX_EVT_PREAMBLE_FOUND, NULL, 0);
@ -439,3 +448,9 @@ bool layer1_rx_is_busy(const layer1_rx_t *rx)
{ {
return rx->state != RX_STATE_ACQUISITION; return rx->state != RX_STATE_ACQUISITION;
} }
float layer1_rx_get_coarse_carrier_frequency(const layer1_rx_t *rx)
{
return nco_crcf_get_frequency(rx->carrier_coarse_nco);
}

View file

@ -120,4 +120,15 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
*/ */
bool layer1_rx_is_busy(const layer1_rx_t *rx); bool layer1_rx_is_busy(const layer1_rx_t *rx);
/*!
* \brief Retrieve the coarsely estimated carrier frequency.
*
* The carrier frequency is measured in radians per sample in the RRC-filtered baseband domain, i.e. the sampling rate is SYMBOL_RATE * RRC_SPS.
*
* \param rx Pointer to the receiver context.
* \returns The estimated carrier frequency in the RRC-filtered domain.
*/
float layer1_rx_get_coarse_carrier_frequency(const layer1_rx_t *rx);
#endif // LAYER1_RX_H #endif // LAYER1_RX_H

View file

@ -1,3 +1,5 @@
#include <liquid/liquid.h>
#include <stdbool.h> #include <stdbool.h>
#include <malloc.h> #include <malloc.h>
@ -41,6 +43,8 @@ result_t layer1_tx_init(layer1_tx_t *tx)
tx->samples_allocated = 0; tx->samples_allocated = 0;
tx->samples_used = 0; tx->samples_used = 0;
tx->carrier_frequency_offset = 0.0f;
transmission_init(&tx->transmission); transmission_init(&tx->transmission);
packet_mod_init(&tx->pmod); packet_mod_init(&tx->pmod);
return OK; return OK;
@ -154,6 +158,28 @@ result_t layer1_tx_finalize_burst(layer1_tx_t *tx)
} }
tx->samples_used += len; tx->samples_used += len;
// mix the signal up to the carrier offset frequency
// allocate a temporary buffer
float complex *tmp = malloc(sizeof(float complex) * tx->samples_used);
if(!tmp) {
fprintf(stderr, "Could not allocate buffer for TX frequency correction.\n", len, tx->samples_used);
return ERR_NO_MEM;
}
nco_crcf carrier_nco = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(carrier_nco, tx->carrier_frequency_offset);
nco_crcf_mix_block_up(carrier_nco, tx->samples, tmp, tx->samples_used);
nco_crcf_destroy(carrier_nco);
// swap the buffers
free(tx->samples);
tx->samples = tmp;
tx->samples_allocated = tx->samples_used;
return OK; return OK;
} }
@ -168,3 +194,9 @@ const float complex* layer1_tx_get_sample_data(const layer1_tx_t *tx)
{ {
return tx->samples; return tx->samples;
} }
void layer1_tx_set_carrier_frequency_offset(layer1_tx_t *tx, float freq)
{
tx->carrier_frequency_offset = freq;
}

View file

@ -16,6 +16,8 @@ typedef struct
size_t samples_allocated; size_t samples_allocated;
size_t samples_used; size_t samples_used;
float carrier_frequency_offset;
transmission_ctx_t transmission; transmission_ctx_t transmission;
packet_mod_ctx_t pmod; packet_mod_ctx_t pmod;
} layer1_tx_t; } layer1_tx_t;
@ -100,4 +102,14 @@ size_t layer1_tx_get_sample_count(const layer1_tx_t *tx);
*/ */
const float complex* layer1_tx_get_sample_data(const layer1_tx_t *tx); const float complex* layer1_tx_get_sample_data(const layer1_tx_t *tx);
/*!
* \brief Set the carrier frequency correction in the RRC-filtered domain.
*
* The carrier frequency is measured in radians per sample in the RRC-filtered baseband domain, i.e. the sampling rate is SYMBOL_RATE * RRC_SPS.
*
* \param tx Pointer to the transmitter context.
* \param freq The carrier frequency offset to use.
*/
void layer1_tx_set_carrier_frequency_offset(layer1_tx_t *tx, float freq);
#endif // LAYER1_TX_H #endif // LAYER1_TX_H

View file

@ -315,6 +315,10 @@ int main(int argc, char **argv)
if(!on_air) { if(!on_air) {
RESULT_CHECK(sdr_stop_rx(&sdr)); RESULT_CHECK(sdr_stop_rx(&sdr));
// transmit packets on the frequency where the last packet was received.
layer1_tx_set_carrier_frequency_offset(&tx, layer1_rx_get_coarse_carrier_frequency(&rx));
RESULT_CHECK(sdr_start_tx(&sdr, burst_len * SDR_OVERSAMPLING)); RESULT_CHECK(sdr_start_tx(&sdr, burst_len * SDR_OVERSAMPLING));
} }