From 85663d530437717a1ba448f8c64fedc5880bb6ce Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Thu, 3 Mar 2022 22:18:19 +0100 Subject: [PATCH] Added SDR abstraction layer --- impl/CMakeLists.txt | 2 + impl/src/config.h | 38 +++---- impl/src/results.h | 5 +- impl/src/sdr/sdr.c | 249 ++++++++++++++++++++++++++++++++++++++++++++ impl/src/sdr/sdr.h | 48 +++++++++ 5 files changed, 322 insertions(+), 20 deletions(-) create mode 100644 impl/src/sdr/sdr.c create mode 100644 impl/src/sdr/sdr.h diff --git a/impl/CMakeLists.txt b/impl/CMakeLists.txt index 20e28eb..f000aae 100644 --- a/impl/CMakeLists.txt +++ b/impl/CMakeLists.txt @@ -33,6 +33,8 @@ set(sources src/layer1/modcod.h src/layer2/tundev.c src/layer2/tundev.h + src/sdr/sdr.c + src/sdr/sdr.h ) include_directories( diff --git a/impl/src/config.h b/impl/src/config.h index 54033c3..c5c6186 100644 --- a/impl/src/config.h +++ b/impl/src/config.h @@ -3,24 +3,6 @@ #include -/*** SDR CONFIG ***/ - -#define SDR_IS_FULL_DUPLEX 0 - -#define SDR_RX_SAMPLING_RATE 1e6f -#define SDR_TX_SAMPLING_RATE RX_SAMPLING_RATE - -// actually transmitted or received signal frequencies, NOT the SDR center frequency. -#define SDR_TX_FREQ 434.100e6f -#define SDR_RX_FREQ 434.100e6f - -// shift applied in the baseband, to get rid of SDR DC peak. If the value here -// is not 0, software mixing will be done on the received signal. -#define SDR_TX_IF_SHIFT 0.000e6f -#define SDR_RX_IF_SHIFT 0.150e6f - -// NOTE: the SDR center frequency will be SDR_RX_FREQ - SDR_RX_IF_SHIFT. - /*** LAYER 1 CONFIG ***/ #define SYMBOL_RATE 100e3f @@ -44,4 +26,24 @@ #define TRANSMISSION_RAMP_UP_LEN 64 // symbols #define TRANSMISSION_RAMP_DOWN_LEN 32 // symbols +/*** SDR CONFIG ***/ + +#define SDR_IS_FULL_DUPLEX 0 + +#define SDR_OVERSAMPLING 3 // integer factor! + +#define SDR_RX_SAMPLING_RATE (SYMBOL_RATE * RRC_SPS * SDR_OVERSAMPLING) +#define SDR_TX_SAMPLING_RATE SDR_RX_SAMPLING_RATE + +// actually transmitted or received signal frequencies, NOT the SDR center frequency. +#define SDR_TX_FREQ 434.100e6f +#define SDR_RX_FREQ 434.100e6f + +// shift applied in the baseband, to get rid of SDR DC peak. If the value here +// is not 0, software mixing will be done on the received signal. +#define SDR_TX_IF_SHIFT 0.000e6f +#define SDR_RX_IF_SHIFT 0.150e6f + +// NOTE: the SDR center frequency will be SDR_RX_FREQ - SDR_RX_IF_SHIFT. + #endif // CONFIG_H diff --git a/impl/src/results.h b/impl/src/results.h index 9f0667e..a59d1af 100644 --- a/impl/src/results.h +++ b/impl/src/results.h @@ -6,8 +6,9 @@ typedef enum { ERR_INVALID_STATE, ERR_NO_MEM, ERR_SIZE, - ERR_LIQUID, - ERR_SYSCALL // a syscall failed. Use errno to determine the cause. + 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. } result_t; #ifdef DEBUG_LIQUID diff --git a/impl/src/sdr/sdr.c b/impl/src/sdr/sdr.c new file mode 100644 index 0000000..ca0c3f6 --- /dev/null +++ b/impl/src/sdr/sdr.c @@ -0,0 +1,249 @@ +#include +#include +#include +#include //printf +#include //free +#include +#include + +#include "config.h" + +#include "sdr.h" + +static void shutdown_streams(sdr_ctx_t *ctx) +{ + if(ctx->rx_stream) { + SoapySDRDevice_deactivateStream(ctx->sdr, ctx->rx_stream, 0, 0); + SoapySDRDevice_closeStream(ctx->sdr, ctx->rx_stream); + } + + if(ctx->tx_stream) { + SoapySDRDevice_deactivateStream(ctx->sdr, ctx->tx_stream, 0, 0); + SoapySDRDevice_closeStream(ctx->sdr, ctx->tx_stream); + } +} + + +result_t sdr_init(sdr_ctx_t *ctx) +{ + size_t length; + + ctx->sdr = NULL; + ctx->tx_stream = NULL; + ctx->rx_stream = NULL; + + ctx->interp = firinterp_crcf_create_prototype(LIQUID_FIRFILT_PM, SDR_OVERSAMPLING, 10, 0.2, 0); + ctx->decim = firdecim_crcf_create_prototype(LIQUID_FIRFILT_PM, SDR_OVERSAMPLING, 10, 0.2, 0); + + ctx->tx_nco = nco_crcf_create(LIQUID_NCO); + ctx->rx_nco = nco_crcf_create(LIQUID_NCO); + + 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_TX_SAMPLING_RATE); + + //enumerate devices + SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length); + for (size_t i = 0; i < length; i++) + { + fprintf(stderr, "sdr: Found device #%d: ", (int)i); + for (size_t j = 0; j < results[i].size; j++) + { + fprintf(stderr, "sdr: %s=%s, ", 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; + 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, "sdr: %s, ", names[i]); + fprintf(stderr, "sdr: \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, "sdr: %s, ", names[i]); + fprintf(stderr, "sdr: \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, "sdr: [%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum); + fprintf(stderr, "sdr: \n"); + free(ranges); + + //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; + } + + return OK; +} + + +result_t sdr_destroy(sdr_ctx_t *ctx) +{ + shutdown_streams(ctx); + + nco_crcf_destroy(ctx->rx_nco); + nco_crcf_destroy(ctx->tx_nco); + + firdecim_crcf_destroy(ctx->decim); + firinterp_crcf_destroy(ctx->interp); + + if(ctx->sdr) { + SoapySDRDevice_unmake(ctx->sdr); + } + + return OK; +} + + +result_t sdr_start_rx(sdr_ctx_t *ctx) +{ + shutdown_streams(ctx); + + //setup a stream (complex floats) + 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; + } + + SoapySDRDevice_activateStream(ctx->sdr, ctx->rx_stream, 0, 0, 0); //start streaming + + return OK; +} + + +result_t sdr_start_tx(sdr_ctx_t *ctx) +{ + shutdown_streams(ctx); + + //setup a stream (complex floats) + ctx->tx_stream = SoapySDRDevice_setupStream(ctx->sdr, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL); + + if(ctx->tx_stream == NULL) { + fprintf(stderr, "sdr: setupStream fail: %s\n", SoapySDRDevice_lastError()); + return ERR_SOAPY; + } + + SoapySDRDevice_activateStream(ctx->sdr, ctx->tx_stream, 0, 0, 0); //start streaming + + return OK; +} + + +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 *buffs[] = {(void*)samples}; + + 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; + } + + return OK; +} + + +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 *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; + } + + *nsamples = ret; + + return OK; +} + + +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) +{ + if((nbb * SDR_OVERSAMPLING) > nrf) { + return ERR_SIZE; + } + + for(size_t i = 0; i < nbb; i++) { + float complex tmp[SDR_OVERSAMPLING]; + + nco_crcf_mix_block_down(ctx->rx_nco, + (complex float*)(rf_samples + i * SDR_OVERSAMPLING), + tmp, + SDR_OVERSAMPLING); + + firdecim_crcf_execute(ctx->decim, tmp, bb_samples + i); + } + + return OK; +} + + +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) +{ + if((nbb * SDR_OVERSAMPLING) > nrf) { + return ERR_SIZE; + } + + for(size_t i = 0; i < nbb; i++) { + float complex tmp[SDR_OVERSAMPLING]; + + firinterp_crcf_execute(ctx->interp, bb_samples[i], tmp); + + nco_crcf_mix_block_up(ctx->tx_nco, + tmp, + rf_samples + i * SDR_OVERSAMPLING, + SDR_OVERSAMPLING); + } + + return OK; +} diff --git a/impl/src/sdr/sdr.h b/impl/src/sdr/sdr.h new file mode 100644 index 0000000..360968e --- /dev/null +++ b/impl/src/sdr/sdr.h @@ -0,0 +1,48 @@ +#ifndef SDR_SDR_H +#define SDR_SDR_H + +#include + +#include + +#include + +#include "results.h" + +typedef struct { + SoapySDRDevice *sdr; + + SoapySDRStream *rx_stream; + SoapySDRStream *tx_stream; + + firinterp_crcf interp; + nco_crcf tx_nco; + + firdecim_crcf decim; + nco_crcf rx_nco; +} sdr_ctx_t; + +result_t sdr_init(sdr_ctx_t *ctx); +result_t sdr_destroy(sdr_ctx_t *ctx); + +result_t sdr_start_rx(sdr_ctx_t *ctx); +result_t sdr_start_tx(sdr_ctx_t *ctx); + +result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, long timeout_us); +result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, long timeout_us); + +/*! + * \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); + +/*! + * \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); + +#endif // SDR_SDR_H