Compare commits
37 Commits
112a8a1215
...
2768e21849
Author | SHA1 | Date |
---|---|---|
Simon Ruderich | 2768e21849 | |
Simon Ruderich | 79fc1c4f59 | |
Thomas Kolb | 7d1a927425 | |
Thomas Kolb | 32632914df | |
Thomas Kolb | 11f19c03a0 | |
Thomas Kolb | 1f5f922cdf | |
Thomas Kolb | 91db4e1f75 | |
Thomas Kolb | 4b46d87edb | |
Thomas Kolb | 93fd8aebbc | |
Thomas Kolb | 4f4128fbd3 | |
Thomas Kolb | 8441b12fbd | |
Thomas Kolb | 171a4a369c | |
Thomas Kolb | 79d7f74dfc | |
Thomas Kolb | bbf7bcf2f0 | |
Thomas Kolb | 66c88bf889 | |
Thomas Kolb | d9707ac4a9 | |
Thomas Kolb | dd5712eada | |
Thomas Kolb | 07555edfdf | |
Thomas Kolb | 16aa2ce92c | |
Thomas Kolb | ef25ca2388 | |
Thomas Kolb | 84c172abc7 | |
Thomas Kolb | 3a4be428b5 | |
Thomas Kolb | e807f0617b | |
Thomas Kolb | a6f12d876b | |
Thomas Kolb | be5fa06950 | |
Thomas Kolb | ee63483b8f | |
Thomas Kolb | ac087634dd | |
Thomas Kolb | 8689ed5b27 | |
Thomas Kolb | fb9c26ccd5 | |
Thomas Kolb | 5564f1751a | |
Thomas Kolb | c9d53ed96e | |
Thomas Kolb | 628659511c | |
Thomas Kolb | 497498acd2 | |
Thomas Kolb | ef0fad4335 | |
Thomas Kolb | d55e3ab902 | |
Thomas Kolb | 2b741acd8a | |
Thomas Kolb | 7995f5bef0 |
|
@ -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)
|
||||
|
|
|
@ -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 $@
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 central’s 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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
180
impl/src/main.c
180
impl/src/main.c
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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'])
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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.")
|
||||
|
|
Loading…
Reference in New Issue