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