diff --git a/Makefile b/Makefile index a09c267..a7d4c04 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,11 @@ PROGNAME=dump1090 +RTLSDR ?= yes +BLADERF ?= yes + CC=gcc 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),"") CPPFLAGS += -DHTMLPATH=\"$(HTMLPATH)\" endif @@ -16,10 +14,30 @@ DIALECT = -std=c11 CFLAGS += $(DIALECT) -O2 -g -Wall -Werror -W -D_DEFAULT_SOURCE LIBS = -lpthread -lm -lrt -ifeq ($(STATIC), yes) -LIBS_RTLSDR = -Wl,-Bstatic -lrtlsdr -Wl,-Bdynamic -lusb-1.0 -else -LIBS_RTLSDR = -lrtlsdr -lusb-1.0 +ifeq ($(RTLSDR), yes) + SDR_OBJ += sdr_rtlsdr.o + CPPFLAGS += -DENABLE_RTLSDR + + 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 all: dump1090 view1090 @@ -27,8 +45,8 @@ all: dump1090 view1090 %.o: %.c *.h $(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) - $(CC) -g -o $@ $^ $(LDFLAGS) $(LIBS) $(LIBS_RTLSDR) -lncurses +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_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) $(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) 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 ./cprtests @@ -47,3 +65,9 @@ cprtests: cpr.o cprtests.o crctests: crc.c crc.h $(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 diff --git a/README.md b/README.md index 8870566..a1dd06b 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,43 @@ It is designed to build as a Debian package. ## 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") ## 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") +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. diff --git a/bladerf/README b/bladerf/README new file mode 100644 index 0000000..672b83d --- /dev/null +++ b/bladerf/README @@ -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. diff --git a/bladerf/decimate8-x40.rbf b/bladerf/decimate8-x40.rbf new file mode 100644 index 0000000..392a2c1 Binary files /dev/null and b/bladerf/decimate8-x40.rbf differ diff --git a/convert.c b/convert.c index 0c02b72..fc2a7ae 100644 --- a/convert.c +++ b/convert.c @@ -26,6 +26,36 @@ struct converter_state { 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, uint16_t *mag_data, unsigned nsamples, @@ -42,55 +72,33 @@ static void convert_uc8_nodc(void *iq_data, MODES_NOTUSED(state); // 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) { - 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; - - 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; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; + DO_ONE_SAMPLE; } for (i = 0; i < (nsamples&7); ++i) { - mag = Modes.maglut[*in++]; - *mag_data++ = mag; - sum_level += mag; - sum_power += (uint32_t)mag * (uint32_t)mag; + DO_ONE_SAMPLE; } +#undef DO_ONE_SAMPLE + if (out_mean_level) { *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) { I = *in++; Q = *in++; - fI = (I - 127.5) / 127.5; - fQ = (Q - 127.5) / 127.5; + fI = (I - 127.5f) / 127.5f; + fQ = (Q - 127.5f) / 127.5f; // DC block 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); sum_power += magsq; 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; @@ -173,8 +181,8 @@ static void convert_sc16_generic(void *iq_data, for (i = 0; i < nsamples; ++i) { I = (int16_t)le16toh(*in++); Q = (int16_t)le16toh(*in++); - fI = I / 32768.0; - fQ = Q / 32768.0; + fI = I / 32768.0f; + fQ = Q / 32768.0f; // DC block 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); sum_power += magsq; 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; @@ -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, uint16_t *mag_data, unsigned nsamples, @@ -225,8 +395,8 @@ static void convert_sc16q11_generic(void *iq_data, for (i = 0; i < nsamples; ++i) { I = (int16_t)le16toh(*in++); Q = (int16_t)le16toh(*in++); - fI = I / 2048.0; - fQ = Q / 2048.0; + fI = I / 2048.0f; + fQ = Q / 2048.0f; // DC block 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); sum_power += magsq; 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; @@ -261,13 +431,20 @@ static struct { int can_filter_dc; iq_convert_fn fn; const char *description; + bool (*init)(); } converters_table[] = { // In order of preference - { INPUT_UC8, 0, convert_uc8_nodc, "UC8, integer/table path" }, - { INPUT_UC8, 1, convert_uc8_generic, "UC8, float path" }, - { INPUT_SC16, 1, convert_sc16_generic, "SC16, float path" }, - { INPUT_SC16Q11, 1, convert_sc16q11_generic, "SC16Q11, float path" }, - { 0, 0, NULL, NULL } + { INPUT_UC8, 0, convert_uc8_nodc, "UC8, integer/table path", init_uc8_lookup }, + { INPUT_UC8, 1, convert_uc8_generic, "UC8, float path", NULL }, + { INPUT_SC16, 0, convert_sc16_nodc, "SC16, float path, no DC", NULL }, + { INPUT_SC16, 1, convert_sc16_generic, "SC16, float path", 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, @@ -291,7 +468,10 @@ iq_convert_fn init_converter(input_format_t format, 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)); if (! *out_state) { diff --git a/convert_benchmark.c b/convert_benchmark.c new file mode 100644 index 0000000..24b1bc1 --- /dev/null +++ b/convert_benchmark.c @@ -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 +// 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 . + +#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); +} diff --git a/debian-wheezy/rules b/debian-wheezy/rules index e27a224..944454c 100755 --- a/debian-wheezy/rules +++ b/debian-wheezy/rules @@ -22,7 +22,7 @@ install_rtlsdr: cd rtl-sdr-build && cmake ../rtl-sdr -DCMAKE_INSTALL_PREFIX=./install && make install 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: dh_auto_test -- DIALECT=-std=gnu1x diff --git a/debian/changelog b/debian/changelog index eab6c6d..9c7042a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -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 Thu, 09 Feb 2017 17:41:43 +0000 + dump1090-fa (3.3.0) stable; urgency=medium * Improvements to the Mode A/C demodulator. diff --git a/debian/control b/debian/control index 1ee5bdb..bac874a 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: dump1090-fa Section: embedded Priority: extra Maintainer: Oliver Jowett -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 Homepage: https://github.com/mutability/dump1090 Vcs-Git: https://github.com/mutability/dump1090.git @@ -19,7 +19,7 @@ Description: transitional dummy package for dump1090 Package: dump1090-fa 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) Breaks: dump1090 (<< 3.0) Description: ADS-B Ground Station System for RTL-SDR diff --git a/debian/dump1090-fa.install b/debian/dump1090-fa.install index 51f96d5..ad434ce 100644 --- a/debian/dump1090-fa.install +++ b/debian/dump1090-fa.install @@ -1,2 +1,3 @@ public_html/* usr/share/dump1090-fa/html debian/lighttpd/* etc/lighttpd/conf-available +bladerf/* /usr/share/dump1090-fa/bladerf diff --git a/debian/dump1090-fa.postinst b/debian/dump1090-fa.postinst index 8bb872d..5608149 100644 --- a/debian/dump1090-fa.postinst +++ b/debian/dump1090-fa.postinst @@ -31,6 +31,9 @@ case "$1" in adduser --system --home /usr/share/$NAME --no-create-home --quiet "$RUNAS" fi + # plugdev required for bladeRF USB access + adduser "$RUNAS" plugdev + # set up lighttpd echo "Enabling lighttpd integration.." >&2 lighty-enable-mod dump1090-fa || true diff --git a/debian/rules b/debian/rules index c8a2de0..e8279ea 100755 --- a/debian/rules +++ b/debian/rules @@ -12,13 +12,15 @@ export DH_VERBOSE=1 export DEB_BUILD_MAINT_OPTIONS = hardening=+all 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') -SRCNAME=$(shell dpkg-parsechangelog | sed -n 's/^Source: //p') +ifeq ($(DEB_HOST_ARCH),armhf) + # 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: - 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: dh_install diff --git a/dump1090.c b/dump1090.c index 64302ec..82eda50 100644 --- a/dump1090.c +++ b/dump1090.c @@ -49,12 +49,8 @@ #include "dump1090.h" -#include - #include -static int verbose_device_search(char *s); - // // ============================= Utility functions ========================== // @@ -96,21 +92,6 @@ static void sigtermHandler(int dummy) { 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) { 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 Modes.gain = MODES_MAX_GAIN; Modes.freq = MODES_DEFAULT_FREQ; - Modes.ppm_error = MODES_DEFAULT_PPM; Modes.check_crc = 1; Modes.net_heartbeat_interval = MODES_NET_HEARTBEAT_INTERVAL; Modes.net_input_raw_ports = strdup("30001"); @@ -142,12 +122,14 @@ void modesInitConfig(void) { Modes.json_location_accuracy = 1; Modes.maxRange = 1852 * 300; // 300NM default max range Modes.mode_ac_auto = 1; + + sdrInitConfig(); } // //========================================================================= // void modesInit(void) { - int i, q; + int i; pthread_mutex_init(&Modes.data_mutex,NULL); pthread_cond_init(&Modes.data_cond,NULL); @@ -157,8 +139,7 @@ void modesInit(void) { // Allocate the various buffers used by Modes 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) || - ((Modes.log10lut = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256) ) == NULL) ) + if ( ((Modes.log10lut = (uint16_t *) malloc(sizeof(uint16_t) * 256 * 256) ) == NULL) ) { fprintf(stderr, "Out of memory allocating data buffer.\n"); exit(1); @@ -202,21 +183,6 @@ void modesInit(void) { if (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) Modes.log10lut[0] = 0; // poorly defined.. for (i = 1; i <= 65535; i++) { @@ -227,138 +193,11 @@ void modesInit(void) { modesChecksumInit(Modes.nfix_crc); icaoFilterInit(); modeACInit(); - interactiveInit(); if (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. // -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 // -void *readerThreadEntryPoint(void *arg) { +void *readerThreadEntryPoint(void *arg) +{ MODES_NOTUSED(arg); - start_cpu_timing(&reader_thread_start); // we accumulate in rtlsdrCallback() or readDataFromFile() - - 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(); - } + sdrRun(); // Wake the main thread (if it's still waiting) pthread_mutex_lock(&Modes.data_mutex); @@ -648,17 +260,36 @@ void snipMode(int level) { // ================================ Main ==================================== // 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( -"-----------------------------------------------------------------------------\n" -"| dump1090 ModeS Receiver %45s |\n" -"-----------------------------------------------------------------------------\n" -"--device-index Select RTL device (default: 0)\n" +" Common options\n" +"\n" "--gain Set gain (default: max gain. Use -10 for auto-gain)\n" -"--enable-agc Enable the Automatic Gain Control (default: off)\n" "--freq Set frequency (default: 1090 Mhz)\n" -"--ifile Read data from file (use '-' for stdin)\n" -"--iformat 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-ttl Remove from list if idle for (default: 60)\n" "--raw Show only messages hex values\n" @@ -698,12 +329,11 @@ void showHelp(void) { "--debug Debug mode (verbose), see README for details\n" "--quiet Disable output to stdout. Use for daemon applications\n" "--show-only Show only messages from the given ICAO on stdout\n" -"--ppm Set receiver error in parts per million (default 0)\n" "--html-dir Use as base directory for the internal HTTP server. Defaults to " HTMLPATH "\n" "--write-json Periodically write json output to (for serving by a separate webserver)\n" "--write-json-every Write json output every t seconds (default 1)\n" "--json-location-accuracy 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" "\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" " p = Log frames with bad preamble\n" " n = Log network debugging info\n" -" j = Log frames to frames.js, loadable by debug.html\n", -MODES_DUMP1090_VARIANT " " MODES_DUMP1090_VERSION +" j = Log frames to frames.js, loadable by debug.html\n" ); } @@ -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++) { 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]); } else if (!strcmp(argv[j],"--gain") && more) { 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")) { Modes.dc_filter = 1; } 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"); } else if (!strcmp(argv[j],"--net-only")) { Modes.net = 1; - Modes.net_only = 1; + Modes.sdr_type = SDR_NONE; } else if (!strcmp(argv[j],"--net-heartbeat") && more) { Modes.net_heartbeat_interval = (uint64_t)(1000 * atof(argv[++j])); } 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"); #endif } else if (!strcmp(argv[j],"--interactive")) { - Modes.interactive = Modes.throttle = 1; - } else if (!strcmp(argv[j],"--throttle")) { - Modes.throttle = 1; + Modes.interactive = 1; } else if (!strcmp(argv[j],"--interactive-ttl") && more) { Modes.interactive_display_ttl = (uint64_t)(1000 * atof(argv[++j])); } else if (!strcmp(argv[j],"--lat") && more) { @@ -1049,8 +597,6 @@ int main(int argc, char **argv) { } else if (!strcmp(argv[j],"--help")) { showHelp(); exit(0); - } else if (!strcmp(argv[j],"--ppm") && more) { - Modes.ppm_error = atoi(argv[++j]); } else if (!strcmp(argv[j],"--quiet")) { Modes.quiet = 1; } 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) { Modes.json_location_accuracy = atoi(argv[++j]); #endif + } else if (sdrHandleOption(argc, argv, &j)) { + /* handled */ } else { fprintf(stderr, "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); modesInit(); - if (Modes.net_only) { - fprintf(stderr,"Net-only mode, no RTL device or file open.\n"); - } else if (Modes.filename == NULL) { - if (modesInitRTLSDR() < 0) { - exit(1); - } - } else { - if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') { - Modes.fd = STDIN_FILENO; - } 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 (!sdrOpen()) { + exit(1); + } + + if (Modes.net) { + modesInitNet(); } - if (Modes.net) modesInitNet(); // init stats: Modes.stats_current.start = Modes.stats_current.end = @@ -1126,9 +660,11 @@ int main(int argc, char **argv) { writeJsonToFile("stats.json", generateStatsJson); writeJsonToFile("aircraft.json", generateAircraftJson); + interactiveInit(); + // If the user specifies --net-only, just run in order to serve network // clients without reading data from the RTL device - if (Modes.net_only) { + if (Modes.sdr_type == SDR_NONE) { while (!Modes.exit) { struct timespec start_time; @@ -1145,7 +681,7 @@ int main(int argc, char **argv) { pthread_mutex_lock(&Modes.data_mutex); pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL); - while (Modes.exit == 0) { + while (!Modes.exit) { struct timespec start_time; 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. pthread_mutex_unlock(&Modes.data_mutex); 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; } } @@ -1228,9 +764,10 @@ int main(int argc, char **argv) { display_total_stats(); } - cleanup_converter(Modes.converter_state); log_with_timestamp("Normal exit."); + sdrClose(); + #ifndef _WIN32 pthread_exit(0); #else diff --git a/dump1090.h b/dump1090.h index a6490c5..07c2247 100644 --- a/dump1090.h +++ b/dump1090.h @@ -65,6 +65,7 @@ #include #include #include + #include #include #include #include @@ -84,12 +85,8 @@ #include "compat/compat.h" -// Avoid a dependency on rtl-sdr except where it's really needed. -typedef struct rtlsdr_dev rtlsdr_dev_t; - // ============================= #defines =============================== -#define MODES_DEFAULT_PPM 52 #define MODES_DEFAULT_FREQ 1090000000 #define MODES_DEFAULT_WIDTH 1000 #define MODES_DEFAULT_HEIGHT 700 @@ -245,9 +242,14 @@ typedef enum { #include "cpr.h" #include "icao_filter.h" #include "convert.h" +#include "sdr.h" //======================== structure declarations ========================= +typedef enum { + SDR_NONE, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF +} sdr_type_t; + // Structure representing one magnitude buffer struct mag_buf { 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 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 int exit; // Exit from the main loop when true // Sample conversion int dc_filter; // should we apply a DC filter? - iq_convert_fn converter_function; - struct converter_state *converter_state; // RTLSDR char * dev_name; int gain; - int enable_agc; - rtlsdr_dev_t *dev; int freq; - int ppm_error; // Networking char aneterr[ANET_ERR_LEN]; @@ -308,7 +302,7 @@ struct { // Internal state #endif // 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 check_crc; // Only display messages with good CRC 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 char *html_dir; // Path to www base directory. 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; struct { diff --git a/public_html/script.js b/public_html/script.js index dc45eb6..b422063 100644 --- a/public_html/script.js +++ b/public_html/script.js @@ -1609,7 +1609,7 @@ function getFlightAwareModeSLink(code, ident, linkText) { function getFlightAwarePhotoLink(registration) { if (registration !== null && registration !== "") { - return "See Aircraft Photos"; + return "See Aircraft Photos"; } return ""; diff --git a/sdr.c b/sdr.c new file mode 100644 index 0000000..f37d3e8 --- /dev/null +++ b/sdr.c @@ -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 +// 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 . + +#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 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(); +} diff --git a/sdr.h b/sdr.h new file mode 100644 index 0000000..072da66 --- /dev/null +++ b/sdr.h @@ -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 +// 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 . + +#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 diff --git a/sdr_bladerf.c b/sdr_bladerf.c new file mode 100644 index 0000000..068da48 --- /dev/null +++ b/sdr_bladerf.c @@ -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 . + +#include "dump1090.h" +#include "sdr_bladerf.h" + +#include +#include + +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 select device by bladeRF 'device identifier'\n"); + printf("--bladerf-fpga use alternative FPGA bitstream ('' to disable FPGA load)\n"); + printf("--bladerf-decimation assume FPGA decimates by a factor of N\n"); + printf("--bladerf-bandwidth 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; + } +} diff --git a/sdr_bladerf.h b/sdr_bladerf.h new file mode 100644 index 0000000..39de012 --- /dev/null +++ b/sdr_bladerf.h @@ -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 . + +#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 diff --git a/sdr_ifile.c b/sdr_ifile.c new file mode 100644 index 0000000..6f7750c --- /dev/null +++ b/sdr_ifile.c @@ -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 +// 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 . + +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// 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 read samples from given file ('-' for stdin)\n"); + printf("--iformat 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; + } +} diff --git a/sdr_ifile.h b/sdr_ifile.h new file mode 100644 index 0000000..6652426 --- /dev/null +++ b/sdr_ifile.h @@ -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 +// 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 . + +#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 diff --git a/sdr_rtlsdr.c b/sdr_rtlsdr.c new file mode 100644 index 0000000..4c3ae02 --- /dev/null +++ b/sdr_rtlsdr.c @@ -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 +// 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 . + +// This file incorporates work covered by the following copyright and +// permission notice: +// +// Copyright (C) 2012 by Salvatore Sanfilippo +// +// 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 + +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 select device by index or serial number\n"); + printf("--enable-agc enable digital AGC (not tuner AGC!)\n"); + printf("--ppm 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; + } +} diff --git a/sdr_rtlsdr.h b/sdr_rtlsdr.h new file mode 100644 index 0000000..317b2e4 --- /dev/null +++ b/sdr_rtlsdr.h @@ -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 +// 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 . + +#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 diff --git a/util.c b/util.c index b66f7e1..3711237 100644 --- a/util.c +++ b/util.c @@ -47,7 +47,7 @@ // (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 "util.h" +#include "dump1090.h" #include #include @@ -79,3 +79,19 @@ void normalize_timespec(struct timespec *ts) 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); +} diff --git a/util.h b/util.h index 41ccbb1..51405b4 100644 --- a/util.h +++ b/util.h @@ -36,4 +36,10 @@ int64_t receiveclock_ns_elapsed(uint64_t t1, uint64_t t2); struct timespec; 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