Use libhackrf directly (not tested yet)

This commit is contained in:
Thomas Kolb 2024-01-02 22:49:16 +01:00
parent 7995f5bef0
commit 2b741acd8a
5 changed files with 219 additions and 191 deletions

View file

@ -1,4 +1,4 @@
cmake_minimum_required (VERSION 3.2) cmake_minimum_required (VERSION 3.20)
project (hamnet70 VERSION 0.1 LANGUAGES C) project (hamnet70 VERSION 0.1 LANGUAGES C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
@ -53,7 +53,7 @@ target_link_libraries(
rt rt
fftw3f fftw3f
fec fec
SoapySDR hackrf
) )
add_subdirectory(test) add_subdirectory(test)

View file

@ -2,12 +2,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <assert.h>
#include <math.h>
#include <ctype.h> #include <ctype.h>
#include <SoapySDR/Logger.h>
#include <liquid/liquid.h> #include <liquid/liquid.h>
#include <sys/poll.h> #include <sys/poll.h>
@ -195,8 +191,6 @@ int main(void)
sdr_ctx_t sdr; sdr_ctx_t sdr;
SoapySDR_setLogLevel(SOAPY_SDR_DEBUG);
bool on_air = true; bool on_air = true;
debug_fd = open("/tmp/dump.cf32", O_CREAT | O_WRONLY | O_TRUNC); debug_fd = open("/tmp/dump.cf32", O_CREAT | O_WRONLY | O_TRUNC);

View file

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

View file

@ -1,43 +1,152 @@
#include <SoapySDR/Device.h> #include <libhackrf/hackrf.h>
#include <SoapySDR/Formats.h>
#include <SoapySDR/Logger.h>
#include <liquid/liquid.h> #include <liquid/liquid.h>
#include <semaphore.h>
#include <stdio.h> //printf #include <stdio.h> //printf
#include <stdlib.h> //free #include <stdlib.h> //free
#include <complex.h> #include <complex.h>
#include <math.h> #include <math.h>
#include <string.h> #include <string.h>
#include <time.h>
#include "config.h" #include "config.h"
#include "sdr.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)", 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 * (transfer->buffer[2*i + 0] + I * transfer->buffer[2*i + 1]);
int result = cbuffercf_push(sdr_ctx->rx_buf, sample);
if(result != LIQUID_OK) {
fprintf(stderr, "cbuffercf_push failed: %d. Samples are lost.\n", 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) { if(s >= 127.0f) {
SoapySDRDevice_deactivateStream(ctx->sdr, ctx->rx_stream, 0, 0); return 127;
SoapySDRDevice_closeStream(ctx->sdr, ctx->rx_stream); } else if(s <= -128.0f) {
return -128;
} else {
return (int8_t)(s + 0.5f);
}
}
static int tx_callback(hackrf_transfer *transfer)
{
int result;
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) { if(sem_wait(&sdr_ctx->buf_sem) < 0) {
SoapySDRDevice_deactivateStream(ctx->sdr, ctx->tx_stream, 0, 0); perror("sem_wait");
SoapySDRDevice_closeStream(ctx->sdr, ctx->tx_stream); 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);
return HACKRF_ERROR_OTHER;
}
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(sem_post(&sdr_ctx->buf_sem) < 0) {
perror("sem_post");
return HACKRF_ERROR_OTHER;
}
return 0;
}
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;
}
struct timespec ts_1ms = {0, 1000000};
while(hackrf_is_streaming(ctx->hackrf) == HACKRF_TRUE) {
puts("wr");
nanosleep(&ts_1ms, NULL);
}
return OK;
} }
result_t sdr_init(sdr_ctx_t *ctx) result_t sdr_init(sdr_ctx_t *ctx)
{ {
size_t length; int result;
ctx->sdr = NULL; result = sem_init(&ctx->buf_sem, 0, 1);
ctx->tx_stream = NULL; if(result < 0) {
ctx->rx_stream = NULL; 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->interp = firinterp_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f);
ctx->decim = firdecim_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f); ctx->decim = firdecim_crcf_create_kaiser(SDR_OVERSAMPLING, 9, 60.0f);
@ -48,88 +157,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->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); nco_crcf_set_frequency(ctx->rx_nco, 2 * M_PI * SDR_RX_IF_SHIFT / SDR_RX_SAMPLING_RATE);
// set up logging ctx->rx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
SoapySDR_registerLogHandler(soapy_log_handler); ctx->tx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
//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;
}
return OK; return OK;
} }
@ -137,7 +166,8 @@ result_t sdr_init(sdr_ctx_t *ctx)
result_t sdr_destroy(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->rx_nco);
nco_crcf_destroy(ctx->tx_nco); nco_crcf_destroy(ctx->tx_nco);
@ -145,9 +175,10 @@ result_t sdr_destroy(sdr_ctx_t *ctx)
firdecim_crcf_destroy(ctx->decim); firdecim_crcf_destroy(ctx->decim);
firinterp_crcf_destroy(ctx->interp); firinterp_crcf_destroy(ctx->interp);
if(ctx->sdr) { cbuffercf_destroy(ctx->rx_buf);
SoapySDRDevice_unmake(ctx->sdr); cbuffercf_destroy(ctx->tx_buf);
}
sem_close(&ctx->buf_sem);
return OK; return OK;
} }
@ -155,26 +186,22 @@ result_t sdr_destroy(sdr_ctx_t *ctx)
result_t sdr_start_rx(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) { int result;
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
// set gains result = hackrf_set_freq(ctx->hackrf, SDR_RX_FREQ - SDR_RX_IF_SHIFT);
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "AMP", SDR_GAIN_RX_AMP) != 0) { CHECK_HACKRF_RESULT(result, "hackrf_set_freq");
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "LNA", SDR_GAIN_RX_LNA) != 0) { result = hackrf_set_lna_gain(ctx->hackrf, SDR_GAIN_RX_LNA);
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError()); CHECK_HACKRF_RESULT(result, "hackrf_set_lna_gain");
return ERR_SOAPY;
}
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_RX, 0, "VGA", SDR_GAIN_RX_VGA) != 0) { result = hackrf_set_vga_gain(ctx->hackrf, SDR_GAIN_RX_VGA);
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError()); CHECK_HACKRF_RESULT(result, "hackrf_set_vga_gain");
return ERR_SOAPY;
} 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");
return OK; return OK;
} }
@ -182,21 +209,21 @@ 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_start_tx(sdr_ctx_t *ctx, size_t burst_size)
{ {
// set gain (void)burst_size;
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;
}
if(SoapySDRDevice_setGainElement(ctx->sdr, SOAPY_SDR_TX, 0, "LNA", SDR_GAIN_TX_LNA) != 0) { int result;
fprintf(stderr, "sdr: setGainElement fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
if(SoapySDRDevice_activateStream(ctx->sdr, ctx->tx_stream, 0, 0, burst_size) != 0) { result = hackrf_set_freq(ctx->hackrf, SDR_TX_FREQ);
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError()); CHECK_HACKRF_RESULT(result, "hackrf_set_freq");
return ERR_SOAPY;
} 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_AMP);
CHECK_HACKRF_RESULT(result, "hackrf_set_txvga_gain");
result = hackrf_start_tx(ctx->hackrf, tx_callback, ctx);
CHECK_HACKRF_RESULT(result, "hackrf_start_tx");
return OK; return OK;
} }
@ -204,61 +231,70 @@ 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_rx(sdr_ctx_t *ctx)
{ {
if(SoapySDRDevice_deactivateStream(ctx->sdr, ctx->rx_stream, 0, 0) != 0) { return stop_streaming(ctx);
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
return OK;
} }
result_t sdr_stop_tx(sdr_ctx_t *ctx) result_t sdr_stop_tx(sdr_ctx_t *ctx)
{ {
if(SoapySDRDevice_deactivateStream(ctx->sdr, ctx->tx_stream, 0, 0) != 0) { return stop_streaming(ctx);
fprintf(stderr, "sdr: activateStream fail: %s\n", SoapySDRDevice_lastError());
return ERR_SOAPY;
}
return OK;
} }
result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, long timeout_us) result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, long timeout_us)
{ {
if(ctx->tx_stream == NULL) { (void)timeout_us; // not implemented yet
return ERR_INVALID_STATE;
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) {
int ret = SoapySDRDevice_writeStream(ctx->sdr, ctx->tx_stream, (const void* const*)buffs, nsamples, 0, 0, timeout_us); fprintf(stderr, "cbuffercf_push failed: %d. Samples are lost.\n", result);
sem_post(&ctx->buf_sem);
if(ret <= 0) { return ERR_LIQUID;
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; return OK;
} }
result_t sdr_receive(sdr_ctx_t *ctx, 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)
{ {
if(ctx->rx_stream == NULL) { (void)timeout_us; // not implemented yet
return ERR_INVALID_STATE;
int result;
if(sem_wait(&ctx->buf_sem) < 0) {
perror("sem_wait");
return ERR_SDR;
} }
void *buffs[] = {(void*)samples}; unsigned int samples_read;
float complex *buf_samples;
int ret = SoapySDRDevice_readStream(ctx->sdr, ctx->rx_stream, (void* const*)buffs, *nsamples, 0, 0, timeout_us); result = cbuffercf_read(ctx->tx_buf, *nsamples, &buf_samples, &samples_read);
if(result != LIQUID_OK) {
if(ret <= 0) { fprintf(stderr, "cbuffercf_read failed: %d. Samples are lost.\n", result);
fprintf(stderr, "sdr: readStream fail: %s\n", SoapySDRDevice_lastError()); sem_post(&ctx->buf_sem);
return ERR_SOAPY; return ERR_LIQUID;
} }
*nsamples = ret; memcpy(samples, buf_samples, samples_read * sizeof(samples[0]));
if(sem_post(&ctx->buf_sem) < 0) {
perror("sem_post");
return ERR_SDR;
}
*nsamples = samples_read;
return OK; return OK;
} }
@ -266,21 +302,8 @@ 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) result_t sdr_flush_tx_buffer(sdr_ctx_t *ctx)
{ {
size_t mtu = SoapySDRDevice_getStreamMTU(ctx->sdr, ctx->tx_stream); // currently not implemented
(void)ctx;
float complex zeros[mtu];
memset(zeros, 0, sizeof(zeros));
for(int i = 0; i < 4; i++) {
fprintf(stderr, "z");
unsigned tries = 0;
int ret;
do {
ret = sdr_transmit(ctx, zeros, sizeof(zeros)/sizeof(zeros[0]), 100000);
} while(ret <= 0 && tries++ < 3);
}
return OK; return OK;
} }

View file

@ -2,24 +2,34 @@
#define SDR_SDR_H #define SDR_SDR_H
#include <complex.h> #include <complex.h>
#include <semaphore.h>
#include <SoapySDR/Device.h> #include <libhackrf/hackrf.h>
#include <liquid/liquid.h> #include <liquid/liquid.h>
#include "results.h" #include "results.h"
typedef struct { typedef enum {
SoapySDRDevice *sdr; SDR_STATUS_IDLE,
SDR_STATUS_TX,
SDR_STATUS_RX
} sdr_status_t;
SoapySDRStream *rx_stream; typedef struct {
SoapySDRStream *tx_stream; hackrf_device *hackrf;
firinterp_crcf interp; firinterp_crcf interp;
nco_crcf tx_nco; nco_crcf tx_nco;
cbuffercf tx_buf;
firdecim_crcf decim; firdecim_crcf decim;
nco_crcf rx_nco; nco_crcf rx_nco;
cbuffercf rx_buf;
sem_t buf_sem;
sdr_status_t status;
} sdr_ctx_t; } sdr_ctx_t;
result_t sdr_init(sdr_ctx_t *ctx); result_t sdr_init(sdr_ctx_t *ctx);