From ebc089a20dc2755eeb8183ac351e87839bd84e48 Mon Sep 17 00:00:00 2001 From: David Michaeli Date: Thu, 23 Dec 2021 00:30:28 +0200 Subject: [PATCH] added ads-b example --- examples/cpp/CMakeLists.txt | 3 +- examples/cpp/SoapyRateTest.cpp | 174 ---- examples/cpp/SoapySDRProbe.cpp | 336 -------- examples/cpp/build/CMakeFiles/Makefile.cmake | 71 -- examples/cpp/build/CMakeFiles/Makefile2 | 4 +- .../caribou_dump1090.dir/C.includecache | 8 + .../caribou_dump1090.dir/CXX.includecache | 16 + .../caribou_dump1090.dir/DependInfo.cmake | 9 + .../caribou_dump1090.dir/build.make | 19 +- .../caribou_dump1090.dir/cmake_clean.cmake | 3 +- .../caribou_dump1090.dir/depend.internal | 4 + .../caribou_dump1090.dir/depend.make | 6 +- .../caribou_dump1090.dir/flags.make | 7 + .../CMakeFiles/caribou_dump1090.dir/link.txt | 2 +- .../caribou_dump1090.dir/progress.make | 1 + examples/cpp/build/CMakeFiles/progress.marks | 2 +- examples/cpp/build/Makefile | 30 + examples/cpp/build/caribou_dump1090 | Bin 20792 -> 30536 bytes examples/cpp/dump1090.cpp | 86 +- examples/cpp/modes.c | 747 ++++++++++++++++++ examples/cpp/modes.h | 88 +++ .../src/caribou_smi/kernel/bcm2835_smi.c | 49 +- .../src/caribou_smi/kernel/smi_stream_dev.c | 13 +- .../libcariboulite/src/cariboulite_radios.c | 11 +- .../libcariboulite/src/cariboulite_setup.c | 8 +- .../src/io_utils/pigpio/pigpio.c | 10 +- .../libcariboulite/src/rffc507x/rffc507x.c | 4 +- .../src/soapy_api/Cariboulite.cpp | 6 +- .../src/soapy_api/Cariboulite.hpp | 2 +- .../src/soapy_api/CaribouliteSampleQueue.cpp | 2 +- .../src/soapy_api/CaribouliteSession.cpp | 4 +- .../src/soapy_api/CaribouliteStream.cpp | 12 +- 32 files changed, 1053 insertions(+), 684 deletions(-) delete mode 100644 examples/cpp/SoapyRateTest.cpp delete mode 100644 examples/cpp/SoapySDRProbe.cpp create mode 100644 examples/cpp/build/CMakeFiles/caribou_dump1090.dir/C.includecache create mode 100644 examples/cpp/modes.c create mode 100644 examples/cpp/modes.h diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index 1a99e37..83cbe40 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -19,8 +19,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) ######################################################################## add_executable(caribou_dump1090 dump1090.cpp -# SoapySDRProbe.cpp -# SoapyRateTest.cpp + modes.c ) target_link_libraries(caribou_dump1090 SoapySDR) diff --git a/examples/cpp/SoapyRateTest.cpp b/examples/cpp/SoapyRateTest.cpp deleted file mode 100644 index 86dd945..0000000 --- a/examples/cpp/SoapyRateTest.cpp +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2016-2017 Josh Blum -// SPDX-License-Identifier: BSL-1.0 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static sig_atomic_t loopDone = false; -static void sigIntHandler(const int) -{ - loopDone = true; -} - -void runRateTestStreamLoop( - SoapySDR::Device *device, - SoapySDR::Stream *stream, - const int direction, - const size_t numChans, - const size_t elemSize) -{ - //allocate buffers for the stream read/write - const size_t numElems = device->getStreamMTU(stream); - std::vector> buffMem(numChans, std::vector(elemSize*numElems)); - std::vector buffs(numChans); - for (size_t i = 0; i < numChans; i++) buffs[i] = buffMem[i].data(); - - //state collected in this loop - unsigned int overflows(0); - unsigned int underflows(0); - unsigned long long totalSamples(0); - const auto startTime = std::chrono::high_resolution_clock::now(); - auto timeLastPrint = std::chrono::high_resolution_clock::now(); - auto timeLastSpin = std::chrono::high_resolution_clock::now(); - auto timeLastStatus = std::chrono::high_resolution_clock::now(); - int spinIndex(0); - - std::cout << "Starting stream loop, press Ctrl+C to exit..." << std::endl; - device->activateStream(stream); - signal(SIGINT, sigIntHandler); - while (not loopDone) - { - int ret(0); - int flags(0); - long long timeNs(0); - switch(direction) - { - case SOAPY_SDR_RX: - ret = device->readStream(stream, buffs.data(), numElems, flags, timeNs); - break; - case SOAPY_SDR_TX: - ret = device->writeStream(stream, buffs.data(), numElems, flags, timeNs); - break; - } - - if (ret == SOAPY_SDR_TIMEOUT) continue; - if (ret == SOAPY_SDR_OVERFLOW) - { - overflows++; - continue; - } - if (ret == SOAPY_SDR_UNDERFLOW) - { - underflows++; - continue; - } - if (ret < 0) - { - std::cerr << "Unexpected stream error " << SoapySDR::errToStr(ret) << std::endl; - break; - } - totalSamples += ret; - - const auto now = std::chrono::high_resolution_clock::now(); - if (timeLastSpin + std::chrono::milliseconds(300) < now) - { - timeLastSpin = now; - static const char spin[] = {"|/-\\"}; - printf("\b%c", spin[(spinIndex++)%4]); - fflush(stdout); - } - //occasionally read out the stream status (non blocking) - if (timeLastStatus + std::chrono::seconds(1) < now) - { - timeLastStatus = now; - while (true) - { - size_t chanMask; int flags; long long timeNs; - ret = device->readStreamStatus(stream, chanMask, flags, timeNs, 0); - if (ret == SOAPY_SDR_OVERFLOW) overflows++; - else if (ret == SOAPY_SDR_UNDERFLOW) underflows++; - else if (ret == SOAPY_SDR_TIME_ERROR) {} - else break; - } - } - if (timeLastPrint + std::chrono::seconds(5) < now) - { - timeLastPrint = now; - const auto timePassed = std::chrono::duration_cast(now - startTime); - const auto sampleRate = double(totalSamples)/timePassed.count(); - printf("\b%g Msps\t%g MBps", sampleRate, sampleRate*numChans*elemSize); - if (overflows != 0) printf("\tOverflows %u", overflows); - if (underflows != 0) printf("\tUnderflows %u", underflows); - printf("\n "); - } - - } - device->deactivateStream(stream); -} - -int SoapySDRRateTest( - const std::string &argStr, - const double sampleRate, - const std::string &formatStr, - const std::string &channelStr, - const std::string &directionStr) -{ - SoapySDR::Device *device(nullptr); - - try - { - device = SoapySDR::Device::make(argStr); - - //parse the direction to the integer enum - int direction(-1); - if (directionStr == "RX" or directionStr == "rx") direction = SOAPY_SDR_RX; - if (directionStr == "TX" or directionStr == "tx") direction = SOAPY_SDR_TX; - if (direction == -1) throw std::invalid_argument("direction not in RX/TX: " + directionStr); - - //build channels list, using KwargsFromString is a easy parsing hack - std::vector channels; - for (const auto &pair : SoapySDR::KwargsFromString(channelStr)) - { - channels.push_back(std::stoi(pair.first)); - } - if (channels.empty()) channels.push_back(0); - - //initialize the sample rate for all channels - for (const auto &chan : channels) - { - device->setSampleRate(direction, chan, sampleRate); - } - - //create the stream, use the native format - double fullScale(0.0); - const auto format = formatStr.empty() ? device->getNativeStreamFormat(direction, channels.front(), fullScale) : formatStr; - const size_t elemSize = SoapySDR::formatToSize(format); - auto stream = device->setupStream(direction, format, channels); - - //run the rate test one setup is complete - std::cout << "Stream format: " << format << std::endl; - std::cout << "Num channels: " << channels.size() << std::endl; - std::cout << "Element size: " << elemSize << " bytes" << std::endl; - std::cout << "Begin " << directionStr << " rate test at " << (sampleRate/1e6) << " Msps" << std::endl; - runRateTestStreamLoop(device, stream, direction, channels.size(), elemSize); - - //cleanup stream and device - device->closeStream(stream); - SoapySDR::Device::unmake(device); - } - catch (const std::exception &ex) - { - std::cerr << "Error in rate test: " << ex.what() << std::endl; - SoapySDR::Device::unmake(device); - return EXIT_FAILURE; - } - return EXIT_FAILURE; -} diff --git a/examples/cpp/SoapySDRProbe.cpp b/examples/cpp/SoapySDRProbe.cpp deleted file mode 100644 index c2c52dc..0000000 --- a/examples/cpp/SoapySDRProbe.cpp +++ /dev/null @@ -1,336 +0,0 @@ -// Copyright (c) 2015-2017 Josh Blum -// Copyright (c) 2016-2016 Bastille Networks -// SPDX-License-Identifier: BSL-1.0 - -#include -#include -#include - -template -std::string toString(const std::vector &options) -{ - std::stringstream ss; - if (options.empty()) return ""; - for (size_t i = 0; i < options.size(); i++) - { - if (not ss.str().empty()) ss << ", "; - ss << options[i]; - } - return ss.str(); -} - -std::string toString(const SoapySDR::Range &range) -{ - std::stringstream ss; - ss << "[" << range.minimum() << ", " << range.maximum(); - if (range.step() != 0.0) ss << ", " << range.step(); - ss << "]"; - return ss.str(); -} - -std::string toString(const SoapySDR::RangeList &range, const double scale) -{ - const size_t MAXRLEN = 10; //for abbreviating long lists - std::stringstream ss; - for (size_t i = 0; i < range.size(); i++) - { - if (range.size() >= MAXRLEN and i >= MAXRLEN/2 and i < (range.size()-MAXRLEN/2)) - { - if (i == MAXRLEN) ss << ", ..."; - continue; - } - if (not ss.str().empty()) ss << ", "; - if (range[i].minimum() == range[i].maximum()) ss << (range[i].minimum()/scale); - else ss << "[" << (range[i].minimum()/scale) << ", " << (range[i].maximum()/scale) << "]"; - } - return ss.str(); -} - -std::string toString(const std::vector &nums, const double scale) -{ - std::stringstream ss; - - if (nums.size() > 3) - { - ss << "[" << (nums.front()/scale) << ", " << (nums.back()/scale) << "]"; - return ss.str(); - } - - for (size_t i = 0; i < nums.size(); i++) - { - if (not ss.str().empty()) ss << ", "; - ss << (nums[i]/scale); - } - return "[" + ss.str() + "]"; -} - -std::string toString(const SoapySDR::ArgInfo &argInfo, const std::string indent = " ") -{ - std::stringstream ss; - - //name, or use key if missing - std::string name = argInfo.name; - if (argInfo.name.empty()) name = argInfo.key; - ss << indent << " * " << name; - - //optional description - std::string desc = argInfo.description; - const std::string replace("\n"+indent+" "); - for (size_t pos = 0; (pos=desc.find("\n", pos)) != std::string::npos; pos+=replace.size()) - { - desc.replace(pos, 1, replace); - } - if (not desc.empty()) ss << " - " << desc << std::endl << indent << " "; - - //other fields - ss << " [key=" << argInfo.key; - if (not argInfo.units.empty()) ss << ", units=" << argInfo.units; - if (not argInfo.value.empty()) ss << ", default=" << argInfo.value; - - //type - switch (argInfo.type) - { - case SoapySDR::ArgInfo::BOOL: ss << ", type=bool"; break; - case SoapySDR::ArgInfo::INT: ss << ", type=int"; break; - case SoapySDR::ArgInfo::FLOAT: ss << ", type=float"; break; - case SoapySDR::ArgInfo::STRING: ss << ", type=string"; break; - } - - //optional range/enumeration - if (argInfo.range.minimum() < argInfo.range.maximum()) ss << ", range=" << toString(argInfo.range); - if (not argInfo.options.empty()) ss << ", options=(" << toString(argInfo.options) << ")"; - - ss << "]"; - - return ss.str(); -} - -std::string toString(const SoapySDR::ArgInfoList &argInfos) -{ - std::stringstream ss; - - for (size_t i = 0; i < argInfos.size(); i++) - { - ss << toString(argInfos[i]) << std::endl; - } - - return ss.str(); -} - -std::string sensorReadings(SoapySDR::Device *device) -{ - std::stringstream ss; - - /******************************************************************* - * Sensor readings - ******************************************************************/ - std::vector sensors = device->listSensors(); - - for (size_t i = 0; i < sensors.size(); i++) - { - std::string key = sensors[i]; - SoapySDR::ArgInfo info = device->getSensorInfo(key); - std::string reading = device->readSensor(key); - - ss << " * " << sensors[i]; - if (not info.name.empty()) ss << " (" << info.name << ")"; - ss << ":"; - if (info.range.maximum() > std::numeric_limits::min()) ss << toString(info.range); - ss << toString(info.options); - ss << " " << reading; - if (not info.units.empty()) ss << " " << info.units; - ss << std::endl; - if (not info.description.empty()) ss << " " << info.description << std::endl; - } - - return ss.str(); -} - -std::string channelSensorReadings(SoapySDR::Device *device, const int dir, const size_t chan) -{ - std::stringstream ss; - - /******************************************************************* - * Channel sensor readings - ******************************************************************/ - std::vector sensors = device->listSensors(dir, chan); - - for (size_t i = 0; i < sensors.size(); i++) - { - std::string key = sensors[i]; - SoapySDR::ArgInfo info = device->getSensorInfo(dir, chan, key); - std::string reading = device->readSensor(dir, chan, key); - - ss << " * " << sensors[i]; - if (not info.name.empty()) ss << " (" << info.name << ")"; - ss << ":"; - if (info.range.maximum() > std::numeric_limits::min()) ss << toString(info.range); - ss << toString(info.options); - ss << " " << reading; - if (not info.units.empty()) ss << " " << info.units; - ss << std::endl; - if (not info.description.empty()) ss << " " << info.description << std::endl; - } - - return ss.str(); -} - -static std::string probeChannel(SoapySDR::Device *device, const int dir, const size_t chan) -{ - std::stringstream ss; - - std::string dirName = (dir==SOAPY_SDR_TX)?"TX":"RX"; - ss << std::endl; - ss << "----------------------------------------------------" << std::endl; - ss << "-- " << dirName << " Channel " << chan << std::endl; - ss << "----------------------------------------------------" << std::endl; - - // info - const auto info = device->getChannelInfo(dir, chan); - if (info.size() > 0) - { - ss << " Channel Information:" << std::endl; - for (const auto &it : info) - { - ss << " " << it.first << "=" << it.second << std::endl; - } - } - - ss << " Full-duplex: " << (device->getFullDuplex(dir, chan)?"YES":"NO") << std::endl; - ss << " Supports AGC: " << (device->hasGainMode(dir, chan)?"YES":"NO") << std::endl; - - //formats - std::string formats = toString(device->getStreamFormats(dir, chan)); - if (not formats.empty()) ss << " Stream formats: " << formats << std::endl; - - //native - double fullScale = 0.0; - std::string native = device->getNativeStreamFormat(dir, chan, fullScale); - ss << " Native format: " << native << " [full-scale=" << fullScale << "]" << std::endl; - - //stream args - std::string streamArgs = toString(device->getStreamArgsInfo(dir, chan)); - if (not streamArgs.empty()) ss << " Stream args:" << std::endl << streamArgs; - - //antennas - std::string antennas = toString(device->listAntennas(dir, chan)); - if (not antennas.empty()) ss << " Antennas: " << antennas << std::endl; - - //corrections - std::vector correctionsList; - if (device->hasDCOffsetMode(dir, chan)) correctionsList.push_back("DC removal"); - if (device->hasDCOffset(dir, chan)) correctionsList.push_back("DC offset"); - if (device->hasIQBalance(dir, chan)) correctionsList.push_back("IQ balance"); - std::string corrections = toString(correctionsList); - if (not corrections.empty()) ss << " Corrections: " << corrections << std::endl; - - //gains - ss << " Full gain range: " << toString(device->getGainRange(dir, chan)) << " dB" << std::endl; - std::vector gainsList = device->listGains(dir, chan); - for (size_t i = 0; i < gainsList.size(); i++) - { - const std::string name = gainsList[i]; - ss << " " << name << " gain range: " << toString(device->getGainRange(dir, chan, name)) << " dB" << std::endl; - } - - //frequencies - ss << " Full freq range: " << toString(device->getFrequencyRange(dir, chan), 1e6) << " MHz" << std::endl; - std::vector freqsList = device->listFrequencies(dir, chan); - for (size_t i = 0; i < freqsList.size(); i++) - { - const std::string name = freqsList[i]; - ss << " " << name << " freq range: " << toString(device->getFrequencyRange(dir, chan, name), 1e6) << " MHz" << std::endl; - } - - //freq args - std::string freqArgs = toString(device->getFrequencyArgsInfo(dir, chan)); - if (not freqArgs.empty()) ss << " Tune args:" << std::endl << freqArgs; - - //rates - ss << " Sample rates: " << toString(device->getSampleRateRange(dir, chan), 1e6) << " MSps" << std::endl; - - //bandwidths - const auto bws = device->getBandwidthRange(dir, chan); - if (not bws.empty()) ss << " Filter bandwidths: " << toString(bws, 1e6) << " MHz" << std::endl; - - //sensors - std::string sensors = toString(device->listSensors(dir, chan)); - if (not sensors.empty()) ss << " Sensors: " << sensors << std::endl; - ss << channelSensorReadings(device, dir, chan); - - //settings - std::string settings = toString(device->getSettingInfo(dir, chan)); - if (not settings.empty()) ss << " Other Settings:" << std::endl << settings; - - return ss.str(); -} - -std::string SoapySDRDeviceProbe(SoapySDR::Device *device) -{ - std::stringstream ss; - - /******************************************************************* - * Identification info - ******************************************************************/ - ss << std::endl; - ss << "----------------------------------------------------" << std::endl; - ss << "-- Device identification" << std::endl; - ss << "----------------------------------------------------" << std::endl; - - ss << " driver=" << device->getDriverKey() << std::endl; - ss << " hardware=" << device->getHardwareKey() << std::endl; - for (const auto &it : device->getHardwareInfo()) - { - ss << " " << it.first << "=" << it.second << std::endl; - } - - /******************************************************************* - * Available peripherals - ******************************************************************/ - ss << std::endl; - ss << "----------------------------------------------------" << std::endl; - ss << "-- Peripheral summary" << std::endl; - ss << "----------------------------------------------------" << std::endl; - - size_t numRxChans = device->getNumChannels(SOAPY_SDR_RX); - size_t numTxChans = device->getNumChannels(SOAPY_SDR_TX); - ss << " Channels: " << numRxChans << " Rx, " << numTxChans << " Tx" << std::endl; - - ss << " Timestamps: " << (device->hasHardwareTime()?"YES":"NO") << std::endl; - - std::string clockSources = toString(device->listClockSources()); - if (not clockSources.empty()) ss << " Clock sources: " << clockSources << std::endl; - - std::string timeSources = toString(device->listTimeSources()); - if (not timeSources.empty()) ss << " Time sources: " << timeSources << std::endl; - - std::string sensors = toString(device->listSensors()); - if (not sensors.empty()) ss << " Sensors: " << sensors << std::endl; - ss << sensorReadings(device); - - std::string registers = toString(device->listRegisterInterfaces()); - if (not registers.empty()) ss << " Registers: " << registers << std::endl; - - std::string settings = toString(device->getSettingInfo()); - if (not settings.empty()) ss << " Other Settings:" << std::endl << settings; - - std::string gpios = toString(device->listGPIOBanks()); - if (not gpios.empty()) ss << " GPIOs: " << gpios << std::endl; - - std::string uarts = toString(device->listUARTs()); - if (not uarts.empty()) ss << " UARTs: " << uarts << std::endl; - - /******************************************************************* - * Per-channel info - ******************************************************************/ - for (size_t chan = 0; chan < numRxChans; chan++) - { - ss << probeChannel(device, SOAPY_SDR_RX, chan); - } - for (size_t chan = 0; chan < numTxChans; chan++) - { - ss << probeChannel(device, SOAPY_SDR_TX, chan); - } - - return ss.str(); -} diff --git a/examples/cpp/build/CMakeFiles/Makefile.cmake b/examples/cpp/build/CMakeFiles/Makefile.cmake index b4db3e3..32a4a98 100644 --- a/examples/cpp/build/CMakeFiles/Makefile.cmake +++ b/examples/cpp/build/CMakeFiles/Makefile.cmake @@ -11,86 +11,20 @@ set(CMAKE_MAKEFILE_DEPENDS "CMakeFiles/3.18.4/CMakeCCompiler.cmake" "CMakeFiles/3.18.4/CMakeCXXCompiler.cmake" "CMakeFiles/3.18.4/CMakeSystem.cmake" - "/usr/share/cmake-3.18/Modules/CMakeCCompiler.cmake.in" - "/usr/share/cmake-3.18/Modules/CMakeCCompilerABI.c" "/usr/share/cmake-3.18/Modules/CMakeCInformation.cmake" - "/usr/share/cmake-3.18/Modules/CMakeCXXCompiler.cmake.in" - "/usr/share/cmake-3.18/Modules/CMakeCXXCompilerABI.cpp" "/usr/share/cmake-3.18/Modules/CMakeCXXInformation.cmake" "/usr/share/cmake-3.18/Modules/CMakeCheckCompilerFlagCommonPatterns.cmake" "/usr/share/cmake-3.18/Modules/CMakeCommonLanguageInclude.cmake" - "/usr/share/cmake-3.18/Modules/CMakeCompilerIdDetection.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCCompiler.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCXXCompiler.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCompileFeatures.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCompilerABI.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineCompilerId.cmake" - "/usr/share/cmake-3.18/Modules/CMakeDetermineSystem.cmake" - "/usr/share/cmake-3.18/Modules/CMakeFindBinUtils.cmake" "/usr/share/cmake-3.18/Modules/CMakeGenericSystem.cmake" "/usr/share/cmake-3.18/Modules/CMakeInitializeConfigs.cmake" "/usr/share/cmake-3.18/Modules/CMakeLanguageInformation.cmake" - "/usr/share/cmake-3.18/Modules/CMakeParseImplicitIncludeInfo.cmake" - "/usr/share/cmake-3.18/Modules/CMakeParseImplicitLinkInfo.cmake" - "/usr/share/cmake-3.18/Modules/CMakeSystem.cmake.in" "/usr/share/cmake-3.18/Modules/CMakeSystemSpecificInformation.cmake" "/usr/share/cmake-3.18/Modules/CMakeSystemSpecificInitialize.cmake" - "/usr/share/cmake-3.18/Modules/CMakeTestCCompiler.cmake" - "/usr/share/cmake-3.18/Modules/CMakeTestCXXCompiler.cmake" - "/usr/share/cmake-3.18/Modules/CMakeTestCompilerCommon.cmake" - "/usr/share/cmake-3.18/Modules/CMakeUnixFindMake.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/ADSP-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/ARMCC-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/ARMClang-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/AppleClang-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Borland-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Bruce-C-DetermineCompiler.cmake" "/usr/share/cmake-3.18/Modules/Compiler/CMakeCommonCompilerMacros.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Clang-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Clang-DetermineCompilerInternal.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Comeau-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Compaq-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Compaq-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Cray-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Embarcadero-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Fujitsu-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/GHS-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/GNU-C-DetermineCompiler.cmake" "/usr/share/cmake-3.18/Modules/Compiler/GNU-C.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/GNU-CXX-DetermineCompiler.cmake" "/usr/share/cmake-3.18/Modules/Compiler/GNU-CXX.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/GNU-FindBinUtils.cmake" "/usr/share/cmake-3.18/Modules/Compiler/GNU.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/HP-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/HP-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/IAR-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/IBMCPP-C-DetermineVersionInternal.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/IBMCPP-CXX-DetermineVersionInternal.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Intel-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/MSVC-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/NVIDIA-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/OpenWatcom-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/PGI-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/PathScale-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/SCO-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/SDCC-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/SunPro-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/SunPro-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/TI-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/TinyCC-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/VisualAge-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/VisualAge-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/Watcom-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/XL-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/XL-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/XLClang-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/XLClang-CXX-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/zOS-C-DetermineCompiler.cmake" - "/usr/share/cmake-3.18/Modules/Compiler/zOS-CXX-DetermineCompiler.cmake" "/usr/share/cmake-3.18/Modules/Internal/CMakeCheckCompilerFlag.cmake" - "/usr/share/cmake-3.18/Modules/Internal/FeatureTesting.cmake" - "/usr/share/cmake-3.18/Modules/Platform/Linux-Determine-CXX.cmake" "/usr/share/cmake-3.18/Modules/Platform/Linux-GNU-C.cmake" "/usr/share/cmake-3.18/Modules/Platform/Linux-GNU-CXX.cmake" "/usr/share/cmake-3.18/Modules/Platform/Linux-GNU.cmake" @@ -106,11 +40,6 @@ set(CMAKE_MAKEFILE_OUTPUTS # Byproducts of CMake generate step: set(CMAKE_MAKEFILE_PRODUCTS - "CMakeFiles/3.18.4/CMakeSystem.cmake" - "CMakeFiles/3.18.4/CMakeCCompiler.cmake" - "CMakeFiles/3.18.4/CMakeCXXCompiler.cmake" - "CMakeFiles/3.18.4/CMakeCCompiler.cmake" - "CMakeFiles/3.18.4/CMakeCXXCompiler.cmake" "CMakeFiles/CMakeDirectoryInformation.cmake" ) diff --git a/examples/cpp/build/CMakeFiles/Makefile2 b/examples/cpp/build/CMakeFiles/Makefile2 index 6e7d30f..d99620b 100644 --- a/examples/cpp/build/CMakeFiles/Makefile2 +++ b/examples/cpp/build/CMakeFiles/Makefile2 @@ -93,12 +93,12 @@ clean: CMakeFiles/caribou_dump1090.dir/clean CMakeFiles/caribou_dump1090.dir/all: $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/depend $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/build - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles --progress-num=1,2 "Built target caribou_dump1090" + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles --progress-num=1,2,3 "Built target caribou_dump1090" .PHONY : CMakeFiles/caribou_dump1090.dir/all # Build rule for subdir invocation for target. CMakeFiles/caribou_dump1090.dir/rule: cmake_check_build_system - $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles 2 + $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles 3 $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 CMakeFiles/caribou_dump1090.dir/all $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles 0 .PHONY : CMakeFiles/caribou_dump1090.dir/rule diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/C.includecache b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/C.includecache new file mode 100644 index 0000000..8e54ca9 --- /dev/null +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/C.includecache @@ -0,0 +1,8 @@ +#IncludeRegexLine: ^[ ]*[#%][ ]*(include|import)[ ]*[<"]([^">]+)([">]) + +#IncludeRegexScan: ^.*$ + +#IncludeRegexComplain: ^$ + +#IncludeRegexTransform: + diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/CXX.includecache b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/CXX.includecache index ac84f64..ecfe406 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/CXX.includecache +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/CXX.includecache @@ -39,4 +39,20 @@ sys/types.h - sys/stat.h - +modes.h +/home/pi/projects/cariboulite/examples/cpp/modes.h + +/home/pi/projects/cariboulite/examples/cpp/modes.h +string.h +- +stdlib.h +- +stdint.h +- +unistd.h +- +math.h +- +sys/time.h +- diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/DependInfo.cmake b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/DependInfo.cmake index b9389a0..5ee87f3 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/DependInfo.cmake +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/DependInfo.cmake @@ -1,8 +1,17 @@ # The set of languages for which implicit dependencies are needed: set(CMAKE_DEPENDS_LANGUAGES + "C" "CXX" ) # The set of files for implicit dependencies of each language: +set(CMAKE_DEPENDS_CHECK_C + "/home/pi/projects/cariboulite/examples/cpp/modes.c" "/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/modes.c.o" + ) +set(CMAKE_C_COMPILER_ID "GNU") + +# The include file search paths: +set(CMAKE_C_TARGET_INCLUDE_PATH + ) set(CMAKE_DEPENDS_CHECK_CXX "/home/pi/projects/cariboulite/examples/cpp/dump1090.cpp" "/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o" ) diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/build.make b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/build.make index 7e108cb..d601ac5 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/build.make +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/build.make @@ -89,17 +89,32 @@ CMakeFiles/caribou_dump1090.dir/dump1090.cpp.s: cmake_force @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/caribou_dump1090.dir/dump1090.cpp.s" /usr/bin/c++ $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S /home/pi/projects/cariboulite/examples/cpp/dump1090.cpp -o CMakeFiles/caribou_dump1090.dir/dump1090.cpp.s +CMakeFiles/caribou_dump1090.dir/modes.c.o: CMakeFiles/caribou_dump1090.dir/flags.make +CMakeFiles/caribou_dump1090.dir/modes.c.o: ../modes.c + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Building C object CMakeFiles/caribou_dump1090.dir/modes.c.o" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -o CMakeFiles/caribou_dump1090.dir/modes.c.o -c /home/pi/projects/cariboulite/examples/cpp/modes.c + +CMakeFiles/caribou_dump1090.dir/modes.c.i: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing C source to CMakeFiles/caribou_dump1090.dir/modes.c.i" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -E /home/pi/projects/cariboulite/examples/cpp/modes.c > CMakeFiles/caribou_dump1090.dir/modes.c.i + +CMakeFiles/caribou_dump1090.dir/modes.c.s: cmake_force + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling C source to assembly CMakeFiles/caribou_dump1090.dir/modes.c.s" + /usr/bin/cc $(C_DEFINES) $(C_INCLUDES) $(C_FLAGS) -S /home/pi/projects/cariboulite/examples/cpp/modes.c -o CMakeFiles/caribou_dump1090.dir/modes.c.s + # Object files for target caribou_dump1090 caribou_dump1090_OBJECTS = \ -"CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o" +"CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o" \ +"CMakeFiles/caribou_dump1090.dir/modes.c.o" # External object files for target caribou_dump1090 caribou_dump1090_EXTERNAL_OBJECTS = caribou_dump1090: CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o +caribou_dump1090: CMakeFiles/caribou_dump1090.dir/modes.c.o caribou_dump1090: CMakeFiles/caribou_dump1090.dir/build.make caribou_dump1090: CMakeFiles/caribou_dump1090.dir/link.txt - @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX executable caribou_dump1090" + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=/home/pi/projects/cariboulite/examples/cpp/build/CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Linking CXX executable caribou_dump1090" $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles/caribou_dump1090.dir/link.txt --verbose=$(VERBOSE) # Rule to build all files generated by this target. diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/cmake_clean.cmake b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/cmake_clean.cmake index ecf721d..8074c57 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/cmake_clean.cmake +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/cmake_clean.cmake @@ -1,10 +1,11 @@ file(REMOVE_RECURSE "CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o" + "CMakeFiles/caribou_dump1090.dir/modes.c.o" "caribou_dump1090" "caribou_dump1090.pdb" ) # Per-language clean rules from dependency scanning. -foreach(lang CXX) +foreach(lang C CXX) include(CMakeFiles/caribou_dump1090.dir/cmake_clean_${lang}.cmake OPTIONAL) endforeach() diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.internal b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.internal index e50d60a..5bf8fbc 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.internal +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.internal @@ -1,5 +1,9 @@ # CMAKE generated file: DO NOT EDIT! # Generated by "Unix Makefiles" Generator, CMake Version 3.18 +CMakeFiles/caribou_dump1090.dir/modes.c.o + /home/pi/projects/cariboulite/examples/cpp/modes.c + /home/pi/projects/cariboulite/examples/cpp/modes.h CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o /home/pi/projects/cariboulite/examples/cpp/dump1090.cpp + /home/pi/projects/cariboulite/examples/cpp/modes.h diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.make b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.make index 2c53a03..c5580a4 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.make +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/depend.make @@ -1,5 +1,9 @@ # CMAKE generated file: DO NOT EDIT! # Generated by "Unix Makefiles" Generator, CMake Version 3.18 -CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o: ../dump1090.cpp +CMakeFiles/caribou_dump1090.dir/modes.c.o: ../modes.c +CMakeFiles/caribou_dump1090.dir/modes.c.o: ../modes.h + +CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o: ../dump1090.cpp +CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o: ../modes.h diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/flags.make b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/flags.make index 3000b65..ab7bdbe 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/flags.make +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/flags.make @@ -1,7 +1,14 @@ # CMAKE generated file: DO NOT EDIT! # Generated by "Unix Makefiles" Generator, CMake Version 3.18 +# compile C with /usr/bin/cc # compile CXX with /usr/bin/c++ +C_DEFINES = + +C_INCLUDES = + +C_FLAGS = -O3 -DNDEBUG -fPIE + CXX_DEFINES = CXX_INCLUDES = diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/link.txt b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/link.txt index 927791f..0f02b96 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/link.txt +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/link.txt @@ -1 +1 @@ -/usr/bin/c++ -O3 -DNDEBUG CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o -o caribou_dump1090 -lSoapySDR +/usr/bin/c++ -O3 -DNDEBUG CMakeFiles/caribou_dump1090.dir/dump1090.cpp.o CMakeFiles/caribou_dump1090.dir/modes.c.o -o caribou_dump1090 -lSoapySDR diff --git a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/progress.make b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/progress.make index abadeb0..6a9dc74 100644 --- a/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/progress.make +++ b/examples/cpp/build/CMakeFiles/caribou_dump1090.dir/progress.make @@ -1,3 +1,4 @@ CMAKE_PROGRESS_1 = 1 CMAKE_PROGRESS_2 = 2 +CMAKE_PROGRESS_3 = 3 diff --git a/examples/cpp/build/CMakeFiles/progress.marks b/examples/cpp/build/CMakeFiles/progress.marks index 0cfbf08..00750ed 100644 --- a/examples/cpp/build/CMakeFiles/progress.marks +++ b/examples/cpp/build/CMakeFiles/progress.marks @@ -1 +1 @@ -2 +3 diff --git a/examples/cpp/build/Makefile b/examples/cpp/build/Makefile index 33a5d84..f1e8062 100644 --- a/examples/cpp/build/Makefile +++ b/examples/cpp/build/Makefile @@ -169,6 +169,33 @@ dump1090.cpp.s: $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/dump1090.cpp.s .PHONY : dump1090.cpp.s +modes.o: modes.c.o + +.PHONY : modes.o + +# target to build an object file +modes.c.o: + $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/modes.c.o +.PHONY : modes.c.o + +modes.i: modes.c.i + +.PHONY : modes.i + +# target to preprocess a source file +modes.c.i: + $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/modes.c.i +.PHONY : modes.c.i + +modes.s: modes.c.s + +.PHONY : modes.s + +# target to generate assembly for a file +modes.c.s: + $(MAKE) $(MAKESILENT) -f CMakeFiles/caribou_dump1090.dir/build.make CMakeFiles/caribou_dump1090.dir/modes.c.s +.PHONY : modes.c.s + # Help Target help: @echo "The following are some of the valid targets for this Makefile:" @@ -181,6 +208,9 @@ help: @echo "... dump1090.o" @echo "... dump1090.i" @echo "... dump1090.s" + @echo "... modes.o" + @echo "... modes.i" + @echo "... modes.s" .PHONY : help diff --git a/examples/cpp/build/caribou_dump1090 b/examples/cpp/build/caribou_dump1090 index 1e4c4136ae0b516810c3563bea5d1689aac7978c..19dbe3162b0418b2a4d56e989859a70da40d98ad 100755 GIT binary patch literal 30536 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 axujkR(K{kpcZpH@>}rF$EvY~f$^H}XRJnQp diff --git a/examples/cpp/dump1090.cpp b/examples/cpp/dump1090.cpp index 83b931a..29fbe52 100644 --- a/examples/cpp/dump1090.cpp +++ b/examples/cpp/dump1090.cpp @@ -1,12 +1,10 @@ -// Copyright (c) 2014-2021 Josh Blum -// SPDX-License-Identifier: BSL-1.0 - #include #include #include #include #include -#include //sort, min, max + +#include #include #include #include @@ -17,15 +15,7 @@ #include #include #include - -std::string SoapySDRDeviceProbe(SoapySDR::Device *); -std::string sensorReadings(SoapySDR::Device *); -int SoapySDRRateTest( - const std::string &argStr, - const double sampleRate, - const std::string &formatStr, - const std::string &channelStr, - const std::string &directionStr); +#include "modes.h" /*********************************************************************** * Print the banner @@ -70,6 +60,11 @@ static void sigIntHandler(const int) loopDone = true; } +void onModeSMessage(mode_s_t *self, struct mode_s_msg *mm) +{ + printf("Got message from flight %s at altitude %d\n", mm->flight, mm->altitude); +} + void runSoapyProcess( SoapySDR::Device *device, SoapySDR::Stream *stream, @@ -80,20 +75,14 @@ void runSoapyProcess( //allocate buffers for the stream read/write const size_t numElems = device->getStreamMTU(stream); std::vector> buffMem(numChans, std::vector(elemSize*numElems)); + std::vector magMem(numElems); std::vector buffs(numChans); for (size_t i = 0; i < numChans; i++) buffs[i] = buffMem[i].data(); - //state collected in this loop - unsigned int overflows(0); - unsigned int underflows(0); - unsigned long long totalSamples(0); - const auto startTime = std::chrono::high_resolution_clock::now(); - auto timeLastPrint = std::chrono::high_resolution_clock::now(); - auto timeLastSpin = std::chrono::high_resolution_clock::now(); - auto timeLastStatus = std::chrono::high_resolution_clock::now(); - int spinIndex(0); + // MODE-S Stuff + mode_s_t state; + mode_s_init(&state); - std::cout << "Num Elements / read = " << numElems << std::endl; std::cout << "Starting stream loop, press Ctrl+C to exit..." << std::endl; device->activateStream(stream); signal(SIGINT, sigIntHandler); @@ -107,12 +96,12 @@ void runSoapyProcess( if (ret == SOAPY_SDR_TIMEOUT) continue; if (ret == SOAPY_SDR_OVERFLOW) { - overflows++; + //overflows++; continue; } if (ret == SOAPY_SDR_UNDERFLOW) { - underflows++; + //underflows++; continue; } if (ret < 0) @@ -120,47 +109,19 @@ void runSoapyProcess( std::cerr << "Unexpected stream error " << ret << std::endl; break; } - //std::cout << ret << std::endl; - totalSamples += ret; + //totalSamples += ret; - const auto now = std::chrono::high_resolution_clock::now(); - /*if (timeLastSpin + std::chrono::milliseconds(300) < now) - { - timeLastSpin = now; - static const char spin[] = {"|/-\\"}; - printf("\b%c", spin[(spinIndex++)%4]); - fflush(stdout); - }*/ + // compute the magnitude of the signal + mode_s_compute_magnitude_vector((short*)(buffs[0]), magMem.data(), ret); - //occasionally read out the stream status (non blocking) - if (timeLastStatus + std::chrono::seconds(1) < now) - { - timeLastStatus = now; - while (true) - { - size_t chanMask; int flags; long long timeNs; - ret = device->readStreamStatus(stream, chanMask, flags, timeNs, 0); - if (ret == SOAPY_SDR_OVERFLOW) overflows++; - else if (ret == SOAPY_SDR_UNDERFLOW) underflows++; - else if (ret == SOAPY_SDR_TIME_ERROR) {} - else break; - } - } - if (timeLastPrint + std::chrono::seconds(5) < now) - { - timeLastPrint = now; - const auto timePassed = std::chrono::duration_cast(now - startTime); - const auto sampleRate = double(totalSamples)/timePassed.count(); - printf("\b%g Msps\t%g MBps", sampleRate, sampleRate*numChans*elemSize); - if (overflows != 0) printf("\tOverflows %u", overflows); - if (underflows != 0) printf("\tUnderflows %u", underflows); - printf("\n "); - } + // detect Mode S messages in the signal and call on_msg with each message + mode_s_detect(&state, magMem.data(), ret, onModeSMessage); } device->deactivateStream(stream); } + /*********************************************************************** * Main entry point **********************************************************************/ @@ -184,9 +145,10 @@ int main(int argc, char *argv[]) // set the sample rate device->setSampleRate(SOAPY_SDR_RX, channels[0], 4e6); - - //create the stream, use the native format - + device->setBandwidth(SOAPY_SDR_RX, channels[0], 2500e3); + device->setGainMode(SOAPY_SDR_RX, channels[0], true); + + //create the stream, use the native format const auto format = device->getNativeStreamFormat(SOAPY_SDR_RX, channels.front(), fullScale); const size_t elemSize = SoapySDR::formatToSize(format); auto stream = device->setupStream(SOAPY_SDR_RX, format, channels); diff --git a/examples/cpp/modes.c b/examples/cpp/modes.c new file mode 100644 index 0000000..01c92f3 --- /dev/null +++ b/examples/cpp/modes.c @@ -0,0 +1,747 @@ +#include "modes.h" + +#define MODE_S_PREAMBLE_US 8 // microseconds +#define MODE_S_LONG_MSG_BITS 112 +#define MODE_S_SHORT_MSG_BITS 56 +#define MODE_S_FULL_LEN (MODE_S_PREAMBLE_US+MODE_S_LONG_MSG_BITS) + +#define MODE_S_ICAO_CACHE_TTL 60 // Time to live of cached addresses. + +static uint16_t maglut[129*129*2]; +static int maglut_initialized = 0; + +// =============================== Initialization =========================== + +void mode_s_init(mode_s_t *self) { + int i, q; + + self->fix_errors = 1; + self->check_crc = 1; + self->aggressive = 0; + + // Allocate the ICAO address cache. We use two uint32_t for every entry + // because it's a addr / timestamp pair for every entry + memset(&self->icao_cache, 0, sizeof(self->icao_cache)); + + // Populate the I/Q -> Magnitude lookup table. It is used because sqrt or + // round may be expensive and may vary a lot depending on the libc used. + // + // We scale to 0-255 range multiplying by 1.4 in order to ensure that every + // different I/Q pair will result in a different magnitude value, not losing + // any resolution. + if (!maglut_initialized) { + for (i = 0; i <= 128; i++) { + for (q = 0; q <= 128; q++) { + maglut[i*129+q] = round(sqrt(i*i+q*q)*360); + } + } + maglut_initialized = 1; + } +} + +// ===================== Mode S detection and decoding ===================== + +// Parity table for MODE S Messages. +// +// The table contains 112 elements, every element corresponds to a bit set in +// the message, starting from the first bit of actual data after the preamble. +// +// For messages of 112 bit, the whole table is used. For messages of 56 bits +// only the last 56 elements are used. +// +// The algorithm is as simple as xoring all the elements in this table for +// which the corresponding bit on the message is set to 1. +// +// The latest 24 elements in this table are set to 0 as the checksum at the end +// of the message should not affect the computation. +// +// Note: this function can be used with DF11 and DF17, other modes have the CRC +// xored with the sender address as they are reply to interrogations, but a +// casual listener can't split the address from the checksum. +uint32_t mode_s_checksum_table[] = { + 0x3935ea, 0x1c9af5, 0xf1b77e, 0x78dbbf, 0xc397db, 0x9e31e9, 0xb0e2f0, 0x587178, + 0x2c38bc, 0x161c5e, 0x0b0e2f, 0xfa7d13, 0x82c48d, 0xbe9842, 0x5f4c21, 0xd05c14, + 0x682e0a, 0x341705, 0xe5f186, 0x72f8c3, 0xc68665, 0x9cb936, 0x4e5c9b, 0xd8d449, + 0x939020, 0x49c810, 0x24e408, 0x127204, 0x093902, 0x049c81, 0xfdb444, 0x7eda22, + 0x3f6d11, 0xe04c8c, 0x702646, 0x381323, 0xe3f395, 0x8e03ce, 0x4701e7, 0xdc7af7, + 0x91c77f, 0xb719bb, 0xa476d9, 0xadc168, 0x56e0b4, 0x2b705a, 0x15b82d, 0xf52612, + 0x7a9309, 0xc2b380, 0x6159c0, 0x30ace0, 0x185670, 0x0c2b38, 0x06159c, 0x030ace, + 0x018567, 0xff38b7, 0x80665f, 0xbfc92b, 0xa01e91, 0xaff54c, 0x57faa6, 0x2bfd53, + 0xea04ad, 0x8af852, 0x457c29, 0xdd4410, 0x6ea208, 0x375104, 0x1ba882, 0x0dd441, + 0xf91024, 0x7c8812, 0x3e4409, 0xe0d800, 0x706c00, 0x383600, 0x1c1b00, 0x0e0d80, + 0x0706c0, 0x038360, 0x01c1b0, 0x00e0d8, 0x00706c, 0x003836, 0x001c1b, 0xfff409, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, + 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000 +}; + +uint32_t mode_s_checksum(unsigned char *msg, int bits) { + uint32_t crc = 0; + int offset = (bits == 112) ? 0 : (112-56); + int j; + + for(j = 0; j < bits; j++) { + int byte = j/8; + int bit = j%8; + int bitmask = 1 << (7-bit); + + // If bit is set, xor with corresponding table entry. + if (msg[byte] & bitmask) + crc ^= mode_s_checksum_table[j+offset]; + } + return crc; // 24 bit checksum. +} + +// Given the Downlink Format (DF) of the message, return the message length in +// bits. +int mode_s_msg_len_by_type(int type) { + if (type == 16 || type == 17 || + type == 19 || type == 20 || + type == 21) + return MODE_S_LONG_MSG_BITS; + else + return MODE_S_SHORT_MSG_BITS; +} + +// Try to fix single bit errors using the checksum. On success modifies the +// original buffer with the fixed version, and returns the position of the +// error bit. Otherwise if fixing failed -1 is returned. +int fix_single_bit_errors(unsigned char *msg, int bits) { + int j; + unsigned char aux[MODE_S_LONG_MSG_BITS/8]; + + for (j = 0; j < bits; j++) { + int byte = j/8; + int bitmask = 1 << (7-(j%8)); + uint32_t crc1, crc2; + + memcpy(aux, msg, bits/8); + aux[byte] ^= bitmask; // Flip j-th bit. + + crc1 = ((uint32_t)aux[(bits/8)-3] << 16) | + ((uint32_t)aux[(bits/8)-2] << 8) | + (uint32_t)aux[(bits/8)-1]; + crc2 = mode_s_checksum(aux, bits); + + if (crc1 == crc2) { + // The error is fixed. Overwrite the original buffer with the + // corrected sequence, and returns the error bit position. + memcpy(msg, aux, bits/8); + return j; + } + } + return -1; +} + +// Similar to fix_single_bit_errors() but try every possible two bit +// combination. This is very slow and should be tried only against DF17 +// messages that don't pass the checksum, and only in Aggressive Mode. +int fix_two_bits_errors(unsigned char *msg, int bits) { + int j, i; + unsigned char aux[MODE_S_LONG_MSG_BITS/8]; + + for (j = 0; j < bits; j++) { + int byte1 = j/8; + int bitmask1 = 1 << (7-(j%8)); + + // Don't check the same pairs multiple times, so i starts from j+1 + for (i = j+1; i < bits; i++) { + int byte2 = i/8; + int bitmask2 = 1 << (7-(i%8)); + uint32_t crc1, crc2; + + memcpy(aux, msg, bits/8); + + aux[byte1] ^= bitmask1; // Flip j-th bit. + aux[byte2] ^= bitmask2; // Flip i-th bit. + + crc1 = ((uint32_t)aux[(bits/8)-3] << 16) | + ((uint32_t)aux[(bits/8)-2] << 8) | + (uint32_t)aux[(bits/8)-1]; + crc2 = mode_s_checksum(aux, bits); + + if (crc1 == crc2) { + // The error is fixed. Overwrite the original buffer with the + // corrected sequence, and returns the error bit position. + memcpy(msg, aux, bits/8); + // We return the two bits as a 16 bit integer by shifting 'i' + // on the left. This is possible since 'i' will always be + // non-zero because i starts from j+1. + return j | (i<<8); + } + } + } + return -1; +} + +// Hash the ICAO address to index our cache of MODE_S_ICAO_CACHE_LEN elements, +// that is assumed to be a power of two. +uint32_t icao_cache_has_addr(uint32_t a) { + // The following three rounds wil make sure that every bit affects every + // output bit with ~ 50% of probability. + a = ((a >> 16) ^ a) * 0x45d9f3b; + a = ((a >> 16) ^ a) * 0x45d9f3b; + a = ((a >> 16) ^ a); + return a & (MODE_S_ICAO_CACHE_LEN-1); +} + +// Add the specified entry to the cache of recently seen ICAO addresses. Note +// that we also add a timestamp so that we can make sure that the entry is only +// valid for MODE_S_ICAO_CACHE_TTL seconds. +void add_recently_seen_icao_addr(mode_s_t *self, uint32_t addr) { + uint32_t h = icao_cache_has_addr(addr); + self->icao_cache[h*2] = addr; + self->icao_cache[h*2+1] = (uint32_t) time(NULL); +} + +// Returns 1 if the specified ICAO address was seen in a DF format with proper +// checksum (not xored with address) no more than * MODE_S_ICAO_CACHE_TTL +// seconds ago. Otherwise returns 0. +int icao_addr_was_recently_seen(mode_s_t *self, uint32_t addr) { + uint32_t h = icao_cache_has_addr(addr); + uint32_t a = self->icao_cache[h*2]; + int32_t t = self->icao_cache[h*2+1]; + + return a && a == addr && time(NULL)-t <= MODE_S_ICAO_CACHE_TTL; +} + +// If the message type has the checksum xored with the ICAO address, try to +// brute force it using a list of recently seen ICAO addresses. +// +// Do this in a brute-force fashion by xoring the predicted CRC with the +// address XOR checksum field in the message. This will recover the address: if +// we found it in our cache, we can assume the message is ok. +// +// This function expects mm->msgtype and mm->msgbits to be correctly populated +// by the caller. +// +// On success the correct ICAO address is stored in the mode_s_msg structure in +// the aa3, aa2, and aa1 fiedls. +// +// If the function successfully recovers a message with a correct checksum it +// returns 1. Otherwise 0 is returned. +int brute_force_ap(mode_s_t *self, unsigned char *msg, struct mode_s_msg *mm) { + unsigned char aux[MODE_S_LONG_MSG_BYTES]; + int msgtype = mm->msgtype; + int msgbits = mm->msgbits; + + if (msgtype == 0 || // Short air surveillance + msgtype == 4 || // Surveillance, altitude reply + msgtype == 5 || // Surveillance, identity reply + msgtype == 16 || // Long Air-Air survillance + msgtype == 20 || // Comm-A, altitude request + msgtype == 21 || // Comm-A, identity request + msgtype == 24) // Comm-C ELM + { + uint32_t addr; + uint32_t crc; + int lastbyte = (msgbits/8)-1; + + // Work on a copy. + memcpy(aux, msg, msgbits/8); + + // Compute the CRC of the message and XOR it with the AP field so that + // we recover the address, because: + // + // (ADDR xor CRC) xor CRC = ADDR. + crc = mode_s_checksum(aux, msgbits); + aux[lastbyte] ^= crc & 0xff; + aux[lastbyte-1] ^= (crc >> 8) & 0xff; + aux[lastbyte-2] ^= (crc >> 16) & 0xff; + + // If the obtained address exists in our cache we consider the message + // valid. + addr = aux[lastbyte] | (aux[lastbyte-1] << 8) | (aux[lastbyte-2] << 16); + if (icao_addr_was_recently_seen(self, addr)) { + mm->aa1 = aux[lastbyte-2]; + mm->aa2 = aux[lastbyte-1]; + mm->aa3 = aux[lastbyte]; + return 1; + } + } + return 0; +} + +// Decode the 13 bit AC altitude field (in DF 20 and others). Returns the +// altitude, and set 'unit' to either MODE_S_UNIT_METERS or MDOES_UNIT_FEETS. +int decode_ac13_field(unsigned char *msg, int *unit) { + int m_bit = msg[3] & (1<<6); + int q_bit = msg[3] & (1<<4); + + if (!m_bit) { + *unit = MODE_S_UNIT_FEET; + if (q_bit) { + // N is the 11 bit integer resulting from the removal of bit Q and M + int n = ((msg[2]&31)<<6) | + ((msg[3]&0x80)>>2) | + ((msg[3]&0x20)>>1) | + (msg[3]&15); + // The final altitude is due to the resulting number multiplied by + // 25, minus 1000. + return n*25-1000; + } else { + // TODO: Implement altitude where Q=0 and M=0 + } + } else { + *unit = MODE_S_UNIT_METERS; + // TODO: Implement altitude when meter unit is selected. + } + return 0; +} + +// Decode the 12 bit AC altitude field (in DF 17 and others). Returns the +// altitude or 0 if it can't be decoded. +int decode_ac12_field(unsigned char *msg, int *unit) { + int q_bit = msg[5] & 1; + + if (q_bit) { + // N is the 11 bit integer resulting from the removal of bit Q + *unit = MODE_S_UNIT_FEET; + int n = ((msg[5]>>1)<<4) | ((msg[6]&0xF0) >> 4); + // The final altitude is due to the resulting number multiplied by 25, + // minus 1000. + return n*25-1000; + } else { + return 0; + } +} + +static const char *ais_charset = "?ABCDEFGHIJKLMNOPQRSTUVWXYZ????? ???????????????0123456789??????"; + +// Decode a raw Mode S message demodulated as a stream of bytes by +// mode_s_detect(), and split it into fields populating a mode_s_msg structure. +void mode_s_decode(mode_s_t *self, struct mode_s_msg *mm, unsigned char *msg) { + uint32_t crc2; // Computed CRC, used to verify the message CRC. + + // Work on our local copy + memcpy(mm->msg, msg, MODE_S_LONG_MSG_BYTES); + msg = mm->msg; + + // Get the message type ASAP as other operations depend on this + mm->msgtype = msg[0]>>3; // Downlink Format + mm->msgbits = mode_s_msg_len_by_type(mm->msgtype); + + // CRC is always the last three bytes. + mm->crc = ((uint32_t)msg[(mm->msgbits/8)-3] << 16) | + ((uint32_t)msg[(mm->msgbits/8)-2] << 8) | + (uint32_t)msg[(mm->msgbits/8)-1]; + crc2 = mode_s_checksum(msg, mm->msgbits); + + // Check CRC and fix single bit errors using the CRC when possible (DF 11 and 17). + mm->errorbit = -1; // No error + mm->crcok = (mm->crc == crc2); + + if (!mm->crcok && self->fix_errors && (mm->msgtype == 11 || mm->msgtype == 17)) { + if ((mm->errorbit = fix_single_bit_errors(msg, mm->msgbits)) != -1) { + mm->crc = mode_s_checksum(msg, mm->msgbits); + mm->crcok = 1; + } else if (self->aggressive && mm->msgtype == 17 && + (mm->errorbit = fix_two_bits_errors(msg, mm->msgbits)) != -1) { + mm->crc = mode_s_checksum(msg, mm->msgbits); + mm->crcok = 1; + } + } + + // Note that most of the other computation happens *after* we fix the + // single bit errors, otherwise we would need to recompute the fields + // again. + mm->ca = msg[0] & 7; // Responder capabilities. + + // ICAO address + mm->aa1 = msg[1]; + mm->aa2 = msg[2]; + mm->aa3 = msg[3]; + + // DF 17 type (assuming this is a DF17, otherwise not used) + mm->metype = msg[4] >> 3; // Extended squitter message type. + mm->mesub = msg[4] & 7; // Extended squitter message subtype. + + // Fields for DF4,5,20,21 + mm->fs = msg[0] & 7; // Flight status for DF4,5,20,21 + mm->dr = msg[1] >> 3 & 31; // Request extraction of downlink request. + mm->um = ((msg[1] & 7)<<3)| // Request extraction of downlink request. + msg[2]>>5; + + // In the squawk (identity) field bits are interleaved like that (message + // bit 20 to bit 32): + // + // C1-A1-C2-A2-C4-A4-ZERO-B1-D1-B2-D2-B4-D4 + // + // So every group of three bits A, B, C, D represent an integer from 0 to + // 7. + // + // The actual meaning is just 4 octal numbers, but we convert it into a + // base ten number tha happens to represent the four octal numbers. + // + // For more info: http://en.wikipedia.org/wiki/Gillham_code + { + int a, b, c, d; + + a = ((msg[3] & 0x80) >> 5) | + ((msg[2] & 0x02) >> 0) | + ((msg[2] & 0x08) >> 3); + b = ((msg[3] & 0x02) << 1) | + ((msg[3] & 0x08) >> 2) | + ((msg[3] & 0x20) >> 5); + c = ((msg[2] & 0x01) << 2) | + ((msg[2] & 0x04) >> 1) | + ((msg[2] & 0x10) >> 4); + d = ((msg[3] & 0x01) << 2) | + ((msg[3] & 0x04) >> 1) | + ((msg[3] & 0x10) >> 4); + mm->identity = a*1000 + b*100 + c*10 + d; + } + + // DF 11 & 17: try to populate our ICAO addresses whitelist. DFs with an AP + // field (xored addr and crc), try to decode it. + if (mm->msgtype != 11 && mm->msgtype != 17) { + // Check if we can check the checksum for the Downlink Formats where + // the checksum is xored with the aircraft ICAO address. We try to + // brute force it using a list of recently seen aircraft addresses. + if (brute_force_ap(self, msg, mm)) { + // We recovered the message, mark the checksum as valid. + mm->crcok = 1; + } else { + mm->crcok = 0; + } + } else { + // If this is DF 11 or DF 17 and the checksum was ok, we can add this + // address to the list of recently seen addresses. + if (mm->crcok && mm->errorbit == -1) { + uint32_t addr = (mm->aa1 << 16) | (mm->aa2 << 8) | mm->aa3; + add_recently_seen_icao_addr(self, addr); + } + } + + // Decode 13 bit altitude for DF0, DF4, DF16, DF20 + if (mm->msgtype == 0 || mm->msgtype == 4 || + mm->msgtype == 16 || mm->msgtype == 20) { + mm->altitude = decode_ac13_field(msg, &mm->unit); + } + + // Decode extended squitter specific stuff. + if (mm->msgtype == 17) { + // Decode the extended squitter message. + + if (mm->metype >= 1 && mm->metype <= 4) { + // Aircraft Identification and Category + mm->aircraft_type = mm->metype-1; + mm->flight[0] = (ais_charset)[msg[5]>>2]; + mm->flight[1] = ais_charset[((msg[5]&3)<<4)|(msg[6]>>4)]; + mm->flight[2] = ais_charset[((msg[6]&15)<<2)|(msg[7]>>6)]; + mm->flight[3] = ais_charset[msg[7]&63]; + mm->flight[4] = ais_charset[msg[8]>>2]; + mm->flight[5] = ais_charset[((msg[8]&3)<<4)|(msg[9]>>4)]; + mm->flight[6] = ais_charset[((msg[9]&15)<<2)|(msg[10]>>6)]; + mm->flight[7] = ais_charset[msg[10]&63]; + mm->flight[8] = '\0'; + } else if (mm->metype >= 9 && mm->metype <= 18) { + // Airborne position Message + mm->fflag = msg[6] & (1<<2); + mm->tflag = msg[6] & (1<<3); + mm->altitude = decode_ac12_field(msg, &mm->unit); + mm->raw_latitude = ((msg[6] & 3) << 15) | + (msg[7] << 7) | + (msg[8] >> 1); + mm->raw_longitude = ((msg[8]&1) << 16) | + (msg[9] << 8) | + msg[10]; + } else if (mm->metype == 19 && mm->mesub >= 1 && mm->mesub <= 4) { + // Airborne Velocity Message + if (mm->mesub == 1 || mm->mesub == 2) { + mm->ew_dir = (msg[5]&4) >> 2; + mm->ew_velocity = ((msg[5]&3) << 8) | msg[6]; + mm->ns_dir = (msg[7]&0x80) >> 7; + mm->ns_velocity = ((msg[7]&0x7f) << 3) | ((msg[8]&0xe0) >> 5); + mm->vert_rate_source = (msg[8]&0x10) >> 4; + mm->vert_rate_sign = (msg[8]&0x8) >> 3; + mm->vert_rate = ((msg[8]&7) << 6) | ((msg[9]&0xfc) >> 2); + // Compute velocity and angle from the two speed components + mm->velocity = sqrt(mm->ns_velocity*mm->ns_velocity+ + mm->ew_velocity*mm->ew_velocity); + if (mm->velocity) { + int ewv = mm->ew_velocity; + int nsv = mm->ns_velocity; + double heading; + + if (mm->ew_dir) ewv *= -1; + if (mm->ns_dir) nsv *= -1; + heading = atan2(ewv, nsv); + + // Convert to degrees. + mm->heading = heading * 360 / (M_PI*2); + // We don't want negative values but a 0-360 scale. + if (mm->heading < 0) mm->heading += 360; + } else { + mm->heading = 0; + } + } else if (mm->mesub == 3 || mm->mesub == 4) { + mm->heading_is_valid = msg[5] & (1<<2); + mm->heading = (360.0/128) * (((msg[5] & 3) << 5) | + (msg[6] >> 3)); + } + } + } + mm->phase_corrected = 0; // Set to 1 by the caller if needed. +} + +// Turn I/Q samples pointed by `data` into the magnitude vector pointed by `mag` +void mode_s_compute_magnitude_vector(short *data, uint16_t *mag, uint32_t size) +{ + uint32_t j; + + // Compute the magnitude vector. It's just SQRT(I^2 + Q^2), but we rescale + // to the 0-255 range to exploit the full resolution. + /*for (j = 0; j < size; j += 2) { + int i = data[j]-127; + int q = data[j+1]-127; + + if (i < 0) i = -i; + if (q < 0) q = -q; + mag[j/2] = maglut[i*129+q]; + }*/ + for (j=0; j mag[2]/3) return 1; + if (mag[10] > mag[9]/3) return 1; + if (mag[6] > mag[7]/3) return -1; + if (mag[-1] > mag[1]/3) return -1; + return 0; +} + +// This function does not really correct the phase of the message, it just +// applies a transformation to the first sample representing a given bit: +// +// If the previous bit was one, we amplify it a bit. +// If the previous bit was zero, we decrease it a bit. +// +// This simple transformation makes the message a bit more likely to be +// correctly decoded for out of phase messages: +// +// When messages are out of phase there is more uncertainty in sequences of the +// same bit multiple times, since 11111 will be transmitted as continuously +// altering magnitude (high, low, high, low...) +// +// However because the message is out of phase some part of the high is mixed +// in the low part, so that it is hard to distinguish if it is a zero or a one. +// +// However when the message is out of phase passing from 0 to 1 or from 1 to 0 +// happens in a very recognizable way, for instance in the 0 -> 1 transition, +// magnitude goes low, high, high, low, and one of of the two middle samples +// the high will be *very* high as part of the previous or next high signal +// will be mixed there. +// +// Applying our simple transformation we make more likely if the current bit is +// a zero, to detect another zero. Symmetrically if it is a one it will be more +// likely to detect a one because of the transformation. In this way similar +// levels will be interpreted more likely in the correct way. +void apply_phase_correction(uint16_t *mag) { + int j; + + mag += 16; // Skip preamble. + for (j = 0; j < (MODE_S_LONG_MSG_BITS-1)*2; j += 2) { + if (mag[j] > mag[j+1]) { + // One + mag[j+2] = (mag[j+2] * 5) / 4; + } else { + // Zero + mag[j+2] = (mag[j+2] * 4) / 5; + } + } +} + +// Detect a Mode S messages inside the magnitude buffer pointed by 'mag' and of +// size 'maglen' bytes. Every detected Mode S message is convert it into a +// stream of bits and passed to the function to display it. +void mode_s_detect(mode_s_t *self, uint16_t *mag, uint32_t maglen, mode_s_callback_t cb) { + unsigned char bits[MODE_S_LONG_MSG_BITS]; + unsigned char msg[MODE_S_LONG_MSG_BITS/2]; + uint16_t aux[MODE_S_LONG_MSG_BITS*2]; + uint32_t j; + int use_correction = 0; + + // The Mode S preamble is made of impulses of 0.5 microseconds at the + // following time offsets: + // + // 0 - 0.5 usec: first impulse. + // 1.0 - 1.5 usec: second impulse. + // 3.5 - 4 usec: third impulse. + // 4.5 - 5 usec: last impulse. + // + // Since we are sampling at 2 Mhz every sample in our magnitude vector is + // 0.5 usec, so the preamble will look like this, assuming there is an + // impulse at offset 0 in the array: + // + // 0 ----------------- + // 1 - + // 2 ------------------ + // 3 -- + // 4 - + // 5 -- + // 6 - + // 7 ------------------ + // 8 -- + // 9 ------------------- + for (j = 0; j < maglen - MODE_S_FULL_LEN*2; j++) { + int low, high, delta, i, errors; + int good_message = 0; + + if (use_correction) goto good_preamble; // We already checked it. + + // First check of relations between the first 10 samples representing a + // valid preamble. We don't even investigate further if this simple + // test is not passed. + if (!(mag[j] > mag[j+1] && + mag[j+1] < mag[j+2] && + mag[j+2] > mag[j+3] && + mag[j+3] < mag[j] && + mag[j+4] < mag[j] && + mag[j+5] < mag[j] && + mag[j+6] < mag[j] && + mag[j+7] > mag[j+8] && + mag[j+8] < mag[j+9] && + mag[j+9] > mag[j+6])) + { + continue; + } + + // The samples between the two spikes must be < than the average of the + // high spikes level. We don't test bits too near to the high levels as + // signals can be out of phase so part of the energy can be in the near + // samples. + high = (mag[j]+mag[j+2]+mag[j+7]+mag[j+9])/6; + if (mag[j+4] >= high || + mag[j+5] >= high) + { + continue; + } + + // Similarly samples in the range 11-14 must be low, as it is the space + // between the preamble and real data. Again we don't test bits too + // near to high levels, see above. + if (mag[j+11] >= high || + mag[j+12] >= high || + mag[j+13] >= high || + mag[j+14] >= high) + { + continue; + } + +good_preamble: + // If the previous attempt with this message failed, retry using + // magnitude correction. + if (use_correction) { + memcpy(aux, mag+j+MODE_S_PREAMBLE_US*2, sizeof(aux)); + if (j && detect_out_of_phase(mag+j)) { + apply_phase_correction(mag+j); + } + // TODO ... apply other kind of corrections. + } + + // Decode all the next 112 bits, regardless of the actual message size. + // We'll check the actual message type later. + errors = 0; + for (i = 0; i < MODE_S_LONG_MSG_BITS*2; i += 2) { + low = mag[j+i+MODE_S_PREAMBLE_US*2]; + high = mag[j+i+MODE_S_PREAMBLE_US*2+1]; + delta = low-high; + if (delta < 0) delta = -delta; + + if (i > 0 && delta < 256) { + bits[i/2] = bits[i/2-1]; + } else if (low == high) { + // Checking if two adiacent samples have the same magnitude is + // an effective way to detect if it's just random noise that + // was detected as a valid preamble. + bits[i/2] = 2; // error + if (i < MODE_S_SHORT_MSG_BITS*2) errors++; + } else if (low > high) { + bits[i/2] = 1; + } else { + // (low < high) for exclusion + bits[i/2] = 0; + } + } + + // Restore the original message if we used magnitude correction. + if (use_correction) + memcpy(mag+j+MODE_S_PREAMBLE_US*2, aux, sizeof(aux)); + + // Pack bits into bytes + for (i = 0; i < MODE_S_LONG_MSG_BITS; i += 8) { + msg[i/8] = + bits[i]<<7 | + bits[i+1]<<6 | + bits[i+2]<<5 | + bits[i+3]<<4 | + bits[i+4]<<3 | + bits[i+5]<<2 | + bits[i+6]<<1 | + bits[i+7]; + } + + int msgtype = msg[0]>>3; + int msglen = mode_s_msg_len_by_type(msgtype)/8; + + // Last check, high and low bits are different enough in magnitude to + // mark this as real message and not just noise? + delta = 0; + for (i = 0; i < msglen*8*2; i += 2) { + delta += abs(mag[j+i+MODE_S_PREAMBLE_US*2]- + mag[j+i+MODE_S_PREAMBLE_US*2+1]); + } + delta /= msglen*4; + + // Filter for an average delta of three is small enough to let almost + // every kind of message to pass, but high enough to filter some random + // noise. + if (delta < 10*255) { + use_correction = 0; + continue; + } + + // If we reached this point, and error is zero, we are very likely with + // a Mode S message in our hands, but it may still be broken and CRC + // may not be correct. This is handled by the next layer. + if (errors == 0 || (self->aggressive && errors < 3)) { + struct mode_s_msg mm; + + // Decode the received message + mode_s_decode(self, &mm, msg); + + // Skip this message if we are sure it's fine. + if (mm.crcok) { + j += (MODE_S_PREAMBLE_US+(msglen*8))*2; + good_message = 1; + if (use_correction) + mm.phase_corrected = 1; + } + + // Pass data to the next layer + if (self->check_crc == 0 || mm.crcok) { + cb(self, &mm); + } + } + + // Retry with phase correction if possible. + if (!good_message && !use_correction) { + j--; + use_correction = 1; + } else { + use_correction = 0; + } + } +} \ No newline at end of file diff --git a/examples/cpp/modes.h b/examples/cpp/modes.h new file mode 100644 index 0000000..358efa5 --- /dev/null +++ b/examples/cpp/modes.h @@ -0,0 +1,88 @@ +#ifndef __MODE_S_DECODER_H +#define __MODE_S_DECODER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include +#include +#include + +#define MODE_S_ICAO_CACHE_LEN 1024 // Power of two required +#define MODE_S_LONG_MSG_BYTES (112/8) +#define MODE_S_UNIT_FEET 0 +#define MODE_S_UNIT_METERS 1 + +// Program state +typedef struct { + // Internal state + uint32_t icao_cache[sizeof(uint32_t)*MODE_S_ICAO_CACHE_LEN*2]; // Recently seen ICAO addresses cache + + // Configuration + int fix_errors; // Single bit error correction if true + int aggressive; // Aggressive detection algorithm + int check_crc; // Only display messages with good CRC +} mode_s_t; + +// The struct we use to store information about a decoded message +struct mode_s_msg { + // Generic fields + unsigned char msg[MODE_S_LONG_MSG_BYTES]; // Binary message + int msgbits; // Number of bits in message + int msgtype; // Downlink format # + int crcok; // True if CRC was valid + uint32_t crc; // Message CRC + int errorbit; // Bit corrected. -1 if no bit corrected. + int aa1, aa2, aa3; // ICAO Address bytes 1 2 and 3 + int phase_corrected; // True if phase correction was applied. + + // DF 11 + int ca; // Responder capabilities. + + // DF 17 + int metype; // Extended squitter message type. + int mesub; // Extended squitter message subtype. + int heading_is_valid; + int heading; + int aircraft_type; + int fflag; // 1 = Odd, 0 = Even CPR message. + int tflag; // UTC synchronized? + int raw_latitude; // Non decoded latitude + int raw_longitude; // Non decoded longitude + char flight[9]; // 8 chars flight number. + int ew_dir; // 0 = East, 1 = West. + int ew_velocity; // E/W velocity. + int ns_dir; // 0 = North, 1 = South. + int ns_velocity; // N/S velocity. + int vert_rate_source; // Vertical rate source. + int vert_rate_sign; // Vertical rate sign. + int vert_rate; // Vertical rate. + int velocity; // Computed from EW and NS velocity. + + // DF4, DF5, DF20, DF21 + int fs; // Flight status for DF4,5,20,21 + int dr; // Request extraction of downlink request. + int um; // Request extraction of downlink request. + int identity; // 13 bits identity (Squawk). + + // Fields used by multiple message types. + int altitude, unit; +}; + +typedef void (*mode_s_callback_t)(mode_s_t *self, struct mode_s_msg *mm); + +void mode_s_init(mode_s_t *self); +void mode_s_compute_magnitude_vector(short *data, uint16_t *mag, uint32_t size); +void mode_s_detect(mode_s_t *self, uint16_t *mag, uint32_t maglen, mode_s_callback_t); +void mode_s_decode(mode_s_t *self, struct mode_s_msg *mm, unsigned char *msg); + +#ifdef __cplusplus +} +#endif + + +#endif \ No newline at end of file diff --git a/software/libcariboulite/src/caribou_smi/kernel/bcm2835_smi.c b/software/libcariboulite/src/caribou_smi/kernel/bcm2835_smi.c index c77e428..c40a0b3 100644 --- a/software/libcariboulite/src/caribou_smi/kernel/bcm2835_smi.c +++ b/software/libcariboulite/src/caribou_smi/kernel/bcm2835_smi.c @@ -49,6 +49,7 @@ #include #include #include +#include #define BCM2835_SMI_IMPLEMENTATION #include @@ -228,10 +229,10 @@ void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *inst) // Additions (DM) <.. - smics_temp |= SMICS_INTR; // Generate interrupt while RXR = 1 + //smics_temp |= SMICS_INTR; // Generate interrupt while RXR = 1 // RXR = 1: RX FIFO is at least ¾ full or the transfer has finished and the // FIFO still needs reading. The transfer direction must be set to READ - smics_temp |= SMICS_INTT; // Generate interrupt while TXW = 1 + //smics_temp |= SMICS_INTT; // Generate interrupt while TXW = 1 // TXW = 1: TX FIFO is less than ¼ full and the transfer direction is set to WRITE. // ..> Additions (DM) @@ -690,6 +691,50 @@ static void smi_dma_write_sgl( } } +ssize_t bcm2835_smi_user_dma_read_to_fifo( + struct bcm2835_smi_instance *inst, + struct kfifo *fifo, + size_t fifo_len) +{ + struct dma_async_tx_descriptor *desc; + struct scatterlist sg[2]; + unsigned int ret; + size_t n_bytes; + + ret = kfifo_dma_in_prepare(fifo, sg, ARRAY_SIZE(sg), fifo_len); + + if (ret <= 0) + { + // no space in fifo, return 0; + return 0; + } + + n_bytes = sg[0].length + sg[1].length; + smi_disable(inst, DMA_DEV_TO_MEM); + desc = smi_dma_submit_sgl(inst, sg, ret, DMA_DEV_TO_MEM, NULL); + + dma_async_issue_pending(inst->dma_chan); + if (inst->settings.data_width == SMI_WIDTH_8BIT) + { + smi_init_programmed_read(inst, n_bytes); + } + else + { + smi_init_programmed_read(inst, n_bytes / 2); + } + + /*if (dma_wait_for_async_tx(desc) == DMA_ERROR) + { + smi_dump_context_labelled(inst, "DMA timeout!"); + return 0; + }*/ + + kfifo_dma_in_finish(fifo, n_bytes); + + return n_bytes; +} +EXPORT_SYMBOL(bcm2835_smi_user_dma_read_to_fifo); + ssize_t bcm2835_smi_user_dma( struct bcm2835_smi_instance *inst, enum dma_transfer_direction dma_dir, diff --git a/software/libcariboulite/src/caribou_smi/kernel/smi_stream_dev.c b/software/libcariboulite/src/caribou_smi/kernel/smi_stream_dev.c index 1bb0160..3b051d2 100644 --- a/software/libcariboulite/src/caribou_smi/kernel/smi_stream_dev.c +++ b/software/libcariboulite/src/caribou_smi/kernel/smi_stream_dev.c @@ -184,6 +184,11 @@ static long smi_stream_ioctl(struct file *file, unsigned int cmd, unsigned long return ret; } +ssize_t bcm2835_smi_user_dma_read_to_fifo( + struct bcm2835_smi_instance *inst, + struct kfifo *fifo, + size_t fifo_len); + int reader_thread_stream_function(void *pv) { struct bcm2835_smi_bounce_info *bounce = NULL; @@ -198,6 +203,12 @@ int reader_thread_stream_function(void *pv) continue; } + /*count = bcm2835_smi_user_dma_read_to_fifo(smi_inst, &inst->rx_fifo, FIFO_SIZE_MULTIPLIER * DMA_BOUNCE_BUFFER_SIZE); + if (count) + { + wake_up_interruptible(&inst->readable); + }*/ + count = bcm2835_smi_user_dma(smi_inst, DMA_DEV_TO_MEM, inst->rx_buffer, DMA_BOUNCE_BUFFER_SIZE, &bounce); //printk("count1 = %d\n", count); @@ -211,7 +222,7 @@ int reader_thread_stream_function(void *pv) } //count = 0;//dma_bounce_user(DMA_DEV_TO_MEM, inst->rx_buffer, DMA_BOUNCE_BUFFER_SIZE, bounce); - /* Wait for current chunk to complete: */ + // Wait for current chunk to complete: if (down_timeout(&bounce->callback_sem, msecs_to_jiffies(1000))) { printk("DMA bounce timed out"); diff --git a/software/libcariboulite/src/cariboulite_radios.c b/software/libcariboulite/src/cariboulite_radios.c index ff660c5..2c3437d 100644 --- a/software/libcariboulite/src/cariboulite_radios.c +++ b/software/libcariboulite/src/cariboulite_radios.c @@ -259,7 +259,7 @@ int cariboulite_set_rx_bandwidth(cariboulite_radios_st* radios, at86rf215_radio_f_cut_en fcut = at86rf215_radio_rx_f_cut_half_fs; // Automatically calculate the digital f_cut - /*if (rx_bw >= at86rf215_radio_rx_bw_BW160KHZ_IF250KHZ && rx_bw <= at86rf215_radio_rx_bw_BW500KHZ_IF500KHZ) + if (rx_bw >= at86rf215_radio_rx_bw_BW160KHZ_IF250KHZ && rx_bw <= at86rf215_radio_rx_bw_BW500KHZ_IF500KHZ) fcut = at86rf215_radio_rx_f_cut_0_25_half_fs; else if (rx_bw >= at86rf215_radio_rx_bw_BW630KHZ_IF1000KHZ && rx_bw <= at86rf215_radio_rx_bw_BW630KHZ_IF1000KHZ) fcut = at86rf215_radio_rx_f_cut_0_375_half_fs; @@ -268,7 +268,7 @@ int cariboulite_set_rx_bandwidth(cariboulite_radios_st* radios, else if (rx_bw >= at86rf215_radio_rx_bw_BW1250KHZ_IF2000KHZ && rx_bw <= at86rf215_radio_rx_bw_BW1250KHZ_IF2000KHZ) fcut = at86rf215_radio_rx_f_cut_0_75_half_fs; else - fcut = at86rf215_radio_rx_f_cut_half_fs;*/ + fcut = at86rf215_radio_rx_f_cut_half_fs; rad->rx_fcut = fcut; @@ -643,8 +643,10 @@ int cariboulite_set_frequency( cariboulite_radios_st* radios, double f_rf_mod_26 = (f_rf / 26e6); f_rf_mod_32 -= (uint64_t)(f_rf_mod_32); f_rf_mod_26 -= (uint64_t)(f_rf_mod_26); + f_rf_mod_32 *= 32e6; + f_rf_mod_26 *= 26e6; if (f_rf_mod_32 > 16e6) f_rf_mod_32 = 32e6 - f_rf_mod_32; - if (f_rf_mod_26 > 13e6) f_rf_mod_26 = 13e6 - f_rf_mod_26; + if (f_rf_mod_26 > 13e6) f_rf_mod_26 = 26e6 - f_rf_mod_26; ext_ref_choice = f_rf_mod_32 > f_rf_mod_26 ? cariboulite_ext_ref_32mhz : cariboulite_ext_ref_26mhz; cariboulite_setup_ext_ref (rad->cariboulite_sys, ext_ref_choice); @@ -668,9 +670,10 @@ int cariboulite_set_frequency( cariboulite_radios_st* radios, conversion_direction = conversion_dir_up; } //------------------------------------- - else if ( f_rf >= CARIBOULITE_2G4_MIN && + else if ( f_rf > CARIBOULITE_2G4_MIN && f_rf <= CARIBOULITE_2G4_MAX ) { + cariboulite_setup_ext_ref (rad->cariboulite_sys, cariboulite_ext_ref_off); // region #2 - bypass mode // setup modem frequency <= f_rf modem_act_freq = (double)at86rf215_setup_channel (&rad->cariboulite_sys->modem, diff --git a/software/libcariboulite/src/cariboulite_setup.c b/software/libcariboulite/src/cariboulite_setup.c index 03e3c35..81b7abc 100644 --- a/software/libcariboulite/src/cariboulite_setup.c +++ b/software/libcariboulite/src/cariboulite_setup.c @@ -280,17 +280,17 @@ int cariboulite_setup_ext_ref ( cariboulite_st *sys, cariboulite_ext_ref_freq_en { case cariboulite_ext_ref_26mhz: ZF_LOGD("Setting ext_ref = 26MHz"); - at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_4ma, at86rf215_clock_out_freq_26mhz); + at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_2ma, at86rf215_clock_out_freq_26mhz); rffc507x_setup_reference_freq(&sys->mixer, 26e6); break; case cariboulite_ext_ref_32mhz: ZF_LOGD("Setting ext_ref = 32MHz"); - at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_4ma, at86rf215_clock_out_freq_32mhz); + at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_2ma, at86rf215_clock_out_freq_32mhz); rffc507x_setup_reference_freq(&sys->mixer, 32e6); break; case cariboulite_ext_ref_off: ZF_LOGD("Setting ext_ref = OFF"); - at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_4ma, at86rf215_clock_out_freq_off); + at86rf215_set_clock_output(&sys->modem, at86rf215_drive_current_2ma, at86rf215_clock_out_freq_off); default: return -1; break; @@ -355,7 +355,7 @@ int cariboulite_init_submodules (cariboulite_st* sys) at86rf215_iq_interface_config_st modem_iq_config = { .loopback_enable = 0, - .drv_strength = at86rf215_iq_drive_current_4ma, + .drv_strength = at86rf215_iq_drive_current_2ma, .common_mode_voltage = at86rf215_iq_common_mode_v_ieee1596_1v2, .tx_control_with_iq_if = false, .radio09_mode = at86rf215_iq_if_mode, diff --git a/software/libcariboulite/src/io_utils/pigpio/pigpio.c b/software/libcariboulite/src/io_utils/pigpio/pigpio.c index 5bc56a1..322aee9 100644 --- a/software/libcariboulite/src/io_utils/pigpio/pigpio.c +++ b/software/libcariboulite/src/io_utils/pigpio/pigpio.c @@ -8746,7 +8746,7 @@ void gpioTerminate(void) { int i; - printf("gpioTerminate\n"); + //printf("gpioTerminate\n"); DBG(DBG_USER, ""); if (!libInitialised) return; @@ -8765,7 +8765,7 @@ void gpioTerminate(void) initKillDMA(dmaOut); } - printf("gpioTerminate - reset DMA\n"); + //printf("gpioTerminate - reset DMA\n"); #ifndef EMBEDDED_IN_VM if ((gpioCfg.internals & PI_CFG_STATS) && @@ -8804,12 +8804,12 @@ void gpioTerminate(void) } #endif - printf("gpioTerminate - initReleaseResources\n"); + //printf("gpioTerminate - initReleaseResources\n"); initReleaseResources(); - printf("gpioTerminate - initReleaseResources finished\n"); + //printf("gpioTerminate - initReleaseResources finished\n"); fflush(NULL); - printf("gpioTerminate - finished FFlush\n"); + //printf("gpioTerminate - finished FFlush\n"); libInitialised = 0; } diff --git a/software/libcariboulite/src/rffc507x/rffc507x.c b/software/libcariboulite/src/rffc507x/rffc507x.c index 3c81801..16b128a 100644 --- a/software/libcariboulite/src/rffc507x/rffc507x.c +++ b/software/libcariboulite/src/rffc507x/rffc507x.c @@ -149,8 +149,8 @@ int rffc507x_init( rffc507x_st* dev, set_RFFC507X_P2CTV(dev, 12); set_RFFC507X_P1CTV(dev, 12); set_RFFC507X_RGBYP(dev, 1); - set_RFFC507X_P2MIXIDD(dev, 2); - set_RFFC507X_P1MIXIDD(dev, 2); + set_RFFC507X_P2MIXIDD(dev, 6); + set_RFFC507X_P1MIXIDD(dev, 6); // Others set_RFFC507X_LDEN(dev, 1); diff --git a/software/libcariboulite/src/soapy_api/Cariboulite.cpp b/software/libcariboulite/src/soapy_api/Cariboulite.cpp index 7ffb27e..ba8736b 100644 --- a/software/libcariboulite/src/soapy_api/Cariboulite.cpp +++ b/software/libcariboulite/src/soapy_api/Cariboulite.cpp @@ -25,7 +25,7 @@ Cariboulite::Cariboulite(const SoapySDR::Kwargs &args) for (int i = 0; i < 4; i++) { int stream_id = CARIBOU_SMI_GET_STREAM_ID(types[i], channels[i]); - sample_queues[i] = new SampleQueue(1024*1024/2, NUM_SAMPLEQUEUE_BUFS); + sample_queues[i] = new SampleQueue(getStreamMTU(NULL)*NUM_BYTES_PER_CPLX_ELEM, NUM_SAMPLEQUEUE_BUFS); int dir = (types[i] == caribou_smi_stream_type_write)? SOAPY_SDR_TX : SOAPY_SDR_RX; int ch = (channels[i] == caribou_smi_channel_900)? cariboulite_channel_s1g : cariboulite_channel_6g; sample_queues[i]->AttachStreamId(stream_id, dir, ch); @@ -33,7 +33,7 @@ Cariboulite::Cariboulite(const SoapySDR::Kwargs &args) cariboulite_init_radios(&radios, &sess.cariboulite_sys); - SoapySDR_logf(SOAPY_SDR_INFO, "Cariboulite c'tor"); + //SoapySDR_logf(SOAPY_SDR_INFO, "Cariboulite c'tor"); // TODO: Exception when error } @@ -47,7 +47,7 @@ Cariboulite::~Cariboulite() { delete sample_queues[i]; } - SoapySDR_logf(SOAPY_SDR_INFO, "Cariboulite d'tor"); + //SoapySDR_logf(SOAPY_SDR_INFO, "Cariboulite d'tor"); } /******************************************************************* diff --git a/software/libcariboulite/src/soapy_api/Cariboulite.hpp b/software/libcariboulite/src/soapy_api/Cariboulite.hpp index 8aa16b7..7ef2d9b 100644 --- a/software/libcariboulite/src/soapy_api/Cariboulite.hpp +++ b/software/libcariboulite/src/soapy_api/Cariboulite.hpp @@ -30,7 +30,7 @@ enum Cariboulite_Format }; //#define BUFFER_SIZE_MS ( 10 ) -#define NUM_SAMPLEQUEUE_BUFS ( 10 ) +#define NUM_SAMPLEQUEUE_BUFS ( 3 ) #define NUM_BYTES_PER_CPLX_ELEM ( 4 ) //#define GET_MTU_MS(ms) ( 4000*(ms) ) //#define GET_MTU_MS_BYTES(ms) ( GET_MTU_MS(ms) * NUM_BYTES_PER_CPLX_ELEM ) diff --git a/software/libcariboulite/src/soapy_api/CaribouliteSampleQueue.cpp b/software/libcariboulite/src/soapy_api/CaribouliteSampleQueue.cpp index 528a8b5..f991fda 100644 --- a/software/libcariboulite/src/soapy_api/CaribouliteSampleQueue.cpp +++ b/software/libcariboulite/src/soapy_api/CaribouliteSampleQueue.cpp @@ -47,7 +47,7 @@ SampleQueue::SampleQueue(int mtu_bytes, int num_buffers) SampleQueue::~SampleQueue() { //printf("~SampleQueue streamID: %d, dir: %d, channel: %d\n", stream_id, stream_dir, stream_channel); - SoapySDR_logf(SOAPY_SDR_INFO, "~SampleQueue streamID: %d, dir: %d, channel: %d", stream_id, stream_dir, stream_channel); + //SoapySDR_logf(SOAPY_SDR_INFO, "~SampleQueue streamID: %d, dir: %d, channel: %d", stream_id, stream_dir, stream_channel); stream_id = -1; stream_dir = -1; diff --git a/software/libcariboulite/src/soapy_api/CaribouliteSession.cpp b/software/libcariboulite/src/soapy_api/CaribouliteSession.cpp index 541da8c..97746ac 100644 --- a/software/libcariboulite/src/soapy_api/CaribouliteSession.cpp +++ b/software/libcariboulite/src/soapy_api/CaribouliteSession.cpp @@ -64,11 +64,11 @@ SoapyCaribouliteSession::~SoapyCaribouliteSession(void) { std::lock_guard lock(sessionMutex); //printf("~SoapyCaribouliteSession, sessionCount: %ld\n", sessionCount); - SoapySDR_logf(SOAPY_SDR_INFO, "~SoapyCaribouliteSession, sessionCount: %ld", sessionCount); + //SoapySDR_logf(SOAPY_SDR_INFO, "~SoapyCaribouliteSession, sessionCount: %ld", sessionCount); sessionCount--; if (sessionCount == 0) { cariboulite_release_driver(&cariboulite_sys); } - SoapySDR_logf(SOAPY_SDR_INFO, "~SoapyCaribouliteSession CaribouLite released"); + //SoapySDR_logf(SOAPY_SDR_INFO, "~SoapyCaribouliteSession CaribouLite released"); } diff --git a/software/libcariboulite/src/soapy_api/CaribouliteStream.cpp b/software/libcariboulite/src/soapy_api/CaribouliteStream.cpp index d8e63f6..7895ab7 100644 --- a/software/libcariboulite/src/soapy_api/CaribouliteStream.cpp +++ b/software/libcariboulite/src/soapy_api/CaribouliteStream.cpp @@ -246,8 +246,8 @@ SoapySDR::Stream *Cariboulite::setupStream(const int direction, } } - SoapySDR_logf(SOAPY_SDR_INFO, "finished setup stream, stream_id = %d, CW=%d", stream_id, cw); - return (SoapySDR::Stream *)((void*)stream_id); + //SoapySDR_logf(SOAPY_SDR_INFO, "finished setup stream, stream_id = %d, CW=%d", stream_id, cw); + return (SoapySDR::Stream *)((void*)(stream_id)); } //======================================================== @@ -258,7 +258,7 @@ SoapySDR::Stream *Cariboulite::setupStream(const int direction, */ void Cariboulite::closeStream(SoapySDR::Stream *stream) { - SoapySDR_logf(SOAPY_SDR_INFO, "closeStream"); + //SoapySDR_logf(SOAPY_SDR_INFO, "closeStream"); if (stream == NULL) return; int stream_id = (intptr_t)stream; @@ -278,7 +278,7 @@ void Cariboulite::closeStream(SoapySDR::Stream *stream) size_t Cariboulite::getStreamMTU(SoapySDR::Stream *stream) const { //printf("getStreamMTU\n"); - return 1024*1024/2; // # milliseconds of buffer + return 1024 * 1024 / 2 / 4; // # milliseconds of buffer } //======================================================== @@ -305,7 +305,7 @@ int Cariboulite::activateStream(SoapySDR::Stream *stream, const size_t numElems) { //printf("activateStream\n"); - SoapySDR_logf(SOAPY_SDR_INFO, "activateStream"); + //SoapySDR_logf(SOAPY_SDR_INFO, "activateStream"); int stream_id = (intptr_t)stream; cariboulite_activate_channel(&radios, @@ -336,7 +336,7 @@ int Cariboulite::activateStream(SoapySDR::Stream *stream, */ int Cariboulite::deactivateStream(SoapySDR::Stream *stream, const int flags, const long long timeNs) { - SoapySDR_logf(SOAPY_SDR_INFO, "deactivateStream"); + //SoapySDR_logf(SOAPY_SDR_INFO, "deactivateStream"); int stream_id = (intptr_t)stream; if ((cariboulite_channel_en)sample_queues[stream_id]->is_cw == 0)