Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
Thomas Kolb | a520b3232d | ||
Thomas Kolb | 5aa9eeb18b | ||
Thomas Kolb | e4a6e6b300 | ||
Thomas Kolb | 79d1178150 | ||
Thomas Kolb | ec8770f399 | ||
Thomas Kolb | eb03e6a661 |
|
@ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.20)
|
||||||
project (hamnet70 VERSION 0.1 LANGUAGES C)
|
project (hamnet70 VERSION 0.1 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_FLAGS "-Wall -pedantic -Wextra -DDEBUG_LIQUID")
|
set(CMAKE_C_FLAGS "-Wall -pedantic -Wextra -DDEBUG_LIQUID -fsanitize=address")
|
||||||
|
|
||||||
include_directories(src)
|
include_directories(src)
|
||||||
|
|
||||||
|
@ -55,8 +55,8 @@ set(sources
|
||||||
src/layer2/ham64.h
|
src/layer2/ham64.h
|
||||||
src/layer2/connection.c
|
src/layer2/connection.c
|
||||||
src/layer2/connection.h
|
src/layer2/connection.h
|
||||||
src/sdr/sdr.c
|
src/sdr/sdr_sxceiver.c
|
||||||
src/sdr/sdr.h
|
src/sdr/sdr_sxceiver.h
|
||||||
)
|
)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
|
@ -75,7 +75,7 @@ target_link_libraries(
|
||||||
rt
|
rt
|
||||||
fftw3f
|
fftw3f
|
||||||
fec
|
fec
|
||||||
hackrf
|
SoapySDR
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
#include "layer2/tundev.h"
|
#include "layer2/tundev.h"
|
||||||
|
|
||||||
#include "sdr/sdr.h"
|
#include "sdr/sdr_sxceiver.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@
|
||||||
static int m_tunfd = -1;
|
static int m_tunfd = -1;
|
||||||
static bool m_running = true;
|
static bool m_running = true;
|
||||||
|
|
||||||
|
static double m_tstart = 0.0;
|
||||||
|
|
||||||
static double next_tx_switch_time = 0.0;
|
static double next_tx_switch_time = 0.0;
|
||||||
|
|
||||||
static rx_stats_t m_rx_stats;
|
static rx_stats_t m_rx_stats;
|
||||||
|
@ -149,14 +151,8 @@ void cb_rx(rx_evt_t evt, const struct layer1_rx_s *rx, uint8_t *packet_data, siz
|
||||||
|
|
||||||
static result_t transmit(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 to_transmit_rf = len * SDR_OVERSAMPLING;
|
int64_t tx_time_ns = (int64_t)((get_hires_time() - m_tstart + 0.1) * 1e9);
|
||||||
float complex *rf_samples = malloc(sizeof(*rf_samples) * to_transmit_rf);
|
result_t result = sdr_transmit(sdr, samples, len, tx_time_ns, 100000);
|
||||||
|
|
||||||
RESULT_CHECK(sdr_baseband_to_rf(sdr, samples, len, rf_samples, &to_transmit_rf));
|
|
||||||
|
|
||||||
result_t result = sdr_transmit(sdr, rf_samples, to_transmit_rf, 100000);
|
|
||||||
|
|
||||||
free(rf_samples);
|
|
||||||
|
|
||||||
fprintf(stderr, "t");
|
fprintf(stderr, "t");
|
||||||
return result;
|
return result;
|
||||||
|
@ -230,10 +226,11 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
// start in TX mode to work around SoapyHackRF not setting the correct frequency.
|
// start in TX mode to work around SoapyHackRF not setting the correct frequency.
|
||||||
RESULT_CHECK(sdr_start_tx(&sdr, 1));
|
RESULT_CHECK(sdr_start_tx(&sdr, 1));
|
||||||
|
m_tstart = get_hires_time();
|
||||||
|
|
||||||
unsigned rx_retries = 0;
|
unsigned rx_retries = 0;
|
||||||
|
|
||||||
double old = get_hires_time();
|
double old = m_tstart;
|
||||||
size_t total_samples = 0;
|
size_t total_samples = 0;
|
||||||
double next_stats_print_time = old + 0.5;
|
double next_stats_print_time = old + 0.5;
|
||||||
|
|
||||||
|
@ -256,7 +253,7 @@ int main(int argc, char **argv)
|
||||||
// there is a packet to be (re)transmitted.
|
// there is a packet to be (re)transmitted.
|
||||||
|
|
||||||
// check free buffer space (50 ms required corresponding to 5000 baseband symbols)
|
// 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);
|
size_t buffer_free_space_samples = 1000000; // FIXME! sdr_get_tx_buffer_free_space(&sdr);
|
||||||
|
|
||||||
LOG(LVL_DEBUG, "TX buffer free: %zu", buffer_free_space_samples);
|
LOG(LVL_DEBUG, "TX buffer free: %zu", buffer_free_space_samples);
|
||||||
|
|
||||||
|
@ -318,7 +315,6 @@ int main(int argc, char **argv)
|
||||||
on_air = true;
|
on_air = true;
|
||||||
} else if(on_air) { // TX on, but no more bursts to send
|
} else if(on_air) { // TX on, but no more bursts to send
|
||||||
LOG(LVL_INFO, "TX -> RX");
|
LOG(LVL_INFO, "TX -> RX");
|
||||||
RESULT_CHECK(sdr_flush_tx_buffer(&sdr));
|
|
||||||
RESULT_CHECK(layer1_rx_reset(&rx));
|
RESULT_CHECK(layer1_rx_reset(&rx));
|
||||||
|
|
||||||
RESULT_CHECK(sdr_stop_tx(&sdr));
|
RESULT_CHECK(sdr_stop_tx(&sdr));
|
||||||
|
@ -343,7 +339,7 @@ int main(int argc, char **argv)
|
||||||
size_t n_rf_samples = CHUNKSIZE_RF;
|
size_t n_rf_samples = CHUNKSIZE_RF;
|
||||||
size_t n_bb_samples = CHUNKSIZE_BB;
|
size_t n_bb_samples = CHUNKSIZE_BB;
|
||||||
|
|
||||||
if(sdr_receive(&sdr, rf_samples, &n_rf_samples, 100000, SDR_OVERSAMPLING) != OK) {
|
if(sdr_receive(&sdr, bb_samples, &n_bb_samples, 100000) != OK) {
|
||||||
rx_retries++;
|
rx_retries++;
|
||||||
LOG(LVL_INFO, "sdr_receive() failed %d times.", rx_retries);
|
LOG(LVL_INFO, "sdr_receive() failed %d times.", rx_retries);
|
||||||
if(rx_retries >= 3) {
|
if(rx_retries >= 3) {
|
||||||
|
@ -382,8 +378,6 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
fprintf(stderr, "r");
|
fprintf(stderr, "r");
|
||||||
|
|
||||||
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));
|
RESULT_CHECK(layer1_rx_process(&rx, bb_samples, n_bb_samples));
|
||||||
} else {
|
} else {
|
||||||
rx_retries = 0;
|
rx_retries = 0;
|
||||||
|
|
330
impl/src/sdr/sdr_sxceiver.c
Normal file
330
impl/src/sdr/sdr_sxceiver.c
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
#include <SoapySDR/Device.h>
|
||||||
|
#include <SoapySDR/Formats.h>
|
||||||
|
#include <SoapySDR/Logger.h>
|
||||||
|
#include <liquid/liquid.h>
|
||||||
|
#include <stdio.h> //printf
|
||||||
|
#include <stdlib.h> //free
|
||||||
|
#include <complex.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "results.h"
|
||||||
|
|
||||||
|
#include "sdr_sxceiver.h"
|
||||||
|
|
||||||
|
void soapy_log_handler(const SoapySDRLogLevel logLevel, const char *message)
|
||||||
|
{
|
||||||
|
int level;
|
||||||
|
|
||||||
|
switch(logLevel) {
|
||||||
|
case SOAPY_SDR_CRITICAL:
|
||||||
|
case SOAPY_SDR_FATAL:
|
||||||
|
level = LVL_FATAL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_ERROR:
|
||||||
|
level = LVL_ERR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_WARNING:
|
||||||
|
level = LVL_WARN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_INFO:
|
||||||
|
level = LVL_INFO;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_DEBUG:
|
||||||
|
level = LVL_DEBUG;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
level = LVL_DUMP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(level, "soapy [%d]: %s", logLevel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void close_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->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_RX_SAMPLING_RATE);
|
||||||
|
|
||||||
|
// set up logging
|
||||||
|
SoapySDR_registerLogHandler(soapy_log_handler);
|
||||||
|
//SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
|
||||||
|
|
||||||
|
//enumerate devices
|
||||||
|
SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length);
|
||||||
|
for (size_t i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
LOG(LVL_INFO, "Found device #%d:", (int)i);
|
||||||
|
for (size_t j = 0; j < results[i].size; j++)
|
||||||
|
{
|
||||||
|
LOG(LVL_INFO, "- %s=%s", results[i].keys[j], results[i].vals[j]);
|
||||||
|
}
|
||||||
|
LOG(LVL_ERR, "");
|
||||||
|
}
|
||||||
|
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", "sx");
|
||||||
|
ctx->sdr = SoapySDRDevice_make(&args);
|
||||||
|
SoapySDRKwargs_clear(&args);
|
||||||
|
|
||||||
|
if (ctx->sdr == NULL)
|
||||||
|
{
|
||||||
|
LOG(LVL_ERR, "SoapySDRDevice_make fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
//query device info
|
||||||
|
SoapySDRRange *ranges;
|
||||||
|
char** names = SoapySDRDevice_listAntennas(ctx->sdr, SOAPY_SDR_RX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Rx antennas: ");
|
||||||
|
for (size_t i = 0; i < length; i++) LOG(LVL_INFO, "%s, ", names[i]);
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
SoapySDRStrings_clear(&names, length);
|
||||||
|
|
||||||
|
names = SoapySDRDevice_listGains(ctx->sdr, SOAPY_SDR_RX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Rx gains: ");
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
SoapySDRRange range = SoapySDRDevice_getGainRange(ctx->sdr, SOAPY_SDR_RX, i);
|
||||||
|
LOG(LVL_INFO, "%s [%.3lf .. %.3lf / %.3lf], ", names[i], range.minimum, range.maximum, range.step);
|
||||||
|
}
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
SoapySDRStrings_clear(&names, length);
|
||||||
|
|
||||||
|
ranges = SoapySDRDevice_getFrequencyRange(ctx->sdr, SOAPY_SDR_RX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Rx freq ranges: ");
|
||||||
|
for (size_t i = 0; i < length; i++) LOG(LVL_INFO, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
free(ranges);
|
||||||
|
|
||||||
|
names = SoapySDRDevice_listAntennas(ctx->sdr, SOAPY_SDR_TX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Tx antennas: ");
|
||||||
|
for (size_t i = 0; i < length; i++) LOG(LVL_INFO, "%s, ", names[i]);
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
SoapySDRStrings_clear(&names, length);
|
||||||
|
|
||||||
|
names = SoapySDRDevice_listGains(ctx->sdr, SOAPY_SDR_TX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Tx gains: ");
|
||||||
|
for (size_t i = 0; i < length; i++) {
|
||||||
|
SoapySDRRange range = SoapySDRDevice_getGainRange(ctx->sdr, SOAPY_SDR_TX, i);
|
||||||
|
LOG(LVL_INFO, "%s [%.3lf .. %.3lf / %.3lf], ", names[i], range.minimum, range.maximum, range.step);
|
||||||
|
}
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
SoapySDRStrings_clear(&names, length);
|
||||||
|
|
||||||
|
ranges = SoapySDRDevice_getFrequencyRange(ctx->sdr, SOAPY_SDR_TX, 0, &length);
|
||||||
|
LOG(LVL_INFO, "Tx freq ranges: ");
|
||||||
|
for (size_t i = 0; i < length; i++) LOG(LVL_INFO, "[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
|
||||||
|
LOG(LVL_INFO, "");
|
||||||
|
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) {
|
||||||
|
LOG(LVL_ERR, "setupStream fail: %s", 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) {
|
||||||
|
LOG(LVL_ERR, "setupStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
//apply settings
|
||||||
|
if (SoapySDRDevice_setSampleRate(ctx->sdr, SOAPY_SDR_RX, 0, SDR_RX_SAMPLING_RATE) != 0) {
|
||||||
|
LOG(LVL_ERR, "setSampleRate fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
if (SoapySDRDevice_setFrequency(ctx->sdr, SOAPY_SDR_RX, 0, SDR_RX_FREQ - SDR_RX_IF_SHIFT, NULL) != 0) {
|
||||||
|
LOG(LVL_ERR, "setFrequency fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SoapySDRDevice_setSampleRate(ctx->sdr, SOAPY_SDR_TX, 0, SDR_TX_SAMPLING_RATE) != 0) {
|
||||||
|
LOG(LVL_ERR, "setSampleRate fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
if (SoapySDRDevice_setFrequency(ctx->sdr, SOAPY_SDR_TX, 0, SDR_TX_FREQ, NULL) != 0) {
|
||||||
|
LOG(LVL_ERR, "setFrequency fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
// PA always on
|
||||||
|
if (SoapySDRDevice_writeSetting(ctx->sdr, "PA", "ON") != 0) {
|
||||||
|
LOG(LVL_ERR, "writeSetting fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// set up and start RX
|
||||||
|
|
||||||
|
// set gains
|
||||||
|
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "PGA", SDR_GAIN_RX_PGA) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGainElement fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "LNA", SDR_GAIN_RX_LNA) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGainElement fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SoapySDRDevice_activateStream(ctx->sdr, ctx->rx_stream, 0, 0, 0) != 0) {
|
||||||
|
LOG(LVL_ERR, "activateStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set up and start TX
|
||||||
|
|
||||||
|
// set gain
|
||||||
|
/*if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_TX, 0, "DAC", SDR_GAIN_TX_DAC) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGainElement fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_TX, 0, "MIXER", SDR_GAIN_TX_MIXER) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGainElement fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
if(SoapySDRDevice_setGain(ctx->sdr, SOAPY_SDR_TX, 0, SDR_GAIN_TX) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGain fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SoapySDRDevice_activateStream(ctx->sdr, ctx->tx_stream, 0, 0, 0) != 0) {
|
||||||
|
LOG(LVL_ERR, "activateStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_destroy(sdr_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
close_streams(ctx);
|
||||||
|
|
||||||
|
nco_crcf_destroy(ctx->rx_nco);
|
||||||
|
nco_crcf_destroy(ctx->tx_nco);
|
||||||
|
|
||||||
|
if(ctx->sdr) {
|
||||||
|
SoapySDRDevice_unmake(ctx->sdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_start_rx(sdr_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_start_tx(sdr_ctx_t *ctx, size_t burst_size)
|
||||||
|
{
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_stop_rx(sdr_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_stop_tx(sdr_ctx_t *ctx)
|
||||||
|
{
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, int64_t time_ns, long timeout_us)
|
||||||
|
{
|
||||||
|
if(ctx->tx_stream == NULL) {
|
||||||
|
return ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
if(time_ns != 0) {
|
||||||
|
flags |= SOAPY_SDR_HAS_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *buffs[] = {(void*)(samples)};
|
||||||
|
|
||||||
|
int ret = SoapySDRDevice_writeStream(ctx->sdr, ctx->tx_stream, (const void* const*)buffs, nsamples, &flags, time_ns, timeout_us);
|
||||||
|
|
||||||
|
if(ret <= 0) {
|
||||||
|
LOG(LVL_ERR, "writeStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
} else if(ret != nsamples) {
|
||||||
|
LOG(LVL_WARN, "writeStream() did not transmit all samples: %i/%zu", ret, nsamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
long long timeNs;
|
||||||
|
int flags;
|
||||||
|
int ret = SoapySDRDevice_readStream(ctx->sdr, ctx->rx_stream, (void* const*)buffs, *nsamples, &flags, &timeNs, timeout_us);
|
||||||
|
|
||||||
|
if(ret <= 0) {
|
||||||
|
LOG(LVL_ERR, "readStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
*nsamples = ret;
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
34
impl/src/sdr/sdr_sxceiver.h
Normal file
34
impl/src/sdr/sdr_sxceiver.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef SDR_SDR_H
|
||||||
|
#define SDR_SDR_H
|
||||||
|
|
||||||
|
#include <complex.h>
|
||||||
|
|
||||||
|
#include <SoapySDR/Device.h>
|
||||||
|
|
||||||
|
#include <liquid/liquid.h>
|
||||||
|
|
||||||
|
#include "results.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SoapySDRDevice *sdr;
|
||||||
|
|
||||||
|
SoapySDRStream *rx_stream;
|
||||||
|
SoapySDRStream *tx_stream;
|
||||||
|
|
||||||
|
nco_crcf tx_nco;
|
||||||
|
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, size_t burst_size);
|
||||||
|
|
||||||
|
result_t sdr_stop_rx(sdr_ctx_t *ctx);
|
||||||
|
result_t sdr_stop_tx(sdr_ctx_t *ctx);
|
||||||
|
|
||||||
|
result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, int64_t time_ns, long timeout_us);
|
||||||
|
result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, long timeout_us);
|
||||||
|
|
||||||
|
#endif // SDR_SDR_H
|
|
@ -222,3 +222,24 @@ target_link_libraries(
|
||||||
test_interleaver
|
test_interleaver
|
||||||
m
|
m
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#------------------------------------
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
test_sx
|
||||||
|
../src/utils.c
|
||||||
|
../src/utils.h
|
||||||
|
../src/logger.c
|
||||||
|
../src/logger.h
|
||||||
|
test_sx.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
test_sx
|
||||||
|
liquid
|
||||||
|
m
|
||||||
|
rt
|
||||||
|
fftw3f
|
||||||
|
fec
|
||||||
|
SoapySDR
|
||||||
|
)
|
||||||
|
|
195
impl/test/test_sx.c
Normal file
195
impl/test/test_sx.c
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
#include <SoapySDR/Device.h>
|
||||||
|
#include <SoapySDR/Formats.h>
|
||||||
|
#include <SoapySDR/Logger.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "results.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
void soapy_log_handler(const SoapySDRLogLevel logLevel, const char *message)
|
||||||
|
{
|
||||||
|
int level;
|
||||||
|
|
||||||
|
switch(logLevel) {
|
||||||
|
case SOAPY_SDR_CRITICAL:
|
||||||
|
case SOAPY_SDR_FATAL:
|
||||||
|
level = LVL_FATAL;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_ERROR:
|
||||||
|
level = LVL_ERR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_WARNING:
|
||||||
|
level = LVL_WARN;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_INFO:
|
||||||
|
level = LVL_INFO;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SOAPY_SDR_DEBUG:
|
||||||
|
level = LVL_DEBUG;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
level = LVL_DUMP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(level, "soapy [%d]: %s", logLevel, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
SoapySDRDevice *sdr;
|
||||||
|
|
||||||
|
SoapySDRStream *rx_stream;
|
||||||
|
SoapySDRStream *tx_stream;
|
||||||
|
} sdr_ctx_t;
|
||||||
|
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
sdr_ctx_t ctx;
|
||||||
|
|
||||||
|
logger_init();
|
||||||
|
|
||||||
|
// set up logging
|
||||||
|
SoapySDR_registerLogHandler(soapy_log_handler);
|
||||||
|
SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
|
||||||
|
|
||||||
|
SoapySDRKwargs args;
|
||||||
|
memset(&args, 0, sizeof(args));
|
||||||
|
SoapySDRKwargs_set(&args, "driver", "sx");
|
||||||
|
ctx.sdr = SoapySDRDevice_make(&args);
|
||||||
|
SoapySDRKwargs_clear(&args);
|
||||||
|
|
||||||
|
if (ctx.sdr == NULL)
|
||||||
|
{
|
||||||
|
LOG(LVL_ERR, "SoapySDRDevice_make fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
//setup streams
|
||||||
|
ctx.rx_stream = SoapySDRDevice_setupStream(ctx.sdr, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL);
|
||||||
|
|
||||||
|
if(ctx.rx_stream == NULL) {
|
||||||
|
LOG(LVL_ERR, "setupStream fail: %s", 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) {
|
||||||
|
LOG(LVL_ERR, "setupStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
//apply settings
|
||||||
|
if (SoapySDRDevice_setSampleRate(ctx.sdr, SOAPY_SDR_RX, 0, SDR_RX_SAMPLING_RATE) != 0) {
|
||||||
|
LOG(LVL_ERR, "setSampleRate fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
if (SoapySDRDevice_setFrequency(ctx.sdr, SOAPY_SDR_RX, 0, SDR_RX_FREQ - SDR_RX_IF_SHIFT, NULL) != 0) {
|
||||||
|
LOG(LVL_ERR, "setFrequency fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SoapySDRDevice_setSampleRate(ctx.sdr, SOAPY_SDR_TX, 0, SDR_TX_SAMPLING_RATE) != 0) {
|
||||||
|
LOG(LVL_ERR, "setSampleRate fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
if (SoapySDRDevice_setFrequency(ctx.sdr, SOAPY_SDR_TX, 0, SDR_TX_FREQ, NULL) != 0) {
|
||||||
|
LOG(LVL_ERR, "setFrequency fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SoapySDRDevice_setGain(ctx.sdr, SOAPY_SDR_TX, 0, SDR_GAIN_TX) != 0) {
|
||||||
|
LOG(LVL_ERR, "setGain fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BUFSIZE 300000
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if(SoapySDRDevice_activateStream(ctx.sdr, ctx.tx_stream, 0, 0, 0) != 0) {
|
||||||
|
LOG(LVL_ERR, "activateStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float complex samples[BUFSIZE];
|
||||||
|
|
||||||
|
float twopi = 6.283185307f;
|
||||||
|
float offset_hz = 20e3;
|
||||||
|
float fm_osc_freq_hz = 1.0f;
|
||||||
|
float fm_osc_dev_hz = 5e3;
|
||||||
|
|
||||||
|
float fm_dphi = twopi * fm_osc_freq_hz / SDR_TX_SAMPLING_RATE;
|
||||||
|
|
||||||
|
float fm_phi = 0.0f;
|
||||||
|
float phi = 0.0f;
|
||||||
|
|
||||||
|
int64_t timeNs = 0;
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
|
||||||
|
for(size_t i = 0; i < BUFSIZE; i++) {
|
||||||
|
fm_phi += fm_dphi;
|
||||||
|
if(fm_phi > twopi) {
|
||||||
|
fm_phi -= twopi;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fm_dev_hz = fm_osc_dev_hz * sinf(fm_phi);
|
||||||
|
|
||||||
|
float dphi = twopi * (offset_hz + fm_dev_hz) / SDR_TX_SAMPLING_RATE;
|
||||||
|
|
||||||
|
phi += dphi;
|
||||||
|
if(phi > twopi) {
|
||||||
|
phi -= twopi;
|
||||||
|
}
|
||||||
|
samples[i] = cexpf(I * phi);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNs += 1000000000LL + 1000000000LL * BUFSIZE / SDR_TX_SAMPLING_RATE;
|
||||||
|
|
||||||
|
if(SoapySDRDevice_activateStream(ctx.sdr, ctx.tx_stream, 0, 0, 0) != 0) {
|
||||||
|
LOG(LVL_ERR, "activateStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
double tstart = get_hires_time();
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
void *buffs[] = {(void*)samples};
|
||||||
|
size_t timeout_us = 100000;
|
||||||
|
int ret = SoapySDRDevice_writeStream(ctx.sdr, ctx.tx_stream, (const void* const*)buffs, BUFSIZE, &flags, timeNs, timeout_us);
|
||||||
|
|
||||||
|
if(ret <= 0) {
|
||||||
|
LOG(LVL_ERR, "writeStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
double tend_write = get_hires_time();
|
||||||
|
|
||||||
|
if(SoapySDRDevice_deactivateStream(ctx.sdr, ctx.tx_stream, 0, 0) != 0) {
|
||||||
|
LOG(LVL_ERR, "deactivateStream fail: %s", SoapySDRDevice_lastError());
|
||||||
|
return ERR_SOAPY;
|
||||||
|
}
|
||||||
|
|
||||||
|
double tend = get_hires_time();
|
||||||
|
|
||||||
|
LOG(LVL_INFO, "write duration: %.3f ms", (tend_write - tstart) * 1000);
|
||||||
|
LOG(LVL_INFO, "total duration: %.3f ms", (tend - tstart) * 1000);
|
||||||
|
|
||||||
|
|
||||||
|
fsleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OK;
|
||||||
|
}
|
Loading…
Reference in a new issue