Restructure: Move sdr from common code to 'libsdr'
This commit is contained in:
26
src/libsdr/Makefile.am
Normal file
26
src/libsdr/Makefile.am
Normal file
@@ -0,0 +1,26 @@
|
||||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libsdr.a
|
||||
|
||||
libsdr_a_SOURCES = \
|
||||
dd sdr_config.c \
|
||||
sdr.c \
|
||||
display_iq.c \
|
||||
display_spectrum.c
|
||||
|
||||
AM_CPPFLAGS += -DHAVE_SDR
|
||||
|
||||
if HAVE_UHD
|
||||
AM_CPPFLAGS += -DHAVE_UHD
|
||||
|
||||
libsdr_a_SOURCES += \
|
||||
uhd.c
|
||||
endif
|
||||
|
||||
if HAVE_SOAPY
|
||||
AM_CPPFLAGS += -DHAVE_SOAPY
|
||||
|
||||
libsdr_a_SOURCES += \
|
||||
soapy.c
|
||||
endif
|
||||
|
280
src/libsdr/display_iq.c
Normal file
280
src/libsdr/display_iq.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/* display IQ data form functions
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include "../common/sample.h"
|
||||
#include "../common/sender.h"
|
||||
|
||||
/* must be odd value! */
|
||||
#define SIZE 23
|
||||
|
||||
static char screen[SIZE][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_color[SIZE][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_history[SIZE * 2][MAX_DISPLAY_WIDTH];
|
||||
static int iq_on = 0;
|
||||
static double db = 80;
|
||||
|
||||
static dispiq_t disp;
|
||||
|
||||
void display_iq_init(int samplerate)
|
||||
{
|
||||
memset(&disp, 0, sizeof(disp));
|
||||
memset(&screen_history, 0, sizeof(screen_history));
|
||||
disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5;
|
||||
/* should not happen due to low interval */
|
||||
if (disp.interval_max < MAX_DISPLAY_IQ - 1)
|
||||
disp.interval_max = MAX_DISPLAY_IQ - 1;
|
||||
}
|
||||
|
||||
void display_iq_on(int on)
|
||||
{
|
||||
int j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
|
||||
if (iq_on) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_history, 0, sizeof(screen_history));
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < SIZE; j++) {
|
||||
screen[j][w] = '\0';
|
||||
puts(screen[j]);
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
}
|
||||
|
||||
if (on < 0) {
|
||||
if (++iq_on == 3)
|
||||
iq_on = 0;
|
||||
} else
|
||||
iq_on = on;
|
||||
}
|
||||
|
||||
void display_iq_limit_scroll(int on)
|
||||
{
|
||||
int w, h;
|
||||
|
||||
if (!iq_on)
|
||||
return;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
|
||||
printf("\0337");
|
||||
printf("\033[%d;%dr", (on) ? SIZE + 1 : 1, h);
|
||||
printf("\0338");
|
||||
}
|
||||
|
||||
/*
|
||||
* plot IQ data:
|
||||
*
|
||||
* theoretical example: SIZE = 3 allows 6 steps plotted as dots
|
||||
*
|
||||
* Line 0: :
|
||||
* Line 1: :
|
||||
* Line 2: :
|
||||
*
|
||||
* The level of -1.0 .. 1.0 is scaled to -3 and 3.
|
||||
*
|
||||
* The lowest of the upper 3 dots ranges from 0.0 .. <1.5.
|
||||
* The upper most dot ranges from 2.5 .. <3.5.
|
||||
* The highest of the lower 3 dots ranges from <0.0 .. >-1.5;
|
||||
* The lower most dot ranges from -2.5 .. >-3.5.
|
||||
*
|
||||
* The center column ranges from -0.5 .. <0.5.
|
||||
* The columns about the center from -1.5 .. <1.5.
|
||||
*/
|
||||
void display_iq(float *samples, int length)
|
||||
{
|
||||
int pos, max;
|
||||
float *buffer;
|
||||
int i, j, k;
|
||||
int color = 9; /* default color */
|
||||
int x_center, y_center;
|
||||
double I, Q, L, l, s;
|
||||
int x, y;
|
||||
int v, r;
|
||||
int width, h;
|
||||
|
||||
if (!iq_on)
|
||||
return;
|
||||
|
||||
get_win_size(&width, &h);
|
||||
|
||||
/* at what line we draw our zero-line and what character we use */
|
||||
x_center = width >> 1;
|
||||
y_center = (SIZE - 1) >> 1;
|
||||
|
||||
pos = disp.interval_pos;
|
||||
max = disp.interval_max;
|
||||
buffer = disp.buffer;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (pos >= MAX_DISPLAY_IQ) {
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
buffer[pos * 2] = samples[i * 2];
|
||||
buffer[pos * 2 + 1] = samples[i * 2 + 1];
|
||||
pos++;
|
||||
if (pos == MAX_DISPLAY_IQ) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_color, 7, sizeof(screen_color));
|
||||
/* render screen history to screen */
|
||||
for (y = 0; y < SIZE * 2; y++) {
|
||||
for (x = 0; x < width; x++) {
|
||||
v = screen_history[y][x];
|
||||
v -= 8;
|
||||
if (v < 0)
|
||||
v = 0;
|
||||
screen_history[y][x] = v;
|
||||
r = random() & 0x3f;
|
||||
if (r >= v)
|
||||
continue;
|
||||
if (screen[y/2][x] == ':')
|
||||
continue;
|
||||
if (screen[y/2][x] == '.') {
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = ':';
|
||||
continue;
|
||||
}
|
||||
if (screen[y/2][x] == '\'') {
|
||||
if ((y & 1))
|
||||
screen[y/2][x] = ':';
|
||||
continue;
|
||||
}
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = '\'';
|
||||
else
|
||||
screen[y/2][x] = '.';
|
||||
screen_color[y/2][x] = 4;
|
||||
}
|
||||
}
|
||||
/* plot current IQ date */
|
||||
for (j = 0; j < MAX_DISPLAY_IQ; j++) {
|
||||
I = buffer[j * 2];
|
||||
Q = buffer[j * 2 + 1];
|
||||
L = I*I + Q*Q;
|
||||
if (iq_on > 1) {
|
||||
/* logarithmic scale */
|
||||
l = sqrt(L);
|
||||
s = log10(l) * 20 + db;
|
||||
if (s < 0)
|
||||
s = 0;
|
||||
I = (I / l) * (s / db);
|
||||
Q = (Q / l) * (s / db);
|
||||
}
|
||||
x = x_center + (int)(I * (double)SIZE + (double)width + 0.5) - width;
|
||||
if (x < 0)
|
||||
continue;
|
||||
if (x > width - 1)
|
||||
continue;
|
||||
if (Q >= 0)
|
||||
y = SIZE - 1 - (int)(Q * (double)SIZE - 0.5);
|
||||
else
|
||||
y = SIZE - (int)(Q * (double)SIZE + 0.5);
|
||||
if (y < 0)
|
||||
continue;
|
||||
if (y > SIZE * 2 - 1)
|
||||
continue;
|
||||
if (screen[y/2][x] == ':' && screen_color[y/2][x] >= 10)
|
||||
goto cont;
|
||||
if (screen[y/2][x] == '.' && screen_color[y/2][x] >= 10) {
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = ':';
|
||||
goto cont;
|
||||
}
|
||||
if (screen[y/2][x] == '\'' && screen_color[y/2][x] >= 10) {
|
||||
if ((y & 1))
|
||||
screen[y/2][x] = ':';
|
||||
goto cont;
|
||||
}
|
||||
if ((y & 1) == 0)
|
||||
screen[y/2][x] = '\'';
|
||||
else
|
||||
screen[y/2][x] = '.';
|
||||
cont:
|
||||
screen_history[y][x] = 255;
|
||||
/* overdrive:
|
||||
* red = close to -1..1 or above
|
||||
* yellow = close to -0.5..0.5 or above
|
||||
* Note: L is square of vector length,
|
||||
* so we compare with square values.
|
||||
*/
|
||||
if (L > 0.9 * 0.9)
|
||||
screen_color[y/2][x] = 11;
|
||||
else if (L > 0.45 * 0.45 && screen_color[y/2][x] != 11)
|
||||
screen_color[y/2][x] = 13;
|
||||
else if (screen_color[y/2][x] < 10)
|
||||
screen_color[y/2][x] = 12;
|
||||
}
|
||||
if (iq_on == 1)
|
||||
sprintf(screen[0], "(IQ linear");
|
||||
else
|
||||
sprintf(screen[0], "(IQ log %.0f dB", db);
|
||||
*strchr(screen[0], '\0') = ')';
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < SIZE; j++) {
|
||||
for (k = 0; k < width; k++) {
|
||||
if ((j == y_center || k == x_center) && screen[j][k] == ' ') {
|
||||
/* cross */
|
||||
if (color != 4) {
|
||||
color = 4;
|
||||
printf("\033[0;34m");
|
||||
}
|
||||
if (j == y_center) {
|
||||
if (k == x_center)
|
||||
putchar('o');
|
||||
else if (k == x_center - SIZE)
|
||||
putchar('+');
|
||||
else if (k == x_center + SIZE)
|
||||
putchar('+');
|
||||
else
|
||||
putchar('-');
|
||||
} else {
|
||||
if (j == 0 || j == SIZE - 1)
|
||||
putchar('+');
|
||||
else
|
||||
putchar('|');
|
||||
}
|
||||
} else {
|
||||
if (screen_color[j][k] != color) {
|
||||
color = screen_color[j][k];
|
||||
printf("\033[%d;3%dm", color / 10, color % 10);
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
disp.interval_pos = pos;
|
||||
}
|
||||
|
||||
|
292
src/libsdr/display_spectrum.c
Normal file
292
src/libsdr/display_spectrum.c
Normal file
@@ -0,0 +1,292 @@
|
||||
/* display spectrum of IQ data
|
||||
*
|
||||
* (C) 2016 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "../common/sample.h"
|
||||
#include "../common/sender.h"
|
||||
#include "../libfft/fft.h"
|
||||
|
||||
#define HEIGHT 20
|
||||
|
||||
static double buffer_max[MAX_DISPLAY_SPECTRUM];
|
||||
static char screen[HEIGHT][MAX_DISPLAY_WIDTH];
|
||||
static uint8_t screen_color[HEIGHT][MAX_DISPLAY_WIDTH];
|
||||
static int spectrum_on = 0;
|
||||
static double db = 120;
|
||||
static double center_frequency, frequency_range;
|
||||
|
||||
static dispspectrum_t disp;
|
||||
|
||||
void display_spectrum_init(int samplerate, double _center_frequency)
|
||||
{
|
||||
memset(&disp, 0, sizeof(disp));
|
||||
disp.interval_max = (double)samplerate * DISPLAY_INTERVAL + 0.5;
|
||||
/* should not happen due to low interval */
|
||||
if (disp.interval_max < MAX_DISPLAY_SPECTRUM - 1)
|
||||
disp.interval_max = MAX_DISPLAY_SPECTRUM - 1;
|
||||
memset(buffer_max, 0, sizeof(buffer_max));
|
||||
|
||||
center_frequency = _center_frequency;
|
||||
frequency_range = (double)samplerate;
|
||||
}
|
||||
|
||||
void display_spectrum_on(int on)
|
||||
{
|
||||
int j;
|
||||
int w, h;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
|
||||
if (spectrum_on) {
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
screen[j][w] = '\0';
|
||||
puts(screen[j]);
|
||||
}
|
||||
printf("\0338"); fflush(stdout);
|
||||
}
|
||||
|
||||
if (on < 0) {
|
||||
if (++spectrum_on == 2)
|
||||
spectrum_on = 0;
|
||||
} else
|
||||
spectrum_on = on;
|
||||
}
|
||||
|
||||
void display_spectrum_limit_scroll(int on)
|
||||
{
|
||||
int w, h;
|
||||
|
||||
if (!spectrum_on)
|
||||
return;
|
||||
|
||||
get_win_size(&w, &h);
|
||||
|
||||
printf("\0337");
|
||||
printf("\033[%d;%dr", (on) ? HEIGHT + 1 : 1, h);
|
||||
printf("\0338");
|
||||
}
|
||||
|
||||
/*
|
||||
* plot spectrum data:
|
||||
*
|
||||
*/
|
||||
void display_spectrum(float *samples, int length)
|
||||
{
|
||||
sender_t *sender;
|
||||
char print_channel[32], print_frequency[32];
|
||||
int width, h;
|
||||
int pos, max;
|
||||
double *buffer_I, *buffer_Q;
|
||||
int color = 9; /* default color */
|
||||
int i, j, k, o;
|
||||
double I, Q, v;
|
||||
int s, e, l, n;
|
||||
|
||||
if (!spectrum_on)
|
||||
return;
|
||||
|
||||
get_win_size(&width, &h);
|
||||
if (width > MAX_DISPLAY_WIDTH)
|
||||
width = MAX_DISPLAY_WIDTH;
|
||||
|
||||
/* calculate size of FFT */
|
||||
int m, fft_size = 0, fft_taps = 0;
|
||||
for (m = 0; m < 16; m++) {
|
||||
if ((1 << m) > MAX_DISPLAY_SPECTRUM)
|
||||
break;
|
||||
if ((1 << m) <= width) {
|
||||
fft_taps = m;
|
||||
fft_size = 1 << m;
|
||||
}
|
||||
}
|
||||
if (m == 16) {
|
||||
fprintf(stderr, "Size of spectrum is not a power of 2, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
int heigh[fft_size], low[fft_size];
|
||||
|
||||
pos = disp.interval_pos;
|
||||
max = disp.interval_max;
|
||||
buffer_I = disp.buffer_I;
|
||||
buffer_Q = disp.buffer_Q;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if (pos >= fft_size) {
|
||||
if (++pos == max)
|
||||
pos = 0;
|
||||
continue;
|
||||
}
|
||||
buffer_I[pos] = samples[i * 2];
|
||||
buffer_Q[pos] = samples[i * 2 + 1];
|
||||
pos++;
|
||||
if (pos == fft_size) {
|
||||
fft_process(1, fft_taps, buffer_I, buffer_Q);
|
||||
k = 0;
|
||||
for (j = 0; j < fft_size; j++) {
|
||||
/* scale result vertically */
|
||||
I = buffer_I[(j + fft_size / 2) % fft_size];
|
||||
Q = buffer_Q[(j + fft_size / 2) % fft_size];
|
||||
v = sqrt(I*I + Q*Q);
|
||||
v = log10(v) * 20 + db;
|
||||
if (v < 0)
|
||||
v = 0;
|
||||
v /= db;
|
||||
buffer_max[j] -= DISPLAY_INTERVAL / 10.0;
|
||||
if (v > buffer_max[j])
|
||||
buffer_max[j] = v;
|
||||
|
||||
/* heigh is the maximum value */
|
||||
heigh[j] = (double)(HEIGHT * 2 - 1) * (1.0 - buffer_max[j]);
|
||||
if (heigh[j] < 0)
|
||||
heigh[j] = 0;
|
||||
if (heigh[j] >= (HEIGHT * 2))
|
||||
heigh[j] = (HEIGHT * 2) - 1;
|
||||
/* low is the current value */
|
||||
low[j] = (double)(HEIGHT * 2 - 1) * (1.0 - v);
|
||||
if (low[j] < 0)
|
||||
low[j] = 0;
|
||||
if (low[j] >= (HEIGHT * 2))
|
||||
low[j] = (HEIGHT * 2) - 1;
|
||||
}
|
||||
/* plot scaled buffer */
|
||||
memset(&screen, ' ', sizeof(screen));
|
||||
memset(&screen_color, 7, sizeof(screen_color)); /* all white */
|
||||
sprintf(screen[0], "(spectrum log %.0f dB", db);
|
||||
*strchr(screen[0], '\0') = ')';
|
||||
o = (width - fft_size) / 2; /* offset from left border */
|
||||
for (j = 0; j < fft_size; j++) {
|
||||
s = l = n = low[j];
|
||||
/* get last and next value */
|
||||
if (j > 0)
|
||||
l = (low[j - 1] + s) / 2;
|
||||
if (j < fft_size - 1)
|
||||
n = (low[j + 1] + s) / 2;
|
||||
if (s > l && s > n) {
|
||||
/* current value is a minimum */
|
||||
e = s;
|
||||
s = (l < n) ? (l + 1) : (n + 1);
|
||||
} else if (s < l && s < n) {
|
||||
/* current value is a maximum */
|
||||
e = (l > n) ? l : n;
|
||||
} else if (l < n) {
|
||||
/* last value is higher, next value is lower */
|
||||
s = l + 1;
|
||||
e = n;
|
||||
} else if (l > n) {
|
||||
/* last value is lower, next value is higher */
|
||||
s = n + 1;
|
||||
e = l;
|
||||
} else {
|
||||
/* current, last and next values are equal */
|
||||
e = s;
|
||||
}
|
||||
if (s == e) {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 13;
|
||||
} else {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '|';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 13;
|
||||
if ((e & 1) == 0)
|
||||
screen[e >> 1][j + o] = '\'';
|
||||
else
|
||||
screen[e >> 1][j + o] = '|';
|
||||
screen_color[e >> 1][j + o] = 13;
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++) {
|
||||
screen[k][j + o] = '|';
|
||||
screen_color[k][j + o] = 13;
|
||||
}
|
||||
}
|
||||
e = s;
|
||||
s = heigh[j];
|
||||
if ((s >> 1) < (e >> 1)) {
|
||||
if ((s & 1) == 0)
|
||||
screen[s >> 1][j + o] = '|';
|
||||
else
|
||||
screen[s >> 1][j + o] = '.';
|
||||
screen_color[s >> 1][j + o] = 4;
|
||||
for (k = (s >> 1) + 1; k < (e >> 1); k++) {
|
||||
screen[k][j + o] = '|';
|
||||
screen_color[k][j + o] = 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (sender = sender_head; sender; sender = sender->next) {
|
||||
j = (int)((sender->empfangsfrequenz - center_frequency) / frequency_range * (double) fft_size + width / 2 + 0.5);
|
||||
if (j < 0 || j >= width) /* check out-of-range, should not happen */
|
||||
continue;
|
||||
for (k = 0; k < HEIGHT; k++) {
|
||||
/* skip yellow graph */
|
||||
if (screen_color[k][j] == 13)
|
||||
continue;
|
||||
screen[k][j] = ':';
|
||||
screen_color[k][j] = 12;
|
||||
}
|
||||
sprintf(print_channel, "Ch%d", sender->kanal);
|
||||
for (o = 0; o < (int)strlen(print_channel); o++) {
|
||||
s = j - strlen(print_channel) + o;
|
||||
if (s >= 0 && s < width) {
|
||||
screen[HEIGHT - 1][s] = print_channel[o];
|
||||
screen_color[HEIGHT - 1][s] = 7;
|
||||
}
|
||||
}
|
||||
if (fmod(sender->empfangsfrequenz, 1000.0))
|
||||
sprintf(print_frequency, "%.4f", sender->empfangsfrequenz / 1e6);
|
||||
else
|
||||
sprintf(print_frequency, "%.3f", sender->empfangsfrequenz / 1e6);
|
||||
for (o = 0; o < (int)strlen(print_frequency); o++) {
|
||||
s = j + o + 1;
|
||||
if (s >= 0 && s < width) {
|
||||
screen[HEIGHT - 1][s] = print_frequency[o];
|
||||
screen_color[HEIGHT - 1][s] = 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("\0337\033[H");
|
||||
for (j = 0; j < HEIGHT; j++) {
|
||||
for (k = 0; k < width; k++) {
|
||||
if (screen_color[j][k] != color) {
|
||||
color = screen_color[j][k];
|
||||
printf("\033[%d;3%dm", color / 10, color % 10);
|
||||
}
|
||||
putchar(screen[j][k]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
/* reset color and position */
|
||||
printf("\033[0;39m\0338"); fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
disp.interval_pos = pos;
|
||||
}
|
||||
|
||||
|
865
src/libsdr/sdr.c
Normal file
865
src/libsdr/sdr.c
Normal file
@@ -0,0 +1,865 @@
|
||||
/* SDR processing
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum paging_signal;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <getopt.h>
|
||||
#define __USE_GNU
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include "../common/sample.h"
|
||||
#include "../libfm/fm.h"
|
||||
#include "../libtimer/timer.h"
|
||||
#include "../common/sender.h"
|
||||
#include "sdr_config.h"
|
||||
#include "sdr.h"
|
||||
#ifdef HAVE_UHD
|
||||
#include "uhd.h"
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
#include "soapy.h"
|
||||
#endif
|
||||
#include "../common/debug.h"
|
||||
|
||||
/* enable to debug buffer handling */
|
||||
//#define DEBUG_BUFFER
|
||||
|
||||
/* enable to test without oversampling filter */
|
||||
//#define DISABLE_FILTER
|
||||
|
||||
/* usable bandwidth of IQ rate, because no filter is perfect */
|
||||
#define USABLE_BANDWIDTH 0.75
|
||||
|
||||
int sdr_rx_overflow = 0;
|
||||
|
||||
typedef struct sdr_thread {
|
||||
int use;
|
||||
volatile int running, exit; /* flags to control exit of threads */
|
||||
int buffer_size;
|
||||
volatile float *buffer;
|
||||
float *buffer2;
|
||||
volatile int in, out; /* in and out pointers (atomic, so no locking required) */
|
||||
int max_fill; /* measure maximum buffer fill */
|
||||
double max_fill_timer; /* timer to display/reset maximum fill */
|
||||
iir_filter_t lp[2]; /* filter for upsample/downsample IQ data */
|
||||
} sdr_thread_t;
|
||||
|
||||
typedef struct sdr_chan {
|
||||
double tx_frequency; /* frequency used */
|
||||
double rx_frequency; /* frequency used */
|
||||
fm_mod_t mod; /* modulator instance */
|
||||
fm_demod_t demod; /* demodulator instance */
|
||||
dispmeasparam_t *dmp_rf_level;
|
||||
dispmeasparam_t *dmp_freq_offset;
|
||||
dispmeasparam_t *dmp_deviation;
|
||||
} sdr_chan_t;
|
||||
|
||||
typedef struct sdr {
|
||||
int threads; /* use threads */
|
||||
int oversample; /* oversample IQ rate */
|
||||
sdr_thread_t thread_read,
|
||||
thread_write;
|
||||
sdr_chan_t *chan; /* settings for all channels */
|
||||
int paging_channel; /* if set, points to paging channel */
|
||||
sdr_chan_t paging_chan; /* settings for extra paging channel */
|
||||
int channels; /* number of frequencies */
|
||||
double amplitude; /* amplitude of each carrier */
|
||||
int samplerate; /* sample rate of audio data */
|
||||
int latspl; /* latency in audio samples */
|
||||
wave_rec_t wave_rx_rec;
|
||||
wave_rec_t wave_tx_rec;
|
||||
wave_play_t wave_rx_play;
|
||||
wave_play_t wave_tx_play;
|
||||
float *modbuff; /* buffer for FM transmodulation */
|
||||
sample_t *modbuff_I;
|
||||
sample_t *modbuff_Q;
|
||||
sample_t *wavespl0; /* sample buffer for wave generation */
|
||||
sample_t *wavespl1;
|
||||
} sdr_t;
|
||||
|
||||
void *sdr_open(const char __attribute__((__unused__)) *audiodev, double *tx_frequency, double *rx_frequency, int channels, double paging_frequency, int samplerate, int latspl, double max_deviation, double max_modulation)
|
||||
{
|
||||
sdr_t *sdr;
|
||||
int threads = 1, oversample = 1; /* always use threads */
|
||||
double bandwidth;
|
||||
double tx_center_frequency = 0.0, rx_center_frequency = 0.0;
|
||||
int rc;
|
||||
int c;
|
||||
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Open SDR device\n");
|
||||
|
||||
if (sdr_config->samplerate != samplerate) {
|
||||
if (samplerate > sdr_config->samplerate) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "SDR sample rate must be greater than audio sample rate!\n");
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "You selected an SDR rate of %d and an audio rate of %d.\n", sdr_config->samplerate, samplerate);
|
||||
return NULL;
|
||||
}
|
||||
if ((sdr_config->samplerate % samplerate)) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "SDR sample rate must be a multiple of audio sample rate!\n");
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "You selected an SDR rate of %d and an audio rate of %d.\n", sdr_config->samplerate, samplerate);
|
||||
return NULL;
|
||||
}
|
||||
oversample = sdr_config->samplerate / samplerate;
|
||||
threads = 1;
|
||||
}
|
||||
|
||||
bandwidth = 2.0 * (max_deviation + max_modulation);
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Require bandwidth of each channel is 2 * (%.1f deviation + %.1f modulation) = %.1f KHz\n", max_deviation / 1e3, max_modulation / 1e3, bandwidth / 1e3);
|
||||
|
||||
if (channels < 1) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "No channel given, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
sdr = calloc(sizeof(*sdr), 1);
|
||||
if (!sdr) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->channels = channels;
|
||||
sdr->amplitude = 1.0 / (double)channels;
|
||||
sdr->samplerate = samplerate;
|
||||
sdr->latspl = latspl;
|
||||
sdr->threads = threads; /* always requried, because write may block */
|
||||
sdr->oversample = oversample;
|
||||
|
||||
if (threads) {
|
||||
memset(&sdr->thread_read, 0, sizeof(sdr->thread_read));
|
||||
sdr->thread_read.buffer_size = sdr->latspl * 2 * sdr->oversample + 2;
|
||||
sdr->thread_read.buffer = calloc(sdr->thread_read.buffer_size, sizeof(*sdr->thread_read.buffer));
|
||||
if (!sdr->thread_read.buffer) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "No mem!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->thread_read.buffer2 = calloc(sdr->thread_read.buffer_size, sizeof(*sdr->thread_read.buffer2));
|
||||
if (!sdr->thread_read.buffer2) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "No mem!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->thread_read.in = sdr->thread_read.out = 0;
|
||||
if (oversample > 1) {
|
||||
iir_lowpass_init(&sdr->thread_read.lp[0], samplerate / 2.0, sdr_config->samplerate, 2);
|
||||
iir_lowpass_init(&sdr->thread_read.lp[1], samplerate / 2.0, sdr_config->samplerate, 2);
|
||||
}
|
||||
memset(&sdr->thread_write, 0, sizeof(sdr->thread_write));
|
||||
sdr->thread_write.buffer_size = sdr->latspl * 2 + 2;
|
||||
sdr->thread_write.buffer = calloc(sdr->thread_write.buffer_size, sizeof(*sdr->thread_write.buffer));
|
||||
if (!sdr->thread_write.buffer) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "No mem!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->thread_write.buffer2 = calloc(sdr->thread_write.buffer_size * sdr->oversample, sizeof(*sdr->thread_write.buffer2));
|
||||
if (!sdr->thread_write.buffer2) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "No mem!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->thread_write.in = sdr->thread_write.out = 0;
|
||||
if (oversample > 1) {
|
||||
iir_lowpass_init(&sdr->thread_write.lp[0], samplerate / 2.0, sdr_config->samplerate, 2);
|
||||
iir_lowpass_init(&sdr->thread_write.lp[1], samplerate / 2.0, sdr_config->samplerate, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* alloc fm modulation buffers */
|
||||
sdr->modbuff = calloc(sdr->latspl * 2, sizeof(*sdr->modbuff));
|
||||
if (!sdr->modbuff) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->modbuff_I = calloc(sdr->latspl, sizeof(*sdr->modbuff_I));
|
||||
if (!sdr->modbuff_I) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->modbuff_Q = calloc(sdr->latspl, sizeof(*sdr->modbuff_Q));
|
||||
if (!sdr->modbuff_Q) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->wavespl0 = calloc(sdr->latspl, sizeof(*sdr->wavespl0));
|
||||
if (!sdr->wavespl0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
sdr->wavespl1 = calloc(sdr->latspl, sizeof(*sdr->wavespl1));
|
||||
if (!sdr->wavespl1) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* special case where we use a paging frequency */
|
||||
if (paging_frequency) {
|
||||
/* add extra paging channel */
|
||||
sdr->paging_channel = channels;
|
||||
}
|
||||
|
||||
/* create list of channel states */
|
||||
sdr->chan = calloc(sizeof(*sdr->chan), channels + (sdr->paging_channel != 0));
|
||||
if (!sdr->chan) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "NO MEM!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (tx_frequency) {
|
||||
/* calculate required bandwidth (IQ rate) */
|
||||
for (c = 0; c < channels; c++) {
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Frequency #%d: TX = %.6f MHz\n", c, tx_frequency[c] / 1e6);
|
||||
sdr->chan[c].tx_frequency = tx_frequency[c];
|
||||
}
|
||||
if (sdr->paging_channel) {
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Paging Frequency: TX = %.6f MHz\n", paging_frequency / 1e6);
|
||||
sdr->chan[sdr->paging_channel].tx_frequency = paging_frequency;
|
||||
}
|
||||
|
||||
double tx_low_frequency = sdr->chan[0].tx_frequency, tx_high_frequency = sdr->chan[0].tx_frequency;
|
||||
for (c = 1; c < channels; c++) {
|
||||
if (sdr->chan[c].tx_frequency < tx_low_frequency)
|
||||
tx_low_frequency = sdr->chan[c].tx_frequency;
|
||||
if (sdr->chan[c].tx_frequency > tx_high_frequency)
|
||||
tx_high_frequency = sdr->chan[c].tx_frequency;
|
||||
}
|
||||
if (sdr->paging_channel) {
|
||||
if (sdr->chan[sdr->paging_channel].tx_frequency < tx_low_frequency)
|
||||
tx_low_frequency = sdr->chan[sdr->paging_channel].tx_frequency;
|
||||
if (sdr->chan[sdr->paging_channel].tx_frequency > tx_high_frequency)
|
||||
tx_high_frequency = sdr->chan[sdr->paging_channel].tx_frequency;
|
||||
}
|
||||
/* range of TX */
|
||||
double range = tx_high_frequency - tx_low_frequency + bandwidth;
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Total bandwidth for all TX Frequencies: %.0f Hz\n", range);
|
||||
if (range > samplerate * USABLE_BANDWIDTH) {
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "*******************************************************************************\n");
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "The required bandwidth of %.0f Hz exceeds %.0f%% of the sample rate.\n", range, USABLE_BANDWIDTH * 100.0);
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "Please increase samplerate!\n");
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "*******************************************************************************\n");
|
||||
goto error;
|
||||
}
|
||||
tx_center_frequency = (tx_high_frequency + tx_low_frequency) / 2.0;
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Using center frequency: TX %.6f MHz\n", tx_center_frequency / 1e6);
|
||||
/* set offsets to center frequency */
|
||||
for (c = 0; c < channels; c++) {
|
||||
double tx_offset;
|
||||
tx_offset = sdr->chan[c].tx_frequency - tx_center_frequency;
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Frequency #%d: TX offset: %.6f MHz\n", c, tx_offset / 1e6);
|
||||
rc = fm_mod_init(&sdr->chan[c].mod, samplerate, tx_offset, sdr->amplitude);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
}
|
||||
if (sdr->paging_channel) {
|
||||
double tx_offset;
|
||||
tx_offset = sdr->chan[sdr->paging_channel].tx_frequency - tx_center_frequency;
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Paging Frequency: TX offset: %.6f MHz\n", tx_offset / 1e6);
|
||||
rc = fm_mod_init(&sdr->chan[sdr->paging_channel].mod, samplerate, tx_offset, sdr->amplitude);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
}
|
||||
/* show gain */
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Using gain: TX %.1f dB\n", sdr_config->tx_gain);
|
||||
/* open wave */
|
||||
if (sdr_config->write_iq_tx_wave) {
|
||||
rc = wave_create_record(&sdr->wave_tx_rec, sdr_config->write_iq_tx_wave, samplerate, 2, 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (sdr_config->read_iq_tx_wave) {
|
||||
rc = wave_create_playback(&sdr->wave_tx_play, sdr_config->read_iq_tx_wave, samplerate, 2, 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frequency) {
|
||||
for (c = 0; c < channels; c++) {
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Frequency #%d: RX = %.6f MHz\n", c, rx_frequency[c] / 1e6);
|
||||
sdr->chan[c].rx_frequency = rx_frequency[c];
|
||||
}
|
||||
|
||||
/* calculate required bandwidth (IQ rate) */
|
||||
double rx_low_frequency = sdr->chan[0].rx_frequency, rx_high_frequency = sdr->chan[0].rx_frequency;
|
||||
for (c = 1; c < channels; c++) {
|
||||
if (sdr->chan[c].rx_frequency < rx_low_frequency)
|
||||
rx_low_frequency = sdr->chan[c].rx_frequency;
|
||||
if (sdr->chan[c].rx_frequency > rx_high_frequency)
|
||||
rx_high_frequency = sdr->chan[c].rx_frequency;
|
||||
}
|
||||
/* range of RX */
|
||||
double range = rx_high_frequency - rx_low_frequency + bandwidth;
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Total bandwidth for all RX Frequencies: %.0f Hz\n", range);
|
||||
if (range > samplerate * USABLE_BANDWIDTH) {
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "*******************************************************************************\n");
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "The required bandwidth of %.0f Hz exceeds %.0f%% of the sample rate.\n", range, USABLE_BANDWIDTH * 100.0);
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "Please increase samplerate!\n");
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "*******************************************************************************\n");
|
||||
goto error;
|
||||
}
|
||||
rx_center_frequency = (rx_high_frequency + rx_low_frequency) / 2.0;
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Using center frequency: RX %.6f MHz\n", rx_center_frequency / 1e6);
|
||||
/* set offsets to center frequency */
|
||||
for (c = 0; c < channels; c++) {
|
||||
double rx_offset;
|
||||
rx_offset = sdr->chan[c].rx_frequency - rx_center_frequency;
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Frequency #%d: RX offset: %.6f MHz\n", c, rx_offset / 1e6);
|
||||
rc = fm_demod_init(&sdr->chan[c].demod, samplerate, rx_offset, bandwidth);
|
||||
if (rc < 0)
|
||||
goto error;
|
||||
}
|
||||
/* show gain */
|
||||
PDEBUG(DSDR, DEBUG_INFO, "Using gain: RX %.1f dB\n", sdr_config->rx_gain);
|
||||
/* open wave */
|
||||
if (sdr_config->write_iq_rx_wave) {
|
||||
rc = wave_create_record(&sdr->wave_rx_rec, sdr_config->write_iq_rx_wave, samplerate, 2, 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create WAVE recoding instance!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
if (sdr_config->read_iq_rx_wave) {
|
||||
rc = wave_create_playback(&sdr->wave_rx_play, sdr_config->read_iq_rx_wave, samplerate, 2, 1.0);
|
||||
if (rc < 0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create WAVE playback instance!\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
/* init measurements display */
|
||||
for (c = 0; c < channels; c++) {
|
||||
sender_t *sender = get_sender_by_empfangsfrequenz(sdr->chan[c].rx_frequency);
|
||||
if (!sender)
|
||||
continue;
|
||||
sdr->chan[c].dmp_rf_level = display_measurements_add(sender, "RF Level", "%.1f dB", DISPLAY_MEAS_AVG, DISPLAY_MEAS_LEFT, -96.0, 0.0, -INFINITY);
|
||||
sdr->chan[c].dmp_freq_offset = display_measurements_add(sender, "Freq. Offset", "%+.2f KHz", DISPLAY_MEAS_AVG, DISPLAY_MEAS_CENTER, -max_deviation / 1000.0 * 2.0, max_deviation / 1000.0 * 2.0, 0.0);
|
||||
sdr->chan[c].dmp_deviation = display_measurements_add(sender, "Deviation", "%.2f KHz", DISPLAY_MEAS_PEAK2PEAK, DISPLAY_MEAS_LEFT, 0.0, max_deviation / 1000.0 * 1.5, max_deviation / 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (sdr_config->swap_links) {
|
||||
double temp;
|
||||
PDEBUG(DSDR, DEBUG_NOTICE, "Sapping RX and TX frequencies!\n");
|
||||
temp = rx_center_frequency;
|
||||
rx_center_frequency = tx_center_frequency;
|
||||
tx_center_frequency = temp;
|
||||
}
|
||||
|
||||
display_iq_init(samplerate);
|
||||
display_spectrum_init(samplerate, rx_center_frequency);
|
||||
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd) {
|
||||
rc = uhd_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, tx_center_frequency, rx_center_frequency, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth, sdr_config->uhd_tx_timestamps);
|
||||
if (rc)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy) {
|
||||
rc = soapy_open(sdr_config->channel, sdr_config->device_args, sdr_config->stream_args, sdr_config->tune_args, sdr_config->tx_antenna, sdr_config->rx_antenna, tx_center_frequency, rx_center_frequency, sdr_config->samplerate, sdr_config->tx_gain, sdr_config->rx_gain, sdr_config->bandwidth);
|
||||
if (rc)
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
|
||||
return sdr;
|
||||
|
||||
error:
|
||||
sdr_close(sdr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *sdr_write_child(void *arg)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)arg;
|
||||
int num;
|
||||
int fill, out;
|
||||
int s, ss, o;
|
||||
|
||||
while (sdr->thread_write.running) {
|
||||
/* write to SDR */
|
||||
fill = (sdr->thread_write.in - sdr->thread_write.out + sdr->thread_write.buffer_size) % sdr->thread_write.buffer_size;
|
||||
num = fill / 2;
|
||||
if (num) {
|
||||
#ifdef DEBUG_BUFFER
|
||||
printf("Thread found %d samples in write buffer and forwards them to SDR.\n", num);
|
||||
#endif
|
||||
out = sdr->thread_write.out;
|
||||
for (s = 0, ss = 0; s < num; s++) {
|
||||
for (o = 0; o < sdr->oversample; o++) {
|
||||
sdr->thread_write.buffer2[ss++] = sdr->thread_write.buffer[out];
|
||||
sdr->thread_write.buffer2[ss++] = sdr->thread_write.buffer[out + 1];
|
||||
}
|
||||
out = (out + 2) % sdr->thread_write.buffer_size;
|
||||
}
|
||||
sdr->thread_write.out = out;
|
||||
#ifndef DISABLE_FILTER
|
||||
/* filter spectrum */
|
||||
if (sdr->oversample > 1) {
|
||||
iir_process_baseband(&sdr->thread_write.lp[0], sdr->thread_write.buffer2, num * sdr->oversample);
|
||||
iir_process_baseband(&sdr->thread_write.lp[1], sdr->thread_write.buffer2 + 1, num * sdr->oversample);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
uhd_send(sdr->thread_write.buffer2, num * sdr->oversample);
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
soapy_send(sdr->thread_write.buffer2, num * sdr->oversample);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* delay some time */
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Thread received exit!\n");
|
||||
sdr->thread_write.exit = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *sdr_read_child(void *arg)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)arg;
|
||||
int num, count = 0;
|
||||
int space, in;
|
||||
int s, ss;
|
||||
|
||||
while (sdr->thread_read.running) {
|
||||
/* read from SDR */
|
||||
space = (sdr->thread_read.out - sdr->thread_read.in - 2 + sdr->thread_read.buffer_size) % sdr->thread_read.buffer_size;
|
||||
num = space / 2;
|
||||
if (num) {
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
count = uhd_receive(sdr->thread_read.buffer2, num);
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
count = soapy_receive(sdr->thread_read.buffer2, num);
|
||||
#endif
|
||||
if (count > 0) {
|
||||
#ifdef DEBUG_BUFFER
|
||||
printf("Thread read %d samples from SDR and writes them to read buffer.\n", count);
|
||||
#endif
|
||||
#ifndef DISABLE_FILTER
|
||||
/* filter spectrum */
|
||||
if (sdr->oversample > 1) {
|
||||
iir_process_baseband(&sdr->thread_read.lp[0], sdr->thread_read.buffer2, count);
|
||||
iir_process_baseband(&sdr->thread_read.lp[1], sdr->thread_read.buffer2 + 1, count);
|
||||
}
|
||||
#endif
|
||||
in = sdr->thread_read.in;
|
||||
for (s = 0, ss = 0; s < count; s++) {
|
||||
sdr->thread_read.buffer[in++] = sdr->thread_read.buffer2[ss++];
|
||||
sdr->thread_read.buffer[in++] = sdr->thread_read.buffer2[ss++];
|
||||
in %= sdr->thread_read.buffer_size;
|
||||
}
|
||||
sdr->thread_read.in = in;
|
||||
}
|
||||
}
|
||||
|
||||
/* delay some time */
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Thread received exit!\n");
|
||||
sdr->thread_read.exit = 1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* start streaming */
|
||||
int sdr_start(void *inst)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)inst;
|
||||
int rc = -EINVAL;
|
||||
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
rc = uhd_start();
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
rc = soapy_start();
|
||||
#endif
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
if (sdr->threads) {
|
||||
int rc;
|
||||
pthread_t tid;
|
||||
char tname[64];
|
||||
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Create threads!\n");
|
||||
sdr->thread_write.running = 1;
|
||||
sdr->thread_write.exit = 0;
|
||||
rc = pthread_create(&tid, NULL, sdr_write_child, inst);
|
||||
if (rc < 0) {
|
||||
sdr->thread_write.running = 0;
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create thread!\n");
|
||||
return rc;
|
||||
}
|
||||
pthread_getname_np(tid, tname, sizeof(tname));
|
||||
strncat(tname, "-sdr_tx", sizeof(tname));
|
||||
tname[sizeof(tname) - 1] = '\0';
|
||||
pthread_setname_np(tid, tname);
|
||||
sdr->thread_read.running = 1;
|
||||
sdr->thread_read.exit = 0;
|
||||
rc = pthread_create(&tid, NULL, sdr_read_child, inst);
|
||||
if (rc < 0) {
|
||||
sdr->thread_read.running = 0;
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Failed to create thread!\n");
|
||||
return rc;
|
||||
}
|
||||
pthread_getname_np(tid, tname, sizeof(tname));
|
||||
strncat(tname, "-sdr_rx", sizeof(tname));
|
||||
tname[sizeof(tname) - 1] = '\0';
|
||||
pthread_setname_np(tid, tname);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sdr_close(void *inst)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)inst;
|
||||
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Close SDR device\n");
|
||||
|
||||
if (sdr->threads) {
|
||||
if (sdr->thread_write.running) {
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Thread sending exit!\n");
|
||||
sdr->thread_write.running = 0;
|
||||
while (sdr->thread_write.exit == 0)
|
||||
usleep(1000);
|
||||
}
|
||||
if (sdr->thread_read.running) {
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "Thread sending exit!\n");
|
||||
sdr->thread_read.running = 0;
|
||||
while (sdr->thread_read.exit == 0)
|
||||
usleep(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (sdr->thread_read.buffer)
|
||||
free((void *)sdr->thread_read.buffer);
|
||||
if (sdr->thread_read.buffer2)
|
||||
free((void *)sdr->thread_read.buffer2);
|
||||
if (sdr->thread_write.buffer)
|
||||
free((void *)sdr->thread_write.buffer);
|
||||
if (sdr->thread_write.buffer2)
|
||||
free((void *)sdr->thread_write.buffer2);
|
||||
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
uhd_close();
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
soapy_close();
|
||||
#endif
|
||||
|
||||
if (sdr) {
|
||||
free(sdr->modbuff);
|
||||
free(sdr->modbuff_I);
|
||||
free(sdr->modbuff_Q);
|
||||
free(sdr->wavespl0);
|
||||
free(sdr->wavespl1);
|
||||
wave_destroy_record(&sdr->wave_rx_rec);
|
||||
wave_destroy_record(&sdr->wave_tx_rec);
|
||||
wave_destroy_playback(&sdr->wave_rx_play);
|
||||
wave_destroy_playback(&sdr->wave_tx_play);
|
||||
if (sdr->chan) {
|
||||
int c;
|
||||
|
||||
for (c = 0; c < sdr->channels; c++) {
|
||||
fm_mod_exit(&sdr->chan[c].mod);
|
||||
fm_demod_exit(&sdr->chan[c].demod);
|
||||
}
|
||||
if (sdr->paging_channel)
|
||||
fm_mod_exit(&sdr->chan[sdr->paging_channel].mod);
|
||||
free(sdr->chan);
|
||||
}
|
||||
free(sdr);
|
||||
sdr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int sdr_write(void *inst, sample_t **samples, uint8_t **power, int num, enum paging_signal __attribute__((unused)) *paging_signal, int *on, int channels)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)inst;
|
||||
float *buff = NULL;
|
||||
int c, s, ss;
|
||||
int sent = 0;
|
||||
|
||||
if (num > sdr->latspl) {
|
||||
fprintf(stderr, "exceeding maximum size given by sdr_latspl, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
if (channels != sdr->channels && channels != 0) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Invalid number of channels, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* process all channels */
|
||||
if (channels) {
|
||||
buff = sdr->modbuff;
|
||||
memset(buff, 0, sizeof(*buff) * num * 2);
|
||||
for (c = 0; c < channels; c++) {
|
||||
/* switch to paging channel, if requested */
|
||||
if (on[c] && sdr->paging_channel)
|
||||
fm_modulate_complex(&sdr->chan[sdr->paging_channel].mod, samples[c], power[c], num, buff);
|
||||
else
|
||||
fm_modulate_complex(&sdr->chan[c].mod, samples[c], power[c], num, buff);
|
||||
}
|
||||
} else {
|
||||
buff = (float *)samples;
|
||||
}
|
||||
|
||||
if (sdr->wave_tx_rec.fp) {
|
||||
sample_t *spl_list[2] = { sdr->wavespl0, sdr->wavespl1 };
|
||||
for (s = 0, ss = 0; s < num; s++) {
|
||||
spl_list[0][s] = buff[ss++];
|
||||
spl_list[1][s] = buff[ss++];
|
||||
}
|
||||
wave_write(&sdr->wave_tx_rec, spl_list, num);
|
||||
}
|
||||
if (sdr->wave_tx_play.fp) {
|
||||
sample_t *spl_list[2] = { sdr->wavespl0, sdr->wavespl1 };
|
||||
wave_read(&sdr->wave_tx_play, spl_list, num);
|
||||
for (s = 0, ss = 0; s < num; s++) {
|
||||
buff[ss++] = spl_list[0][s];
|
||||
buff[ss++] = spl_list[1][s];
|
||||
}
|
||||
}
|
||||
|
||||
if (sdr->threads) {
|
||||
/* store data towards SDR in ring buffer */
|
||||
int fill, space, in;
|
||||
|
||||
fill = (sdr->thread_write.in - sdr->thread_write.out + sdr->thread_write.buffer_size) % sdr->thread_write.buffer_size;
|
||||
space = (sdr->thread_write.out - sdr->thread_write.in - 2 + sdr->thread_write.buffer_size) % sdr->thread_write.buffer_size;
|
||||
|
||||
/* debug fill level */
|
||||
if (fill > sdr->thread_write.max_fill)
|
||||
sdr->thread_write.max_fill = fill;
|
||||
if (sdr->thread_write.max_fill_timer == 0.0)
|
||||
sdr->thread_write.max_fill_timer = get_time();
|
||||
if (get_time() - sdr->thread_write.max_fill_timer > 1.0) {
|
||||
double delay;
|
||||
delay = (double)sdr->thread_write.max_fill / 2.0 / (double)sdr->samplerate;
|
||||
sdr->thread_write.max_fill = 0;
|
||||
sdr->thread_write.max_fill_timer += 1.0;
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "write delay = %.3f ms\n", delay * 1000.0);
|
||||
}
|
||||
|
||||
if (space < num * 2) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "Write SDR buffer overflow!\n");
|
||||
num = space / 2;
|
||||
}
|
||||
#ifdef DEBUG_BUFFER
|
||||
printf("Writing %d samples to write buffer.\n", num);
|
||||
#endif
|
||||
in = sdr->thread_write.in;
|
||||
for (s = 0, ss = 0; s < num; s++) {
|
||||
sdr->thread_write.buffer[in++] = buff[ss++];
|
||||
sdr->thread_write.buffer[in++] = buff[ss++];
|
||||
in %= sdr->thread_write.buffer_size;
|
||||
}
|
||||
sdr->thread_write.in = in;
|
||||
sent = num;
|
||||
} else {
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
sent = uhd_send(buff, num);
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
sent = soapy_send(buff, num);
|
||||
#endif
|
||||
if (sent < 0)
|
||||
return sent;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
int sdr_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)inst;
|
||||
float *buff = NULL;
|
||||
int count = 0;
|
||||
int c, s, ss;
|
||||
|
||||
if (num > sdr->latspl) {
|
||||
fprintf(stderr, "exceeding maximum size given by sdr_latspl, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (channels) {
|
||||
buff = sdr->modbuff;
|
||||
} else {
|
||||
buff = (float *)samples;
|
||||
}
|
||||
|
||||
if (sdr->threads) {
|
||||
/* load data from SDR out of ring buffer */
|
||||
int fill, out;
|
||||
|
||||
fill = (sdr->thread_read.in - sdr->thread_read.out + sdr->thread_read.buffer_size) % sdr->thread_read.buffer_size;
|
||||
|
||||
/* debug fill level */
|
||||
if (fill > sdr->thread_read.max_fill)
|
||||
sdr->thread_read.max_fill = fill;
|
||||
if (sdr->thread_read.max_fill_timer == 0.0)
|
||||
sdr->thread_read.max_fill_timer = get_time();
|
||||
if (get_time() - sdr->thread_read.max_fill_timer > 1.0) {
|
||||
double delay;
|
||||
delay = (double)sdr->thread_read.max_fill / 2.0 / (double)sdr_config->samplerate;
|
||||
sdr->thread_read.max_fill = 0;
|
||||
sdr->thread_read.max_fill_timer += 1.0;
|
||||
PDEBUG(DSDR, DEBUG_DEBUG, "read delay = %.3f ms\n", delay * 1000.0);
|
||||
}
|
||||
|
||||
if (fill / 2 / sdr->oversample < num)
|
||||
num = fill / 2 / sdr->oversample;
|
||||
#ifdef DEBUG_BUFFER
|
||||
printf("Reading %d samples from read buffer.\n", num);
|
||||
#endif
|
||||
out = sdr->thread_read.out;
|
||||
for (s = 0, ss = 0; s < num; s++) {
|
||||
buff[ss++] = sdr->thread_read.buffer[out];
|
||||
buff[ss++] = sdr->thread_read.buffer[out + 1];
|
||||
out = (out + 2 * sdr->oversample) % sdr->thread_read.buffer_size;
|
||||
}
|
||||
sdr->thread_read.out = out;
|
||||
count = num;
|
||||
} else {
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
count = uhd_receive(buff, num);
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
count = soapy_receive(buff, num);
|
||||
#endif
|
||||
if (count <= 0)
|
||||
return count;
|
||||
}
|
||||
|
||||
if (sdr_rx_overflow) {
|
||||
PDEBUG(DSDR, DEBUG_ERROR, "SDR RX overflow!\n");
|
||||
sdr_rx_overflow = 0;
|
||||
}
|
||||
|
||||
if (sdr->wave_rx_rec.fp) {
|
||||
sample_t *spl_list[2] = { sdr->wavespl0, sdr->wavespl1 };
|
||||
for (s = 0, ss = 0; s < count; s++) {
|
||||
spl_list[0][s] = buff[ss++];
|
||||
spl_list[1][s] = buff[ss++];
|
||||
}
|
||||
wave_write(&sdr->wave_rx_rec, spl_list, count);
|
||||
}
|
||||
if (sdr->wave_rx_play.fp) {
|
||||
sample_t *spl_list[2] = { sdr->wavespl0, sdr->wavespl1 };
|
||||
wave_read(&sdr->wave_rx_play, spl_list, count);
|
||||
for (s = 0, ss = 0; s < count; s++) {
|
||||
buff[ss++] = spl_list[0][s];
|
||||
buff[ss++] = spl_list[1][s];
|
||||
}
|
||||
}
|
||||
display_iq(buff, count);
|
||||
display_spectrum(buff, count);
|
||||
|
||||
if (channels) {
|
||||
for (c = 0; c < channels; c++) {
|
||||
fm_demodulate_complex(&sdr->chan[c].demod, samples[c], count, buff, sdr->modbuff_I, sdr->modbuff_Q);
|
||||
sender_t *sender = get_sender_by_empfangsfrequenz(sdr->chan[c].rx_frequency);
|
||||
if (!sender || !count)
|
||||
continue;
|
||||
double min, max, avg;
|
||||
avg = 0.0;
|
||||
for (s = 0; s < count; s++) {
|
||||
/* average the square length of vector */
|
||||
avg += sdr->modbuff_I[s] * sdr->modbuff_I[s] + sdr->modbuff_Q[s] * sdr->modbuff_Q[s];
|
||||
}
|
||||
avg = sqrt(avg /(double)count); /* RMS */
|
||||
avg = log10(avg) * 20;
|
||||
display_measurements_update(sdr->chan[c].dmp_rf_level, avg, 0.0);
|
||||
rf_level_db[c] = avg;
|
||||
min = 0.0;
|
||||
max = 0.0;
|
||||
avg = 0.0;
|
||||
for (s = 0; s < count; s++) {
|
||||
avg += samples[c][s];
|
||||
if (s == 0 || samples[c][s] > max)
|
||||
max = samples[c][s];
|
||||
if (s == 0 || samples[c][s] < min)
|
||||
min = samples[c][s];
|
||||
}
|
||||
avg /= (double)count;
|
||||
display_measurements_update(sdr->chan[c].dmp_freq_offset, avg / 1000.0, 0.0);
|
||||
/* use half min and max, because we want the deviation above/below (+-) center frequency. */
|
||||
display_measurements_update(sdr->chan[c].dmp_deviation, min / 2.0 / 1000.0, max / 2.0 / 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* how much do we need to send (in audio sample duration) to get the target delay (latspl) */
|
||||
int sdr_get_tosend(void *inst, int latspl)
|
||||
{
|
||||
sdr_t *sdr = (sdr_t *)inst;
|
||||
int count = 0;
|
||||
|
||||
#ifdef HAVE_UHD
|
||||
if (sdr_config->uhd)
|
||||
count = uhd_get_tosend(latspl * sdr->oversample);
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
if (sdr_config->soapy)
|
||||
count = soapy_get_tosend(latspl * sdr->oversample);
|
||||
#endif
|
||||
if (count < 0)
|
||||
return count;
|
||||
count /= sdr->oversample;
|
||||
|
||||
if (sdr->threads) {
|
||||
/* substract what we have in write buffer, because this is not jent sent to the SDR */
|
||||
int fill;
|
||||
|
||||
fill = (sdr->thread_write.in - sdr->thread_write.out + sdr->thread_write.buffer_size) % sdr->thread_write.buffer_size;
|
||||
count -= fill / 2;
|
||||
if (count < 0)
|
||||
count = 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
8
src/libsdr/sdr.h
Normal file
8
src/libsdr/sdr.h
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
int sdr_start(void *inst);
|
||||
void *sdr_open(const char *audiodev, double *tx_frequency, double *rx_frequency, int channels, double paging_frequency, int samplerate, int latspl, double bandwidth, double sample_deviation);
|
||||
void sdr_close(void *inst);
|
||||
int sdr_write(void *inst, sample_t **samples, uint8_t **power, int num, enum paging_signal *paging_signal, int *on, int channels);
|
||||
int sdr_read(void *inst, sample_t **samples, int num, int channels, double *rf_level_db);
|
||||
int sdr_get_tosend(void *inst, int latspl);
|
||||
|
262
src/libsdr/sdr_config.c
Normal file
262
src/libsdr/sdr_config.c
Normal file
@@ -0,0 +1,262 @@
|
||||
/* Config for SDR
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
enum paging_signal;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <getopt.h>
|
||||
#include "../common/sample.h"
|
||||
#include "sdr.h"
|
||||
#include "sdr_config.h"
|
||||
|
||||
static int got_init = 0;
|
||||
extern int use_sdr;
|
||||
sdr_config_t *sdr_config = NULL;
|
||||
|
||||
void sdr_config_init(void)
|
||||
{
|
||||
sdr_config = calloc(1, sizeof(*sdr_config));
|
||||
memset(sdr_config, 0, sizeof(*sdr_config));
|
||||
sdr_config->device_args = "";
|
||||
sdr_config->stream_args = "";
|
||||
sdr_config->tune_args = "";
|
||||
|
||||
got_init = 1;
|
||||
}
|
||||
|
||||
void sdr_config_print_help(void)
|
||||
{
|
||||
printf("\nSDR options:\n");
|
||||
/* - - */
|
||||
#ifdef HAVE_UHD
|
||||
printf(" --sdr-uhd\n");
|
||||
printf(" Force UHD driver\n");
|
||||
#endif
|
||||
#ifdef HAVE_SOAPY
|
||||
printf(" --sdr-soapy\n");
|
||||
printf(" Force SoapySDR driver\n");
|
||||
#endif
|
||||
printf(" --sdr-channel <channel #>\n");
|
||||
printf(" Give channel number for multi channel SDR device (default = %d)\n", sdr_config->channel);
|
||||
printf(" --sdr-device-args <args>\n");
|
||||
printf(" --sdr-stream-args <args>\n");
|
||||
printf(" --sdr-tune-args <args>\n");
|
||||
printf(" Optional SDR device arguments, seperated by comma\n");
|
||||
printf(" e.g. --sdr-device-args <key>=<value>[,<key>=<value>[,...]]\n");
|
||||
printf(" --sdr-samplerate <samplerate>\n");
|
||||
printf(" Sample rate to use with SDR. By default it equals the regular sample\n");
|
||||
printf(" rate.\n");
|
||||
printf(" --sdr-bandwidth <bandwidth>\n");
|
||||
printf(" Give IF filter bandwidth to use. If not, sample rate is used.\n");
|
||||
printf(" --sdr-rx-antenna <name>\n");
|
||||
printf(" SDR device's RX antenna name, use 'list' to get a list\n");
|
||||
printf(" --sdr-tx-antenna <name>\n");
|
||||
printf(" SDR device's TX antenna name, use 'list' to get a list\n");
|
||||
printf(" --sdr-rx-gain <gain>\n");
|
||||
printf(" SDR device's RX gain in dB (default = %.1f)\n", sdr_config->rx_gain);
|
||||
printf(" --sdr-tx-gain <gain>\n");
|
||||
printf(" SDR device's TX gain in dB (default = %.1f)\n", sdr_config->tx_gain);
|
||||
printf(" --write-iq-rx-wave <file>\n");
|
||||
printf(" Write received IQ data to given wave file.\n");
|
||||
printf(" --write-iq-tx-wave <file>\n");
|
||||
printf(" Write transmitted IQ data to given wave file.\n");
|
||||
printf(" --read-iq-rx-wave <file>\n");
|
||||
printf(" Replace received IQ data by given wave file.\n");
|
||||
printf(" --read-iq-tx-wave <file>\n");
|
||||
printf(" Replace transmitted IQ data by given wave file.\n");
|
||||
printf(" --sdr-swap-links\n");
|
||||
printf(" Swap RX and TX frequencies for loopback tests over the air.\n");
|
||||
#ifdef HAVE_UHD
|
||||
printf(" --sdr-uhd-tx-timestamps\n");
|
||||
printf(" Use TX timestamps on UHD device. (May not work with some devices!)\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void sdr_config_print_hotkeys(void)
|
||||
{
|
||||
printf("Press 'q' key to toggle display of RX I/Q vector.\n");
|
||||
printf("Press 's' key to toggle display of RX spectrum.\n");
|
||||
}
|
||||
|
||||
#define OPT_SDR_UHD 1500
|
||||
#define OPT_SDR_SOAPY 1501
|
||||
#define OPT_SDR_CHANNEL 1502
|
||||
#define OPT_SDR_DEVICE_ARGS 1503
|
||||
#define OPT_SDR_STREAM_ARGS 1504
|
||||
#define OPT_SDR_TUNE_ARGS 1505
|
||||
#define OPT_SDR_RX_ANTENNA 1506
|
||||
#define OPT_SDR_TX_ANTENNA 1507
|
||||
#define OPT_SDR_RX_GAIN 1508
|
||||
#define OPT_SDR_TX_GAIN 1509
|
||||
#define OPT_SDR_SAMPLERATE 1510
|
||||
#define OPT_SDR_BANDWIDTH 1511
|
||||
#define OPT_WRITE_IQ_RX_WAVE 1512
|
||||
#define OPT_WRITE_IQ_TX_WAVE 1513
|
||||
#define OPT_READ_IQ_RX_WAVE 1514
|
||||
#define OPT_READ_IQ_TX_WAVE 1515
|
||||
#define OPT_SDR_SWAP_LINKS 1516
|
||||
#define OPT_SDR_UHD_TX_TS 1517
|
||||
|
||||
struct option sdr_config_long_options[] = {
|
||||
{"sdr-uhd", 0, 0, OPT_SDR_UHD},
|
||||
{"sdr-soapy", 0, 0, OPT_SDR_SOAPY},
|
||||
{"sdr-channel", 1, 0, OPT_SDR_CHANNEL},
|
||||
{"sdr-device-args", 1, 0, OPT_SDR_DEVICE_ARGS},
|
||||
{"sdr-stream-args", 1, 0, OPT_SDR_STREAM_ARGS},
|
||||
{"sdr-tune-args", 1, 0, OPT_SDR_TUNE_ARGS},
|
||||
{"sdr-samplerate", 1, 0, OPT_SDR_SAMPLERATE},
|
||||
{"sdr-bandwidth", 1, 0, OPT_SDR_BANDWIDTH},
|
||||
{"sdr-rx-antenna", 1, 0, OPT_SDR_RX_ANTENNA},
|
||||
{"sdr-tx-antenna", 1, 0, OPT_SDR_TX_ANTENNA},
|
||||
{"sdr-rx-gain", 1, 0, OPT_SDR_RX_GAIN},
|
||||
{"sdr-tx-gain", 1, 0, OPT_SDR_TX_GAIN},
|
||||
{"write-iq-rx-wave", 1, 0, OPT_WRITE_IQ_RX_WAVE},
|
||||
{"write-iq-tx-wave", 1, 0, OPT_WRITE_IQ_TX_WAVE},
|
||||
{"read-iq-rx-wave", 1, 0, OPT_READ_IQ_RX_WAVE},
|
||||
{"read-iq-tx-wave", 1, 0, OPT_READ_IQ_TX_WAVE},
|
||||
{"sdr-swap-links", 0, 0, OPT_SDR_SWAP_LINKS},
|
||||
{"sdr-uhd-tx-timestamps", 0, 0, OPT_SDR_UHD_TX_TS},
|
||||
{0, 0, 0, 0}
|
||||
};
|
||||
|
||||
const char *sdr_config_optstring = "";
|
||||
|
||||
int sdr_config_opt_switch(int c, int *skip_args)
|
||||
{
|
||||
switch (c) {
|
||||
case OPT_SDR_UHD:
|
||||
#ifdef HAVE_UHD
|
||||
sdr_config->uhd = 1;
|
||||
use_sdr = 1;
|
||||
#else
|
||||
fprintf(stderr, "UHD SDR support not compiled in!\n");
|
||||
exit(0);
|
||||
#endif
|
||||
*skip_args += 1;
|
||||
break;
|
||||
case OPT_SDR_SOAPY:
|
||||
#ifdef HAVE_SOAPY
|
||||
sdr_config->soapy = 1;
|
||||
use_sdr = 1;
|
||||
#else
|
||||
fprintf(stderr, "SoapySDR support not compiled in!\n");
|
||||
exit(0);
|
||||
#endif
|
||||
*skip_args += 1;
|
||||
break;
|
||||
case OPT_SDR_CHANNEL:
|
||||
sdr_config->channel = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_DEVICE_ARGS:
|
||||
sdr_config->device_args = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_STREAM_ARGS:
|
||||
sdr_config->stream_args = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_TUNE_ARGS:
|
||||
sdr_config->tune_args = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_SAMPLERATE:
|
||||
sdr_config->samplerate = atoi(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_BANDWIDTH:
|
||||
sdr_config->bandwidth = atof(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_RX_ANTENNA:
|
||||
sdr_config->rx_antenna = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_TX_ANTENNA:
|
||||
sdr_config->tx_antenna = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_RX_GAIN:
|
||||
sdr_config->rx_gain = atof(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_TX_GAIN:
|
||||
sdr_config->tx_gain = atof(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_WRITE_IQ_RX_WAVE:
|
||||
sdr_config->write_iq_rx_wave = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_WRITE_IQ_TX_WAVE:
|
||||
sdr_config->write_iq_tx_wave = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_READ_IQ_RX_WAVE:
|
||||
sdr_config->read_iq_rx_wave = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_READ_IQ_TX_WAVE:
|
||||
sdr_config->read_iq_tx_wave = strdup(optarg);
|
||||
*skip_args += 2;
|
||||
break;
|
||||
case OPT_SDR_SWAP_LINKS:
|
||||
sdr_config->swap_links = 1;
|
||||
*skip_args += 1;
|
||||
break;
|
||||
case OPT_SDR_UHD_TX_TS:
|
||||
sdr_config->uhd_tx_timestamps = 1;
|
||||
*skip_args += 1;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sdr_configure(int samplerate)
|
||||
{
|
||||
if (!got_init) {
|
||||
fprintf(stderr, "sdr_config_init was not called, please fix!\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
/* no sdr selected -> return 0 */
|
||||
if (!sdr_config->uhd && !sdr_config->soapy)
|
||||
return 0;
|
||||
|
||||
if ((sdr_config->uhd == 1 && sdr_config->soapy == 1)) {
|
||||
fprintf(stderr, "You must choose which one you want: --sdr-uhd or --sdr-soapy\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (sdr_config->samplerate == 0)
|
||||
sdr_config->samplerate = samplerate;
|
||||
if (sdr_config->bandwidth == 0.0)
|
||||
sdr_config->bandwidth = (double)sdr_config->samplerate;
|
||||
|
||||
/* sdr selected -> return 1 */
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
32
src/libsdr/sdr_config.h
Normal file
32
src/libsdr/sdr_config.h
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
typedef struct sdr_config {
|
||||
int uhd, /* select UHD API */
|
||||
soapy; /* select Soapy SDR API */
|
||||
int channel; /* channel number */
|
||||
const char *device_args, /* arguments */
|
||||
*stream_args,
|
||||
*tune_args;
|
||||
int samplerate; /* ADC/DAC sample rate */
|
||||
double bandwidth; /* IF bandwidth */
|
||||
double tx_gain, /* gain */
|
||||
rx_gain;
|
||||
const char *tx_antenna, /* list/override antennas */
|
||||
*rx_antenna;
|
||||
const char *write_iq_tx_wave; /* wave recording and playback */
|
||||
const char *write_iq_rx_wave;
|
||||
const char *read_iq_tx_wave;
|
||||
const char *read_iq_rx_wave;
|
||||
int swap_links; /* swap DL and UL frequency */
|
||||
int uhd_tx_timestamps; /* use UHD time stamps */
|
||||
} sdr_config_t;
|
||||
|
||||
extern sdr_config_t *sdr_config;
|
||||
|
||||
void sdr_config_init(void);
|
||||
void sdr_config_print_help(void);
|
||||
void sdr_config_print_hotkeys(void);
|
||||
extern struct option sdr_config_long_options[];
|
||||
extern const char *sdr_config_optstring;
|
||||
int sdr_config_opt_switch(int c, int *skip_args);
|
||||
int sdr_configure(int samplerate);
|
||||
|
468
src/libsdr/soapy.c
Normal file
468
src/libsdr/soapy.c
Normal file
@@ -0,0 +1,468 @@
|
||||
/* SoapySDR device access
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <SoapySDR/Device.h>
|
||||
#include <SoapySDR/Formats.h>
|
||||
#include "soapy.h"
|
||||
#include "../common/debug.h"
|
||||
|
||||
extern int sdr_rx_overflow;
|
||||
|
||||
static SoapySDRDevice *sdr = NULL;
|
||||
SoapySDRStream *rxStream = NULL;
|
||||
SoapySDRStream *txStream = NULL;
|
||||
static int tx_samps_per_buff, rx_samps_per_buff;
|
||||
static double samplerate;
|
||||
static uint64_t rx_count = 0;
|
||||
static uint64_t tx_count = 0;
|
||||
|
||||
static int parse_args(SoapySDRKwargs *args, const char *_args_string)
|
||||
{
|
||||
char *args_string = strdup(_args_string), *key, *val;
|
||||
|
||||
memset(args, 0, sizeof(*args));
|
||||
while (args_string && *args_string) {
|
||||
key = args_string;
|
||||
val = strchr(key, '=');
|
||||
if (!val) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Error parsing SDR args: No '=' after key\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
*val++ = '\0';
|
||||
args_string = strchr(val, ',');
|
||||
if (args_string)
|
||||
*args_string++ = '\0';
|
||||
PDEBUG(DSOAPY, DEBUG_DEBUG, "SDR device args: key='%s' value='%s'\n", key, val);
|
||||
SoapySDRKwargs_set(args, key, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int soapy_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, double tx_frequency, double rx_frequency, double rate, double tx_gain, double rx_gain, double bandwidth)
|
||||
{
|
||||
double got_frequency, got_rate, got_gain, got_bandwidth;
|
||||
const char *got_antenna;
|
||||
size_t num_channels;
|
||||
SoapySDRKwargs device_args;
|
||||
SoapySDRKwargs stream_args;
|
||||
SoapySDRKwargs tune_args;
|
||||
int rc;
|
||||
|
||||
samplerate = rate;
|
||||
|
||||
/* parsing ARGS */
|
||||
PDEBUG(DSOAPY, DEBUG_INFO, "Using device args \"%s\"\n", _device_args);
|
||||
rc = parse_args(&device_args, _device_args);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
PDEBUG(DSOAPY, DEBUG_INFO, "Using stream args \"%s\"\n", _stream_args);
|
||||
rc = parse_args(&stream_args, _stream_args);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
PDEBUG(DSOAPY, DEBUG_INFO, "Using tune args \"%s\"\n", _tune_args);
|
||||
rc = parse_args(&tune_args, _tune_args);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
/* create SoapySDR device */
|
||||
sdr = SoapySDRDevice_make(&device_args);
|
||||
if (!sdr) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to create SoapySDR\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (tx_frequency) {
|
||||
/* get number of channels and check if requested channel is in range */
|
||||
num_channels = SoapySDRDevice_getNumChannels(sdr, SOAPY_SDR_TX);
|
||||
PDEBUG(DSOAPY, DEBUG_DEBUG, "We have %d TX channel, selecting channel #%d\n", (int)num_channels, (int)channel);
|
||||
if (channel >= num_channels) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Requested channel #%d (capable of TX) does not exist. Please select channel %d..%d!\n", (int)channel, 0, (int)num_channels - 1);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* antenna */
|
||||
if (tx_antenna && tx_antenna[0]) {
|
||||
if (!strcasecmp(tx_antenna, "list")) {
|
||||
char **antennas;
|
||||
size_t antennas_length;
|
||||
int i;
|
||||
antennas = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_TX, channel, &antennas_length);
|
||||
if (!antennas) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to request list of TX antennas!\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
for (i = 0; i < (int)antennas_length; i++)
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "TX Antenna: '%s'\n", antennas[i]);
|
||||
got_antenna = SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_TX, channel);
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Default TX Antenna: '%s'\n", got_antenna);
|
||||
soapy_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SoapySDRDevice_setAntenna(sdr, SOAPY_SDR_TX, channel, tx_antenna) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX antenna to '%s'\n", tx_antenna);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
got_antenna = SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_TX, channel);
|
||||
if (!!strcasecmp(tx_antenna, got_antenna)) {
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Given TX antenna '%s' was accepted, but driver claims to use '%s'\n", tx_antenna, got_antenna);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* set rate */
|
||||
if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_TX, channel, rate) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX rate to %.0f Hz\n", rate);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what rate actually is */
|
||||
got_rate = SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_TX, channel);
|
||||
if (fabs(got_rate - rate) > 1.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given TX rate %.3f Hz is not supported, try %.3f Hz\n", rate, got_rate);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (tx_gain) {
|
||||
/* set gain */
|
||||
if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_TX, channel, tx_gain) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX gain to %.0f\n", tx_gain);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what gain actually is */
|
||||
got_gain = SoapySDRDevice_getGain(sdr, SOAPY_SDR_TX, channel);
|
||||
if (fabs(got_gain - tx_gain) > 0.001) {
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Given TX gain %.3f is not supported, we use %.3f\n", tx_gain, got_gain);
|
||||
tx_gain = got_gain;
|
||||
}
|
||||
}
|
||||
|
||||
/* set frequency */
|
||||
if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_TX, channel, tx_frequency, &tune_args) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX frequency to %.0f Hz\n", tx_frequency);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what frequency actually is */
|
||||
got_frequency = SoapySDRDevice_getFrequency(sdr, SOAPY_SDR_TX, channel);
|
||||
if (fabs(got_frequency - tx_frequency) > 100.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given TX frequency %.0f Hz is not supported, try %.0f Hz\n", tx_frequency, got_frequency);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set bandwidth */
|
||||
if (SoapySDRDevice_setBandwidth(sdr, SOAPY_SDR_TX, channel, bandwidth) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX bandwidth to %.0f Hz\n", bandwidth);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what bandwidth actually is */
|
||||
got_bandwidth = SoapySDRDevice_getBandwidth(sdr, SOAPY_SDR_TX, channel);
|
||||
if (fabs(got_bandwidth - bandwidth) > 100.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given TX bandwidth %.0f Hz is not supported, try %.0f Hz\n", bandwidth, got_bandwidth);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set up streamer */
|
||||
if (SoapySDRDevice_setupStream(sdr, &txStream, SOAPY_SDR_TX, SOAPY_SDR_CF32, &channel, 1, &stream_args) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set TX streamer args\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* get buffer sizes */
|
||||
tx_samps_per_buff = SoapySDRDevice_getStreamMTU(sdr, txStream);
|
||||
if (tx_samps_per_buff == 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to get TX streamer sample buffer\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frequency) {
|
||||
/* get number of channels and check if requested channel is in range */
|
||||
num_channels = SoapySDRDevice_getNumChannels(sdr, SOAPY_SDR_RX);
|
||||
PDEBUG(DSOAPY, DEBUG_DEBUG, "We have %d RX channel, selecting channel #%d\n", (int)num_channels, (int)channel);
|
||||
if (channel >= num_channels) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Requested channel #%d (capable of RX) does not exist. Please select channel %d..%d!\n", (int)channel, 0, (int)num_channels - 1);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* antenna */
|
||||
if (rx_antenna && rx_antenna[0]) {
|
||||
if (!strcasecmp(rx_antenna, "list")) {
|
||||
char **antennas;
|
||||
size_t antennas_length;
|
||||
int i;
|
||||
antennas = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_RX, channel, &antennas_length);
|
||||
if (!antennas) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to request list of RX antennas!\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
for (i = 0; i < (int)antennas_length; i++)
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "RX Antenna: '%s'\n", antennas[i]);
|
||||
got_antenna = SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_RX, channel);
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Default RX Antenna: '%s'\n", got_antenna);
|
||||
soapy_close();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SoapySDRDevice_setAntenna(sdr, SOAPY_SDR_RX, channel, rx_antenna) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX antenna to '%s'\n", rx_antenna);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
got_antenna = SoapySDRDevice_getAntenna(sdr, SOAPY_SDR_RX, channel);
|
||||
if (!!strcasecmp(rx_antenna, got_antenna)) {
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Given RX antenna '%s' was accepted, but driver claims to use '%s'\n", rx_antenna, got_antenna);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* set rate */
|
||||
if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, channel, rate) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX rate to %.0f Hz\n", rate);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what rate actually is */
|
||||
got_rate = SoapySDRDevice_getSampleRate(sdr, SOAPY_SDR_RX, channel);
|
||||
if (fabs(got_rate - rate) > 1.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given RX rate %.3f Hz is not supported, try %.3f Hz\n", rate, got_rate);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (rx_gain) {
|
||||
/* set gain */
|
||||
if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, channel, rx_gain) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX gain to %.0f\n", rx_gain);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what gain actually is */
|
||||
got_gain = SoapySDRDevice_getGain(sdr, SOAPY_SDR_RX, channel);
|
||||
if (fabs(got_gain - rx_gain) > 0.001) {
|
||||
PDEBUG(DSOAPY, DEBUG_NOTICE, "Given RX gain %.3f is not supported, we use %.3f\n", rx_gain, got_gain);
|
||||
rx_gain = got_gain;
|
||||
}
|
||||
}
|
||||
|
||||
/* set frequency */
|
||||
if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, channel, rx_frequency, &tune_args) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX frequency to %.0f Hz\n", rx_frequency);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what frequency actually is */
|
||||
got_frequency = SoapySDRDevice_getFrequency(sdr, SOAPY_SDR_RX, channel);
|
||||
if (fabs(got_frequency - rx_frequency) > 100.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given RX frequency %.0f Hz is not supported, try %.0f Hz\n", rx_frequency, got_frequency);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set bandwidth */
|
||||
if (SoapySDRDevice_setBandwidth(sdr, SOAPY_SDR_RX, channel, bandwidth) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX bandwidth to %.0f Hz\n", bandwidth);
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what bandwidth actually is */
|
||||
got_bandwidth = SoapySDRDevice_getBandwidth(sdr, SOAPY_SDR_RX, channel);
|
||||
if (fabs(got_bandwidth - bandwidth) > 100.0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Given RX bandwidth %.0f Hz is not supported, try %.0f Hz\n", bandwidth, got_bandwidth);
|
||||
soapy_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set up streamer */
|
||||
if (SoapySDRDevice_setupStream(sdr, &rxStream, SOAPY_SDR_RX, SOAPY_SDR_CF32, &channel, 1, &stream_args) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to set RX streamer args\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* get buffer sizes */
|
||||
rx_samps_per_buff = SoapySDRDevice_getStreamMTU(sdr, rxStream);
|
||||
if (rx_samps_per_buff == 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to get RX streamer sample buffer\n");
|
||||
soapy_close();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* start streaming */
|
||||
int soapy_start(void)
|
||||
{
|
||||
/* enable rx stream */
|
||||
if (SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to issue RX stream command\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* enable tx stream */
|
||||
if (SoapySDRDevice_activateStream(sdr, txStream, 0, 0, 0) != 0) {
|
||||
PDEBUG(DSOAPY, DEBUG_ERROR, "Failed to issue TX stream command\n");
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void soapy_close(void)
|
||||
{
|
||||
PDEBUG(DSOAPY, DEBUG_DEBUG, "Clean up SoapySDR\n");
|
||||
if (txStream) {
|
||||
SoapySDRDevice_deactivateStream(sdr, txStream, 0, 0);
|
||||
SoapySDRDevice_closeStream(sdr, txStream);
|
||||
txStream = NULL;
|
||||
}
|
||||
if (rxStream) {
|
||||
SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0);
|
||||
SoapySDRDevice_closeStream(sdr, rxStream);
|
||||
rxStream = NULL;
|
||||
}
|
||||
if (sdr) {
|
||||
SoapySDRDevice_unmake(sdr);
|
||||
sdr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int soapy_send(float *buff, int num)
|
||||
{
|
||||
const void *buffs_ptr[1];
|
||||
int chunk;
|
||||
int sent = 0, count;
|
||||
int flags = 0;
|
||||
|
||||
while (num) {
|
||||
chunk = num;
|
||||
if (chunk > tx_samps_per_buff)
|
||||
chunk = tx_samps_per_buff;
|
||||
/* create tx metadata */
|
||||
buffs_ptr[0] = buff;
|
||||
count = SoapySDRDevice_writeStream(sdr, txStream, buffs_ptr, chunk, &flags, 0, 1000000);
|
||||
if (count <= 0) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to write to TX streamer (error=%d)\n", count);
|
||||
break;
|
||||
}
|
||||
|
||||
sent += count;
|
||||
buff += count * 2;
|
||||
num -= count;
|
||||
}
|
||||
/* increment tx counter */
|
||||
tx_count += sent;
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
/* read what we got, return 0, if buffer is empty, otherwise return the number of samples */
|
||||
int soapy_receive(float *buff, int max)
|
||||
{
|
||||
void *buffs_ptr[1];
|
||||
int got = 0, count;
|
||||
long long timeNs;
|
||||
int flags = 0;
|
||||
|
||||
while (1) {
|
||||
if (max < rx_samps_per_buff) {
|
||||
/* no more space this time */
|
||||
sdr_rx_overflow = 1;
|
||||
break;
|
||||
}
|
||||
/* read RX stream */
|
||||
buffs_ptr[0] = buff;
|
||||
count = SoapySDRDevice_readStream(sdr, rxStream, buffs_ptr, rx_samps_per_buff, &flags, &timeNs, 0);
|
||||
if (count > 0) {
|
||||
/* commit received data to buffer */
|
||||
got += count;
|
||||
buff += count * 2;
|
||||
max -= count;
|
||||
} else {
|
||||
/* got nothing this time */
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* update current rx time */
|
||||
rx_count += got;
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
/* estimate number of samples that can be sent */
|
||||
int soapy_get_tosend(int latspl)
|
||||
{
|
||||
int tosend;
|
||||
|
||||
/* we need the rx time stamp to determine how much data is already sent in advance */
|
||||
if (rx_count == 0)
|
||||
return 0;
|
||||
|
||||
/* if we have not yet sent any data, we set initial tx time stamp */
|
||||
if (tx_count == 0)
|
||||
tx_count = rx_count;
|
||||
|
||||
/* we check how advance our transmitted time stamp is */
|
||||
tosend = latspl - (tx_count - rx_count);
|
||||
/* in case of underrun: */
|
||||
if (tosend > latspl) {
|
||||
// It is normal that we have underruns, prior inital filling of buffer.
|
||||
// FIXME: better solution to detect underrun
|
||||
// PDEBUG(DSOAPY, DEBUG_ERROR, "SDR TX underrun!\n");
|
||||
tosend = 0;
|
||||
tx_count = rx_count;
|
||||
}
|
||||
if (tosend < 0)
|
||||
tosend = 0;
|
||||
|
||||
return tosend;
|
||||
}
|
||||
|
8
src/libsdr/soapy.h
Normal file
8
src/libsdr/soapy.h
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
int soapy_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, double tx_frequency, double rx_frequency, double rate, double tx_gain, double rx_gain, double bandwidth);
|
||||
int soapy_start(void);
|
||||
void soapy_close(void);
|
||||
int soapy_send(float *buff, int num);
|
||||
int soapy_receive(float *buff, int max);
|
||||
int soapy_get_tosend(int latspl);
|
||||
|
585
src/libsdr/uhd.c
Normal file
585
src/libsdr/uhd.c
Normal file
@@ -0,0 +1,585 @@
|
||||
/* UHD device access
|
||||
*
|
||||
* (C) 2017 by Andreas Eversberg <jolly@eversberg.eu>
|
||||
* All Rights Reserved
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <uhd.h>
|
||||
#include <uhd/usrp/usrp.h>
|
||||
#include "uhd.h"
|
||||
#include "../common/debug.h"
|
||||
|
||||
/* use to TX time stamp */
|
||||
//#define TX_TIMESTAMP
|
||||
|
||||
extern int sdr_rx_overflow;
|
||||
|
||||
static uhd_usrp_handle usrp = NULL;
|
||||
static uhd_tx_streamer_handle tx_streamer = NULL;
|
||||
static uhd_rx_streamer_handle rx_streamer = NULL;
|
||||
static uhd_tx_metadata_handle tx_metadata = NULL;
|
||||
static uhd_rx_metadata_handle rx_metadata = NULL;
|
||||
static uhd_tune_request_t tune_request;
|
||||
static uhd_tune_result_t tune_result;
|
||||
static uhd_stream_args_t stream_args;
|
||||
static uhd_stream_cmd_t stream_cmd;
|
||||
static size_t tx_samps_per_buff, rx_samps_per_buff;
|
||||
static double samplerate;
|
||||
static time_t rx_time_secs = 0;
|
||||
static double rx_time_fract_sec = 0.0;
|
||||
static time_t tx_time_secs = 0;
|
||||
static double tx_time_fract_sec = 0.0;
|
||||
static int tx_timestamps;
|
||||
|
||||
int uhd_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, double tx_frequency, double rx_frequency, double rate, double tx_gain, double rx_gain, double bandwidth, int _tx_timestamps)
|
||||
{
|
||||
uhd_error error;
|
||||
double got_frequency, got_rate, got_gain, got_bandwidth;
|
||||
char got_antenna[64];
|
||||
|
||||
samplerate = rate;
|
||||
tx_timestamps = _tx_timestamps;
|
||||
|
||||
PDEBUG(DUHD, DEBUG_INFO, "Using device args \"%s\"\n", _device_args);
|
||||
PDEBUG(DUHD, DEBUG_INFO, "Using stream args \"%s\"\n", _stream_args);
|
||||
PDEBUG(DUHD, DEBUG_INFO, "Using tune args \"%s\"\n", _tune_args);
|
||||
|
||||
/* create USRP */
|
||||
PDEBUG(DUHD, DEBUG_INFO, "Creating USRP with args \"%s\"...\n", _device_args);
|
||||
error = uhd_usrp_make(&usrp, _device_args);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create USRP\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (tx_frequency) {
|
||||
/* antenna */
|
||||
if (tx_antenna && tx_antenna[0]) {
|
||||
if (!strcasecmp(tx_antenna, "list")) {
|
||||
uhd_string_vector_handle antennas;
|
||||
size_t antennas_length;
|
||||
int i;
|
||||
error = uhd_string_vector_make(&antennas);
|
||||
if (error) {
|
||||
tx_vector_error:
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to hande UHD vector, please fix!\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_usrp_get_tx_antennas(usrp, channel, &antennas);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to request list of TX antennas!\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_string_vector_size(antennas, &antennas_length);
|
||||
if (error)
|
||||
goto tx_vector_error;
|
||||
for (i = 0; i < (int)antennas_length; i++) {
|
||||
error = uhd_string_vector_at(antennas, i, got_antenna, sizeof(got_antenna));
|
||||
if (error)
|
||||
goto tx_vector_error;
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "TX Antenna: '%s'\n", got_antenna);
|
||||
}
|
||||
uhd_string_vector_free(&antennas);
|
||||
error = uhd_usrp_get_tx_antenna(usrp, channel, got_antenna, sizeof(got_antenna));
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX antenna\n");
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Default TX Antenna: '%s'\n", got_antenna);
|
||||
uhd_close();
|
||||
return 1;
|
||||
}
|
||||
error = uhd_usrp_set_tx_antenna(usrp, tx_antenna, channel);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX antenna to '%s'\n", tx_antenna);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_usrp_get_tx_antenna(usrp, channel, got_antenna, sizeof(got_antenna));
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX antenna\n");
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!!strcasecmp(tx_antenna, got_antenna)) {
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given TX antenna '%s' was accepted, but driver claims to use '%s'\n", tx_antenna, got_antenna);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
/* create streamers */
|
||||
error = uhd_tx_streamer_make(&tx_streamer);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create TX streamer\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* set rate */
|
||||
error = uhd_usrp_set_tx_rate(usrp, rate, channel);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX rate to %.0f Hz\n", rate);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what rate actually is */
|
||||
error = uhd_usrp_get_tx_rate(usrp, channel, &got_rate);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX rate\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_rate - rate) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given TX rate %.0f Hz is not supported, try %.0f Hz\n", rate, got_rate);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set gain */
|
||||
error = uhd_usrp_set_tx_gain(usrp, tx_gain, channel, "");
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX gain to %.0f\n", tx_gain);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what gain actually is */
|
||||
error = uhd_usrp_get_tx_gain(usrp, channel, "", &got_gain);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX gain\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_gain - tx_gain) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given TX gain %.0f is not supported, we use %.0f\n", tx_gain, got_gain);
|
||||
tx_gain = got_gain;
|
||||
}
|
||||
|
||||
/* set frequency */
|
||||
memset(&tune_request, 0, sizeof(tune_request));
|
||||
tune_request.target_freq = tx_frequency;
|
||||
tune_request.rf_freq_policy = UHD_TUNE_REQUEST_POLICY_AUTO;
|
||||
tune_request.dsp_freq_policy = UHD_TUNE_REQUEST_POLICY_AUTO;
|
||||
tune_request.args = strdup(_tune_args);
|
||||
error = uhd_usrp_set_tx_freq(usrp, &tune_request, channel, &tune_result);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX frequeny to %.0f Hz\n", tx_frequency);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what frequency actually is */
|
||||
error = uhd_usrp_get_tx_freq(usrp, channel, &got_frequency);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX frequency\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_frequency - tx_frequency) > 100.0) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given TX frequency %.0f Hz is not supported, try %.0f Hz\n", tx_frequency, got_frequency);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set bandwidth */
|
||||
if (uhd_usrp_set_tx_bandwidth(usrp, bandwidth, channel) != 0) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX bandwidth to %.0f Hz\n", bandwidth);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what bandwidth actually is */
|
||||
error = uhd_usrp_get_tx_bandwidth(usrp, channel, &got_bandwidth);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX bandwidth\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_bandwidth - bandwidth) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given TX bandwidth %.0f Hz is not supported, try %.0f Hz\n", bandwidth, got_bandwidth);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set up streamer */
|
||||
memset(&stream_args, 0, sizeof(stream_args));
|
||||
stream_args.cpu_format = "fc32";
|
||||
stream_args.otw_format = "sc16";
|
||||
stream_args.args = strdup(_stream_args);
|
||||
stream_args.channel_list = &channel;
|
||||
stream_args.n_channels = 1;
|
||||
error = uhd_usrp_get_tx_stream(usrp, &stream_args, tx_streamer);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set TX streamer args\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* get buffer sizes */
|
||||
error = uhd_tx_streamer_max_num_samps(tx_streamer, &tx_samps_per_buff);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get TX streamer sample buffer\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
if (rx_frequency) {
|
||||
/* antenna */
|
||||
if (rx_antenna && rx_antenna[0]) {
|
||||
if (!strcasecmp(rx_antenna, "list")) {
|
||||
uhd_string_vector_handle antennas;
|
||||
size_t antennas_length;
|
||||
int i;
|
||||
error = uhd_string_vector_make(&antennas);
|
||||
if (error) {
|
||||
rx_vector_error:
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to hande UHD vector, please fix!\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_usrp_get_rx_antennas(usrp, channel, &antennas);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to request list of RX antennas!\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_string_vector_size(antennas, &antennas_length);
|
||||
if (error)
|
||||
goto rx_vector_error;
|
||||
for (i = 0; i < (int)antennas_length; i++) {
|
||||
error = uhd_string_vector_at(antennas, i, got_antenna, sizeof(got_antenna));
|
||||
if (error)
|
||||
goto rx_vector_error;
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "RX Antenna: '%s'\n", got_antenna);
|
||||
}
|
||||
uhd_string_vector_free(&antennas);
|
||||
error = uhd_usrp_get_rx_antenna(usrp, channel, got_antenna, sizeof(got_antenna));
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX antenna\n");
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Default RX Antenna: '%s'\n", got_antenna);
|
||||
uhd_close();
|
||||
return 1;
|
||||
}
|
||||
error = uhd_usrp_set_rx_antenna(usrp, rx_antenna, channel);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX antenna to '%s'\n", rx_antenna);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
error = uhd_usrp_get_rx_antenna(usrp, channel, got_antenna, sizeof(got_antenna));
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX antenna\n");
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!!strcasecmp(rx_antenna, got_antenna)) {
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given RX antenna '%s' was accepted, but driver claims to use '%s'\n", rx_antenna, got_antenna);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
/* create streamers */
|
||||
error = uhd_rx_streamer_make(&rx_streamer);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create RX streamer\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* create metadata */
|
||||
error = uhd_rx_metadata_make(&rx_metadata);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create RX metadata\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* set rate */
|
||||
error = uhd_usrp_set_rx_rate(usrp, rate, channel);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX rate to %.0f Hz\n", rate);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what rate actually is */
|
||||
error = uhd_usrp_get_rx_rate(usrp, channel, &got_rate);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX rate\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_rate - rate) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given RX rate %.0f Hz is not supported, try %.0f Hz\n", rate, got_rate);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set gain */
|
||||
error = uhd_usrp_set_rx_gain(usrp, rx_gain, channel, "");
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX gain to %.0f\n", rx_gain);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what gain actually is */
|
||||
error = uhd_usrp_get_rx_gain(usrp, channel, "", &got_gain);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX gain\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_gain - rx_gain) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_NOTICE, "Given RX gain %.3f is not supported, we use %.3f\n", rx_gain, got_gain);
|
||||
rx_gain = got_gain;
|
||||
}
|
||||
|
||||
/* set frequency */
|
||||
memset(&tune_request, 0, sizeof(tune_request));
|
||||
tune_request.target_freq = rx_frequency;
|
||||
tune_request.rf_freq_policy = UHD_TUNE_REQUEST_POLICY_AUTO;
|
||||
tune_request.dsp_freq_policy = UHD_TUNE_REQUEST_POLICY_AUTO;
|
||||
tune_request.args = strdup(_tune_args);
|
||||
error = uhd_usrp_set_rx_freq(usrp, &tune_request, channel, &tune_result);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX frequeny to %.0f Hz\n", rx_frequency);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what frequency actually is */
|
||||
error = uhd_usrp_get_rx_freq(usrp, channel, &got_frequency);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX frequency\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_frequency - rx_frequency) > 100.0) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given RX frequency %.0f Hz is not supported, try %.0f Hz\n", rx_frequency, got_frequency);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set bandwidth */
|
||||
if (uhd_usrp_set_rx_bandwidth(usrp, bandwidth, channel) != 0) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX bandwidth to %.0f Hz\n", bandwidth);
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* see what bandwidth actually is */
|
||||
error = uhd_usrp_get_rx_bandwidth(usrp, channel, &got_bandwidth);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX bandwidth\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
if (fabs(got_bandwidth - bandwidth) > 0.001) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Given RX bandwidth %.0f Hz is not supported, try %.0f Hz\n", bandwidth, got_bandwidth);
|
||||
uhd_close();
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* set up streamer */
|
||||
memset(&stream_args, 0, sizeof(stream_args));
|
||||
stream_args.cpu_format = "fc32";
|
||||
stream_args.otw_format = "sc16";
|
||||
stream_args.args = strdup(_stream_args);
|
||||
stream_args.channel_list = &channel;
|
||||
stream_args.n_channels = 1;
|
||||
error = uhd_usrp_get_rx_stream(usrp, &stream_args, rx_streamer);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to set RX streamer args\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* get buffer sizes */
|
||||
error = uhd_rx_streamer_max_num_samps(rx_streamer, &rx_samps_per_buff);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to get RX streamer sample buffer\n");
|
||||
uhd_close();
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* start streaming */
|
||||
int uhd_start(void)
|
||||
{
|
||||
uhd_error error;
|
||||
|
||||
/* enable rx stream */
|
||||
memset(&stream_cmd, 0, sizeof(stream_cmd));
|
||||
stream_cmd.stream_mode = UHD_STREAM_MODE_START_CONTINUOUS;
|
||||
stream_cmd.stream_now = true;
|
||||
error = uhd_rx_streamer_issue_stream_cmd(rx_streamer, &stream_cmd);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to issue RX stream command\n");
|
||||
return -EIO;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uhd_close(void)
|
||||
{
|
||||
PDEBUG(DUHD, DEBUG_DEBUG, "Clean up UHD\n");
|
||||
if (tx_metadata)
|
||||
uhd_tx_metadata_free(&tx_metadata);
|
||||
if (rx_metadata)
|
||||
uhd_rx_metadata_free(&rx_metadata);
|
||||
if (tx_streamer)
|
||||
uhd_tx_streamer_free(&tx_streamer);
|
||||
if (rx_streamer)
|
||||
uhd_rx_streamer_free(&rx_streamer);
|
||||
if (usrp)
|
||||
uhd_usrp_free(&usrp);
|
||||
}
|
||||
|
||||
int uhd_send(float *buff, int num)
|
||||
{
|
||||
const void *buffs_ptr[1];
|
||||
int chunk;
|
||||
size_t sent = 0, count;
|
||||
uhd_error error;
|
||||
|
||||
while (num) {
|
||||
chunk = num;
|
||||
if (chunk > (int)tx_samps_per_buff)
|
||||
chunk = (int)tx_samps_per_buff;
|
||||
/* create tx metadata */
|
||||
if (tx_timestamps)
|
||||
error = uhd_tx_metadata_make(&tx_metadata, true, tx_time_secs, tx_time_fract_sec, false, false);
|
||||
else
|
||||
error = uhd_tx_metadata_make(&tx_metadata, false, 0, 0.0, false, false);
|
||||
if (error)
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to create TX metadata\n");
|
||||
buffs_ptr[0] = buff;
|
||||
count = 0;
|
||||
error = uhd_tx_streamer_send(tx_streamer, buffs_ptr, chunk, &tx_metadata, 1.0, &count);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to write to TX streamer\n");
|
||||
break;
|
||||
}
|
||||
if (count == 0)
|
||||
break;
|
||||
|
||||
/* increment time stamp */
|
||||
tx_time_fract_sec += (double)count / samplerate;
|
||||
while (tx_time_fract_sec >= 1.0) {
|
||||
tx_time_secs++;
|
||||
tx_time_fract_sec -= 1.0;
|
||||
}
|
||||
//printf("adv=%.3f\n", ((double)tx_time_secs + tx_time_fract_sec) - ((double)rx_time_secs + rx_time_fract_sec));
|
||||
|
||||
sent += count;
|
||||
buff += count * 2;
|
||||
num -= count;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
/* read what we got, return 0, if buffer is empty, otherwise return the number of samples */
|
||||
int uhd_receive(float *buff, int max)
|
||||
{
|
||||
void *buffs_ptr[1];
|
||||
size_t got = 0, count;
|
||||
uhd_error error;
|
||||
|
||||
while (1) {
|
||||
if (max < (int)rx_samps_per_buff) {
|
||||
/* no more space this time */
|
||||
sdr_rx_overflow = 1;
|
||||
break;
|
||||
}
|
||||
/* read RX stream */
|
||||
buffs_ptr[0] = buff;
|
||||
count = 0;
|
||||
error = uhd_rx_streamer_recv(rx_streamer, buffs_ptr, rx_samps_per_buff, &rx_metadata, 0.0, false, &count);
|
||||
if (error) {
|
||||
PDEBUG(DUHD, DEBUG_ERROR, "Failed to read from UHD device.\n");
|
||||
break;
|
||||
}
|
||||
if (count) {
|
||||
/* get time stamp of received RX packet */
|
||||
uhd_rx_metadata_time_spec(rx_metadata, &rx_time_secs, &rx_time_fract_sec);
|
||||
/* commit received data to buffer */
|
||||
got += count;
|
||||
buff += count * 2;
|
||||
max -= count;
|
||||
} else {
|
||||
/* got nothing this time */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
/* estimate number of samples that can be sent */
|
||||
int uhd_get_tosend(int latspl)
|
||||
{
|
||||
double advance;
|
||||
int tosend;
|
||||
|
||||
/* we need the rx time stamp to determine how much data is already sent in advance */
|
||||
if (rx_time_secs == 0 && rx_time_fract_sec == 0.0)
|
||||
return 0;
|
||||
|
||||
/* if we have not yet sent any data, we set initial tx time stamp */
|
||||
if (tx_time_secs == 0 && tx_time_fract_sec == 0.0) {
|
||||
tx_time_secs = rx_time_secs;
|
||||
tx_time_fract_sec = rx_time_fract_sec;
|
||||
if (tx_timestamps) {
|
||||
tx_time_fract_sec += (double)latspl / samplerate;
|
||||
if (tx_time_fract_sec >= 1.0) {
|
||||
tx_time_fract_sec -= 1.0;
|
||||
tx_time_secs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* we check how advance our transmitted time stamp is */
|
||||
advance = ((double)tx_time_secs + tx_time_fract_sec) - ((double)rx_time_secs + rx_time_fract_sec);
|
||||
/* in case of underrun: */
|
||||
if (advance < 0)
|
||||
advance = 0;
|
||||
tosend = latspl - (int)(advance * samplerate);
|
||||
if (tosend < 0)
|
||||
tosend = 0;
|
||||
|
||||
return tosend;
|
||||
}
|
||||
|
8
src/libsdr/uhd.h
Normal file
8
src/libsdr/uhd.h
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
int uhd_open(size_t channel, const char *_device_args, const char *_stream_args, const char *_tune_args, const char *tx_antenna, const char *rx_antenna, double tx_frequency, double rx_frequency, double rate, double tx_gain, double rx_gain, double bandwidth, int _tx_timestamps);
|
||||
int uhd_start(void);
|
||||
void uhd_close(void);
|
||||
int uhd_send(float *buff, int num);
|
||||
int uhd_receive(float *buff, int max);
|
||||
int uhd_get_tosend(int latspl);
|
||||
|
Reference in New Issue
Block a user