476 lines
10 KiB
C
476 lines
10 KiB
C
#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"
|
|
|
|
#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)
|
|
{
|
|
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 inline int8_t clamp_float2int8(float s)
|
|
{
|
|
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(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)
|
|
{
|
|
int result;
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
ctx->rx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
|
|
ctx->tx_buf = cbuffercf_create(SDR_BUFFER_SIZE_SAMPLES);
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
result_t sdr_destroy(sdr_ctx_t *ctx)
|
|
{
|
|
stop_streaming(ctx);
|
|
hackrf_close(ctx->hackrf);
|
|
|
|
nco_crcf_destroy(ctx->rx_nco);
|
|
nco_crcf_destroy(ctx->tx_nco);
|
|
|
|
firdecim_crcf_destroy(ctx->decim);
|
|
firinterp_crcf_destroy(ctx->interp);
|
|
|
|
cbuffercf_destroy(ctx->rx_buf);
|
|
cbuffercf_destroy(ctx->tx_buf);
|
|
|
|
sem_close(&ctx->buf_sem);
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
result_t sdr_start_rx(sdr_ctx_t *ctx)
|
|
{
|
|
int result;
|
|
|
|
result = hackrf_set_sample_rate(ctx->hackrf, SDR_RX_SAMPLING_RATE);
|
|
CHECK_HACKRF_RESULT(result, "hackrf_set_sample_rate");
|
|
|
|
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");
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
result_t sdr_start_tx(sdr_ctx_t *ctx, size_t burst_size)
|
|
{
|
|
(void)burst_size;
|
|
|
|
int result;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
result_t sdr_stop_rx(sdr_ctx_t *ctx)
|
|
{
|
|
return stop_streaming(ctx);
|
|
}
|
|
|
|
|
|
result_t sdr_stop_tx(sdr_ctx_t *ctx)
|
|
{
|
|
return stop_streaming(ctx);
|
|
}
|
|
|
|
|
|
result_t sdr_transmit(sdr_ctx_t *ctx, const float complex *samples, size_t nsamples, long timeout_us)
|
|
{
|
|
(void)timeout_us; // not implemented yet
|
|
|
|
if(sem_wait(&ctx->buf_sem) < 0) {
|
|
perror("sem_wait failed. Samples lost. Error:");
|
|
return ERR_SDR;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if(sem_post(&ctx->buf_sem) < 0) {
|
|
perror("sem_post");
|
|
return ERR_SDR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
|
|
result_t sdr_receive(sdr_ctx_t *ctx, float complex *samples, size_t *nsamples, long timeout_us)
|
|
{
|
|
(void)timeout_us; // not implemented yet
|
|
|
|
int result;
|
|
|
|
if(sem_wait(&ctx->buf_sem) < 0) {
|
|
perror("sem_wait");
|
|
return ERR_SDR;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
result_t sdr_flush_tx_buffer(sdr_ctx_t *ctx)
|
|
{
|
|
// block until all samples have been transmitted
|
|
while(true) {
|
|
if(sem_wait(&ctx->buf_sem) < 0) {
|
|
perror("sem_wait");
|
|
return 0;
|
|
}
|
|
|
|
double now = get_hires_time();
|
|
double end = ctx->tx_start_time + ctx->tx_duration;
|
|
|
|
if(sem_post(&ctx->buf_sem) < 0) {
|
|
perror("sem_post");
|
|
return 0;
|
|
}
|
|
|
|
if(now >= end) {
|
|
break;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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(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) {
|
|
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];
|
|
|
|
firinterp_crcf_execute(ctx->interp, bb_samples[i] / 2.0f, tmp);
|
|
|
|
nco_crcf_mix_block_up(ctx->tx_nco,
|
|
tmp,
|
|
rf_samples + i * SDR_OVERSAMPLING,
|
|
SDR_OVERSAMPLING);
|
|
}
|
|
|
|
return OK;
|
|
}
|