// Part of dump1090, a Mode S message decoder for RTLSDR devices. // // sdr_limesdr.c: LimeSDR dongle support // // Copyright (c) 2020 Gluttton // // 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_limesdr.h" #include static struct { lms_device_t *dev; lms_stream_t stream; bool is_stream_opened; bool is_stop; int bytes_in_sample; iq_convert_fn converter; struct converter_state *converter_state; } LimeSDR; void limesdrLogHandler(int lvl, const char *msg) { FILE *out = NULL; switch (lvl) { default: case LMS_LOG_DEBUG: return; case LMS_LOG_INFO: out = stdout; break; case LMS_LOG_WARNING: case LMS_LOG_ERROR: case LMS_LOG_CRITICAL: out = stderr; break; } fprintf(out, "limesdr: %s\n", msg); } void limesdrInitConfig() { LimeSDR.dev = NULL; LimeSDR.stream.channel = 0; LimeSDR.stream.fifoSize = 1024 * 1024; LimeSDR.stream.throughputVsLatency = 1.0; // best throughput LimeSDR.stream.isTx = false; LimeSDR.stream.dataFmt = LMS_FMT_I16; // should be matched with conveter LimeSDR.is_stream_opened = false; LimeSDR.is_stop = false; LimeSDR.bytes_in_sample = 2 * sizeof(int16_t); // hardcoded for LMS_FMT_I16 LMS_RegisterLogHandler(limesdrLogHandler); } void limesdrShowHelp() { printf(" limesdr-specific options (use with --device-type limesdr)\n"); printf("\n"); printf("so far there is no any LimeSDR specific option...\n"); printf("\n"); } bool limesdrHandleOption(int argc, char **argv, int *jptr) { MODES_NOTUSED(argc); MODES_NOTUSED(argv); MODES_NOTUSED(jptr); return false; } bool limesdrOpen(void) { const size_t devCountMax = 8; lms_info_str_t list[devCountMax]; const int devCount = LMS_GetDeviceList(list); if (devCount < 0) { fprintf(stderr, "limesdr: unable to get a number of connected devices\n"); goto error; } if (devCount < 1) { fprintf(stderr, "limesdr: no connected devices\n"); goto error; } if (LMS_Open(&LimeSDR.dev, list[0], NULL) ) { fprintf(stderr, "limesdr: unable to open device\n"); goto error; } if (LMS_Init(LimeSDR.dev)) { fprintf(stderr, "limesdr: unable to initialize device\n"); goto error; } if (LMS_EnableChannel(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, true)) { fprintf(stderr, "limesdr: unable to enable RX channel\n"); goto error; } if (LMS_SetLOFrequency(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.freq)) { fprintf(stderr, "limesdr: unable to set frequency\n"); goto error; } if (LMS_SetAntenna(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, LMS_PATH_LNAL)) { fprintf(stderr, "limesdr: unable to set RF port\n"); goto error; } if (LMS_SetSampleRate(LimeSDR.dev, Modes.sample_rate, 0/*default oversample*/)) { fprintf(stderr, "limesdr: unable to set sampling rate\n"); goto error; } if (LMS_SetNormalizedGain(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, 0.85)) { fprintf(stderr, "limesdr: unable to set gain\n"); goto error; } if (LMS_SetLPFBW(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, Modes.sample_rate)) { fprintf(stderr, "limesdr: unable to set LP filter\n"); goto error; } LimeSDR.is_stream_opened = true; if (LMS_SetupStream(LimeSDR.dev, &LimeSDR.stream)) { fprintf(stderr, "limesdr: unable to setup stream\n"); LimeSDR.is_stream_opened = false; goto error; } if (LMS_Calibrate(LimeSDR.dev, LMS_CH_RX, LimeSDR.stream.channel, 2.5e6/*0.5e5*/, 0)) { fprintf(stderr, "limesdr: unable to calibrate device\n"); goto error; } LimeSDR.converter = init_converter(INPUT_SC16, Modes.sample_rate, Modes.dc_filter, &LimeSDR.converter_state); if (!LimeSDR.converter) { fprintf(stderr, "limesdr: can't initialize sample converter.\n"); goto error; } return true; error: if (LimeSDR.is_stream_opened) { LMS_DestroyStream(LimeSDR.dev, &LimeSDR.stream); LimeSDR.is_stream_opened = false; } if (LimeSDR.dev) { LMS_Close(LimeSDR.dev); LimeSDR.dev = NULL; } return false; } static struct timespec limesdr_thread_cpu; void limesdrCallback(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 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) { LimeSDR.is_stop = true; // 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: limesdr 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) / LimeSDR.bytes_in_sample; outbuf->dropped += discard; buf += discard * LimeSDR.bytes_in_sample; len -= discard * LimeSDR.bytes_in_sample; } } slen = len / LimeSDR.bytes_in_sample; // Drops any trailing odd sample, that's OK 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; // Get the approx system time for the start of this block block_duration = 1e3 * slen / Modes.sample_rate; outbuf->sysTimestamp = mstime() - block_duration; // 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; LimeSDR.converter(buf, &outbuf->data[Modes.trailing_samples], slen, LimeSDR.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(&limesdr_thread_cpu, &Modes.reader_cpu_accumulator); start_cpu_timing(&limesdr_thread_cpu); pthread_cond_signal(&Modes.data_cond); pthread_mutex_unlock(&Modes.data_mutex); } void limesdrRun() { if (!LimeSDR.dev) { return; } int16_t *buffer = malloc(MODES_RTL_BUF_SIZE); LMS_StartStream(&LimeSDR.stream); start_cpu_timing(&limesdr_thread_cpu); while (!LimeSDR.is_stop) { int sampleCnt = LMS_RecvStream(&LimeSDR.stream, buffer, MODES_RTL_BUF_SIZE / LimeSDR.bytes_in_sample, NULL, 1000); if (sampleCnt) { limesdrCallback((unsigned char *)buffer, sampleCnt * LimeSDR.bytes_in_sample, NULL); } } if (!Modes.exit) { fprintf(stderr, "limesdr: async read returned unexpectedly.\n"); } free(buffer); LMS_StopStream(&LimeSDR.stream); } void limesdrClose() { if (LimeSDR.converter) { cleanup_converter(LimeSDR.converter_state); LimeSDR.converter = NULL; LimeSDR.converter_state = NULL; } LMS_StopStream(&LimeSDR.stream); if (LimeSDR.is_stream_opened) { LMS_DestroyStream(LimeSDR.dev, &LimeSDR.stream); LimeSDR.is_stream_opened = false; } if (LimeSDR.dev) { LMS_Close(LimeSDR.dev); LimeSDR.dev = NULL; } }