Moved receiver to separate module
This commit is contained in:
parent
a5634ed736
commit
876bafd6f9
|
@ -25,6 +25,8 @@ set(sources
|
||||||
src/layer1/transmission.h
|
src/layer1/transmission.h
|
||||||
src/layer1/tx.c
|
src/layer1/tx.c
|
||||||
src/layer1/tx.h
|
src/layer1/tx.h
|
||||||
|
src/layer1/rx.c
|
||||||
|
src/layer1/rx.h
|
||||||
src/layer1/whitening.c
|
src/layer1/whitening.c
|
||||||
src/layer1/whitening.h
|
src/layer1/whitening.h
|
||||||
)
|
)
|
||||||
|
|
269
impl/src/layer1/rx.c
Normal file
269
impl/src/layer1/rx.c
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "correlator.h"
|
||||||
|
#include "preamble.h"
|
||||||
|
#include "freq_est.h"
|
||||||
|
#include "whitening.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include "rx.h"
|
||||||
|
|
||||||
|
#define HEADER_SIZE_BYTES 4
|
||||||
|
#define FREQ_EST_L 8
|
||||||
|
|
||||||
|
static void update_nco_pll(nco_crcf nco, float phase_error)
|
||||||
|
{
|
||||||
|
static const float pll_alpha = 0.05f; // phase adjustment factor
|
||||||
|
static const float pll_beta = (pll_alpha * pll_alpha) / 2.0f; // frequency adjustment factor
|
||||||
|
|
||||||
|
nco_crcf_adjust_phase(nco, pll_alpha * phase_error);
|
||||||
|
nco_crcf_adjust_frequency(nco, pll_beta * phase_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
|
||||||
|
{
|
||||||
|
static float complex freq_est_history[FREQ_EST_L];
|
||||||
|
static unsigned int freq_est_history_write_idx = 0;
|
||||||
|
|
||||||
|
// preamble search
|
||||||
|
float complex corr_out = correlator_step(&rx->preamble_correlator, sample);
|
||||||
|
|
||||||
|
if(cabsf(corr_out) > 0.5f * preamble_get_symbol_count()) {
|
||||||
|
// Preamble found!
|
||||||
|
printf("Preamble found: %.3f > %.3f\n", cabsf(corr_out), 0.5f * preamble_get_symbol_count());
|
||||||
|
|
||||||
|
float phase_offset;
|
||||||
|
float mean_frequency_error = correlator_get_mean_frequency_deviation(&rx->preamble_correlator, FREQ_EST_L, &phase_offset);
|
||||||
|
|
||||||
|
printf("Phase offset: %.6f rad\n", phase_offset);
|
||||||
|
printf("Preamble frequency deviation: %.6f rad/symbol\n", mean_frequency_error);
|
||||||
|
|
||||||
|
// Set the fine carrier correction NCO to the values estimated from the preamble
|
||||||
|
nco_crcf_set_frequency(rx->carrier_fine_nco, mean_frequency_error / RRC_SPS * 0.5f);
|
||||||
|
nco_crcf_set_phase(rx->carrier_fine_nco, phase_offset);
|
||||||
|
|
||||||
|
printf("New estimated carrier frequency: %.6f + %.6f\n", nco_crcf_get_frequency(rx->carrier_coarse_nco), nco_crcf_get_frequency(rx->carrier_fine_nco));
|
||||||
|
|
||||||
|
//float complex input_history[preamble_get_symbol_count()];
|
||||||
|
//correlator_get_input_history(&rx->preamble_correlator, input_history);
|
||||||
|
|
||||||
|
//printf("import numpy as np\n");
|
||||||
|
//printf("import matplotlib.pyplot as pp\n");
|
||||||
|
//print_complex_array("pre", preamble_get_symbols(), preamble_get_symbol_count());
|
||||||
|
//print_complex_array("recv", input_history, preamble_get_symbol_count());
|
||||||
|
//printf("pp.plot(np.angle(recv * pre.conj()))\n");
|
||||||
|
//printf("pp.show()\n");
|
||||||
|
|
||||||
|
// start over with frequency estimation when preamble search restarts.
|
||||||
|
freq_est_history_write_idx = 0;
|
||||||
|
return true; // preamble found!
|
||||||
|
} else {
|
||||||
|
// preamble not found.
|
||||||
|
|
||||||
|
// Run the frequency estimator in blocks of FREQ_EST_L samples.
|
||||||
|
// This is an implementation that works with unknown BPSK symbols
|
||||||
|
// and therefore can be used during ramp-up and preamble.
|
||||||
|
if(freq_est_history_write_idx == FREQ_EST_L) {
|
||||||
|
float freq_est = freq_est_unknown_bpsk(freq_est_history, FREQ_EST_L, NULL);
|
||||||
|
|
||||||
|
float freq_adjustment = (freq_est / RRC_SPS) * 0.3f;
|
||||||
|
nco_crcf_adjust_frequency(rx->carrier_coarse_nco, freq_adjustment);
|
||||||
|
|
||||||
|
printf("Frequency adjustment: %.6f - carrier frequency: %.6f\n", freq_adjustment, nco_crcf_get_frequency(rx->carrier_coarse_nco));
|
||||||
|
|
||||||
|
freq_est_history_write_idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
freq_est_history[freq_est_history_write_idx] = sample;
|
||||||
|
freq_est_history_write_idx++;
|
||||||
|
|
||||||
|
return false; // preamble not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t sample_count)
|
||||||
|
{
|
||||||
|
static size_t symbol_counter = 0;
|
||||||
|
static uint8_t symbols_int[1 << 12];
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < sample_count; i++) {
|
||||||
|
// Mix the input signal with the carrier NCO, which oscillates at the
|
||||||
|
// frequency coarsly estimated so far.
|
||||||
|
float complex mixed_sample;
|
||||||
|
nco_crcf_step(rx->carrier_coarse_nco);
|
||||||
|
nco_crcf_mix_down(rx->carrier_coarse_nco, samples[i], &mixed_sample);
|
||||||
|
|
||||||
|
// run the timing synchronizer (works even with shifted frequency)
|
||||||
|
unsigned int out_len;
|
||||||
|
float complex symsync_out;
|
||||||
|
symsync_crcf_execute(rx->symsync, &mixed_sample, 1, &symsync_out, &out_len);
|
||||||
|
|
||||||
|
if(out_len != 0) {
|
||||||
|
switch(rx->state) {
|
||||||
|
// Try to acquire packets by synchronizing the frequency
|
||||||
|
// (symbol-independent search) and correlating the preamble.
|
||||||
|
case RX_STATE_ACQUISITION:
|
||||||
|
if(acquire_preamble(rx, symsync_out)) {
|
||||||
|
// Preamble found and frequency corrected!
|
||||||
|
rx->callback(RX_EVT_PREAMBLE_FOUND, NULL, 0);
|
||||||
|
|
||||||
|
// go on with decoding the header
|
||||||
|
rx->state = RX_STATE_HEADER;
|
||||||
|
symbol_counter = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RX_STATE_HEADER:
|
||||||
|
nco_crcf_mix_down(rx->carrier_fine_nco, symsync_out, &mixed_sample);
|
||||||
|
if(symbol_counter < rx->hdr_len_symbols) {
|
||||||
|
unsigned int sym_demod;
|
||||||
|
modem_demodulate(rx->hdr_demod, mixed_sample, &sym_demod);
|
||||||
|
|
||||||
|
float phase_error = modem_get_demodulator_phase_error(rx->hdr_demod);
|
||||||
|
printf("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
|
||||||
|
|
||||||
|
update_nco_pll(rx->carrier_fine_nco, phase_error);
|
||||||
|
|
||||||
|
symbols_int[symbol_counter] = sym_demod;
|
||||||
|
symbol_counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(symbol_counter == rx->hdr_len_symbols) {
|
||||||
|
unsigned int nsyms;
|
||||||
|
uint8_t header_enc[rx->hdr_len_enc_bytes];
|
||||||
|
uint8_t header[HEADER_SIZE_BYTES];
|
||||||
|
|
||||||
|
ERR_CHECK_LIQUID(liquid_repack_bytes(
|
||||||
|
symbols_int, modem_get_bps(rx->hdr_demod), rx->hdr_len_symbols,
|
||||||
|
header_enc, 8, sizeof(header_enc), &nsyms));
|
||||||
|
|
||||||
|
fec_decode(rx->hdr_fec, sizeof(header), header_enc, header);
|
||||||
|
|
||||||
|
uint16_t payload_bps = modem_get_bps(rx->payload_demod);
|
||||||
|
|
||||||
|
rx->payload_len_bytes = (((uint16_t)header[0] << 8) | header[1]) & 0x07FF;
|
||||||
|
rx->payload_crc = ((uint16_t)header[2] << 8) | header[3];
|
||||||
|
|
||||||
|
rx->payload_len_enc_bytes = fec_get_enc_msg_length(PAYLOAD_CHANNEL_CODE, rx->payload_len_bytes);
|
||||||
|
rx->payload_len_symbols = (rx->payload_len_enc_bytes * 8 + payload_bps - 1) / payload_bps;
|
||||||
|
|
||||||
|
printf("=== DECODED HEADER ===\n");
|
||||||
|
printf("Payload length: %u symbols, %u bytes encoded, %u bytes decoded\n", rx->payload_len_symbols, rx->payload_len_enc_bytes, rx->payload_len_bytes);
|
||||||
|
printf("CRC16: 0x%04x\n", rx->payload_crc);
|
||||||
|
printf("======================\n");
|
||||||
|
|
||||||
|
//dump_array_cf(symbols_cpx, symbol_counter, 1.0f, "/tmp/hdr.cpx");
|
||||||
|
|
||||||
|
rx->state = RX_STATE_DATA;
|
||||||
|
symbol_counter = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RX_STATE_DATA:
|
||||||
|
nco_crcf_mix_down(rx->carrier_fine_nco, symsync_out, &mixed_sample);
|
||||||
|
if(symbol_counter < rx->payload_len_symbols) {
|
||||||
|
unsigned int sym_demod;
|
||||||
|
modem_demodulate(rx->payload_demod, mixed_sample, &sym_demod);
|
||||||
|
|
||||||
|
float phase_error = modem_get_demodulator_phase_error(rx->payload_demod);
|
||||||
|
printf("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
|
||||||
|
|
||||||
|
update_nco_pll(rx->carrier_fine_nco, phase_error);
|
||||||
|
|
||||||
|
symbols_int[symbol_counter] = sym_demod;
|
||||||
|
symbol_counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(symbol_counter == rx->payload_len_symbols) {
|
||||||
|
unsigned int nsyms;
|
||||||
|
unsigned char payload_enc[rx->payload_len_enc_bytes];
|
||||||
|
unsigned char payload[rx->payload_len_bytes];
|
||||||
|
|
||||||
|
ERR_CHECK_LIQUID(liquid_repack_bytes(
|
||||||
|
symbols_int, modem_get_bps(rx->payload_demod), rx->payload_len_symbols,
|
||||||
|
payload_enc, 8, sizeof(payload_enc), &nsyms));
|
||||||
|
|
||||||
|
fec_decode(rx->payload_fec, rx->payload_len_bytes, payload_enc, payload);
|
||||||
|
|
||||||
|
// de-whiten the data
|
||||||
|
whitening_apply_in_place(payload, rx->payload_len_bytes);
|
||||||
|
|
||||||
|
int valid = crc_validate_message(PAYLOAD_CRC_SCHEME, payload, rx->payload_len_bytes, rx->payload_crc);
|
||||||
|
|
||||||
|
payload[rx->payload_len_bytes] = '\0';
|
||||||
|
|
||||||
|
//dump_array_cf(symbols_cpx, symbol_counter, 1.0f, "/tmp/payload.cpx");
|
||||||
|
if(valid) {
|
||||||
|
rx->callback(RX_EVT_PACKET_RECEIVED, payload, rx->payload_len_bytes);
|
||||||
|
} else {
|
||||||
|
rx->callback(RX_EVT_CHECKSUM_ERROR, payload, rx->payload_len_bytes);
|
||||||
|
}
|
||||||
|
rx->state = RX_STATE_ACQUISITION;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t layer1_rx_init(layer1_rx_t *rx, rx_callback_t callback)
|
||||||
|
{
|
||||||
|
rx->callback = callback;
|
||||||
|
|
||||||
|
rx->state = RX_STATE_ACQUISITION;
|
||||||
|
|
||||||
|
// create NCOs for carrier frequency compensation
|
||||||
|
rx->carrier_coarse_nco = nco_crcf_create(LIQUID_NCO);
|
||||||
|
nco_crcf_set_frequency(rx->carrier_coarse_nco, 0.00f);
|
||||||
|
nco_crcf_set_phase(rx->carrier_coarse_nco, 0.0f);
|
||||||
|
|
||||||
|
rx->carrier_fine_nco = nco_crcf_create(LIQUID_NCO);
|
||||||
|
nco_crcf_set_frequency(rx->carrier_fine_nco, 0.00f);
|
||||||
|
nco_crcf_set_phase(rx->carrier_fine_nco, 0.0f);
|
||||||
|
|
||||||
|
// forward error correction objects
|
||||||
|
rx->hdr_fec = fec_create(HEADER_CHANNEL_CODE, NULL);
|
||||||
|
rx->payload_fec = fec_create(PAYLOAD_CHANNEL_CODE, NULL);
|
||||||
|
|
||||||
|
// demodulators
|
||||||
|
rx->hdr_demod = modem_create(HEADER_MODULATION);
|
||||||
|
rx->payload_demod = modem_create(PAYLOAD_MODULATION);
|
||||||
|
|
||||||
|
// symbol timing synchronizer
|
||||||
|
rx->symsync = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC, RRC_SPS, RRC_DELAY, RRC_BETA, 32);
|
||||||
|
|
||||||
|
// Correlator for preamble search
|
||||||
|
correlator_init(&rx->preamble_correlator, preamble_get_symbols(), preamble_get_symbol_count());
|
||||||
|
|
||||||
|
// precalculate encoded header length in symbols
|
||||||
|
unsigned int hdr_bps = modem_get_bps(rx->hdr_demod);
|
||||||
|
rx->hdr_len_enc_bytes = fec_get_enc_msg_length(HEADER_CHANNEL_CODE, HEADER_SIZE_BYTES);
|
||||||
|
rx->hdr_len_symbols = (rx->hdr_len_enc_bytes * 8 + hdr_bps - 1) / hdr_bps;
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t layer1_rx_shutdown(layer1_rx_t *rx)
|
||||||
|
{
|
||||||
|
nco_crcf_destroy(rx->carrier_coarse_nco);
|
||||||
|
nco_crcf_destroy(rx->carrier_fine_nco);
|
||||||
|
|
||||||
|
fec_destroy(rx->hdr_fec);
|
||||||
|
fec_destroy(rx->payload_fec);
|
||||||
|
|
||||||
|
modem_destroy(rx->hdr_demod);
|
||||||
|
modem_destroy(rx->payload_demod);
|
||||||
|
|
||||||
|
symsync_crcf_destroy(rx->symsync);
|
||||||
|
|
||||||
|
correlator_free(&rx->preamble_correlator);
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
102
impl/src/layer1/rx.h
Normal file
102
impl/src/layer1/rx.h
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
#ifndef LAYER1_RX_H
|
||||||
|
#define LAYER1_RX_H
|
||||||
|
|
||||||
|
#include <liquid/liquid.h>
|
||||||
|
|
||||||
|
#include "correlator.h"
|
||||||
|
|
||||||
|
#include "results.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RX_STATE_ACQUISITION,
|
||||||
|
RX_STATE_HEADER,
|
||||||
|
RX_STATE_DATA,
|
||||||
|
} rx_state_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RX_EVT_PACKET_RECEIVED,
|
||||||
|
RX_EVT_PREAMBLE_FOUND,
|
||||||
|
RX_EVT_CHECKSUM_ERROR,
|
||||||
|
} rx_evt_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef void (*rx_callback_t)(rx_evt_t evt, uint8_t *packet_data, size_t packet_len);
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
// NCOs for carrier frequency offset correction
|
||||||
|
nco_crcf carrier_coarse_nco;
|
||||||
|
nco_crcf carrier_fine_nco;
|
||||||
|
|
||||||
|
// Forward error correction objects
|
||||||
|
fec hdr_fec;
|
||||||
|
fec payload_fec;
|
||||||
|
|
||||||
|
// Demodulators
|
||||||
|
modem hdr_demod;
|
||||||
|
modem payload_demod;
|
||||||
|
|
||||||
|
// symbol timing synchronizer
|
||||||
|
symsync_crcf symsync;
|
||||||
|
|
||||||
|
// preamble correlator
|
||||||
|
correlator_ctx_t preamble_correlator;
|
||||||
|
|
||||||
|
// Receiver state
|
||||||
|
rx_state_t state;
|
||||||
|
|
||||||
|
// Callback function to notify user of certain events
|
||||||
|
rx_callback_t callback;
|
||||||
|
|
||||||
|
// Precalcuated header lengths
|
||||||
|
unsigned int hdr_len_symbols;
|
||||||
|
unsigned int hdr_len_enc_bytes;
|
||||||
|
|
||||||
|
// Information from the decoded header
|
||||||
|
uint16_t payload_len_symbols;
|
||||||
|
uint16_t payload_len_bytes;
|
||||||
|
uint16_t payload_crc;
|
||||||
|
uint16_t payload_len_enc_bytes;
|
||||||
|
} layer1_rx_t;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Initialize the receiver.
|
||||||
|
*
|
||||||
|
* \param tx Pointer to the receiver context.
|
||||||
|
* \param callback Function to be called on receiver events.
|
||||||
|
* \returns The result of the initialization.
|
||||||
|
*/
|
||||||
|
result_t layer1_rx_init(layer1_rx_t *rx, rx_callback_t callback);
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Shut the receiver down. Frees all memory.
|
||||||
|
*
|
||||||
|
* \param tx Pointer to the receiver context.
|
||||||
|
* \returns The result of the shutdown.
|
||||||
|
*/
|
||||||
|
result_t layer1_rx_shutdown(layer1_rx_t *rx);
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Process the given block of samples.
|
||||||
|
*
|
||||||
|
* Runs the whole receiver chain for the given samples. This includes timing-
|
||||||
|
* and frequency synchronization, preamble search, header demodulation and
|
||||||
|
* decoding and payload demodulation and decoding.
|
||||||
|
*
|
||||||
|
* On specific events, most notably when a packet is successfully decoded, the
|
||||||
|
* callback function passed to \ref layer1_rx_init() will be called. See \ref
|
||||||
|
* rx_evt_t for possible events.
|
||||||
|
*
|
||||||
|
* \param tx Pointer to the receiver context.
|
||||||
|
* \param samples Pointer to the samples to process.
|
||||||
|
* \param sample_count Number of samples to process.
|
||||||
|
* \returns The result of the shutdown.
|
||||||
|
*/
|
||||||
|
result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t sample_count);
|
||||||
|
|
||||||
|
#endif // LAYER1_RX_H
|
299
impl/src/main.c
299
impl/src/main.c
|
@ -5,13 +5,8 @@
|
||||||
#include <liquid/liquid.h>
|
#include <liquid/liquid.h>
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "layer1/results.h"
|
|
||||||
#include "layer1/config.h"
|
|
||||||
#include "layer1/preamble.h"
|
|
||||||
#include "layer1/correlator.h"
|
|
||||||
#include "layer1/freq_est.h"
|
|
||||||
#include "layer1/whitening.h"
|
|
||||||
#include "layer1/tx.h"
|
#include "layer1/tx.h"
|
||||||
|
#include "layer1/rx.h"
|
||||||
|
|
||||||
#define RESULT_CHECK(stmt) { \
|
#define RESULT_CHECK(stmt) { \
|
||||||
result_t res = stmt; \
|
result_t res = stmt; \
|
||||||
|
@ -21,13 +16,6 @@
|
||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
RX_STATE_ACQUISITION,
|
|
||||||
RX_STATE_HEADER,
|
|
||||||
RX_STATE_DATA,
|
|
||||||
} rx_state_t;
|
|
||||||
|
|
||||||
|
|
||||||
void print_complex_array(const char *varname, float complex const *array, size_t len)
|
void print_complex_array(const char *varname, float complex const *array, size_t len)
|
||||||
{
|
{
|
||||||
printf("%s=np.array([%f%+fj", varname, crealf(array[0]), cimagf(array[0]));
|
printf("%s=np.array([%f%+fj", varname, crealf(array[0]), cimagf(array[0]));
|
||||||
|
@ -38,13 +26,24 @@ void print_complex_array(const char *varname, float complex const *array, size_t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void update_nco_pll(nco_crcf nco, float phase_error)
|
void cb_rx(rx_evt_t evt, uint8_t *packet_data, size_t packet_len)
|
||||||
{
|
{
|
||||||
static const float pll_alpha = 0.05f; // phase adjustment factor
|
switch(evt)
|
||||||
static const float pll_beta = (pll_alpha * pll_alpha) / 2.0f; // frequency adjustment factor
|
{
|
||||||
|
case RX_EVT_CHECKSUM_ERROR:
|
||||||
|
printf("Received a message of %zu bytes, but decoding failed.\n", packet_len);
|
||||||
|
break;
|
||||||
|
|
||||||
nco_crcf_adjust_phase(nco, pll_alpha * phase_error);
|
case RX_EVT_PACKET_RECEIVED:
|
||||||
nco_crcf_adjust_frequency(nco, pll_beta * phase_error);
|
printf("=== DECODED PAYLOAD ===\n");
|
||||||
|
printf("%s\n", packet_data);
|
||||||
|
printf("=======================\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RX_EVT_PREAMBLE_FOUND:
|
||||||
|
printf("Found preamble!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +51,8 @@ int main(void)
|
||||||
{
|
{
|
||||||
uint8_t msg_org[] = "Hello Liquid! This is the message to transmit. Hopefully it can be decoded correctly...";
|
uint8_t msg_org[] = "Hello Liquid! This is the message to transmit. Hopefully it can be decoded correctly...";
|
||||||
|
|
||||||
|
// ** Modulate packet **
|
||||||
|
|
||||||
layer1_tx_t tx;
|
layer1_tx_t tx;
|
||||||
|
|
||||||
RESULT_CHECK(layer1_tx_init(&tx));
|
RESULT_CHECK(layer1_tx_init(&tx));
|
||||||
|
@ -63,28 +64,14 @@ int main(void)
|
||||||
|
|
||||||
dump_array_cf(whole_burst, burst_len, 1.0f, "/tmp/tx.cpx");
|
dump_array_cf(whole_burst, burst_len, 1.0f, "/tmp/tx.cpx");
|
||||||
|
|
||||||
// ** RX starts here **
|
// ** Apply channel distortions **
|
||||||
|
|
||||||
fec hdr_fec = fec_create(HEADER_CHANNEL_CODE, NULL);
|
|
||||||
fec payload_fec = fec_create(PAYLOAD_CHANNEL_CODE, NULL);
|
|
||||||
|
|
||||||
modem hdr_demod = modem_create(HEADER_MODULATION);
|
|
||||||
modem payload_demod = modem_create(PAYLOAD_MODULATION);
|
|
||||||
|
|
||||||
channel_cccf channel = channel_cccf_create();
|
channel_cccf channel = channel_cccf_create();
|
||||||
|
|
||||||
float snr = 7.0f;
|
float snr = 8.0f;
|
||||||
channel_cccf_add_awgn(channel, -snr, snr);
|
channel_cccf_add_awgn(channel, -snr, snr);
|
||||||
channel_cccf_add_carrier_offset(channel, 0.20f, 1.00f);
|
channel_cccf_add_carrier_offset(channel, 0.20f, 1.00f);
|
||||||
|
|
||||||
// precalculate encoded header length in symbols
|
|
||||||
uint8_t header[4];
|
|
||||||
unsigned int hdr_len_enc_bytes = fec_get_enc_msg_length(HEADER_CHANNEL_CODE, sizeof(header));
|
|
||||||
unsigned int hdr_bps = modem_get_bps(hdr_demod);
|
|
||||||
unsigned int hdr_len_symbols = (hdr_len_enc_bytes * 8 + hdr_bps - 1) / hdr_bps;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
float complex msg_received[burst_len];
|
float complex msg_received[burst_len];
|
||||||
|
|
||||||
|
@ -93,248 +80,20 @@ int main(void)
|
||||||
channel_cccf_execute_block(channel, whole_burst, burst_len, msg_received);
|
channel_cccf_execute_block(channel, whole_burst, burst_len, msg_received);
|
||||||
dump_array_cf(msg_received, burst_len, 1.0f, "/tmp/rx.cpx");
|
dump_array_cf(msg_received, burst_len, 1.0f, "/tmp/rx.cpx");
|
||||||
|
|
||||||
// create NCOs for carrier frequency compensation
|
// ** RX starts here **
|
||||||
nco_crcf carrier_nco = nco_crcf_create(LIQUID_NCO);
|
|
||||||
nco_crcf_set_frequency(carrier_nco, 0.00f);
|
|
||||||
nco_crcf_set_phase(carrier_nco, 0.0f);
|
|
||||||
|
|
||||||
nco_crcf carrier_fine_nco = nco_crcf_create(LIQUID_NCO);
|
layer1_rx_t rx;
|
||||||
nco_crcf_set_frequency(carrier_fine_nco, 0.00f);
|
|
||||||
nco_crcf_set_phase(carrier_fine_nco, 0.0f);
|
|
||||||
|
|
||||||
// create symbol synchronizer
|
RESULT_CHECK(layer1_rx_init(&rx, cb_rx));
|
||||||
symsync_crcf symsync = symsync_crcf_create_rnyquist(LIQUID_FIRFILT_RRC, RRC_SPS, RRC_DELAY, RRC_BETA, 32);
|
|
||||||
|
|
||||||
float complex symsync_out[burst_len];
|
RESULT_CHECK(layer1_rx_process(&rx, msg_received, burst_len));
|
||||||
unsigned int symsync_out_len = 0;
|
|
||||||
|
|
||||||
float complex symbols_cpx[16384];
|
// cleanup
|
||||||
unsigned char symbols_int[16384];
|
|
||||||
unsigned int symbol_counter = 0; // for demodulation
|
|
||||||
|
|
||||||
uint16_t payload_len_symbols;
|
|
||||||
uint16_t payload_len_bytes;
|
|
||||||
uint16_t payload_crc;
|
|
||||||
uint16_t payload_len_enc_bytes;
|
|
||||||
uint16_t payload_bps = modem_get_bps(payload_demod);
|
|
||||||
|
|
||||||
#define FREQ_EST_L 8
|
|
||||||
// General receiver state
|
|
||||||
rx_state_t rx_state = RX_STATE_ACQUISITION;
|
|
||||||
|
|
||||||
// Correlator for preamble search
|
|
||||||
correlator_ctx_t preamble_correlator;
|
|
||||||
correlator_init(&preamble_correlator, preamble_get_symbols(), preamble_get_symbol_count());
|
|
||||||
|
|
||||||
for(unsigned int i = 0; i < burst_len; i++) {
|
|
||||||
// Mix the input signal with the carrier NCO, which oscillates at the
|
|
||||||
// frequency estimated so far.
|
|
||||||
float complex mixed_sample;
|
|
||||||
nco_crcf_step(carrier_nco);
|
|
||||||
nco_crcf_mix_down(carrier_nco, msg_received[i], &mixed_sample);
|
|
||||||
|
|
||||||
// run the timing synchronizer (works even with shifted frequency)
|
|
||||||
unsigned int out_len;
|
|
||||||
symsync_crcf_execute(symsync, &mixed_sample, 1, symsync_out + symsync_out_len, &out_len);
|
|
||||||
|
|
||||||
if(out_len != 0) {
|
|
||||||
float complex corr_out;
|
|
||||||
switch(rx_state) {
|
|
||||||
// Try to acquire packets by synchronizing the frequency
|
|
||||||
// (symbol-independent search) and correlating the preamble.
|
|
||||||
case RX_STATE_ACQUISITION:
|
|
||||||
// preamble search
|
|
||||||
corr_out = correlator_step(&preamble_correlator, symsync_out[symsync_out_len]);
|
|
||||||
|
|
||||||
if(cabsf(corr_out) > 0.5f * preamble_get_symbol_count()) {
|
|
||||||
// Preamble found!
|
|
||||||
printf("Preamble found at symbol %u: %.3f > %.3f\n", i/RRC_SPS, cabsf(corr_out), 0.5f * preamble_get_symbol_count());
|
|
||||||
|
|
||||||
float phase_offset;
|
|
||||||
float mean_frequency_error = correlator_get_mean_frequency_deviation(&preamble_correlator, FREQ_EST_L, &phase_offset);
|
|
||||||
|
|
||||||
printf("Phase offset: %.6f rad\n", phase_offset);
|
|
||||||
printf("Preamble frequency deviation: %.6f rad/symbol\n", mean_frequency_error);
|
|
||||||
|
|
||||||
// adjust the frequency and phase of the NCO with the estimations from the preamble
|
|
||||||
nco_crcf_set_frequency(carrier_fine_nco, mean_frequency_error / RRC_SPS * 0.5f);
|
|
||||||
nco_crcf_set_phase(carrier_fine_nco, phase_offset);
|
|
||||||
|
|
||||||
printf("New estimated carrier frequency: %.6f + %.6f\n", nco_crcf_get_frequency(carrier_nco), nco_crcf_get_frequency(carrier_fine_nco));
|
|
||||||
|
|
||||||
float complex input_history[preamble_get_symbol_count()];
|
|
||||||
correlator_get_input_history(&preamble_correlator, input_history);
|
|
||||||
|
|
||||||
printf("import numpy as np\n");
|
|
||||||
printf("import matplotlib.pyplot as pp\n");
|
|
||||||
print_complex_array("pre", preamble_get_symbols(), preamble_get_symbol_count());
|
|
||||||
print_complex_array("recv", input_history, preamble_get_symbol_count());
|
|
||||||
printf("pp.plot(np.angle(recv * pre.conj()))\n");
|
|
||||||
printf("pp.show()\n");
|
|
||||||
|
|
||||||
// receive and decode the header
|
|
||||||
rx_state = RX_STATE_HEADER;
|
|
||||||
symbol_counter = 0;
|
|
||||||
} else {
|
|
||||||
// preamble not found.
|
|
||||||
|
|
||||||
// Run the frequency estimator in blocks of FREQ_EST_L samples.
|
|
||||||
// This is an implementation that works with unknown BPSK symbols
|
|
||||||
// and therefore can be used during ramp-up and preamble.
|
|
||||||
if(((i/RRC_SPS) % FREQ_EST_L) == 0) {
|
|
||||||
float freq_est = freq_est_unknown_bpsk(symsync_out + symsync_out_len - FREQ_EST_L, FREQ_EST_L, NULL);
|
|
||||||
|
|
||||||
float freq_adjustment = (freq_est / RRC_SPS) * 0.3f;
|
|
||||||
nco_crcf_adjust_frequency(carrier_nco, freq_adjustment);
|
|
||||||
|
|
||||||
printf("Frequency adjustment: %.6f - carrier frequency: %.6f\n", freq_adjustment, nco_crcf_get_frequency(carrier_nco));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RX_STATE_HEADER:
|
|
||||||
nco_crcf_mix_down(carrier_fine_nco, symsync_out[symsync_out_len], &mixed_sample);
|
|
||||||
if(symbol_counter < hdr_len_symbols) {
|
|
||||||
unsigned int sym_demod;
|
|
||||||
modem_demodulate(hdr_demod, mixed_sample, &sym_demod);
|
|
||||||
|
|
||||||
float phase_error = modem_get_demodulator_phase_error(hdr_demod);
|
|
||||||
printf("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
|
|
||||||
|
|
||||||
update_nco_pll(carrier_fine_nco, phase_error);
|
|
||||||
|
|
||||||
symbols_cpx[symbol_counter] = mixed_sample;
|
|
||||||
symbols_int[symbol_counter] = sym_demod;
|
|
||||||
symbol_counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(symbol_counter == hdr_len_symbols) {
|
|
||||||
unsigned int nsyms;
|
|
||||||
unsigned char header_enc[4];
|
|
||||||
|
|
||||||
ERR_CHECK_LIQUID(liquid_repack_bytes(
|
|
||||||
symbols_int, modem_get_bps(hdr_demod), hdr_len_symbols,
|
|
||||||
header_enc, 8, sizeof(header_enc), &nsyms));
|
|
||||||
|
|
||||||
fec_decode(hdr_fec, sizeof(header), header_enc, header);
|
|
||||||
|
|
||||||
payload_len_bytes = ((uint16_t)header[0] << 8) | header[1];
|
|
||||||
payload_crc = ((uint16_t)header[2] << 8) | header[3];
|
|
||||||
|
|
||||||
payload_len_enc_bytes = fec_get_enc_msg_length(PAYLOAD_CHANNEL_CODE, payload_len_bytes);
|
|
||||||
payload_len_symbols = (payload_len_enc_bytes * 8 + payload_bps - 1) / payload_bps;
|
|
||||||
|
|
||||||
printf("=== DECODED HEADER ===\n");
|
|
||||||
printf("Payload length: %u symbols, %u bytes encoded, %u bytes decoded\n", payload_len_symbols, payload_len_enc_bytes, payload_len_bytes);
|
|
||||||
printf("CRC16: 0x%04x\n", payload_crc);
|
|
||||||
printf("======================\n");
|
|
||||||
|
|
||||||
dump_array_cf(symbols_cpx, symbol_counter, 1.0f, "/tmp/hdr.cpx");
|
|
||||||
|
|
||||||
rx_state = RX_STATE_DATA;
|
|
||||||
symbol_counter = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RX_STATE_DATA:
|
|
||||||
nco_crcf_mix_down(carrier_fine_nco, symsync_out[symsync_out_len], &mixed_sample);
|
|
||||||
if(symbol_counter < payload_len_symbols) {
|
|
||||||
unsigned int sym_demod;
|
|
||||||
modem_demodulate(payload_demod, mixed_sample, &sym_demod);
|
|
||||||
|
|
||||||
float phase_error = modem_get_demodulator_phase_error(payload_demod);
|
|
||||||
printf("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
|
|
||||||
|
|
||||||
update_nco_pll(carrier_fine_nco, phase_error);
|
|
||||||
|
|
||||||
symbols_cpx[symbol_counter] = mixed_sample;
|
|
||||||
symbols_int[symbol_counter] = sym_demod;
|
|
||||||
symbol_counter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(symbol_counter == payload_len_symbols) {
|
|
||||||
unsigned int nsyms;
|
|
||||||
unsigned char payload_enc[16384];
|
|
||||||
unsigned char payload[16384];
|
|
||||||
|
|
||||||
ERR_CHECK_LIQUID(liquid_repack_bytes(
|
|
||||||
symbols_int, modem_get_bps(payload_demod), payload_len_symbols,
|
|
||||||
payload_enc, 8, sizeof(payload_enc), &nsyms));
|
|
||||||
|
|
||||||
fec_decode(payload_fec, payload_len_bytes, payload_enc, payload);
|
|
||||||
|
|
||||||
// de-whiten the data
|
|
||||||
whitening_apply_in_place(payload, payload_len_bytes);
|
|
||||||
|
|
||||||
int valid = crc_validate_message(PAYLOAD_CRC_SCHEME, payload, payload_len_bytes, payload_crc);
|
|
||||||
|
|
||||||
payload[payload_len_bytes] = '\0';
|
|
||||||
|
|
||||||
printf("=== DECODED PAYLOAD (valid: %d) ===\n", valid);
|
|
||||||
printf("%s\n", payload);
|
|
||||||
printf("==================================\n");
|
|
||||||
|
|
||||||
dump_array_cf(symbols_cpx, symbol_counter, 1.0f, "/tmp/payload.cpx");
|
|
||||||
rx_state = RX_STATE_ACQUISITION;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
symsync_out_len += out_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
dump_array_cf(symsync_out, symsync_out_len, 1.0f, "/tmp/synced.cpx");
|
|
||||||
|
|
||||||
#if 0
|
|
||||||
// demodulate
|
|
||||||
unsigned int bps = modem_get_bps(payload_demod);
|
|
||||||
|
|
||||||
unsigned char msg_demod_syms[nsyms];
|
|
||||||
unsigned char msg_demod[k+1];
|
|
||||||
|
|
||||||
for(size_t i = 0; i < nsyms; i++) {
|
|
||||||
unsigned int symbol;
|
|
||||||
|
|
||||||
modem_demodulate(payload_demod, msg_received[i], &symbol);
|
|
||||||
msg_demod_syms[i] = symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int received_bytes;
|
|
||||||
liquid_repack_bytes(msg_demod_syms, bps, nsyms, msg_demod, 8, sizeof(msg_demod), &received_bytes);
|
|
||||||
|
|
||||||
//assert(received_bytes == k);
|
|
||||||
|
|
||||||
// decode
|
|
||||||
uint8_t msg_dec[sizeof(msg_org)];
|
|
||||||
//memcpy(msg_dec, msg_enc, sizeof(msg_dec));
|
|
||||||
fec_decode(q, sizeof(msg_dec), msg_demod, msg_dec);
|
|
||||||
|
|
||||||
// compare original to decoded message
|
|
||||||
for(size_t i = 0; i < sizeof(msg_org); i++)
|
|
||||||
{
|
|
||||||
printf("%02x => %02x", msg_org[i], msg_dec[i]);
|
|
||||||
if(msg_org[i] != msg_dec[i]) {
|
|
||||||
printf(" <<< !!!\n");
|
|
||||||
} else {
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned int bit_errors = count_bit_errors_array(msg_org, msg_dec, sizeof(msg_org));
|
|
||||||
printf("%u bit errors detected.\n", bit_errors);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
nco_crcf_destroy(carrier_nco);
|
|
||||||
|
|
||||||
symsync_crcf_destroy(symsync);
|
|
||||||
|
|
||||||
fec_destroy(hdr_fec);
|
|
||||||
|
|
||||||
modem_destroy(payload_demod);
|
|
||||||
modem_destroy(hdr_demod);
|
|
||||||
|
|
||||||
channel_cccf_destroy(channel);
|
channel_cccf_destroy(channel);
|
||||||
|
|
||||||
|
layer1_tx_shutdown(&tx);
|
||||||
|
layer1_rx_shutdown(&rx);
|
||||||
|
|
||||||
printf("Done.\n");
|
printf("Done.\n");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue