From 8427f3257f7092541376a548c0070ae078329dab Mon Sep 17 00:00:00 2001 From: Deepak Shandilya Date: Sun, 2 Feb 2025 00:15:42 -0700 Subject: [PATCH] Add support for IIO/AD9361 based devices (e.g. ADALM PlutoSDR, LibreSDR etc.) --- Makefile | 16 ++- dump1090.h | 2 +- sdr.c | 6 + sdr_ad9361.c | 315 +++++++++++++++++++++++++++++++++++++++++++++++++++ sdr_ad9361.h | 32 ++++++ 5 files changed, 369 insertions(+), 2 deletions(-) create mode 100644 sdr_ad9361.c create mode 100644 sdr_ad9361.h diff --git a/Makefile b/Makefile index abe5b0656..4d422ff98 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,11 @@ ifeq ($(PKGCONFIG), yes) ifndef SOAPYSDR SOAPYSDR := $(shell pkg-config --exists SoapySDR && echo "yes" || echo "no") endif + + ifndef AD9361SDR + AD9361SDR := $(shell pkg-config --exists libiio libad9361 && echo "yes" || echo "no") + endif + else # pkg-config not available. Only use explicitly enabled libraries. RTLSDR ?= no @@ -42,6 +47,7 @@ else HACKRF ?= no LIMESDR ?= no SOAPYSDR ?= no + AD9361SDR ?= no endif BUILD_UNAME := $(shell uname) @@ -54,7 +60,7 @@ ifeq ($(UNAME), Linux) DUMP1090_CPPFLAGS += -D_DEFAULT_SOURCE LIBS += -lrt LIBS_USB += -lusb-1.0 - LIBS_CURSES := -lncurses + LIBS_CURSES := -lncurses -ltinfo CPUFEATURES ?= yes endif @@ -167,6 +173,13 @@ ifeq ($(SOAPYSDR), yes) LIBS_SDR += $(shell pkg-config --libs SoapySDR) endif +ifeq ($(AD9361SDR), yes) + SDR_OBJ += sdr_ad9361.o + DUMP1090_CPPFLAGS += -DENABLE_LIBIIO -DENABLE_AD9361 + DUMP1090_CFLAGS += -DENABLE_LIBIIO -DENABLE_AD9361 + DUMP1090_CFLAGS += $(shell pkg-config --cflags libiio libad9361) + LIBS_SDR += $(shell pkg-config --libs libiio libad9361) +endif ## ## starch (runtime DSP code selection) mix, architecture-specific @@ -218,6 +231,7 @@ showconfig: @echo " HackRF support: $(HACKRF)" >&2 @echo " LimeSDR support: $(LIMESDR)" >&2 @echo " SoapySDR support: $(SOAPYSDR)" >&2 + @echo " AD9361 support: $(AD9361SDR)" >&2 %.o: %.c *.h $(CC) $(ALL_CCFLAGS) -c $< -o $@ diff --git a/dump1090.h b/dump1090.h index 46fe55f28..8937daa53 100644 --- a/dump1090.h +++ b/dump1090.h @@ -299,7 +299,7 @@ typedef enum { //======================== structure declarations ========================= typedef enum { - SDR_NONE, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF, SDR_HACKRF, SDR_LIMESDR, SDR_SOAPYSDR + SDR_NONE, SDR_IFILE, SDR_RTLSDR, SDR_BLADERF, SDR_HACKRF, SDR_LIMESDR, SDR_SOAPYSDR, SDR_AD9361 } sdr_type_t; // Program global state diff --git a/sdr.c b/sdr.c index 765d2edb7..8f86f6908 100644 --- a/sdr.c +++ b/sdr.c @@ -36,6 +36,9 @@ #ifdef ENABLE_SOAPYSDR # include "sdr_soapy.h" #endif +#ifdef ENABLE_AD9361 +# include "sdr_ad9361.h" +#endif typedef struct { const char *name; @@ -134,6 +137,9 @@ static sdr_handler sdr_handlers[] = { #ifdef ENABLE_SOAPYSDR { "soapy", SDR_SOAPYSDR, soapyInitConfig, soapyShowHelp, soapyHandleOption, soapyOpen, soapyRun, noStop, soapyClose, soapyGetGain, soapyGetMaxGain, soapyGetGainDb, soapySetGain }, #endif +#ifdef ENABLE_AD9361 + { "ad9361", SDR_AD9361, ad9361InitConfig, ad9361ShowHelp, ad9361HandleOption, ad9361Open, ad9361Run, noStop, ad9361Close, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, +#endif { "none", SDR_NONE, noInitConfig, noShowHelp, noHandleOption, noOpen, noRun, noStop, noClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, { "ifile", SDR_IFILE, ifileInitConfig, ifileShowHelp, ifileHandleOption, ifileOpen, ifileRun, noStop, ifileClose, noGetGain, noGetMaxGain, noGetGainDb, noSetGain }, diff --git a/sdr_ad9361.c b/sdr_ad9361.c new file mode 100644 index 000000000..ec26f164c --- /dev/null +++ b/sdr_ad9361.c @@ -0,0 +1,315 @@ +// Part of dump1090, a Mode S message decoder +// +// sdr_ad9361.c: AD9361 One support +// +// Copyright (c) 2019 FlightAware LLC +// Copyright (c) 2025 Deepak Shandilya (deepakshekar@gmail.com) +// +// 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_ad9361.h" + +#include +#include +#include + +#define AD9361_DEFAULT_RATE 2400000 +#define AD9361_DEFAULT_FREQ 1090000000 +#define AD9361_DEFAULT_WIDTH 1000 +#define AD9361_DEFAULT_HEIGHT 700 +#define AD9361_ASYNC_BUF_NUMBER 12 +#define AD9361_DATA_LEN (32*16384) /* 512k */ +#define AD9361_AUTO_GAIN -100 /* Use automatic gain. */ +#define AD9361_MAX_GAIN 70 /* Use max available gain. */ + + +static struct +{ + struct iio_device *device; + struct iio_context *ctx; + int dev_index; + uint64_t freq; + int gain; + int enable_agc; + uint32_t samplerate; + struct iio_channel *rx0_i; + struct iio_channel *rx0_q; + struct iio_buffer *rxbuf; + + iq_convert_fn converter; + struct converter_state *converter_state; +} AD9361; + +void ad9361InitConfig() +{ + AD9361.device = NULL; + AD9361.ctx = NULL; + AD9361.freq = Modes.freq; + AD9361.enable_agc = 1; + AD9361.gain = AD9361_AUTO_GAIN; + AD9361.samplerate = AD9361_DEFAULT_RATE; + AD9361.converter = NULL; + AD9361.converter_state = NULL; +} + +bool ad9361HandleOption(int argc, char **argv, int *jptr) +{ + int j = *jptr; + bool more = (j + 1 < argc); + + if (!strcmp (argv[j], "--host") && more) { + Modes.dev_name = strdup (argv[++j]); + } + else if (!strcmp (argv[j], "--samplerate") && more) { + AD9361.samplerate = atoi (argv[++j]); + } + else if (!strcmp (argv[j], "--set-gain") && more) { + AD9361.gain = atoi (argv[++j]); + } + else if (!strcmp (argv[j], "--9361freq") && more) { + AD9361.freq = atoi (argv[++j]); + } + else { + return false; + } + + *jptr = j; + return true; +} + +void ad9361ShowHelp() +{ + printf(" AD9361-specific options (use with --device-type ad9361)\n"); + printf("\n"); + printf("--host device string : e.g ip:pluto.local or ip:\n"); + printf("--samplerate set sample rate (default: 2400000Hz)\n"); + printf("--set-gain set gain : -100 for auto or upto 70 for manual gain\n"); + printf("--9361freq set frequency (default : 1090000000 Hz)\n"); + printf("\n"); +} + +static void show_config() +{ + fprintf (stderr, "* Frequency : %" PRIu64 "\n", AD9361.freq); + fprintf (stderr, "* Sample Rate : %u \n", AD9361.samplerate); + fprintf (stderr, "* Gain (-100 = AGC) : %d \n", AD9361.gain); + if (NULL != Modes.dev_name) { + fprintf (stderr, "* Device : %s\n", Modes.dev_name); + } + else + fprintf (stderr, "* Device : Local\n"); +} + +bool ad9361Open() +{ + if (AD9361.device) { + return true; + } + + int device_count; + + AD9361.ctx = NULL; + fprintf (stderr, "* Acquiring IIO context\n"); + if (NULL != Modes.dev_name) { + AD9361.ctx = iio_create_network_context (Modes.dev_name); + } + if (AD9361.ctx == NULL) { + AD9361.ctx = iio_create_default_context (); + } + if (AD9361.ctx == NULL) { + AD9361.ctx = iio_create_network_context ("pluto.local"); + } + if (NULL == AD9361.ctx) return false; + + device_count = iio_context_get_devices_count (AD9361.ctx); + + if (!device_count) { + fprintf (stderr, "No supported AD9361 devices found.\n"); + return false; + } + + fprintf (stderr, "Found %d device(s):\n", device_count); + + fprintf (stderr, "* Acquiring AD9361 streaming devices\n"); + + AD9361.device = iio_context_find_device (AD9361.ctx, "cf-ad9361-lpc"); + + if (AD9361.device == NULL) { + fprintf (stderr, "Error opening the PLUTOSDR device: %s\n", strerror (errno)); + return false; + } + + fprintf (stderr, "* Acquiring AD9361 phy channel 0\n"); + + struct iio_channel *phy_chn = + iio_device_find_channel (iio_context_find_device + (AD9361.ctx, "ad9361-phy"), "voltage0", false); + + iio_channel_attr_write (phy_chn, "rf_port_select", "A_BALANCED"); + iio_channel_attr_write_longlong (phy_chn, "rf_bandwidth", + AD9361.samplerate); + iio_channel_attr_write_longlong (phy_chn, "sampling_frequency", + AD9361.samplerate); + + struct iio_channel *lo_chn = + iio_device_find_channel (iio_context_find_device + (AD9361.ctx, "ad9361-phy"), "altvoltage0", true); + + iio_channel_attr_write_longlong (lo_chn, "frequency", AD9361.freq); + + fprintf (stderr, "* Initializing AD9361 IIO streaming channels\n"); + + AD9361.rx0_i = iio_device_find_channel (AD9361.device, "voltage0", false); + + if (!AD9361.rx0_i) AD9361.rx0_i = iio_device_find_channel (AD9361.device, "altvoltage0", false); + + AD9361.rx0_q = iio_device_find_channel (AD9361.device, "voltage1", false); + + if (!AD9361.rx0_q) AD9361.rx0_q = iio_device_find_channel (AD9361.device, "altvoltage1", false); + + ad9361_set_bb_rate (iio_context_find_device (AD9361.ctx, "ad9361-phy"), AD9361.samplerate); + + fprintf (stderr, "* Enabling IIO streaming channels\n"); + + iio_channel_enable (AD9361.rx0_i); + iio_channel_enable (AD9361.rx0_q); + + fprintf (stderr, "* Creating non-cyclic IIO buffers \n"); + + AD9361.rxbuf = + iio_device_create_buffer (AD9361.device, AD9361_DATA_LEN / 4, false); + + if (!AD9361.rxbuf) { + fprintf (stderr, "Could not create RX buffer"); + return false; + } + + if (AD9361.gain == AD9361_AUTO_GAIN) { + iio_channel_attr_write (phy_chn, "gain_control_mode", "slow_attack"); + fprintf (stderr, "* Using AGC\r\n"); + } + else { + if (AD9361.gain > AD9361_MAX_GAIN) AD9361.gain = AD9361_MAX_GAIN; + iio_channel_attr_write (phy_chn, "gain_control_mode", "manual"); + iio_channel_attr_write_longlong (phy_chn, "hardwaregain", AD9361.gain); + } + + show_config(); + + AD9361.converter = init_converter (INPUT_SC16, + Modes.sample_rate, + Modes.dc_filter, + &AD9361.converter_state); + + if (!AD9361.converter) { + fprintf (stderr, "AD9361: can't initialize sample converter\n"); + return false; + } + + return true; +} + +void ad9361Run() +{ + void *p_dat; + static unsigned dropped = 0; + static unsigned sampleCounter = 0; + size_t sample_size = iio_device_get_sample_size (AD9361.device); + + if (!AD9361.device) { + return; + } + + while (!Modes.exit) { + ssize_t bytes_read = iio_buffer_refill (AD9361.rxbuf); + p_dat = iio_buffer_first (AD9361.rxbuf, AD9361.rx0_i); + int samples_read = bytes_read / sample_size; + struct mag_buf *outbuf = NULL; + outbuf = fifo_acquire ( /* don't wait */ 0); + + if (!outbuf) { + // FIFO is full. Drop this block. + fprintf (stderr, "FIFO is full \r\n"); + dropped += samples_read; + sampleCounter += samples_read; + return; + } + + outbuf->flags = 0; + if (dropped) { + // We previously dropped some samples due to no buffers being available + fprintf (stderr, "Was previously dropped \r\n"); + outbuf->flags |= MAGBUF_DISCONTINUOUS; + outbuf->dropped = dropped; + } + + dropped = 0; + + // Compute the sample timestamp and system timestamp for the start of the block + outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; + sampleCounter += samples_read; + + // Get the approx system time for the start of this block + uint64_t block_duration = 1e3 * samples_read / Modes.sample_rate; + outbuf->sysTimestamp = mstime () - block_duration; + + // Convert the new data + unsigned int to_convert = samples_read; + if (to_convert + outbuf->overlap > outbuf->totalLength) { + // how did that happen? + fprintf (stderr, "Overlap overflow \r\n"); + to_convert = outbuf->totalLength - outbuf->overlap; + dropped = samples_read - to_convert; + } + + AD9361.converter (p_dat, &outbuf->data[outbuf->overlap], to_convert, + AD9361.converter_state, &outbuf->mean_level, + &outbuf->mean_power); + + outbuf->validLength = outbuf->overlap + to_convert; + + // Push to the demodulation thread + fifo_enqueue (outbuf); + } +} + +void ad9361Close() +{ + if (AD9361.device) { + fprintf (stderr, "* Destroying buffers\n"); + + if (AD9361.rxbuf) { + iio_buffer_destroy (AD9361.rxbuf); + } + + fprintf (stderr, "* Disabling streaming channels\n"); + + if (AD9361.rx0_i) { + iio_channel_disable (AD9361.rx0_i); + } + + if (AD9361.rx0_q) { + iio_channel_disable (AD9361.rx0_q); + } + + fprintf (stderr, "* Destroying context\n"); + + if (AD9361.ctx) { + iio_context_destroy (AD9361.ctx); + } + + AD9361.device = NULL; + } +} diff --git a/sdr_ad9361.h b/sdr_ad9361.h new file mode 100644 index 000000000..87b01ced8 --- /dev/null +++ b/sdr_ad9361.h @@ -0,0 +1,32 @@ +// Part of dump1090, a Mode S message decoder +// +// sdr_ad9361.h: AD9361 One support (header) +// +// Copyright (c) 2019 FlightAware LLC +// Copyright (c) 2025 Deepak Shandilya (deepakshekar@gmail.com) +// +// 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 AD9361_H +#define AD9361_H + +void ad9361InitConfig(); +void ad9361ShowHelp(); +bool ad9361HandleOption(int argc, char **argv, int *jptr); +bool ad9361Open(); +void ad9361Run(); +void ad9361Close(); + +#endif