Compare commits

...

37 Commits

Author SHA1 Message Date
Simon Ruderich 2768e21849 test_rx_file: set nco frequency 2024-04-20 01:14:17 +02:00
Simon Ruderich 79fc1c4f59 utils: fix numpy deprecation warnings 2024-04-20 01:14:17 +02:00
Thomas Kolb 7d1a927425 Calculate HackRF center frequency correctly 2024-04-20 01:07:21 +02:00
Thomas Kolb 32632914df Prevent TX exception in QPSK mode
QPSK-modulated packets require more samples, which must be available in the TX
buffer. Therefore we increase the required free space.
2024-04-20 01:04:13 +02:00
Thomas Kolb 11f19c03a0 Fix TX handling for multiple packets
- (only) time-based end-of-transmission tracking
  - removed tx_done flag
  - count zero-buffers correctly in time-tracking
  - add 10 ms of headroom so the transmission does not stop before buffer was
    completely transmitted (race condition)
  - fix race condition with tx_start_time in sdr_start_tx()
- simplified packet queuing (no chunking)
- read multiple packets before starting transmission (to fill buffers initially)

Thanks to rudi_s!
2024-04-20 00:55:39 +02:00
Thomas Kolb 1f5f922cdf Adjust squelch level and rampup 2024-04-12 21:50:49 +02:00
Thomas Kolb 91db4e1f75 rx: implement squelch + symsync reset
Whenever the squelch opens, the symsync is reset to prevent lock-up in
that module due to noise.
2024-04-08 22:03:09 +02:00
Thomas Kolb 4b46d87edb Add missing variable declaration 2024-03-31 22:26:34 +02:00
Thomas Kolb 93fd8aebbc 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.
2024-03-31 22:23:04 +02:00
Thomas Kolb 4f4128fbd3 test: fixed build errors 2024-03-31 18:01:00 +02:00
Thomas Kolb 8441b12fbd rx: delay frequency estimation until history was fully refreshed 2024-03-30 22:10:13 +01:00
Thomas Kolb 171a4a369c rx: fix header checksum verification 2024-03-30 22:07:08 +01:00
Thomas Kolb 79d7f74dfc rx: use different PLL BW for header and data 2024-03-30 22:06:13 +01:00
Thomas Kolb bbf7bcf2f0 test/freq_est: add channel simulation 2024-03-30 22:05:02 +01:00
Thomas Kolb 66c88bf889 Improvements to debug scripts 2024-03-30 22:04:26 +01:00
Thomas Kolb d9707ac4a9 test: add test_rx_file 2024-03-30 21:56:00 +01:00
Thomas Kolb dd5712eada Add options parsing module 2024-03-30 21:51:27 +01:00
Thomas Kolb 07555edfdf Add CRC8 to the header
This should prevent wrong decodings of modcod and packet length, which may disrupt decoding of directly following packets.
2024-01-07 00:02:26 +01:00
Thomas Kolb 16aa2ce92c correlator: improved phase estimation
The phase is now calculated based on the preamble correlation directly instead
of the method of the frequency estimator.
2024-01-06 23:54:41 +01:00
Thomas Kolb ef25ca2388 Enable Hamming 8/4 code for the header 2024-01-05 21:29:41 +01:00
Thomas Kolb 84c172abc7 Fixed iterative frequency refinement; count header errors 2024-01-05 21:28:38 +01:00
Thomas Kolb 3a4be428b5 sdr: stop reading packets if the TX buffer is too full 2024-01-05 14:13:11 +01:00
Thomas Kolb e807f0617b rx: skip zero-length packets
If passed on, these cause a segmentation fault in the channel decoder. As
zero-length packets currently make no real sense in this protocol, they are
ignored now.
2024-01-05 13:49:50 +01:00
Thomas Kolb a6f12d876b sdr: fixed RX sample data conversion 2024-01-05 13:48:41 +01:00
Thomas Kolb be5fa06950 Handle SIGTERM and SIGINT for graceful shutdown 2024-01-03 22:03:19 +01:00
Thomas Kolb ee63483b8f sdr: scale sample for HackRF API 2024-01-03 17:41:35 +01:00
Thomas Kolb ac087634dd sdr: ensure that all samples were transmitted before TX is stopped
This is done by tracking the time that the transmitter must stay on based on
the number of samples to be transmitted and the sampling rate.
2024-01-03 17:32:51 +01:00
Thomas Kolb 8689ed5b27 Use fsleep instead of nanosleep 2024-01-03 17:29:51 +01:00
Thomas Kolb fb9c26ccd5 make.sh: allow passing parameters to `make` 2024-01-03 17:29:05 +01:00
Thomas Kolb 5564f1751a Fixed TX gain setting 2024-01-03 17:04:47 +01:00
Thomas Kolb c9d53ed96e Ensure that buffer is full when transmission starts 2024-01-03 00:33:54 +01:00
Thomas Kolb 628659511c sdr: properly flush the transmit buffers 2024-01-03 00:25:55 +01:00
Thomas Kolb 497498acd2 rx: fix overflow in symbol buffer 2024-01-03 00:01:27 +01:00
Thomas Kolb ef0fad4335 Improved handling of up- and downsampling 2024-01-02 23:49:55 +01:00
Thomas Kolb d55e3ab902 sdr: release buffered data; set sampling rate 2024-01-02 23:25:14 +01:00
Thomas Kolb 2b741acd8a Use libhackrf directly (not tested yet) 2024-01-02 22:49:16 +01:00
Thomas Kolb 7995f5bef0 Print RX stats periodically instead of debug logging 2024-01-02 19:04:19 +01:00
23 changed files with 980 additions and 322 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.2)
cmake_minimum_required (VERSION 3.20)
project (hamnet70 VERSION 0.1 LANGUAGES C)
set(CMAKE_C_STANDARD 99)
@ -10,6 +10,8 @@ include_directories(src)
set(sources
src/utils.c
src/utils.h
src/options.c
src/options.h
src/main.c
src/results.h
src/config.h
@ -53,7 +55,7 @@ target_link_libraries(
rt
fftw3f
fec
SoapySDR
hackrf
)
add_subdirectory(test)

View File

@ -2,5 +2,6 @@
mkdir -p build
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=true ..
make
#cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_EXPORT_COMPILE_COMMANDS=true ..
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=true ..
make $@

View File

@ -16,7 +16,7 @@
#define PAYLOAD_CHANNEL_CODE LIQUID_FEC_CONV_V27P34
#define PAYLOAD_MODULATION LIQUID_MODEM_QAM16
#define HEADER_CHANNEL_CODE LIQUID_FEC_NONE
#define HEADER_CHANNEL_CODE LIQUID_FEC_HAMMING84
#define HEADER_MODULATION LIQUID_MODEM_QPSK
#define PAYLOAD_CRC_SCHEME LIQUID_CRC_16
@ -54,7 +54,7 @@
// gain configuration
#define SDR_GAIN_TX_AMP 0.0f // extra power amplifier. Set to 14.0f to activate.
#define SDR_GAIN_TX_LNA 0.0f // normal variable gain amplifier.
#define SDR_GAIN_TX_VGA 0.0f // normal variable gain amplifier.
#define SDR_GAIN_RX_AMP 0.0f // extra input amplifier. Set to 14.0f to activate.
#define SDR_GAIN_RX_LNA 30.0f // RF variable gain amplifier.

View File

@ -97,6 +97,11 @@ float complex correlator_step(correlator_ctx_t *ctx, float complex sample)
result += ctx->search_sequence[m] * ctx->input_history[nm];
}
// the current mean phase can be basically calculated for free here, as the
// sum of rotated symbols is already available, so we do so and cache that
// value.
ctx->phase_deviation = cargf(result);
return result;
}
@ -118,19 +123,10 @@ const float complex* correlator_get_conj_search_sequence(correlator_ctx_t *ctx)
}
float correlator_get_mean_phase_deviation(correlator_ctx_t *ctx, size_t L)
float correlator_get_mean_phase_deviation(correlator_ctx_t *ctx)
{
float complex mean_unrotated_symbol = 0;
size_t n = (ctx->history_ptr - ctx->search_sequence_len + 1) & ctx->buffer_size_mask;
for(size_t m = ctx->search_sequence_len - L; m < ctx->search_sequence_len; m++) {
size_t nm = (n + m) & ctx->buffer_size_mask;
mean_unrotated_symbol += ctx->search_sequence[m] * ctx->input_history[nm];
}
// no need to divide by ctx->search_sequence_len because division does not change the angle
return cargf(mean_unrotated_symbol);
// return cached value from last call to correlator_step().
return ctx->phase_deviation;
}
@ -147,5 +143,12 @@ float correlator_get_mean_frequency_deviation(correlator_ctx_t *ctx, size_t L, f
z[m - m0] = conjf(ctx->search_sequence[m]) * ctx->input_history[nm];
}
return freq_est_data_free(z, L, phase_offset);
float freq = freq_est_data_free(z, L, NULL);
// we calculate the final phase based on the phase estimated from the
// preamble and not the phase from the frequency estimator because this
// method is more reliable.
*phase_offset = ctx->phase_deviation + (ctx->search_sequence_len+1)/2.0f * freq;
return freq;
}

View File

@ -24,6 +24,8 @@ typedef struct
float complex *search_sequence;
float complex *input_history;
float phase_deviation;
/*!
* \brief The history pointer.
*
@ -120,10 +122,9 @@ const float complex* correlator_get_conj_search_sequence(correlator_ctx_t *ctx);
* found.
*
* \param ctx The correlator context to use.
* \param L Number of symbols to use (at the end of the sequence)
* \returns The mean phase deviation in radians.
*/
float correlator_get_mean_phase_deviation(correlator_ctx_t *ctx, size_t L);
float correlator_get_mean_phase_deviation(correlator_ctx_t *ctx);
/*!
* \brief Calculate mean frequency deviation between current input and search sequence.

View File

@ -148,7 +148,7 @@ float freq_est_in_rampup(const float complex *recv, size_t n, float *final_phase
float phase = cargf(rotated[i]);
if(i > 0) {
float phase_delta = phase - prev_phase;
if(fabsf(phase_delta) > 3.14159f/4.0f) {
if(fabsf(phase_delta) > 3.14159f/2.0f) {
// abort because we either have the wrong signal or too much noise.
if(final_phase) {
*final_phase = 0.0f;

View File

@ -173,7 +173,7 @@ result_t packet_mod_add_header(packet_mod_ctx_t *ctx)
}
// build the header. All field are transferred in network byte order (big endian).
uint8_t header[4];
uint8_t header[5];
// set length
header[0] = (ctx->raw_data_len >> 8) & 0x07;
@ -183,6 +183,9 @@ result_t packet_mod_add_header(packet_mod_ctx_t *ctx)
header[2] = (ctx->raw_data_crc >> 8) & 0xFF;
header[3] = (ctx->raw_data_crc >> 0) & 0xFF;
// append an 8-bit CRC to the header
crc_append_key(LIQUID_CRC_8, header, 4);
// note: the header is coded and modulated differently than the data.
// encode the header

View File

@ -1,7 +1,7 @@
#include <liquid/liquid.h>
#include <math.h>
#include <assert.h>
#include <math.h>
#include "correlator.h"
#include "preamble.h"
@ -9,12 +9,15 @@
#include "whitening.h"
#include "config.h"
#include "options.h"
#include "rx.h"
#include "utils.h"
#define HEADER_SIZE_BYTES 4
#define SYMBOL_BUFFER_SIZE 16384
#define HEADER_SIZE_BYTES 5 // including CRC
#define FREQ_EST_L 24
#define AGC_BW_ACQUISITION 5e-2f
@ -30,21 +33,24 @@
# define DEBUG_LOG(...) {}
#endif
static void update_nco_pll(nco_crcf nco, float phase_error)
#define PLL_BW_HEADER 0.03f
#define PLL_BW_DATA 0.01f
static void update_nco_pll(nco_crcf nco, float phase_error, float bw)
{
static const float pll_alpha = 0.03f; // phase adjustment factor
static const float pll_beta = (pll_alpha * pll_alpha) / 2.0f; // frequency adjustment factor
const float pll_alpha = bw; // phase adjustment factor
const float pll_beta = (pll_alpha * pll_alpha) * 1.0f; // frequency adjustment factor
nco_crcf_adjust_phase(nco, pll_alpha * phase_error);
nco_crcf_adjust_frequency(nco, pll_beta * phase_error);
DEBUG_LOG("NCO adjusted: f=%.6f, φ=%.6f\n",
/*DEBUG_LOG("NCO adjusted: f=%.6f, φ=%.6f\n",
nco_crcf_get_frequency(nco),
nco_crcf_get_phase(nco));
nco_crcf_get_phase(nco));*/
}
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 unsigned int freq_est_history_write_idx = 0;
@ -84,8 +90,9 @@ static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
// start over with frequency estimation when preamble search restarts.
freq_est_history_write_idx = 0;
freq_est_holdoff_samples = FREQ_EST_L;
return true; // preamble found!
} else {
} else if(do_coarse_freq_est) {
// preamble not found.
//DEBUG_LOG("Preamble not found: %.3f < %.3f\n", cabsf(corr_out), 0.5f * preamble_get_symbol_count());
@ -101,21 +108,26 @@ static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
float freq_est = freq_est_in_rampup(freq_est_history, FREQ_EST_L, NULL);
float freq_adjustment = freq_est / RRC_SPS;
nco_crcf_adjust_frequency(rx->carrier_coarse_nco, freq_adjustment);
float actual_freq = nco_crcf_get_frequency(rx->carrier_coarse_nco);
if(actual_freq > MAX_COARSE_FREQ_OFFSET) {
//fprintf(stderr, ">");
nco_crcf_set_frequency(rx->carrier_coarse_nco, MAX_COARSE_FREQ_OFFSET);
} else if(actual_freq < -MAX_COARSE_FREQ_OFFSET) {
//fprintf(stderr, "<");
nco_crcf_set_frequency(rx->carrier_coarse_nco, -MAX_COARSE_FREQ_OFFSET);
}
//freq_est_history_write_idx = 0;
// freq_est_in_rampup will return exactly 0.0 if (and probably only if)
// the sequency quality is not good enough. We skip the adjustment in
// this case.
if(freq_est != 0.0f) {
// apply the frequency adjustment
float freq_adjustment = freq_est / RRC_SPS / FREQ_EST_L;
nco_crcf_adjust_frequency(rx->carrier_coarse_nco, freq_adjustment);
// limit the absolute frequency offset value
float actual_freq = nco_crcf_get_frequency(rx->carrier_coarse_nco);
if(actual_freq > MAX_COARSE_FREQ_OFFSET) {
//fprintf(stderr, ">");
nco_crcf_set_frequency(rx->carrier_coarse_nco, MAX_COARSE_FREQ_OFFSET);
} else if(actual_freq < -MAX_COARSE_FREQ_OFFSET) {
//fprintf(stderr, "<");
nco_crcf_set_frequency(rx->carrier_coarse_nco, -MAX_COARSE_FREQ_OFFSET);
}
//freq_est_history_write_idx = 0;
DEBUG_LOG("Freq. est (x%d): %0.6f - Adj (x1): %.6f - carrier frequency (x1): %.6f\n",
RRC_SPS, freq_est, freq_adjustment, nco_crcf_get_frequency(rx->carrier_coarse_nco));
@ -130,22 +142,69 @@ static bool acquire_preamble(layer1_rx_t *rx, const float complex sample)
freq_est_history[freq_est_history_write_idx] = sample;
freq_est_history_write_idx++;
freq_est_history_write_idx %= FREQ_EST_L;
return false; // preamble not found
}
return false; // preamble not found
}
typedef enum squelch_state_t {
SQUELCH_CLOSED,
SQUELCH_OPEN,
SQUELCH_JUST_OPENED,
};
static enum squelch_state_t update_and_check_squelch(layer1_rx_t *rx, unsigned int sample_idx)
{
float level = agc_crcf_get_rssi(rx->agc);
enum squelch_state_t result = SQUELCH_CLOSED;
if((sample_idx & 0xFF) == 0) { // every 256 samples
if(level < rx->noise_floor_level) {
rx->noise_floor_level = level;
} else {
level += 1e-4f; // slowly increase the measured noise floor level to compensate for drift
}
agc_crcf_squelch_set_threshold(rx->agc, rx->noise_floor_level + 10.0f); // in dB
}
switch(agc_crcf_squelch_get_status(rx->agc)) {
case LIQUID_AGC_SQUELCH_RISE:
DEBUG_LOG("Squelch disabled at #%u RSSI = %.3f dB [thr: %.3f dB]\n", sample_idx, level, agc_crcf_squelch_get_threshold(rx->agc));
result = SQUELCH_JUST_OPENED;
break;
case LIQUID_AGC_SQUELCH_SIGNALHI:
result = SQUELCH_OPEN;
break;
case LIQUID_AGC_SQUELCH_FALL:
DEBUG_LOG("Squelch enabled at #%u RSSI = %.3f dB [thr: %.3f dB]\n", sample_idx, level, agc_crcf_squelch_get_threshold(rx->agc));
// fall through
case LIQUID_AGC_SQUELCH_SIGNALLO:
case LIQUID_AGC_SQUELCH_ENABLED:
case LIQUID_AGC_SQUELCH_TIMEOUT:
result = SQUELCH_CLOSED;
break;
}
return result;
}
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];
static uint8_t symbols_int[SYMBOL_BUFFER_SIZE];
float complex samples2dump[sample_count];
size_t nsamples2dump = 0;
float complex samples2dump2[sample_count];
size_t nsamples2dump2 = 0;
// cache configuration flags
bool is_central_node = options_is_flag_set(OPTIONS_FLAG_IS_CENTRAL_NODE);
// filter the input
float complex filtered_samples[sample_count];
firfilt_crcf_execute_block(rx->channel_filter, (float complex *)samples, sample_count, filtered_samples);
@ -159,11 +218,33 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
float complex agc_out;
agc_crcf_execute(rx->agc, filtered_samples[i], &agc_out);
switch(update_and_check_squelch(rx, i)) {
case SQUELCH_CLOSED:
// ignore this sample
continue;
case SQUELCH_JUST_OPENED:
symsync_crcf_reset(rx->symsync);
// fall through
case SQUELCH_OPEN:
// sample processing continues
break;
}
// 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, 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;
@ -173,7 +254,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);
if(out_len != 0) {
switch(rx->state) {
// Try to acquire packets by synchronizing the frequency
// (symbol-independent search) and correlating the preamble.
@ -186,7 +266,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);
}
if(acquire_preamble(rx, symsync_out)) {
if(acquire_preamble(rx, symsync_out, !is_central_node)) {
// Preamble found and frequency corrected!
rx->callback(RX_EVT_PREAMBLE_FOUND, NULL, 0);
@ -208,10 +288,11 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
modem_demodulate(rx->hdr_demod, mixed_sample, &sym_demod);
float phase_error = modem_get_demodulator_phase_error(rx->hdr_demod);
//DEBUG_LOG("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
DEBUG_LOG("Sym: %d; Phase error: %f %s\n", sym_demod, phase_error,
(fabs(phase_error) > 0.3) ? "!!!" : "");
update_nco_pll(rx->carrier_fine_nco, phase_error);
update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_HEADER);
symbols_int[symbol_counter] = sym_demod;
symbol_counter++;
@ -226,7 +307,22 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
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);
if(fec_decode(rx->hdr_fec, sizeof(header), header_enc, header) != LIQUID_OK) {
DEBUG_LOG("Header decoding failed!\n", rx->modcod);
rx->state = RX_STATE_ACQUISITION;
rx->callback(RX_EVT_HEADER_ERROR, NULL, 0);
break;
}
// CRC check
if(crc_check_key(LIQUID_CRC_8, header, HEADER_SIZE_BYTES) != LIQUID_OK) {
uint8_t expected_crc = crc_generate_key(LIQUID_CRC_8, header, 4);
DEBUG_LOG("Header CRC check failed! Expected: 0x%02x, received: 0x%02x\n",
expected_crc, header[4]);
rx->state = RX_STATE_ACQUISITION;
rx->callback(RX_EVT_HEADER_ERROR, NULL, 0);
break;
}
rx->payload_len_bytes = (((uint16_t)header[0] << 8) | header[1]) & 0x07FF;
rx->payload_crc = ((uint16_t)header[2] << 8) | header[3];
@ -236,6 +332,15 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
if(!MODCOD_IS_VALID(rx->modcod)) {
DEBUG_LOG("Decoded MODCOD %d is invalid!\n", rx->modcod);
rx->state = RX_STATE_ACQUISITION;
rx->callback(RX_EVT_HEADER_ERROR, NULL, 0);
break;
}
// check the payload length
if(rx->payload_len_bytes == 0) {
DEBUG_LOG("Packet length %u is not supported.\n", rx->payload_len_bytes);
rx->state = RX_STATE_ACQUISITION;
rx->callback(RX_EVT_HEADER_ERROR, NULL, 0);
break;
}
@ -245,6 +350,15 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
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;
if(rx->payload_len_symbols > SYMBOL_BUFFER_SIZE) {
DEBUG_LOG("Symbol count %u is too lange for buffer. Ignoring packet.\n", rx->payload_len_symbols);
rx->state = RX_STATE_ACQUISITION;
rx->callback(RX_EVT_HEADER_ERROR, NULL, 0);
break;
}
assert(rx->payload_len_symbols < sizeof(symbols_int)/sizeof(symbols_int[0]));
DEBUG_LOG("=== DECODED HEADER ===\n");
DEBUG_LOG("Payload length: %u symbols, %u bytes encoded, %u bytes decoded\n", rx->payload_len_symbols, rx->payload_len_enc_bytes, rx->payload_len_bytes);
DEBUG_LOG("CRC16: 0x%04x\n", rx->payload_crc);
@ -269,7 +383,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
float phase_error = modem_get_demodulator_phase_error(rx->payload_demod);
//DEBUG_LOG("Sym: %d; Phase error: %f\n", sym_demod, phase_error);
update_nco_pll(rx->carrier_fine_nco, phase_error);
update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_DATA);
symbols_int[symbol_counter] = sym_demod;
symbol_counter++;
@ -340,6 +454,11 @@ result_t layer1_rx_init(layer1_rx_t *rx, rx_callback_t callback)
rx->agc = agc_crcf_create();
agc_crcf_set_bandwidth(rx->agc, AGC_BW_ACQUISITION);
// set up squelch
rx->noise_floor_level = 0.0f;
agc_crcf_squelch_set_threshold(rx->agc, rx->noise_floor_level + 3.0f); // in dB
agc_crcf_squelch_enable(rx->agc);
// 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);
@ -395,3 +514,9 @@ bool layer1_rx_is_busy(const layer1_rx_t *rx)
{
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

@ -18,6 +18,7 @@ typedef enum {
typedef enum {
RX_EVT_PACKET_RECEIVED,
RX_EVT_PREAMBLE_FOUND,
RX_EVT_HEADER_ERROR,
RX_EVT_CHECKSUM_ERROR,
} rx_evt_t;
@ -33,6 +34,8 @@ typedef struct
// AGC
agc_crcf agc;
float noise_floor_level;
// NCOs for carrier frequency offset correction
nco_crcf carrier_coarse_nco;
nco_crcf carrier_fine_nco;
@ -119,4 +122,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);
/*!
* \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

View File

@ -1,3 +1,5 @@
#include <liquid/liquid.h>
#include <stdbool.h>
#include <malloc.h>
@ -41,6 +43,8 @@ result_t layer1_tx_init(layer1_tx_t *tx)
tx->samples_allocated = 0;
tx->samples_used = 0;
tx->carrier_frequency_offset = 0.0f;
transmission_init(&tx->transmission);
packet_mod_init(&tx->pmod);
return OK;
@ -154,6 +158,28 @@ result_t layer1_tx_finalize_burst(layer1_tx_t *tx)
}
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;
}
@ -168,3 +194,9 @@ const float complex* layer1_tx_get_sample_data(const layer1_tx_t *tx)
{
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_used;
float carrier_frequency_offset;
transmission_ctx_t transmission;
packet_mod_ctx_t pmod;
} 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);
/*!
* \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

View File

@ -1,21 +1,18 @@
#include <linux/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#include <SoapySDR/Logger.h>
#include <liquid/liquid.h>
#include <sys/poll.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include "utils.h"
#include "options.h"
#include "layer1/tx.h"
#include "layer1/rx.h"
@ -38,6 +35,24 @@ static bool m_running = true;
static double next_tx_switch_time = 0.0;
static struct {
size_t preambles_found;
size_t successful_decodes;
size_t failed_decodes;
size_t header_errors;
} m_stats;
static void signal_handler(int signal, siginfo_t *info, void *ctx)
{
(void)signal;
(void)info;
(void)ctx;
fprintf(stderr, "\nGracefully shutting down on signal %d.\n", signal);
m_running = false;
}
static void block_tx_for(unsigned offset_ms)
{
@ -102,18 +117,25 @@ void cb_rx(rx_evt_t evt, uint8_t *packet_data, size_t packet_len)
switch(evt)
{
case RX_EVT_CHECKSUM_ERROR:
fprintf(stderr, "Received a message of %zu bytes, but decoding failed.\n", packet_len);
//fprintf(stderr, "Received a message of %zu bytes, but decoding failed.\n", packet_len);
//fprintf(stderr, "=== FAILED PAYLOAD ===\n");
//hexdump(packet_data, packet_len);
//fprintf(stderr, "=======================\n");
m_stats.failed_decodes++;
break;
case RX_EVT_HEADER_ERROR:
m_stats.header_errors++;
break;
case RX_EVT_PACKET_RECEIVED:
fprintf(stderr, "A message of %zu bytes was decoded successfully.\n", packet_len);
//fprintf(stderr, "A message of %zu bytes was decoded successfully.\n", packet_len);
//fprintf(stderr, "=== DECODED PAYLOAD (%4zu bytes) ===\n", packet_len);
//hexdump(packet_data, packet_len < 64 ? packet_len : 64);
//fprintf(stderr, "====================================\n");
m_stats.successful_decodes++;
block_tx_for(TX_SWITCH_BACKOFF_END_OF_PACKET_MS);
ret = write(m_tunfd, packet_data, packet_len);
@ -123,8 +145,9 @@ void cb_rx(rx_evt_t evt, uint8_t *packet_data, size_t packet_len)
break;
case RX_EVT_PREAMBLE_FOUND:
fprintf(stderr, "Found preamble!\n");
//fprintf(stderr, "Found preamble!\n");
block_tx_for(TX_SWITCH_BACKOFF_PREAMBLE_MS);
m_stats.preambles_found++;
break;
}
}
@ -132,60 +155,32 @@ void cb_rx(rx_evt_t evt, uint8_t *packet_data, size_t packet_len)
static int debug_fd;
#define CHUNKSIZE_BB 16384
#define CHUNKSIZE_RF (CHUNKSIZE_BB * SDR_OVERSAMPLING)
static result_t transmit_in_chunks(sdr_ctx_t *sdr, const float complex *samples, size_t len)
static result_t transmit(sdr_ctx_t *sdr, const float complex *samples, size_t len)
{
size_t transmitted = 0;
unsigned retry_counter = 0;
size_t to_transmit_rf = len * SDR_OVERSAMPLING;
float complex rf_samples[to_transmit_rf];
float complex rf_samples[CHUNKSIZE_RF];
RESULT_CHECK(sdr_baseband_to_rf(sdr, samples, len, rf_samples, &to_transmit_rf));
while(transmitted < len) {
size_t to_transmit_bb = len - transmitted;
if(to_transmit_bb > CHUNKSIZE_BB) {
to_transmit_bb = CHUNKSIZE_BB;
}
result_t result = sdr_transmit(sdr, rf_samples, to_transmit_rf, 100000);
RESULT_CHECK(sdr_baseband_to_rf(sdr, samples + transmitted, to_transmit_bb, rf_samples, CHUNKSIZE_RF));
size_t to_transmit_rf = to_transmit_bb * SDR_OVERSAMPLING;
result_t result = sdr_transmit(sdr, rf_samples, to_transmit_rf, 100000);
if(result != OK) {
retry_counter++;
fprintf(stderr, "sdr_transmit failed %d times\n", retry_counter);
if(retry_counter > 3) {
return result;
} else {
continue;
}
}
//write(debug_fd, rf_samples, to_transmit_rf*sizeof(rf_samples[0]));
fprintf(stderr, "t");
transmitted += to_transmit_bb;
retry_counter = 0;
}
return OK;
fprintf(stderr, "t");
return result;
}
int main(void)
int main(int argc, char **argv)
{
layer1_tx_t tx;
layer1_rx_t rx;
sdr_ctx_t sdr;
SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
int parsed_options = options_parse(argc, argv);
fprintf(stderr, "%d options parsed.\n", parsed_options);
if(parsed_options < 0) {
return EXIT_FAILURE;
}
bool on_air = true;
@ -205,6 +200,21 @@ int main(void)
RESULT_CHECK(layer1_tx_init(&tx));
RESULT_CHECK(layer1_rx_init(&rx, cb_rx));
// ** Set up signal handling
struct sigaction term_action = {0};
term_action.sa_sigaction = signal_handler;
if(sigaction(SIGTERM, &term_action, NULL) < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
if(sigaction(SIGINT, &term_action, NULL) < 0) {
perror("sigaction");
exit(EXIT_FAILURE);
}
// ** Process packets **
struct pollfd pfd;
@ -220,7 +230,7 @@ int main(void)
double old = get_hires_time();
size_t total_samples = 0;
double next_rate_print_time = old + 0.5;
double next_stats_print_time = old + 0.5;
while(m_running) {
double now = get_hires_time();
@ -232,6 +242,18 @@ int main(void)
break;
} else if(ret > 0) {
// there is a packet to be read.
// check free buffer space (50 ms required corresponding to 5000 baseband symbols)
size_t buffer_free_space_samples = sdr_get_tx_buffer_free_space(&sdr);
fprintf(stderr, "TX buffer free: %zu\n", buffer_free_space_samples);
if(buffer_free_space_samples < 400000) { // sample count for 200 ms at 2 MHz
// try again after a short delay
fsleep(10e-3);
continue;
}
uint8_t packetbuf[2048];
ret = read(m_tunfd, packetbuf, sizeof(packetbuf));
if(ret < 0) {
@ -256,22 +278,39 @@ int main(void)
dump_array_cf(whole_burst, burst_len, 1.0f, "/tmp/tx.cpx");
// ensure that the buffer is full before TX is turned on to avoid transmitting empty buffers
RESULT_CHECK(transmit(&sdr, whole_burst, burst_len));
if(!on_air) {
//RESULT_CHECK(sdr_stop_rx(&sdr));
size_t buffer_used_samples = sdr_get_tx_buffer_used_space(&sdr);
const size_t min_required_samples = 4*128*1024;
int ret2 = poll(&pfd, 1, 0);
if(ret2 < 0) {
perror("poll");
break;
} else if(ret2 != 0 && (buffer_used_samples < min_required_samples)) {
// enqueue more packets before starting TX
fprintf(stderr, "Pre-buffering more packets: %zd / %zd samples.\n", buffer_used_samples, min_required_samples);
continue;
}
fprintf(stderr, "RX -> TX\n");
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));
// hopefully no longer needed with latest libhackrf
//RESULT_CHECK(sdr_flush_tx_buffer(&sdr));
}
RESULT_CHECK(transmit_in_chunks(&sdr, whole_burst, burst_len));
on_air = true;
} else if(on_air) { // ret == 0
//RESULT_CHECK(sdr_flush_tx_buffer(&sdr));
fprintf(stderr, "TX -> RX\n");
RESULT_CHECK(sdr_flush_tx_buffer(&sdr));
//RESULT_CHECK(sdr_stop_tx(&sdr));
//RESULT_CHECK(sdr_start_rx(&sdr));
RESULT_CHECK(sdr_stop_tx(&sdr));
RESULT_CHECK(sdr_start_rx(&sdr));
on_air = false;
block_tx_for(TX_SWITCH_BACKOFF_AFTER_RX_ON);
@ -285,7 +324,7 @@ int main(void)
float complex bb_samples[CHUNKSIZE_BB];
size_t n_rf_samples = CHUNKSIZE_RF;
size_t n_bb_samples;
size_t n_bb_samples = CHUNKSIZE_BB;
if(sdr_receive(&sdr, rf_samples, &n_rf_samples, 100000) != OK) {
rx_retries++;
@ -297,13 +336,26 @@ int main(void)
}
}
if(n_rf_samples == 0) {
fsleep(1e-3);
continue;
}
total_samples += n_rf_samples;
double new = get_hires_time();
if(new >= next_rate_print_time) {
if(new >= next_stats_print_time) {
double rate = total_samples / (new - old);
fprintf(stderr, "\nEstimated rate: %.3f MS/s\n", rate / 1e6);
next_rate_print_time += 0.5;
fprintf(stderr, "Receiver statistics:\n");
fprintf(stderr, " Preambles found: %8zd\n", m_stats.preambles_found);
fprintf(stderr, " Successful decodes: %8zd (%6.2f %%)\n",
m_stats.successful_decodes, m_stats.successful_decodes * 100.0f / m_stats.preambles_found);
fprintf(stderr, " Header errors: %8zd (%6.2f %%)\n",
m_stats.header_errors, m_stats.header_errors * 100.0f / m_stats.preambles_found);
fprintf(stderr, " Failed decodes: %8zd (%6.2f %%)\n",
m_stats.failed_decodes, m_stats.failed_decodes * 100.0f / m_stats.preambles_found);
next_stats_print_time += 0.5;
total_samples = 0;
old = new;
@ -313,9 +365,7 @@ int main(void)
fprintf(stderr, "r");
RESULT_CHECK(sdr_rf_to_baseband(&sdr, rf_samples, n_rf_samples, bb_samples, CHUNKSIZE_BB));
n_bb_samples = n_rf_samples / SDR_OVERSAMPLING;
RESULT_CHECK(sdr_rf_to_baseband(&sdr, rf_samples, n_rf_samples, bb_samples, &n_bb_samples));
RESULT_CHECK(layer1_rx_process(&rx, bb_samples, n_bb_samples));
} else {

37
impl/src/options.c Normal file
View File

@ -0,0 +1,37 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include "options.h"
static uint32_t m_flags;
int options_parse(int argc, char **argv)
{
int opt;
int nparsed = 0;
while((opt = getopt(argc, argv, "c")) != -1) {
switch(opt) {
case 'c':
m_flags |= (1 << OPTIONS_FLAG_IS_CENTRAL_NODE);
break;
default:
fprintf(stderr,
"Usage: %s [-c]\n\n"
" -c Makes this node a central node (base station).\n",
argv[0]);
exit(EXIT_FAILURE);
}
nparsed++;
}
return nparsed;
}
bool options_is_flag_set(options_flag_t flag)
{
return (m_flags & (1 << flag)) != 0;
}

14
impl/src/options.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef OPTIONS_H
#define OPTIONS_H
#include <stdbool.h>
typedef enum {
OPTIONS_FLAG_IS_CENTRAL_NODE,
} options_flag_t;
int options_parse(int argc, char **argv);
bool options_is_flag_set(options_flag_t flag);
#endif // OPTIONS_H

View File

@ -9,6 +9,7 @@ typedef enum {
ERR_LIQUID, // an error occurred in the LiquidDSP library.
ERR_SYSCALL, // a syscall failed. Use errno to determine the cause.
ERR_SOAPY, // an error occurred in the SoapySDR library.
ERR_SDR, // an error occurred in the SDR interface.
} result_t;
#ifdef DEBUG_LIQUID

View File

@ -1,43 +1,186 @@
#include <SoapySDR/Device.h>
#include <SoapySDR/Formats.h>
#include <SoapySDR/Logger.h>
#include <libhackrf/hackrf.h>
#include <liquid/liquid.h>
#include <semaphore.h>
#include <stdio.h> //printf
#include <stdlib.h> //free
#include <complex.h>
#include <math.h>
#include <string.h>
#include <assert.h>
#include "config.h"
#include "utils.h"
#include "sdr.h"
void soapy_log_handler(const SoapySDRLogLevel logLevel, const char *message)
#define SDR_BUFFER_SIZE_SAMPLES 2048000
#define CHECK_HACKRF_RESULT(result, call) do { \
if(result != HACKRF_SUCCESS) { \
fprintf(stderr, call "() failed: %s (%d)\n", hackrf_error_name(result), result); \
return ERR_SDR; \
} \
} while(0);
static int rx_callback(hackrf_transfer *transfer)
{
fprintf(stderr, "soapy [%d]: %s\n", logLevel, message);
sdr_ctx_t *sdr_ctx = (sdr_ctx_t*)transfer->rx_ctx;
size_t nsamples = transfer->valid_length / 2;
if(sem_wait(&sdr_ctx->buf_sem) < 0) {
perror("sem_wait failed. Samples lost. Error:");
return HACKRF_ERROR_OTHER;
}
for(size_t i = 0; i < nsamples; i++) {
liquid_float_complex sample =
0.0078125f * ((int8_t)transfer->buffer[2*i + 0] + I * (int8_t)transfer->buffer[2*i + 1]);
int result = cbuffercf_push(sdr_ctx->rx_buf, sample);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_push failed on sample %zu of %zu: %d. Samples are lost.\n", i, nsamples, result);
break;
}
}
if(sem_post(&sdr_ctx->buf_sem) < 0) {
perror("sem_post");
return HACKRF_ERROR_OTHER;
}
return 0;
}
static void close_streams(sdr_ctx_t *ctx)
static inline int8_t clamp_float2int8(float s)
{
if(ctx->rx_stream) {
SoapySDRDevice_deactivateStream(ctx->sdr, ctx->rx_stream, 0, 0);
SoapySDRDevice_closeStream(ctx->sdr, ctx->rx_stream);
s *= 127.0f;
if(s >= 127.0f) {
return 127;
} else if(s <= -128.0f) {
return -128;
} else {
return (int8_t)(s + 0.5f);
}
}
static int tx_callback(hackrf_transfer *transfer)
{
int result;
int retcode = 0;
fprintf(stderr, "tx_callback()\n");
sdr_ctx_t *sdr_ctx = (sdr_ctx_t*)transfer->tx_ctx;
if(transfer->valid_length % 2 != 0) {
fprintf(stderr, "WARNING! Buffer unaligned!\n");
}
if(ctx->tx_stream) {
SoapySDRDevice_deactivateStream(ctx->sdr, ctx->tx_stream, 0, 0);
SoapySDRDevice_closeStream(ctx->sdr, ctx->tx_stream);
if(sem_wait(&sdr_ctx->buf_sem) < 0) {
perror("sem_wait");
return HACKRF_ERROR_OTHER;
}
unsigned int samples_requested = transfer->valid_length / 2;
unsigned int samples_read;
liquid_float_complex *samples;
result = cbuffercf_read(sdr_ctx->tx_buf, samples_requested, &samples, &samples_read);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_read failed: %d. Samples are lost.\n", result);
sem_post(&sdr_ctx->buf_sem);
return HACKRF_ERROR_OTHER;
}
if(sdr_ctx->tx_start_time == 0.0) {
sdr_ctx->tx_start_time = get_hires_time();
sdr_ctx->tx_duration = 10e-3; // give a little headroom
fprintf(stderr, "TX time tracking reset: start = %.3f.\n", sdr_ctx->tx_start_time);
}
for(size_t i = 0; i < samples_read; i++) {
transfer->buffer[2*i + 0] = clamp_float2int8(creal(samples[i]));
transfer->buffer[2*i + 1] = clamp_float2int8(cimag(samples[i]));
}
if(samples_read != 0) {
// only add time if any actual samples were transmitted
sdr_ctx->tx_duration += (double)samples_requested / SDR_TX_SAMPLING_RATE;
}
fprintf(stderr, "copied %u samples to HackRF.\n", samples_read);
if(samples_read < samples_requested) {
// fill the rest with zeros
memset(&transfer->buffer[2*samples_read], 0, 2*(samples_requested-samples_read));
}
result = cbuffercf_release(sdr_ctx->tx_buf, samples_read);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_release failed: %d.\n", result);
sem_post(&sdr_ctx->buf_sem);
return ERR_LIQUID;
}
if(sem_post(&sdr_ctx->buf_sem) < 0) {
perror("sem_post");
return HACKRF_ERROR_OTHER;
}
return retcode;
}
static result_t stop_streaming(sdr_ctx_t *ctx)
{
int result;
switch(ctx->status) {
case SDR_STATUS_RX:
result = hackrf_stop_rx(ctx->hackrf);
CHECK_HACKRF_RESULT(result, "hackrf_stop_rx");
break;
case SDR_STATUS_TX:
result = hackrf_stop_tx(ctx->hackrf);
CHECK_HACKRF_RESULT(result, "hackrf_stop_tx");
break;
default:
// no action needed
return OK;
}
while(hackrf_is_streaming(ctx->hackrf) == HACKRF_TRUE) {
puts("wr");
fsleep(1e-3);
}
fsleep(10e-3); // ensure all asynchronous USB communication has stopped
ctx->status = SDR_STATUS_IDLE;
return OK;
}
result_t sdr_init(sdr_ctx_t *ctx)
{
size_t length;
int result;
ctx->sdr = NULL;
ctx->tx_stream = NULL;
ctx->rx_stream = NULL;
result = sem_init(&ctx->buf_sem, 0, 1);
if(result < 0) {
perror("sem_init failed");
return ERR_SDR;
}
hackrf_init();
result = hackrf_open(&ctx->hackrf);
CHECK_HACKRF_RESULT(result, "hackrf_open");
ctx->status = SDR_STATUS_IDLE;
ctx->interp = firinterp_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f);
ctx->decim = firdecim_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f);
@ -48,88 +191,8 @@ result_t sdr_init(sdr_ctx_t *ctx)
nco_crcf_set_frequency(ctx->tx_nco, 2 * M_PI * SDR_TX_IF_SHIFT / SDR_TX_SAMPLING_RATE);
nco_crcf_set_frequency(ctx->rx_nco, 2 * M_PI * SDR_RX_IF_SHIFT / SDR_RX_SAMPLING_RATE);
// set up logging
SoapySDR_registerLogHandler(soapy_log_handler);
//enumerate devices
SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length);
for (size_t i = 0; i < length; i++)
{
fprintf(stderr, "sdr: Found device #%d:\n", (int)i);
for (size_t j = 0; j < results[i].size; j++)
{
fprintf(stderr, "sdr: - %s=%s\n", results[i].keys[j], results[i].vals[j]);
}
fprintf(stderr, "sdr: \n");
}
SoapySDRKwargsList_clear(results, length);
//create device instance
//args can be user defined or from the enumeration result
SoapySDRKwargs args;
memset(&args, 0, sizeof(args));
SoapySDRKwargs_set(&args, "driver", "hackrf");
ctx->sdr = SoapySDRDevice_make(&args);
SoapySDRKwargs_clear(&args);
if (ctx->sdr == NULL)
{
fprintf(stderr, "sdr: SoapySDRDevice_make fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
//query device info
char** names = SoapySDRDevice_listAntennas(ctx->sdr, SOAPY_SDR_RX, 0, &length);
fprintf(stderr, "sdr: Rx antennas: ");
for (size_t i = 0; i < length; i++) fprintf(stderr, "%s, ", names[i]);
fprintf(stderr, "\n");
SoapySDRStrings_clear(&names, length);
names = SoapySDRDevice_listGains(ctx->sdr, SOAPY_SDR_RX, 0, &length);
fprintf(stderr, "sdr: Rx gains: ");
for (size_t i = 0; i < length; i++) fprintf(stderr, "%s, ", names[i]);
fprintf(stderr, "\n");
SoapySDRStrings_clear(&names, length);
SoapySDRRange *ranges = SoapySDRDevice_getFrequencyRange(ctx->sdr, SOAPY_SDR_RX, 0, &length);
fprintf(stderr, "sdr: Rx freq ranges: ");
for (size_t i = 0; i < length; i++) fprintf(stderr, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
fprintf(stderr, "\n");
free(ranges);
//setup streams
ctx->rx_stream = SoapySDRDevice_setupStream(ctx->sdr, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL);
if(ctx->rx_stream == NULL) {
fprintf(stderr, "sdr: setupStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
ctx->tx_stream = SoapySDRDevice_setupStream(ctx->sdr, SOAPY_SDR_TX, SOAPY_SDR_CF32, NULL, 0, NULL);
if(ctx->tx_stream == NULL) {
fprintf(stderr, "sdr: setupStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
//apply settings
if (SoapySDRDevice_setSampleRate(ctx->sdr, SOAPY_SDR_RX, 0, SDR_RX_SAMPLING_RATE) != 0) {
fprintf(stderr, "sdr: setSampleRate fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
if (SoapySDRDevice_setFrequency(ctx->sdr, SOAPY_SDR_RX, 0, SDR_RX_FREQ - SDR_RX_IF_SHIFT, NULL) != 0) {
fprintf(stderr, "sdr: setFrequency fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
if (SoapySDRDevice_setSampleRate(ctx->sdr, SOAPY_SDR_TX, 0, SDR_TX_SAMPLING_RATE) != 0) {
fprintf(stderr, "sdr: setSampleRate fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
if (SoapySDRDevice_setFrequency(ctx->sdr, SOAPY_SDR_TX, 0, SDR_TX_FREQ, NULL) != 0) {
fprintf(stderr, "sdr: setFrequency fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
ctx->rx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
ctx->tx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
return OK;
}
@ -137,7 +200,8 @@ result_t sdr_init(sdr_ctx_t *ctx)
result_t sdr_destroy(sdr_ctx_t *ctx)
{
close_streams(ctx);
stop_streaming(ctx);
hackrf_close(ctx->hackrf);
nco_crcf_destroy(ctx->rx_nco);
nco_crcf_destroy(ctx->tx_nco);
@ -145,9 +209,10 @@ result_t sdr_destroy(sdr_ctx_t *ctx)
firdecim_crcf_destroy(ctx->decim);
firinterp_crcf_destroy(ctx->interp);
if(ctx->sdr) {
SoapySDRDevice_unmake(ctx->sdr);
}
cbuffercf_destroy(ctx->rx_buf);
cbuffercf_destroy(ctx->tx_buf);
sem_close(&ctx->buf_sem);
return OK;
}
@ -155,26 +220,28 @@ result_t sdr_destroy(sdr_ctx_t *ctx)
result_t sdr_start_rx(sdr_ctx_t *ctx)
{
if(SoapySDRDevice_activateStream(ctx->sdr, ctx->rx_stream, 0, 0, 0) != 0) {
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
int result;
// set gains
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "AMP", SDR_GAIN_RX_AMP) != 0) {
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
result = hackrf_set_sample_rate(ctx->hackrf, SDR_RX_SAMPLING_RATE);
CHECK_HACKRF_RESULT(result, "hackrf_set_sample_rate");
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "LNA", SDR_GAIN_RX_LNA) != 0) {
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
result = hackrf_set_freq(ctx->hackrf, SDR_RX_FREQ - SDR_RX_IF_SHIFT);
fprintf(stderr, "Set RX frequency to %f Hz.\n", SDR_RX_FREQ - SDR_RX_IF_SHIFT);
CHECK_HACKRF_RESULT(result, "hackrf_set_freq");
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "VGA", SDR_GAIN_RX_VGA) != 0) {
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
result = hackrf_set_lna_gain(ctx->hackrf, SDR_GAIN_RX_LNA);
CHECK_HACKRF_RESULT(result, "hackrf_set_lna_gain");
result = hackrf_set_vga_gain(ctx->hackrf, SDR_GAIN_RX_VGA);
CHECK_HACKRF_RESULT(result, "hackrf_set_vga_gain");
result = hackrf_set_amp_enable(ctx->hackrf, SDR_GAIN_RX_AMP > 0);
CHECK_HACKRF_RESULT(result, "hackrf_set_amp_enable");
result = hackrf_start_rx(ctx->hackrf, rx_callback, ctx);
CHECK_HACKRF_RESULT(result, "hackrf_start_rx");
ctx->status = SDR_STATUS_RX;
return OK;
}
@ -182,21 +249,28 @@ result_t sdr_start_rx(sdr_ctx_t *ctx)
result_t sdr_start_tx(sdr_ctx_t *ctx, size_t burst_size)
{
// set gain
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_TX, 0, "AMP", SDR_GAIN_TX_AMP) != 0) {
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
(void)burst_size;
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_TX, 0, "LNA", SDR_GAIN_TX_LNA) != 0) {
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
int result;
if(SoapySDRDevice_activateStream(ctx->sdr, ctx->tx_stream, 0, 0, burst_size) != 0) {
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
result = hackrf_set_freq(ctx->hackrf, SDR_TX_FREQ - SDR_TX_IF_SHIFT);
CHECK_HACKRF_RESULT(result, "hackrf_set_freq");
result = hackrf_set_sample_rate(ctx->hackrf, SDR_TX_SAMPLING_RATE);
CHECK_HACKRF_RESULT(result, "hackrf_set_sample_rate");
result = hackrf_set_amp_enable(ctx->hackrf, SDR_GAIN_TX_AMP > 0);
CHECK_HACKRF_RESULT(result, "hackrf_set_amp_enable");
result = hackrf_set_txvga_gain(ctx->hackrf, SDR_GAIN_TX_VGA);
CHECK_HACKRF_RESULT(result, "hackrf_set_txvga_gain");
ctx->tx_start_time = 0.0f; // will be updated by tx_callback
result = hackrf_start_tx(ctx->hackrf, tx_callback, ctx);
CHECK_HACKRF_RESULT(result, "hackrf_start_tx");
ctx->status = SDR_STATUS_TX;
return OK;
}
@ -204,39 +278,35 @@ result_t sdr_start_tx(sdr_ctx_t *ctx, size_t burst_size)
result_t sdr_stop_rx(sdr_ctx_t *ctx)
{
if(SoapySDRDevice_deactivateStream(ctx->sdr, ctx->rx_stream, 0, 0) != 0) {
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
return OK;
return stop_streaming(ctx);
}
result_t sdr_stop_tx(sdr_ctx_t *ctx)
{
if(SoapySDRDevice_deactivateStream(ctx->sdr, ctx->tx_stream, 0, 0) != 0) {
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
return OK;
return stop_streaming(ctx);
}
result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, long timeout_us)
{
if(ctx->tx_stream == NULL) {
return ERR_INVALID_STATE;
(void)timeout_us; // not implemented yet
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait failed. Samples lost. Error:");
return ERR_SDR;
}
void *buffs[] = {(void*)samples};
int result = cbuffercf_write(ctx->tx_buf, (float complex*)samples, nsamples);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_write failed: %d. Samples are lost.\n", result);
sem_post(&ctx->buf_sem);
return ERR_LIQUID;
}
int ret = SoapySDRDevice_writeStream(ctx->sdr, ctx->tx_stream, (const void* const*)buffs, nsamples, 0, 0, timeout_us);
if(ret <= 0) {
fprintf(stderr, "sdr: writeStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return ERR_SDR;
}
return OK;
@ -245,20 +315,39 @@ result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamp
result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, long timeout_us)
{
if(ctx->rx_stream == NULL) {
return ERR_INVALID_STATE;
(void)timeout_us; // not implemented yet
int result;
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait");
return ERR_SDR;
}
void *buffs[] = {(void*)samples};
int ret = SoapySDRDevice_readStream(ctx->sdr, ctx->rx_stream, (void* const*)buffs, *nsamples, 0, 0, timeout_us);
if(ret <= 0) {
fprintf(stderr, "sdr: readStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
unsigned int samples_read;
float complex *buf_samples;
result = cbuffercf_read(ctx->rx_buf, *nsamples, &buf_samples, &samples_read);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_read failed: %d. Samples are lost.\n", result);
sem_post(&ctx->buf_sem);
return ERR_LIQUID;
}
*nsamples = ret;
memcpy(samples, buf_samples, samples_read * sizeof(samples[0]));
result = cbuffercf_release(ctx->rx_buf, samples_read);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_release failed: %d.\n", result);
sem_post(&ctx->buf_sem);
return ERR_LIQUID;
}
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return ERR_SDR;
}
*nsamples = samples_read;
return OK;
}
@ -266,38 +355,88 @@ result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, l
result_t sdr_flush_tx_buffer(sdr_ctx_t *ctx)
{
size_t mtu = SoapySDRDevice_getStreamMTU(ctx->sdr, ctx->tx_stream);
// block until all samples have been transmitted
while(true) {
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait");
return 0;
}
float complex zeros[mtu];
memset(zeros, 0, sizeof(zeros));
double now = get_hires_time();
double end = ctx->tx_start_time + ctx->tx_duration;
for(int i = 0; i < 4; i++) {
fprintf(stderr, "z");
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return 0;
}
unsigned tries = 0;
int ret;
if(now >= end) {
break;
}
do {
ret = sdr_transmit(ctx, zeros, sizeof(zeros)/sizeof(zeros[0]), 100000);
} while(ret <= 0 && tries++ < 3);
sleep_until(end);
}
return OK;
}
size_t sdr_get_tx_buffer_free_space(sdr_ctx_t *ctx)
{
size_t free_samples = 0;
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait");
return 0;
}
free_samples = cbuffercf_space_available(ctx->tx_buf);
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return 0;
}
return free_samples;
}
size_t sdr_get_tx_buffer_used_space(sdr_ctx_t *ctx)
{
size_t used_samples = 0;
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait");
return 0;
}
used_samples = cbuffercf_size(ctx->tx_buf);
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return 0;
}
return used_samples;
}
result_t sdr_rf_to_baseband(sdr_ctx_t *ctx,
const float complex *rf_samples, size_t nrf,
float complex *bb_samples, size_t nbb)
float complex *bb_samples, size_t *nbb)
{
if((nbb * SDR_OVERSAMPLING) < nrf) {
fprintf(stderr, "sdr_rf_to_baseband: result would not fit in output: %zd * %d < %zd\n", nbb, SDR_OVERSAMPLING, nrf);
if((*nbb * SDR_OVERSAMPLING) < nrf) {
fprintf(stderr, "sdr_rf_to_baseband: result would not fit in output: %zd * %d < %zd\n", *nbb, SDR_OVERSAMPLING, nrf);
return ERR_SIZE;
}
for(size_t i = 0; i < nbb; i++) {
*nbb = nrf / SDR_OVERSAMPLING;
for(size_t i = 0; i < *nbb; i++) {
float complex tmp[SDR_OVERSAMPLING];
assert(i*SDR_OVERSAMPLING < nrf);
nco_crcf_mix_block_down(ctx->rx_nco,
(complex float*)(rf_samples + i * SDR_OVERSAMPLING),
tmp,
@ -312,13 +451,15 @@ result_t sdr_rf_to_baseband(sdr_ctx_t *ctx,
result_t sdr_baseband_to_rf(sdr_ctx_t *ctx,
const float complex *bb_samples, size_t nbb,
float complex *rf_samples, size_t nrf)
float complex *rf_samples, size_t *nrf)
{
if((nbb * SDR_OVERSAMPLING) > nrf) {
fprintf(stderr, "sdr_baseband_to_rf: result would not fit in output: %zd * %d > %zd\n", nbb, SDR_OVERSAMPLING, nrf);
if((nbb * SDR_OVERSAMPLING) > *nrf) {
fprintf(stderr, "sdr_baseband_to_rf: result would not fit in output: %zd * %d > %zd\n", nbb, SDR_OVERSAMPLING, *nrf);
return ERR_SIZE;
}
*nrf = nbb * SDR_OVERSAMPLING;
for(size_t i = 0; i < nbb; i++) {
float complex tmp[SDR_OVERSAMPLING];

View File

@ -2,24 +2,37 @@
#define SDR_SDR_H
#include <complex.h>
#include <semaphore.h>
#include <stdbool.h>
#include <SoapySDR/Device.h>
#include <libhackrf/hackrf.h>
#include <liquid/liquid.h>
#include "results.h"
typedef struct {
SoapySDRDevice *sdr;
typedef enum {
SDR_STATUS_IDLE,
SDR_STATUS_TX,
SDR_STATUS_RX
} sdr_status_t;
SoapySDRStream *rx_stream;
SoapySDRStream *tx_stream;
typedef struct {
hackrf_device *hackrf;
firinterp_crcf interp;
nco_crcf tx_nco;
cbuffercf tx_buf;
firdecim_crcf decim;
nco_crcf rx_nco;
cbuffercf rx_buf;
sem_t buf_sem;
sdr_status_t status;
double tx_duration;
double tx_start_time; // time when tx_callback() was first called
} sdr_ctx_t;
result_t sdr_init(sdr_ctx_t *ctx);
@ -35,19 +48,21 @@ result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamp
result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, long timeout_us);
result_t sdr_flush_tx_buffer(sdr_ctx_t *ctx);
size_t sdr_get_tx_buffer_free_space(sdr_ctx_t *ctx);
size_t sdr_get_tx_buffer_used_space(sdr_ctx_t *ctx);
/*!
* \brief Convert and resample a received signal to baseband.
*/
result_t sdr_rf_to_baseband(sdr_ctx_t *ctx,
const float complex *rf_samples, size_t nrf,
float complex *bb_samples, size_t nbb);
float complex *bb_samples, size_t *nbb);
/*!
* \brief Convert and resample a baseband signal for transmission.
*/
result_t sdr_baseband_to_rf(sdr_ctx_t *ctx,
const float complex *bb_samples, size_t nbb,
float complex *rf_samples, size_t nrf);
float complex *rf_samples, size_t *nrf);
#endif // SDR_SDR_H

View File

@ -37,6 +37,9 @@ add_executable(
../src/layer1/modcod.c
../src/layer1/modcod.h
../src/utils.c
../src/utils.h
../src/options.c
../src/options.h
layer1/test_loopback.c
)
@ -44,6 +47,9 @@ target_link_libraries(
test_layer1_loopback
m
liquid
fftw3
fftw3f
fec
)
#------------------------------------
@ -58,6 +64,39 @@ add_executable(
target_link_libraries(
test_freq_est
fftw3f
m
liquid
)
#------------------------------------
add_executable(
test_rx_file
../src/layer1/correlator.c
../src/layer1/correlator.h
../src/layer1/freq_est.c
../src/layer1/freq_est.h
../src/layer1/rx.c
../src/layer1/rx.h
../src/layer1/whitening.c
../src/layer1/whitening.h
../src/layer1/modcod.c
../src/layer1/modcod.h
../src/layer1/preamble.c
../src/layer1/preamble.h
../src/utils.c
../src/utils.h
../src/options.c
../src/options.h
../src/config.h
layer1/test_rx_file.c
)
target_link_libraries(
test_rx_file
fec
fftw3f
m
liquid
)

View File

@ -1,8 +1,5 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <time.h>
#include <liquid/liquid.h>
@ -21,18 +18,26 @@
int main(void)
{
#define N 1024
#define N 63
float complex s1[N];
float complex s2[N];
float complex s3[N];
nco_crcf nco;
channel_cccf chan;
// ** Initialize **
srand(time(NULL));
nco = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(nco, 0.05f);
nco_crcf_set_phase(nco, 1.37f);
nco_crcf_set_frequency(nco, -0.08f);
nco_crcf_set_phase(nco, -1.37f);
chan = channel_cccf_create();
channel_cccf_add_awgn(chan, -80, 5);
// ** Generate signal **
@ -44,14 +49,17 @@ int main(void)
nco_crcf_mix_block_up(nco, s1, s2, N);
channel_cccf_execute_block(chan, s2, N, s3);
dump_array_cf(s1, N, 1.0f, "/tmp/freq_est_source.cpx");
dump_array_cf(s2, N, 1.0f, "/tmp/freq_est_mixed.cpx");
dump_array_cf(s3, N, 1.0f, "/tmp/freq_est_channelized.cpx");
// ** Estimate frequency **
float final_phase;
float est_freq = freq_est_data_free(s2, 32, &final_phase);
float est_freq = freq_est_data_free(s3, N, &final_phase);
fprintf(stderr, "Estimated frequency: %.6f rad/sample\n", est_freq);
fprintf(stderr, "Final phase: %.6f rad\n", final_phase);

View File

@ -0,0 +1,157 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <liquid/liquid.h>
#include "layer1/rx.h"
#include "config.h"
#define RESULT_CHECK(stmt) { \
result_t res = stmt; \
if(res != OK) { \
fprintf(stderr, "Error %d in %s:%d!\n", res, __FILE__, __LINE__); \
exit(1); \
} \
}
#define CHUNKSIZE_INPUT 8000
#define CHUNKSIZE_RF (CHUNKSIZE_INPUT/2)
#define CHUNKSIZE_BB (CHUNKSIZE_RF/SDR_OVERSAMPLING)
static struct {
size_t preambles_found;
size_t successful_decodes;
size_t failed_decodes;
size_t header_errors;
} m_stats;
static result_t sdr_rf_to_baseband(nco_crcf nco, firdecim_crcf decim,
const float complex *rf_samples, size_t nrf,
float complex *bb_samples, size_t *nbb)
{
if((*nbb * SDR_OVERSAMPLING) < nrf) {
fprintf(stderr, "sdr_rf_to_baseband: result would not fit in output: %zd * %d < %zd\n", *nbb, SDR_OVERSAMPLING, nrf);
return ERR_SIZE;
}
*nbb = nrf / SDR_OVERSAMPLING;
for(size_t i = 0; i < *nbb; i++) {
float complex tmp[SDR_OVERSAMPLING];
assert(i*SDR_OVERSAMPLING < nrf);
nco_crcf_mix_block_down(nco,
(complex float*)(rf_samples + i * SDR_OVERSAMPLING),
tmp,
SDR_OVERSAMPLING);
firdecim_crcf_execute(decim, tmp, bb_samples + i);
}
return OK;
}
void cb_rx(rx_evt_t evt, uint8_t *packet_data, size_t packet_len)
{
switch(evt)
{
case RX_EVT_CHECKSUM_ERROR:
//fprintf(stderr, "Received a message of %zu bytes, but decoding failed.\n", packet_len);
//fprintf(stderr, "=== FAILED PAYLOAD ===\n");
//hexdump(packet_data, packet_len);
//fprintf(stderr, "=======================\n");
m_stats.failed_decodes++;
break;
case RX_EVT_HEADER_ERROR:
m_stats.header_errors++;
break;
case RX_EVT_PACKET_RECEIVED:
//fprintf(stderr, "A message of %zu bytes was decoded successfully.\n", packet_len);
//fprintf(stderr, "=== DECODED PAYLOAD (%4zu bytes) ===\n", packet_len);
//hexdump(packet_data, packet_len < 64 ? packet_len : 64);
//fprintf(stderr, "====================================\n");
m_stats.successful_decodes++;
break;
case RX_EVT_PREAMBLE_FOUND:
//fprintf(stderr, "Found preamble!\n");
m_stats.preambles_found++;
break;
}
}
int main(int argc, char **argv)
{
if(argc < 2) {
fprintf(stderr, "Not enough arguments!\n");
fprintf(stderr, "usage: %s <dump-file>\n\n", argv[0]);
fprintf(stderr, "dump-file: HackRF dump in 8-bit signed interleaved I/Q format.\n");
return 1;
}
layer1_rx_t rx;
FILE *inputfile;
int8_t rbuf[CHUNKSIZE_INPUT];
// ** Initialize **
firdecim_crcf decim = firdecim_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f);
nco_crcf rx_nco = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(rx_nco, 2 * 3.14159 * SDR_RX_IF_SHIFT / SDR_RX_SAMPLING_RATE);
inputfile = fopen(argv[1], "rb");
if(!inputfile) {
fprintf(stderr, "Could not open input file!\n");
return 1;
}
RESULT_CHECK(layer1_rx_init(&rx, cb_rx));
// ** Process packets **
size_t nread;
while((nread = fread(rbuf, 1, sizeof(rbuf), inputfile)) != 0) {
assert(nread % 2 == 0);
// ** Receive signal **
float complex rf_samples[CHUNKSIZE_RF];
float complex bb_samples[CHUNKSIZE_BB];
size_t n_rf_samples = CHUNKSIZE_RF;
size_t n_bb_samples = CHUNKSIZE_BB;
for(size_t iout = 0; iout < CHUNKSIZE_RF; iout++) {
rf_samples[iout] = 0.0078125f * (rbuf[2*iout + 0] + I * rbuf[2*iout + 1]);
}
RESULT_CHECK(sdr_rf_to_baseband(rx_nco, decim, rf_samples, n_rf_samples, bb_samples, &n_bb_samples));
RESULT_CHECK(layer1_rx_process(&rx, bb_samples, n_bb_samples));
fprintf(stderr, "Receiver statistics:\n");
fprintf(stderr, " Preambles found: %8zd\n", m_stats.preambles_found);
fprintf(stderr, " Successful decodes: %8zd (%6.2f %%)\n",
m_stats.successful_decodes, m_stats.successful_decodes * 100.0f / m_stats.preambles_found);
fprintf(stderr, " Header errors: %8zd (%6.2f %%)\n",
m_stats.header_errors, m_stats.header_errors * 100.0f / m_stats.preambles_found);
fprintf(stderr, " Failed decodes: %8zd (%6.2f %%)\n",
m_stats.failed_decodes, m_stats.failed_decodes * 100.0f / m_stats.preambles_found);
}
// ** Cleanup **
layer1_rx_shutdown(&rx);
fprintf(stderr, "Done.\n");
}

View File

@ -17,7 +17,7 @@ with open(sys.argv[1], 'rb') as infile:
t = np.arange(0, T*data.size, T)
amp = np.absolute(data)
phase = np.angle(data)
phase = np.unwrap(np.angle(data))
pp.plot(t, amp, 'r-')
pp.legend(['Amplitude'])

View File

@ -14,7 +14,7 @@ def load_data():
print(f"Error: not a complex signal file. Format {header} not implemented yet.")
exit(1)
T = np.fromfile(infile, dtype=np.float32, count=1)
T = np.fromfile(infile, dtype=np.float32, count=1)[0]
data = np.fromfile(infile, dtype=np.complex64)
f = np.arange(-1/(2*T), 1/(2*T), 1/T/data.size)[:data.size]
@ -35,8 +35,11 @@ ax.legend(['Spectrum'])
ax.set_xlabel('Frequency [Hz]')
def update(*args):
spec, f = load_data()
line.set_ydata(spec)
try:
spec, f = load_data()
line.set_ydata(spec)
except Exception as e:
print(e)
return [line]

View File

@ -8,10 +8,10 @@ import matplotlib.pyplot as pp
with open(sys.argv[1], 'rb') as infile:
header = infile.read(4)
if header == b'CPX_':
T = np.fromfile(infile, dtype=np.float32, count=1)
T = np.fromfile(infile, dtype=np.float32, count=1)[0]
data = np.fromfile(infile, dtype=np.complex64)
elif header == b'FLT_':
T = np.fromfile(infile, dtype=np.float32, count=1)
T = np.fromfile(infile, dtype=np.float32, count=1)[0]
data = np.fromfile(infile, dtype=np.float32)
else:
print(f"Error: Format {header} not implemented yet.")