// Part of readsb, a Mode-S/ADSB/TIS message decoder. // // sdr_pluto.c: PlutoSDR support // // Copyright (c) 2019 Michael Wolf // // This file is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // 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 #include #include "readsb.h" #include "sdr_plutosdr.h" static struct { input_format_t input_format; int dev_index; struct iio_channel *rx0_i; struct iio_channel *rx0_q; struct iio_buffer *rxbuf; struct iio_context *ctx; struct iio_device *dev; int16_t *readbuf; iq_convert_fn converter; struct converter_state *converter_state; char *uri; char *network; } PLUTOSDR; static struct timespec thread_cpu; void plutosdrInitConfig() { PLUTOSDR.readbuf = NULL; PLUTOSDR.converter = NULL; PLUTOSDR.converter_state = NULL; PLUTOSDR.uri = NULL; PLUTOSDR.network = strdup("pluto.local"); } bool plutosdrHandleOption(int key, char *arg) { switch (key) { case OptPlutoUri: PLUTOSDR.uri = strdup(arg); break; case OptPlutoNetwork: PLUTOSDR.network = strdup(arg); break; default: return false; } return true; } bool plutosdrOpen() { PLUTOSDR.ctx = iio_create_default_context(); if (PLUTOSDR.ctx == NULL && PLUTOSDR.uri != NULL) { PLUTOSDR.ctx = iio_create_context_from_uri(PLUTOSDR.uri); } else if (PLUTOSDR.ctx == NULL) { PLUTOSDR.ctx = iio_create_network_context(PLUTOSDR.network); } if (PLUTOSDR.ctx == NULL) { char buf[1024]; iio_strerror(errno, buf, sizeof(buf)); fprintf(stderr, "plutosdr: Failed creating IIO context: %s\n", buf); return false; } struct iio_scan_context *ctx; struct iio_context_info **info; ctx = iio_create_scan_context(NULL, 0); if (ctx) { int info_count = iio_scan_context_get_info_list(ctx, &info); if(info_count > 0) { fprintf(stderr, "plutosdr: %s\n", iio_context_info_get_description(info[0])); iio_context_info_list_free(info); } iio_scan_context_destroy(ctx); } int device_count = iio_context_get_devices_count(PLUTOSDR.ctx); if (!device_count) { fprintf(stderr, "plutosdr: No supported PLUTOSDR devices found.\n"); plutosdrClose(); } fprintf(stderr, "plutosdr: Context has %d device(s).\n", device_count); PLUTOSDR.dev = iio_context_find_device(PLUTOSDR.ctx, "cf-ad9361-lpc"); if (PLUTOSDR.dev == NULL) { fprintf(stderr, "plutosdr: Error opening the PLUTOSDR device: %s\n", strerror(errno)); plutosdrClose(); } struct iio_channel* phy_chn = iio_device_find_channel(iio_context_find_device(PLUTOSDR.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", (long long)1750000); iio_channel_attr_write_longlong(phy_chn, "sampling_frequency", (long long)Modes.sample_rate); if (Modes.gain == MODES_AUTO_GAIN) { iio_channel_attr_write(phy_chn, "gain_control_mode", "slow_attack"); } else { // We use 10th of dB here, max is 77dB up to 1300MHz if (Modes.gain > 770) Modes.gain = 770; iio_channel_attr_write(phy_chn, "gain_control_mode", "manual"); iio_channel_attr_write_longlong(phy_chn, "hardwaregain", Modes.gain / 10); } iio_channel_attr_write_bool( iio_device_find_channel(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), "altvoltage1", true) , "powerdown", true); // Turn OFF TX LO iio_channel_attr_write_longlong( iio_device_find_channel(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), "altvoltage0", true) , "frequency", (long long)Modes.freq); // Set RX LO frequency PLUTOSDR.rx0_i = iio_device_find_channel(PLUTOSDR.dev, "voltage0", false); if (!PLUTOSDR.rx0_i) PLUTOSDR.rx0_i = iio_device_find_channel(PLUTOSDR.dev, "altvoltage0", false); PLUTOSDR.rx0_q = iio_device_find_channel(PLUTOSDR.dev, "voltage1", false); if (!PLUTOSDR.rx0_q) PLUTOSDR.rx0_q = iio_device_find_channel(PLUTOSDR.dev, "altvoltage1", false); ad9361_set_bb_rate(iio_context_find_device(PLUTOSDR.ctx, "ad9361-phy"), Modes.sample_rate); iio_channel_enable(PLUTOSDR.rx0_i); iio_channel_enable(PLUTOSDR.rx0_q); PLUTOSDR.rxbuf = iio_device_create_buffer(PLUTOSDR.dev, Modes.sdr_buf_samples, false); if (!PLUTOSDR.rxbuf) { perror("plutosdr: Could not create RX buffer"); } if (!(PLUTOSDR.readbuf = cmalloc(Modes.sdr_buf_size * 4))) { fprintf(stderr, "plutosdr: Failed to allocate read buffer\n"); plutosdrClose(); return false; } PLUTOSDR.converter = init_converter(INPUT_SC16, Modes.sample_rate, Modes.dc_filter, &PLUTOSDR.converter_state); if (!PLUTOSDR.converter) { fprintf(stderr, "plutosdr: Can't initialize sample converter\n"); plutosdrClose(); return false; } return true; } static void plutosdrCallback(int16_t *buf, uint32_t len) { struct mag_buf *outbuf; struct mag_buf *lastbuf; uint32_t slen; unsigned next_free_buffer; unsigned free_bufs; int64_t block_duration; static int was_odd = 0; static int dropping = 0; static uint64_t sampleCounter = 0; int64_t sysMicroseconds = mono_micro_seconds(); int64_t sysTimestamp = mstime(); lockReader(); 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; if (len != Modes.sdr_buf_size) { fprintf(stderr, "weirdness: plutosdr gave us a block with an unusual size (got %u bytes, expected %u bytes)\n", (unsigned) len, (unsigned) Modes.sdr_buf_size); if (len > Modes.sdr_buf_size) { unsigned discard = (len - Modes.sdr_buf_size + 1) / 2; outbuf->dropped += discard; buf += discard * 2; len -= discard * 2; } } if (was_odd) { ++buf; --len; ++outbuf->dropped; } was_odd = (len & 1); slen = len / 2; if (free_bufs == 0 || (dropping && free_bufs < MODES_MAG_BUFFERS / 2)) { dropping = 1; outbuf->dropped += slen; sampleCounter += slen; unlockReader(); return; } dropping = 0; unlockReader(); outbuf->sampleTimestamp = sampleCounter * 12e6 / Modes.sample_rate; sampleCounter += slen; block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = sysTimestamp; outbuf->sysMicroseconds = sysMicroseconds; outbuf->sysTimestamp -= block_duration; outbuf->sysMicroseconds -= block_duration * 1000; 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)); } outbuf->length = slen; PLUTOSDR.converter(buf, &outbuf->data[Modes.trailing_samples], slen, PLUTOSDR.converter_state, &outbuf->mean_level, &outbuf->mean_power); lockReader(); Modes.mag_buffers[next_free_buffer].dropped = 0; Modes.mag_buffers[next_free_buffer].length = 0; Modes.first_free_buffer = next_free_buffer; end_cpu_timing(&thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&thread_cpu); wakeDecode(); unlockReader(); } void plutosdrRun() { void *p_dat, *p_end; ptrdiff_t p_inc; if (!PLUTOSDR.dev) { return; } start_cpu_timing(&thread_cpu); while (!Modes.exit) { int16_t *p = PLUTOSDR.readbuf; uint32_t len = (uint32_t) iio_buffer_refill(PLUTOSDR.rxbuf) / 2; p_inc = iio_buffer_step(PLUTOSDR.rxbuf); p_end = iio_buffer_end(PLUTOSDR.rxbuf); p_dat = iio_buffer_first(PLUTOSDR.rxbuf, PLUTOSDR.rx0_i); for (p_dat = iio_buffer_first(PLUTOSDR.rxbuf, PLUTOSDR.rx0_i); p_dat < p_end; p_dat += p_inc) { *p++ = ((int16_t*) p_dat)[0]; // Real (I) *p++ = ((int16_t*) p_dat)[1]; // Imag (Q) } plutosdrCallback(PLUTOSDR.readbuf, len); } } void plutosdrClose() { if(PLUTOSDR.readbuf) { free(PLUTOSDR.readbuf); } if (PLUTOSDR.rxbuf) { iio_buffer_destroy(PLUTOSDR.rxbuf); } if (PLUTOSDR.rx0_i) { iio_channel_disable(PLUTOSDR.rx0_i); } if (PLUTOSDR.rx0_q) { iio_channel_disable(PLUTOSDR.rx0_q); } if (PLUTOSDR.ctx) { iio_context_destroy(PLUTOSDR.ctx); } free(PLUTOSDR.network); free(PLUTOSDR.uri); }