Merge branch 'dev' into master
This commit is contained in:
commit
3a819a5aeb
48
Makefile
48
Makefile
|
@ -1,13 +1,11 @@
|
||||||
PROGNAME=dump1090
|
PROGNAME=dump1090
|
||||||
|
|
||||||
|
RTLSDR ?= yes
|
||||||
|
BLADERF ?= yes
|
||||||
|
|
||||||
CC=gcc
|
CC=gcc
|
||||||
CPPFLAGS += -DMODES_DUMP1090_VERSION=\"$(DUMP1090_VERSION)\" -DMODES_DUMP1090_VARIANT=\"dump1090-fa\"
|
CPPFLAGS += -DMODES_DUMP1090_VERSION=\"$(DUMP1090_VERSION)\" -DMODES_DUMP1090_VARIANT=\"dump1090-fa\"
|
||||||
|
|
||||||
ifneq ($(RTLSDR_PREFIX),"")
|
|
||||||
CPPFLAGS += -I$(RTLSDR_PREFIX)/include
|
|
||||||
LDFLAGS += -L$(RTLSDR_PREFIX)/lib
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifneq ($(HTMLPATH),"")
|
ifneq ($(HTMLPATH),"")
|
||||||
CPPFLAGS += -DHTMLPATH=\"$(HTMLPATH)\"
|
CPPFLAGS += -DHTMLPATH=\"$(HTMLPATH)\"
|
||||||
endif
|
endif
|
||||||
|
@ -16,10 +14,30 @@ DIALECT = -std=c11
|
||||||
CFLAGS += $(DIALECT) -O2 -g -Wall -Werror -W -D_DEFAULT_SOURCE
|
CFLAGS += $(DIALECT) -O2 -g -Wall -Werror -W -D_DEFAULT_SOURCE
|
||||||
LIBS = -lpthread -lm -lrt
|
LIBS = -lpthread -lm -lrt
|
||||||
|
|
||||||
ifeq ($(STATIC), yes)
|
ifeq ($(RTLSDR), yes)
|
||||||
LIBS_RTLSDR = -Wl,-Bstatic -lrtlsdr -Wl,-Bdynamic -lusb-1.0
|
SDR_OBJ += sdr_rtlsdr.o
|
||||||
else
|
CPPFLAGS += -DENABLE_RTLSDR
|
||||||
LIBS_RTLSDR = -lrtlsdr -lusb-1.0
|
|
||||||
|
ifdef RTLSDR_PREFIX
|
||||||
|
CPPFLAGS += -I$(RTLSDR_PREFIX)/include
|
||||||
|
LDFLAGS += -L$(RTLSDR_PREFIX)/lib
|
||||||
|
else
|
||||||
|
CFLAGS += $(shell pkg-config --cflags librtlsdr)
|
||||||
|
LDFLAGS += $(shell pkg-config --libs-only-L librtlsdr)
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(STATIC), yes)
|
||||||
|
LIBS_SDR += -Wl,-Bstatic -lrtlsdr -Wl,-Bdynamic -lusb-1.0
|
||||||
|
else
|
||||||
|
LIBS_SDR += -lrtlsdr -lusb-1.0
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifeq ($(BLADERF), yes)
|
||||||
|
SDR_OBJ += sdr_bladerf.o
|
||||||
|
CPPFLAGS += -DENABLE_BLADERF
|
||||||
|
CFLAGS += $(shell pkg-config --cflags libbladeRF)
|
||||||
|
LIBS_SDR += $(shell pkg-config --libs libbladeRF)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
all: dump1090 view1090
|
all: dump1090 view1090
|
||||||
|
@ -27,8 +45,8 @@ all: dump1090 view1090
|
||||||
%.o: %.c *.h
|
%.o: %.c *.h
|
||||||
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o $(COMPAT)
|
dump1090: dump1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o demod_2400.o stats.o cpr.o icao_filter.o track.o util.o convert.o sdr_ifile.o sdr.o $(SDR_OBJ) $(COMPAT)
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_RTLSDR) -lncurses
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_SDR) -lncurses
|
||||||
|
|
||||||
view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
view1090: view1090.o anet.o interactive.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao_filter.o track.o util.o $(COMPAT)
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) -lncurses
|
||||||
|
@ -37,7 +55,7 @@ faup1090: faup1090.o anet.o mode_ac.o mode_s.o net_io.o crc.o stats.o cpr.o icao
|
||||||
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS)
|
$(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o compat/clock_gettime/*.o compat/clock_nanosleep/*.o dump1090 view1090 faup1090 cprtests crctests
|
rm -f *.o compat/clock_gettime/*.o compat/clock_nanosleep/*.o dump1090 view1090 faup1090 cprtests crctests convert_benchmark
|
||||||
|
|
||||||
test: cprtests
|
test: cprtests
|
||||||
./cprtests
|
./cprtests
|
||||||
|
@ -47,3 +65,9 @@ cprtests: cpr.o cprtests.o
|
||||||
|
|
||||||
crctests: crc.c crc.h
|
crctests: crc.c crc.h
|
||||||
$(CC) $(CPPFLAGS) $(CFLAGS) -g -DCRCDEBUG -o $@ $<
|
$(CC) $(CPPFLAGS) $(CFLAGS) -g -DCRCDEBUG -o $@ $<
|
||||||
|
|
||||||
|
benchmarks: convert_benchmark
|
||||||
|
./convert_benchmark
|
||||||
|
|
||||||
|
convert_benchmark: convert_benchmark.o convert.o util.o
|
||||||
|
$(CC) $(CPPFLAGS) $(CFLAGS) -g -o $@ $^ -lm
|
||||||
|
|
35
README.md
35
README.md
|
@ -8,10 +8,43 @@ It is designed to build as a Debian package.
|
||||||
|
|
||||||
## Building under jessie
|
## Building under jessie
|
||||||
|
|
||||||
|
### Dependencies - bladeRF
|
||||||
|
|
||||||
|
You will need a build of libbladeRF. You can build packages from source:
|
||||||
|
|
||||||
|
$ git clone https://github.com/Nuand/bladeRF.git
|
||||||
|
$ cd bladeRF
|
||||||
|
$ dpkg-buildpackage -b
|
||||||
|
|
||||||
|
Or Nuand has some build/install instructions including an Ubuntu PPA
|
||||||
|
at https://github.com/Nuand/bladeRF/wiki/Getting-Started:-Linux
|
||||||
|
|
||||||
|
Or FlightAware provides armhf packages as part of the piaware repository;
|
||||||
|
see https://flightaware.com/adsb/piaware/install
|
||||||
|
|
||||||
|
### Dependencies - rtlsdr
|
||||||
|
|
||||||
|
This is packaged with jessie. "sudo apt-get install librtlsdr-dev"
|
||||||
|
|
||||||
|
### Actually building it
|
||||||
|
|
||||||
Nothing special, just build it ("dpkg-buildpackage -b")
|
Nothing special, just build it ("dpkg-buildpackage -b")
|
||||||
|
|
||||||
## Building under wheezy
|
## Building under wheezy
|
||||||
|
|
||||||
First run "prepare-wheezy-tree.sh". This will create a package tre in
|
First run "prepare-wheezy-tree.sh". This will create a package tree in
|
||||||
package-wheezy/. Build in there ("dpkg-buildpackage -b")
|
package-wheezy/. Build in there ("dpkg-buildpackage -b")
|
||||||
|
|
||||||
|
The wheezy build does not include bladeRF support.
|
||||||
|
|
||||||
|
## Building manually
|
||||||
|
|
||||||
|
You can probably just run "make" after installing the required dependencies.
|
||||||
|
Binaries are built in the source directory; you will need to arrange to
|
||||||
|
install them (and a method for starting them) yourself.
|
||||||
|
|
||||||
|
"make BLADERF=no" will disable bladeRF support and remove the dependency on
|
||||||
|
libbladeRF.
|
||||||
|
|
||||||
|
"make RTLSDR=no" will disable rtl-sdr support and remove the dependency on
|
||||||
|
librtlsdr.
|
||||||
|
|
43
bladerf/README
Normal file
43
bladerf/README
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
bladeRF support
|
||||||
|
---------------
|
||||||
|
|
||||||
|
There is basic support for the bladeRF included in dump1090-fa.
|
||||||
|
It uses the same demodulator as the regular rtlsdr version and it does not
|
||||||
|
take advantage of the bladeRF's improved sampling rate or FPGA (but see below)
|
||||||
|
|
||||||
|
To use it:
|
||||||
|
|
||||||
|
$ dump1090-fa --device-type bladerf --gain 66 (.. other options ..)
|
||||||
|
|
||||||
|
The bladeRF has sensitivity problems when receiving ADS-B and will benefit
|
||||||
|
from all the gain you can give it. 66dB is the maximum configurable gain on
|
||||||
|
the board itself, you may want to add an external LNA too.
|
||||||
|
|
||||||
|
|
||||||
|
bladeRF custom FPGA bitstream
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The package includes a custom FPGA bitstream for the bladeRF which can
|
||||||
|
improve ADS-B reception with dump1090.
|
||||||
|
|
||||||
|
It adds a Fs/4 mixer step to avoid the DC offset of the bladeRF, and
|
||||||
|
decimate-by-8 downsampling step to improve sensitivity. Some of the bladeRF
|
||||||
|
parameters need to be adjusted so that it produces correctly tuned/downsampled
|
||||||
|
data in the form that dump1090 is expecting.
|
||||||
|
|
||||||
|
To use it:
|
||||||
|
|
||||||
|
$ dump1090-fa --device-type bladerf --gain 66 \
|
||||||
|
--bladerf-fpga /usr/share/dump1090-fa/bladerf/decimate8-x40.rbf \
|
||||||
|
--bladerf-decimation 8 \
|
||||||
|
--bladerf-bandwidth 14000000 \
|
||||||
|
--freq 1085200000 \
|
||||||
|
(other options ...)
|
||||||
|
|
||||||
|
The bladeRF will be configured for a 19.2MHz sampling rate and will tune to
|
||||||
|
1085.2MHz so that the 1090MHz signal appears around 4.8MHz in the baseband
|
||||||
|
samples. The FPGA Fs/4 mixing step shifts the baseband signal so that it is centered
|
||||||
|
around 0, and the FPGA downsampling step produces samples at 2.4MHz, which is what
|
||||||
|
dump1090 is expecting.
|
||||||
|
|
||||||
|
This FPGA bitstream is built for the 40kLE bladeRF.
|
BIN
bladerf/decimate8-x40.rbf
Normal file
BIN
bladerf/decimate8-x40.rbf
Normal file
Binary file not shown.
296
convert.c
296
convert.c
|
@ -26,6 +26,36 @@ struct converter_state {
|
||||||
float z1_Q;
|
float z1_Q;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static uint16_t *uc8_lookup;
|
||||||
|
static bool init_uc8_lookup()
|
||||||
|
{
|
||||||
|
if (uc8_lookup)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
uc8_lookup = malloc(sizeof(uint16_t) * 256 * 256);
|
||||||
|
if (!uc8_lookup) {
|
||||||
|
fprintf(stderr, "can't allocate UC8 conversion lookup table\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i <= 255; i++) {
|
||||||
|
for (int q = 0; q <= 255; q++) {
|
||||||
|
float fI, fQ, magsq;
|
||||||
|
|
||||||
|
fI = (i - 127.5) / 127.5;
|
||||||
|
fQ = (q - 127.5) / 127.5;
|
||||||
|
magsq = fI * fI + fQ * fQ;
|
||||||
|
if (magsq > 1)
|
||||||
|
magsq = 1;
|
||||||
|
float mag = sqrtf(magsq);
|
||||||
|
|
||||||
|
uc8_lookup[le16toh((i*256)+q)] = (uint16_t) (mag * 65535.0f + 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void convert_uc8_nodc(void *iq_data,
|
static void convert_uc8_nodc(void *iq_data,
|
||||||
uint16_t *mag_data,
|
uint16_t *mag_data,
|
||||||
unsigned nsamples,
|
unsigned nsamples,
|
||||||
|
@ -42,55 +72,33 @@ static void convert_uc8_nodc(void *iq_data,
|
||||||
MODES_NOTUSED(state);
|
MODES_NOTUSED(state);
|
||||||
|
|
||||||
// unroll this a bit
|
// unroll this a bit
|
||||||
|
|
||||||
|
#define DO_ONE_SAMPLE \
|
||||||
|
do { \
|
||||||
|
mag = uc8_lookup[*in++]; \
|
||||||
|
*mag_data++ = mag; \
|
||||||
|
sum_level += mag; \
|
||||||
|
sum_power += (uint32_t)mag * (uint32_t)mag; \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
// unroll this a bit
|
||||||
for (i = 0; i < (nsamples>>3); ++i) {
|
for (i = 0; i < (nsamples>>3); ++i) {
|
||||||
mag = Modes.maglut[*in++];
|
DO_ONE_SAMPLE;
|
||||||
*mag_data++ = mag;
|
DO_ONE_SAMPLE;
|
||||||
sum_level += mag;
|
DO_ONE_SAMPLE;
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
DO_ONE_SAMPLE;
|
||||||
|
DO_ONE_SAMPLE;
|
||||||
mag = Modes.maglut[*in++];
|
DO_ONE_SAMPLE;
|
||||||
*mag_data++ = mag;
|
DO_ONE_SAMPLE;
|
||||||
sum_level += mag;
|
DO_ONE_SAMPLE;
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
|
|
||||||
mag = Modes.maglut[*in++];
|
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < (nsamples&7); ++i) {
|
for (i = 0; i < (nsamples&7); ++i) {
|
||||||
mag = Modes.maglut[*in++];
|
DO_ONE_SAMPLE;
|
||||||
*mag_data++ = mag;
|
|
||||||
sum_level += mag;
|
|
||||||
sum_power += (uint32_t)mag * (uint32_t)mag;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef DO_ONE_SAMPLE
|
||||||
|
|
||||||
if (out_mean_level) {
|
if (out_mean_level) {
|
||||||
*out_mean_level = sum_level / 65536.0 / nsamples;
|
*out_mean_level = sum_level / 65536.0 / nsamples;
|
||||||
}
|
}
|
||||||
|
@ -121,8 +129,8 @@ static void convert_uc8_generic(void *iq_data,
|
||||||
for (i = 0; i < nsamples; ++i) {
|
for (i = 0; i < nsamples; ++i) {
|
||||||
I = *in++;
|
I = *in++;
|
||||||
Q = *in++;
|
Q = *in++;
|
||||||
fI = (I - 127.5) / 127.5;
|
fI = (I - 127.5f) / 127.5f;
|
||||||
fQ = (Q - 127.5) / 127.5;
|
fQ = (Q - 127.5f) / 127.5f;
|
||||||
|
|
||||||
// DC block
|
// DC block
|
||||||
z1_I = fI * dc_a + z1_I * dc_b;
|
z1_I = fI * dc_a + z1_I * dc_b;
|
||||||
|
@ -137,7 +145,7 @@ static void convert_uc8_generic(void *iq_data,
|
||||||
float mag = sqrtf(magsq);
|
float mag = sqrtf(magsq);
|
||||||
sum_power += magsq;
|
sum_power += magsq;
|
||||||
sum_level += mag;
|
sum_level += mag;
|
||||||
*mag_data++ = (uint16_t)(mag * 65535.0 + 0.5);
|
*mag_data++ = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
state->z1_I = z1_I;
|
state->z1_I = z1_I;
|
||||||
|
@ -173,8 +181,8 @@ static void convert_sc16_generic(void *iq_data,
|
||||||
for (i = 0; i < nsamples; ++i) {
|
for (i = 0; i < nsamples; ++i) {
|
||||||
I = (int16_t)le16toh(*in++);
|
I = (int16_t)le16toh(*in++);
|
||||||
Q = (int16_t)le16toh(*in++);
|
Q = (int16_t)le16toh(*in++);
|
||||||
fI = I / 32768.0;
|
fI = I / 32768.0f;
|
||||||
fQ = Q / 32768.0;
|
fQ = Q / 32768.0f;
|
||||||
|
|
||||||
// DC block
|
// DC block
|
||||||
z1_I = fI * dc_a + z1_I * dc_b;
|
z1_I = fI * dc_a + z1_I * dc_b;
|
||||||
|
@ -189,7 +197,7 @@ static void convert_sc16_generic(void *iq_data,
|
||||||
float mag = sqrtf(magsq);
|
float mag = sqrtf(magsq);
|
||||||
sum_power += magsq;
|
sum_power += magsq;
|
||||||
sum_level += mag;
|
sum_level += mag;
|
||||||
*mag_data++ = (uint16_t)(mag * 65535.0 + 0.5);
|
*mag_data++ = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
state->z1_I = z1_I;
|
state->z1_I = z1_I;
|
||||||
|
@ -204,6 +212,168 @@ static void convert_sc16_generic(void *iq_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void convert_sc16_nodc(void *iq_data,
|
||||||
|
uint16_t *mag_data,
|
||||||
|
unsigned nsamples,
|
||||||
|
struct converter_state *state,
|
||||||
|
double *out_mean_level,
|
||||||
|
double *out_mean_power)
|
||||||
|
{
|
||||||
|
MODES_NOTUSED(state);
|
||||||
|
|
||||||
|
uint16_t *in = iq_data;
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
int16_t I, Q;
|
||||||
|
float fI, fQ, magsq;
|
||||||
|
float sum_level = 0, sum_power = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < nsamples; ++i) {
|
||||||
|
I = (int16_t)le16toh(*in++);
|
||||||
|
Q = (int16_t)le16toh(*in++);
|
||||||
|
fI = I / 32768.0f;
|
||||||
|
fQ = Q / 32768.0f;
|
||||||
|
|
||||||
|
magsq = fI * fI + fQ * fQ;
|
||||||
|
if (magsq > 1)
|
||||||
|
magsq = 1;
|
||||||
|
|
||||||
|
float mag = sqrtf(magsq);
|
||||||
|
sum_power += magsq;
|
||||||
|
sum_level += mag;
|
||||||
|
*mag_data++ = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_level) {
|
||||||
|
*out_mean_level = sum_level / nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_power) {
|
||||||
|
*out_mean_power = sum_power / nsamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SC16Q11_TABLE_BITS controls the size of the lookup table
|
||||||
|
// for SC16Q11 data. The size of the table is 2 * (1 << (2*BITS))
|
||||||
|
// bytes. Reducing the number of bits reduces precision but
|
||||||
|
// can run substantially faster by staying in cache.
|
||||||
|
// See convert_benchmark.c for some numbers.
|
||||||
|
|
||||||
|
// Leaving SC16QQ_TABLE_BITS undefined will disable the table lookup and always use
|
||||||
|
// the floating-point path, which may be faster on some systems
|
||||||
|
|
||||||
|
#if defined(SC16Q11_TABLE_BITS)
|
||||||
|
|
||||||
|
#define USE_BITS SC16Q11_TABLE_BITS
|
||||||
|
#define LOSE_BITS (11 - SC16Q11_TABLE_BITS)
|
||||||
|
|
||||||
|
static uint16_t *sc16q11_lookup;
|
||||||
|
static bool init_sc16q11_lookup()
|
||||||
|
{
|
||||||
|
if (sc16q11_lookup)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
sc16q11_lookup = malloc(sizeof(uint16_t) * (1 << (USE_BITS * 2)));
|
||||||
|
if (!sc16q11_lookup) {
|
||||||
|
fprintf(stderr, "can't allocate SC16Q11 conversion lookup table\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 2048; i += (1 << LOSE_BITS)) {
|
||||||
|
for (int q = 0; q < 2048; q += (1 << LOSE_BITS)) {
|
||||||
|
float fI = i / 2048.0, fQ = q / 2048.0;
|
||||||
|
float magsq = fI * fI + fQ * fQ;
|
||||||
|
if (magsq > 1)
|
||||||
|
magsq = 1;
|
||||||
|
float mag = sqrtf(magsq);
|
||||||
|
|
||||||
|
unsigned index = ((i >> LOSE_BITS) << USE_BITS) | (q >> LOSE_BITS);
|
||||||
|
sc16q11_lookup[index] = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void convert_sc16q11_table(void *iq_data,
|
||||||
|
uint16_t *mag_data,
|
||||||
|
unsigned nsamples,
|
||||||
|
struct converter_state *state,
|
||||||
|
double *out_mean_level,
|
||||||
|
double *out_mean_power)
|
||||||
|
{
|
||||||
|
uint16_t *in = iq_data;
|
||||||
|
unsigned i;
|
||||||
|
uint16_t I, Q;
|
||||||
|
uint64_t sum_level = 0;
|
||||||
|
uint64_t sum_power = 0;
|
||||||
|
uint16_t mag;
|
||||||
|
|
||||||
|
MODES_NOTUSED(state);
|
||||||
|
|
||||||
|
for (i = 0; i < nsamples; ++i) {
|
||||||
|
I = abs((int16_t)le16toh(*in++)) & 2047;
|
||||||
|
Q = abs((int16_t)le16toh(*in++)) & 2047;
|
||||||
|
mag = sc16q11_lookup[((I >> LOSE_BITS) << USE_BITS) | (Q >> LOSE_BITS)];
|
||||||
|
*mag_data++ = mag;
|
||||||
|
sum_level += mag;
|
||||||
|
sum_power += (uint32_t)mag * (uint32_t)mag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_level) {
|
||||||
|
*out_mean_level = sum_level / 65536.0 / nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_power) {
|
||||||
|
*out_mean_power = sum_power / 65535.0 / 65535.0 / nsamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* ! defined(SC16Q11_TABLE_BITS) */
|
||||||
|
|
||||||
|
static void convert_sc16q11_nodc(void *iq_data,
|
||||||
|
uint16_t *mag_data,
|
||||||
|
unsigned nsamples,
|
||||||
|
struct converter_state *state,
|
||||||
|
double *out_mean_level,
|
||||||
|
double *out_mean_power)
|
||||||
|
{
|
||||||
|
MODES_NOTUSED(state);
|
||||||
|
|
||||||
|
uint16_t *in = iq_data;
|
||||||
|
|
||||||
|
unsigned i;
|
||||||
|
int16_t I, Q;
|
||||||
|
float fI, fQ, magsq;
|
||||||
|
float sum_level = 0, sum_power = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < nsamples; ++i) {
|
||||||
|
I = (int16_t)le16toh(*in++);
|
||||||
|
Q = (int16_t)le16toh(*in++);
|
||||||
|
fI = I / 2048.0f;
|
||||||
|
fQ = Q / 2048.0f;
|
||||||
|
|
||||||
|
magsq = fI * fI + fQ * fQ;
|
||||||
|
if (magsq > 1)
|
||||||
|
magsq = 1;
|
||||||
|
|
||||||
|
float mag = sqrtf(magsq);
|
||||||
|
sum_power += magsq;
|
||||||
|
sum_level += mag;
|
||||||
|
*mag_data++ = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_level) {
|
||||||
|
*out_mean_level = sum_level / nsamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_mean_power) {
|
||||||
|
*out_mean_power = sum_power / nsamples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* defined(SC16Q11_TABLE_BITS) */
|
||||||
|
|
||||||
static void convert_sc16q11_generic(void *iq_data,
|
static void convert_sc16q11_generic(void *iq_data,
|
||||||
uint16_t *mag_data,
|
uint16_t *mag_data,
|
||||||
unsigned nsamples,
|
unsigned nsamples,
|
||||||
|
@ -225,8 +395,8 @@ static void convert_sc16q11_generic(void *iq_data,
|
||||||
for (i = 0; i < nsamples; ++i) {
|
for (i = 0; i < nsamples; ++i) {
|
||||||
I = (int16_t)le16toh(*in++);
|
I = (int16_t)le16toh(*in++);
|
||||||
Q = (int16_t)le16toh(*in++);
|
Q = (int16_t)le16toh(*in++);
|
||||||
fI = I / 2048.0;
|
fI = I / 2048.0f;
|
||||||
fQ = Q / 2048.0;
|
fQ = Q / 2048.0f;
|
||||||
|
|
||||||
// DC block
|
// DC block
|
||||||
z1_I = fI * dc_a + z1_I * dc_b;
|
z1_I = fI * dc_a + z1_I * dc_b;
|
||||||
|
@ -241,7 +411,7 @@ static void convert_sc16q11_generic(void *iq_data,
|
||||||
float mag = sqrtf(magsq);
|
float mag = sqrtf(magsq);
|
||||||
sum_power += magsq;
|
sum_power += magsq;
|
||||||
sum_level += mag;
|
sum_level += mag;
|
||||||
*mag_data++ = (uint16_t)(sqrtf(magsq) * 65535.0 + 0.5);
|
*mag_data++ = (uint16_t)(mag * 65535.0f + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
state->z1_I = z1_I;
|
state->z1_I = z1_I;
|
||||||
|
@ -261,13 +431,20 @@ static struct {
|
||||||
int can_filter_dc;
|
int can_filter_dc;
|
||||||
iq_convert_fn fn;
|
iq_convert_fn fn;
|
||||||
const char *description;
|
const char *description;
|
||||||
|
bool (*init)();
|
||||||
} converters_table[] = {
|
} converters_table[] = {
|
||||||
// In order of preference
|
// In order of preference
|
||||||
{ INPUT_UC8, 0, convert_uc8_nodc, "UC8, integer/table path" },
|
{ INPUT_UC8, 0, convert_uc8_nodc, "UC8, integer/table path", init_uc8_lookup },
|
||||||
{ INPUT_UC8, 1, convert_uc8_generic, "UC8, float path" },
|
{ INPUT_UC8, 1, convert_uc8_generic, "UC8, float path", NULL },
|
||||||
{ INPUT_SC16, 1, convert_sc16_generic, "SC16, float path" },
|
{ INPUT_SC16, 0, convert_sc16_nodc, "SC16, float path, no DC", NULL },
|
||||||
{ INPUT_SC16Q11, 1, convert_sc16q11_generic, "SC16Q11, float path" },
|
{ INPUT_SC16, 1, convert_sc16_generic, "SC16, float path", NULL },
|
||||||
{ 0, 0, NULL, NULL }
|
#if defined(SC16Q11_TABLE_BITS)
|
||||||
|
{ INPUT_SC16Q11, 0, convert_sc16q11_table, "SC16Q11, integer/table path", init_sc16q11_lookup },
|
||||||
|
#else
|
||||||
|
{ INPUT_SC16Q11, 0, convert_sc16q11_nodc, "SC16Q11, float path, no DC", NULL },
|
||||||
|
#endif
|
||||||
|
{ INPUT_SC16Q11, 1, convert_sc16q11_generic, "SC16Q11, float path", NULL },
|
||||||
|
{ 0, 0, NULL, NULL, NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
iq_convert_fn init_converter(input_format_t format,
|
iq_convert_fn init_converter(input_format_t format,
|
||||||
|
@ -291,7 +468,10 @@ iq_convert_fn init_converter(input_format_t format,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "Using sample converter: %s\n", converters_table[i].description);
|
if (converters_table[i].init) {
|
||||||
|
if (!converters_table[i].init())
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
*out_state = malloc(sizeof(struct converter_state));
|
*out_state = malloc(sizeof(struct converter_state));
|
||||||
if (! *out_state) {
|
if (! *out_state) {
|
||||||
|
|
148
convert_benchmark.c
Normal file
148
convert_benchmark.c
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// convert_benchmark.c: benchmarks for IQ sample converters
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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 "dump1090.h"
|
||||||
|
|
||||||
|
static void **testdata_uc8;
|
||||||
|
static void **testdata_sc16;
|
||||||
|
static void **testdata_sc16q11;
|
||||||
|
static uint16_t *outdata;
|
||||||
|
|
||||||
|
// SC16Q11_TABLE_BITS notes:
|
||||||
|
|
||||||
|
// 11 bits (8MB) gives you full precision, but a large table that doesn't fit in cache
|
||||||
|
// 9 bits (512kB) will fit in the Pi 2/3's shared L2 cache
|
||||||
|
// (but there will be contention from other cores)
|
||||||
|
// 8 bits (128kB) will fit in the Pi 1's L2 cache
|
||||||
|
// 7 bits (32kB) will fit in the Pi 1/2/3's L1 cache
|
||||||
|
|
||||||
|
// Sample results for "SC16Q11, no DC":
|
||||||
|
|
||||||
|
// Core i7-3610QM @ 2300MHz
|
||||||
|
// SC16Q11_TABLE_BITS undefined: 152.80M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=11: 101.22M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=9: 243.04M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=8: 316.84M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=7: 375.70M samples/second
|
||||||
|
|
||||||
|
// Pi3B @ 1200MHz
|
||||||
|
// SC16Q11_TABLE_BITS undefined: 22.19M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=11: 5.86M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=9: 19.33M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=8: 33.50M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=7: 59.78M samples/second
|
||||||
|
|
||||||
|
// Pi1B @ 700MHz
|
||||||
|
// SC16Q11_TABLE_BITS undefined: 5.24M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=11: 2.53M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=9: 3.23M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=8: 5.77M samples/second
|
||||||
|
// SC16Q11_TABLE_BITS=7: 10.23M samples/second
|
||||||
|
|
||||||
|
void prepare()
|
||||||
|
{
|
||||||
|
srand(1);
|
||||||
|
|
||||||
|
testdata_uc8 = calloc(10, sizeof(void*));
|
||||||
|
testdata_sc16 = calloc(10, sizeof(void*));
|
||||||
|
testdata_sc16q11 = calloc(10, sizeof(void*));
|
||||||
|
outdata = calloc(MODES_MAG_BUF_SAMPLES, sizeof(uint16_t));
|
||||||
|
|
||||||
|
for (int buf = 0; buf < 10; ++buf) {
|
||||||
|
uint8_t *uc8 = calloc(MODES_MAG_BUF_SAMPLES, 2);
|
||||||
|
testdata_uc8[buf] = uc8;;
|
||||||
|
uint16_t *sc16 = calloc(MODES_MAG_BUF_SAMPLES, 4);
|
||||||
|
testdata_sc16[buf] = sc16;
|
||||||
|
uint16_t *sc16q11 = calloc(MODES_MAG_BUF_SAMPLES, 4);
|
||||||
|
testdata_sc16q11[buf] = sc16q11;
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < MODES_MAG_BUF_SAMPLES; ++i) {
|
||||||
|
double I = 2.0 * rand() / (RAND_MAX + 1.0) - 1.0;
|
||||||
|
double Q = 2.0 * rand() / (RAND_MAX + 1.0) - 1.0;
|
||||||
|
|
||||||
|
uc8[i*2] = (uint8_t) (I * 128 + 128);
|
||||||
|
uc8[i*2+1] = (uint8_t) (Q * 128 + 128);
|
||||||
|
|
||||||
|
sc16[i*2] = htole16( (int16_t) (I * 32768.0) );
|
||||||
|
sc16[i*2+1] = htole16( (int16_t) (Q * 32768.0) );
|
||||||
|
|
||||||
|
sc16q11[i*2] = htole16( (int16_t) (I * 2048.0) );
|
||||||
|
sc16q11[i*2+1] = htole16( (int16_t) (Q * 2048.0) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void test(const char *what, input_format_t format, void **data, double sample_rate, bool filter_dc) {
|
||||||
|
fprintf(stderr, "Benchmarking: %s ", what);
|
||||||
|
|
||||||
|
struct converter_state *state;
|
||||||
|
iq_convert_fn converter = init_converter(format, sample_rate, filter_dc, &state);
|
||||||
|
if (!converter) {
|
||||||
|
fprintf(stderr, "Can't initialize converter\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct timespec total = { 0, 0 };
|
||||||
|
int iterations = 0;
|
||||||
|
|
||||||
|
// Run it once to force init.
|
||||||
|
converter(data[0], outdata, MODES_MAG_BUF_SAMPLES, state, NULL, NULL);
|
||||||
|
|
||||||
|
while (total.tv_sec < 5) {
|
||||||
|
fprintf(stderr, ".");
|
||||||
|
|
||||||
|
struct timespec start;
|
||||||
|
start_cpu_timing(&start);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
converter(data[i], outdata, MODES_MAG_BUF_SAMPLES, state, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
end_cpu_timing(&start, &total);
|
||||||
|
iterations++;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "\n");
|
||||||
|
cleanup_converter(state);
|
||||||
|
|
||||||
|
double samples = 10.0 * iterations * MODES_MAG_BUF_SAMPLES;
|
||||||
|
double nanos = total.tv_sec * 1e9 + total.tv_nsec;
|
||||||
|
fprintf(stderr, " %.2fM samples in %.6f seconds\n",
|
||||||
|
samples / 1e6, nanos / 1e9);
|
||||||
|
fprintf(stderr, " %.2fM samples/second\n",
|
||||||
|
samples / nanos * 1e3);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
MODES_NOTUSED(argc);
|
||||||
|
MODES_NOTUSED(argv);
|
||||||
|
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
test("SC16Q11, DC", INPUT_SC16Q11, testdata_sc16q11, 2400000, true);
|
||||||
|
test("SC16Q11, no DC", INPUT_SC16Q11, testdata_sc16q11, 2400000, false);
|
||||||
|
|
||||||
|
test("UC8, DC", INPUT_UC8, testdata_uc8, 2400000, true);
|
||||||
|
test("UC8, no DC", INPUT_UC8, testdata_uc8, 2400000, false);
|
||||||
|
|
||||||
|
test("SC16, DC", INPUT_SC16, testdata_sc16, 2400000, true);
|
||||||
|
test("SC16, no DC", INPUT_SC16, testdata_sc16, 2400000, false);
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ install_rtlsdr:
|
||||||
cd rtl-sdr-build && cmake ../rtl-sdr -DCMAKE_INSTALL_PREFIX=./install && make install
|
cd rtl-sdr-build && cmake ../rtl-sdr -DCMAKE_INSTALL_PREFIX=./install && make install
|
||||||
|
|
||||||
override_dh_auto_build: install_rtlsdr
|
override_dh_auto_build: install_rtlsdr
|
||||||
dh_auto_build -- DIALECT=-std=gnu1x HTMLPATH=/usr/share/$(SRCNAME)/html DUMP1090_VERSION=$(DUMP1090_VERSION) STATIC=yes RTLSDR_PREFIX=rtl-sdr-build/install
|
dh_auto_build -- RTLSDR=yes BLADERF=no DIALECT=-std=gnu1x HTMLPATH=/usr/share/$(SRCNAME)/html DUMP1090_VERSION=$(DUMP1090_VERSION) STATIC=yes RTLSDR_PREFIX=rtl-sdr-build/install
|
||||||
|
|
||||||
override_dh_auto_test:
|
override_dh_auto_test:
|
||||||
dh_auto_test -- DIALECT=-std=gnu1x
|
dh_auto_test -- DIALECT=-std=gnu1x
|
||||||
|
|
8
debian/changelog
vendored
8
debian/changelog
vendored
|
@ -1,3 +1,11 @@
|
||||||
|
dump1090-fa (3.4.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* Add bladeRF support.
|
||||||
|
* Clean up the FlightAware photos link so it works for registrations containing
|
||||||
|
non-alphanumeric characters.
|
||||||
|
|
||||||
|
-- Oliver Jowett <oliver@mutability.co.uk> Thu, 09 Feb 2017 17:41:43 +0000
|
||||||
|
|
||||||
dump1090-fa (3.3.0) stable; urgency=medium
|
dump1090-fa (3.3.0) stable; urgency=medium
|
||||||
|
|
||||||
* Improvements to the Mode A/C demodulator.
|
* Improvements to the Mode A/C demodulator.
|
||||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -2,7 +2,7 @@ Source: dump1090-fa
|
||||||
Section: embedded
|
Section: embedded
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Oliver Jowett <oliver@mutability.co.uk>
|
Maintainer: Oliver Jowett <oliver@mutability.co.uk>
|
||||||
Build-Depends: debhelper(>=9), librtlsdr-dev, libusb-1.0-0-dev, pkg-config, dh-systemd, libncurses5-dev
|
Build-Depends: debhelper(>=9), librtlsdr-dev, libusb-1.0-0-dev, pkg-config, dh-systemd, libncurses5-dev, libbladerf-dev
|
||||||
Standards-Version: 3.9.3
|
Standards-Version: 3.9.3
|
||||||
Homepage: https://github.com/mutability/dump1090
|
Homepage: https://github.com/mutability/dump1090
|
||||||
Vcs-Git: https://github.com/mutability/dump1090.git
|
Vcs-Git: https://github.com/mutability/dump1090.git
|
||||||
|
@ -19,7 +19,7 @@ Description: transitional dummy package for dump1090
|
||||||
|
|
||||||
Package: dump1090-fa
|
Package: dump1090-fa
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, lighttpd
|
Depends: ${shlibs:Depends}, ${misc:Depends}, libbladerf1(>=2016.06), libbladerf-udev, adduser, lighttpd
|
||||||
Replaces: dump1090 (<< 3.0)
|
Replaces: dump1090 (<< 3.0)
|
||||||
Breaks: dump1090 (<< 3.0)
|
Breaks: dump1090 (<< 3.0)
|
||||||
Description: ADS-B Ground Station System for RTL-SDR
|
Description: ADS-B Ground Station System for RTL-SDR
|
||||||
|
|
1
debian/dump1090-fa.install
vendored
1
debian/dump1090-fa.install
vendored
|
@ -1,2 +1,3 @@
|
||||||
public_html/* usr/share/dump1090-fa/html
|
public_html/* usr/share/dump1090-fa/html
|
||||||
debian/lighttpd/* etc/lighttpd/conf-available
|
debian/lighttpd/* etc/lighttpd/conf-available
|
||||||
|
bladerf/* /usr/share/dump1090-fa/bladerf
|
||||||
|
|
3
debian/dump1090-fa.postinst
vendored
3
debian/dump1090-fa.postinst
vendored
|
@ -31,6 +31,9 @@ case "$1" in
|
||||||
adduser --system --home /usr/share/$NAME --no-create-home --quiet "$RUNAS"
|
adduser --system --home /usr/share/$NAME --no-create-home --quiet "$RUNAS"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# plugdev required for bladeRF USB access
|
||||||
|
adduser "$RUNAS" plugdev
|
||||||
|
|
||||||
# set up lighttpd
|
# set up lighttpd
|
||||||
echo "Enabling lighttpd integration.." >&2
|
echo "Enabling lighttpd integration.." >&2
|
||||||
lighty-enable-mod dump1090-fa || true
|
lighty-enable-mod dump1090-fa || true
|
||||||
|
|
10
debian/rules
vendored
10
debian/rules
vendored
|
@ -12,13 +12,15 @@ export DH_VERBOSE=1
|
||||||
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
|
||||||
|
|
||||||
DPKG_EXPORT_BUILDFLAGS = 1
|
DPKG_EXPORT_BUILDFLAGS = 1
|
||||||
include /usr/share/dpkg/buildflags.mk
|
include /usr/share/dpkg/default.mk
|
||||||
|
|
||||||
DUMP1090_VERSION=$(shell dpkg-parsechangelog | sed -n 's/^Version: /v/p')
|
ifeq ($(DEB_HOST_ARCH),armhf)
|
||||||
SRCNAME=$(shell dpkg-parsechangelog | sed -n 's/^Source: //p')
|
# Assume a Pi-like target, where using an 8-bit table is a fairly big win over the float path
|
||||||
|
CPPFLAGS += -DSC16Q11_TABLE_BITS=8
|
||||||
|
endif
|
||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
dh_auto_build -- HTMLPATH=/usr/share/$(SRCNAME)/html DUMP1090_VERSION=$(DUMP1090_VERSION) RTLSDR_PREFIX=/usr
|
dh_auto_build -- RTLSDR=yes BLADERF=yes HTMLPATH=/usr/share/$(DEB_SOURCE)/html DUMP1090_VERSION=$(DEB_VERSION)
|
||||||
|
|
||||||
override_dh_install:
|
override_dh_install:
|
||||||
dh_install
|
dh_install
|
||||||
|
|
571
dump1090.c
571
dump1090.c
|
@ -49,12 +49,8 @@
|
||||||
|
|
||||||
#include "dump1090.h"
|
#include "dump1090.h"
|
||||||
|
|
||||||
#include <rtl-sdr.h>
|
|
||||||
|
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
|
||||||
static int verbose_device_search(char *s);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// ============================= Utility functions ==========================
|
// ============================= Utility functions ==========================
|
||||||
//
|
//
|
||||||
|
@ -96,21 +92,6 @@ static void sigtermHandler(int dummy) {
|
||||||
log_with_timestamp("Caught SIGTERM, shutting down..\n");
|
log_with_timestamp("Caught SIGTERM, shutting down..\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void start_cpu_timing(struct timespec *start_time)
|
|
||||||
{
|
|
||||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, start_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void end_cpu_timing(const struct timespec *start_time, struct timespec *add_to)
|
|
||||||
{
|
|
||||||
struct timespec end_time;
|
|
||||||
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time);
|
|
||||||
add_to->tv_sec += (end_time.tv_sec - start_time->tv_sec - 1);
|
|
||||||
add_to->tv_nsec += (1000000000L + end_time.tv_nsec - start_time->tv_nsec);
|
|
||||||
add_to->tv_sec += add_to->tv_nsec / 1000000000L;
|
|
||||||
add_to->tv_nsec = add_to->tv_nsec % 1000000000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
void receiverPositionChanged(float lat, float lon, float alt)
|
void receiverPositionChanged(float lat, float lon, float alt)
|
||||||
{
|
{
|
||||||
log_with_timestamp("Autodetected receiver location: %.5f, %.5f at %.0fm AMSL", lat, lon, alt);
|
log_with_timestamp("Autodetected receiver location: %.5f, %.5f at %.0fm AMSL", lat, lon, alt);
|
||||||
|
@ -128,7 +109,6 @@ void modesInitConfig(void) {
|
||||||
// Now initialise things that should not be 0/NULL to their defaults
|
// Now initialise things that should not be 0/NULL to their defaults
|
||||||
Modes.gain = MODES_MAX_GAIN;
|
Modes.gain = MODES_MAX_GAIN;
|
||||||
Modes.freq = MODES_DEFAULT_FREQ;
|
Modes.freq = MODES_DEFAULT_FREQ;
|
||||||
Modes.ppm_error = MODES_DEFAULT_PPM;
|
|
||||||
Modes.check_crc = 1;
|
Modes.check_crc = 1;
|
||||||
Modes.net_heartbeat_interval = MODES_NET_HEARTBEAT_INTERVAL;
|
Modes.net_heartbeat_interval = MODES_NET_HEARTBEAT_INTERVAL;
|
||||||
Modes.net_input_raw_ports = strdup("30001");
|
Modes.net_input_raw_ports = strdup("30001");
|
||||||
|
@ -142,12 +122,14 @@ void modesInitConfig(void) {
|
||||||
Modes.json_location_accuracy = 1;
|
Modes.json_location_accuracy = 1;
|
||||||
Modes.maxRange = 1852 * 300; // 300NM default max range
|
Modes.maxRange = 1852 * 300; // 300NM default max range
|
||||||
Modes.mode_ac_auto = 1;
|
Modes.mode_ac_auto = 1;
|
||||||
|
|
||||||
|
sdrInitConfig();
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
//
|
//
|
||||||
void modesInit(void) {
|
void modesInit(void) {
|
||||||
int i, q;
|
int i;
|
||||||
|
|
||||||
pthread_mutex_init(&Modes.data_mutex,NULL);
|
pthread_mutex_init(&Modes.data_mutex,NULL);
|
||||||
pthread_cond_init(&Modes.data_cond,NULL);
|
pthread_cond_init(&Modes.data_cond,NULL);
|
||||||
|
@ -157,8 +139,7 @@ void modesInit(void) {
|
||||||
// Allocate the various buffers used by Modes
|
// Allocate the various buffers used by Modes
|
||||||
Modes.trailing_samples = (MODES_PREAMBLE_US + MODES_LONG_MSG_BITS + 16) * 1e-6 * Modes.sample_rate;
|
Modes.trailing_samples = (MODES_PREAMBLE_US + MODES_LONG_MSG_BITS + 16) * 1e-6 * Modes.sample_rate;
|
||||||
|
|
||||||
if ( ((Modes.maglut = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256) ) == NULL) ||
|
if ( ((Modes.log10lut = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256) ) == NULL) )
|
||||||
((Modes.log10lut = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256) ) == NULL) )
|
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Out of memory allocating data buffer.\n");
|
fprintf(stderr, "Out of memory allocating data buffer.\n");
|
||||||
exit(1);
|
exit(1);
|
||||||
|
@ -202,21 +183,6 @@ void modesInit(void) {
|
||||||
if (Modes.net_sndbuf_size > (MODES_NET_SNDBUF_MAX))
|
if (Modes.net_sndbuf_size > (MODES_NET_SNDBUF_MAX))
|
||||||
{Modes.net_sndbuf_size = MODES_NET_SNDBUF_MAX;}
|
{Modes.net_sndbuf_size = MODES_NET_SNDBUF_MAX;}
|
||||||
|
|
||||||
// compute UC8 magnitude lookup table
|
|
||||||
for (i = 0; i <= 255; i++) {
|
|
||||||
for (q = 0; q <= 255; q++) {
|
|
||||||
float fI, fQ, magsq;
|
|
||||||
|
|
||||||
fI = (i - 127.5) / 127.5;
|
|
||||||
fQ = (q - 127.5) / 127.5;
|
|
||||||
magsq = fI * fI + fQ * fQ;
|
|
||||||
if (magsq > 1)
|
|
||||||
magsq = 1;
|
|
||||||
|
|
||||||
Modes.maglut[le16toh((i*256)+q)] = (uint16_t) round(sqrtf(magsq) * 65535.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare the log10 lookup table: 100log10(x)
|
// Prepare the log10 lookup table: 100log10(x)
|
||||||
Modes.log10lut[0] = 0; // poorly defined..
|
Modes.log10lut[0] = 0; // poorly defined..
|
||||||
for (i = 1; i <= 65535; i++) {
|
for (i = 1; i <= 65535; i++) {
|
||||||
|
@ -227,138 +193,11 @@ void modesInit(void) {
|
||||||
modesChecksumInit(Modes.nfix_crc);
|
modesChecksumInit(Modes.nfix_crc);
|
||||||
icaoFilterInit();
|
icaoFilterInit();
|
||||||
modeACInit();
|
modeACInit();
|
||||||
interactiveInit();
|
|
||||||
|
|
||||||
if (Modes.show_only)
|
if (Modes.show_only)
|
||||||
icaoFilterAdd(Modes.show_only);
|
icaoFilterAdd(Modes.show_only);
|
||||||
|
|
||||||
// Prepare sample conversion
|
|
||||||
if (!Modes.net_only) {
|
|
||||||
if (Modes.filename == NULL) // using a real RTLSDR, use UC8 input always
|
|
||||||
Modes.input_format = INPUT_UC8;
|
|
||||||
|
|
||||||
Modes.converter_function = init_converter(Modes.input_format,
|
|
||||||
Modes.sample_rate,
|
|
||||||
Modes.dc_filter,
|
|
||||||
&Modes.converter_state);
|
|
||||||
if (!Modes.converter_function) {
|
|
||||||
fprintf(stderr, "Can't initialize sample converter, giving up.\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void convert_samples(void *iq,
|
|
||||||
uint16_t *mag,
|
|
||||||
unsigned nsamples,
|
|
||||||
double *mean_level,
|
|
||||||
double *mean_power)
|
|
||||||
{
|
|
||||||
Modes.converter_function(iq, mag, nsamples, Modes.converter_state, mean_level, mean_power);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// =============================== RTLSDR handling ==========================
|
|
||||||
//
|
|
||||||
int modesInitRTLSDR(void) {
|
|
||||||
int j;
|
|
||||||
int device_count, dev_index = 0;
|
|
||||||
char vendor[256], product[256], serial[256];
|
|
||||||
|
|
||||||
if (Modes.dev_name) {
|
|
||||||
if ( (dev_index = verbose_device_search(Modes.dev_name)) < 0 )
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
device_count = rtlsdr_get_device_count();
|
|
||||||
if (!device_count) {
|
|
||||||
fprintf(stderr, "No supported RTLSDR devices found.\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fprintf(stderr, "Found %d device(s):\n", device_count);
|
|
||||||
for (j = 0; j < device_count; j++) {
|
|
||||||
if (rtlsdr_get_device_usb_strings(j, vendor, product, serial) != 0) {
|
|
||||||
fprintf(stderr, "%d: unable to read device details\n", j);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "%d: %s, %s, SN: %s %s\n", j, vendor, product, serial,
|
|
||||||
(j == dev_index) ? "(currently selected)" : "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rtlsdr_open(&Modes.dev, dev_index) < 0) {
|
|
||||||
fprintf(stderr, "Error opening the RTLSDR device: %s\n",
|
|
||||||
strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set gain, frequency, sample rate, and reset the device
|
|
||||||
rtlsdr_set_tuner_gain_mode(Modes.dev,
|
|
||||||
(Modes.gain == MODES_AUTO_GAIN) ? 0 : 1);
|
|
||||||
if (Modes.gain != MODES_AUTO_GAIN) {
|
|
||||||
int *gains;
|
|
||||||
int numgains;
|
|
||||||
|
|
||||||
numgains = rtlsdr_get_tuner_gains(Modes.dev, NULL);
|
|
||||||
if (numgains <= 0) {
|
|
||||||
fprintf(stderr, "Error getting tuner gains\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
gains = malloc(numgains * sizeof(int));
|
|
||||||
if (rtlsdr_get_tuner_gains(Modes.dev, gains) != numgains) {
|
|
||||||
fprintf(stderr, "Error getting tuner gains\n");
|
|
||||||
free(gains);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Modes.gain == MODES_MAX_GAIN) {
|
|
||||||
int highest = -1;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < numgains; ++i) {
|
|
||||||
if (gains[i] > highest)
|
|
||||||
highest = gains[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
Modes.gain = highest;
|
|
||||||
fprintf(stderr, "Max available gain is: %.2f dB\n", Modes.gain/10.0);
|
|
||||||
} else {
|
|
||||||
int closest = -1;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < numgains; ++i) {
|
|
||||||
if (closest == -1 || abs(gains[i] - Modes.gain) < abs(closest - Modes.gain))
|
|
||||||
closest = gains[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (closest != Modes.gain) {
|
|
||||||
Modes.gain = closest;
|
|
||||||
fprintf(stderr, "Closest available gain: %.2f dB\n", Modes.gain/10.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(gains);
|
|
||||||
|
|
||||||
fprintf(stderr, "Setting gain to: %.2f dB\n", Modes.gain/10.0);
|
|
||||||
if (rtlsdr_set_tuner_gain(Modes.dev, Modes.gain) < 0) {
|
|
||||||
fprintf(stderr, "Error setting tuner gains\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Using automatic gain control.\n");
|
|
||||||
}
|
|
||||||
rtlsdr_set_freq_correction(Modes.dev, Modes.ppm_error);
|
|
||||||
if (Modes.enable_agc) rtlsdr_set_agc_mode(Modes.dev, 1);
|
|
||||||
rtlsdr_set_center_freq(Modes.dev, Modes.freq);
|
|
||||||
rtlsdr_set_sample_rate(Modes.dev, (unsigned)Modes.sample_rate);
|
|
||||||
|
|
||||||
rtlsdr_reset_buffer(Modes.dev);
|
|
||||||
fprintf(stderr, "Gain reported by device: %.2f dB\n",
|
|
||||||
rtlsdr_get_tuner_gain(Modes.dev)/10.0);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
//
|
//
|
||||||
|
@ -371,208 +210,6 @@ int modesInitRTLSDR(void) {
|
||||||
// A Mutex is used to avoid races with the decoding thread.
|
// A Mutex is used to avoid races with the decoding thread.
|
||||||
//
|
//
|
||||||
|
|
||||||
static struct timespec reader_thread_start;
|
|
||||||
|
|
||||||
void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
|
|
||||||
struct mag_buf *outbuf;
|
|
||||||
struct mag_buf *lastbuf;
|
|
||||||
uint32_t slen;
|
|
||||||
unsigned next_free_buffer;
|
|
||||||
unsigned free_bufs;
|
|
||||||
unsigned block_duration;
|
|
||||||
|
|
||||||
static int was_odd = 0; // paranoia!!
|
|
||||||
static int dropping = 0;
|
|
||||||
|
|
||||||
MODES_NOTUSED(ctx);
|
|
||||||
|
|
||||||
// Lock the data buffer variables before accessing them
|
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
|
||||||
if (Modes.exit) {
|
|
||||||
rtlsdr_cancel_async(Modes.dev); // ask our caller to exit
|
|
||||||
}
|
|
||||||
|
|
||||||
next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS;
|
|
||||||
outbuf = &Modes.mag_buffers[Modes.first_free_buffer];
|
|
||||||
lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS];
|
|
||||||
free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS;
|
|
||||||
|
|
||||||
// Paranoia! Unlikely, but let's go for belt and suspenders here
|
|
||||||
|
|
||||||
if (len != MODES_RTL_BUF_SIZE) {
|
|
||||||
fprintf(stderr, "weirdness: rtlsdr gave us a block with an unusual size (got %u bytes, expected %u bytes)\n",
|
|
||||||
(unsigned)len, (unsigned)MODES_RTL_BUF_SIZE);
|
|
||||||
|
|
||||||
if (len > MODES_RTL_BUF_SIZE) {
|
|
||||||
// wat?! Discard the start.
|
|
||||||
unsigned discard = (len - MODES_RTL_BUF_SIZE + 1) / 2;
|
|
||||||
outbuf->dropped += discard;
|
|
||||||
buf += discard*2;
|
|
||||||
len -= discard*2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (was_odd) {
|
|
||||||
// Drop a sample so we are in sync with I/Q samples again (hopefully)
|
|
||||||
++buf;
|
|
||||||
--len;
|
|
||||||
++outbuf->dropped;
|
|
||||||
}
|
|
||||||
|
|
||||||
was_odd = (len & 1);
|
|
||||||
slen = len/2;
|
|
||||||
|
|
||||||
if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS/2)) {
|
|
||||||
// FIFO is full. Drop this block.
|
|
||||||
dropping = 1;
|
|
||||||
outbuf->dropped += slen;
|
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dropping = 0;
|
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
|
||||||
|
|
||||||
// Compute the sample timestamp and system timestamp for the start of the block
|
|
||||||
outbuf->sampleTimestamp = lastbuf->sampleTimestamp + 12e6 * (lastbuf->length + outbuf->dropped) / Modes.sample_rate;
|
|
||||||
block_duration = 1e9 * slen / Modes.sample_rate;
|
|
||||||
|
|
||||||
// Get the approx system time for the start of this block
|
|
||||||
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
|
|
||||||
outbuf->sysTimestamp.tv_nsec -= block_duration;
|
|
||||||
normalize_timespec(&outbuf->sysTimestamp);
|
|
||||||
|
|
||||||
// Copy trailing data from last block (or reset if not valid)
|
|
||||||
if (outbuf->dropped == 0 && lastbuf->length >= Modes.trailing_samples) {
|
|
||||||
memcpy(outbuf->data, lastbuf->data + lastbuf->length - Modes.trailing_samples, Modes.trailing_samples * sizeof(uint16_t));
|
|
||||||
} else {
|
|
||||||
memset(outbuf->data, 0, Modes.trailing_samples * sizeof(uint16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the new data
|
|
||||||
outbuf->length = slen;
|
|
||||||
convert_samples(buf, &outbuf->data[Modes.trailing_samples], slen, &outbuf->mean_level, &outbuf->mean_power);
|
|
||||||
|
|
||||||
// Push the new data to the demodulation thread
|
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
|
||||||
|
|
||||||
Modes.mag_buffers[next_free_buffer].dropped = 0;
|
|
||||||
Modes.mag_buffers[next_free_buffer].length = 0; // just in case
|
|
||||||
Modes.first_free_buffer = next_free_buffer;
|
|
||||||
|
|
||||||
// accumulate CPU while holding the mutex, and restart measurement
|
|
||||||
end_cpu_timing(&reader_thread_start, &Modes.reader_cpu_accumulator);
|
|
||||||
start_cpu_timing(&reader_thread_start);
|
|
||||||
|
|
||||||
pthread_cond_signal(&Modes.data_cond);
|
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
|
||||||
}
|
|
||||||
//
|
|
||||||
//=========================================================================
|
|
||||||
//
|
|
||||||
// This is used when --ifile is specified in order to read data from file
|
|
||||||
// instead of using an RTLSDR device
|
|
||||||
//
|
|
||||||
void readDataFromFile(void) {
|
|
||||||
int eof = 0;
|
|
||||||
struct timespec next_buffer_delivery;
|
|
||||||
void *readbuf;
|
|
||||||
int bytes_per_sample = 0;
|
|
||||||
|
|
||||||
switch (Modes.input_format) {
|
|
||||||
case INPUT_UC8:
|
|
||||||
bytes_per_sample = 2;
|
|
||||||
break;
|
|
||||||
case INPUT_SC16:
|
|
||||||
case INPUT_SC16Q11:
|
|
||||||
bytes_per_sample = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(readbuf = malloc(MODES_MAG_BUF_SAMPLES * bytes_per_sample))) {
|
|
||||||
fprintf(stderr, "failed to allocate read buffer\n");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
clock_gettime(CLOCK_MONOTONIC, &next_buffer_delivery);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
|
||||||
while (!Modes.exit && !eof) {
|
|
||||||
ssize_t nread, toread;
|
|
||||||
void *r;
|
|
||||||
struct mag_buf *outbuf, *lastbuf;
|
|
||||||
unsigned next_free_buffer;
|
|
||||||
unsigned slen;
|
|
||||||
|
|
||||||
next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS;
|
|
||||||
if (next_free_buffer == Modes.first_filled_buffer) {
|
|
||||||
// no space for output yet
|
|
||||||
pthread_cond_wait(&Modes.data_cond, &Modes.data_mutex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
outbuf = &Modes.mag_buffers[Modes.first_free_buffer];
|
|
||||||
lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS];
|
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
|
||||||
|
|
||||||
// Compute the sample timestamp and system timestamp for the start of the block
|
|
||||||
outbuf->sampleTimestamp = lastbuf->sampleTimestamp + 12e6 * lastbuf->length / Modes.sample_rate;
|
|
||||||
|
|
||||||
// Copy trailing data from last block (or reset if not valid)
|
|
||||||
if (lastbuf->length >= Modes.trailing_samples) {
|
|
||||||
memcpy(outbuf->data, lastbuf->data + lastbuf->length - Modes.trailing_samples, Modes.trailing_samples * sizeof(uint16_t));
|
|
||||||
} else {
|
|
||||||
memset(outbuf->data, 0, Modes.trailing_samples * sizeof(uint16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the system time for the start of this block
|
|
||||||
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
|
|
||||||
|
|
||||||
toread = MODES_MAG_BUF_SAMPLES * bytes_per_sample;
|
|
||||||
r = readbuf;
|
|
||||||
while (toread) {
|
|
||||||
nread = read(Modes.fd, r, toread);
|
|
||||||
if (nread <= 0) {
|
|
||||||
// Done.
|
|
||||||
eof = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
r += nread;
|
|
||||||
toread -= nread;
|
|
||||||
}
|
|
||||||
|
|
||||||
slen = outbuf->length = MODES_MAG_BUF_SAMPLES - toread/bytes_per_sample;
|
|
||||||
|
|
||||||
// Convert the new data
|
|
||||||
convert_samples(readbuf, &outbuf->data[Modes.trailing_samples], slen, &outbuf->mean_level, &outbuf->mean_power);
|
|
||||||
|
|
||||||
if (Modes.throttle) {
|
|
||||||
// Wait until we are allowed to release this buffer to the main thread
|
|
||||||
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_buffer_delivery, NULL) == EINTR)
|
|
||||||
;
|
|
||||||
|
|
||||||
// compute the time we can deliver the next buffer.
|
|
||||||
next_buffer_delivery.tv_nsec += outbuf->length * 1e9 / Modes.sample_rate;
|
|
||||||
normalize_timespec(&next_buffer_delivery);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push the new data to the main thread
|
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
|
||||||
Modes.first_free_buffer = next_free_buffer;
|
|
||||||
// accumulate CPU while holding the mutex, and restart measurement
|
|
||||||
end_cpu_timing(&reader_thread_start, &Modes.reader_cpu_accumulator);
|
|
||||||
start_cpu_timing(&reader_thread_start);
|
|
||||||
pthread_cond_signal(&Modes.data_cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(readbuf);
|
|
||||||
|
|
||||||
// Wait for the main thread to consume all data
|
|
||||||
while (!Modes.exit && Modes.first_filled_buffer != Modes.first_free_buffer)
|
|
||||||
pthread_cond_wait(&Modes.data_cond, &Modes.data_mutex);
|
|
||||||
|
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
//
|
//
|
||||||
|
@ -580,36 +217,11 @@ void readDataFromFile(void) {
|
||||||
// without caring about data acquisition
|
// without caring about data acquisition
|
||||||
//
|
//
|
||||||
|
|
||||||
void *readerThreadEntryPoint(void *arg) {
|
void *readerThreadEntryPoint(void *arg)
|
||||||
|
{
|
||||||
MODES_NOTUSED(arg);
|
MODES_NOTUSED(arg);
|
||||||
|
|
||||||
start_cpu_timing(&reader_thread_start); // we accumulate in rtlsdrCallback() or readDataFromFile()
|
sdrRun();
|
||||||
|
|
||||||
if (Modes.filename == NULL) {
|
|
||||||
while (!Modes.exit) {
|
|
||||||
rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
|
|
||||||
MODES_RTL_BUFFERS,
|
|
||||||
MODES_RTL_BUF_SIZE);
|
|
||||||
|
|
||||||
if (!Modes.exit) {
|
|
||||||
log_with_timestamp("Warning: lost the connection to the RTLSDR device.");
|
|
||||||
rtlsdr_close(Modes.dev);
|
|
||||||
Modes.dev = NULL;
|
|
||||||
|
|
||||||
do {
|
|
||||||
sleep(5);
|
|
||||||
log_with_timestamp("Trying to reconnect to the RTLSDR device..");
|
|
||||||
} while (!Modes.exit && modesInitRTLSDR() < 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Modes.dev != NULL) {
|
|
||||||
rtlsdr_close(Modes.dev);
|
|
||||||
Modes.dev = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
readDataFromFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wake the main thread (if it's still waiting)
|
// Wake the main thread (if it's still waiting)
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
@ -648,17 +260,36 @@ void snipMode(int level) {
|
||||||
// ================================ Main ====================================
|
// ================================ Main ====================================
|
||||||
//
|
//
|
||||||
void showHelp(void) {
|
void showHelp(void) {
|
||||||
|
|
||||||
|
printf("-----------------------------------------------------------------------------\n");
|
||||||
|
printf("| dump1090 ModeS Receiver %45s |\n", MODES_DUMP1090_VARIANT " " MODES_DUMP1090_VERSION);
|
||||||
|
printf("| build options: %-58s |\n",
|
||||||
|
""
|
||||||
|
#ifdef ENABLE_RTLSDR
|
||||||
|
"ENABLE_RTLSDR "
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_BLADERF
|
||||||
|
"ENABLE_BLADERF "
|
||||||
|
#endif
|
||||||
|
#ifdef SC16Q11_TABLE_BITS
|
||||||
|
// This is a little silly, but that's how the preprocessor works..
|
||||||
|
#define _stringize(x) #x
|
||||||
|
#define stringize(x) _stringize(x)
|
||||||
|
"SC16Q11_TABLE_BITS=" stringize(SC16Q11_TABLE_BITS)
|
||||||
|
#undef stringize
|
||||||
|
#undef _stringize
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
printf("-----------------------------------------------------------------------------\n");
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
sdrShowHelp();
|
||||||
|
|
||||||
printf(
|
printf(
|
||||||
"-----------------------------------------------------------------------------\n"
|
" Common options\n"
|
||||||
"| dump1090 ModeS Receiver %45s |\n"
|
"\n"
|
||||||
"-----------------------------------------------------------------------------\n"
|
|
||||||
"--device-index <index> Select RTL device (default: 0)\n"
|
|
||||||
"--gain <db> Set gain (default: max gain. Use -10 for auto-gain)\n"
|
"--gain <db> Set gain (default: max gain. Use -10 for auto-gain)\n"
|
||||||
"--enable-agc Enable the Automatic Gain Control (default: off)\n"
|
|
||||||
"--freq <hz> Set frequency (default: 1090 Mhz)\n"
|
"--freq <hz> Set frequency (default: 1090 Mhz)\n"
|
||||||
"--ifile <filename> Read data from file (use '-' for stdin)\n"
|
|
||||||
"--iformat <format> Sample format for --ifile: UC8 (default), SC16, or SC16Q11\n"
|
|
||||||
"--throttle When reading from a file, play back in realtime, not at max speed\n"
|
|
||||||
"--interactive Interactive mode refreshing data on screen. Implies --throttle\n"
|
"--interactive Interactive mode refreshing data on screen. Implies --throttle\n"
|
||||||
"--interactive-ttl <sec> Remove from list if idle for <sec> (default: 60)\n"
|
"--interactive-ttl <sec> Remove from list if idle for <sec> (default: 60)\n"
|
||||||
"--raw Show only messages hex values\n"
|
"--raw Show only messages hex values\n"
|
||||||
|
@ -698,12 +329,11 @@ void showHelp(void) {
|
||||||
"--debug <flags> Debug mode (verbose), see README for details\n"
|
"--debug <flags> Debug mode (verbose), see README for details\n"
|
||||||
"--quiet Disable output to stdout. Use for daemon applications\n"
|
"--quiet Disable output to stdout. Use for daemon applications\n"
|
||||||
"--show-only <addr> Show only messages from the given ICAO on stdout\n"
|
"--show-only <addr> Show only messages from the given ICAO on stdout\n"
|
||||||
"--ppm <error> Set receiver error in parts per million (default 0)\n"
|
|
||||||
"--html-dir <dir> Use <dir> as base directory for the internal HTTP server. Defaults to " HTMLPATH "\n"
|
"--html-dir <dir> Use <dir> as base directory for the internal HTTP server. Defaults to " HTMLPATH "\n"
|
||||||
"--write-json <dir> Periodically write json output to <dir> (for serving by a separate webserver)\n"
|
"--write-json <dir> Periodically write json output to <dir> (for serving by a separate webserver)\n"
|
||||||
"--write-json-every <t> Write json output every t seconds (default 1)\n"
|
"--write-json-every <t> Write json output every t seconds (default 1)\n"
|
||||||
"--json-location-accuracy <n> Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n"
|
"--json-location-accuracy <n> Accuracy of receiver location in json metadata: 0=no location, 1=approximate, 2=exact\n"
|
||||||
"--dcfilter Apply a 1Hz DC filter to input data (requires lots more CPU)\n"
|
"--dcfilter Apply a 1Hz DC filter to input data (requires more CPU)\n"
|
||||||
"--help Show this help\n"
|
"--help Show this help\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Debug mode flags: d = Log frames decoded with errors\n"
|
"Debug mode flags: d = Log frames decoded with errors\n"
|
||||||
|
@ -712,8 +342,7 @@ void showHelp(void) {
|
||||||
" C = Log frames with good CRC\n"
|
" C = Log frames with good CRC\n"
|
||||||
" p = Log frames with bad preamble\n"
|
" p = Log frames with bad preamble\n"
|
||||||
" n = Log network debugging info\n"
|
" n = Log network debugging info\n"
|
||||||
" j = Log frames to frames.js, loadable by debug.html\n",
|
" j = Log frames to frames.js, loadable by debug.html\n"
|
||||||
MODES_DUMP1090_VARIANT " " MODES_DUMP1090_VERSION
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,68 +459,6 @@ void backgroundTasks(void) {
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
//
|
//
|
||||||
int verbose_device_search(char *s)
|
|
||||||
{
|
|
||||||
int i, device_count, device, offset;
|
|
||||||
char *s2;
|
|
||||||
char vendor[256], product[256], serial[256];
|
|
||||||
device_count = rtlsdr_get_device_count();
|
|
||||||
if (!device_count) {
|
|
||||||
fprintf(stderr, "No supported devices found.\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "Found %d device(s):\n", device_count);
|
|
||||||
for (i = 0; i < device_count; i++) {
|
|
||||||
if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0) {
|
|
||||||
fprintf(stderr, " %d: unable to read device details\n", i);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fprintf(stderr, "\n");
|
|
||||||
/* does string look like raw id number */
|
|
||||||
device = (int)strtol(s, &s2, 0);
|
|
||||||
if (s2[0] == '\0' && device >= 0 && device < device_count) {
|
|
||||||
fprintf(stderr, "Using device %d: %s\n",
|
|
||||||
device, rtlsdr_get_device_name((uint32_t)device));
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
/* does string exact match a serial */
|
|
||||||
for (i = 0; i < device_count; i++) {
|
|
||||||
rtlsdr_get_device_usb_strings(i, vendor, product, serial);
|
|
||||||
if (strcmp(s, serial) != 0) {
|
|
||||||
continue;}
|
|
||||||
device = i;
|
|
||||||
fprintf(stderr, "Using device %d: %s\n",
|
|
||||||
device, rtlsdr_get_device_name((uint32_t)device));
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
/* does string prefix match a serial */
|
|
||||||
for (i = 0; i < device_count; i++) {
|
|
||||||
rtlsdr_get_device_usb_strings(i, vendor, product, serial);
|
|
||||||
if (strncmp(s, serial, strlen(s)) != 0) {
|
|
||||||
continue;}
|
|
||||||
device = i;
|
|
||||||
fprintf(stderr, "Using device %d: %s\n",
|
|
||||||
device, rtlsdr_get_device_name((uint32_t)device));
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
/* does string suffix match a serial */
|
|
||||||
for (i = 0; i < device_count; i++) {
|
|
||||||
rtlsdr_get_device_usb_strings(i, vendor, product, serial);
|
|
||||||
offset = strlen(serial) - strlen(s);
|
|
||||||
if (offset < 0) {
|
|
||||||
continue;}
|
|
||||||
if (strncmp(s, serial+offset, strlen(s)) != 0) {
|
|
||||||
continue;}
|
|
||||||
device = i;
|
|
||||||
fprintf(stderr, "Using device %d: %s\n",
|
|
||||||
device, rtlsdr_get_device_name((uint32_t)device));
|
|
||||||
return device;
|
|
||||||
}
|
|
||||||
fprintf(stderr, "No matching devices found.\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
//
|
//
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
//
|
//
|
||||||
|
@ -909,29 +476,12 @@ int main(int argc, char **argv) {
|
||||||
for (j = 1; j < argc; j++) {
|
for (j = 1; j < argc; j++) {
|
||||||
int more = j+1 < argc; // There are more arguments
|
int more = j+1 < argc; // There are more arguments
|
||||||
|
|
||||||
if (!strcmp(argv[j],"--device-index") && more) {
|
if (!strcmp(argv[j],"--freq") && more) {
|
||||||
|
Modes.freq = (int) strtoll(argv[++j],NULL,10);
|
||||||
|
} else if ( (!strcmp(argv[j], "--device") || !strcmp(argv[j], "--device-index")) && more) {
|
||||||
Modes.dev_name = strdup(argv[++j]);
|
Modes.dev_name = strdup(argv[++j]);
|
||||||
} else if (!strcmp(argv[j],"--gain") && more) {
|
} else if (!strcmp(argv[j],"--gain") && more) {
|
||||||
Modes.gain = (int) (atof(argv[++j])*10); // Gain is in tens of DBs
|
Modes.gain = (int) (atof(argv[++j])*10); // Gain is in tens of DBs
|
||||||
} else if (!strcmp(argv[j],"--enable-agc")) {
|
|
||||||
Modes.enable_agc++;
|
|
||||||
} else if (!strcmp(argv[j],"--freq") && more) {
|
|
||||||
Modes.freq = (int) strtoll(argv[++j],NULL,10);
|
|
||||||
} else if (!strcmp(argv[j],"--ifile") && more) {
|
|
||||||
Modes.filename = strdup(argv[++j]);
|
|
||||||
} else if (!strcmp(argv[j],"--iformat") && more) {
|
|
||||||
++j;
|
|
||||||
if (!strcasecmp(argv[j], "uc8")) {
|
|
||||||
Modes.input_format = INPUT_UC8;
|
|
||||||
} else if (!strcasecmp(argv[j], "sc16")) {
|
|
||||||
Modes.input_format = INPUT_SC16;
|
|
||||||
} else if (!strcasecmp(argv[j], "sc16q11")) {
|
|
||||||
Modes.input_format = INPUT_SC16Q11;
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Input format '%s' not understood (supported values: UC8, SC16, SC16Q11)\n",
|
|
||||||
argv[j]);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
} else if (!strcmp(argv[j],"--dcfilter")) {
|
} else if (!strcmp(argv[j],"--dcfilter")) {
|
||||||
Modes.dc_filter = 1;
|
Modes.dc_filter = 1;
|
||||||
} else if (!strcmp(argv[j],"--measure-noise")) {
|
} else if (!strcmp(argv[j],"--measure-noise")) {
|
||||||
|
@ -957,7 +507,7 @@ int main(int argc, char **argv) {
|
||||||
fprintf(stderr, "--net-beast ignored, use --net-bo-port to control where Beast output is generated\n");
|
fprintf(stderr, "--net-beast ignored, use --net-bo-port to control where Beast output is generated\n");
|
||||||
} else if (!strcmp(argv[j],"--net-only")) {
|
} else if (!strcmp(argv[j],"--net-only")) {
|
||||||
Modes.net = 1;
|
Modes.net = 1;
|
||||||
Modes.net_only = 1;
|
Modes.sdr_type = SDR_NONE;
|
||||||
} else if (!strcmp(argv[j],"--net-heartbeat") && more) {
|
} else if (!strcmp(argv[j],"--net-heartbeat") && more) {
|
||||||
Modes.net_heartbeat_interval = (uint64_t)(1000 * atof(argv[++j]));
|
Modes.net_heartbeat_interval = (uint64_t)(1000 * atof(argv[++j]));
|
||||||
} else if (!strcmp(argv[j],"--net-ro-size") && more) {
|
} else if (!strcmp(argv[j],"--net-ro-size") && more) {
|
||||||
|
@ -1007,9 +557,7 @@ int main(int argc, char **argv) {
|
||||||
fprintf(stderr, "warning: --aggressive not supported in this build, option ignored.\n");
|
fprintf(stderr, "warning: --aggressive not supported in this build, option ignored.\n");
|
||||||
#endif
|
#endif
|
||||||
} else if (!strcmp(argv[j],"--interactive")) {
|
} else if (!strcmp(argv[j],"--interactive")) {
|
||||||
Modes.interactive = Modes.throttle = 1;
|
Modes.interactive = 1;
|
||||||
} else if (!strcmp(argv[j],"--throttle")) {
|
|
||||||
Modes.throttle = 1;
|
|
||||||
} else if (!strcmp(argv[j],"--interactive-ttl") && more) {
|
} else if (!strcmp(argv[j],"--interactive-ttl") && more) {
|
||||||
Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j]));
|
Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j]));
|
||||||
} else if (!strcmp(argv[j],"--lat") && more) {
|
} else if (!strcmp(argv[j],"--lat") && more) {
|
||||||
|
@ -1049,8 +597,6 @@ int main(int argc, char **argv) {
|
||||||
} else if (!strcmp(argv[j],"--help")) {
|
} else if (!strcmp(argv[j],"--help")) {
|
||||||
showHelp();
|
showHelp();
|
||||||
exit(0);
|
exit(0);
|
||||||
} else if (!strcmp(argv[j],"--ppm") && more) {
|
|
||||||
Modes.ppm_error = atoi(argv[++j]);
|
|
||||||
} else if (!strcmp(argv[j],"--quiet")) {
|
} else if (!strcmp(argv[j],"--quiet")) {
|
||||||
Modes.quiet = 1;
|
Modes.quiet = 1;
|
||||||
} else if (!strcmp(argv[j],"--show-only") && more) {
|
} else if (!strcmp(argv[j],"--show-only") && more) {
|
||||||
|
@ -1071,6 +617,8 @@ int main(int argc, char **argv) {
|
||||||
} else if (!strcmp(argv[j], "--json-location-accuracy") && more) {
|
} else if (!strcmp(argv[j], "--json-location-accuracy") && more) {
|
||||||
Modes.json_location_accuracy = atoi(argv[++j]);
|
Modes.json_location_accuracy = atoi(argv[++j]);
|
||||||
#endif
|
#endif
|
||||||
|
} else if (sdrHandleOption(argc, argv, &j)) {
|
||||||
|
/* handled */
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"Unknown or not enough arguments for option '%s'.\n\n",
|
"Unknown or not enough arguments for option '%s'.\n\n",
|
||||||
|
@ -1089,27 +637,13 @@ int main(int argc, char **argv) {
|
||||||
log_with_timestamp("%s %s starting up.", MODES_DUMP1090_VARIANT, MODES_DUMP1090_VERSION);
|
log_with_timestamp("%s %s starting up.", MODES_DUMP1090_VARIANT, MODES_DUMP1090_VERSION);
|
||||||
modesInit();
|
modesInit();
|
||||||
|
|
||||||
if (Modes.net_only) {
|
if (!sdrOpen()) {
|
||||||
fprintf(stderr,"Net-only mode, no RTL device or file open.\n");
|
|
||||||
} else if (Modes.filename == NULL) {
|
|
||||||
if (modesInitRTLSDR() < 0) {
|
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
|
if (Modes.net) {
|
||||||
Modes.fd = STDIN_FILENO;
|
modesInitNet();
|
||||||
} else if ((Modes.fd = open(Modes.filename,
|
|
||||||
#ifdef _WIN32
|
|
||||||
(O_RDONLY | O_BINARY)
|
|
||||||
#else
|
|
||||||
(O_RDONLY)
|
|
||||||
#endif
|
|
||||||
)) == -1) {
|
|
||||||
perror("Opening data file");
|
|
||||||
exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (Modes.net) modesInitNet();
|
|
||||||
|
|
||||||
// init stats:
|
// init stats:
|
||||||
Modes.stats_current.start = Modes.stats_current.end =
|
Modes.stats_current.start = Modes.stats_current.end =
|
||||||
|
@ -1126,9 +660,11 @@ int main(int argc, char **argv) {
|
||||||
writeJsonToFile("stats.json", generateStatsJson);
|
writeJsonToFile("stats.json", generateStatsJson);
|
||||||
writeJsonToFile("aircraft.json", generateAircraftJson);
|
writeJsonToFile("aircraft.json", generateAircraftJson);
|
||||||
|
|
||||||
|
interactiveInit();
|
||||||
|
|
||||||
// If the user specifies --net-only, just run in order to serve network
|
// If the user specifies --net-only, just run in order to serve network
|
||||||
// clients without reading data from the RTL device
|
// clients without reading data from the RTL device
|
||||||
if (Modes.net_only) {
|
if (Modes.sdr_type == SDR_NONE) {
|
||||||
while (!Modes.exit) {
|
while (!Modes.exit) {
|
||||||
struct timespec start_time;
|
struct timespec start_time;
|
||||||
|
|
||||||
|
@ -1145,7 +681,7 @@ int main(int argc, char **argv) {
|
||||||
pthread_mutex_lock(&Modes.data_mutex);
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
|
pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
|
||||||
|
|
||||||
while (Modes.exit == 0) {
|
while (!Modes.exit) {
|
||||||
struct timespec start_time;
|
struct timespec start_time;
|
||||||
|
|
||||||
if (Modes.first_free_buffer == Modes.first_filled_buffer) {
|
if (Modes.first_free_buffer == Modes.first_filled_buffer) {
|
||||||
|
@ -1201,7 +737,7 @@ int main(int argc, char **argv) {
|
||||||
// Nothing to process this time around.
|
// Nothing to process this time around.
|
||||||
pthread_mutex_unlock(&Modes.data_mutex);
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
if (--watchdogCounter <= 0) {
|
if (--watchdogCounter <= 0) {
|
||||||
log_with_timestamp("No data received from the dongle for a long time, it may have wedged");
|
log_with_timestamp("No data received from the SDR for a long time, it may have wedged");
|
||||||
watchdogCounter = 600;
|
watchdogCounter = 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1228,9 +764,10 @@ int main(int argc, char **argv) {
|
||||||
display_total_stats();
|
display_total_stats();
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup_converter(Modes.converter_state);
|
|
||||||
log_with_timestamp("Normal exit.");
|
log_with_timestamp("Normal exit.");
|
||||||
|
|
||||||
|
sdrClose();
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
pthread_exit(0);
|
pthread_exit(0);
|
||||||
#else
|
#else
|
||||||
|
|
21
dump1090.h
21
dump1090.h
|
@ -65,6 +65,7 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
@ -84,12 +85,8 @@
|
||||||
|
|
||||||
#include "compat/compat.h"
|
#include "compat/compat.h"
|
||||||
|
|
||||||
// Avoid a dependency on rtl-sdr except where it's really needed.
|
|
||||||
typedef struct rtlsdr_dev rtlsdr_dev_t;
|
|
||||||
|
|
||||||
// ============================= #defines ===============================
|
// ============================= #defines ===============================
|
||||||
|
|
||||||
#define MODES_DEFAULT_PPM 52
|
|
||||||
#define MODES_DEFAULT_FREQ 1090000000
|
#define MODES_DEFAULT_FREQ 1090000000
|
||||||
#define MODES_DEFAULT_WIDTH 1000
|
#define MODES_DEFAULT_WIDTH 1000
|
||||||
#define MODES_DEFAULT_HEIGHT 700
|
#define MODES_DEFAULT_HEIGHT 700
|
||||||
|
@ -245,9 +242,14 @@ typedef enum {
|
||||||
#include "cpr.h"
|
#include "cpr.h"
|
||||||
#include "icao_filter.h"
|
#include "icao_filter.h"
|
||||||
#include "convert.h"
|
#include "convert.h"
|
||||||
|
#include "sdr.h"
|
||||||
|
|
||||||
//======================== structure declarations =========================
|
//======================== structure declarations =========================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SDR_NONE, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF
|
||||||
|
} sdr_type_t;
|
||||||
|
|
||||||
// Structure representing one magnitude buffer
|
// Structure representing one magnitude buffer
|
||||||
struct mag_buf {
|
struct mag_buf {
|
||||||
uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block
|
uint16_t *data; // Magnitude data. Starts with Modes.trailing_samples worth of overlap from the previous block
|
||||||
|
@ -274,24 +276,16 @@ struct { // Internal state
|
||||||
unsigned trailing_samples; // extra trailing samples in magnitude buffers
|
unsigned trailing_samples; // extra trailing samples in magnitude buffers
|
||||||
double sample_rate; // actual sample rate in use (in hz)
|
double sample_rate; // actual sample rate in use (in hz)
|
||||||
|
|
||||||
int fd; // --ifile option file descriptor
|
|
||||||
input_format_t input_format; // --iformat option
|
|
||||||
uint16_t *maglut; // I/Q -> Magnitude lookup table
|
|
||||||
uint16_t *log10lut; // Magnitude -> log10 lookup table
|
uint16_t *log10lut; // Magnitude -> log10 lookup table
|
||||||
int exit; // Exit from the main loop when true
|
int exit; // Exit from the main loop when true
|
||||||
|
|
||||||
// Sample conversion
|
// Sample conversion
|
||||||
int dc_filter; // should we apply a DC filter?
|
int dc_filter; // should we apply a DC filter?
|
||||||
iq_convert_fn converter_function;
|
|
||||||
struct converter_state *converter_state;
|
|
||||||
|
|
||||||
// RTLSDR
|
// RTLSDR
|
||||||
char * dev_name;
|
char * dev_name;
|
||||||
int gain;
|
int gain;
|
||||||
int enable_agc;
|
|
||||||
rtlsdr_dev_t *dev;
|
|
||||||
int freq;
|
int freq;
|
||||||
int ppm_error;
|
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
char aneterr[ANET_ERR_LEN];
|
char aneterr[ANET_ERR_LEN];
|
||||||
|
@ -308,7 +302,7 @@ struct { // Internal state
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
char *filename; // Input form file, --ifile option
|
sdr_type_t sdr_type; // where are we getting data from?
|
||||||
int nfix_crc; // Number of crc bit error(s) to correct
|
int nfix_crc; // Number of crc bit error(s) to correct
|
||||||
int check_crc; // Only display messages with good CRC
|
int check_crc; // Only display messages with good CRC
|
||||||
int raw; // Raw output format
|
int raw; // Raw output format
|
||||||
|
@ -343,7 +337,6 @@ struct { // Internal state
|
||||||
uint64_t json_interval; // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval
|
uint64_t json_interval; // Interval between rewriting the json aircraft file, in milliseconds; also the advertised map refresh interval
|
||||||
char *html_dir; // Path to www base directory.
|
char *html_dir; // Path to www base directory.
|
||||||
int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact
|
int json_location_accuracy; // Accuracy of location metadata: 0=none, 1=approx, 2=exact
|
||||||
int throttle; // When reading from a file, throttle file playback to realtime?
|
|
||||||
|
|
||||||
int json_aircraft_history_next;
|
int json_aircraft_history_next;
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -1609,7 +1609,7 @@ function getFlightAwareModeSLink(code, ident, linkText) {
|
||||||
|
|
||||||
function getFlightAwarePhotoLink(registration) {
|
function getFlightAwarePhotoLink(registration) {
|
||||||
if (registration !== null && registration !== "") {
|
if (registration !== null && registration !== "") {
|
||||||
return "<a target=\"_blank\" href=\"https://flightaware.com/photos/aircraft/" + registration.trim() + "\">See Aircraft Photos</a>";
|
return "<a target=\"_blank\" href=\"https://flightaware.com/photos/aircraft/" + registration.replace(/[^0-9a-z]/ig,'') + "\">See Aircraft Photos</a>";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
|
|
169
sdr.c
Normal file
169
sdr.c
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr.c: generic SDR infrastructure
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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 "dump1090.h"
|
||||||
|
|
||||||
|
#include "sdr_ifile.h"
|
||||||
|
#ifdef ENABLE_RTLSDR
|
||||||
|
# include "sdr_rtlsdr.h"
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_BLADERF
|
||||||
|
# include "sdr_bladerf.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *name;
|
||||||
|
sdr_type_t sdr_type;
|
||||||
|
void (*initConfig)();
|
||||||
|
void (*showHelp)();
|
||||||
|
bool (*handleOption)(int, char**, int*);
|
||||||
|
bool (*open)();
|
||||||
|
void (*run)();
|
||||||
|
void (*close)();
|
||||||
|
} sdr_handler;
|
||||||
|
|
||||||
|
static void noInitConfig()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void noShowHelp()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool noHandleOption(int argc, char **argv, int *jptr)
|
||||||
|
{
|
||||||
|
MODES_NOTUSED(argc);
|
||||||
|
MODES_NOTUSED(argv);
|
||||||
|
MODES_NOTUSED(jptr);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool noOpen()
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Net-only mode, no SDR device or file open.\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void noRun()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void noClose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool unsupportedOpen()
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Support for this SDR type was not enabled in this build.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sdr_handler sdr_handlers[] = {
|
||||||
|
#ifdef ENABLE_RTLSDR
|
||||||
|
{ "rtlsdr", SDR_RTLSDR, rtlsdrInitConfig, rtlsdrShowHelp, rtlsdrHandleOption, rtlsdrOpen, rtlsdrRun, rtlsdrClose },
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_BLADERF
|
||||||
|
{ "bladerf", SDR_BLADERF, bladeRFInitConfig, bladeRFShowHelp, bladeRFHandleOption, bladeRFOpen, bladeRFRun, bladeRFClose },
|
||||||
|
#endif
|
||||||
|
|
||||||
|
{ "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, ifileClose },
|
||||||
|
{ "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noClose },
|
||||||
|
|
||||||
|
{ NULL, SDR_NONE, NULL, NULL, NULL, NULL, NULL, NULL } /* must come last */
|
||||||
|
};
|
||||||
|
|
||||||
|
void sdrInitConfig()
|
||||||
|
{
|
||||||
|
// Default SDR is the first type available in the handlers array.
|
||||||
|
Modes.sdr_type = sdr_handlers[0].sdr_type;
|
||||||
|
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
sdr_handlers[i].initConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sdrShowHelp()
|
||||||
|
{
|
||||||
|
printf("--device-type <type> Select SDR type (default: %s)\n", sdr_handlers[0].name);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
sdr_handlers[i].showHelp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sdrHandleOption(int argc, char **argv, int *jptr)
|
||||||
|
{
|
||||||
|
int j = *jptr;
|
||||||
|
if (!strcmp(argv[j], "--device-type") && (j+1) < argc) {
|
||||||
|
++j;
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
if (!strcasecmp(sdr_handlers[i].name, argv[j])) {
|
||||||
|
Modes.sdr_type = sdr_handlers[i].sdr_type;
|
||||||
|
*jptr = j;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "SDR type '%s' not recognized; supported SDR types are:\n", argv[j]);
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
fprintf(stderr, " %s\n", sdr_handlers[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
if (sdr_handlers[i].handleOption(argc, argv, jptr))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sdr_handler *current_handler()
|
||||||
|
{
|
||||||
|
static sdr_handler unsupported_handler = { "unsupported", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, unsupportedOpen, noRun, noClose };
|
||||||
|
|
||||||
|
for (int i = 0; sdr_handlers[i].name; ++i) {
|
||||||
|
if (Modes.sdr_type == sdr_handlers[i].sdr_type) {
|
||||||
|
return &sdr_handlers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &unsupported_handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sdrOpen()
|
||||||
|
{
|
||||||
|
return current_handler()->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sdrRun()
|
||||||
|
{
|
||||||
|
return current_handler()->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sdrClose()
|
||||||
|
{
|
||||||
|
current_handler()->close();
|
||||||
|
}
|
33
sdr.h
Normal file
33
sdr.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr.h: generic SDR infrastructure (header)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
#ifndef SDR_H
|
||||||
|
#define SDR_H
|
||||||
|
|
||||||
|
// Common interface to different SDR inputs.
|
||||||
|
|
||||||
|
void sdrInitConfig();
|
||||||
|
void sdrShowHelp();
|
||||||
|
bool sdrHandleOption(int argc, char **argv, int *jptr);
|
||||||
|
bool sdrOpen();
|
||||||
|
void sdrRun();
|
||||||
|
void sdrClose();
|
||||||
|
|
||||||
|
#endif
|
515
sdr_bladerf.c
Normal file
515
sdr_bladerf.c
Normal file
|
@ -0,0 +1,515 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_bladerf.c: bladeRF support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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 "dump1090.h"
|
||||||
|
#include "sdr_bladerf.h"
|
||||||
|
|
||||||
|
#include <libbladeRF.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const char *device_str;
|
||||||
|
const char *fpga_path;
|
||||||
|
unsigned decimation;
|
||||||
|
bladerf_lpf_mode lpf_mode;
|
||||||
|
unsigned lpf_bandwidth;
|
||||||
|
|
||||||
|
struct bladerf *device;
|
||||||
|
|
||||||
|
iq_convert_fn converter;
|
||||||
|
struct converter_state *converter_state;
|
||||||
|
|
||||||
|
unsigned block_size;
|
||||||
|
} BladeRF;
|
||||||
|
|
||||||
|
void bladeRFInitConfig()
|
||||||
|
{
|
||||||
|
BladeRF.device_str = NULL;
|
||||||
|
BladeRF.fpga_path = NULL;
|
||||||
|
BladeRF.decimation = 1;
|
||||||
|
BladeRF.lpf_mode = BLADERF_LPF_NORMAL;
|
||||||
|
BladeRF.lpf_bandwidth = 1750000;
|
||||||
|
BladeRF.device = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bladeRFHandleOption(int argc, char **argv, int *jptr)
|
||||||
|
{
|
||||||
|
int j = *jptr;
|
||||||
|
bool more = (j+1 < argc);
|
||||||
|
if (!strcmp(argv[j], "--bladerf-fpga") && more) {
|
||||||
|
BladeRF.fpga_path = strdup(argv[++j]);
|
||||||
|
} else if (!strcmp(argv[j], "--bladerf-decimation") && more) {
|
||||||
|
BladeRF.decimation = atoi(argv[++j]);
|
||||||
|
} else if (!strcmp(argv[j], "--bladerf-bandwidth") && more) {
|
||||||
|
++j;
|
||||||
|
if (!strcasecmp(argv[j], "bypass")) {
|
||||||
|
BladeRF.lpf_mode = BLADERF_LPF_BYPASSED;
|
||||||
|
} else {
|
||||||
|
BladeRF.lpf_mode = BLADERF_LPF_NORMAL;
|
||||||
|
BladeRF.lpf_bandwidth = atoi(argv[j]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*jptr = j;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bladeRFShowHelp()
|
||||||
|
{
|
||||||
|
printf(" bladeRF-specific options (use with --device-type bladerf)\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("--device <ident> select device by bladeRF 'device identifier'\n");
|
||||||
|
printf("--bladerf-fpga <path> use alternative FPGA bitstream ('' to disable FPGA load)\n");
|
||||||
|
printf("--bladerf-decimation <N> assume FPGA decimates by a factor of N\n");
|
||||||
|
printf("--bladerf-bandwidth <hz> set LPF bandwidth ('bypass' to bypass the LPF)\n");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int lna_gain_db(bladerf_lna_gain gain)
|
||||||
|
{
|
||||||
|
switch (gain) {
|
||||||
|
case BLADERF_LNA_GAIN_BYPASS:
|
||||||
|
return 0;
|
||||||
|
case BLADERF_LNA_GAIN_MID:
|
||||||
|
return BLADERF_LNA_GAIN_MID_DB;
|
||||||
|
case BLADERF_LNA_GAIN_MAX:
|
||||||
|
return BLADERF_LNA_GAIN_MAX_DB;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_config()
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
unsigned rate;
|
||||||
|
unsigned freq;
|
||||||
|
bladerf_lpf_mode lpf_mode;
|
||||||
|
unsigned lpf_bw;
|
||||||
|
bladerf_lna_gain lna_gain;
|
||||||
|
int rxvga1_gain;
|
||||||
|
int rxvga2_gain;
|
||||||
|
int16_t lms_dc_i, lms_dc_q;
|
||||||
|
int16_t fpga_phase, fpga_gain;
|
||||||
|
struct bladerf_lms_dc_cals dc_cals;
|
||||||
|
|
||||||
|
if ((status = bladerf_get_sample_rate(BladeRF.device, BLADERF_MODULE_RX, &rate)) < 0 ||
|
||||||
|
(status = bladerf_get_frequency(BladeRF.device, BLADERF_MODULE_RX, &freq)) < 0 ||
|
||||||
|
(status = bladerf_get_lpf_mode(BladeRF.device, BLADERF_MODULE_RX, &lpf_mode)) < 0 ||
|
||||||
|
(status = bladerf_get_bandwidth(BladeRF.device, BLADERF_MODULE_RX, &lpf_bw)) < 0 ||
|
||||||
|
(status = bladerf_get_lna_gain(BladeRF.device, &lna_gain)) < 0 ||
|
||||||
|
(status = bladerf_get_rxvga1(BladeRF.device, &rxvga1_gain)) < 0 ||
|
||||||
|
(status = bladerf_get_rxvga2(BladeRF.device, &rxvga2_gain)) < 0 ||
|
||||||
|
(status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_I, &lms_dc_i)) < 0 ||
|
||||||
|
(status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_LMS_DCOFF_Q, &lms_dc_q)) < 0 ||
|
||||||
|
(status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_PHASE, &fpga_phase)) < 0 ||
|
||||||
|
(status = bladerf_get_correction(BladeRF.device, BLADERF_MODULE_RX, BLADERF_CORR_FPGA_GAIN, &fpga_gain)) < 0 ||
|
||||||
|
(status = bladerf_lms_get_dc_cals(BladeRF.device, &dc_cals)) < 0) {
|
||||||
|
fprintf(stderr, "bladeRF: couldn't read back device configuration\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "bladeRF: sampling rate: %.1f MHz\n", rate/1e6);
|
||||||
|
fprintf(stderr, "bladeRF: frequency: %.1f MHz\n", freq/1e6);
|
||||||
|
fprintf(stderr, "bladeRF: LNA gain: %ddB\n", lna_gain_db(lna_gain));
|
||||||
|
fprintf(stderr, "bladeRF: RXVGA1 gain: %ddB\n", rxvga1_gain);
|
||||||
|
fprintf(stderr, "bladeRF: RXVGA2 gain: %ddB\n", rxvga2_gain);
|
||||||
|
|
||||||
|
switch (lpf_mode) {
|
||||||
|
case BLADERF_LPF_NORMAL:
|
||||||
|
fprintf(stderr, "bladeRF: LPF bandwidth: %.2f MHz\n", lpf_bw/1e6);
|
||||||
|
break;
|
||||||
|
case BLADERF_LPF_BYPASSED:
|
||||||
|
fprintf(stderr, "bladeRF: LPF bypassed\n");
|
||||||
|
break;
|
||||||
|
case BLADERF_LPF_DISABLED:
|
||||||
|
fprintf(stderr, "bladeRF: LPF disabled\n");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "bladeRF: LPF in unknown state\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "bladeRF: calibration settings:\n");
|
||||||
|
fprintf(stderr, " LMS DC adjust: I=%d Q=%d\n", lms_dc_i, lms_dc_q);
|
||||||
|
fprintf(stderr, " FPGA phase adjust: %+.3f degrees\n", fpga_phase * 10.0 / 4096);
|
||||||
|
fprintf(stderr, " FPGA gain adjust: %+.3f\n", fpga_gain * 1.0 / 4096);
|
||||||
|
fprintf(stderr, " LMS LPF tuning: %d\n", dc_cals.lpf_tuning);
|
||||||
|
fprintf(stderr, " LMS RX LPF filter: I=%d Q=%d\n", dc_cals.rx_lpf_i, dc_cals.rx_lpf_q);
|
||||||
|
fprintf(stderr, " LMS RXVGA2 DC ref: %d\n", dc_cals.dc_ref);
|
||||||
|
fprintf(stderr, " LMS RXVGA2A: I=%d Q=%d\n", dc_cals.rxvga2a_i, dc_cals.rxvga2a_q);
|
||||||
|
fprintf(stderr, " LMS RXVGA2B: I=%d Q=%d\n", dc_cals.rxvga2b_i, dc_cals.rxvga2b_q);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bladeRFOpen()
|
||||||
|
{
|
||||||
|
if (BladeRF.device) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int status;
|
||||||
|
|
||||||
|
bladerf_set_usb_reset_on_open(true);
|
||||||
|
if ((status = bladerf_open(&BladeRF.device, Modes.dev_name)) < 0) {
|
||||||
|
fprintf(stderr, "Failed to open bladeRF: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *fpga_path;
|
||||||
|
if (BladeRF.fpga_path) {
|
||||||
|
fpga_path = BladeRF.fpga_path;
|
||||||
|
} else {
|
||||||
|
bladerf_fpga_size size;
|
||||||
|
if ((status = bladerf_get_fpga_size(BladeRF.device, &size)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_get_fpga_size failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case BLADERF_FPGA_40KLE:
|
||||||
|
fpga_path = "/usr/share/Nuand/bladeRF/hostedx40.rbf";
|
||||||
|
break;
|
||||||
|
case BLADERF_FPGA_115KLE:
|
||||||
|
fpga_path = "/usr/share/Nuand/bladeRF/hostedx115.rbf";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "bladeRF: unknown FPGA size, skipping FPGA load");
|
||||||
|
fpga_path = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fpga_path && fpga_path[0]) {
|
||||||
|
fprintf(stderr, "bladeRF: loading FPGA bitstream from %s\n", fpga_path);
|
||||||
|
if ((status = bladerf_load_fpga(BladeRF.device, fpga_path)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_load_fpga() failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (bladerf_device_speed(BladeRF.device)) {
|
||||||
|
case BLADERF_DEVICE_SPEED_HIGH:
|
||||||
|
BladeRF.block_size = 1024;
|
||||||
|
break;
|
||||||
|
case BLADERF_DEVICE_SPEED_SUPER:
|
||||||
|
BladeRF.block_size = 2048;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "couldn't determine bladerf device speed\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_sample_rate(BladeRF.device, BLADERF_MODULE_RX, Modes.sample_rate * BladeRF.decimation, NULL)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_sample_rate failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_frequency(BladeRF.device, BLADERF_MODULE_RX, Modes.freq)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_frequency failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_lpf_mode(BladeRF.device, BLADERF_MODULE_RX, BladeRF.lpf_mode)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_lpf_mode failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_bandwidth(BladeRF.device, BLADERF_MODULE_RX, BladeRF.lpf_bandwidth, NULL)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_lpf_bandwidth failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* turn the tx gain right off, just in case */
|
||||||
|
if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_TX, -100)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_gain(TX) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_gain(BladeRF.device, BLADERF_MODULE_RX, Modes.gain / 10.0)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_gain(RX) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_set_loopback(BladeRF.device, BLADERF_LB_NONE)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_loopback() failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_LPF_TUNING)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_calibrate_dc(LPF_TUNING) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_RX_LPF)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_calibrate_dc(RX_LPF) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_calibrate_dc(BladeRF.device, BLADERF_DC_CAL_RXVGA2)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_calibrate_dc(RXVGA2) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
show_config();
|
||||||
|
|
||||||
|
BladeRF.converter = init_converter(INPUT_SC16Q11,
|
||||||
|
Modes.sample_rate,
|
||||||
|
Modes.dc_filter,
|
||||||
|
&BladeRF.converter_state);
|
||||||
|
if (!BladeRF.converter) {
|
||||||
|
fprintf(stderr, "can't initialize sample converter\n");
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
error:
|
||||||
|
if (BladeRF.device) {
|
||||||
|
bladerf_close(BladeRF.device);
|
||||||
|
BladeRF.device = NULL;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct timespec thread_cpu;
|
||||||
|
static unsigned timeouts = 0;
|
||||||
|
|
||||||
|
static void *handle_bladerf_samples(struct bladerf *dev,
|
||||||
|
struct bladerf_stream *stream,
|
||||||
|
struct bladerf_metadata *meta,
|
||||||
|
void *samples,
|
||||||
|
size_t num_samples,
|
||||||
|
void *user_data)
|
||||||
|
{
|
||||||
|
static uint64_t nextTimestamp = 0;
|
||||||
|
static bool dropping = false;
|
||||||
|
|
||||||
|
MODES_NOTUSED(dev);
|
||||||
|
MODES_NOTUSED(stream);
|
||||||
|
MODES_NOTUSED(meta);
|
||||||
|
MODES_NOTUSED(user_data);
|
||||||
|
MODES_NOTUSED(num_samples);
|
||||||
|
|
||||||
|
// record initial time for later sys timestamp calculation
|
||||||
|
struct timespec entryTimestamp;
|
||||||
|
clock_gettime(CLOCK_REALTIME, &entryTimestamp);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
if (Modes.exit) {
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
return BLADERF_STREAM_SHUTDOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS;
|
||||||
|
struct mag_buf *outbuf = &Modes.mag_buffers[Modes.first_free_buffer];
|
||||||
|
struct mag_buf *lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS];
|
||||||
|
unsigned free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS;
|
||||||
|
|
||||||
|
if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS/2)) {
|
||||||
|
// FIFO is full. Drop this block.
|
||||||
|
dropping = true;
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropping = false;
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
|
||||||
|
// Copy trailing data from last block (or reset if not valid)
|
||||||
|
if (outbuf->dropped == 0) {
|
||||||
|
memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
} else {
|
||||||
|
memset(outbuf->data, 0, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// start handling metadata blocks
|
||||||
|
outbuf->dropped = 0;
|
||||||
|
outbuf->length = 0;
|
||||||
|
outbuf->mean_level = outbuf->mean_power = 0;
|
||||||
|
|
||||||
|
unsigned blocks_processed = 0;
|
||||||
|
unsigned samples_per_block = (BladeRF.block_size - 16) / 4;
|
||||||
|
|
||||||
|
static bool overrun = true; // ignore initial overruns as we get up to speed
|
||||||
|
static bool first_buffer = true;
|
||||||
|
for (unsigned offset = 0; offset < MODES_MAG_BUF_SAMPLES * 4; offset += BladeRF.block_size) {
|
||||||
|
// read the next metadata header
|
||||||
|
uint8_t *header = ((uint8_t*)samples) + offset;
|
||||||
|
uint64_t metadata_magic = le32toh(*(uint32_t*)(header));
|
||||||
|
uint64_t metadata_timestamp = le64toh(*(uint64_t*)(header + 4));
|
||||||
|
uint32_t metadata_flags = le32toh(*(uint32_t*)(header + 12));
|
||||||
|
void *sample_data = header + 16;
|
||||||
|
|
||||||
|
if (metadata_magic != 0x12344321) {
|
||||||
|
// first buffer is often in the wrong mode
|
||||||
|
if (!first_buffer) {
|
||||||
|
fprintf(stderr, "bladeRF: wrong metadata header magic value, skipping rest of buffer\n");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata_flags & BLADERF_META_STATUS_OVERRUN) {
|
||||||
|
if (!overrun) {
|
||||||
|
fprintf(stderr, "bladeRF: receive overrun\n");
|
||||||
|
}
|
||||||
|
overrun = true;
|
||||||
|
} else {
|
||||||
|
overrun = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef BROKEN_FPGA_METADATA
|
||||||
|
// this needs a fixed decimating FPGA image that handles the timestamp correctly
|
||||||
|
if (nextTimestamp && nextTimestamp != metadata_timestamp) {
|
||||||
|
// dropped data or lost sync. start again.
|
||||||
|
if (metadata_timestamp > nextTimestamp)
|
||||||
|
outbuf->dropped += (metadata_timestamp - nextTimestamp);
|
||||||
|
outbuf->dropped += outbuf->length;
|
||||||
|
outbuf->length = 0;
|
||||||
|
blocks_processed = 0;
|
||||||
|
outbuf->mean_level = outbuf->mean_power = 0;
|
||||||
|
nextTimestamp = metadata_timestamp;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
MODES_NOTUSED(metadata_timestamp);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!blocks_processed) {
|
||||||
|
// Compute the sample timestamp for the start of the block
|
||||||
|
outbuf->sampleTimestamp = nextTimestamp * 12e6 / Modes.sample_rate / BladeRF.decimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a block of data
|
||||||
|
double mean_level, mean_power;
|
||||||
|
BladeRF.converter(sample_data, &outbuf->data[Modes.trailing_samples + outbuf->length], samples_per_block, BladeRF.converter_state, &mean_level, &mean_power);
|
||||||
|
outbuf->length += samples_per_block;
|
||||||
|
outbuf->mean_level += mean_level;
|
||||||
|
outbuf->mean_power += mean_power;
|
||||||
|
nextTimestamp += samples_per_block * BladeRF.decimation;
|
||||||
|
++blocks_processed;
|
||||||
|
timeouts = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
first_buffer = false;
|
||||||
|
|
||||||
|
if (blocks_processed) {
|
||||||
|
// Get the approx system time for the start of this block
|
||||||
|
unsigned block_duration = 1e9 * outbuf->length / Modes.sample_rate;
|
||||||
|
outbuf->sysTimestamp = entryTimestamp;
|
||||||
|
outbuf->sysTimestamp.tv_nsec -= block_duration;
|
||||||
|
normalize_timespec(&outbuf->sysTimestamp);
|
||||||
|
|
||||||
|
outbuf->mean_level /= blocks_processed;
|
||||||
|
outbuf->mean_power /= blocks_processed;
|
||||||
|
|
||||||
|
// Push the new data to the demodulation thread
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
|
||||||
|
// accumulate CPU while holding the mutex, and restart measurement
|
||||||
|
end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator);
|
||||||
|
start_cpu_timing(&thread_cpu);
|
||||||
|
|
||||||
|
Modes.mag_buffers[next_free_buffer].dropped = 0;
|
||||||
|
Modes.mag_buffers[next_free_buffer].length = 0; // just in case
|
||||||
|
Modes.first_free_buffer = next_free_buffer;
|
||||||
|
|
||||||
|
pthread_cond_signal(&Modes.data_cond);
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void bladeRFRun()
|
||||||
|
{
|
||||||
|
if (!BladeRF.device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned transfers = 7;
|
||||||
|
|
||||||
|
int status;
|
||||||
|
struct bladerf_stream *stream = NULL;
|
||||||
|
void **buffers = NULL;
|
||||||
|
|
||||||
|
if ((status = bladerf_init_stream(&stream,
|
||||||
|
BladeRF.device,
|
||||||
|
handle_bladerf_samples,
|
||||||
|
&buffers,
|
||||||
|
/* num_buffers */ transfers,
|
||||||
|
BLADERF_FORMAT_SC16_Q11_META,
|
||||||
|
/* samples_per_buffer */ MODES_MAG_BUF_SAMPLES,
|
||||||
|
/* num_transfers */ transfers,
|
||||||
|
/* user_data */ NULL)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_init_stream() failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned ms_per_transfer = 1000 * MODES_MAG_BUF_SAMPLES / Modes.sample_rate;
|
||||||
|
if ((status = bladerf_set_stream_timeout(BladeRF.device, BLADERF_MODULE_RX, ms_per_transfer * (transfers + 2))) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_set_stream_timeout() failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((status = bladerf_enable_module(BladeRF.device, BLADERF_MODULE_RX, true) < 0)) {
|
||||||
|
fprintf(stderr, "bladerf_enable_module(RX, true) failed: %s\n", bladerf_strerror(status));
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_cpu_timing(&thread_cpu);
|
||||||
|
|
||||||
|
timeouts = 0; // reset to zero when we get a callback with some data
|
||||||
|
retry:
|
||||||
|
if ((status = bladerf_stream(stream, BLADERF_MODULE_RX)) < 0) {
|
||||||
|
fprintf(stderr, "bladerf_stream() failed: %s\n", bladerf_strerror(status));
|
||||||
|
if (status == BLADERF_ERR_TIMEOUT) {
|
||||||
|
if (++timeouts < 5)
|
||||||
|
goto retry;
|
||||||
|
fprintf(stderr, "bladerf is wedged, giving up.\n");
|
||||||
|
}
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
if ((status = bladerf_enable_module(BladeRF.device, BLADERF_MODULE_RX, false) < 0)) {
|
||||||
|
fprintf(stderr, "bladerf_enable_module(RX, false) failed: %s\n", bladerf_strerror(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
bladerf_deinit_stream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void bladeRFClose()
|
||||||
|
{
|
||||||
|
if (BladeRF.converter) {
|
||||||
|
cleanup_converter(BladeRF.converter_state);
|
||||||
|
BladeRF.converter = NULL;
|
||||||
|
BladeRF.converter_state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BladeRF.device) {
|
||||||
|
bladerf_close(BladeRF.device);
|
||||||
|
BladeRF.device = NULL;
|
||||||
|
}
|
||||||
|
}
|
32
sdr_bladerf.h
Normal file
32
sdr_bladerf.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_bladerf.h: bladeRF support (header)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
#ifndef BLADERF_H
|
||||||
|
#define BLADERF_H
|
||||||
|
|
||||||
|
// Support for the Nuand bladeRF SDR
|
||||||
|
|
||||||
|
void bladeRFInitConfig();
|
||||||
|
void bladeRFShowHelp();
|
||||||
|
bool bladeRFHandleOption(int argc, char **argv, int *jptr);
|
||||||
|
bool bladeRFOpen();
|
||||||
|
void bladeRFRun();
|
||||||
|
void bladeRFClose();
|
||||||
|
|
||||||
|
#endif
|
286
sdr_ifile.c
Normal file
286
sdr_ifile.c
Normal file
|
@ -0,0 +1,286 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_ifile.c: "file" SDR support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
// This file incorporates work covered by the following copyright and
|
||||||
|
// permission notice:
|
||||||
|
//
|
||||||
|
// Copyright (C) 2012 by Salvatore Sanfilippo <antirez@gmail.com>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include "dump1090.h"
|
||||||
|
#include "sdr_ifile.h"
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
const char *filename;
|
||||||
|
input_format_t input_format;
|
||||||
|
bool throttle;
|
||||||
|
|
||||||
|
int fd;
|
||||||
|
unsigned bytes_per_sample;
|
||||||
|
void *readbuf;
|
||||||
|
iq_convert_fn converter;
|
||||||
|
struct converter_state *converter_state;
|
||||||
|
} ifile;
|
||||||
|
|
||||||
|
void ifileInitConfig(void)
|
||||||
|
{
|
||||||
|
ifile.filename = NULL;
|
||||||
|
ifile.input_format = INPUT_UC8;
|
||||||
|
ifile.throttle = false;
|
||||||
|
ifile.fd = -1;
|
||||||
|
ifile.bytes_per_sample = 0;
|
||||||
|
ifile.readbuf = NULL;
|
||||||
|
ifile.converter = NULL;
|
||||||
|
ifile.converter_state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifileShowHelp()
|
||||||
|
{
|
||||||
|
printf(" ifile-specific options (use with --ifile)\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("--ifile <path> read samples from given file ('-' for stdin)\n");
|
||||||
|
printf("--iformat <type> set sample format (UC8, SC16, SC16Q11)\n");
|
||||||
|
printf("--throttle process samples at the original capture speed\n");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ifileHandleOption(int argc, char **argv, int *jptr)
|
||||||
|
{
|
||||||
|
int j = *jptr;
|
||||||
|
bool more = (j +1 < argc);
|
||||||
|
|
||||||
|
if (!strcmp(argv[j], "--ifile") && more) {
|
||||||
|
// implies --device-type ifile
|
||||||
|
ifile.filename = strdup(argv[++j]);
|
||||||
|
Modes.sdr_type = SDR_IFILE;
|
||||||
|
} else if (!strcmp(argv[j],"--iformat") && more) {
|
||||||
|
++j;
|
||||||
|
if (!strcasecmp(argv[j], "uc8")) {
|
||||||
|
ifile.input_format = INPUT_UC8;
|
||||||
|
} else if (!strcasecmp(argv[j], "sc16")) {
|
||||||
|
ifile.input_format = INPUT_SC16;
|
||||||
|
} else if (!strcasecmp(argv[j], "sc16q11")) {
|
||||||
|
ifile.input_format = INPUT_SC16Q11;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Input format '%s' not understood (supported values: UC8, SC16, SC16Q11)\n",
|
||||||
|
argv[j]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (!strcmp(argv[j],"--throttle")) {
|
||||||
|
ifile.throttle = true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*jptr = j;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//=========================================================================
|
||||||
|
//
|
||||||
|
// This is used when --ifile is specified in order to read data from file
|
||||||
|
// instead of using an RTLSDR device
|
||||||
|
//
|
||||||
|
bool ifileOpen(void)
|
||||||
|
{
|
||||||
|
if (!ifile.filename) {
|
||||||
|
fprintf(stderr, "SDR type 'ifile' requires an --ifile argument\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(ifile.filename, "-")) {
|
||||||
|
ifile.fd = STDIN_FILENO;
|
||||||
|
} else if ((ifile.fd = open(ifile.filename, O_RDONLY)) < 0) {
|
||||||
|
fprintf(stderr, "ifile: could not open %s: %s\n",
|
||||||
|
ifile.filename, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ifile.input_format) {
|
||||||
|
case INPUT_UC8:
|
||||||
|
ifile.bytes_per_sample = 2;
|
||||||
|
break;
|
||||||
|
case INPUT_SC16:
|
||||||
|
case INPUT_SC16Q11:
|
||||||
|
ifile.bytes_per_sample = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "ifile: unhandled input format\n");
|
||||||
|
ifileClose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(ifile.readbuf = malloc(MODES_MAG_BUF_SAMPLES * ifile.bytes_per_sample))) {
|
||||||
|
fprintf(stderr, "ifile: failed to allocate read buffer\n");
|
||||||
|
ifileClose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ifile.converter = init_converter(ifile.input_format,
|
||||||
|
Modes.sample_rate,
|
||||||
|
Modes.dc_filter,
|
||||||
|
&ifile.converter_state);
|
||||||
|
if (!ifile.converter) {
|
||||||
|
fprintf(stderr, "ifile: can't initialize sample converter\n");
|
||||||
|
ifileClose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifileRun()
|
||||||
|
{
|
||||||
|
if (ifile.fd < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int eof = 0;
|
||||||
|
struct timespec next_buffer_delivery;
|
||||||
|
|
||||||
|
struct timespec thread_cpu;
|
||||||
|
start_cpu_timing(&thread_cpu);
|
||||||
|
|
||||||
|
uint64_t sampleCounter = 0;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &next_buffer_delivery);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
while (!Modes.exit && !eof) {
|
||||||
|
ssize_t nread, toread;
|
||||||
|
void *r;
|
||||||
|
struct mag_buf *outbuf, *lastbuf;
|
||||||
|
unsigned next_free_buffer;
|
||||||
|
unsigned slen;
|
||||||
|
|
||||||
|
next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS;
|
||||||
|
if (next_free_buffer == Modes.first_filled_buffer) {
|
||||||
|
// no space for output yet
|
||||||
|
pthread_cond_wait(&Modes.data_cond, &Modes.data_mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
outbuf = &Modes.mag_buffers[Modes.first_free_buffer];
|
||||||
|
lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS];
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
|
||||||
|
// Compute the sample timestamp for the start of the block
|
||||||
|
outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate;
|
||||||
|
sampleCounter += MODES_MAG_BUF_SAMPLES;
|
||||||
|
|
||||||
|
// Copy trailing data from last block (or reset if not valid)
|
||||||
|
if (lastbuf->length >= Modes.trailing_samples) {
|
||||||
|
memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
} else {
|
||||||
|
memset(outbuf->data, 0, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the system time for the start of this block
|
||||||
|
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
|
||||||
|
|
||||||
|
toread = MODES_MAG_BUF_SAMPLES * ifile.bytes_per_sample;
|
||||||
|
r = ifile.readbuf;
|
||||||
|
while (toread) {
|
||||||
|
nread = read(ifile.fd, r, toread);
|
||||||
|
if (nread <= 0) {
|
||||||
|
if (nread < 0) {
|
||||||
|
fprintf(stderr, "ifile: error reading input file: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
// Done.
|
||||||
|
eof = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
r += nread;
|
||||||
|
toread -= nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
slen = outbuf->length = MODES_MAG_BUF_SAMPLES - toread / ifile.bytes_per_sample;
|
||||||
|
|
||||||
|
// Convert the new data
|
||||||
|
ifile.converter(ifile.readbuf, &outbuf->data[Modes.trailing_samples], slen, ifile.converter_state, &outbuf->mean_level, &outbuf->mean_power);
|
||||||
|
|
||||||
|
if (ifile.throttle || Modes.interactive) {
|
||||||
|
// Wait until we are allowed to release this buffer to the main thread
|
||||||
|
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_buffer_delivery, NULL) == EINTR)
|
||||||
|
;
|
||||||
|
|
||||||
|
// compute the time we can deliver the next buffer.
|
||||||
|
next_buffer_delivery.tv_nsec += outbuf->length * 1e9 / Modes.sample_rate;
|
||||||
|
normalize_timespec(&next_buffer_delivery);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the new data to the main thread
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
Modes.first_free_buffer = next_free_buffer;
|
||||||
|
// accumulate CPU while holding the mutex, and restart measurement
|
||||||
|
end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator);
|
||||||
|
start_cpu_timing(&thread_cpu);
|
||||||
|
pthread_cond_signal(&Modes.data_cond);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the main thread to consume all data
|
||||||
|
while (!Modes.exit && Modes.first_filled_buffer != Modes.first_free_buffer)
|
||||||
|
pthread_cond_wait(&Modes.data_cond, &Modes.data_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ifileClose()
|
||||||
|
{
|
||||||
|
if (ifile.converter) {
|
||||||
|
cleanup_converter(ifile.converter_state);
|
||||||
|
ifile.converter = NULL;
|
||||||
|
ifile.converter_state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ifile.readbuf) {
|
||||||
|
free(ifile.readbuf);
|
||||||
|
ifile.readbuf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ifile.fd >= 0 && ifile.fd != STDIN_FILENO) {
|
||||||
|
close(ifile.fd);
|
||||||
|
ifile.fd = -1;
|
||||||
|
}
|
||||||
|
}
|
33
sdr_ifile.h
Normal file
33
sdr_ifile.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_ifile.c: "file" SDR support (header)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
#ifndef SDR_IFILE_H
|
||||||
|
#define SDR_IFILE_H
|
||||||
|
|
||||||
|
// Pseudo-SDR that reads from a sample file
|
||||||
|
|
||||||
|
void ifileInitConfig();
|
||||||
|
void ifileShowHelp();
|
||||||
|
bool ifileHandleOption(int argc, char **argv, int *jptr);
|
||||||
|
bool ifileOpen();
|
||||||
|
void ifileRun();
|
||||||
|
void ifileClose();
|
||||||
|
|
||||||
|
#endif
|
385
sdr_rtlsdr.c
Normal file
385
sdr_rtlsdr.c
Normal file
|
@ -0,0 +1,385 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_rtlsdr.c: rtlsdr dongle support
|
||||||
|
//
|
||||||
|
// Copyright (c) 2014-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
// This file incorporates work covered by the following copyright and
|
||||||
|
// permission notice:
|
||||||
|
//
|
||||||
|
// Copyright (C) 2012 by Salvatore Sanfilippo <antirez@gmail.com>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer in the
|
||||||
|
// documentation and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#include "dump1090.h"
|
||||||
|
#include "sdr_rtlsdr.h"
|
||||||
|
|
||||||
|
#include <rtl-sdr.h>
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
rtlsdr_dev_t *dev;
|
||||||
|
bool digital_agc;
|
||||||
|
int ppm_error;
|
||||||
|
|
||||||
|
iq_convert_fn converter;
|
||||||
|
struct converter_state *converter_state;
|
||||||
|
} RTLSDR;
|
||||||
|
|
||||||
|
//
|
||||||
|
// =============================== RTLSDR handling ==========================
|
||||||
|
//
|
||||||
|
|
||||||
|
void rtlsdrInitConfig()
|
||||||
|
{
|
||||||
|
RTLSDR.dev = NULL;
|
||||||
|
RTLSDR.digital_agc = false;
|
||||||
|
RTLSDR.ppm_error = 0;
|
||||||
|
RTLSDR.converter = NULL;
|
||||||
|
RTLSDR.converter_state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void show_rtlsdr_devices()
|
||||||
|
{
|
||||||
|
int device_count = rtlsdr_get_device_count();
|
||||||
|
fprintf(stderr, "rtlsdr: found %d device(s):\n", device_count);
|
||||||
|
for (int i = 0; i < device_count; i++) {
|
||||||
|
char vendor[256], product[256], serial[256];
|
||||||
|
|
||||||
|
if (rtlsdr_get_device_usb_strings(i, vendor, product, serial) != 0) {
|
||||||
|
fprintf(stderr, " %d: unable to read device details\n", i);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, " %d: %s, %s, SN: %s\n", i, vendor, product, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int find_device_index(char *s)
|
||||||
|
{
|
||||||
|
int device_count = rtlsdr_get_device_count();
|
||||||
|
if (!device_count) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* does string look like raw id number */
|
||||||
|
if (!strcmp(s, "0")) {
|
||||||
|
return 0;
|
||||||
|
} else if (s[0] != '0') {
|
||||||
|
char *s2;
|
||||||
|
int device = (int)strtol(s, &s2, 10);
|
||||||
|
if (s2[0] == '\0' && device >= 0 && device < device_count) {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* does string exact match a serial */
|
||||||
|
for (int i = 0; i < device_count; i++) {
|
||||||
|
char serial[256];
|
||||||
|
if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strcmp(s, serial)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* does string prefix match a serial */
|
||||||
|
for (int i = 0; i < device_count; i++) {
|
||||||
|
char serial[256];
|
||||||
|
if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && !strncmp(s, serial, strlen(s))) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* does string suffix match a serial */
|
||||||
|
for (int i = 0; i < device_count; i++) {
|
||||||
|
char serial[256];
|
||||||
|
if (rtlsdr_get_device_usb_strings(i, NULL, NULL, serial) == 0 && strlen(s) < strlen(serial) && !strcmp(serial + strlen(serial) - strlen(s), s)) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rtlsdrShowHelp()
|
||||||
|
{
|
||||||
|
printf(" rtlsdr-specific options (use with --device-type rtlsdr)\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("--device <index|serial> select device by index or serial number\n");
|
||||||
|
printf("--enable-agc enable digital AGC (not tuner AGC!)\n");
|
||||||
|
printf("--ppm <correction> set oscillator frequency correction in PPM\n");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rtlsdrHandleOption(int argc, char **argv, int *jptr)
|
||||||
|
{
|
||||||
|
int j = *jptr;
|
||||||
|
bool more = (j +1 < argc);
|
||||||
|
|
||||||
|
if (!strcmp(argv[j], "--enable-agc")) {
|
||||||
|
RTLSDR.digital_agc = true;
|
||||||
|
} else if (!strcmp(argv[j], "--ppm") && more) {
|
||||||
|
RTLSDR.ppm_error = atoi(argv[++j]);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*jptr = j;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rtlsdrOpen(void) {
|
||||||
|
if (!rtlsdr_get_device_count()) {
|
||||||
|
fprintf(stderr, "rtlsdr: no supported devices found.\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int dev_index = 0;
|
||||||
|
if (Modes.dev_name) {
|
||||||
|
if ((dev_index = find_device_index(Modes.dev_name)) < 0) {
|
||||||
|
fprintf(stderr, "rtlsdr: no device matching '%s' found.\n", Modes.dev_name);
|
||||||
|
show_rtlsdr_devices();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char manufacturer[256];
|
||||||
|
char product[256];
|
||||||
|
char serial[256];
|
||||||
|
if (rtlsdr_get_device_usb_strings(dev_index, manufacturer, product, serial) < 0) {
|
||||||
|
fprintf(stderr, "rtlsdr: error querying device #%d: %s\n", dev_index, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "rtlsdr: using device #%d: %s (%s, %s, SN %s)\n",
|
||||||
|
dev_index, rtlsdr_get_device_name(dev_index),
|
||||||
|
manufacturer, product, serial);
|
||||||
|
|
||||||
|
if (rtlsdr_open(&RTLSDR.dev, dev_index) < 0) {
|
||||||
|
fprintf(stderr, "rtlsdr: error opening the RTLSDR device: %s\n",
|
||||||
|
strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set gain, frequency, sample rate, and reset the device
|
||||||
|
if (Modes.gain == MODES_AUTO_GAIN) {
|
||||||
|
fprintf(stderr, "rtlsdr: enabling tuner AGC\n");
|
||||||
|
rtlsdr_set_tuner_gain_mode(RTLSDR.dev, 1);
|
||||||
|
} else {
|
||||||
|
int *gains;
|
||||||
|
int numgains;
|
||||||
|
|
||||||
|
numgains = rtlsdr_get_tuner_gains(RTLSDR.dev, NULL);
|
||||||
|
if (numgains <= 0) {
|
||||||
|
fprintf(stderr, "rtlsdr: error getting tuner gains\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
gains = malloc(numgains * sizeof(int));
|
||||||
|
if (rtlsdr_get_tuner_gains(RTLSDR.dev, gains) != numgains) {
|
||||||
|
fprintf(stderr, "rtlsdr: error getting tuner gains\n");
|
||||||
|
free(gains);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target = (Modes.gain == MODES_MAX_GAIN ? 9999 : Modes.gain);
|
||||||
|
int closest = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < numgains; ++i) {
|
||||||
|
if (closest == -1 || abs(gains[i] - target) < abs(gains[closest] - target))
|
||||||
|
closest = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(gains);
|
||||||
|
|
||||||
|
rtlsdr_set_tuner_gain(RTLSDR.dev, gains[closest]);
|
||||||
|
fprintf(stderr, "rtlsdr: tuner gain set to %.1f dB\n",
|
||||||
|
rtlsdr_get_tuner_gain(RTLSDR.dev)/10.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RTLSDR.digital_agc) {
|
||||||
|
fprintf(stderr, "rtlsdr: enabling digital AGC\n");
|
||||||
|
rtlsdr_set_agc_mode(RTLSDR.dev, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
rtlsdr_set_freq_correction(RTLSDR.dev, RTLSDR.ppm_error);
|
||||||
|
rtlsdr_set_center_freq(RTLSDR.dev, Modes.freq);
|
||||||
|
rtlsdr_set_sample_rate(RTLSDR.dev, (unsigned)Modes.sample_rate);
|
||||||
|
|
||||||
|
rtlsdr_reset_buffer(RTLSDR.dev);
|
||||||
|
|
||||||
|
RTLSDR.converter = init_converter(INPUT_UC8,
|
||||||
|
Modes.sample_rate,
|
||||||
|
Modes.dc_filter,
|
||||||
|
&RTLSDR.converter_state);
|
||||||
|
if (!RTLSDR.converter) {
|
||||||
|
fprintf(stderr, "rtlsdr: can't initialize sample converter\n");
|
||||||
|
rtlsdrClose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct timespec rtlsdr_thread_cpu;
|
||||||
|
|
||||||
|
void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
|
||||||
|
struct mag_buf *outbuf;
|
||||||
|
struct mag_buf *lastbuf;
|
||||||
|
uint32_t slen;
|
||||||
|
unsigned next_free_buffer;
|
||||||
|
unsigned free_bufs;
|
||||||
|
unsigned block_duration;
|
||||||
|
|
||||||
|
static int was_odd = 0; // paranoia!!
|
||||||
|
static int dropping = 0;
|
||||||
|
static uint64_t sampleCounter = 0;
|
||||||
|
|
||||||
|
MODES_NOTUSED(ctx);
|
||||||
|
|
||||||
|
// Lock the data buffer variables before accessing them
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
if (Modes.exit) {
|
||||||
|
rtlsdr_cancel_async(RTLSDR.dev); // ask our caller to exit
|
||||||
|
}
|
||||||
|
|
||||||
|
next_free_buffer = (Modes.first_free_buffer + 1) % MODES_MAG_BUFFERS;
|
||||||
|
outbuf = &Modes.mag_buffers[Modes.first_free_buffer];
|
||||||
|
lastbuf = &Modes.mag_buffers[(Modes.first_free_buffer + MODES_MAG_BUFFERS - 1) % MODES_MAG_BUFFERS];
|
||||||
|
free_bufs = (Modes.first_filled_buffer - next_free_buffer + MODES_MAG_BUFFERS) % MODES_MAG_BUFFERS;
|
||||||
|
|
||||||
|
// Paranoia! Unlikely, but let's go for belt and suspenders here
|
||||||
|
|
||||||
|
if (len != MODES_RTL_BUF_SIZE) {
|
||||||
|
fprintf(stderr, "weirdness: rtlsdr gave us a block with an unusual size (got %u bytes, expected %u bytes)\n",
|
||||||
|
(unsigned)len, (unsigned)MODES_RTL_BUF_SIZE);
|
||||||
|
|
||||||
|
if (len > MODES_RTL_BUF_SIZE) {
|
||||||
|
// wat?! Discard the start.
|
||||||
|
unsigned discard = (len - MODES_RTL_BUF_SIZE + 1) / 2;
|
||||||
|
outbuf->dropped += discard;
|
||||||
|
buf += discard*2;
|
||||||
|
len -= discard*2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (was_odd) {
|
||||||
|
// Drop a sample so we are in sync with I/Q samples again (hopefully)
|
||||||
|
++buf;
|
||||||
|
--len;
|
||||||
|
++outbuf->dropped;
|
||||||
|
}
|
||||||
|
|
||||||
|
was_odd = (len & 1);
|
||||||
|
slen = len/2;
|
||||||
|
|
||||||
|
if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS/2)) {
|
||||||
|
// FIFO is full. Drop this block.
|
||||||
|
dropping = 1;
|
||||||
|
outbuf->dropped += slen;
|
||||||
|
sampleCounter += slen;
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropping = 0;
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
|
||||||
|
// Compute the sample timestamp and system timestamp for the start of the block
|
||||||
|
outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate;
|
||||||
|
sampleCounter += slen;
|
||||||
|
block_duration = 1e9 * slen / Modes.sample_rate;
|
||||||
|
|
||||||
|
// Get the approx system time for the start of this block
|
||||||
|
clock_gettime(CLOCK_REALTIME, &outbuf->sysTimestamp);
|
||||||
|
outbuf->sysTimestamp.tv_nsec -= block_duration;
|
||||||
|
normalize_timespec(&outbuf->sysTimestamp);
|
||||||
|
|
||||||
|
// Copy trailing data from last block (or reset if not valid)
|
||||||
|
if (outbuf->dropped == 0) {
|
||||||
|
memcpy(outbuf->data, lastbuf->data + lastbuf->length, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
} else {
|
||||||
|
memset(outbuf->data, 0, Modes.trailing_samples * sizeof(uint16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the new data
|
||||||
|
outbuf->length = slen;
|
||||||
|
RTLSDR.converter(buf, &outbuf->data[Modes.trailing_samples], slen, RTLSDR.converter_state, &outbuf->mean_level, &outbuf->mean_power);
|
||||||
|
|
||||||
|
// Push the new data to the demodulation thread
|
||||||
|
pthread_mutex_lock(&Modes.data_mutex);
|
||||||
|
|
||||||
|
Modes.mag_buffers[next_free_buffer].dropped = 0;
|
||||||
|
Modes.mag_buffers[next_free_buffer].length = 0; // just in case
|
||||||
|
Modes.first_free_buffer = next_free_buffer;
|
||||||
|
|
||||||
|
// accumulate CPU while holding the mutex, and restart measurement
|
||||||
|
end_cpu_timing(&rtlsdr_thread_cpu, &Modes.reader_cpu_accumulator);
|
||||||
|
start_cpu_timing(&rtlsdr_thread_cpu);
|
||||||
|
|
||||||
|
pthread_cond_signal(&Modes.data_cond);
|
||||||
|
pthread_mutex_unlock(&Modes.data_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void rtlsdrRun()
|
||||||
|
{
|
||||||
|
if (!RTLSDR.dev) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_cpu_timing(&rtlsdr_thread_cpu);
|
||||||
|
|
||||||
|
while (!Modes.exit) {
|
||||||
|
rtlsdr_read_async(RTLSDR.dev, rtlsdrCallback, NULL,
|
||||||
|
/* MODES_RTL_BUFFERS */ 4,
|
||||||
|
MODES_RTL_BUF_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rtlsdrClose()
|
||||||
|
{
|
||||||
|
if (RTLSDR.dev) {
|
||||||
|
rtlsdr_close(RTLSDR.dev);
|
||||||
|
RTLSDR.dev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RTLSDR.converter) {
|
||||||
|
cleanup_converter(RTLSDR.converter_state);
|
||||||
|
RTLSDR.converter = NULL;
|
||||||
|
RTLSDR.converter_state = NULL;
|
||||||
|
}
|
||||||
|
}
|
31
sdr_rtlsdr.h
Normal file
31
sdr_rtlsdr.h
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Part of dump1090, a Mode S message decoder for RTLSDR devices.
|
||||||
|
//
|
||||||
|
// sdr_rtlsdr.h: rtlsdr dongle support (header)
|
||||||
|
//
|
||||||
|
// Copyright (c) 2016-2017 Oliver Jowett <oliver@mutability.co.uk>
|
||||||
|
// Copyright (c) 2017 FlightAware LLC
|
||||||
|
//
|
||||||
|
// This file is free software: you may copy, redistribute and/or modify it
|
||||||
|
// under the terms of the GNU General Public License as published by the
|
||||||
|
// Free Software Foundation, either version 2 of the License, or (at your
|
||||||
|
// option) any later version.
|
||||||
|
//
|
||||||
|
// This file 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/>.
|
||||||
|
|
||||||
|
#ifndef SDR_RTLSDR_H
|
||||||
|
#define SDR_RTLSDR_H
|
||||||
|
|
||||||
|
void rtlsdrInitConfig();
|
||||||
|
void rtlsdrShowHelp();
|
||||||
|
bool rtlsdrOpen();
|
||||||
|
void rtlsdrRun();
|
||||||
|
void rtlsdrClose();
|
||||||
|
bool rtlsdrHandleOption(int argc, char **argv, int *jptr);
|
||||||
|
|
||||||
|
#endif
|
18
util.c
18
util.c
|
@ -47,7 +47,7 @@
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#include "util.h"
|
#include "dump1090.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
@ -79,3 +79,19 @@ void normalize_timespec(struct timespec *ts)
|
||||||
ts->tv_nsec = (ts->tv_nsec + 1000000000 * adjust) % 1000000000;
|
ts->tv_nsec = (ts->tv_nsec + 1000000000 * adjust) % 1000000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* record current CPU time in start_time */
|
||||||
|
void start_cpu_timing(struct timespec *start_time)
|
||||||
|
{
|
||||||
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, start_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add difference between start_time and the current CPU time to add_to */
|
||||||
|
void end_cpu_timing(const struct timespec *start_time, struct timespec *add_to)
|
||||||
|
{
|
||||||
|
struct timespec end_time;
|
||||||
|
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end_time);
|
||||||
|
add_to->tv_sec += end_time.tv_sec - start_time->tv_sec;
|
||||||
|
add_to->tv_nsec += end_time.tv_nsec - start_time->tv_nsec;
|
||||||
|
normalize_timespec(add_to);
|
||||||
|
}
|
||||||
|
|
6
util.h
6
util.h
|
@ -36,4 +36,10 @@ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2);
|
||||||
struct timespec;
|
struct timespec;
|
||||||
void normalize_timespec(struct timespec *ts);
|
void normalize_timespec(struct timespec *ts);
|
||||||
|
|
||||||
|
/* record current CPU time in start_time */
|
||||||
|
void start_cpu_timing(struct timespec *start_time);
|
||||||
|
|
||||||
|
/* add difference between start_time and the current CPU time to add_to */
|
||||||
|
void end_cpu_timing(const struct timespec *start_time, struct timespec *add_to);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue