From c0244e819e8bb0d6590de5913c06057a525e64f9 Mon Sep 17 00:00:00 2001 From: Ryzerth Date: Thu, 13 May 2021 17:31:40 +0200 Subject: [PATCH] Push before merge --- CMakeLists.txt | 5 + rx888_source/CMakeLists.txt | 22 - rx888_source/src/CyAPI/Si5351.cpp | 256 ---- rx888_source/src/CyAPI/Si5351.h | 13 - rx888_source/src/CyAPI/openFX3.cpp | 208 --- rx888_source/src/CyAPI/openFX3.h | 36 - rx888_source/src/main.cpp | 272 ---- sddc_source/CMakeLists.txt | 46 + sddc_source/res/firmwares/SDDC_FX3.img | Bin 0 -> 145388 bytes sddc_source/src/libsddc/CMakeLists.txt | 78 ++ sddc_source/src/libsddc/Core/CMakeLists.txt | 35 + sddc_source/src/libsddc/Core/FX3Class.h | 37 + sddc_source/src/libsddc/Core/PScope_uti.cpp | 35 + sddc_source/src/libsddc/Core/PScope_uti.h | 9 + sddc_source/src/libsddc/Core/RadioHandler.cpp | 440 +++++++ sddc_source/src/libsddc/Core/RadioHandler.h | 341 +++++ .../libsddc/Core/arch/linux/FX3handler.cpp | 95 ++ .../src/libsddc/Core/arch/linux/FX3handler.h | 44 + .../src/libsddc/Core/arch/linux/ezusb.c | 815 ++++++++++++ .../src/libsddc/Core/arch/linux/ezusb.h | 114 ++ .../src/libsddc/Core/arch/linux/logging.c | 47 + .../src/libsddc/Core/arch/linux/logging.h | 43 + .../src/libsddc/Core/arch/linux/streaming.c | 387 ++++++ .../src/libsddc/Core/arch/linux/streaming.h | 63 + .../src/libsddc/Core/arch/linux/usb_device.c | 574 +++++++++ .../src/libsddc/Core/arch/linux/usb_device.h | 60 + .../Core/arch/linux/usb_device_internals.h | 52 + .../libsddc/Core/arch/win32}/CyAPI/CyAPI.cpp | 51 +- .../libsddc/Core/arch/win32}/CyAPI/CyAPI.h | 2 +- .../Core/arch/win32}/CyAPI/CyUSB30_def.h | 0 .../Core/arch/win32}/CyAPI/UsbdStatus.h | 0 .../Core/arch/win32}/CyAPI/VersionNo.h | 0 .../libsddc/Core/arch/win32}/CyAPI/cyioctl.h | 0 .../libsddc/Core/arch/win32}/CyAPI/devioctl.h | 0 .../libsddc/Core/arch/win32}/CyAPI/usb100.h | 0 .../libsddc/Core/arch/win32}/CyAPI/usb200.h | 0 .../libsddc/Core/arch/win32/FX3handler.cpp | 456 +++++++ .../src/libsddc/Core/arch/win32/FX3handler.h | 73 ++ sddc_source/src/libsddc/Core/dsp/ringbuffer.h | 191 +++ sddc_source/src/libsddc/Core/fft_mt_r2iq.cpp | 263 ++++ sddc_source/src/libsddc/Core/fft_mt_r2iq.h | 127 ++ .../src/libsddc/Core/fft_mt_r2iq_avx.cpp | 9 + .../src/libsddc/Core/fft_mt_r2iq_avx2.cpp | 9 + .../src/libsddc/Core/fft_mt_r2iq_avx512.cpp | 9 + .../src/libsddc/Core/fft_mt_r2iq_def.cpp | 9 + .../src/libsddc/Core/fft_mt_r2iq_impl.hpp | 171 +++ sddc_source/src/libsddc/Core/fir.cpp | 105 ++ sddc_source/src/libsddc/Core/fir.h | 3 + sddc_source/src/libsddc/Core/license.txt | 28 + .../src/libsddc/Core/pffft/LICENSE.txt | 38 + .../src/libsddc/Core/pffft/bench_mixers.c | 733 +++++++++++ sddc_source/src/libsddc/Core/pffft/fmv.h | 20 + .../src/libsddc/Core/pffft/pf_mixer.cpp | 1148 +++++++++++++++++ sddc_source/src/libsddc/Core/pffft/pf_mixer.h | 283 ++++ sddc_source/src/libsddc/Core/r2iq.h | 50 + .../src/libsddc/Core/radio/BBRF103Radio.cpp | 145 +++ .../src/libsddc/Core/radio/HF103Radio.cpp | 50 + .../src/libsddc/Core/radio/RX888R2Radio.cpp | 178 +++ .../src/libsddc/Core/radio/RX888R3Radio.cpp | 210 +++ .../src/libsddc/Core/radio/RX888Radio.cpp | 1 + .../src/libsddc/Core/radio/RX999Radio.cpp | 118 ++ sddc_source/src/libsddc/Core/radio/RXLucy.cpp | 120 ++ .../src/libsddc/Core/radio/RadioHardware.cpp | 22 + sddc_source/src/libsddc/Core/sddc_config.cpp | 8 + sddc_source/src/libsddc/Core/sddc_config.h | 94 ++ sddc_source/src/libsddc/HWSDRtable.h | 44 + sddc_source/src/libsddc/Interface.h | 154 +++ sddc_source/src/libsddc/LICENSE.txt | 23 + .../src/libsddc/libsddc/CMakeLists.txt | 37 + sddc_source/src/libsddc/libsddc/libsddc.cpp | 396 ++++++ sddc_source/src/libsddc/libsddc/libsddc.h | 171 +++ sddc_source/src/libsddc/libsddc/wavehdr.h | 102 ++ sddc_source/src/libsddc/libsddc/wavewrite.c | 232 ++++ sddc_source/src/libsddc/libsddc/wavewrite.h | 55 + sddc_source/src/main.cpp | 248 ++++ 75 files changed, 9479 insertions(+), 834 deletions(-) delete mode 100644 rx888_source/CMakeLists.txt delete mode 100644 rx888_source/src/CyAPI/Si5351.cpp delete mode 100644 rx888_source/src/CyAPI/Si5351.h delete mode 100644 rx888_source/src/CyAPI/openFX3.cpp delete mode 100644 rx888_source/src/CyAPI/openFX3.h delete mode 100644 rx888_source/src/main.cpp create mode 100644 sddc_source/CMakeLists.txt create mode 100644 sddc_source/res/firmwares/SDDC_FX3.img create mode 100644 sddc_source/src/libsddc/CMakeLists.txt create mode 100644 sddc_source/src/libsddc/Core/CMakeLists.txt create mode 100644 sddc_source/src/libsddc/Core/FX3Class.h create mode 100644 sddc_source/src/libsddc/Core/PScope_uti.cpp create mode 100644 sddc_source/src/libsddc/Core/PScope_uti.h create mode 100644 sddc_source/src/libsddc/Core/RadioHandler.cpp create mode 100644 sddc_source/src/libsddc/Core/RadioHandler.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/FX3handler.cpp create mode 100644 sddc_source/src/libsddc/Core/arch/linux/FX3handler.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/ezusb.c create mode 100644 sddc_source/src/libsddc/Core/arch/linux/ezusb.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/logging.c create mode 100644 sddc_source/src/libsddc/Core/arch/linux/logging.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/streaming.c create mode 100644 sddc_source/src/libsddc/Core/arch/linux/streaming.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/usb_device.c create mode 100644 sddc_source/src/libsddc/Core/arch/linux/usb_device.h create mode 100644 sddc_source/src/libsddc/Core/arch/linux/usb_device_internals.h rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/CyAPI.cpp (99%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/CyAPI.h (99%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/CyUSB30_def.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/UsbdStatus.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/VersionNo.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/cyioctl.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/devioctl.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/usb100.h (100%) rename {rx888_source/src => sddc_source/src/libsddc/Core/arch/win32}/CyAPI/usb200.h (100%) create mode 100644 sddc_source/src/libsddc/Core/arch/win32/FX3handler.cpp create mode 100644 sddc_source/src/libsddc/Core/arch/win32/FX3handler.h create mode 100644 sddc_source/src/libsddc/Core/dsp/ringbuffer.h create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq.cpp create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq.h create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq_avx.cpp create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq_avx2.cpp create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq_avx512.cpp create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq_def.cpp create mode 100644 sddc_source/src/libsddc/Core/fft_mt_r2iq_impl.hpp create mode 100644 sddc_source/src/libsddc/Core/fir.cpp create mode 100644 sddc_source/src/libsddc/Core/fir.h create mode 100644 sddc_source/src/libsddc/Core/license.txt create mode 100644 sddc_source/src/libsddc/Core/pffft/LICENSE.txt create mode 100644 sddc_source/src/libsddc/Core/pffft/bench_mixers.c create mode 100644 sddc_source/src/libsddc/Core/pffft/fmv.h create mode 100644 sddc_source/src/libsddc/Core/pffft/pf_mixer.cpp create mode 100644 sddc_source/src/libsddc/Core/pffft/pf_mixer.h create mode 100644 sddc_source/src/libsddc/Core/r2iq.h create mode 100644 sddc_source/src/libsddc/Core/radio/BBRF103Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/HF103Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RX888R2Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RX888R3Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RX888Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RX999Radio.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RXLucy.cpp create mode 100644 sddc_source/src/libsddc/Core/radio/RadioHardware.cpp create mode 100644 sddc_source/src/libsddc/Core/sddc_config.cpp create mode 100644 sddc_source/src/libsddc/Core/sddc_config.h create mode 100644 sddc_source/src/libsddc/HWSDRtable.h create mode 100644 sddc_source/src/libsddc/Interface.h create mode 100644 sddc_source/src/libsddc/LICENSE.txt create mode 100644 sddc_source/src/libsddc/libsddc/CMakeLists.txt create mode 100644 sddc_source/src/libsddc/libsddc/libsddc.cpp create mode 100644 sddc_source/src/libsddc/libsddc/libsddc.h create mode 100644 sddc_source/src/libsddc/libsddc/wavehdr.h create mode 100644 sddc_source/src/libsddc/libsddc/wavewrite.c create mode 100644 sddc_source/src/libsddc/libsddc/wavewrite.h create mode 100644 sddc_source/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ced26f..dbe4719 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ option(OPT_BUILD_SDRPLAY_SOURCE "Build SDRplay Source Module (Depedencies: libsd option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Depedencies: libiio, libad9361)" ON) option(OPT_BUILD_HACKRF_SOURCE "Build HackRF Source Module (Depedencies: libhackrf)" ON) option(OPT_BUILD_RTL_SDR_SOURCE "Build RTL-SDR Source Module (Depedencies: librtlsdr)" ON) +option(OPT_BUILD_SDDC_SOURCE "Build SDDC Source Module (Depedencies: libusb-1.0)" OFF) option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Depedencies: rtaudio)" ON) option(OPT_BUILD_FALCON9_DECODER "Build the falcon9 live decoder (Dependencies: ffplay)" OFF) option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON) @@ -72,6 +73,10 @@ if (OPT_BUILD_RTL_SDR_SOURCE) add_subdirectory("rtl_sdr_source") endif (OPT_BUILD_RTL_SDR_SOURCE) +if (OPT_BUILD_SDDC_SOURCE) +add_subdirectory("sddc_source") +endif (OPT_BUILD_SDDC_SOURCE) + if (OPT_BUILD_AUDIO_SINK) add_subdirectory("audio_sink") endif (OPT_BUILD_AUDIO_SINK) diff --git a/rx888_source/CMakeLists.txt b/rx888_source/CMakeLists.txt deleted file mode 100644 index 9ed580a..0000000 --- a/rx888_source/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -cmake_minimum_required(VERSION 3.13) -project(rx888_source) - -if (MSVC) - set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") -elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") -else () - set(CMAKE_CXX_FLAGS "-O3 -std=c++17") -endif () - -include_directories("src/CyAPI") - -file(GLOB SRC "src/*.cpp") -file(GLOB CYAPI "src/CyAPI/*.cpp") - -add_library(rx888_source SHARED ${SRC} ${CYAPI}) -target_link_libraries(rx888_source PRIVATE sdrpp_core) -set_target_properties(rx888_source PROPERTIES PREFIX "") - -# Install directives -install(TARGETS rx888_source DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/rx888_source/src/CyAPI/Si5351.cpp b/rx888_source/src/CyAPI/Si5351.cpp deleted file mode 100644 index b85a274..0000000 --- a/rx888_source/src/CyAPI/Si5351.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "Si5351.h" -#include "openFX3.h" -#include - -#define DbgPrintf spdlog::info - -#define SI_CLK0_CONTROL 16 // Registers -#define SI_CLK1_CONTROL 17 -#define SI_CLK2_CONTROL 18 -#define SI_SYNTH_PLL_A 26 -#define SI_SYNTH_PLL_B 34 -#define SI_SYNTH_MS_0 42 -#define SI_SYNTH_MS_1 50 -#define SI_SYNTH_MS_2 58 -#define SI_PLL_RESET (0x177) - -#define SI_R_DIV_1 (0) // 0b00000000 / -#define SI_R_DIV_2 (0x10) // 0b00010000 -#define SI_R_DIV_4 (0x20) // 0b00100000 -#define SI_R_DIV_8 (0x30) // 0b00110000 -#define SI_R_DIV_16 (0x40) // 0b01000000 -#define SI_R_DIV_32 (0x50) // 0b01010000 -#define SI_R_DIV_64 (0x60) // 0b01100000 -#define SI_R_DIV_128 (0x70) // 0b01110000 - -#define SI_CLK_SRC_PLL_A 0b00000000 -#define SI_CLK_SRC_PLL_B 0b00100000 - -#define SI5351_FREQ 27000000 // Crystal frequency -#define SI5351_PLL_FIXED 80000000000ULL - -#define SI5351_ADDR (0x60 << 1 ) - -#define SI5351_CRYSTAL_LOAD 183 -#define SI5351_CRYSTAL_LOAD_MASK (3<<6) -#define SI5351_CRYSTAL_LOAD_0PF (0<<6) -#define SI5351_CRYSTAL_LOAD_6PF (1<<6) -#define SI5351_CRYSTAL_LOAD_8PF (2<<6) -#define SI5351_CRYSTAL_LOAD_10PF (3<<6) - -#define SI5351_PLL_INPUT_SOURCE 15 -#define SI5351_CLKIN_DIV_MASK (3<<6) -#define SI5351_CLKIN_DIV_1 (0<<6) -#define SI5351_CLKIN_DIV_2 (1<<6) -#define SI5351_CLKIN_DIV_4 (2<<6) -#define SI5351_CLKIN_DIV_8 (3<<6) -#define SI5351_PLLB_SOURCE (1<<3) -#define SI5351_PLLA_SOURCE (1<<2) - -void Si5351init() -{ - // Set crystal load capacitance - fx3SendI2cbyte(SI5351_ADDR, SI5351_CRYSTAL_LOAD, SI5351_CRYSTAL_LOAD_6PF | 0b00010010); - fx3SendI2cbyte(SI5351_ADDR, SI_CLK0_CONTROL, 0x80); // clocks off - fx3SendI2cbyte(SI5351_ADDR, SI_CLK1_CONTROL, 0x80); - fx3SendI2cbyte(SI5351_ADDR, SI_CLK2_CONTROL, 0x80); - // si5351aSetFrequency(ADC_FREQ, R820T_ZERO); - DbgPrintf("Si5351_init()\n"); -} - - -#define _PLLDEBUG_ -// -// Set up specified PLL with mult, num and denom -// mult is 15..90 -// num is 0..1,048,575 (0xFFFFF) -// denom is 0..1,048,575 (0xFFFFF) -// -static void setupPLL(UINT8 pll, UINT8 mult, UINT32 num, UINT32 denom) -{ - UINT32 P1; // PLL config register P1 - UINT32 P2; // PLL config register P2 - UINT32 P3; // PLL config register P3 - UINT8 data[8]; - - // the actual multiplier is mult + num / denom -#ifdef _PLLDEBUG_ - double fa; - double x = 27000000.0; - fa = x * ((double)mult + (double)num / (double)denom); - DbgPrintf((char *) "pll = %d , mult = %d , num = %d , denom = %d\n", pll, mult, num, denom); - DbgPrintf((char *) "pll target %e \n", fa); -#endif - - P1 = (UINT32)(128 * ((float)num / (float)denom)); - P1 = (UINT32)(128 * (UINT32)(mult)+P1 - 512); - P2 = (UINT32)(128 * ((float)num / (float)denom)); - P2 = (UINT32)(128 * num - denom * P2); - P3 = denom; - - data[0] = (P3 & 0x0000FF00) >> 8; - data[1] = (P3 & 0x000000FF); - data[2] = (P1 & 0x00030000) >> 16; - data[3] = (P1 & 0x0000FF00) >> 8; - data[4] = (P1 & 0x000000FF); - data[5] = ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16); - data[6] = (P2 & 0x0000FF00) >> 8; - data[7] = (P2 & 0x000000FF); - - fx3SendI2cbytes(SI5351_ADDR, pll, data, sizeof(data)); -} -// -// Set up MultiSynth with integer divider and R divider -// R divider is the bit value which is OR'ed onto the appropriate register, it is a #define in si5351a.h -// -static void setupMultisynth(UINT8 synth, UINT32 divider, UINT8 rDiv) -{ - UINT32 P1; // Synth config register P1 - UINT32 P2; // Synth config register P2 - UINT32 P3; // Synth config register P3 - UINT8 data[8]; - -#ifdef _PLLDEBUG_ - DbgPrintf("setupMultisynth synth = %d divider = %d rDiv= %d \n", synth, divider, rDiv); - //DbgPrintf("output expected f = %e\n", fa / divider); -#endif - - P1 = 128 * divider - 512; - P2 = 0; // P2 = 0, P3 = 1 forces an integer value for the divider - P3 = 1; - - data[0] = (P3 & 0x0000FF00) >> 8; - data[1] = (P3 & 0x000000FF); - data[2] = ((P1 & 0x00030000) >> 16) | rDiv; - data[3] = (P1 & 0x0000FF00) >> 8; - data[4] = (P1 & 0x000000FF); - data[5] = ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16); - data[6] = (P2 & 0x0000FF00) >> 8; - data[7] = (P2 & 0x000000FF); - - fx3SendI2cbytes(SI5351_ADDR, synth, data, sizeof(data)); -} -// -// Switches off Si5351a output -// Example: si5351aOutputOff(SI_CLK0_CONTROL); -// will switch off output CLK0 -// -void si5351aOutputOff(UINT8 clk) -{ - fx3SendI2cbyte(SI5351_ADDR, clk, 0x80); // Refer to SiLabs AN619 to see bit values - 0x80 turns off the output stage -} - -void si5351aSetFrequency(UINT32 freq, UINT32 freq2) -{ - UINT32 frequency; - UINT32 pllFreq; - UINT32 xtalFreq = SI5351_FREQ; - UINT32 l; - double f; - UINT8 mult; - UINT32 num; - UINT32 denom; - UINT32 divider; - UINT32 rdiv; - - double corr = 0.9999314; - //double corr = 1.0; - - DbgPrintf("BBRF103 si5351 SetFreq ADC sampling:%d R820T reference:%d\n", freq, freq2); - - rdiv = (UINT32)SI_R_DIV_1; - - frequency = (UINT32)((double)freq * corr); - - while (frequency < 1000000) - { - frequency = frequency * 2; - rdiv += SI_R_DIV_2; - } -#ifdef _PLLDEBUG_ - DbgPrintf((char *) "\nCLK0 frequency %d \n", frequency); -#endif - divider = 900000000UL / frequency;// Calculate the division ratio. 900,000,000 is the maximum internal - // PLL frequency: 900MHz - if (divider % 2) divider--; // Ensure an even integer division ratio - - pllFreq = divider * frequency; // Calculate the pllFrequency: the divider * desired output frequency -#ifdef _PLLDEBUG_ - DbgPrintf((char *) "pllA Freq %d \n", pllFreq); -#endif - mult = pllFreq / xtalFreq; // Determine the multiplier to get to the required pllFrequency - l = pllFreq % xtalFreq; // It has three parts: - f = (double)l; // mult is an integer that must be in the range 15..90 - f *= 1048575; // num and denom are the fractional parts, the numerator and denominator - f /= xtalFreq; // each is 20 bits (range 0..1048575) - num = (UINT32)f; // the actual multiplier is mult + num / denom - denom = 1048575; // For simplicity we set the denominator to the maximum 1048575 - // Set up PLL A with the calculated multiplication ratio - setupPLL(SI_SYNTH_PLL_A, mult, num, denom); - // Set up MultiSynth divider 0, with the calculated divider. - // The final R division stage can divide by a power of two, from 1..128. - // represented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file) - // If you want to output frequencies below 1MHz, you have to use the - // final R division stage - setupMultisynth(SI_SYNTH_MS_0, divider, rdiv); - // Reset the PLL. This causes a glitch in the output. For small changes to - // the parameters, you don't need to reset the PLL, and there is no glitch - fx3SendI2cbyte((UINT8)SI5351_ADDR, (UINT8)SI_PLL_RESET, (UINT8)0x20); //pllA - // Finally switch on the CLK0 output (0x4F) - // and set the MultiSynth0 input to be PLL A - fx3SendI2cbyte(SI5351_ADDR, SI_CLK0_CONTROL, 0x4F | SI_CLK_SRC_PLL_A); - - if (freq2 > 0) - { - // calculate clk2 - frequency = (UINT32)((double)freq2 * corr); - rdiv = SI_R_DIV_1; - xtalFreq = SI5351_FREQ; - while (frequency <= 1000000) - { - frequency = frequency * 2; - rdiv += SI_R_DIV_2; - } -#ifdef _PLLDEBUG_ - DbgPrintf((char *) "\nCLK2 frequency %d \n", frequency); -#endif - divider = 900000000UL / frequency;// Calculate the division ratio. 900,000,000 is the maximum internal - // PLL frequency: 900MHz - if (divider % 2) divider--; // Ensure an even integer division ratio - - pllFreq = divider * frequency; // Calculate the pllFrequency: the divider * desired output frequency -#ifdef _PLLDEBUG_ - DbgPrintf((char *) "pllB Freq %d \n", pllFreq); -#endif - mult = pllFreq / xtalFreq; // Determine the multiplier to get to the required pllFrequency - l = pllFreq % xtalFreq; // It has three parts: - f = (double)l; // mult is an integer that must be in the range 15..90 - f *= 1048575; // num and denom are the fractional parts, the numerator and denominator - f /= xtalFreq; // each is 20 bits (range 0..1048575) - num = (UINT32)f; // the actual multiplier is mult + num / denom - denom = 1048575; // For simplicity we set the denominator to the maximum 1048575 - - // Set up PLL B with the calculated multiplication ratio - setupPLL(SI_SYNTH_PLL_B, mult, num, denom); - // Set up MultiSynth divider 0, with the calculated divider. - // The final R division stage can divide by a power of two, from 1..128. - // represented by constants SI_R_DIV1 to SI_R_DIV128 (see si5351a.h header file) - // If you want to output frequencies below 1MHz, you have to use the - // final R division stage - - setupMultisynth(SI_SYNTH_MS_2, divider, rdiv); - // Reset the PLL. This causes a glitch in the output. For small changes to - // the parameters, you don't need to reset the PLL, and there is no glitch - fx3SendI2cbyte((UINT8)SI5351_ADDR, (UINT8)SI_PLL_RESET, (UINT8)0x80); //pllB - // Finally switch on the CLK2 output (0x4C) - // and set the MultiSynth0 input to be PLL A - fx3SendI2cbyte(SI5351_ADDR, SI_CLK2_CONTROL, 0x4C | SI_CLK_SRC_PLL_B); // select PLLB - // calculate clk2 - } - else - { - fx3SendI2cbyte(SI5351_ADDR, SI_CLK2_CONTROL, 0x80); - } - -} - diff --git a/rx888_source/src/CyAPI/Si5351.h b/rx888_source/src/CyAPI/Si5351.h deleted file mode 100644 index b94c183..0000000 --- a/rx888_source/src/CyAPI/Si5351.h +++ /dev/null @@ -1,13 +0,0 @@ -// MIT License Copyright (c) 2017 ik1xpv@gmail.com, http://www.steila.com/blog - -#ifndef SI5351_H -#define SI5351_H - -#include - -void Si5351init(); -void si5351aSetFrequency(UINT32 freq, UINT32 freq2); -void si5351aOutputOff(UINT8 clk); - - -#endif diff --git a/rx888_source/src/CyAPI/openFX3.cpp b/rx888_source/src/CyAPI/openFX3.cpp deleted file mode 100644 index 25e0f54..0000000 --- a/rx888_source/src/CyAPI/openFX3.cpp +++ /dev/null @@ -1,208 +0,0 @@ -// MIT License Copyright (c) 2016 Booya Corp. -// booyasdr@gmail.com, http://booyasdr.sf.net -// modified 2017 11 30 ik1xpv@gmail.com, http://www.steila.com/blog -#include - -#include -#include -#include -#include "openfx3.h" - -#include - -#define DbgPrintf spdlog::info - -using namespace std; - -void fx3Control(FX3Command command) { // firmware control - long lgt = 1; - UINT8 z = 0; // dummy data = 0 - fx3dev->ControlEndPt->ReqCode = command; - fx3dev->ControlEndPt->Write(&z, lgt); -} - -bool fx3Control(FX3Command command, PUINT8 data) { // firmware control BBRF - long lgt = 1; // default - bool r = false; - switch (command) - { - case GPIOFX3: - fx3dev->ControlEndPt->ReqCode = command; - fx3dev->ControlEndPt->Value = (USHORT)0; - fx3dev->ControlEndPt->Index = (USHORT)0; - lgt = 2; - DbgPrintf("GPIO %x %x\n", data[0], data[1]); - r = fx3dev->ControlEndPt->Write(data, lgt); - break; - case TESTFX3: - fx3dev->ControlEndPt->ReqCode = command; - fx3dev->ControlEndPt->Value = (USHORT)0; - fx3dev->ControlEndPt->Index = (USHORT)0; - lgt = 4; // TESTFX3 len - r = fx3dev->ControlEndPt->Read(data, lgt); - break; - default: - break; - } - if (r == false) - { - DbgPrintf("WARNING FX3FWControl failed %x .%x %x\n", r, command, *data); - closeFX3(); - } - return r; -} - - -bool fx3SendI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len) -{ - bool r = false; - LONG lgt = len; - fx3dev->ControlEndPt->ReqCode = I2CWFX3; - fx3dev->ControlEndPt->Value = (USHORT)i2caddr; - fx3dev->ControlEndPt->Index = (USHORT)regaddr; - Sleep(10); - r = fx3dev->ControlEndPt->Write(pdata, lgt); - if (r == false) - DbgPrintf("\nWARNING fx3FWSendI2cbytes i2caddr 0x%02x regaddr 0x%02x 1data 0x%02x len 0x%02x \n", - i2caddr, regaddr, *pdata, len); - return r; -} - -bool fx3SendI2cbyte(UINT8 i2caddr, UINT8 regaddr, UINT8 data) -{ - return fx3SendI2cbytes(i2caddr, regaddr, &data, 1); -} - -bool fx3ReadI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len) -{ - bool r = false; - LONG lgt = len; - WORD saveValue, saveIndex; - saveValue = fx3dev->ControlEndPt->Value; - saveIndex = fx3dev->ControlEndPt->Index; - - fx3dev->ControlEndPt->ReqCode = I2CRFX3; - fx3dev->ControlEndPt->Value = (USHORT)i2caddr; - fx3dev->ControlEndPt->Index = (USHORT)regaddr; - r = fx3dev->ControlEndPt->Read(pdata, lgt); - if (r == false) - printf("WARNING fx3FWReadI2cbytes failed %x : %02x %02x %02x %02x : %02x\n", r, I2CRFX3, i2caddr, regaddr, len, *pdata); - fx3dev->ControlEndPt->Value = saveValue; - fx3dev->ControlEndPt->Index = saveIndex; - return r; -} - -bool fx3ReadI2cbyte(UINT8 i2caddr, UINT8 regaddr, UINT8 data) -{ - return fx3ReadI2cbytes(i2caddr, regaddr, &data, 1); -} - -bool GetFx3Device(void); // open the device, called in initFX3() - -CCyFX3Device *fx3dev = NULL; -const int VENDOR_ID = 0x04B4; -const int STREAMER_ID = 0x00F1; -const int BOOTLOADER_ID = 0x00F3; -CCyUSBEndPoint *EndPt; -bool bHighSpeedDevice; -bool bSuperSpeedDevice; - -bool openFX3() { - bool r = false; - fx3dev = new CCyFX3Device; // instantiate the device - if (fx3dev == NULL) return 0; // return if failed - int n = fx3dev->DeviceCount(); // return if no devices connected - if (n == 0) { - DbgPrintf("Device Count = 0, Exit\n"); - return r; - } - if (!GetFx3Device()) { DbgPrintf("No streamer or boot device found, Exit\n"); return r; } - - char fwname[] = "rx888.img"; // firmware file - - if (!fx3dev->IsBootLoaderRunning()) { // if not bootloader device - fx3Control(RESETFX3); // reset the fx3 firmware via CyU3PDeviceReset(false) - DbgPrintf("Reset Device\n"); - Sleep(300); - fx3dev->Close(); // close class - delete fx3dev; // destroy class - Sleep(300); - fx3dev = new CCyFX3Device; // create class - GetFx3Device(); // open class - } - FX3_FWDWNLOAD_ERROR_CODE dlf = FAILED; - if (fx3dev->IsBootLoaderRunning()) { - dlf = fx3dev->DownloadFw(fwname, RAM); - Sleep(500); // wait for download to finish - } - if (dlf == 0) { - struct stat stbuf; - stat(fwname, &stbuf); - char *timestr; - timestr = ctime(&stbuf.st_mtime); - DbgPrintf("Loaded NEW FIRMWARE {0} {1}", fwname, timestr); - } - else if (dlf != 0) - { - DbgPrintf("OLD FIRMWARE\n"); - } - - GetFx3Device(); // open class with new firmware - if (!fx3dev->IsOpen()) { - DbgPrintf("Failed to open device\n"); - return r; - } - EndPt = fx3dev->BulkInEndPt; - if (!EndPt) { - DbgPrintf("No Bulk In end point\n"); - return r; // init failed - } - r = true; - return r; // init success - -} - -bool closeFX3() { - bool r = false; - fx3dev->Close(); // close class - delete fx3dev; // destroy class - Sleep(300); - return r; -} - -bool GetFx3Device() { // open class - - bool r = false; - if (fx3dev == NULL) return r; - int n = fx3dev->DeviceCount(); - // Walk through all devices looking for VENDOR_ID/STREAMER_ID - if (n == 0) { DbgPrintf("Device Count = 0, Exit\n"); return r; } - - fx3dev->Open(0); - // go down the list of devices to find our device - for (int i = 1; i <= n; i++) { - cout << hex << fx3dev->VendorID << " " - << hex << fx3dev->ProductID << " " << fx3dev->FriendlyName << '\n'; - if ((fx3dev->VendorID == VENDOR_ID) && (fx3dev->ProductID == STREAMER_ID)) - { - r = true; - break; - } - - if ((fx3dev->VendorID == VENDOR_ID) && (fx3dev->ProductID == BOOTLOADER_ID)) - { - r = true; - break; - } - - fx3dev->Open(i); - } - if (r == false) - fx3dev->Close(); - return r; -} - -bool fx3Check() -{ - return (fx3dev != NULL); -} diff --git a/rx888_source/src/CyAPI/openFX3.h b/rx888_source/src/CyAPI/openFX3.h deleted file mode 100644 index b906798..0000000 --- a/rx888_source/src/CyAPI/openFX3.h +++ /dev/null @@ -1,36 +0,0 @@ -// MIT License Copyright (c) 2016 Booya Corp. -// booyasdr@gmail.com, http://booyasdr.sf.net -// modified 2017 11 30 ik1xpv@gmail.com, http://www.steila.com/blog - -#ifndef FX3DEV_H -#define FX3DEV_H - -#include -#include "CyAPI.h" - -#define PUINT8 UINT8* - -bool openFX3(void); -extern CCyFX3Device *fx3dev; -extern CCyUSBEndPoint *EndPt; -bool closeFX3(void); - -enum FX3Command { - STARTFX3 = 0xaa, - STOPFX3 = 0xab, - TESTFX3 = 0xac, - RESETFX3 = 0xcc, - PAUSEFX3 = 0xdd, - GPIOFX3 = 0xbc, - I2CWFX3 = 0xba, - I2CRFX3 = 0xbe -}; -void fx3Control(FX3Command command); -bool fx3Control(FX3Command command, PUINT8 data); -bool fx3SendI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len); -bool fx3SendI2cbyte(UINT8 i2caddr, UINT8 regaddr, UINT8 pdata); -bool fx3ReadI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len); -bool fx3ReadI2cbyte(UINT8 i2caddr, UINT8 regaddr, UINT8 pdata); -bool fx3Check(); - -#endif diff --git a/rx888_source/src/main.cpp b/rx888_source/src/main.cpp deleted file mode 100644 index 16222dd..0000000 --- a/rx888_source/src/main.cpp +++ /dev/null @@ -1,272 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define CONCAT(a, b) ((std::string(a) + b).c_str()) - -#define ADC_RATE 64000000 -#define XFER_TIMEOUT 5000 - -#define SEL0 (8) // SEL0 GPIO26 -#define SEL1 (16) // SEL1 GPIO27 - -SDRPP_MOD_INFO { - /* Name: */ "rx888_source", - /* Description: */ "RX888 source module for SDR++", - /* Author: */ "Ryzerth", - /* Version: */ 0, 1, 0, - /* Max instances */ 1 -}; - -class RX888SourceModule : public ModuleManager::Instance { -public: - RX888SourceModule(std::string name) { - this->name = name; - - if (!openFX3()) { - spdlog::error("No RX888 found!"); - return; - } - - Si5351init(); - - decimation = 5; - sampleRate = (ADC_RATE >> (decimation - 1)); - freq = sampleRate / 2; - - - handler.ctx = this; - handler.selectHandler = menuSelected; - handler.deselectHandler = menuDeselected; - handler.menuHandler = menuHandler; - handler.startHandler = start; - handler.stopHandler = stop; - handler.tuneHandler = tune; - handler.stream = &stream; - sigpath::sourceManager.registerSource("RX888", &handler); - - spdlog::info("RX888SourceModule '{0}': Instance created!", name); - } - - ~RX888SourceModule() { - spdlog::info("RX888SourceModule '{0}': Instance deleted!", name); - } - - void enable() { - enabled = true; - } - - void disable() { - enabled = false; - } - - bool isEnabled() { - return enabled; - } - -private: - - static void menuSelected(void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - spdlog::info("RX888SourceModule '{0}': Menu Select!", _this->name); - - core::setInputSampleRate(_this->sampleRate); - } - - static void menuDeselected(void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - spdlog::info("RX888SourceModule '{0}': Menu Deselect!", _this->name); - } - - static void start(void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - - _this->doStart(); - - spdlog::info("RX888SourceModule '{0}': Start!", _this->name); - } - - void doStart() { - uint8_t Bgpio[2]; - Bgpio[0] = 0x17 | SEL0 & (~SEL1); - Bgpio[1] = 0x00; - - si5351aSetFrequency(ADC_RATE, 0); - fx3Control(GPIOFX3, Bgpio); - - running = true; - usbThread = std::thread(_usbWorker, this); - workerThread = std::thread(_worker, this); - } - - static void stop(void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - _this->running = false; - _this->realStream.stopWriter(); - _this->realStream.stopReader(); - _this->stream.stopWriter(); - _this->usbThread.join(); - _this->workerThread.join(); - _this->realStream.clearWriteStop(); - _this->realStream.clearReadStop(); - _this->stream.clearWriteStop(); - spdlog::info("RX888SourceModule '{0}': Stop!", _this->name); - } - - static void tune(double freq, void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - _this->freq = freq; - - spdlog::info("RX888SourceModule '{0}': Tune: {1}!", _this->name, freq); - } - - - static void menuHandler(void* ctx) { - RX888SourceModule* _this = (RX888SourceModule*)ctx; - if (ImGui::InputInt("Decimation", &_this->decimation)) { - _this->sampleRate = (ADC_RATE >> (_this->decimation - 1)); - core::setInputSampleRate(_this->sampleRate); - } - } - - static void _usbWorker(RX888SourceModule* _this) { - // Calculate hardware block siz - int realBlockSize = ADC_RATE / 100; - int i; - for (i = 1; i < realBlockSize; i = (i << 1)); - realBlockSize = (i >> 1); - int realDataSize = realBlockSize * sizeof(int16_t); - - int16_t* buffer = new int16_t[realBlockSize]; - - spdlog::info("Real block size: {0}", realBlockSize); - - // Initialize transfer - long pktSize = EndPt->MaxPktSize; - EndPt->SetXferSize(realDataSize); - long ppx = realDataSize / pktSize; - OVERLAPPED inOvLap; - inOvLap.hEvent = CreateEvent(NULL, false, false, NULL); - - auto context = EndPt->BeginDataXfer((PUCHAR)buffer, realDataSize, &inOvLap); - - // Check weather the transfer begin was successful - if (EndPt->NtStatus || EndPt->UsbdStatus) { - spdlog::error("Xfer request rejected. 1 STATUS = {0} {1}", EndPt->NtStatus, EndPt->UsbdStatus); - delete[] buffer; - return; - } - - // Start the USB chip - fx3Control(STARTFX3); - - // Data loop - while (_this->running) { - // Wait for the transfer to begin and about if timeout - LONG rLen = realDataSize; - if (!EndPt->WaitForXfer(&inOvLap, XFER_TIMEOUT)) { - spdlog::error("Transfer aborted"); - EndPt->Abort(); - if (EndPt->LastError == ERROR_IO_PENDING) { - WaitForSingleObject(inOvLap.hEvent, XFER_TIMEOUT); - } - break; - } - - // Check if the incomming data is bulk I/Q and end transfer - if (EndPt->Attributes == 2) { - if (EndPt->FinishDataXfer((PUCHAR)buffer, rLen, &inOvLap, context)) { - memcpy(_this->realStream.writeBuf, buffer, rLen); - _this->realStream.swap(rLen / 2); - } - } - - // Start next transfer - context = EndPt->BeginDataXfer((PUCHAR)buffer, realBlockSize * 2, &inOvLap); - } - - delete[] buffer; - - // Stop FX3 chip - fx3Control(STOPFX3); - } - - static void _worker(RX888SourceModule* _this) { - dsp::complex_t* iqbuffer = new dsp::complex_t[STREAM_BUFFER_SIZE]; - lv_32fc_t phase = lv_cmake(1.0f, 0.0f); - - // Read from real stream - int count = _this->realStream.read(); - int blockSize = count; - - while (count >= 0) { - for (int i = 0; i < count; i++) { - iqbuffer[i].q = (float)_this->realStream.readBuf[i] / 32768.0f; - } - _this->realStream.flush(); - - // Calculate the traslation frequency based on the tuning - float delta = -(_this->freq / (float)ADC_RATE) * 2.0f * FL_M_PI; - lv_32fc_t phaseDelta = lv_cmake(std::cos(delta), std::sin(delta)); - - // Apply translation - volk_32fc_s32fc_x2_rotator_32fc((lv_32fc_t*)_this->stream.writeBuf, (lv_32fc_t*)iqbuffer, phaseDelta, &phase, count); - - // Decimate - blockSize = count; - for (int d = 0; d < (_this->decimation - 1); d++) { - blockSize = (blockSize >> 1); - for (int i = 0; i < blockSize; i++) { - _this->stream.writeBuf[i].i = (_this->stream.writeBuf[i*2].i + _this->stream.writeBuf[(i*2)+1].i) * 0.5f; - _this->stream.writeBuf[i].q = (_this->stream.writeBuf[i*2].q + _this->stream.writeBuf[(i*2)+1].q) * 0.5f; - } - } - - // Write to output stream - _this->stream.swap(blockSize); - - // Read from real stream - count = _this->realStream.read(); - } - - // Stop FX3 chip - fx3Control(STOPFX3); - - // Deallocate buffers - delete[] iqbuffer; - } - - std::string name; - dsp::stream stream; - dsp::stream realStream; - SourceManager::SourceHandler handler; - std::thread usbThread; - std::thread workerThread; - double freq; - double sampleRate; - int decimation; - bool running = false; - bool enabled = true; -}; - -MOD_EXPORT void _INIT_() { - -} - -MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { - return new RX888SourceModule(name); -} - -MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { - delete (RX888SourceModule*)instance; -} -MOD_EXPORT void _END_() { - -} \ No newline at end of file diff --git a/sddc_source/CMakeLists.txt b/sddc_source/CMakeLists.txt new file mode 100644 index 0000000..559af30 --- /dev/null +++ b/sddc_source/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.13) +project(sddc_source) + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup") +else () + set(CMAKE_CXX_FLAGS "-O3 -std=c++17") +endif () + +include_directories("src/" "src/libsddc/" "src/libsddc/Core/" "src/libsddc/Core/pffft/" "src/libsddc/libsddc/") + + +if (MSVC) + # What the fuck? + file(GLOB SRC "src/*.cpp" "src/*.c" "src/libsddc/libsddc/*.c" "src/libsddc/libsddc/*.cpp" "src/libsddc/Core/*.c" "src/libsddc/Core/*.cpp" "src/libsddc/Core/radio/*.cpp" "src/libsddc/Core/pffft/*.c" "src/libsddc/Core/pffft/*.cpp" "src/libsddc/Core/arch/win32/*.c" "src/libsddc/Core/arch/win32/*.cpp" "src/libsddc/Core/arch/win32/CyAPI/*.cpp") +else (MSVC) + file(GLOB SRC "src/*.cpp" "src/*.c" "src/libsddc/libsddc/*.c" "src/libsddc/libsddc/*.cpp" "src/libsddc/Core/*.c" "src/libsddc/Core/*.cpp" "src/libsddc/Core/radio/*.cpp" "src/libsddc/Core/pffft/*.c" "src/libsddc/Core/pffft/*.cpp" "src/libsddc/Core/arch/linux/*.c" "src/libsddc/Core/arch/linux/*.cpp") +endif () + +add_library(sddc_source SHARED ${SRC}) +target_link_libraries(sddc_source PRIVATE sdrpp_core) +set_target_properties(sddc_source PROPERTIES PREFIX "") + +if (MSVC) + # Lib path + target_link_directories(sddc_source PUBLIC "C:/Program Files/PothosSDR/lib/") + + # Misc headers + target_include_directories(sddc_source PUBLIC "C:/Program Files/PothosSDR/include/libusb-1.0/") + + target_link_libraries(sddc_source PUBLIC libusb-1.0) + target_link_libraries(sddc_source PUBLIC Setupapi.lib) +else (MSVC) + find_package(PkgConfig) + + pkg_check_modules(LIBUSB REQUIRED libusb-1.0) + + target_include_directories(sddc_source PUBLIC ${LIBUSB_INCLUDE_DIRS}) + target_link_directories(sddc_source PUBLIC ${LIBUSB_LIBRARY_DIRS}) + target_link_libraries(sddc_source PUBLIC ${LIBUSB_LIBRARIES}) +endif () + +# Install directives +install(TARGETS sddc_source DESTINATION lib/sdrpp/plugins) \ No newline at end of file diff --git a/sddc_source/res/firmwares/SDDC_FX3.img b/sddc_source/res/firmwares/SDDC_FX3.img new file mode 100644 index 0000000000000000000000000000000000000000..8b3b08abd7d120c817defbd4286d510ae7c4b73e GIT binary patch literal 145388 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
fd<`{$=`e=lh=4`X@5@TM*Dnf?(3XQ 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 DB6YNi literal 0 HcmV?d00001 diff --git a/sddc_source/src/libsddc/CMakeLists.txt b/sddc_source/src/libsddc/CMakeLists.txt new file mode 100644 index 0000000..41ca755 --- /dev/null +++ b/sddc_source/src/libsddc/CMakeLists.txt @@ -0,0 +1,78 @@ +cmake_minimum_required(VERSION 3.13) +project(SDDC VERSION 1.0.1) + +include(CTest) + +### build options +# default build type: Release +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "Release") +endif(NOT CMAKE_BUILD_TYPE) +message(STATUS "Build type: " ${CMAKE_BUILD_TYPE} " - Version: " ${VERSION} " / " ${LIBVER}) + +# allow disabling optimizations - for debug reasons +option(USE_SIMD_OPTIMIZATIONS "enable SIMD optimizations" ON) + +# allow enabling address sanitizer - for debug reasons +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + option(USE_DEBUG_ASAN "use GCC's address sanitizer?" OFF) +endif() + +if (USE_DEBUG_ASAN) + set(ASANLIB "asan") +else() + set(ASANLIB "") +endif() + +if (MSVC) + set(CMAKE_CXX_FLAGS "-O2 /std:c++17 /EHsc /W3") + + get_filename_component(SDKPATH ${CMAKE_LINKER} DIRECTORY) + find_program(LIBEXE lib HINTS ${SDKPATH} REQUIRED) + + # External Project FFTW on Windows + if(${CMAKE_EXE_LINKER_FLAGS} MATCHES "X86") + SET(FFTW_URL "ftp://ftp.fftw.org/pub/fftw/fftw-3.3.5-dll32.zip") + SET(ARCH x86) + SET(HASH 29882a43033c9393479a4df52a2e9120589c06a2b724155b1a682747fa3e57d4) + else() + SET(FFTW_URL "ftp://ftp.fftw.org/pub/fftw/fftw-3.3.5-dll64.zip") + SET(ARCH x64) + SET(HASH cfd88dc0e8d7001115ea79e069a2c695d52c8947f5b4f3b7ac54a192756f439f) + endif() + + include(ExternalProject) + ExternalProject_Add( + LIBFFTW + URL ${FFTW_URL} + URL_HASH SHA256=${HASH} + BUILD_IN_SOURCE TRUE + CONFIGURE_COMMAND "" + BUILD_COMMAND ${LIBEXE} /def:./libfftw3f-3.def /MACHINE:${ARCH} /OUT:./fftw3f-3.lib + INSTALL_COMMAND "" + ) + ExternalProject_Get_Property(LIBFFTW SOURCE_DIR) + SET(LIBFFTW_INCLUDE_DIRS ${SOURCE_DIR}) + SET(LIBFFTW_LIBRARY_DIRS ${SOURCE_DIR}) + SET(LIBFFTW_LIBRARIES fftw3f-3) + + add_subdirectory(ExtIO_sddc) +else() + + if (USE_DEBUG_ASAN) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") + set(CMAKE_CXX_FLAGS "-O0 -std=c++17 -Wall -Werror") + else() + set(CMAKE_CXX_FLAGS "-O3 -std=c++17 -Wall -Werror") + endif(USE_DEBUG_ASAN) + #add_compile_options(-Wall -Wextra -pedantic) + include(FindPkgConfig) + pkg_check_modules(LIBUSB REQUIRED libusb-1.0) + pkg_check_modules(LIBFFTW REQUIRED fftw3f) +endif (MSVC) + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +add_subdirectory(Core) +add_subdirectory(libsddc) +add_subdirectory(unittest) diff --git a/sddc_source/src/libsddc/Core/CMakeLists.txt b/sddc_source/src/libsddc/Core/CMakeLists.txt new file mode 100644 index 0000000..7585a6d --- /dev/null +++ b/sddc_source/src/libsddc/Core/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.13) + +if (MSVC) + file(GLOB ARCH_SRC "arch/win32/*.cpp" "arch/win32/CyAPI/*.cpp") +else() + file(GLOB ARCH_SRC "arch/linux/*.c" "arch/linux/*.cpp") +endif (MSVC) + +file(GLOB SRC "*.cpp" "radio/*.cpp" "pffft/*.cpp" ${ARCH_SRC}) + +include_directories("." "CyAPI/" "pffft/") + +if (MSVC) + set_source_files_properties(fft_mt_r2iq_avx.cpp PROPERTIES COMPILE_FLAGS /arch:AVX) + set_source_files_properties(fft_mt_r2iq_avx2.cpp PROPERTIES COMPILE_FLAGS /arch:AVX2) + set_source_files_properties(fft_mt_r2iq_avx512.cpp PROPERTIES COMPILE_FLAGS /arch:AVX512) +else() + set_source_files_properties(fft_mt_r2iq_avx.cpp PROPERTIES COMPILE_FLAGS -mavx) + set_source_files_properties(fft_mt_r2iq_avx2.cpp PROPERTIES COMPILE_FLAGS -mavx2) + set_source_files_properties(fft_mt_r2iq_avx512.cpp PROPERTIES COMPILE_FLAGS -mavx512f) + + include_directories(${LIBUSB_INCLUDE_DIRS}) +endif (MSVC) + +include_directories(${LIBFFTW_INCLUDE_DIRS}) + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG") + +add_library(SDDC_CORE STATIC ${SRC}) +set_target_properties(SDDC_CORE PROPERTIES POSITION_INDEPENDENT_CODE True) +target_compile_definitions(SDDC_CORE PUBLIC _CRT_SECURE_NO_WARNINGS) + +if (NOT USE_SIMD_OPTIMIZATIONS) + target_compile_definitions(SDDC_CORE PRIVATE NO_SIMD_OPTIM) +endif() diff --git a/sddc_source/src/libsddc/Core/FX3Class.h b/sddc_source/src/libsddc/Core/FX3Class.h new file mode 100644 index 0000000..9cb9622 --- /dev/null +++ b/sddc_source/src/libsddc/Core/FX3Class.h @@ -0,0 +1,37 @@ +#ifndef FX3CLASS_H +#define FX3CLASS_H + +// +// FX3handler.cpp +// 2020 10 12 Oscar Steila ik1xpv +// loading arm code.img from resource by Howard Su and Hayati Ayguen +// This module was previous named:openFX3.cpp +// MIT License Copyright (c) 2016 Booya Corp. +// booyasdr@gmail.com, http://booyasdr.sf.net +// modified 2017 11 30 ik1xpv@gmail.com, http://www.steila.com/blog +// + +#include +#include +#include "../Interface.h" +#include "dsp/ringbuffer.h" + +class fx3class +{ +public: + virtual ~fx3class(void) {} + virtual bool Open(uint8_t* fw_data, uint32_t fw_size) = 0; + virtual bool Control(FX3Command command, uint8_t data = 0) = 0; + virtual bool Control(FX3Command command, uint32_t data) = 0; + virtual bool Control(FX3Command command, uint64_t data) = 0; + virtual bool SetArgument(uint16_t index, uint16_t value) = 0; + virtual bool GetHardwareInfo(uint32_t* data) = 0; + virtual bool ReadDebugTrace(uint8_t* pdata, uint8_t len) = 0; + virtual void StartStream(ringbuffer& input, int numofblock) = 0; + virtual void StopStream() = 0; + +}; + +extern "C" fx3class* CreateUsbHandler(); + +#endif // FX3CLASS_H diff --git a/sddc_source/src/libsddc/Core/PScope_uti.cpp b/sddc_source/src/libsddc/Core/PScope_uti.cpp new file mode 100644 index 0000000..bb323e3 --- /dev/null +++ b/sddc_source/src/libsddc/Core/PScope_uti.cpp @@ -0,0 +1,35 @@ +#include "PScope_uti.h" +#include "license.txt" + +int PScopeShot(const char * filename, const char * title2, const char * title1, short * data, float samplerate, unsigned int numsamples ) +{ + FILE *fp; + fp = fopen(filename, "w+"); + fputs("Version,115\n", fp); + fprintf(fp, "Retainers,0,1,%d,1024,0,%f,1,1\n",numsamples,samplerate ); + fputs("Placement,44,0,1,-1,-1,-1,-1,88,40,1116,879", fp); + fputs("WindMgr,7,2,0\n", fp); + fputs("Page,0,2\n", fp); + fputs("Col,3,1\n", fp); + fputs("Row,2,1\n", fp); + fputs("Row,3,146\n", fp); + fputs("Row,1,319\n", fp); + fputs("Col,2,1063\n", fp); + fputs("Row,4,1\n", fp); + fputs("Row,0,319\n", fp); + fputs("Page,1,2\n", fp); + fputs("Col,1,1\n", fp); + fputs("Row,1,1\n", fp); + fputs("Col,2,425\n", fp); + fputs("Row,4,1\n", fp); + fputs("Row,0,319\n", fp); + fprintf(fp,"DemoID,%s,%s,0\n", title1, title2 ); + fprintf(fp,"RawData,1,%d,16,-32768,32767,%f,-3.276800e+04,3.276800e+04\n", numsamples,samplerate); + for (unsigned int n = 0; n < numsamples; n++ ) + { + fprintf(fp, "%d\n", data[n]); + } + fputs("end\n", fp); + return fclose(fp); +} + diff --git a/sddc_source/src/libsddc/Core/PScope_uti.h b/sddc_source/src/libsddc/Core/PScope_uti.h new file mode 100644 index 0000000..ffe2944 --- /dev/null +++ b/sddc_source/src/libsddc/Core/PScope_uti.h @@ -0,0 +1,9 @@ +#ifndef _PSCOPE_ +#define _PSCOPE_ +#include "license.txt" +#include +#include + +int PScopeShot(const char * filename, const char * title1, const char * title2, short * data, float samplerate, unsigned int numsamples ); + +#endif // _PSCOPE_ diff --git a/sddc_source/src/libsddc/Core/RadioHandler.cpp b/sddc_source/src/libsddc/Core/RadioHandler.cpp new file mode 100644 index 0000000..0312830 --- /dev/null +++ b/sddc_source/src/libsddc/Core/RadioHandler.cpp @@ -0,0 +1,440 @@ +#include "license.txt" + +#include +#include +#include +#include "pf_mixer.h" +#include "RadioHandler.h" +#include "sddc_config.h" +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "PScope_uti.h" +#include "../Interface.h" + +#include + +using namespace std::chrono; + +// transfer variables + +unsigned long Failures = 0; + +void RadioHandlerClass::OnDataPacket() +{ + auto len = outputbuffer.getBlockSize() / 2 / sizeof(float); + + while(run) + { + auto buf = outputbuffer.getReadPtr(); + + if (!run) + break; + + if (fc != 0.0f) + { + std::unique_lock lk(fc_mutex); + shift_limited_unroll_C_sse_inp_c((complexf*)buf, len, stateFineTune); + } + +#ifdef _DEBUG //PScope buffer screenshot + if (saveADCsamplesflag == true) + { + saveADCsamplesflag = false; // do it once + unsigned int numsamples = transferSize / sizeof(int16_t); + float samplerate = (float) getSampleRate(); + PScopeShot("ADCrealsamples.adc", "ExtIO_sddc.dll", + "ADCrealsamples.adc input real ADC 16 bit samples", + (short*)buf, samplerate, numsamples); + } +#endif + + Callback(buf, len); + + outputbuffer.ReadDone(); + + SamplesXIF += len; + } +} + +RadioHandlerClass::RadioHandlerClass() : + DbgPrintFX3(nullptr), + GetConsoleIn(nullptr), + run(false), + pga(false), + dither(false), + randout(false), + biasT_HF(false), + biasT_VHF(false), + firmware(0), + modeRF(NOMODE), + adcrate(DEFAULT_ADC_FREQ), + fc(0.0f), + hardware(new DummyRadio(nullptr)) +{ + inputbuffer.setBlockSize(transferSamples); + + stateFineTune = new shift_limited_unroll_C_sse_data_t(); +} + +RadioHandlerClass::~RadioHandlerClass() +{ + delete stateFineTune; +} + +const char *RadioHandlerClass::getName() +{ + return hardware->getName(); +} + +bool RadioHandlerClass::Init(fx3class* Fx3, void (*callback)(const float*, uint32_t), r2iqControlClass *r2iqCntrl) +{ + uint8_t rdata[4]; + this->fx3 = Fx3; + this->Callback = callback; + + if (r2iqCntrl == nullptr) + r2iqCntrl = new fft_mt_r2iq(); + + Fx3->GetHardwareInfo((uint32_t*)rdata); + + radio = (RadioModel)rdata[0]; + firmware = (rdata[1] << 8) + rdata[2]; + + delete hardware; // delete dummy instance + switch (radio) + { + case HF103: + hardware = new HF103Radio(Fx3); + break; + + case BBRF103: + hardware = new BBRF103Radio(Fx3); + break; + + case RX888: + hardware = new RX888Radio(Fx3); + break; + + case RX888r2: + hardware = new RX888R2Radio(Fx3); + break; + + case RX888r3: + hardware = new RX888R3Radio(Fx3); + break; + + case RX999: + hardware = new RX999Radio(Fx3); + break; + + case RXLUCY: + hardware = new RXLucyRadio(Fx3); + break; + + default: + hardware = new DummyRadio(Fx3); + DbgPrintf("WARNING no SDR connected\n"); + break; + } + adcrate = adcnominalfreq; + hardware->Initialize(adcnominalfreq); + DbgPrintf("%s | firmware %x\n", hardware->getName(), firmware); + this->r2iqCntrl = r2iqCntrl; + r2iqCntrl->Init(hardware->getGain(), &inputbuffer, &outputbuffer); + + return true; +} + +bool RadioHandlerClass::Start(int srate_idx) +{ + Stop(); + DbgPrintf("RadioHandlerClass::Start\n"); + + int decimate = 4 - srate_idx; // 5 IF bands + if (adcnominalfreq > N2_BANDSWITCH) + decimate = 5 - srate_idx; // 6 IF bands + if (decimate < 0) + { + decimate = 0; + DbgPrintf("WARNING decimate mismatch at srate_idx = %d\n", srate_idx); + } + run = true; + count = 0; + + hardware->FX3producerOn(); // FX3 start the producer + + outputbuffer.setBlockSize(EXT_BLOCKLEN * 2 * sizeof(float)); + + // 0,1,2,3,4 => 32,16,8,4,2 MHz + r2iqCntrl->setDecimate(decimate); + r2iqCntrl->TurnOn(); + fx3->StartStream(inputbuffer, QUEUE_SIZE); + + submit_thread = std::thread( + [this]() { + this->OnDataPacket(); + }); + + show_stats_thread = std::thread([this](void*) { + this->CaculateStats(); + }, nullptr); + + return true; +} + +bool RadioHandlerClass::Stop() +{ + std::unique_lock lk(stop_mutex); + DbgPrintf("RadioHandlerClass::Stop %d\n", run); + if (run) + { + run = false; // now waits for threads + + r2iqCntrl->TurnOff(); + + fx3->StopStream(); + + run = false; // now waits for threads + + show_stats_thread.join(); //first to be joined + DbgPrintf("show_stats_thread join2\n"); + + submit_thread.join(); + DbgPrintf("submit_thread join1\n"); + + hardware->FX3producerOff(); //FX3 stop the producer + } + return true; +} + + +bool RadioHandlerClass::Close() +{ + delete hardware; + hardware = nullptr; + + return true; +} + +bool RadioHandlerClass::UpdateSampleRate(uint32_t samplefreq) +{ + hardware->Initialize(samplefreq); + + this->adcrate = samplefreq; + + return 0; +} + +// attenuator RF used in HF +int RadioHandlerClass::UpdateattRF(int att) +{ + if (hardware->UpdateattRF(att)) + { + return att; + } + return 0; +} + +// attenuator RF used in HF +int RadioHandlerClass::UpdateIFGain(int idx) +{ + if (hardware->UpdateGainIF(idx)) + { + return idx; + } + + return 0; +} + +int RadioHandlerClass::GetRFAttSteps(const float **steps) +{ + return hardware->getRFSteps(steps); +} + +int RadioHandlerClass::GetIFGainSteps(const float **steps) +{ + return hardware->getIFSteps(steps); +} + +bool RadioHandlerClass::UpdatemodeRF(rf_mode mode) +{ + if (modeRF != mode){ + modeRF = mode; + DbgPrintf("Switch to mode: %d\n", modeRF); + + hardware->UpdatemodeRF(mode); + + if (mode == VHFMODE) + r2iqCntrl->setSideband(true); + else + r2iqCntrl->setSideband(false); + } + return true; +} + +rf_mode RadioHandlerClass::PrepareLo(uint64_t lo) +{ + return hardware->PrepareLo(lo); +} + +uint64_t RadioHandlerClass::TuneLO(uint64_t wishedFreq) +{ + uint64_t actLo; + + actLo = hardware->TuneLo(wishedFreq); + + // we need shift the samples + int64_t offset = wishedFreq - actLo; + DbgPrintf("Offset freq %" PRIi64 "\n", offset); + float fc = r2iqCntrl->setFreqOffset(offset / (getSampleRate() / 2.0f)); + if (GetmodeRF() == VHFMODE) + fc = -fc; // sign change with sideband used + if (this->fc != fc) + { + std::unique_lock lk(fc_mutex); + *stateFineTune = shift_limited_unroll_C_sse_init(fc, 0.0F); + this->fc = fc; + } + + return wishedFreq; +} + +bool RadioHandlerClass::UptDither(bool b) +{ + dither = b; + if (dither) + hardware->FX3SetGPIO(DITH); + else + hardware->FX3UnsetGPIO(DITH); + return dither; +} + +bool RadioHandlerClass::UptPga(bool b) +{ + pga = b; + if (pga) + hardware->FX3SetGPIO(PGA_EN); + else + hardware->FX3UnsetGPIO(PGA_EN); + return pga; +} + +bool RadioHandlerClass::UptRand(bool b) +{ + randout = b; + if (randout) + hardware->FX3SetGPIO(RANDO); + else + hardware->FX3UnsetGPIO(RANDO); + r2iqCntrl->updateRand(randout); + return randout; +} + +void RadioHandlerClass::CaculateStats() +{ + high_resolution_clock::time_point EndingTime; + float kbRead = 0; + float kSReadIF = 0; + + kbRead = 0; // zeros the kilobytes counter + kSReadIF = 0; + + BytesXferred = 0; + SamplesXIF = 0; + + uint8_t debdata[MAXLEN_D_USB]; + memset(debdata, 0, MAXLEN_D_USB); + + auto StartingTime = high_resolution_clock::now(); + + while (run) { + kbRead = float(BytesXferred) / 1000.0f; + kSReadIF = float(SamplesXIF) / 1000.0f; + + EndingTime = high_resolution_clock::now(); + + duration> timeElapsed(EndingTime-StartingTime); + + mBps = (float)kbRead / timeElapsed.count() / 1000 / sizeof(int16_t); + mSpsIF = (float)kSReadIF / timeElapsed.count() / 1000; + + BytesXferred = 0; + SamplesXIF = 0; + + StartingTime = high_resolution_clock::now(); + +#ifdef _DEBUG + int nt = 10; + while (nt-- > 0) + { + std::this_thread::sleep_for(0.05s); + debdata[0] = 0; //clean buffer + if (GetConsoleIn != nullptr) + { + GetConsoleIn((char *)debdata, MAXLEN_D_USB); + if (debdata[0] !=0) + DbgPrintf("%s", (char*)debdata); + } + + if (hardware->ReadDebugTrace(debdata, MAXLEN_D_USB) == true) // there are message from FX3 ? + { + int len = strlen((char*)debdata); + if (len > MAXLEN_D_USB - 1) len = MAXLEN_D_USB - 1; + debdata[len] = 0; + if ((len > 0)&&(DbgPrintFX3 != nullptr)) + { + DbgPrintFX3("%s", (char*)debdata); + memset(debdata, 0, sizeof(debdata)); + } + } + + } +#else + std::this_thread::sleep_for(0.5s); +#endif + } + return; +} + +void RadioHandlerClass::UpdBiasT_HF(bool flag) +{ + biasT_HF = flag; + + if (biasT_HF) + hardware->FX3SetGPIO(BIAS_HF); + else + hardware->FX3UnsetGPIO(BIAS_HF); +} + +void RadioHandlerClass::UpdBiasT_VHF(bool flag) +{ + biasT_VHF = flag; + if (biasT_VHF) + hardware->FX3SetGPIO(BIAS_VHF); + else + hardware->FX3UnsetGPIO(BIAS_VHF); +} + +void RadioHandlerClass::uptLed(int led, bool on) +{ + int pin; + switch(led) + { + case 0: + pin = LED_YELLOW; + break; + case 1: + pin = LED_RED; + break; + case 2: + pin = LED_BLUE; + break; + default: + return; + } + + if (on) + hardware->FX3SetGPIO(pin); + else + hardware->FX3UnsetGPIO(pin); +} diff --git a/sddc_source/src/libsddc/Core/RadioHandler.h b/sddc_source/src/libsddc/Core/RadioHandler.h new file mode 100644 index 0000000..4ae6d4e --- /dev/null +++ b/sddc_source/src/libsddc/Core/RadioHandler.h @@ -0,0 +1,341 @@ +#ifndef RADIOHANDLER_H +#define RADIOHANDLER_H + +#include "license.txt" + +#include "sddc_config.h" +#include +#include +#include +#include +#include "FX3Class.h" + +#include "dsp/ringbuffer.h" + +class RadioHardware; +class r2iqControlClass; + +enum { + RESULT_OK, + RESULT_BIG_STEP, + RESULT_TOO_HIGH, + RESULT_TOO_LOW, + RESULT_NOT_POSSIBLE +}; + +struct shift_limited_unroll_C_sse_data_s; +typedef struct shift_limited_unroll_C_sse_data_s shift_limited_unroll_C_sse_data_t; + +class RadioHandlerClass { +public: + RadioHandlerClass(); + virtual ~RadioHandlerClass(); + bool Init(fx3class* Fx3, void (*callback)(const float*, uint32_t), r2iqControlClass *r2iqCntrl = nullptr); + bool Start(int srate_idx); + bool Stop(); + bool Close(); + bool IsReady(){return true;} + + int GetRFAttSteps(const float **steps); + int UpdateattRF(int attIdx); + + int GetIFGainSteps(const float **steps); + int UpdateIFGain(int attIdx); + + bool UpdatemodeRF(rf_mode mode); + rf_mode GetmodeRF(){return (rf_mode)modeRF;} + bool UptDither (bool b); + bool GetDither () {return dither;} + bool UptPga(bool b); + bool GetPga() { return pga;} + bool UptRand (bool b); + bool GetRand () {return randout;} + uint16_t GetFirmware() { return firmware; } + + uint32_t getSampleRate() { return adcrate; } + bool UpdateSampleRate(uint32_t samplerate); + + float getBps() const { return mBps; } + float getSpsIF() const {return mSpsIF; } + + const char* getName(); + RadioModel getModel() { return radio; } + + bool GetBiasT_HF() { return biasT_HF; } + void UpdBiasT_HF(bool flag); + bool GetBiasT_VHF() { return biasT_VHF; } + void UpdBiasT_VHF(bool flag); + + uint64_t TuneLO(uint64_t lo); + rf_mode PrepareLo(uint64_t lo); + + void uptLed(int led, bool on); + + void EnableDebug(void (*dbgprintFX3)(const char* fmt, ...), bool (*getconsolein)(char* buf, int maxlen)) + { + this->DbgPrintFX3 = dbgprintFX3; + this->GetConsoleIn = getconsolein; + }; + + bool ReadDebugTrace(uint8_t* pdata, uint8_t len) { return fx3->ReadDebugTrace(pdata, len); } + +private: + void AdcSamplesProcess(); + void AbortXferLoop(int qidx); + void CaculateStats(); + void OnDataPacket(); + r2iqControlClass* r2iqCntrl; + + void (*Callback)(const float *data, uint32_t length); + void (*DbgPrintFX3)(const char* fmt, ...); + bool (*GetConsoleIn)(char* buf, int maxlen); + + bool run; + unsigned long count; // absolute index + + bool pga; + bool dither; + bool randout; + bool biasT_HF; + bool biasT_VHF; + uint16_t firmware; + rf_mode modeRF; + RadioModel radio; + + // transfer variables + ringbuffer inputbuffer; + ringbuffer outputbuffer; + + // threads + std::thread show_stats_thread; + std::thread submit_thread; + + // stats + unsigned long BytesXferred; + unsigned long SamplesXIF; + float mBps; + float mSpsIF; + + fx3class *fx3; + uint32_t adcrate; + + std::mutex fc_mutex; + std::mutex stop_mutex; + float fc; + RadioHardware* hardware; + shift_limited_unroll_C_sse_data_t* stateFineTune; +}; + +extern unsigned long Failures; + +class RadioHardware { +public: + RadioHardware(fx3class* fx3) : Fx3(fx3), gpios(0) {} + + virtual ~RadioHardware(); + virtual const char* getName() = 0; + virtual rf_mode PrepareLo(uint64_t freq) = 0; + virtual float getGain() { return BBRF103_GAINFACTOR; } + virtual void Initialize(uint32_t samplefreq) = 0; + virtual bool UpdatemodeRF(rf_mode mode) = 0; + virtual bool UpdateattRF(int attIndex) = 0; + virtual uint64_t TuneLo(uint64_t freq) = 0; + + virtual int getRFSteps(const float** steps ) { return 0; } + virtual int getIFSteps(const float** steps ) { return 0; } + virtual bool UpdateGainIF(int attIndex) { return false; } + + bool FX3producerOn() { return Fx3->Control(STARTFX3); } + bool FX3producerOff() { return Fx3->Control(STOPFX3); } + + bool ReadDebugTrace(uint8_t* pdata, uint8_t len) { return Fx3->ReadDebugTrace(pdata, len); } + + bool FX3SetGPIO(uint32_t mask); + bool FX3UnsetGPIO(uint32_t mask); + +protected: + fx3class* Fx3; + uint32_t gpios; +}; + +class BBRF103Radio : public RadioHardware { +public: + BBRF103Radio(fx3class* fx3); + const char* getName() override { return "BBRF103"; } + float getGain() override { return BBRF103_GAINFACTOR; } + rf_mode PrepareLo(uint64_t freq) override; + void Initialize(uint32_t samplefreq) override; + bool UpdatemodeRF(rf_mode mode) override; + uint64_t TuneLo(uint64_t freq) override; + bool UpdateattRF(int attIndex) override; + bool UpdateGainIF(int attIndex) override; + + int getRFSteps(const float** steps ) override; + int getIFSteps(const float** steps ) override; + +private: + static const int step_size = 29; + static const float steps[step_size]; + static const float hfsteps[3]; + + static const int if_step_size = 16; + static const float if_steps[if_step_size]; + + uint32_t SampleRate; +}; + +class RX888Radio : public BBRF103Radio { +public: + RX888Radio(fx3class* fx3) : BBRF103Radio(fx3) {} + const char* getName() override { return "RX888"; } + float getGain() override { return RX888_GAINFACTOR; } +}; + +class RX888R2Radio : public RadioHardware { +public: + RX888R2Radio(fx3class* fx3); + const char* getName() override { return "RX888 mkII"; } + float getGain() override { return RX888mk2_GAINFACTOR; } + rf_mode PrepareLo(uint64_t freq) override; + void Initialize(uint32_t samplefreq) override; + bool UpdatemodeRF(rf_mode mode) override; + uint64_t TuneLo(uint64_t freq) override; + bool UpdateattRF(int attIndex) override; + bool UpdateGainIF(int attIndex) override; + + int getRFSteps(const float** steps ) override; + int getIFSteps(const float** steps ) override; + +private: + static const int hf_rf_step_size = 64; + static const int hf_if_step_size = 127; + static const int vhf_if_step_size = 16; + static const int vhf_rf_step_size = 29; + + float hf_rf_steps[hf_rf_step_size]; + float hf_if_steps[hf_if_step_size]; + static const float vhf_rf_steps[vhf_rf_step_size]; + static const float vhf_if_steps[vhf_if_step_size]; + + uint32_t SampleRate; +}; + +class RX888R3Radio : public RadioHardware { +public: + RX888R3Radio(fx3class* fx3); + const char* getName() override { return "RX888 mkIII"; } + float getGain() override { return RX888mk2_GAINFACTOR; } + rf_mode PrepareLo(uint64_t freq) override; + void Initialize(uint32_t samplefreq) override; + bool UpdatemodeRF(rf_mode mode) override; + uint64_t TuneLo(uint64_t freq) override; + bool UpdateattRF(int attIndex) override; + bool UpdateGainIF(int attIndex) override; + + int getRFSteps(const float** steps ) override; + int getIFSteps(const float** steps ) override; + +private: + static const int hf_rf_step_size = 64; + static const int hf_if_step_size = 127; + static const int vhf_if_step_size = 16; + static const int vhf_rf_step_size = 29; + + float hf_rf_steps[hf_rf_step_size]; + float hf_if_steps[hf_if_step_size]; + static const float vhf_rf_steps[vhf_rf_step_size]; + static const float vhf_if_steps[vhf_if_step_size]; + + uint32_t SampleRate; +}; + +class RX999Radio : public RadioHardware { +public: + RX999Radio(fx3class* fx3); + const char* getName() override { return "RX999"; } + float getGain() override { return RX888_GAINFACTOR; } + + rf_mode PrepareLo(uint64_t freq) override; + void Initialize(uint32_t samplefreq) override; + bool UpdatemodeRF(rf_mode mode) override; + uint64_t TuneLo(uint64_t freq) override; + bool UpdateattRF(int attIndex) override; + bool UpdateGainIF(int attIndex) override; + + int getRFSteps(const float** steps ) override; + int getIFSteps(const float** steps ) override; + +private: + static const int if_step_size = 127; + + float if_steps[if_step_size]; + uint32_t SampleRate; +}; + +class HF103Radio : public RadioHardware { +public: + HF103Radio(fx3class* fx3); + const char* getName() override { return "HF103"; } + float getGain() override { return HF103_GAINFACTOR; } + + rf_mode PrepareLo(uint64_t freq) override; + + void Initialize(uint32_t samplefreq) override {}; + + bool UpdatemodeRF(rf_mode mode) override; + + uint64_t TuneLo(uint64_t freq) override { return 0; } + + bool UpdateattRF(int attIndex) override; + + int getRFSteps(const float** steps ) override; + +private: + static const int step_size = 64; + float steps[step_size]; +}; + +class RXLucyRadio : public RadioHardware { +public: + RXLucyRadio(fx3class* fx3); + const char* getName() override { return "Lucy"; } + float getGain() override { return HF103_GAINFACTOR; } + + rf_mode PrepareLo(uint64_t freq) override; + void Initialize(uint32_t samplefreq) override; + bool UpdatemodeRF(rf_mode mode) override; + uint64_t TuneLo(uint64_t freq) override ; + bool UpdateattRF(int attIndex) override; + bool UpdateGainIF(int attIndex) override; + + int getRFSteps(const float** steps) override; + int getIFSteps(const float** steps) override; + +private: + static const int step_size = 16; + float steps[step_size]; + + static const int if_step_size = 64; + float if_steps[if_step_size]; + uint32_t SampleRate; +}; + +class DummyRadio : public RadioHardware { +public: + DummyRadio(fx3class* fx3) : RadioHardware(fx3) {} + const char* getName() override { return "Dummy"; } + + rf_mode PrepareLo(uint64_t freq) override + { return HFMODE;} + void Initialize(uint32_t samplefreq) override {} + bool UpdatemodeRF(rf_mode mode) override { return true; } + bool UpdateattRF(int attIndex) override { return true; } + uint64_t TuneLo(uint64_t freq) override { + if (freq < 64000000 /2) + return 0; + else + return freq; + } +}; + +#endif diff --git a/sddc_source/src/libsddc/Core/arch/linux/FX3handler.cpp b/sddc_source/src/libsddc/Core/arch/linux/FX3handler.cpp new file mode 100644 index 0000000..8d4a94b --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/FX3handler.cpp @@ -0,0 +1,95 @@ +#include + +#include "FX3handler.h" +#include "usb_device.h" + +fx3class* CreateUsbHandler() +{ + return new fx3handler(); +} + +fx3handler::fx3handler() +{ +} + +fx3handler::~fx3handler() +{ +} + +bool fx3handler::Open(uint8_t* fw_data, uint32_t fw_size) +{ + dev = usb_device_open(0, (const char*)fw_data, fw_size); + + return dev != nullptr; +} + +bool fx3handler::Control(FX3Command command, uint8_t data) +{ + return usb_device_control(this->dev, command, 0, 0, (uint8_t *) &data, sizeof(data), 0) == 0; +} + +bool fx3handler::Control(FX3Command command, uint32_t data) +{ + return usb_device_control(this->dev, command, 0, 0, (uint8_t *) &data, sizeof(data), 0) == 0; +} + +bool fx3handler::Control(FX3Command command, uint64_t data) +{ + return usb_device_control(this->dev, command, 0, 0, (uint8_t *) &data, sizeof(data), 0) == 0; +} + +bool fx3handler::SetArgument(uint16_t index, uint16_t value) +{ + uint8_t data = 0; + return usb_device_control(this->dev, SETARGFX3, value, index, (uint8_t *) &data, sizeof(data), 0) == 0; +} + +bool fx3handler::GetHardwareInfo(uint32_t* data) +{ + return usb_device_control(this->dev, TESTFX3, 0, 0, (uint8_t *) data, sizeof(*data), 1) == 0; +} + +void fx3handler::StartStream(ringbuffer& input, int numofblock) +{ + inputbuffer = &input; + auto readsize = input.getWriteCount() * sizeof(uint16_t); + stream = streaming_open_async(this->dev, readsize, numofblock, PacketRead, this); + + // Start background thread to poll the events + run = true; + poll_thread = std::thread( + [this]() { + while(run) + { + usb_device_handle_events(this->dev); + } + }); + + if (stream) + { + streaming_start(stream); + } +} + +void fx3handler::StopStream() +{ + run = false; + poll_thread.join(); + + streaming_stop(stream); + streaming_close(stream); +} + +void fx3handler::PacketRead(uint32_t data_size, uint8_t *data, void *context) +{ + fx3handler *handler = (fx3handler*)context; + + auto *ptr = handler->inputbuffer->getWritePtr(); + memcpy(ptr, data, data_size); + handler->inputbuffer->WriteDone(); +} + +bool fx3handler::ReadDebugTrace(uint8_t* pdata, uint8_t len) +{ + return true; +} diff --git a/sddc_source/src/libsddc/Core/arch/linux/FX3handler.h b/sddc_source/src/libsddc/Core/arch/linux/FX3handler.h new file mode 100644 index 0000000..c43cbf9 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/FX3handler.h @@ -0,0 +1,44 @@ +#ifndef FX3HANDLER_H +#define FX3HANDLER_H + +#include "sddc_config.h" + +#define VENDOR_ID (0x04B4) +#define STREAMER_ID (0x00F1) +#define BOOTLOADER_ID (0x00F3) + +#include "FX3Class.h" +#include "usb_device.h" +#include "streaming.h" +#include "dsp/ringbuffer.h" + +class fx3handler : public fx3class +{ +public: + fx3handler(); + virtual ~fx3handler(void); + bool Open(uint8_t* fw_data, uint32_t fw_size) override; + bool Control(FX3Command command, uint8_t data) override; + bool Control(FX3Command command, uint32_t data) override; + bool Control(FX3Command command, uint64_t data) override; + bool SetArgument(uint16_t index, uint16_t value) override; + bool GetHardwareInfo(uint32_t* data) override; + bool ReadDebugTrace(uint8_t* pdata, uint8_t len); + void StartStream(ringbuffer& input, int numofblock); + void StopStream(); + +private: + bool ReadUsb(uint8_t command, uint16_t value, uint16_t index, uint8_t *data, size_t size); + bool WriteUsb(uint8_t command, uint16_t value, uint16_t index, uint8_t *data, size_t size); + + static void PacketRead(uint32_t data_size, uint8_t *data, void *context); + + usb_device_t *dev; + streaming_t *stream; + ringbuffer *inputbuffer; + bool run; + std::thread poll_thread; +}; + + +#endif // FX3HANDLER_H diff --git a/sddc_source/src/libsddc/Core/arch/linux/ezusb.c b/sddc_source/src/libsddc/Core/arch/linux/ezusb.c new file mode 100644 index 0000000..bcb7dc0 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/ezusb.c @@ -0,0 +1,815 @@ +/* + * Copyright © 2001 Stephen Williams (steve@icarus.com) + * Copyright © 2001-2002 David Brownell (dbrownell@users.sourceforge.net) + * Copyright © 2008 Roger Williams (rawqux@users.sourceforge.net) + * Copyright © 2012 Pete Batard (pete@akeo.ie) + * Copyright © 2013 Federico Manzan (f.manzan@gmail.com) + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form 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 program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#include +#include +#include +#include +#include +#include + +#include "libusb.h" +#include "ezusb.h" + +//extern void logerror(const char *format, ...) +// __attribute__ ((format(printf, 1, 2))); +// + +void logerror(const char *format, ...) + __attribute__ ((format (__printf__, 1, 2))); + +void logerror(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + + +/* + * This file contains functions for uploading firmware into Cypress + * EZ-USB microcontrollers. These chips use control endpoint 0 and vendor + * specific commands to support writing into the on-chip SRAM. They also + * support writing into the CPUCS register, which is how we reset the + * processor after loading firmware (including the reset vector). + * + * These Cypress devices are 8-bit 8051 based microcontrollers with + * special support for USB I/O. They come in several packages, and + * some can be set up with external memory when device costs allow. + * Note that the design was originally by AnchorChips, so you may find + * references to that vendor (which was later merged into Cypress). + * The Cypress FX parts are largely compatible with the Anchorhip ones. + */ + +int verbose = 1; + +/* + * return true if [addr,addr+len] includes external RAM + * for Anchorchips EZ-USB or Cypress EZ-USB FX + */ +static bool fx_is_external(uint32_t addr, size_t len) +{ + /* with 8KB RAM, 0x0000-0x1b3f can be written + * we can't tell if it's a 4KB device here + */ + if (addr <= 0x1b3f) + return ((addr + len) > 0x1b40); + + /* there may be more RAM; unclear if we can write it. + * some bulk buffers may be unused, 0x1b3f-0x1f3f + * firmware can set ISODISAB for 2KB at 0x2000-0x27ff + */ + return true; +} + +/* + * return true if [addr,addr+len] includes external RAM + * for Cypress EZ-USB FX2 + */ +static bool fx2_is_external(uint32_t addr, size_t len) +{ + /* 1st 8KB for data/code, 0x0000-0x1fff */ + if (addr <= 0x1fff) + return ((addr + len) > 0x2000); + + /* and 512 for data, 0xe000-0xe1ff */ + else if (addr >= 0xe000 && addr <= 0xe1ff) + return ((addr + len) > 0xe200); + + /* otherwise, it's certainly external */ + else + return true; +} + +/* + * return true if [addr,addr+len] includes external RAM + * for Cypress EZ-USB FX2LP + */ +static bool fx2lp_is_external(uint32_t addr, size_t len) +{ + /* 1st 16KB for data/code, 0x0000-0x3fff */ + if (addr <= 0x3fff) + return ((addr + len) > 0x4000); + + /* and 512 for data, 0xe000-0xe1ff */ + else if (addr >= 0xe000 && addr <= 0xe1ff) + return ((addr + len) > 0xe200); + + /* otherwise, it's certainly external */ + else + return true; +} + + +/*****************************************************************************/ + +/* + * These are the requests (bRequest) that the bootstrap loader is expected + * to recognize. The codes are reserved by Cypress, and these values match + * what EZ-USB hardware, or "Vend_Ax" firmware (2nd stage loader) uses. + * Cypress' "a3load" is nice because it supports both FX and FX2, although + * it doesn't have the EEPROM support (subset of "Vend_Ax"). + */ +#define RW_INTERNAL 0xA0 /* hardware implements this one */ +#define RW_MEMORY 0xA3 + +/* + * Issues the specified vendor-specific write request. + */ +static int ezusb_write(libusb_device_handle *device, const char *label, + uint8_t opcode, uint32_t addr, const unsigned char *data, size_t len) +{ + int status; + + if (verbose > 1) + logerror("%s, addr 0x%08x len %4u (0x%04x)\n", label, addr, (unsigned)len, (unsigned)len); + status = libusb_control_transfer(device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + opcode, addr & 0xFFFF, addr >> 16, + (unsigned char*)data, (uint16_t)len, 1000); + if (status != (signed)len) { + if (status < 0) + logerror("%s: %s\n", label, libusb_error_name(status)); + else + logerror("%s ==> %d\n", label, status); + } + return (status < 0) ? -EIO : 0; +} + +/* + * Issues the specified vendor-specific read request. + */ +static int ezusb_read(libusb_device_handle *device, const char *label, + uint8_t opcode, uint32_t addr, const unsigned char *data, size_t len) +{ + int status; + + if (verbose > 1) + logerror("%s, addr 0x%08x len %4u (0x%04x)\n", label, addr, (unsigned)len, (unsigned)len); + status = libusb_control_transfer(device, + LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + opcode, addr & 0xFFFF, addr >> 16, + (unsigned char*)data, (uint16_t)len, 1000); + if (status != (signed)len) { + if (status < 0) + logerror("%s: %s\n", label, libusb_error_name(status)); + else + logerror("%s ==> %d\n", label, status); + } + return (status < 0) ? -EIO : 0; +} + +/* + * Modifies the CPUCS register to stop or reset the CPU. + * Returns false on error. + */ +static bool ezusb_cpucs(libusb_device_handle *device, uint32_t addr, bool doRun) +{ + int status; + uint8_t data = doRun ? 0x00 : 0x01; + + if (verbose) + logerror("%s\n", data ? "stop CPU" : "reset CPU"); + status = libusb_control_transfer(device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + RW_INTERNAL, addr & 0xFFFF, addr >> 16, + &data, 1, 1000); + if ((status != 1) && + /* We may get an I/O error from libusb as the device disappears */ + ((!doRun) || (status != LIBUSB_ERROR_IO))) + { + const char *mesg = "can't modify CPUCS"; + if (status < 0) + logerror("%s: %s\n", mesg, libusb_error_name(status)); + else + logerror("%s\n", mesg); + return false; + } else + return true; +} + +/* + * Send an FX3 jumpt to address command + * Returns false on error. + */ +static bool ezusb_fx3_jump(libusb_device_handle *device, uint32_t addr) +{ + int status; + + if (verbose) + logerror("transfer execution to Program Entry at 0x%08x\n", addr); + status = libusb_control_transfer(device, + LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, + RW_INTERNAL, addr & 0xFFFF, addr >> 16, + NULL, 0, 1000); + /* We may get an I/O error from libusb as the device disappears */ + if ((status != 0) && (status != LIBUSB_ERROR_IO)) + { + const char *mesg = "failed to send jump command"; + if (status < 0) + logerror("%s: %s\n", mesg, libusb_error_name(status)); + else + logerror("%s\n", mesg); + return false; + } else + return true; +} + +/*****************************************************************************/ + +/* + * Parse an Intel HEX image file and invoke the poke() function on the + * various segments to implement policies such as writing to RAM (with + * a one or two stage loader setup, depending on the firmware) or to + * EEPROM (two stages required). + * + * image - the hex image file + * context - for use by poke() + * is_external - if non-null, used to check which segments go into + * external memory (writable only by software loader) + * poke - called with each memory segment; errors indicated + * by returning negative values. + * + * Caller is responsible for halting CPU as needed, such as when + * overwriting a second stage loader. + */ +static int parse_ihex(FILE *image, void *context, + bool (*is_external)(uint32_t addr, size_t len), + int (*poke) (void *context, uint32_t addr, bool external, + const unsigned char *data, size_t len)) +{ + unsigned char data[1023]; + uint32_t data_addr = 0; + size_t data_len = 0; + int rc; + int first_line = 1; + bool external = false; + + /* Read the input file as an IHEX file, and report the memory segments + * as we go. Each line holds a max of 16 bytes, but uploading is + * faster (and EEPROM space smaller) if we merge those lines into larger + * chunks. Most hex files keep memory segments together, which makes + * such merging all but free. (But it may still be worth sorting the + * hex files to make up for undesirable behavior from tools.) + * + * Note that EEPROM segments max out at 1023 bytes; the upload protocol + * allows segments of up to 64 KBytes (more than a loader could handle). + */ + for (;;) { + char buf[512], *cp; + char tmp, type; + size_t len; + unsigned idx, off; + + cp = fgets(buf, sizeof(buf), image); + if (cp == NULL) { + logerror("EOF without EOF record!\n"); + break; + } + + /* EXTENSION: "# comment-till-end-of-line", for copyrights etc */ + if (buf[0] == '#') + continue; + + if (buf[0] != ':') { + logerror("not an ihex record: %s", buf); + return -2; + } + + /* ignore any newline */ + cp = strchr(buf, '\n'); + if (cp) + *cp = 0; + + if (verbose >= 3) + logerror("** LINE: %s\n", buf); + + /* Read the length field (up to 16 bytes) */ + tmp = buf[3]; + buf[3] = 0; + len = strtoul(buf+1, NULL, 16); + buf[3] = tmp; + + /* Read the target offset (address up to 64KB) */ + tmp = buf[7]; + buf[7] = 0; + off = (unsigned int)strtoul(buf+3, NULL, 16); + buf[7] = tmp; + + /* Initialize data_addr */ + if (first_line) { + data_addr = off; + first_line = 0; + } + + /* Read the record type */ + tmp = buf[9]; + buf[9] = 0; + type = (char)strtoul(buf+7, NULL, 16); + buf[9] = tmp; + + /* If this is an EOF record, then make it so. */ + if (type == 1) { + if (verbose >= 2) + logerror("EOF on hexfile\n"); + break; + } + + if (type != 0) { + logerror("unsupported record type: %u\n", type); + return -3; + } + + if ((len * 2) + 11 > strlen(buf)) { + logerror("record too short?\n"); + return -4; + } + + /* FIXME check for _physically_ contiguous not just virtually + * e.g. on FX2 0x1f00-0x2100 includes both on-chip and external + * memory so it's not really contiguous */ + + /* flush the saved data if it's not contiguous, + * or when we've buffered as much as we can. + */ + if (data_len != 0 + && (off != (data_addr + data_len) + /* || !merge */ + || (data_len + len) > sizeof(data))) { + if (is_external) + external = is_external(data_addr, data_len); + rc = poke(context, data_addr, external, data, data_len); + if (rc < 0) + return -1; + data_addr = off; + data_len = 0; + } + + /* append to saved data, flush later */ + for (idx = 0, cp = buf+9 ; idx < len ; idx += 1, cp += 2) { + tmp = cp[2]; + cp[2] = 0; + data[data_len + idx] = (uint8_t)strtoul(cp, NULL, 16); + cp[2] = tmp; + } + data_len += len; + } + + + /* flush any data remaining */ + if (data_len != 0) { + if (is_external) + external = is_external(data_addr, data_len); + rc = poke(context, data_addr, external, data, data_len); + if (rc < 0) + return -1; + } + return 0; +} + +/* + * Parse a binary image file and write it as is to the target. + * Applies to Cypress BIX images for RAM or Cypress IIC images + * for EEPROM. + * + * image - the BIX image file + * context - for use by poke() + * is_external - if non-null, used to check which segments go into + * external memory (writable only by software loader) + * poke - called with each memory segment; errors indicated + * by returning negative values. + * + * Caller is responsible for halting CPU as needed, such as when + * overwriting a second stage loader. + */ +static int parse_bin(FILE *image, void *context, + bool (*is_external)(uint32_t addr, size_t len), int (*poke)(void *context, + uint32_t addr, bool external, const unsigned char *data, size_t len)) +{ + unsigned char data[4096]; + uint32_t data_addr = 0; + size_t data_len = 0; + int rc; + bool external = false; + + for (;;) { + data_len = fread(data, 1, 4096, image); + if (data_len == 0) + break; + if (is_external) + external = is_external(data_addr, data_len); + rc = poke(context, data_addr, external, data, data_len); + if (rc < 0) + return -1; + data_addr += (uint32_t)data_len; + } + return feof(image)?0:-1; +} + +/* + * Parse a Cypress IIC image file and invoke the poke() function on the + * various segments for writing to RAM + * + * image - the IIC image file + * context - for use by poke() + * is_external - if non-null, used to check which segments go into + * external memory (writable only by software loader) + * poke - called with each memory segment; errors indicated + * by returning negative values. + * + * Caller is responsible for halting CPU as needed, such as when + * overwriting a second stage loader. + */ +static int parse_iic(FILE *image, void *context, + bool (*is_external)(uint32_t addr, size_t len), + int (*poke)(void *context, uint32_t addr, bool external, const unsigned char *data, size_t len)) +{ + unsigned char data[4096]; + uint32_t data_addr = 0; + size_t data_len = 0, read_len; + uint8_t block_header[4]; + int rc; + bool external = false; + long file_size, initial_pos; + + initial_pos = ftell(image); + if (initial_pos < 0) + return -1; + + if (fseek(image, 0L, SEEK_END) != 0) + return -1; + file_size = ftell(image); + if (fseek(image, initial_pos, SEEK_SET) != 0) + return -1; + for (;;) { + /* Ignore the trailing reset IIC data (5 bytes) */ + if (ftell(image) >= (file_size - 5)) + break; + if (fread(&block_header, 1, sizeof(block_header), image) != 4) { + logerror("unable to read IIC block header\n"); + return -1; + } + data_len = (block_header[0] << 8) + block_header[1]; + data_addr = (block_header[2] << 8) + block_header[3]; + if (data_len > sizeof(data)) { + /* If this is ever reported as an error, switch to using malloc/realloc */ + logerror("IIC data block too small - please report this error to libusb.info\n"); + return -1; + } + read_len = fread(data, 1, data_len, image); + if (read_len != data_len) { + logerror("read error\n"); + return -1; + } + if (is_external) + external = is_external(data_addr, data_len); + rc = poke(context, data_addr, external, data, data_len); + if (rc < 0) + return -1; + } + return 0; +} + +/* the parse call will be selected according to the image type */ +static int (*parse[IMG_TYPE_MAX])(FILE *image, void *context, bool (*is_external)(uint32_t addr, size_t len), + int (*poke)(void *context, uint32_t addr, bool external, const unsigned char *data, size_t len)) + = { parse_ihex, parse_iic, parse_bin }; + +/*****************************************************************************/ + +/* + * For writing to RAM using a first (hardware) or second (software) + * stage loader and 0xA0 or 0xA3 vendor requests + */ +typedef enum { + _undef = 0, + internal_only, /* hardware first-stage loader */ + skip_internal, /* first phase, second-stage loader */ + skip_external /* second phase, second-stage loader */ +} ram_mode; + +struct ram_poke_context { + libusb_device_handle *device; + ram_mode mode; + size_t total, count; +}; + +#define RETRY_LIMIT 5 + +static int ram_poke(void *context, uint32_t addr, bool external, + const unsigned char *data, size_t len) +{ + struct ram_poke_context *ctx = (struct ram_poke_context*)context; + int rc; + unsigned retry = 0; + + switch (ctx->mode) { + case internal_only: /* CPU should be stopped */ + if (external) { + logerror("can't write %u bytes external memory at 0x%08x\n", + (unsigned)len, addr); + return -EINVAL; + } + break; + case skip_internal: /* CPU must be running */ + if (!external) { + if (verbose >= 2) { + logerror("SKIP on-chip RAM, %u bytes at 0x%08x\n", + (unsigned)len, addr); + } + return 0; + } + break; + case skip_external: /* CPU should be stopped */ + if (external) { + if (verbose >= 2) { + logerror("SKIP external RAM, %u bytes at 0x%08x\n", + (unsigned)len, addr); + } + return 0; + } + break; + case _undef: + default: + logerror("bug\n"); + return -EDOM; + } + + ctx->total += len; + ctx->count++; + + /* Retry this till we get a real error. Control messages are not + * NAKed (just dropped) so time out means is a real problem. + */ + while ((rc = ezusb_write(ctx->device, + external ? "write external" : "write on-chip", + external ? RW_MEMORY : RW_INTERNAL, + addr, data, len)) < 0 + && retry < RETRY_LIMIT) { + if (rc != LIBUSB_ERROR_TIMEOUT) + break; + retry += 1; + } + return rc; +} + +/* + * Load a Cypress Image file into target RAM. + * See http://www.cypress.com/?docID=41351 (AN76405 PDF) for more info. + */ +int fx3_load_ram(libusb_device_handle *device, const char *image) +{ + uint32_t dCheckSum, dExpectedCheckSum, dAddress, i, dLen, dLength; + uint32_t* dImageBuf; + const unsigned char *bBuf, *hBuf; + unsigned char blBuf[4], rBuf[4096]; + int ret = 0; + int offset = 0; + + hBuf = image; + offset += 4; + // check "CY" signature byte and format + if ((hBuf[0] != 'C') || (hBuf[1] != 'Y')) { + logerror("image doesn't have a CYpress signature\n"); + ret = -3; + goto exit; + } + + // Check bImageType + switch(hBuf[3]) { + case 0xB0: + if (verbose) + logerror("normal FW binary %s image with checksum\n", (hBuf[2]&0x01)?"data":"executable"); + break; + case 0xB1: + logerror("security binary image is not currently supported\n"); + ret = -3; + goto exit; + case 0xB2: + logerror("VID:PID image is not currently supported\n"); + ret = -3; + goto exit; + default: + logerror("invalid image type 0x%02X\n", hBuf[3]); + ret = -3; + goto exit; + } + + // Read the bootloader version + if (verbose) { + if ((ezusb_read(device, "read bootloader version", RW_INTERNAL, 0xFFFF0020, blBuf, 4) < 0)) { + logerror("Could not read bootloader version\n"); + ret = -8; + goto exit; + } + logerror("FX3 bootloader version: 0x%02X%02X%02X%02X\n", blBuf[3], blBuf[2], blBuf[1], blBuf[0]); + } + + dCheckSum = 0; + if (verbose) + logerror("writing image...\n"); + while (1) { + dLength = *(uint32_t*)(image+offset); offset += sizeof(uint32_t); + dAddress = *(uint32_t*)(image+offset); offset += sizeof(uint32_t); + + if (dLength == 0) + break; // done + + // read sections + dImageBuf = (uint32_t*)(image+offset); offset += sizeof(uint32_t) * dLength; + + for (i = 0; i < dLength; i++) + dCheckSum += dImageBuf[i]; + dLength <<= 2; // convert to Byte length + bBuf = (unsigned char*) dImageBuf; + + while (dLength > 0) { + dLen = 4096; // 4K max + if (dLen > dLength) + dLen = dLength; + if ((ezusb_write(device, "write firmware", RW_INTERNAL, dAddress, bBuf, dLen) < 0) || + (ezusb_read(device, "read firmware", RW_INTERNAL, dAddress, rBuf, dLen) < 0)) { + logerror("R/W error\n"); + ret = -5; + goto exit; + } + // Verify data: rBuf with bBuf + for (i = 0; i < dLen; i++) { + if (rBuf[i] != bBuf[i]) { + logerror("verify error"); + ret = -6; + goto exit; + } + } + + dLength -= dLen; + bBuf += dLen; + dAddress += dLen; + } + } + + // read pre-computed checksum data + dExpectedCheckSum = *(uint32_t*)(image + offset); offset += sizeof(uint32_t); + if (dCheckSum != dExpectedCheckSum) { + logerror("checksum error\n"); + ret = -7; + goto exit; + } + + // transfer execution to Program Entry + if (!ezusb_fx3_jump(device, dAddress)) { + ret = -6; + } + +exit: + return ret; +} + +/* + * Load a firmware file into target RAM. device is the open libusb + * device, and the path is the name of the source file. Open the file, + * parse the bytes, and write them in one or two phases. + * + * If stage == 0, this uses the first stage loader, built into EZ-USB + * hardware but limited to writing on-chip memory or CPUCS. Everything + * is written during one stage, unless there's an error such as the image + * holding data that needs to be written to external memory. + * + * Otherwise, things are written in two stages. First the external + * memory is written, expecting a second stage loader to have already + * been loaded. Then file is re-parsed and on-chip memory is written. + */ +int ezusb_load_ram(libusb_device_handle *device, const char *path, int fx_type, int img_type, int stage) +{ + FILE *image; + uint32_t cpucs_addr; + bool (*is_external)(uint32_t off, size_t len); + struct ram_poke_context ctx; + int status; + uint8_t iic_header[8] = { 0 }; + int ret = 0; + + if (fx_type == FX_TYPE_FX3) + return fx3_load_ram(device, path); + + image = fopen(path, "rb"); + if (image == NULL) { + logerror("%s: unable to open for input.\n", path); + return -2; + } else if (verbose > 1) + logerror("open firmware image %s for RAM upload\n", path); + + if (img_type == IMG_TYPE_IIC) { + if ( (fread(iic_header, 1, sizeof(iic_header), image) != sizeof(iic_header)) + || (((fx_type == FX_TYPE_FX2LP) || (fx_type == FX_TYPE_FX2)) && (iic_header[0] != 0xC2)) + || ((fx_type == FX_TYPE_AN21) && (iic_header[0] != 0xB2)) + || ((fx_type == FX_TYPE_FX1) && (iic_header[0] != 0xB6)) ) { + logerror("IIC image does not contain executable code - cannot load to RAM.\n"); + ret = -1; + goto exit; + } + } + + /* EZ-USB original/FX and FX2 devices differ, apart from the 8051 core */ + switch(fx_type) { + case FX_TYPE_FX2LP: + cpucs_addr = 0xe600; + is_external = fx2lp_is_external; + break; + case FX_TYPE_FX2: + cpucs_addr = 0xe600; + is_external = fx2_is_external; + break; + default: + cpucs_addr = 0x7f92; + is_external = fx_is_external; + break; + } + + /* use only first stage loader? */ + if (stage == 0) { + ctx.mode = internal_only; + + /* if required, halt the CPU while we overwrite its code/data */ + if (cpucs_addr && !ezusb_cpucs(device, cpucs_addr, false)) + { + ret = -1; + goto exit; + } + + /* 2nd stage, first part? loader was already uploaded */ + } else { + ctx.mode = skip_internal; + + /* let CPU run; overwrite the 2nd stage loader later */ + if (verbose) + logerror("2nd stage: write external memory\n"); + } + + /* scan the image, first (maybe only) time */ + ctx.device = device; + ctx.total = ctx.count = 0; + status = parse[img_type](image, &ctx, is_external, ram_poke); + if (status < 0) { + logerror("unable to upload %s\n", path); + ret = status; + goto exit; + } + + /* second part of 2nd stage: rescan */ + // TODO: what should we do for non HEX images there? + if (stage) { + ctx.mode = skip_external; + + /* if needed, halt the CPU while we overwrite the 1st stage loader */ + if (cpucs_addr && !ezusb_cpucs(device, cpucs_addr, false)) + { + ret = -1; + goto exit; + } + + /* at least write the interrupt vectors (at 0x0000) for reset! */ + rewind(image); + if (verbose) + logerror("2nd stage: write on-chip memory\n"); + status = parse_ihex(image, &ctx, is_external, ram_poke); + if (status < 0) { + logerror("unable to completely upload %s\n", path); + ret = status; + goto exit; + } + } + + if (verbose && (ctx.count != 0)) { + logerror("... WROTE: %d bytes, %d segments, avg %d\n", + (int)ctx.total, (int)ctx.count, (int)(ctx.total/ctx.count)); + } + + /* if required, reset the CPU so it runs what we just uploaded */ + if (cpucs_addr && !ezusb_cpucs(device, cpucs_addr, true)) + ret = -1; + +exit: + fclose(image); + return ret; +} diff --git a/sddc_source/src/libsddc/Core/arch/linux/ezusb.h b/sddc_source/src/libsddc/Core/arch/linux/ezusb.h new file mode 100644 index 0000000..e18ed33 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/ezusb.h @@ -0,0 +1,114 @@ +#ifndef ezusb_H +#define ezusb_H +/* + * Copyright © 2001 Stephen Williams (steve@icarus.com) + * Copyright © 2002 David Brownell (dbrownell@users.sourceforge.net) + * Copyright © 2013 Federico Manzan (f.manzan@gmail.com) + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form 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 program 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#if defined(_MSC_VER) +#define __attribute__(x) +#if defined(_PREFAST_) +#pragma warning(disable:28193) +#endif +#endif + +#include + +#define FX_TYPE_UNDEFINED -1 +#define FX_TYPE_AN21 0 /* Original AnchorChips parts */ +#define FX_TYPE_FX1 1 /* Updated Cypress versions */ +#define FX_TYPE_FX2 2 /* USB 2.0 versions */ +#define FX_TYPE_FX2LP 3 /* Updated FX2 */ +#define FX_TYPE_FX3 4 /* USB 3.0 versions */ +#define FX_TYPE_MAX 5 +#define FX_TYPE_NAMES { "an21", "fx", "fx2", "fx2lp", "fx3" } + +#define IMG_TYPE_UNDEFINED -1 +#define IMG_TYPE_HEX 0 /* Intel HEX */ +#define IMG_TYPE_IIC 1 /* Cypress 8051 IIC */ +#define IMG_TYPE_BIX 2 /* Cypress 8051 BIX */ +#define IMG_TYPE_IMG 3 /* Cypress IMG format */ +#define IMG_TYPE_MAX 4 +#define IMG_TYPE_NAMES { "Intel HEX", "Cypress 8051 IIC", "Cypress 8051 BIX", "Cypress IMG format" } + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Automatically identified devices (VID, PID, type, designation). + * TODO: Could use some validation. Also where's the FX2? + */ +typedef struct { + uint16_t vid; + uint16_t pid; + int type; + const char* designation; +} fx_known_device; + +#define FX_KNOWN_DEVICES { \ + { 0x0547, 0x2122, FX_TYPE_AN21, "Cypress EZ-USB (2122S)" },\ + { 0x0547, 0x2125, FX_TYPE_AN21, "Cypress EZ-USB (2121S/2125S)" },\ + { 0x0547, 0x2126, FX_TYPE_AN21, "Cypress EZ-USB (2126S)" },\ + { 0x0547, 0x2131, FX_TYPE_AN21, "Cypress EZ-USB (2131Q/2131S/2135S)" },\ + { 0x0547, 0x2136, FX_TYPE_AN21, "Cypress EZ-USB (2136S)" },\ + { 0x0547, 0x2225, FX_TYPE_AN21, "Cypress EZ-USB (2225)" },\ + { 0x0547, 0x2226, FX_TYPE_AN21, "Cypress EZ-USB (2226)" },\ + { 0x0547, 0x2235, FX_TYPE_AN21, "Cypress EZ-USB (2235)" },\ + { 0x0547, 0x2236, FX_TYPE_AN21, "Cypress EZ-USB (2236)" },\ + { 0x04b4, 0x6473, FX_TYPE_FX1, "Cypress EZ-USB FX1" },\ + { 0x04b4, 0x8613, FX_TYPE_FX2LP, "Cypress EZ-USB FX2LP (68013A/68014A/68015A/68016A)" }, \ + { 0x04b4, 0x00f3, FX_TYPE_FX3, "Cypress FX3" },\ +} + +/* + * This function uploads the firmware from the given file into RAM. + * Stage == 0 means this is a single stage load (or the first of + * two stages). Otherwise it's the second of two stages; the + * caller having preloaded the second stage loader. + * + * The target processor is reset at the end of this upload. + */ +extern int ezusb_load_ram(libusb_device_handle *device, + const char *path, int fx_type, int img_type, int stage); + +extern int fx3_load_ram(libusb_device_handle *device, const char *image); + +/* + * This function uploads the firmware from the given file into EEPROM. + * This uses the right CPUCS address to terminate the EEPROM load with + * a reset command where FX parts behave differently than FX2 ones. + * The configuration byte is as provided here (zero for an21xx parts) + * and the EEPROM type is set so that the microcontroller will boot + * from it. + * + * The caller must have preloaded a second stage loader that knows + * how to respond to the EEPROM write request. + */ +extern int ezusb_load_eeprom(libusb_device_handle *device, + const char *path, int fx_type, int img_type, int config); + +/* Verbosity level (default 1). Can be increased or decreased with options v/q */ +extern int verbose; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sddc_source/src/libsddc/Core/arch/linux/logging.c b/sddc_source/src/libsddc/Core/arch/linux/logging.c new file mode 100644 index 0000000..2024044 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/logging.c @@ -0,0 +1,47 @@ +/* + * logging.c - logging functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#include "logging.h" + + +void log_error(const char *error_message, const char *function, + const char *file, int line) { + fprintf(stderr, "ERROR - %s in %s at %s:%d\n", error_message, function, + file, line); + return; +} + +void log_usb_error(int usb_error_code, const char *function, const char *file, + int line) { + fprintf(stderr, "ERROR - USB error %s in %s at %s:%d\n", + libusb_error_name(usb_error_code), function, file, line); + return; +} + +void log_usb_warning(int usb_error_code, const char *function, const char *file, + int line) { + fprintf(stderr, "WARNING - USB warning %s in %s at %s:%d\n", + libusb_error_name(usb_error_code), function, file, line); + return; +} diff --git a/sddc_source/src/libsddc/Core/arch/linux/logging.h b/sddc_source/src/libsddc/Core/arch/linux/logging.h new file mode 100644 index 0000000..6c1bfbd --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/logging.h @@ -0,0 +1,43 @@ +/* + * logging.h - logging functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef __LOGGING_H +#define __LOGGING_H + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +void log_error(const char *error_message, const char *function, + const char *file, int line); +void log_usb_error(int usb_error_code, const char *function, const char *file, + int line); +void log_usb_warning(int usb_error_code, const char *function, const char *file, + int line); + +#ifdef __cplusplus +} +#endif + +#endif /* __LOGGING_H */ diff --git a/sddc_source/src/libsddc/Core/arch/linux/streaming.c b/sddc_source/src/libsddc/Core/arch/linux/streaming.c new file mode 100644 index 0000000..8839b42 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/streaming.c @@ -0,0 +1,387 @@ +/* + * streaming.c - streaming functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* References: + * - librtlsdr.c: https://github.com/librtlsdr/librtlsdr/blob/development/src/librtlsdr.c + * - Ettus Research UHD libusb1_zero_copy.cpp: https://github.com/EttusResearch/uhd/blob/master/host/lib/transport/libusb1_zero_copy.cpp + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "streaming.h" +#include "usb_device.h" +#include "usb_device_internals.h" +#include "logging.h" + + +typedef struct streaming streaming_t; + +/* internal functions */ +static void streaming_read_async_callback(struct libusb_transfer *transfer); + + +enum StreamingStatus { + STREAMING_STATUS_OFF, + STREAMING_STATUS_READY, + STREAMING_STATUS_STREAMING, + STREAMING_STATUS_CANCELLED, + STREAMING_STATUS_FAILED = 0xff +}; + +typedef struct streaming { + enum StreamingStatus status; + int random; + usb_device_t *usb_device; + uint32_t sample_rate; + uint32_t frame_size; + uint32_t num_frames; + sddc_read_async_cb_t callback; + void *callback_context; + uint8_t **frames; + struct libusb_transfer **transfers; + atomic_int active_transfers; +} streaming_t; + + +static const uint32_t DEFAULT_SAMPLE_RATE = 64000000; /* 64Msps */ +static const uint32_t DEFAULT_FRAME_SIZE = (2 * 64000000 / 1000); /* ~ 1 ms */ +static const uint32_t DEFAULT_NUM_FRAMES = 96; /* we should not exceed 120 ms in total! */ +const unsigned int BULK_XFER_TIMEOUT = 5000; // timeout (in ms) for each bulk transfer + + +streaming_t *streaming_open_sync(usb_device_t *usb_device) +{ + streaming_t *ret_val = 0; + + /* we must have a bulk in device to transfer data from */ + if (usb_device->bulk_in_endpoint_address == 0) { + log_error("no USB bulk in endpoint found", __func__, __FILE__, __LINE__); + return ret_val; + } + + /* we are good here - create and initialize the streaming */ + streaming_t *this = (streaming_t *) malloc(sizeof(streaming_t)); + this->status = STREAMING_STATUS_READY; + this->random = 0; + this->usb_device = usb_device; + this->sample_rate = DEFAULT_SAMPLE_RATE; + this->frame_size = 0; + this->num_frames = 0; + this->callback = 0; + this->callback_context = 0; + this->frames = 0; + this->transfers = 0; + atomic_init(&this->active_transfers, 0); + + ret_val = this; + return ret_val; +} + + +streaming_t *streaming_open_async(usb_device_t *usb_device, uint32_t frame_size, + uint32_t num_frames, sddc_read_async_cb_t callback, + void *callback_context) +{ + streaming_t *ret_val = 0; + + /* we must have a bulk in device to transfer data from */ + if (usb_device->bulk_in_endpoint_address == 0) { + log_error("no USB bulk in endpoint found", __func__, __FILE__, __LINE__); + return ret_val; + } + + /* frame size must be a multiple of max_packet_size * max_burst */ + uint32_t max_xfer_size = usb_device->bulk_in_max_packet_size * + usb_device->bulk_in_max_burst; + if ( !max_xfer_size ) { + fprintf(stderr, "ERROR: maximum transfer size is 0. probably not connected at USB 3 port?!\n"); + return ret_val; + } + + num_frames = num_frames > 0 ? num_frames : DEFAULT_NUM_FRAMES; + frame_size = frame_size > 0 ? frame_size : DEFAULT_FRAME_SIZE; + frame_size = max_xfer_size * ((frame_size +max_xfer_size -1) / max_xfer_size); // round up + int iso_packets_per_frame = frame_size / usb_device->bulk_in_max_packet_size; + fprintf(stderr, "frame_size = %u, iso_packets_per_frame = %d\n", (unsigned)frame_size, iso_packets_per_frame); + + if (frame_size % max_xfer_size != 0) { + fprintf(stderr, "frame size must be a multiple of %d\n", max_xfer_size); + return ret_val; + } + + /* allocate frames for zerocopy USB bulk transfers */ + uint8_t **frames = (uint8_t **) malloc(num_frames * sizeof(uint8_t *)); + for (uint32_t i = 0; i < num_frames; ++i) { + frames[i] = libusb_dev_mem_alloc(usb_device->dev_handle, frame_size); + if (frames[i] == 0) { + log_error("libusb_dev_mem_alloc() failed", __func__, __FILE__, __LINE__); + for (uint32_t j = 0; j < i; j++) { + libusb_dev_mem_free(usb_device->dev_handle, frames[j], frame_size); + } + return ret_val; + } + } + + /* we are good here - create and initialize the streaming */ + streaming_t *this = (streaming_t *) malloc(sizeof(streaming_t)); + this->status = STREAMING_STATUS_READY; + this->random = 0; + this->usb_device = usb_device; + this->sample_rate = DEFAULT_SAMPLE_RATE; + this->frame_size = frame_size > 0 ? frame_size : DEFAULT_FRAME_SIZE; + this->num_frames = num_frames > 0 ? num_frames : DEFAULT_NUM_FRAMES; + this->callback = callback; + this->callback_context = callback_context; + this->frames = frames; + + /* populate the required libusb_transfer fields */ + struct libusb_transfer **transfers = (struct libusb_transfer **) malloc(num_frames * sizeof(struct libusb_transfer *)); + for (uint32_t i = 0; i < num_frames; ++i) { + transfers[i] = libusb_alloc_transfer(0); // iso_packets_per_frame ? + libusb_fill_bulk_transfer(transfers[i], usb_device->dev_handle, + usb_device->bulk_in_endpoint_address, + frames[i], frame_size, streaming_read_async_callback, + this, BULK_XFER_TIMEOUT); + } + this->transfers = transfers; + atomic_init(&this->active_transfers, 0); + + ret_val = this; + return ret_val; +} + + +void streaming_close(streaming_t *this) +{ + if (this->transfers) { + for (uint32_t i = 0; i < this->num_frames; ++i) { + libusb_free_transfer(this->transfers[i]); + } + free(this->transfers); + } + if (this->frames != 0) { + for (uint32_t i = 0; i < this->num_frames; ++i) { + libusb_dev_mem_free(this->usb_device->dev_handle, this->frames[i], + this->frame_size); + } + free(this->frames); + } + free(this); + return; +} + + +int streaming_set_sample_rate(streaming_t *this, uint32_t sample_rate) +{ + /* no checks yet */ + this->sample_rate = sample_rate; + return 0; +} + + +int streaming_set_random(streaming_t *this, int random) +{ + this->random = random; + return 0; +} + + +int streaming_start(streaming_t *this) +{ + if (this->status != STREAMING_STATUS_READY) { + fprintf(stderr, "ERROR - streaming_start() called with streaming status not READY: %d\n", this->status); + return -1; + } + + /* if there is no callback, then streaming is synchronous - nothing to do */ + if (this->callback == 0) { + this->status = STREAMING_STATUS_STREAMING; + return 0; + } + + /* submit all the transfers */ + atomic_init(&this->active_transfers, 0); + for (uint32_t i = 0; i < this->num_frames; ++i) { + int ret = libusb_submit_transfer(this->transfers[i]); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + this->status = STREAMING_STATUS_FAILED; + return -1; + } + atomic_fetch_add(&this->active_transfers, 1); + } + + this->status = STREAMING_STATUS_STREAMING; + + return 0; +} + + +int streaming_stop(streaming_t *this) +{ + /* if there is no callback, then streaming is synchronous - nothing to do */ + if (this->callback == 0) { + if (this->status == STREAMING_STATUS_STREAMING) { + this->status = STREAMING_STATUS_READY; + } + return 0; + } + + this->status = STREAMING_STATUS_CANCELLED; + /* cancel all the active transfers */ + for (uint32_t i = 0; i < this->num_frames; ++i) { + int ret = libusb_cancel_transfer(this->transfers[i]); + if (ret < 0) { + if (ret == LIBUSB_ERROR_NOT_FOUND) { + continue; + } + log_usb_error(ret, __func__, __FILE__, __LINE__); + this->status = STREAMING_STATUS_FAILED; + } + } + + /* flush all the events */ + struct timeval noblock = { 0, 0 }; + int ret = libusb_handle_events_timeout_completed(this->usb_device->context, &noblock, 0); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + this->status = STREAMING_STATUS_FAILED; + } + + return 0; +} + + +int streaming_reset_status(streaming_t *this) +{ + switch (this->status) { + case STREAMING_STATUS_READY: + /* nothing to do here */ + return 0; + case STREAMING_STATUS_CANCELLED: + case STREAMING_STATUS_FAILED: + if (this->active_transfers > 0) { + fprintf(stderr, "ERROR - streaming_reset_status() called with %d transfers still active\n", + this->active_transfers); + return -1; + } + break; + default: + fprintf(stderr, "ERROR - streaming_reset_status() called with invalid status: %d\n", + this->status); + return -1; + } + + /* we are good here; reset the status */ + this->status = STREAMING_STATUS_READY; + return 0; +} + + +int streaming_read_sync(streaming_t *this, uint8_t *data, int length, int *transferred) +{ + int ret = libusb_bulk_transfer(this->usb_device->dev_handle, + this->usb_device->bulk_in_endpoint_address, + data, length, transferred, BULK_XFER_TIMEOUT); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + + /* remove ADC randomization */ + if (this->random) { + uint16_t *samples = (uint16_t *) data; + int n = *transferred / 2; + for (int i = 0; i < n; ++i) { + if (samples[i] & 1) { + samples[i] ^= 0xfffe; + } + } + } + + return 0; +} + + +/* internal functions */ +static void LIBUSB_CALL streaming_read_async_callback(struct libusb_transfer *transfer) +{ + streaming_t *this = (streaming_t *) transfer->user_data; + int ret; + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + /* success!!! */ + if (this->status == STREAMING_STATUS_STREAMING) { + /* remove ADC randomization */ + if (this->random) { + uint16_t *samples = (uint16_t *) transfer->buffer; + int n = transfer->actual_length / 2; + for (int i = 0; i < n; ++i) { + if (samples[i] & 1) { + samples[i] ^= 0xfffe; + } + } + } + this->callback(transfer->actual_length, transfer->buffer, + this->callback_context); + ret = libusb_submit_transfer(transfer); + if (ret == 0) { + return; + } + log_usb_error(ret, __func__, __FILE__, __LINE__); + } + break; + case LIBUSB_TRANSFER_CANCELLED: + /* librtlsdr does also ignore LIBUSB_TRANSFER_CANCELLED */ + return; + case LIBUSB_TRANSFER_ERROR: + case LIBUSB_TRANSFER_TIMED_OUT: + case LIBUSB_TRANSFER_STALL: + case LIBUSB_TRANSFER_NO_DEVICE: + case LIBUSB_TRANSFER_OVERFLOW: + log_usb_error(transfer->status, __func__, __FILE__, __LINE__); + break; + } + + this->status = STREAMING_STATUS_FAILED; + atomic_fetch_sub(&this->active_transfers, 1); + fprintf(stderr, "Cancelling\n"); + /* cancel all the active transfers */ + for (uint32_t i = 0; i < this->num_frames; ++i) { + int ret = libusb_cancel_transfer(transfer); + if (ret < 0) { + if (ret == LIBUSB_ERROR_NOT_FOUND) { + continue; + } + log_usb_error(ret, __func__, __FILE__, __LINE__); + } + } + return; +} diff --git a/sddc_source/src/libsddc/Core/arch/linux/streaming.h b/sddc_source/src/libsddc/Core/arch/linux/streaming.h new file mode 100644 index 0000000..6283c91 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/streaming.h @@ -0,0 +1,63 @@ +/* + * streaming.h - streaming functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef __STREAMING_H +#define __STREAMING_H + +#include "usb_device.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct streaming streaming_t; + +typedef void (*sddc_read_async_cb_t)(uint32_t data_size, uint8_t *data, + void *context); + +streaming_t *streaming_open_sync(usb_device_t *usb_device); + +streaming_t *streaming_open_async(usb_device_t *usb_device, uint32_t frame_size, + uint32_t num_frames, + sddc_read_async_cb_t callback, + void *callback_context); + +void streaming_close(streaming_t *that); + +int streaming_set_sample_rate(streaming_t *that, uint32_t sample_rate); + +int streaming_set_random(streaming_t *that, int random); + +int streaming_start(streaming_t *that); + +int streaming_stop(streaming_t *that); + +int streaming_reset_status(streaming_t *that); + +int streaming_read_sync(streaming_t *that, uint8_t *data, int length, + int *transferred); + +#ifdef __cplusplus +} +#endif + +#endif /* __STREAMING_H */ diff --git a/sddc_source/src/libsddc/Core/arch/linux/usb_device.c b/sddc_source/src/libsddc/Core/arch/linux/usb_device.c new file mode 100644 index 0000000..36b454d --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/usb_device.c @@ -0,0 +1,574 @@ +/* + * usb_device.c - Basic USB and USB control functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +/* References: + * - FX3 SDK for Linux Platforms (https://www.cypress.com/documentation/software-and-drivers/ez-usb-fx3-software-development-kit) + * example: cyusb_linux_1.0.5/src/download_fx3.cpp + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usb_device.h" +#include "usb_device_internals.h" +#include "ezusb.h" +#include "logging.h" + + +typedef struct usb_device usb_device_t; + +/* internal functions */ +static libusb_device_handle *find_usb_device(int index, libusb_context *ctx, + libusb_device **device, int *needs_firmware); +static int load_image(libusb_device_handle *dev_handle, + const char *image, uint32_t size); +static int validate_image(const uint8_t *image, const size_t size); +static int transfer_image(const uint8_t *image, + libusb_device_handle *dev_handle); +static int list_endpoints(struct libusb_endpoint_descriptor endpoints[], + struct libusb_ss_endpoint_companion_descriptor ss_endpoints[], + libusb_device *device); + + +struct usb_device_id { + uint16_t vid; + uint16_t pid; + int needs_firmware; +}; + + +static struct usb_device_id usb_device_ids[] = { + { 0x04b4, 0x00f3, 1 }, /* Cypress / FX3 Boot-loader */ + { 0x04b4, 0x00f1, 0 } /* Cypress / FX3 Streamer Example */ +}; +static int n_usb_device_ids = sizeof(usb_device_ids) / sizeof(usb_device_ids[0]); + + +int usb_device_count_devices() +{ + int ret_val = -1; + + int ret = libusb_init(0); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL0; + } + libusb_device **list = 0; + ssize_t nusbdevices = libusb_get_device_list(0, &list); + if (nusbdevices < 0) { + log_usb_error(nusbdevices, __func__, __FILE__, __LINE__); + goto FAIL1; + } + int count = 0; + for (ssize_t i = 0; i < nusbdevices; ++i) { + libusb_device *dev = list[i]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(dev, &desc); + for (int i = 0; i < n_usb_device_ids; ++i) { + if (desc.idVendor == usb_device_ids[i].vid && + desc.idProduct == usb_device_ids[i].pid) { + count++; + } + } + } + libusb_free_device_list(list, 1); + + ret_val = count; + +FAIL1: + libusb_exit(0); +FAIL0: + return ret_val; +} + + +int usb_device_get_device_list(struct usb_device_info **usb_device_infos) +{ + const int MAX_STRING_BYTES = 256; + + int ret_val = -1; + + if (usb_device_infos == 0) { + log_error("argument usb_device_infos is a null pointer", __func__, __FILE__, __LINE__); + goto FAIL0; + } + + int ret = libusb_init(0); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL0; + } + libusb_device **list = 0; + ssize_t nusbdevices = libusb_get_device_list(0, &list); + if (nusbdevices < 0) { + log_usb_error(nusbdevices, __func__, __FILE__, __LINE__); + goto FAIL1; + } + + struct usb_device_info *device_infos = (struct usb_device_info *) malloc((nusbdevices + 1) * sizeof(struct usb_device_info)); + int count = 0; + for (ssize_t j = 0; j < nusbdevices; ++j) { + libusb_device *device = list[j]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(device, &desc); + for (int i = 0; i < n_usb_device_ids; ++i) { + if (!(desc.idVendor == usb_device_ids[i].vid && + desc.idProduct == usb_device_ids[i].pid)) { + continue; + } + + libusb_device_handle *dev_handle = 0; + ret = libusb_open(device, &dev_handle); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL2; + } + + device_infos[count].manufacturer = (unsigned char *) malloc(MAX_STRING_BYTES); + device_infos[count].manufacturer[0] = '\0'; + if (desc.iManufacturer) { + ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iManufacturer, + device_infos[count].manufacturer, MAX_STRING_BYTES); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL3; + } + device_infos[count].manufacturer = (unsigned char *) realloc(device_infos[count].manufacturer, ret); + } + + device_infos[count].product = (unsigned char *) malloc(MAX_STRING_BYTES); + device_infos[count].product[0] = '\0'; + if (desc.iProduct) { + ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iProduct, + device_infos[count].product, MAX_STRING_BYTES); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL3; + } + device_infos[count].product = (unsigned char *) realloc(device_infos[count].product, ret); + } + + device_infos[count].serial_number = (unsigned char *) malloc(MAX_STRING_BYTES); + device_infos[count].serial_number[0] = '\0'; + if (desc.iSerialNumber) { + ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, + device_infos[count].serial_number, MAX_STRING_BYTES); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL3; + } + device_infos[count].serial_number = (unsigned char *) realloc(device_infos[count].serial_number, ret); + } + + ret = 0; +FAIL3: + libusb_close(dev_handle); + if (ret < 0) { + goto FAIL2; + } + count++; + } + } + + device_infos[count].manufacturer = 0; + device_infos[count].product = 0; + device_infos[count].serial_number = 0; + + *usb_device_infos = device_infos; + ret_val = count; + +FAIL2: + libusb_free_device_list(list, 1); +FAIL1: + libusb_exit(0); +FAIL0: + return ret_val; +} + + +int usb_device_free_device_list(struct usb_device_info *usb_device_infos) +{ + for (struct usb_device_info *udi = usb_device_infos; + udi->manufacturer || udi->product || udi->serial_number; + ++udi) { + if (udi->manufacturer) { + free(udi->manufacturer); + } + if (udi->product) { + free(udi->product); + } + if (udi->serial_number) { + free(udi->serial_number); + } + } + free(usb_device_infos); + return 0; +} + + +usb_device_t *usb_device_open(int index, const char* image, + uint32_t size) +{ + usb_device_t *ret_val = 0; + libusb_context *ctx = 0; + + int ret = libusb_init(&ctx); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL0; + } + + libusb_device *device; + int needs_firmware = 0; + libusb_device_handle *dev_handle = find_usb_device(index, ctx, &device, &needs_firmware); + if (dev_handle == 0) { + goto FAIL1; + } + + if (needs_firmware) { + ret = load_image(dev_handle, image, size); + if (ret != 0) { + log_error("load_image() failed", __func__, __FILE__, __LINE__); + goto FAIL2; + } + + /* rescan USB to get a new device handle */ + libusb_close(dev_handle); + + /* wait unitl firmware is ready */ + usleep(500 * 1000L); + + needs_firmware = 0; + dev_handle = find_usb_device(index, ctx, &device, &needs_firmware); + if (dev_handle == 0) { + goto FAIL1; + } + if (needs_firmware) { + log_error("device is still in boot loader mode", __func__, __FILE__, __LINE__); + goto FAIL2; + } + } + + int speed = libusb_get_device_speed(device); + if ( speed == LIBUSB_SPEED_LOW || speed == LIBUSB_SPEED_FULL || speed == LIBUSB_SPEED_HIGH ) { + log_error("USB 3.x SuperSpeed connection failed", __func__, __FILE__, __LINE__); + goto FAIL2; + } + + /* list endpoints */ + struct libusb_endpoint_descriptor endpoints[MAX_ENDPOINTS]; + struct libusb_ss_endpoint_companion_descriptor ss_endpoints[MAX_ENDPOINTS]; + ret = list_endpoints(endpoints, ss_endpoints, device); + if (ret < 0) { + log_error("list_endpoints() failed", __func__, __FILE__, __LINE__); + goto FAIL2; + } + int nendpoints = ret; + uint8_t bulk_in_endpoint_address = 0; + uint16_t bulk_in_max_packet_size = 0; + uint8_t bulk_in_max_burst = 0; + for (int i = 0; i < nendpoints; ++i) { + if ((endpoints[i].bmAttributes & 0x03) == LIBUSB_TRANSFER_TYPE_BULK && + (endpoints[i].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN) { + bulk_in_endpoint_address = endpoints[i].bEndpointAddress; + bulk_in_max_packet_size = endpoints[i].wMaxPacketSize; + bulk_in_max_burst = ss_endpoints[i].bLength == 0 ? 0 : + ss_endpoints[i].bMaxBurst; + break; + } + } + if (bulk_in_endpoint_address == 0) { + fprintf(stderr, "ERROR - bulk in endpoint not found\n"); + goto FAIL2; + } + + /* we are good here - create and initialize the usb_device */ + usb_device_t *this = (usb_device_t *) malloc(sizeof(usb_device_t)); + this->dev = device; + this->dev_handle = dev_handle; + this->context = ctx; + this->completed = 0; + this->nendpoints = nendpoints; + memset(this->endpoints, 0, sizeof(this->endpoints)); + for (int i = 0; i < nendpoints; ++i) { + this->endpoints[i] = endpoints[i]; + this->ss_endpoints[i] = ss_endpoints[i]; + } + this->bulk_in_endpoint_address = bulk_in_endpoint_address; + this->bulk_in_max_packet_size = bulk_in_max_packet_size; + this->bulk_in_max_burst = bulk_in_max_burst; + + ret_val = this; + return ret_val; + +FAIL2: + libusb_close(dev_handle); +FAIL1: + libusb_exit(0); +FAIL0: + return ret_val; +} + + +void usb_device_close(usb_device_t *this) +{ + libusb_close(this->dev_handle); + free(this); + libusb_exit(0); + return; +} + + +int usb_device_handle_events(usb_device_t *this) +{ + return libusb_handle_events_completed(this->context, &this->completed); +} + +int usb_device_control(usb_device_t *this, uint8_t request, uint16_t value, + uint16_t index, uint8_t *data, uint16_t length, int read) { + + const uint8_t bmWriteRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + const uint8_t bmReadRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + const unsigned int timeout = 5000; // timeout (in ms) for each command + int ret; + + if (!read) { + ret = libusb_control_transfer(this->dev_handle, bmWriteRequestType, + request, value, index, data, length, + timeout); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + } + else + { + ret = libusb_control_transfer(this->dev_handle, bmReadRequestType, + request, value, index, data, length, + timeout); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + } + + return 0; +} + + + +/* internal functions */ +static libusb_device_handle *find_usb_device(int index, libusb_context *ctx, + libusb_device **device, int *needs_firmware) +{ + libusb_device_handle *ret_val = 0; + + *device = 0; + *needs_firmware = 0; + + libusb_device **list = 0; + ssize_t nusbdevices = libusb_get_device_list(ctx, &list); + if (nusbdevices < 0) { + log_usb_error(nusbdevices, __func__, __FILE__, __LINE__); + goto FAIL0; + } + + int count = 0; + for (ssize_t j = 0; j < nusbdevices; ++j) { + libusb_device *dev = list[j]; + struct libusb_device_descriptor desc; + libusb_get_device_descriptor(dev, &desc); + for (int i = 0; i < n_usb_device_ids; ++i) { + if (desc.idVendor == usb_device_ids[i].vid && + desc.idProduct == usb_device_ids[i].pid) { + if (count == index) { + *device = dev; + *needs_firmware = usb_device_ids[i].needs_firmware; + } + count++; + } + } + } + + if (*device == 0) { + fprintf(stderr, "ERROR - usb_device@%d not found\n", index); + goto FAIL1; + } + + libusb_device_handle *dev_handle = 0; + int ret = libusb_open(*device, &dev_handle); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAIL1; + } + libusb_free_device_list(list, 1); + + ret = libusb_kernel_driver_active(dev_handle, 0); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAILA; + } + if (ret == 1) { + fprintf(stderr, "ERROR - device busy\n"); + goto FAILA; + } + + ret = libusb_claim_interface(dev_handle, 0); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + goto FAILA; + } + + ret_val = dev_handle; + return ret_val; + +FAILA: + libusb_close(dev_handle); + return ret_val; + +FAIL1: + libusb_free_device_list(list, 1); +FAIL0: + return ret_val; +} + + +int load_image(libusb_device_handle *dev_handle, const char *image, uint32_t image_size) +{ + int ret_val = -1; + + const int fx_type = FX_TYPE_FX3; + const int img_type = IMG_TYPE_IMG; + const int stage = 0; + verbose = 1; + + ret_val = fx3_load_ram(dev_handle, image); + return ret_val; +} + + +static int transfer_image(const uint8_t *image, + libusb_device_handle *dev_handle) +{ + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + const uint8_t bRequest = 0xa0; // vendor command + const unsigned int timeout = 5000; // timeout (in ms) for each command + const size_t max_write_size = 2 * 1024; // max write size in bytes + + // skip first word with 'CY' magic + uint32_t *current = (uint32_t *) image + 1; + + while (1) { + uint32_t loadSz = *current++; + if (loadSz == 0) { + break; + } + uint32_t address = *current++; + + unsigned char *data = (unsigned char *) current; + for (size_t nleft = loadSz * 4; nleft > 0; ) { + uint16_t wLength = nleft > max_write_size ? max_write_size : nleft; + int ret = libusb_control_transfer(dev_handle, bmRequestType, bRequest, + address & 0xffff, address >> 16, + data, wLength, timeout); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + if (!(ret == wLength)) { + fprintf(stderr, "ERROR - libusb_control_transfer() returned less bytes than expected - actual=%hu expected=%hu\n", ret, wLength); + return -1; + } + data += wLength; + nleft -= wLength; + } + current += loadSz; + } + + uint32_t entryAddr = *current++; + uint32_t checksum __attribute__((unused)) = *current++; + + sleep(1); + + int ret = libusb_control_transfer(dev_handle, bmRequestType, bRequest, + entryAddr & 0xffff, entryAddr >> 16, + 0, 0, timeout); + if (ret < 0) { + log_usb_warning(ret, __func__, __FILE__, __LINE__); + } + + return 0; +} + + +static int list_endpoints(struct libusb_endpoint_descriptor endpoints[], + struct libusb_ss_endpoint_companion_descriptor ss_endpoints[], + libusb_device *device) +{ + struct libusb_config_descriptor *config; + int ret = libusb_get_active_config_descriptor(device, &config); + if (ret < 0) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + + int count = 0; + + /* loop through the interfaces */ + for (int intf = 0; intf < config->bNumInterfaces; ++intf) { + const struct libusb_interface *interface = &config->interface[intf]; + for (int setng = 0; setng < interface->num_altsetting; ++setng) { + const struct libusb_interface_descriptor *setting = &interface->altsetting[setng]; + for (int endp = 0; endp < setting->bNumEndpoints; ++endp) { + const struct libusb_endpoint_descriptor *endpoint = &setting->endpoint[endp]; + if (count == MAX_ENDPOINTS) { + fprintf(stderr, "WARNING - found too many USB endpoints; returning only the first %d\n", MAX_ENDPOINTS); + return count; + } + endpoints[count] = *endpoint; + struct libusb_ss_endpoint_companion_descriptor *endpoint_ss_companion; + ret = libusb_get_ss_endpoint_companion_descriptor(0, endpoint, + &endpoint_ss_companion); + if (ret < 0 && ret != LIBUSB_ERROR_NOT_FOUND) { + log_usb_error(ret, __func__, __FILE__, __LINE__); + return -1; + } + if (ret == 0) { + ss_endpoints[count] = *endpoint_ss_companion; + } else { + ss_endpoints[count].bLength = 0; + } + libusb_free_ss_endpoint_companion_descriptor(endpoint_ss_companion); + count++; + } + } + } + + return count; +} diff --git a/sddc_source/src/libsddc/Core/arch/linux/usb_device.h b/sddc_source/src/libsddc/Core/arch/linux/usb_device.h new file mode 100644 index 0000000..f9e4533 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/usb_device.h @@ -0,0 +1,60 @@ +/* + * usb_device.h - Basic USB and USB control functions + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef __USB_DEVICE_H +#define __USB_DEVICE_H + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct usb_device usb_device_t; + +struct usb_device_info { + unsigned char *manufacturer; + unsigned char *product; + unsigned char *serial_number; +}; + +int usb_device_count_devices(); + +int usb_device_get_device_list(struct usb_device_info **usb_device_infos); + +int usb_device_free_device_list(struct usb_device_info *usb_device_infos); + +usb_device_t *usb_device_open(int index, const char* image, + uint32_t size); + +int usb_device_handle_events(usb_device_t *t); + +void usb_device_close(usb_device_t *t); + +int usb_device_control(usb_device_t *t, uint8_t request, uint16_t value, + uint16_t index, uint8_t *data, uint16_t length, int read); + +#ifdef __cplusplus +} +#endif + +#endif /* __USB_DEVICE_H */ diff --git a/sddc_source/src/libsddc/Core/arch/linux/usb_device_internals.h b/sddc_source/src/libsddc/Core/arch/linux/usb_device_internals.h new file mode 100644 index 0000000..b5edd50 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/linux/usb_device_internals.h @@ -0,0 +1,52 @@ +/* + * usb_device_internals.h - internal USB structures to be shared between + * usb_device and usb_streaming + * + * Copyright (C) 2020 by Franco Venturi + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef __USB_DEVICE_INTERNALS_H +#define __USB_DEVICE_INTERNALS_H + +#include "usb_device.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct usb_device { + libusb_device *dev; + libusb_device_handle *dev_handle; + libusb_context *context; + int completed; + int nendpoints; +#define MAX_ENDPOINTS (16) + struct libusb_endpoint_descriptor endpoints[MAX_ENDPOINTS]; + struct libusb_ss_endpoint_companion_descriptor ss_endpoints[MAX_ENDPOINTS]; + uint8_t bulk_in_endpoint_address; + uint16_t bulk_in_max_packet_size; + uint8_t bulk_in_max_burst; +} usb_device_t; +typedef struct usb_device usb_device_t; + +#ifdef __cplusplus +} +#endif + +#endif /* __USB_DEVICE_INTERNALS_H */ diff --git a/rx888_source/src/CyAPI/CyAPI.cpp b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.cpp similarity index 99% rename from rx888_source/src/CyAPI/CyAPI.cpp rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.cpp index ac9a5ce..00d515e 100644 --- a/rx888_source/src/CyAPI/CyAPI.cpp +++ b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.cpp @@ -102,7 +102,6 @@ CCyUSBDevice::~CCyUSBDevice(void) if (hDevNotification) { if (! UnregisterDeviceNotification(hDevNotification)) throw "Failed to close the device notification handle."; - hDevNotification = 0; } @@ -2039,12 +2038,12 @@ bool CCyUSBEndPoint::FinishDataXfer(PUCHAR buf, LONG &bufLen, OVERLAPPED *ov, PU // If a buffer was provided, pass-back the Isoc packet info records if (pktInfos && (bufLen > 0)) { - ZeroMemory(pktInfos, pTransfer->IsoPacketLength); + //ZeroMemory(pktInfos, pTransfer->IsoPacketLength); PUCHAR pktPtr = pXmitBuf + pTransfer->IsoPacketOffset; memcpy(pktInfos, pktPtr, pTransfer->IsoPacketLength); } - delete[] pXmitBuf; // [] Changed in 1.5.1.3 +// delete[] pXmitBuf; // [] Changed in 1.5.1.3 return rResult && (UsbdStatus == 0) && (NtStatus == 0); } @@ -2057,7 +2056,7 @@ PUCHAR CCyUSBEndPoint::BeginBufferedXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *ov int iXmitBufSize = sizeof (SINGLE_TRANSFER) + bufLen; PUCHAR pXmitBuf = new UCHAR[iXmitBufSize]; - ZeroMemory (pXmitBuf, iXmitBufSize); + ZeroMemory (pXmitBuf, sizeof(SINGLE_TRANSFER)); PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER) pXmitBuf; pTransfer->ucEndpointAddress = Address; @@ -2066,7 +2065,7 @@ PUCHAR CCyUSBEndPoint::BeginBufferedXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *ov pTransfer->BufferLength = bufLen; // Copy buf into pXmitBuf - UCHAR *ptr = (PUCHAR) pTransfer + pTransfer->BufferOffset; + UCHAR *ptr = (PUCHAR)pXmitBuf + sizeof(SINGLE_TRANSFER); memcpy (ptr, buf, bufLen); DWORD dwReturnBytes; @@ -2095,7 +2094,7 @@ PUCHAR CCyUSBEndPoint::BeginDirectXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *ov) if ( hDevice == INVALID_HANDLE_VALUE ) return NULL; int iXmitBufSize = sizeof (SINGLE_TRANSFER); - PUCHAR pXmitBuf = new UCHAR[iXmitBufSize]; + PUCHAR pXmitBuf = (PUCHAR)ov + sizeof(OVERLAPPED); ZeroMemory (pXmitBuf, iXmitBufSize); PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER) pXmitBuf; @@ -2334,7 +2333,7 @@ PUCHAR CCyControlEndPoint::BeginDataXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *ov int iXmitBufSize = sizeof (SINGLE_TRANSFER) + bufLen; UCHAR *pXmitBuf = new UCHAR[iXmitBufSize]; - ZeroMemory (pXmitBuf, iXmitBufSize); + ZeroMemory (pXmitBuf, sizeof(SINGLE_TRANSFER)); // The Control Endpoint has a 1 sec resolution on its timeout // But, TimeOut is in milliseconds. @@ -2412,14 +2411,14 @@ PUCHAR CCyIsocEndPoint::BeginBufferedXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *o { if ( hDevice == INVALID_HANDLE_VALUE ) return NULL; - int pkts; - if(MaxPktSize) - pkts = bufLen / MaxPktSize; // Number of packets implied by bufLen & pktSize - else - { - pkts = 0; - return NULL; - } + int pkts; + if(MaxPktSize) + pkts = bufLen / MaxPktSize; // Number of packets implied by bufLen & pktSize + else + { + pkts = 0; + return NULL; + } if (bufLen % MaxPktSize) pkts++; @@ -2427,7 +2426,7 @@ PUCHAR CCyIsocEndPoint::BeginBufferedXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *o int iXmitBufSize = sizeof (SINGLE_TRANSFER) + (pkts * sizeof(ISO_PACKET_INFO)) + bufLen; UCHAR *pXmitBuf = new UCHAR[iXmitBufSize]; - ZeroMemory (pXmitBuf, iXmitBufSize); + ZeroMemory (pXmitBuf, sizeof(SINGLE_TRANSFER) + (pkts * sizeof(ISO_PACKET_INFO))); PSINGLE_TRANSFER pTransfer = (PSINGLE_TRANSFER) pXmitBuf; pTransfer->ucEndpointAddress = Address; @@ -2467,14 +2466,14 @@ PUCHAR CCyIsocEndPoint::BeginDirectXfer(PUCHAR buf, LONG bufLen, OVERLAPPED *ov) { if ( hDevice == INVALID_HANDLE_VALUE ) return NULL; - int pkts; - if(MaxPktSize) - pkts = bufLen / MaxPktSize; // Number of packets implied by bufLen & pktSize - else - { - pkts = 0; - return NULL; - } + int pkts; + if(MaxPktSize) + pkts = bufLen / MaxPktSize; // Number of packets implied by bufLen & pktSize + else + { + pkts = 0; + return NULL; + } if (bufLen % MaxPktSize) pkts++; @@ -2946,7 +2945,7 @@ FX3_FWDWNLOAD_ERROR_CODE CCyFX3Device::DownloadUserIMGtoI2CE2PROM(PUCHAR buffer_ //______________________________________________________________________________ -FX3_FWDWNLOAD_ERROR_CODE CCyFX3Device::DownloadFwToRam(PUCHAR buffer_p, UINT fw_size, UCHAR opCode) +FX3_FWDWNLOAD_ERROR_CODE CCyFX3Device::DownloadFwToRam(const UCHAR *buffer_p, UINT fw_size, UCHAR opCode) { UCHAR downloadBuffer[BUFSIZE_UPORT]; UCHAR uploadbuffer[BUFSIZE_UPORT]; @@ -3113,7 +3112,7 @@ FX3_FWDWNLOAD_ERROR_CODE CCyFX3Device::DownloadFw(char *fileName, FX3_FWDWNLOAD_ UINT fwSize = 0; PUCHAR FwImage; FILE *FwImagePtr; - int error; + //int error; // error = fopen_s(&FwImagePtr, fileName, "rb"); FwImagePtr = fopen( fileName, "rb"); diff --git a/rx888_source/src/CyAPI/CyAPI.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.h similarity index 99% rename from rx888_source/src/CyAPI/CyAPI.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.h index 35492ef..2e42075 100644 --- a/rx888_source/src/CyAPI/CyAPI.h +++ b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyAPI.h @@ -580,6 +580,7 @@ public: ~CCyFX3Device(void); bool IsBootLoaderRunning(); FX3_FWDWNLOAD_ERROR_CODE DownloadFw(char *fileName, FX3_FWDWNLOAD_MEDIA_TYPE enMediaType); + FX3_FWDWNLOAD_ERROR_CODE DownloadFwToRam(const UCHAR *buffer_p, UINT fw_size, UCHAR opCode = 0xA0); private: @@ -589,7 +590,6 @@ private: bool DownloadBufferToDevice(UINT start_addr, USHORT count, UCHAR *data_buf, UCHAR opCode); bool UploadBufferFromDevice(UINT start_addr, USHORT count, UCHAR *data_buf, UCHAR opCode); - FX3_FWDWNLOAD_ERROR_CODE DownloadFwToRam(PUCHAR buffer_p, UINT fw_size, UCHAR opCode); FX3_FWDWNLOAD_ERROR_CODE DownloadUserIMGtoI2CE2PROM(PUCHAR buffer_p, UINT fw_size, UCHAR opCode); FX3_FWDWNLOAD_ERROR_CODE DownloadUserIMGtoSPIFLASH(PUCHAR buffer_p, UINT fw_size, UCHAR opCode); diff --git a/rx888_source/src/CyAPI/CyUSB30_def.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyUSB30_def.h similarity index 100% rename from rx888_source/src/CyAPI/CyUSB30_def.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/CyUSB30_def.h diff --git a/rx888_source/src/CyAPI/UsbdStatus.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/UsbdStatus.h similarity index 100% rename from rx888_source/src/CyAPI/UsbdStatus.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/UsbdStatus.h diff --git a/rx888_source/src/CyAPI/VersionNo.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/VersionNo.h similarity index 100% rename from rx888_source/src/CyAPI/VersionNo.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/VersionNo.h diff --git a/rx888_source/src/CyAPI/cyioctl.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/cyioctl.h similarity index 100% rename from rx888_source/src/CyAPI/cyioctl.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/cyioctl.h diff --git a/rx888_source/src/CyAPI/devioctl.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/devioctl.h similarity index 100% rename from rx888_source/src/CyAPI/devioctl.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/devioctl.h diff --git a/rx888_source/src/CyAPI/usb100.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/usb100.h similarity index 100% rename from rx888_source/src/CyAPI/usb100.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/usb100.h diff --git a/rx888_source/src/CyAPI/usb200.h b/sddc_source/src/libsddc/Core/arch/win32/CyAPI/usb200.h similarity index 100% rename from rx888_source/src/CyAPI/usb200.h rename to sddc_source/src/libsddc/Core/arch/win32/CyAPI/usb200.h diff --git a/sddc_source/src/libsddc/Core/arch/win32/FX3handler.cpp b/sddc_source/src/libsddc/Core/arch/win32/FX3handler.cpp new file mode 100644 index 0000000..8e72d49 --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/win32/FX3handler.cpp @@ -0,0 +1,456 @@ +// +// FX3handler.cpp +// 2020 10 12 Oscar Steila ik1xpv +// loading arm code.img from resource by Howard Su and Hayati Ayguen +// This module was previous named: +// openFX3.cpp MIT License Copyright (c) 2016 Booya Corp. +// booyasdr@gmail.com, http://booyasdr.sf.net +// modified 2017 11 30 ik1xpv@gmail.com, http://www.steila.com/blog +// +#include +#include "FX3handler.h" +#include "./CyAPI/CyAPI.h" +#include "./CyAPI/cyioctl.h" +#define RES_BIN_FIRMWARE 2000 + + +fx3class* CreateUsbHandler() +{ + return new fx3handler(); +} + +fx3handler::fx3handler(): + fx3dev (nullptr), + Fx3IsOn (false) +{ + +} + + +fx3handler::~fx3handler() // reset USB device and exit +{ + DbgPrintf("\r\n~fx3handler\r\n"); + Close(); +} + +bool fx3handler::GetFx3Device() { + bool r = false; + if (fx3dev == nullptr) return r; // no device + int n = fx3dev->DeviceCount(); + if (n == 0) return r; // no one + + // Walk through all devices looking for VENDOR_ID/STREAMER_ID + for (int i = 0; i <= n; i++) { + fx3dev->Open(i); // go down the list of devices to find our device + if ((fx3dev->VendorID == VENDOR_ID) && (fx3dev->ProductID == STREAMER_ID)) + { + r = true; + break; + } + + if ((fx3dev->VendorID == VENDOR_ID) && (fx3dev->ProductID == BOOTLOADER_ID)) + { + r = true; + break; + } + } + if (r == false) + fx3dev->Close(); + return r; +} + +bool fx3handler::GetFx3DeviceStreamer(void) { // open class + bool r = false; + if (fx3dev == NULL) return r; + int n = fx3dev->DeviceCount(); + // Walk through all devices looking for VENDOR_ID/STREAMER_ID + if (n == 0) return r; + // go down the list of devices to find STREAMER_ID device + for (int i = 0; i <= n; i++) { + fx3dev->Open(i); + if ((fx3dev->VendorID == VENDOR_ID) && (fx3dev->ProductID == STREAMER_ID)) + { + r = true; + break; + } + } + if (r == false) + fx3dev->Close(); + return r; +} + +bool fx3handler::Open(uint8_t* fw_data, uint32_t fw_size) { + bool r = false; + fx3dev = new CCyFX3Device; // instantiate the device + if (fx3dev == nullptr) return r; // return if failed + int n = fx3dev->DeviceCount(); + if (n == 0) return r; // return if no devices connected + if (!GetFx3Device()) return r; // NO FX3 device connected + +#ifdef _DEBUG + if (!fx3dev->IsBootLoaderRunning()) { // if not bootloader device + Control(RESETFX3); // reset the fx3 firmware via CyU3PDeviceReset(false) + DbgPrintf("DEBUG - Reset Firmware\n"); + Sleep(300); + fx3dev->Close(); // close class + delete fx3dev; // destroy class + Sleep(300); + fx3dev = new CCyFX3Device; // create class + GetFx3Device(); // open class + } +#endif + + FX3_FWDWNLOAD_ERROR_CODE dlf = SUCCESS; + if (fx3dev->IsBootLoaderRunning()) + { + dlf = fx3dev->DownloadFwToRam(fw_data, fw_size); + Sleep(500); // wait for download to finish + } + + if (dlf != 0) + { + DbgPrintf("MISSING/OLD FIRMWARE\n"); + return false; + } + int x = 0; + int maxretry = 30; + CCyFX3Device* expdev = nullptr; + while (x++ < maxretry) // wait new firmware setup + { + bool r = false; + expdev = new CCyFX3Device; // instantiate the device + if (expdev != NULL) + int n = expdev->DeviceCount(); + if (n > 0) + { + expdev->Open(0); + // go down the list of devices to find our device + for (int i = 1; i <= n; i++) + { + if ((expdev->VendorID == VENDOR_ID) && (expdev->ProductID == STREAMER_ID)) + { + x = maxretry; //got it exit + } + } + } + expdev->Close(); // close class + delete expdev; // destroy class + } + GetFx3DeviceStreamer(); // open class with new ram firmware + if (!fx3dev->IsOpen()) { + DbgPrintf("Failed to open device\n"); + return r; + } + EndPt = fx3dev->BulkInEndPt; + if (!EndPt) { + DbgPrintf("No Bulk In end point\n"); + return r; // init failed + } + + long pktSize = EndPt->MaxPktSize; + EndPt->SetXferSize(transferSize); + long ppx = transferSize / pktSize; + DbgPrintf("buffer transferSize = %d. packet size = %ld. packets per transfer = %ld\n" + , transferSize, pktSize, ppx); + + uint8_t data[4]; + GetHardwareInfo((uint32_t*)&data); + + if (data[1] != FIRMWARE_VER_MAJOR || + data[2] != FIRMWARE_VER_MINOR) + { + DbgPrintf("Firmware version mismatch %d.%d != %d.%d (actual)\n", FIRMWARE_VER_MAJOR, FIRMWARE_VER_MINOR, data[1], data[2]); + Control(RESETFX3); + return false; + } + + Fx3IsOn = true; + return Fx3IsOn; // init success +} + + +using namespace std; + +bool fx3handler::Control(FX3Command command, UINT8 data) { // firmware control BBRF + long lgt = 1; + + fx3dev->ControlEndPt->ReqCode = command; + fx3dev->ControlEndPt->Value = (USHORT)0; + fx3dev->ControlEndPt->Index = (USHORT)0; + bool r = fx3dev->ControlEndPt->Write(&data, lgt); + DbgPrintf("FX3FWControl %x .%x %x\n", r, command, data); + if (r == false) + { + Close(); + } + return r; +} + +bool fx3handler::Control(FX3Command command, UINT32 data) { // firmware control BBRF + long lgt = 4; + + fx3dev->ControlEndPt->ReqCode = command; + fx3dev->ControlEndPt->Value = (USHORT)0; + fx3dev->ControlEndPt->Index = (USHORT)0; + bool r = fx3dev->ControlEndPt->Write((PUCHAR)&data, lgt); + DbgPrintf("FX3FWControl %x .%x %x\n", r, command, data); + if (r == false) + { + Close(); + } + return r; +} + +bool fx3handler::Control(FX3Command command, UINT64 data) { // firmware control BBRF + long lgt = 8; + + fx3dev->ControlEndPt->ReqCode = command; + fx3dev->ControlEndPt->Value = (USHORT)0; + fx3dev->ControlEndPt->Index = (USHORT)0; + bool r = fx3dev->ControlEndPt->Write((PUCHAR)&data, lgt); + DbgPrintf("FX3FWControl %x .%x %llx\n", r, command, data); + if (r == false) + { + Close(); + } + return r; +} + + +bool fx3handler::SetArgument(UINT16 index, UINT16 value) { // firmware control BBRF + long lgt = 1; + uint8_t data = 0; + + fx3dev->ControlEndPt->ReqCode = SETARGFX3; + fx3dev->ControlEndPt->Value = (USHORT)value; + fx3dev->ControlEndPt->Index = (USHORT)index; + bool r = fx3dev->ControlEndPt->Write((PUCHAR)&data, lgt); + DbgPrintf("SetArgument %x .%x (%x, %x)\n", r, SETARGFX3, index, value); + if (r == false) + { + Close(); + } + return r; +} + +bool fx3handler::GetHardwareInfo(UINT32* data) { // firmware control BBRF + long lgt = 4; + + fx3dev->ControlEndPt->ReqCode = TESTFX3; +#ifdef _DEBUG + fx3dev->ControlEndPt->Value = (USHORT) 1; +#else + fx3dev->ControlEndPt->Value = (USHORT) 0; +#endif + fx3dev->ControlEndPt->Index = (USHORT)0; + bool r = fx3dev->ControlEndPt->Read((PUCHAR)data, lgt); + DbgPrintf("GetHardwareInfo %x .%x %x\n", r, TESTFX3, *data); + if (r == false) + { + Close(); + } + return r; + +} + +bool fx3handler::ReadDebugTrace(uint8_t* pdata, uint8_t len) +{ + long lgt = len; + bool r; + fx3dev->ControlEndPt->ReqCode = READINFODEBUG; + fx3dev->ControlEndPt->Value = (USHORT) pdata[0]; // upstream char + r = fx3dev->ControlEndPt->Read((PUCHAR)pdata, lgt); + return r; +} + +bool fx3handler::SendI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len) +{ + bool r = false; + LONG lgt = len; + fx3dev->ControlEndPt->ReqCode = I2CWFX3; + fx3dev->ControlEndPt->Value = (USHORT)i2caddr; + fx3dev->ControlEndPt->Index = (USHORT)regaddr; + Sleep(10); + r = fx3dev->ControlEndPt->Write(pdata, lgt); + if (r == false) + DbgPrintf("\nfx3FWSendI2cbytes 0x%02x regaddr 0x%02x 1data 0x%02x len 0x%02x \n", + i2caddr, regaddr, *pdata, len); + return r; +} + +bool fx3handler::ReadI2cbytes(UINT8 i2caddr, UINT8 regaddr, PUINT8 pdata, UINT8 len) +{ + bool r = false; + LONG lgt = len; + WORD saveValue, saveIndex; + saveValue = fx3dev->ControlEndPt->Value; + saveIndex = fx3dev->ControlEndPt->Index; + + fx3dev->ControlEndPt->ReqCode = I2CRFX3; + fx3dev->ControlEndPt->Value = (USHORT)i2caddr; + fx3dev->ControlEndPt->Index = (USHORT)regaddr; + r = fx3dev->ControlEndPt->Read(pdata, lgt); + if (r == false) + printf("fx3FWReadI2cbytes %x : %02x %02x %02x %02x : %02x\n", r, I2CRFX3, i2caddr, regaddr, len, *pdata); + fx3dev->ControlEndPt->Value = saveValue; + fx3dev->ControlEndPt->Index = saveIndex; + return r; +} + +bool fx3handler::Close() { + fx3dev->Close(); // close class + delete fx3dev; // destroy class + Fx3IsOn = false; + return true; +} + +#define BLOCK_TIMEOUT (80) // block 65.536 ms timeout is 80 + +struct ReadContext +{ + PUCHAR context; + OVERLAPPED overlap; + SINGLE_TRANSFER transfer; + uint8_t* buffer; + long size; +}; + +bool fx3handler::BeginDataXfer(UINT8 *buffer, long transferSize, void** context) +{ + ReadContext *readContext = (ReadContext *)(*context); + + if (!EndPt) + return false; + + if (*context == nullptr) + { + // first time call, allocate the context structure + readContext = new ReadContext; + *context = readContext; + memset(&readContext->overlap, 0, sizeof(readContext->overlap)); + readContext->overlap.hEvent = CreateEvent(NULL, false, false, NULL); + } + + readContext->buffer = buffer; + readContext->size = transferSize; + + readContext->context = EndPt->BeginDataXfer(readContext->buffer, transferSize, &readContext->overlap); + if (EndPt->NtStatus || EndPt->UsbdStatus) {// BeginDataXfer failed + DbgPrintf((char*)"Xfer request rejected. 1 STATUS = %ld %ld\n", EndPt->NtStatus, EndPt->UsbdStatus); + return false; + } + + return true; +} + +bool fx3handler::FinishDataXfer(void** context) +{ + ReadContext *readContext = (ReadContext *)(*context); + + if (!readContext) + { + return nullptr; + } + + if (!EndPt->WaitForXfer(&readContext->overlap, BLOCK_TIMEOUT)) { // block on transfer + DbgPrintf("WaitForXfer timeout. NTSTATUS = 0x%08X\n", EndPt->NtStatus); + EndPt->Abort(); // abort if timeout + return false; + } + + auto requested_size = readContext->size; + if (!EndPt->FinishDataXfer(readContext->buffer, readContext->size, &readContext->overlap, readContext->context)) { + DbgPrintf("FinishDataXfer Failed. NTSTATUS = 0x%08X\n", EndPt->NtStatus); + return false; + } + + if (readContext->size < requested_size) + DbgPrintf("only read %ld but requested %ld\n", readContext->size, requested_size); + + return true; +} + +void fx3handler::CleanupDataXfer(void** context) +{ + ReadContext *readContext = (ReadContext *)(*context); + + CloseHandle(readContext->overlap.hEvent); + delete (readContext); +} + +#define USB_READ_CONCURRENT 4 + +void fx3handler::AdcSamplesProcess() +{ + DbgPrintf("AdcSamplesProc thread runs\n"); + int buf_idx; // queue index + int read_idx; + void* contexts[USB_READ_CONCURRENT]; + + memset(contexts, 0, sizeof(contexts)); + + // Queue-up the first batch of transfer requests + for (int n = 0; n < USB_READ_CONCURRENT; n++) { + auto ptr = inputbuffer->peekWritePtr(n); + if (!BeginDataXfer((uint8_t*)ptr, transferSize, &contexts[n])) { + DbgPrintf("Xfer request rejected.\n"); + return; + } + } + + read_idx = 0; // context cycle index + buf_idx = 0; // buffer cycle index + + // The infinite xfer loop. + while (run) { + if (!FinishDataXfer(&contexts[read_idx])) { + break; + } + + inputbuffer->WriteDone(); + + // Re-submit this queue element to keep the queue full + auto ptr = inputbuffer->peekWritePtr(USB_READ_CONCURRENT - 1); + if (!BeginDataXfer((uint8_t*)ptr, transferSize, &contexts[read_idx])) { // BeginDataXfer failed + DbgPrintf("Xfer request rejected.\n"); + break; + } + + buf_idx = (buf_idx + 1) % QUEUE_SIZE; + read_idx = (read_idx + 1) % USB_READ_CONCURRENT; + } // End of the infinite loop + + for (int n = 0; n < USB_READ_CONCURRENT; n++) { + CleanupDataXfer(&contexts[n]); + } + + DbgPrintf("AdcSamplesProc thread_exit\n"); + return; // void * +} + +void fx3handler::StartStream(ringbuffer& input, int numofblock) +{ + // Allocate the context and buffers + inputbuffer = &input; + + // create the thread + this->numofblock = numofblock; + run = true; + adc_samples_thread = new std::thread( + [this]() { + this->AdcSamplesProcess(); + } + ); +} + +void fx3handler::StopStream() +{ + // set the flag + run = false; + adc_samples_thread->join(); + + // force exit the thread + inputbuffer = nullptr; + + delete adc_samples_thread; +} diff --git a/sddc_source/src/libsddc/Core/arch/win32/FX3handler.h b/sddc_source/src/libsddc/Core/arch/win32/FX3handler.h new file mode 100644 index 0000000..5d580ea --- /dev/null +++ b/sddc_source/src/libsddc/Core/arch/win32/FX3handler.h @@ -0,0 +1,73 @@ +#ifndef FX3HANDLER_H +#define FX3HANDLER_H + +// +// FX3handler.cpp +// 2020 10 12 Oscar Steila ik1xpv +// loading arm code.img from resource by Howard Su and Hayati Ayguen +// This module was previous named:openFX3.cpp +// MIT License Copyright (c) 2016 Booya Corp. +// booyasdr@gmail.com, http://booyasdr.sf.net +// modified 2017 11 30 ik1xpv@gmail.com, http://www.steila.com/blog +// + +#include +#include +#include +#include +#include +#include "sddc_config.h" + +#include "dsp/ringbuffer.h" + +#define VENDOR_ID (0x04B4) +#define STREAMER_ID (0x00F1) +#define BOOTLOADER_ID (0x00F3) + +#include "FX3Class.h" + +class CCyFX3Device; +class CCyUSBEndPoint; + +class fx3handler : public fx3class +{ +public: + fx3handler(); + virtual ~fx3handler(void); + bool Open(uint8_t* fw_data, uint32_t fw_size); + bool IsOn() { return Fx3IsOn; } + bool Control(FX3Command command, uint8_t data); + bool Control(FX3Command command, uint32_t data = 0); + bool Control(FX3Command command, uint64_t data); + bool SetArgument(uint16_t index, uint16_t value); + bool GetHardwareInfo(uint32_t* data); + bool ReadDebugTrace(uint8_t* pdata, uint8_t len); + void StartStream(ringbuffer& input, int numofblock); + void StopStream(); + +private: + bool SendI2cbytes(uint8_t i2caddr, uint8_t regaddr, uint8_t* pdata, uint8_t len); + bool ReadI2cbytes(uint8_t i2caddr, uint8_t regaddr, uint8_t* pdata, uint8_t len); + + bool BeginDataXfer(uint8_t *buffer, long transferSize, void** context); + bool FinishDataXfer(void** context); + void CleanupDataXfer(void** context); + + CCyFX3Device* fx3dev; + CCyUSBEndPoint* EndPt; + + std::thread *adc_samples_thread; + + bool GetFx3Device(); + bool GetFx3DeviceStreamer(); + bool Fx3IsOn; + bool Close(void); + void AdcSamplesProcess(); + + ringbuffer *inputbuffer; + int numofblock; + bool run; +}; + + +#endif // FX3HANDLER_H diff --git a/sddc_source/src/libsddc/Core/dsp/ringbuffer.h b/sddc_source/src/libsddc/Core/dsp/ringbuffer.h new file mode 100644 index 0000000..97f172b --- /dev/null +++ b/sddc_source/src/libsddc/Core/dsp/ringbuffer.h @@ -0,0 +1,191 @@ +#pragma once + +#include +#include +#include + +const int default_count = 64; +const int spin_count = 100; +#define ALIGN (8) + +class ringbufferbase { +public: + ringbufferbase(int count) : + max_count(count), + read_index(0), + write_index(0), + emptyCount(0), + fullCount(0), + writeCount(0) + { + } + + int getFullCount() const { return fullCount; } + + int getEmptyCount() const { return emptyCount; } + + int getWriteCount() const { return writeCount; } + + void ReadDone() + { + std::unique_lock lk(mutex); + if ((write_index + 1) % max_count == read_index) + { + read_index = (read_index + 1) % max_count; + nonfullCV.notify_all(); + } + else + { + read_index = (read_index + 1) % max_count; + } + } + + void WriteDone() + { + std::unique_lock lk(mutex); + if (read_index == write_index) + { + write_index = (write_index + 1) % max_count; + nonemptyCV.notify_all(); + } + else + { + write_index = (write_index + 1) % max_count; + } + writeCount++; + } + + void Stop() + { + std::unique_lock lk(mutex); + read_index = 0; + write_index = max_count / 2; + nonfullCV.notify_all(); + nonemptyCV.notify_all(); + } + +protected: + + void WaitUntilNotEmpty() + { + // if not empty + for (int i = 0; i < spin_count; i++) + { + if (read_index != write_index) + return; + } + + if (read_index == write_index) + { + std::unique_lock lk(mutex); + + emptyCount++; + nonemptyCV.wait(lk, [this] { + return read_index != write_index; + }); + } + } + + void WaitUntilNotFull() + { + for (int i = 0; i < spin_count; i++) + { + if ((write_index + 1) % max_count != read_index) + return; + } + + if ((write_index + 1) % max_count == read_index) + { + std::unique_lock lk(mutex); + fullCount++; + nonfullCV.wait(lk, [this] { + return (write_index + 1) % max_count != read_index; + }); + } + } + + int max_count; + + volatile int read_index; + volatile int write_index; + +private: + int emptyCount; + int fullCount; + int writeCount; + + std::mutex mutex; + std::condition_variable nonemptyCV; + std::condition_variable nonfullCV; +}; + +template class ringbuffer : public ringbufferbase { + typedef T* TPtr; + +public: + ringbuffer(int count = default_count) : + ringbufferbase(count) + { + buffers = new TPtr[max_count]; + buffers[0] = nullptr; + } + + ~ringbuffer() + { + if (buffers[0]) + delete[] buffers[0]; + + delete[] buffers; + } + + void setBlockSize(int size) + { + if (block_size != size) + { + block_size = size; + + if (buffers[0]) + delete[] buffers[0]; + + int aligned_block_size = (block_size + ALIGN - 1) & (~(ALIGN - 1)); + + auto data = new T[max_count * aligned_block_size]; + + for (int i = 0; i < max_count; ++i) + { + buffers[i] = &data[i * aligned_block_size]; + } + } + } + + T* peekWritePtr(int offset) + { + return buffers[(write_index + max_count + offset) % max_count]; + } + + T* peekReadPtr(int offset) + { + return buffers[(read_index + max_count + offset) % max_count]; + } + + T* getWritePtr() + { + // if there is still space + WaitUntilNotFull(); + return buffers[(write_index) % max_count]; + } + + const T* getReadPtr() + { + WaitUntilNotEmpty(); + + return buffers[read_index]; + } + + int getBlockSize() const { return block_size; } + +private: + int block_size; + + TPtr* buffers; +}; \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq.cpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq.cpp new file mode 100644 index 0000000..9840ae6 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq.cpp @@ -0,0 +1,263 @@ +#include "license.txt" +/* +The ADC input real stream of 16 bit samples (at Fs = 64 Msps in the example) is converted to: +- 32 Msps float Fs/2 complex stream, or +- 16 Msps float Fs/2 complex stream, or +- 8 Msps float Fs/2 complex stream, or +- 4 Msps float Fs/2 complex stream, or +- 2 Msps float Fs/2 complex stream. +The decimation factor is selectable from HDSDR GUI sampling rate selector + +The name r2iq as Real 2 I+Q stream + +*/ + +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "fftw3.h" +#include "RadioHandler.h" + +#include "fir.h" + +#include +#include + + +r2iqControlClass::r2iqControlClass() +{ + r2iqOn = false; + randADC = false; + sideband = false; + mdecimation = 0; + mratio[0] = 1; // 1,2,4,8,16 + for (int i = 1; i < NDECIDX; i++) + { + mratio[i] = mratio[i - 1] * 2; + } +} + +fft_mt_r2iq::fft_mt_r2iq() : + r2iqControlClass(), + filterHw(nullptr) +{ + mtunebin = halfFft / 4; + mfftdim[0] = halfFft; + for (int i = 1; i < NDECIDX; i++) + { + mfftdim[i] = mfftdim[i - 1] / 2; + } + GainScale = 0.0f; + +#ifndef NDEBUG + int mratio = 1; // 1,2,4,8,16,.. + const float Astop = 120.0f; + const float relPass = 0.85f; // 85% of Nyquist should be usable + const float relStop = 1.1f; // 'some' alias back into transition band is OK + printf("\n***************************************************************************\n"); + printf("Filter tap estimation, Astop = %.1f dB, relPass = %.2f, relStop = %.2f\n", Astop, relPass, relStop); + for (int d = 0; d < NDECIDX; d++) + { + float Bw = 64.0f / mratio; + int ntaps = KaiserWindow(0, Astop, relPass * Bw / 128.0f, relStop * Bw / 128.0f, nullptr); + printf("decimation %2d: KaiserWindow(Astop = %.1f dB, Fpass = %.3f,Fstop = %.3f, Bw %.3f @ %f ) => %d taps\n", + d, Astop, relPass * Bw, relStop * Bw, Bw, 128.0f, ntaps); + mratio = mratio * 2; + } + printf("***************************************************************************\n"); +#endif + +} + +fft_mt_r2iq::~fft_mt_r2iq() +{ + if (filterHw == nullptr) + return; + + fftwf_export_wisdom_to_filename("wisdom"); + + for (int d = 0; d < NDECIDX; d++) + { + fftwf_free(filterHw[d]); // 4096 + } + fftwf_free(filterHw); + + fftwf_destroy_plan(plan_t2f_r2c); + for (int d = 0; d < NDECIDX; d++) + { + fftwf_destroy_plan(plans_f2t_c2c[d]); + } + + for (unsigned t = 0; t < processor_count; t++) { + auto th = threadArgs[t]; + fftwf_free(th->ADCinTime); + fftwf_free(th->ADCinFreq); + fftwf_free(th->inFreqTmp); + + delete threadArgs[t]; + } +} + + +float fft_mt_r2iq::setFreqOffset(float offset) +{ + // align to 1/4 of halfft + this->mtunebin = int(offset * halfFft / 4) * 4; // mtunebin step 4 bin ? + float delta = ((float)this->mtunebin / halfFft) - offset; + float ret = delta * getRatio(); // ret increases with higher decimation + DbgPrintf("offset %f mtunebin %d delta %f (%f)\n", offset, this->mtunebin, delta, ret); + return ret; +} + +void fft_mt_r2iq::TurnOn() { + this->r2iqOn = true; + this->bufIdx = 0; + this->lastThread = threadArgs[0]; + + for (unsigned t = 0; t < processor_count; t++) { + r2iq_thread[t] = std::thread( + [this] (void* arg) + { return this->r2iqThreadf((r2iqThreadArg*)arg); }, (void*)threadArgs[t]); + } +} + +void fft_mt_r2iq::TurnOff(void) { + this->r2iqOn = false; + + inputbuffer->Stop(); + outputbuffer->Stop(); + for (unsigned t = 0; t < processor_count; t++) { + r2iq_thread[t].join(); + } +} + +bool fft_mt_r2iq::IsOn(void) { return(this->r2iqOn); } + +void fft_mt_r2iq::Init(float gain, ringbuffer *input, ringbuffer* obuffers) +{ + this->inputbuffer = input; // set to the global exported by main_loop + this->outputbuffer = obuffers; // set to the global exported by main_loop + + this->GainScale = gain; + + fftwf_import_wisdom_from_filename("wisdom"); + + // Get the processor count + processor_count = std::thread::hardware_concurrency() - 1; + if (processor_count == 0) + processor_count = 1; + if (processor_count > N_MAX_R2IQ_THREADS) + processor_count = N_MAX_R2IQ_THREADS; + + { + fftwf_plan filterplan_t2f_c2c; // time to frequency fft + + DbgPrintf((char *) "r2iqCntrl initialization\n"); + + + // DbgPrintf((char *) "RandTable generated\n"); + + // filters + fftwf_complex *pfilterht; // time filter ht + pfilterht = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*halfFft); // halfFft + filterHw = (fftwf_complex**)fftwf_malloc(sizeof(fftwf_complex*)*NDECIDX); + for (int d = 0; d < NDECIDX; d++) + { + filterHw[d] = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*halfFft); // halfFft + } + + filterplan_t2f_c2c = fftwf_plan_dft_1d(halfFft, pfilterht, filterHw[0], FFTW_FORWARD, FFTW_MEASURE); + float *pht = new float[halfFft / 4 + 1]; + const float Astop = 120.0f; + const float relPass = 0.85f; // 85% of Nyquist should be usable + const float relStop = 1.1f; // 'some' alias back into transition band is OK + for (int d = 0; d < NDECIDX; d++) // @todo when increasing NDECIDX + { + // @todo: have dynamic bandpass filter size - depending on decimation + // to allow same stopband-attenuation for all decimations + float Bw = 64.0f / mratio[d]; + // Bw *= 0.8f; // easily visualize Kaiser filter's response + KaiserWindow(halfFft / 4 + 1, Astop, relPass * Bw / 128.0f, relStop * Bw / 128.0f, pht); + + float gainadj = gain * 2048.0f / (float)FFTN_R_ADC; // reference is FFTN_R_ADC == 2048 + + for (int t = 0; t < halfFft; t++) + { + pfilterht[t][0] = pfilterht[t][1]= 0.0F; + } + + for (int t = 0; t < (halfFft/4+1); t++) + { + pfilterht[halfFft-1-t][0] = gainadj * pht[t]; + } + + fftwf_execute_dft(filterplan_t2f_c2c, pfilterht, filterHw[d]); + } + delete[] pht; + fftwf_destroy_plan(filterplan_t2f_c2c); + fftwf_free(pfilterht); + + for (unsigned t = 0; t < processor_count; t++) { + r2iqThreadArg *th = new r2iqThreadArg(); + threadArgs[t] = th; + + th->ADCinTime = (float*)fftwf_malloc(sizeof(float) * (halfFft + transferSize / 2)); // 2048 + + th->ADCinFreq = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*(halfFft + 1)); // 1024+1 + th->inFreqTmp = (fftwf_complex*)fftwf_malloc(sizeof(fftwf_complex)*(halfFft)); // 1024 + } + + plan_t2f_r2c = fftwf_plan_dft_r2c_1d(2 * halfFft, threadArgs[0]->ADCinTime, threadArgs[0]->ADCinFreq, FFTW_MEASURE); + for (int d = 0; d < NDECIDX; d++) + { + plans_f2t_c2c[d] = fftwf_plan_dft_1d(mfftdim[d], threadArgs[0]->inFreqTmp, threadArgs[0]->inFreqTmp, FFTW_BACKWARD, FFTW_MEASURE); + } + } +} + +#ifdef _WIN32 + // Windows + #include + #define cpuid(info, x) __cpuidex(info, x, 0) +#else + // GCC Intrinsics + #include + #define cpuid(info, x) __cpuid_count(x, 0, info[0], info[1], info[2], info[3]) +#endif + +void * fft_mt_r2iq::r2iqThreadf(r2iqThreadArg *th) +{ +#ifdef NO_SIMD_OPTIM + DbgPrintf("Hardware Capability: all SIMD features (AVX, AVX2, AVX512) deactivated\n"); + return r2iqThreadf_def(th); +#else + int info[4]; + bool HW_AVX = false; + bool HW_AVX2 = false; + bool HW_AVX512F = false; + + cpuid(info, 0); + int nIds = info[0]; + + if (nIds >= 0x00000001){ + cpuid(info,0x00000001); + HW_AVX = (info[2] & ((int)1 << 28)) != 0; + } + if (nIds >= 0x00000007){ + cpuid(info,0x00000007); + HW_AVX2 = (info[1] & ((int)1 << 5)) != 0; + + HW_AVX512F = (info[1] & ((int)1 << 16)) != 0; + } + + DbgPrintf("Hardware Capability: AVX:%d AVX2:%d AVX512:%d\n", HW_AVX, HW_AVX2, HW_AVX512F); + + if (HW_AVX512F) + return r2iqThreadf_avx512(th); + else if (HW_AVX2) + return r2iqThreadf_avx2(th); + else if (HW_AVX) + return r2iqThreadf_avx(th); + else + return r2iqThreadf_def(th); +#endif +} diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq.h b/sddc_source/src/libsddc/Core/fft_mt_r2iq.h new file mode 100644 index 0000000..e20a619 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq.h @@ -0,0 +1,127 @@ +#pragma once + +#include "r2iq.h" +#include "fftw3.h" +#include "sddc_config.h" +#include +#include + +// use up to this many threads +#define N_MAX_R2IQ_THREADS 1 +#define PRINT_INPUT_RANGE 0 + +static const int halfFft = FFTN_R_ADC / 2; // half the size of the first fft at ADC 64Msps real rate (2048) +static const int fftPerBuf = transferSize / sizeof(short) / (3 * halfFft / 2) + 1; // number of ffts per buffer with 256|768 overlap + +class fft_mt_r2iq : public r2iqControlClass +{ +public: + fft_mt_r2iq(); + virtual ~fft_mt_r2iq(); + + float setFreqOffset(float offset); + + void Init(float gain, ringbuffer* buffers, ringbuffer* obuffers); + void TurnOn(); + void TurnOff(void); + bool IsOn(void); + +protected: + + template void convert_float(const int16_t *input, float* output, int size) + { + for(int m = 0; m < size; m++) + { + int16_t val; + if (rand && (input[m] & 1)) + { + val = input[m] ^ (-2); + } + else + { + val = input[m]; + } + output[m] = float(val); + } + } + + void shift_freq(fftwf_complex* dest, const fftwf_complex* source1, const fftwf_complex* source2, int start, int end) + { + for (int m = start; m < end; m++) + { + // besides circular shift, do complex multiplication with the lowpass filter's spectrum + dest[m][0] = source1[m][0] * source2[m][0] - source1[m][1] * source2[m][1]; + dest[m][1] = source1[m][1] * source2[m][0] + source1[m][0] * source2[m][1]; + } + } + + template void copy(fftwf_complex* dest, const fftwf_complex* source, int count) + { + if (flip) + { + for (int i = 0; i < count; i++) + { + dest[i][0] = source[i][0]; + dest[i][1] = -source[i][1]; + } + } + else + { + for (int i = 0; i < count; i++) + { + dest[i][0] = source[i][0]; + dest[i][1] = source[i][1]; + } + } + } + +private: + ringbuffer* inputbuffer; // pointer to input buffers + ringbuffer* outputbuffer; // pointer to ouput buffers + int bufIdx; // index to next buffer to be processed + r2iqThreadArg* lastThread; + + float GainScale; + int mfftdim [NDECIDX]; // FFT N dimensions: mfftdim[k] = halfFft / 2^k + int mtunebin; + + void *r2iqThreadf(r2iqThreadArg *th); // thread function + + void * r2iqThreadf_def(r2iqThreadArg *th); + void * r2iqThreadf_avx(r2iqThreadArg *th); + void * r2iqThreadf_avx2(r2iqThreadArg *th); + void * r2iqThreadf_avx512(r2iqThreadArg *th); + + fftwf_complex **filterHw; // Hw complex to each decimation ratio + + fftwf_plan plan_t2f_r2c; // fftw plan buffers Freq to Time complex to complex per decimation ratio + fftwf_plan *plan_f2t_c2c; // fftw plan buffers Time to Freq real to complex per buffer + fftwf_plan plans_f2t_c2c[NDECIDX]; + + uint32_t processor_count; + r2iqThreadArg* threadArgs[N_MAX_R2IQ_THREADS]; + std::mutex mutexR2iqControl; // r2iq control lock + std::thread r2iq_thread[N_MAX_R2IQ_THREADS]; // thread pointers +}; + +// assure, that ADC is not oversteered? +struct r2iqThreadArg { + + r2iqThreadArg() + { +#if PRINT_INPUT_RANGE + MinMaxBlockCount = 0; + MinValue = 0; + MaxValue = 0; +#endif + } + + float *ADCinTime; // point to each threads input buffers [nftt][n] + fftwf_complex *ADCinFreq; // buffers in frequency + fftwf_complex *inFreqTmp; // tmp decimation output buffers (after tune shift) +#if PRINT_INPUT_RANGE + int MinMaxBlockCount; + int16_t MinValue; + int16_t MaxValue; +#endif +}; \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx.cpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx.cpp new file mode 100644 index 0000000..77d2391 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx.cpp @@ -0,0 +1,9 @@ +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "fftw3.h" +#include "RadioHandler.h" + +void * fft_mt_r2iq::r2iqThreadf_avx(r2iqThreadArg *th) +{ + #include "fft_mt_r2iq_impl.hpp" +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx2.cpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx2.cpp new file mode 100644 index 0000000..a5f83fe --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx2.cpp @@ -0,0 +1,9 @@ +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "fftw3.h" +#include "RadioHandler.h" + +void * fft_mt_r2iq::r2iqThreadf_avx2(r2iqThreadArg *th) +{ + #include "fft_mt_r2iq_impl.hpp" +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx512.cpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx512.cpp new file mode 100644 index 0000000..873aa23 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq_avx512.cpp @@ -0,0 +1,9 @@ +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "fftw3.h" +#include "RadioHandler.h" + +void * fft_mt_r2iq::r2iqThreadf_avx512(r2iqThreadArg *th) +{ + #include "fft_mt_r2iq_impl.hpp" +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq_def.cpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq_def.cpp new file mode 100644 index 0000000..b4d1a92 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq_def.cpp @@ -0,0 +1,9 @@ +#include "fft_mt_r2iq.h" +#include "sddc_config.h" +#include "fftw3.h" +#include "RadioHandler.h" + +void * fft_mt_r2iq::r2iqThreadf_def(r2iqThreadArg *th) +{ + #include "fft_mt_r2iq_impl.hpp" +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/fft_mt_r2iq_impl.hpp b/sddc_source/src/libsddc/Core/fft_mt_r2iq_impl.hpp new file mode 100644 index 0000000..7d9662b --- /dev/null +++ b/sddc_source/src/libsddc/Core/fft_mt_r2iq_impl.hpp @@ -0,0 +1,171 @@ + +{ + const int decimate = this->mdecimation; + const int mfft = this->mfftdim[decimate]; // = halfFft / 2^mdecimation + const fftwf_complex* filter = filterHw[decimate]; + const bool lsb = this->getSideband(); + const auto filter2 = &filter[halfFft - mfft / 2]; + + plan_f2t_c2c = &plans_f2t_c2c[decimate]; + fftwf_complex* pout = nullptr; + int decimate_count = 0; + + while (r2iqOn) { + const int16_t *dataADC; // pointer to input data + const int16_t *endloop; // pointer to end data to be copied to beginning + + const int _mtunebin = this->mtunebin; // Update LO tune is possible during run + + { + std::unique_lock lk(mutexR2iqControl); + dataADC = inputbuffer->getReadPtr(); + + if (!r2iqOn) + return 0; + + this->bufIdx = (this->bufIdx + 1) % QUEUE_SIZE; + + endloop = inputbuffer->peekReadPtr(-1) + transferSamples - halfFft; + } + + auto inloop = th->ADCinTime; + + // @todo: move the following int16_t conversion to (32-bit) float + // directly inside the following loop (for "k < fftPerBuf") + // just before the forward fft "fftwf_execute_dft_r2c" is called + // idea: this should improve cache/memory locality +#if PRINT_INPUT_RANGE + std::pair blockMinMax = std::make_pair(0, 0); +#endif + if (!this->getRand()) // plain samples no ADC rand set + { + convert_float(endloop, inloop, halfFft); +#if PRINT_INPUT_RANGE + auto minmax = std::minmax_element(dataADC, dataADC + transferSamples); + blockMinMax.first = *minmax.first; + blockMinMax.second = *minmax.second; +#endif + convert_float(dataADC, inloop + halfFft, transferSamples); + } + else + { + convert_float(endloop, inloop, halfFft); + convert_float(dataADC, inloop + halfFft, transferSamples); + } + +#if PRINT_INPUT_RANGE + th->MinValue = std::min(blockMinMax.first, th->MinValue); + th->MaxValue = std::max(blockMinMax.second, th->MaxValue); + ++th->MinMaxBlockCount; + if (th->MinMaxBlockCount * processor_count / 3 >= DEFAULT_TRANSFERS_PER_SEC ) + { + float minBits = (th->MinValue < 0) ? (log10f((float)(-th->MinValue)) / log10f(2.0f)) : -1.0f; + float maxBits = (th->MaxValue > 0) ? (log10f((float)(th->MaxValue)) / log10f(2.0f)) : -1.0f; + printf("r2iq: min = %d (%.1f bits) %.2f%%, max = %d (%.1f bits) %.2f%%\n", + (int)th->MinValue, minBits, th->MinValue *-100.0f / 32768.0f, + (int)th->MaxValue, maxBits, th->MaxValue * 100.0f / 32768.0f); + th->MinValue = 0; + th->MaxValue = 0; + th->MinMaxBlockCount = 0; + } +#endif + dataADC = nullptr; + inputbuffer->ReadDone(); + // decimate in frequency plus tuning + + if (decimate_count == 0) + pout = (fftwf_complex*)outputbuffer->getWritePtr(); + + decimate_count = (decimate_count + 1) & ((1 << decimate) - 1); + + // Calculate the parameters for the first half + const auto count = std::min(mfft/2, halfFft - _mtunebin); + const auto source = &th->ADCinFreq[_mtunebin]; + + // Calculate the parameters for the second half + const auto start = std::max(0, mfft / 2 - _mtunebin); + const auto source2 = &th->ADCinFreq[_mtunebin - mfft / 2]; + const auto dest = &th->inFreqTmp[mfft / 2]; + for (int k = 0; k < fftPerBuf; k++) + { + // core of fast convolution including filter and decimation + // main part is 'overlap-scrap' (IMHO better name for 'overlap-save'), see + // https://en.wikipedia.org/wiki/Overlap%E2%80%93save_method + { + // FFT first stage: time to frequency, real to complex + // 'full' transformation size: 2 * halfFft + fftwf_execute_dft_r2c(plan_t2f_r2c, th->ADCinTime + (3 * halfFft / 2) * k, th->ADCinFreq); + // result now in th->ADCinFreq[] + + // circular shift (mixing in full bins) and low/bandpass filtering (complex multiplication) + { + // circular shift tune fs/2 first half array into th->inFreqTmp[] + shift_freq(th->inFreqTmp, source, filter, 0, count); + if (mfft / 2 != count) + memset(th->inFreqTmp[count], 0, sizeof(float) * 2 * (mfft / 2 - count)); + + // circular shift tune fs/2 second half array + shift_freq(dest, source2, filter2, start, mfft/2); + if (start != 0) + memset(th->inFreqTmp[mfft / 2], 0, sizeof(float) * 2 * start); + } + // result now in th->inFreqTmp[] + + // 'shorter' inverse FFT transform (decimation); frequency (back) to COMPLEX time domain + // transform size: mfft = mfftdim[k] = halfFft / 2^k with k = mdecimation + fftwf_execute_dft(*plan_f2t_c2c, th->inFreqTmp, th->inFreqTmp); // c2c decimation + // result now in th->inFreqTmp[] + } + + // postprocessing + // @todo: is it possible to .. + // 1) + // let inverse FFT produce/save it's result directly + // in "this->obuffers[modx] + offset" (pout) + // ( obuffers[] would need to have additional space ..; + // need to move 'scrap' of 'ovelap-scrap'? ) + // at least FFTW would allow so, + // see http://www.fftw.org/fftw3_doc/New_002darray-Execute-Functions.html + // attention: multithreading! + // 2) + // could mirroring (lower sideband) get calculated together + // with fine mixer - modifying the mixer frequency? (fs - fc)/fs + // (this would reduce one memory pass) + if (lsb) // lower sideband + { + // mirror just by negating the imaginary Q of complex I/Q + if (k == 0) + { + copy(pout, &th->inFreqTmp[mfft / 4], mfft/2); + } + else + { + copy(pout + mfft / 2 + (3 * mfft / 4) * (k - 1), &th->inFreqTmp[0], (3 * mfft / 4)); + } + } + else // upper sideband + { + if (k == 0) + { + copy(pout, &th->inFreqTmp[mfft / 4], mfft/2); + } + else + { + copy(pout + mfft / 2 + (3 * mfft / 4) * (k - 1), &th->inFreqTmp[0], (3 * mfft / 4)); + } + } + // result now in this->obuffers[] + } + + if (decimate_count == 0) { + outputbuffer->WriteDone(); + pout = nullptr; + } + else + { + pout += mfft / 2 + (3 * mfft / 4) * (fftPerBuf - 1); + } + } // while(run) +// DbgPrintf((char *) "r2iqThreadf idx %d pthread_exit %u\n",(int)th->t, pthread_self()); + return 0; +} diff --git a/sddc_source/src/libsddc/Core/fir.cpp b/sddc_source/src/libsddc/Core/fir.cpp new file mode 100644 index 0000000..41a66d0 --- /dev/null +++ b/sddc_source/src/libsddc/Core/fir.cpp @@ -0,0 +1,105 @@ +#include "fir.h" +#include + +#define K_PI 3.141592653f +#define K_2PI (2*K_PI) + +static float Izero(float x) +{ + float x2 = x / 2.0f; + float sum = 1.0f; + float ds = 1.0f; + float di = 1.0f; + float errorlimit = 1e-9f; + float tmp; + do + { + tmp = x2 / di; + tmp *= tmp; + ds *= tmp; + sum += ds; + di += 1.0; + } while (ds >= errorlimit * sum); + //qDebug()<<"x="< +#include +#include +#include +#include +#include + +//#define HAVE_SYS_TIMES + +#ifdef HAVE_SYS_TIMES +# include +# include +#endif + +#define BENCH_REF_TRIG_FUNC 1 +#define BENCH_OUT_OF_PLACE_ALGOS 0 +#define BENCH_INPLACE_ALGOS 1 + +#define SAVE_BY_DEFAULT 1 +#define SAVE_LIMIT_MSPS 16 + +#if 1 + #define BENCH_FILE_SHIFT_MATH_CC "A_shift_math_cc.bin" + #define BENCH_FILE_ADD_FAST_CC "C_shift_addfast_cc.bin" + #define BENCH_FILE_ADD_FAST_INP_C "C_shift_addfast_inp_c.bin" + #define BENCH_FILE_UNROLL_INP_C "D_shift_unroll_inp_c.bin" + #define BENCH_FILE_LTD_UNROLL_INP_C "E_shift_limited_unroll_inp_c.bin" + #define BENCH_FILE_LTD_UNROLL_A_SSE_INP_C "F_shift_limited_unroll_A_sse_inp_c.bin" + #define BENCH_FILE_LTD_UNROLL_B_SSE_INP_C "G_shift_limited_unroll_B_sse_inp_c.bin" + #define BENCH_FILE_LTD_UNROLL_C_SSE_INP_C "H_shift_limited_unroll_C_sse_inp_c.bin" + #define BENCH_FILE_REC_OSC_CC "" + #define BENCH_FILE_REC_OSC_INP_C "I_shift_recursive_osc_inp_c.bin" + #define BENCH_FILE_REC_OSC_SSE_INP_C "J_shift_recursive_osc_sse_inp_c.bin" +#else + #define BENCH_FILE_SHIFT_MATH_CC "" + #define BENCH_FILE_ADD_FAST_CC "" + #define BENCH_FILE_ADD_FAST_INP_C "" + #define BENCH_FILE_UNROLL_INP_C "" + #define BENCH_FILE_LTD_UNROLL_INP_C "" + #define BENCH_FILE_LTD_UNROLL_A_SSE_INP_C "" + #define BENCH_FILE_LTD_UNROLL_B_SSE_INP_C "" + #define BENCH_FILE_LTD_UNROLL_C_SSE_INP_C "" + #define BENCH_FILE_REC_OSC_CC "" + #define BENCH_FILE_REC_OSC_INP_C "" + #define BENCH_FILE_REC_OSC_SSE_INP_C "" +#endif + + + +#if defined(HAVE_SYS_TIMES) + static double ttclk = 0.; + + static double uclock_sec(int find_start) + { + struct tms t0, t; + if (ttclk == 0.) + { + ttclk = sysconf(_SC_CLK_TCK); + fprintf(stderr, "sysconf(_SC_CLK_TCK) => %f\n", ttclk); + } + times(&t); + if (find_start) + { + t0 = t; + while (t0.tms_utime == t.tms_utime) + times(&t); + } + /* use only the user time of this process - not realtime, which depends on OS-scheduler .. */ + return ((double)t.tms_utime) / ttclk; + } + +#elif 0 + // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimes + double uclock_sec(int find_start) + { + FILETIME a, b, c, d; + if (GetProcessTimes(GetCurrentProcess(), &a, &b, &c, &d) != 0) + { + // Returns total user time. + // Can be tweaked to include kernel times as well. + return + (double)(d.dwLowDateTime | + ((unsigned long long)d.dwHighDateTime << 32)) * 0.0000001; + } + else { + // Handle error + return 0; + } + } + +#else + double uclock_sec(int find_start) + { return (double)clock()/(double)CLOCKS_PER_SEC; } +#endif + + +void save(complexf * d, int B, int N, const char * fn) +{ + if (!fn || !fn[0]) + { + if (! SAVE_BY_DEFAULT) + return; + fn = "bench.bin"; + } + FILE* f; + fopen_s(&f, fn, "wb"); + if (!f) { + fprintf(stderr, "error writing result to %s\n", fn); + return; + }else + printf( "saving to %s\n", fn); + + if ( N >= SAVE_LIMIT_MSPS * 1024 * 1024 ) + N = SAVE_LIMIT_MSPS * 1024 * 1024; + for (int off = 0; off + B <= N; off += B) + { + fwrite(d+off, sizeof(complexf), B, f); + } + fclose(f); +} + + +double bench_shift_math_cc(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_math_cc(input+off, output+off, B, -0.0009F, phase); + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(output, B, off, BENCH_FILE_SHIFT_MATH_CC); + + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_table_cc(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + int table_size=65536; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + + shift_table_data_t table_data = shift_table_init(table_size); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_table_cc(input+off, output+off, B, -0.0009F, table_data, phase); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(output, B, off, NULL); + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_addfast(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_addfast_data_t state = shift_addfast_init(-0.0009F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_addfast_cc(input+off, output+off, B, &state, phase); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(output, B, off, BENCH_FILE_ADD_FAST_CC); + + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + +double bench_shift_addfast_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_addfast_data_t state = shift_addfast_init(-0.0009F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_addfast_inp_c(input+off, B, &state, phase); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_ADD_FAST_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_unroll_oop(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_unroll_data_t state = shift_unroll_init(-0.0009F, B); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_unroll_cc(input+off, output+off, B, &state, phase); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(output, B, off, NULL); + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + +double bench_shift_unroll_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_unroll_data_t state = shift_unroll_init(-0.0009F, B); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + phase = shift_unroll_inp_c(input+off, B, &state, phase); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_UNROLL_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + + +double bench_shift_limited_unroll_oop(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_limited_unroll_data_t state = shift_limited_unroll_init(-0.0009F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_limited_unroll_cc(input+off, output+off, B, &state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(output, B, off, NULL); + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_limited_unroll_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_limited_unroll_data_t state = shift_limited_unroll_init(-0.0009F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_limited_unroll_inp_c(input+off, B, &state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_LTD_UNROLL_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_limited_unroll_A_sse_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_limited_unroll_A_sse_data_t *state = malloc(sizeof(shift_limited_unroll_A_sse_data_t)); + + *state = shift_limited_unroll_A_sse_init(-0.0009F, 0.0F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_limited_unroll_A_sse_inp_c(input+off, B, state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_LTD_UNROLL_A_SSE_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + +double bench_shift_limited_unroll_B_sse_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_limited_unroll_B_sse_data_t *state = malloc(sizeof(shift_limited_unroll_B_sse_data_t)); + + *state = shift_limited_unroll_B_sse_init(-0.0009F, 0.0F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + //shift_recursive_osc_init(0.0F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_limited_unroll_B_sse_inp_c(input+off, B, state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_LTD_UNROLL_B_SSE_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + +double bench_shift_limited_unroll_C_sse_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + shift_limited_unroll_C_sse_data_t *state = malloc(sizeof(shift_limited_unroll_C_sse_data_t)); + + *state = shift_limited_unroll_C_sse_init(-0.0009F, 0.0F); + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_limited_unroll_C_sse_inp_c(input+off, B, state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_LTD_UNROLL_C_SSE_INP_C); + + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_rec_osc_cc_oop(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + complexf *output = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state, shift_state; + shift_recursive_osc_conf_t gen_conf, shift_conf; + + shift_recursive_osc_init(-0.0009F, 0.0F, &shift_conf, &shift_state); + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_recursive_osc_cc(input+off, output+off, B, &shift_conf, &shift_state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_REC_OSC_CC); + + save(output, B, off, NULL); + free(input); + free(output); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_rec_osc_cc_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state, shift_state; + shift_recursive_osc_conf_t gen_conf, shift_conf; + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + shift_recursive_osc_init(-0.0009F, 0.0F, &shift_conf, &shift_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_recursive_osc_inp_c(input+off, B, &shift_conf, &shift_state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_REC_OSC_INP_C); + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + +double bench_shift_rec_osc_sse_c_inp(int B, int N) { + double t0, t1, tstop, T, nI; + int iter, off; + float phase = 0.0F; + complexf *input = (complexf *)malloc(N * sizeof(complexf)); + shift_recursive_osc_t gen_state; + shift_recursive_osc_conf_t gen_conf; + + shift_recursive_osc_sse_t *shift_state = malloc(sizeof(shift_recursive_osc_sse_t)); + shift_recursive_osc_sse_conf_t shift_conf; + + shift_recursive_osc_init(0.001F, 0.0F, &gen_conf, &gen_state); + gen_recursive_osc_c(input, N, &gen_conf, &gen_state); + + shift_recursive_osc_sse_init(-0.0009F, 0.0F, &shift_conf, shift_state); + + iter = 0; + off = 0; + t0 = uclock_sec(1); + tstop = t0 + 0.5; /* benchmark duration: 500 ms */ + do { + // work + shift_recursive_osc_sse_inp_c(input+off, B, &shift_conf, shift_state); + + off += B; + ++iter; + t1 = uclock_sec(0); + } while ( t1 < tstop && off + B < N ); + + save(input, B, off, BENCH_FILE_REC_OSC_SSE_INP_C); + free(input); + T = ( t1 - t0 ); /* duration per fft() */ + printf("processed %f Msamples in %f ms\n", off * 1E-6, T*1E3); + nI = ((double)iter) * B; /* number of iterations "normalized" to O(N) = N */ + return (nI / T); /* normalized iterations per second */ +} + + + +int main(int argc, char **argv) +{ + double rt; + + // process up to 64 MSample (512 MByte) in blocks of 8 kSamples (=64 kByte) + int B = 8 * 1024; + int N = 64 * 1024 * 1024; + int showUsage = 0; + + if (argc == 1) + showUsage = 1; + + if (1 < argc) + B = atoi(argv[1]); + if (2 < argc) + N = atoi(argv[2]) * 1024 * 1024; + + if ( !B || !N || showUsage ) + { + fprintf(stderr, "%s [ [] ]\n", argv[0]); + if ( !B || !N ) + return 0; + } + + fprintf(stderr, "processing up to N = %d MSamples with blocke length of %d samples\n", + N / (1024 * 1024), B ); + + +#if BENCH_REF_TRIG_FUNC + printf("\nstarting bench of shift_math_cc (out-of-place) with trig functions ..\n"); + rt = bench_shift_math_cc(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); +#endif + +#if BENCH_OUT_OF_PLACE_ALGOS + printf("starting bench of shift_table_cc (out-of-place) ..\n"); + rt = bench_shift_table_cc(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("starting bench of shift_addfast_cc (out-of-place) ..\n"); + rt = bench_shift_addfast(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("\nstarting bench of shift_unroll_cc (out-of-place) ..\n"); + rt = bench_shift_unroll_oop(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("\nstarting bench of shift_limited_unroll_cc (out-of-place) ..\n"); + rt = bench_shift_limited_unroll_oop(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("\nstarting bench of shift_recursive_osc_cc (out-of-place) ..\n"); + rt = bench_shift_rec_osc_cc_oop(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); +#endif + +#if BENCH_INPLACE_ALGOS + + printf("starting bench of shift_addfast_inp_c in-place ..\n"); + rt = bench_shift_addfast_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("starting bench of shift_unroll_inp_c in-place ..\n"); + rt = bench_shift_unroll_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("starting bench of shift_limited_unroll_inp_c in-place ..\n"); + rt = bench_shift_limited_unroll_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + if ( have_sse_shift_mixer_impl() ) + { + printf("starting bench of shift_limited_unroll_A_sse_inp_c in-place ..\n"); + rt = bench_shift_limited_unroll_A_sse_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("starting bench of shift_limited_unroll_B_sse_inp_c in-place ..\n"); + rt = bench_shift_limited_unroll_B_sse_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + printf("starting bench of shift_limited_unroll_C_sse_inp_c in-place ..\n"); + rt = bench_shift_limited_unroll_C_sse_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + } + + printf("starting bench of shift_recursive_osc_cc in-place ..\n"); + rt = bench_shift_rec_osc_cc_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + + if ( have_sse_shift_mixer_impl() ) + { + printf("starting bench of shift_recursive_osc_sse_c in-place ..\n"); + rt = bench_shift_rec_osc_sse_c_inp(B, N); + printf(" %f MSamples/sec\n\n", rt * 1E-6); + } +#endif + + return 0; +} + diff --git a/sddc_source/src/libsddc/Core/pffft/fmv.h b/sddc_source/src/libsddc/Core/pffft/fmv.h new file mode 100644 index 0000000..0aa439d --- /dev/null +++ b/sddc_source/src/libsddc/Core/pffft/fmv.h @@ -0,0 +1,20 @@ +#ifndef FMV_H + +#if HAVE_FUNC_ATTRIBUTE_IFUNC +#if defined(__has_attribute) +#if __has_attribute(target_clones) +#if defined(__x86_64) + +// see https://gcc.gnu.org/wiki/FunctionMultiVersioning +#define PF_TARGET_CLONES __attribute__((target_clones("avx","sse4.2","sse3","sse2","sse","default"))) +#define HAVE_PF_TARGET_CLONES 1 +#endif +#endif +#endif +#endif + +#ifndef PF_TARGET_CLONES +#define PF_TARGET_CLONES +#endif + +#endif diff --git a/sddc_source/src/libsddc/Core/pffft/pf_mixer.cpp b/sddc_source/src/libsddc/Core/pffft/pf_mixer.cpp new file mode 100644 index 0000000..58ff1a5 --- /dev/null +++ b/sddc_source/src/libsddc/Core/pffft/pf_mixer.cpp @@ -0,0 +1,1148 @@ +/* +This software is part of pffft/pfdsp, a set of simple DSP routines. + +Copyright (c) 2014, Andras Retzler +Copyright (c) 2020 Hayati Ayguen +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. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +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 ANDRAS RETZLER 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 own header first, to see missing includes */ +#include "pf_mixer.h" +#include "fmv.h" + +#include +#include +#include + +//they dropped M_PI in C99, so we define it: +#define PI ((float)3.14159265358979323846) + +//apply to pointers: +#define iof(complexf_input_p,i) (*(((float*)complexf_input_p)+2*(i))) +#define qof(complexf_input_p,i) (*(((float*)complexf_input_p)+2*(i)+1)) + +#define USE_ALIGNED_ADDRESSES 0 + + + +/* + _____ _____ _____ __ _ _ + | __ \ / ____| __ \ / _| | | (_) + | | | | (___ | |__) | | |_ _ _ _ __ ___| |_ _ ___ _ __ ___ + | | | |\___ \| ___/ | _| | | | '_ \ / __| __| |/ _ \| '_ \/ __| + | |__| |____) | | | | | |_| | | | | (__| |_| | (_) | | | \__ \ + |_____/|_____/|_| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ + +*/ + + +#if defined(__GNUC__) +# define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline)) +# define RESTRICT __restrict +#elif defined(_MSC_VER) +# define ALWAYS_INLINE(return_type) __forceinline return_type +# define RESTRICT __restrict +#endif + + +#ifndef PFFFT_SIMD_DISABLE +#if (defined(__x86_64__) || defined(_M_X64) || defined(i386) || defined(_M_IX86)) + #pragma message("Manual SSE x86/x64 optimizations are ON") + #include + #define HAVE_SSE_INTRINSICS 1 + +#elif defined(PFFFT_ENABLE_NEON) && defined(__arm__) + #pragma message "Manual NEON (arm32) optimizations are ON" + #include "sse2neon.h" + #define HAVE_SSE_INTRINSICS 1 + +#elif defined(PFFFT_ENABLE_NEON) && defined(__aarch64__) + #pragma message "Manual NEON (aarch64) optimizations are ON" + #include "sse2neon.h" + #define HAVE_SSE_INTRINSICS 1 + +#endif +#endif + +#ifdef HAVE_SSE_INTRINSICS + +typedef __m128 v4sf; +# define SIMD_SZ 4 + +typedef union v4_union { + __m128 v; + float f[4]; +} v4_union; + +#define VMUL(a,b) _mm_mul_ps(a,b) +#define VDIV(a,b) _mm_div_ps(a,b) +#define VADD(a,b) _mm_add_ps(a,b) +#define VSUB(a,b) _mm_sub_ps(a,b) +#define LD_PS1(s) _mm_set1_ps(s) +#define VLOAD_UNALIGNED(ptr) _mm_loadu_ps((const float *)(ptr)) +#define VLOAD_ALIGNED(ptr) _mm_load_ps((const float *)(ptr)) +#define VSTORE_UNALIGNED(ptr, v) _mm_storeu_ps((float*)(ptr), v) +#define VSTORE_ALIGNED(ptr, v) _mm_store_ps((float*)(ptr), v) +#define INTERLEAVE2(in1, in2, out1, out2) { __m128 tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; } +#define UNINTERLEAVE2(in1, in2, out1, out2) { __m128 tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; } + +#if USE_ALIGNED_ADDRESSES + #define VLOAD(ptr) _mm_load_ps((const float *)(ptr)) + #define VSTORE(ptr, v) _mm_store_ps((float*)(ptr), v) +#else + #define VLOAD(ptr) _mm_loadu_ps((const float *)(ptr)) + #define VSTORE(ptr, v) _mm_storeu_ps((float*)(ptr), v) +#endif + + +int have_sse_shift_mixer_impl() +{ + return 1; +} + +#else + +int have_sse_shift_mixer_impl() +{ + return 0; +} + +#endif + + +/*********************************************************************/ + +/**************/ +/*** ALGO A ***/ +/**************/ + +PF_TARGET_CLONES +float shift_math_cc(complexf *input, complexf* output, int input_size, float rate, float starting_phase) +{ + rate*=2; + //Shifts the complex spectrum. Basically a complex mixer. This version uses cmath. + float phase=starting_phase; + float phase_increment=rate*PI; + float cosval, sinval; + for(int i=0;i2*PI) phase-=2*PI; //@shift_math_cc: normalize phase + while(phase<0) phase+=2*PI; + } + return phase; +} + +/*********************************************************************/ + +/**************/ +/*** ALGO B ***/ +/**************/ + +shift_table_data_t shift_table_init(int table_size) +{ + shift_table_data_t output; + output.table=(float*)malloc(sizeof(float)*table_size); + output.table_size=table_size; + for(int i=0;i1)?-1:1; //in quadrant 2 and 3 + cos_sign=(quadrant&&quadrant<3)?-1:1; //in quadrant 1 and 2 + sinval=sin_sign*table_data.table[sin_index]; + cosval=cos_sign*table_data.table[cos_index]; + //we multiply two complex numbers. + //how? enter this to maxima (software) for explanation: + // (a+b*%i)*(c+d*%i), rectform; + iof(output,i)=cosval*iof(input,i)-sinval*qof(input,i); + qof(output,i)=sinval*iof(input,i)+cosval*qof(input,i); + phase+=phase_increment; + while(phase>2*PI) phase-=2*PI; //@shift_math_cc: normalize phase + while(phase<0) phase+=2*PI; + } + return phase; +} + +/*********************************************************************/ + +/**************/ +/*** ALGO C ***/ +/**************/ + +shift_addfast_data_t shift_addfast_init(float rate) +{ + shift_addfast_data_t output; + output.phase_increment=2*rate*PI; + for(int i=0;i<4;i++) + { + output.dsin[i]=sinf(output.phase_increment*(i+1)); + output.dcos[i]=cosf(output.phase_increment*(i+1)); + } + return output; +} + +#define SADF_L1(j) \ + cos_vals_ ## j = cos_start * dcos_ ## j - sin_start * dsin_ ## j; \ + sin_vals_ ## j = sin_start * dcos_ ## j + cos_start * dsin_ ## j; +#define SADF_L2(j) \ + iof(output,4*i+j)=(cos_vals_ ## j)*iof(input,4*i+j)-(sin_vals_ ## j)*qof(input,4*i+j); \ + qof(output,4*i+j)=(sin_vals_ ## j)*iof(input,4*i+j)+(cos_vals_ ## j)*qof(input,4*i+j); + +PF_TARGET_CLONES +float shift_addfast_cc(complexf *input, complexf* output, int input_size, shift_addfast_data_t* d, float starting_phase) +{ + //input_size should be multiple of 4 + //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); + float cos_start=cosf(starting_phase); + float sin_start=sinf(starting_phase); + float cos_vals_0, cos_vals_1, cos_vals_2, cos_vals_3, + sin_vals_0, sin_vals_1, sin_vals_2, sin_vals_3, + dsin_0 = d->dsin[0], dsin_1 = d->dsin[1], dsin_2 = d->dsin[2], dsin_3 = d->dsin[3], + dcos_0 = d->dcos[0], dcos_1 = d->dcos[1], dcos_2 = d->dcos[2], dcos_3 = d->dcos[3]; + + for(int i=0;iphase_increment; + while(starting_phase>PI) starting_phase-=2*PI; + while(starting_phase<-PI) starting_phase+=2*PI; + return starting_phase; +} + +#undef SADF_L2 + + +#define SADF_L2(j) \ + tmp_inp_cos = iof(in_out,4*i+j); \ + tmp_inp_sin = qof(in_out,4*i+j); \ + iof(in_out,4*i+j)=(cos_vals_ ## j)*tmp_inp_cos - (sin_vals_ ## j)*tmp_inp_sin; \ + qof(in_out,4*i+j)=(sin_vals_ ## j)*tmp_inp_cos + (cos_vals_ ## j)*tmp_inp_sin; + +PF_TARGET_CLONES +float shift_addfast_inp_c(complexf *in_out, int N_cplx, shift_addfast_data_t* d, float starting_phase) +{ + //input_size should be multiple of 4 + //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); + float cos_start=cosf(starting_phase); + float sin_start=sinf(starting_phase); + float tmp_inp_cos, tmp_inp_sin, + cos_vals_0, cos_vals_1, cos_vals_2, cos_vals_3, + sin_vals_0, sin_vals_1, sin_vals_2, sin_vals_3, + dsin_0 = d->dsin[0], dsin_1 = d->dsin[1], dsin_2 = d->dsin[2], dsin_3 = d->dsin[3], + dcos_0 = d->dcos[0], dcos_1 = d->dcos[1], dcos_2 = d->dcos[2], dcos_3 = d->dcos[3]; + + for(int i=0;iphase_increment; + while(starting_phase>PI) starting_phase-=2*PI; + while(starting_phase<-PI) starting_phase+=2*PI; + return starting_phase; +} + +#undef SADF_L1 +#undef SADF_L2 + + +/*********************************************************************/ + +/**************/ +/*** ALGO D ***/ +/**************/ + +shift_unroll_data_t shift_unroll_init(float rate, int size) +{ + shift_unroll_data_t output; + output.phase_increment=2*rate*PI; + output.size = size; + output.dsin=(float*)malloc(sizeof(float)*size); + output.dcos=(float*)malloc(sizeof(float)*size); + float myphase = 0; + for(int i=0;iPI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + output.dsin[i]=sinf(myphase); + output.dcos[i]=cosf(myphase); + } + return output; +} + +void shift_unroll_deinit(shift_unroll_data_t* d) +{ + if (!d) + return; + free(d->dsin); + free(d->dcos); + d->dsin = NULL; + d->dcos = NULL; +} + +PF_TARGET_CLONES +float shift_unroll_cc(complexf *input, complexf* output, int input_size, shift_unroll_data_t* d, float starting_phase) +{ + //input_size should be multiple of 4 + //fprintf(stderr, "shift_addfast_cc: input_size = %d\n", input_size); + float cos_start = cosf(starting_phase); + float sin_start = sinf(starting_phase); + float cos_val = cos_start, sin_val = sin_start; + for(int i=0;idcos[i] - sin_start * d->dsin[i]; + sin_val = sin_start * d->dcos[i] + cos_start * d->dsin[i]; + } + starting_phase+=input_size*d->phase_increment; + while(starting_phase>PI) starting_phase-=2*PI; + while(starting_phase<-PI) starting_phase+=2*PI; + return starting_phase; +} + +PF_TARGET_CLONES +float shift_unroll_inp_c(complexf* in_out, int size, shift_unroll_data_t* d, float starting_phase) +{ + float cos_start = cosf(starting_phase); + float sin_start = sinf(starting_phase); + float cos_val = cos_start, sin_val = sin_start; + for(int i=0;idcos[i] - sin_start * d->dsin[i]; + sin_val = sin_start * d->dcos[i] + cos_start * d->dsin[i]; + } + starting_phase += size * d->phase_increment; + while(starting_phase>PI) starting_phase-=2*PI; + while(starting_phase<-PI) starting_phase+=2*PI; + return starting_phase; +} + + +/*********************************************************************/ + +/**************/ +/*** ALGO E ***/ +/**************/ + +shift_limited_unroll_data_t shift_limited_unroll_init(float rate) +{ + shift_limited_unroll_data_t output; + output.phase_increment=2*rate*PI; + float myphase = 0; + for(int i=0; i < PF_SHIFT_LIMITED_UNROLL_SIZE; i++) + { + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + output.dcos[i] = cosf(myphase); + output.dsin[i] = sinf(myphase); + } + output.complex_phase.i = 1.0F; + output.complex_phase.q = 0.0F; + return output; +} + +PF_TARGET_CLONES +void shift_limited_unroll_cc(const complexf *input, complexf* output, int size, shift_limited_unroll_data_t* d) +{ + float cos_start = d->complex_phase.i; + float sin_start = d->complex_phase.q; + float cos_val = cos_start, sin_val = sin_start, mag; + while (size > 0) + { + int N = (size >= PF_SHIFT_LIMITED_UNROLL_SIZE) ? PF_SHIFT_LIMITED_UNROLL_SIZE : size; + for(int i=0;idcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] - sin_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + sin_val = sin_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] + cos_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + } + } + // "starts := vals := vals / |vals|" + mag = sqrtf(cos_val * cos_val + sin_val * sin_val); + cos_val /= mag; + sin_val /= mag; + cos_start = cos_val; + sin_start = sin_val; + + input += PF_SHIFT_LIMITED_UNROLL_SIZE; + output += PF_SHIFT_LIMITED_UNROLL_SIZE; + size -= PF_SHIFT_LIMITED_UNROLL_SIZE; + } + d->complex_phase.i = cos_val; + d->complex_phase.q = sin_val; +} + +PF_TARGET_CLONES +void shift_limited_unroll_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_data_t* d) +{ + float inp_i[PF_SHIFT_LIMITED_SIMD_SZ]; + float inp_q[PF_SHIFT_LIMITED_SIMD_SZ]; + // "vals := starts := phase_state" + float cos_start = d->complex_phase.i; + float sin_start = d->complex_phase.q; + float cos_val = cos_start, sin_val = sin_start, mag; + while (N_cplx) + { + int N = (N_cplx >= PF_SHIFT_LIMITED_UNROLL_SIZE) ? PF_SHIFT_LIMITED_UNROLL_SIZE : N_cplx; + for(int i=0;idcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] - sin_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + sin_val = sin_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] + cos_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + } + } + // "starts := vals := vals / |vals|" + mag = sqrtf(cos_val * cos_val + sin_val * sin_val); + cos_val /= mag; + sin_val /= mag; + cos_start = cos_val; + sin_start = sin_val; + + in_out += PF_SHIFT_LIMITED_UNROLL_SIZE; + N_cplx -= PF_SHIFT_LIMITED_UNROLL_SIZE; + } + // "phase_state := starts" + d->complex_phase.i = cos_start; + d->complex_phase.q = sin_start; +} + + +#ifdef HAVE_SSE_INTRINSICS + +/*********************************************************************/ + +/**************/ +/*** ALGO F ***/ +/**************/ + +shift_limited_unroll_A_sse_data_t shift_limited_unroll_A_sse_init(float relative_freq, float phase_start_rad) +{ + shift_limited_unroll_A_sse_data_t output; + float myphase; + + output.phase_increment = 2*relative_freq*PI; + + myphase = 0.0F; + for (int i = 0; i < PF_SHIFT_LIMITED_UNROLL_SIZE + PF_SHIFT_LIMITED_SIMD_SZ; i += PF_SHIFT_LIMITED_SIMD_SZ) + { + for (int k = 0; k < PF_SHIFT_LIMITED_SIMD_SZ; k++) + { + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + output.dcos[i] = cosf(myphase); + output.dsin[i] = sinf(myphase); + for (int k = 1; k < PF_SHIFT_LIMITED_SIMD_SZ; k++) + { + output.dcos[i+k] = output.dcos[i]; + output.dsin[i+k] = output.dsin[i]; + } + } + + output.dcos_blk = 0.0F; + output.dsin_blk = 0.0F; + + myphase = phase_start_rad; + for (int i = 0; i < PF_SHIFT_LIMITED_SIMD_SZ; i++) + { + output.phase_state_i[i] = cosf(myphase); + output.phase_state_q[i] = sinf(myphase); + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + return output; +} + + +PF_TARGET_CLONES +void shift_limited_unroll_A_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_A_sse_data_t* d) +{ + // "vals := starts := phase_state" + __m128 cos_starts = VLOAD( &d->phase_state_i[0] ); + __m128 sin_starts = VLOAD( &d->phase_state_q[0] ); + __m128 cos_vals = cos_starts; + __m128 sin_vals = sin_starts; + __m128 inp_re, inp_im; + __m128 product_re, product_im; + __m128 interl_prod_a, interl_prod_b; + __m128 * RESTRICT p_trig_cos_tab; + __m128 * RESTRICT p_trig_sin_tab; + __m128 * RESTRICT u = (__m128*)in_out; + + while (N_cplx) + { + const int NB = (N_cplx >= PF_SHIFT_LIMITED_UNROLL_SIZE) ? PF_SHIFT_LIMITED_UNROLL_SIZE : N_cplx; + int B = NB; + p_trig_cos_tab = (__m128*)( &d->dcos[0] ); + p_trig_sin_tab = (__m128*)( &d->dsin[0] ); + while (B) + { + // complex multiplication of 4 complex values from/to in_out[] + // == u[0..3] *= (cos_val[0..3] + i * sin_val[0..3]): + // "out[] = inp[] * vals" + UNINTERLEAVE2(VLOAD(u), VLOAD(u+1), inp_re, inp_im); /* inp_re = all reals; inp_im = all imags */ + product_re = VSUB( VMUL(inp_re, cos_vals), VMUL(inp_im, sin_vals) ); + product_im = VADD( VMUL(inp_im, cos_vals), VMUL(inp_re, sin_vals) ); + INTERLEAVE2( product_re, product_im, interl_prod_a, interl_prod_b); + VSTORE(u, interl_prod_a); + VSTORE(u+1, interl_prod_b); + u += 2; + // calculate complex phasor for next iteration + // cos_val = cos_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] - sin_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // sin_val = sin_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] + cos_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // cos_val[]/sin_val[] .. can't fade towards 0 inside this while loop :-) + // "vals := d[] * starts" + inp_re = VLOAD(p_trig_cos_tab); + inp_im = VLOAD(p_trig_sin_tab); + cos_vals = VSUB( VMUL(inp_re, cos_starts), VMUL(inp_im, sin_starts) ); + sin_vals = VADD( VMUL(inp_im, cos_starts), VMUL(inp_re, sin_starts) ); + ++p_trig_cos_tab; + ++p_trig_sin_tab; + B -= 4; + } + N_cplx -= NB; + /* normalize d->phase_state_i[]/d->phase_state_q[], that magnitude does not fade towards 0 ! */ + /* re-use product_re[]/product_im[] for normalization */ + // "starts := vals := vals / |vals|" + product_re = VADD( VMUL(cos_vals, cos_vals), VMUL(sin_vals, sin_vals) ); +#if 0 + // more spikes in spectrum! at PF_SHIFT_LIMITED_UNROLL_SIZE = 64 + // higher spikes in spectrum at PF_SHIFT_LIMITED_UNROLL_SIZE = 16 + product_im = _mm_rsqrt_ps(product_re); + cos_starts = cos_vals = VMUL(cos_vals, product_im); + sin_starts = sin_vals = VMUL(sin_vals, product_im); +#else + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 64 - but slower! + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 128 - fast again + product_im = _mm_sqrt_ps(product_re); + cos_starts = cos_vals = VDIV(cos_vals, product_im); + sin_starts = sin_vals = VDIV(sin_vals, product_im); +#endif + } + // "phase_state := starts" + VSTORE( &d->phase_state_i[0], cos_starts ); + VSTORE( &d->phase_state_q[0], sin_starts ); +} + + +/*********************************************************************/ + +/**************/ +/*** ALGO G ***/ +/**************/ + +shift_limited_unroll_B_sse_data_t shift_limited_unroll_B_sse_init(float relative_freq, float phase_start_rad) +{ + shift_limited_unroll_B_sse_data_t output; + float myphase; + + output.phase_increment = 2*relative_freq*PI; + + myphase = 0.0F; + for (int i = 0; i < PF_SHIFT_LIMITED_UNROLL_SIZE + PF_SHIFT_LIMITED_SIMD_SZ; i += PF_SHIFT_LIMITED_SIMD_SZ) + { + for (int k = 0; k < PF_SHIFT_LIMITED_SIMD_SZ; k++) + { + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + output.dtrig[i+0] = cosf(myphase); + output.dtrig[i+1] = sinf(myphase); + output.dtrig[i+2] = output.dtrig[i+0]; + output.dtrig[i+3] = output.dtrig[i+1]; + } + + output.dcos_blk = 0.0F; + output.dsin_blk = 0.0F; + + myphase = phase_start_rad; + for (int i = 0; i < PF_SHIFT_LIMITED_SIMD_SZ; i++) + { + output.phase_state_i[i] = cosf(myphase); + output.phase_state_q[i] = sinf(myphase); + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + return output; +} + + +PF_TARGET_CLONES +void shift_limited_unroll_B_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_B_sse_data_t* d) +{ + // "vals := starts := phase_state" + __m128 cos_starts = VLOAD( &d->phase_state_i[0] ); + __m128 sin_starts = VLOAD( &d->phase_state_q[0] ); + __m128 cos_vals = cos_starts; + __m128 sin_vals = sin_starts; + __m128 inp_re, inp_im; + __m128 product_re, product_im; + __m128 interl_prod_a, interl_prod_b; + __m128 * RESTRICT p_trig_tab; + __m128 * RESTRICT u = (__m128*)in_out; + + while (N_cplx) + { + const int NB = (N_cplx >= PF_SHIFT_LIMITED_UNROLL_SIZE) ? PF_SHIFT_LIMITED_UNROLL_SIZE : N_cplx; + int B = NB; + p_trig_tab = (__m128*)( &d->dtrig[0] ); + while (B) + { + // complex multiplication of 4 complex values from/to in_out[] + // == u[0..3] *= (cos_val[0..3] + i * sin_val[0..3]): + // "out[] = inp[] * vals" + UNINTERLEAVE2(VLOAD(u), VLOAD(u+1), inp_re, inp_im); /* inp_re = all reals; inp_im = all imags */ + product_re = VSUB( VMUL(inp_re, cos_vals), VMUL(inp_im, sin_vals) ); + product_im = VADD( VMUL(inp_im, cos_vals), VMUL(inp_re, sin_vals) ); + INTERLEAVE2( product_re, product_im, interl_prod_a, interl_prod_b); + VSTORE(u, interl_prod_a); + VSTORE(u+1, interl_prod_b); + u += 2; + // calculate complex phasor for next iteration + // cos_val = cos_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] - sin_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // sin_val = sin_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] + cos_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // cos_val[]/sin_val[] .. can't fade towards 0 inside this while loop :-) + // "vals := d[] * starts" + product_re = VLOAD(p_trig_tab); + UNINTERLEAVE2(product_re, product_re, inp_re, inp_im); /* inp_re = all reals; inp_im = all imags */ + cos_vals = VSUB( VMUL(inp_re, cos_starts), VMUL(inp_im, sin_starts) ); + sin_vals = VADD( VMUL(inp_im, cos_starts), VMUL(inp_re, sin_starts) ); + ++p_trig_tab; + B -= 4; + } + N_cplx -= NB; + /* normalize d->phase_state_i[]/d->phase_state_q[], that magnitude does not fade towards 0 ! */ + /* re-use product_re[]/product_im[] for normalization */ + // "starts := vals := vals / |vals|" + product_re = VADD( VMUL(cos_vals, cos_vals), VMUL(sin_vals, sin_vals) ); +#if 0 + // more spikes in spectrum! at PF_SHIFT_LIMITED_UNROLL_SIZE = 64 + // higher spikes in spectrum at PF_SHIFT_LIMITED_UNROLL_SIZE = 16 + product_im = _mm_rsqrt_ps(product_re); + cos_starts = cos_vals = VMUL(cos_vals, product_im); + sin_starts = sin_vals = VMUL(sin_vals, product_im); +#else + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 64 - but slower! + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 128 - fast again + product_im = _mm_sqrt_ps(product_re); + cos_starts = cos_vals = VDIV(cos_vals, product_im); + sin_starts = sin_vals = VDIV(sin_vals, product_im); +#endif + } + // "phase_state := starts" + VSTORE( &d->phase_state_i[0], cos_starts ); + VSTORE( &d->phase_state_q[0], sin_starts ); +} + + +/*********************************************************************/ + + +/**************/ +/*** ALGO H ***/ +/**************/ + +shift_limited_unroll_C_sse_data_t shift_limited_unroll_C_sse_init(float relative_freq, float phase_start_rad) +{ + shift_limited_unroll_C_sse_data_t output; + float myphase; + + output.phase_increment = 2*relative_freq*PI; + + myphase = 0.0F; + for (int i = 0; i < PF_SHIFT_LIMITED_UNROLL_SIZE + PF_SHIFT_LIMITED_SIMD_SZ; i += PF_SHIFT_LIMITED_SIMD_SZ) + { + for (int k = 0; k < PF_SHIFT_LIMITED_SIMD_SZ; k++) + { + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + output.dinterl_trig[2*i] = cosf(myphase); + output.dinterl_trig[2*i+4] = sinf(myphase); + for (int k = 1; k < PF_SHIFT_LIMITED_SIMD_SZ; k++) + { + output.dinterl_trig[2*i+k] = output.dinterl_trig[2*i]; + output.dinterl_trig[2*i+k+4] = output.dinterl_trig[2*i+4]; + } + } + + output.dcos_blk = 0.0F; + output.dsin_blk = 0.0F; + + myphase = phase_start_rad; + for (int i = 0; i < PF_SHIFT_LIMITED_SIMD_SZ; i++) + { + output.phase_state_i[i] = cosf(myphase); + output.phase_state_q[i] = sinf(myphase); + myphase += output.phase_increment; + while(myphase>PI) myphase-=2*PI; + while(myphase<-PI) myphase+=2*PI; + } + return output; +} + + +PF_TARGET_CLONES +void shift_limited_unroll_C_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_C_sse_data_t* d) +{ + // "vals := starts := phase_state" + __m128 cos_starts = VLOAD( &d->phase_state_i[0] ); + __m128 sin_starts = VLOAD( &d->phase_state_q[0] ); + __m128 cos_vals = cos_starts; + __m128 sin_vals = sin_starts; + __m128 inp_re, inp_im; + __m128 product_re, product_im; + __m128 interl_prod_a, interl_prod_b; + __m128 * RESTRICT p_trig_tab; + __m128 * RESTRICT u = (__m128*)in_out; + + while (N_cplx) + { + const int NB = (N_cplx >= PF_SHIFT_LIMITED_UNROLL_SIZE) ? PF_SHIFT_LIMITED_UNROLL_SIZE : N_cplx; + int B = NB; + p_trig_tab = (__m128*)( &d->dinterl_trig[0] ); + while (B) + { + // complex multiplication of 4 complex values from/to in_out[] + // == u[0..3] *= (cos_val[0..3] + i * sin_val[0..3]): + // "out[] = inp[] * vals" + UNINTERLEAVE2(VLOAD(u), VLOAD(u+1), inp_re, inp_im); /* inp_re = all reals; inp_im = all imags */ + product_re = VSUB( VMUL(inp_re, cos_vals), VMUL(inp_im, sin_vals) ); + product_im = VADD( VMUL(inp_im, cos_vals), VMUL(inp_re, sin_vals) ); + INTERLEAVE2( product_re, product_im, interl_prod_a, interl_prod_b); + VSTORE(u, interl_prod_a); + VSTORE(u+1, interl_prod_b); + u += 2; + // calculate complex phasor for next iteration + // cos_val = cos_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] - sin_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // sin_val = sin_start * d->dcos[PF_SHIFT_LIMITED_SIMD_SZ*i+j] + cos_start * d->dsin[PF_SHIFT_LIMITED_SIMD_SZ*i+j]; + // cos_val[]/sin_val[] .. can't fade towards 0 inside this while loop :-) + // "vals := d[] * starts" + inp_re = VLOAD(p_trig_tab); + inp_im = VLOAD(p_trig_tab+1); + cos_vals = VSUB( VMUL(inp_re, cos_starts), VMUL(inp_im, sin_starts) ); + sin_vals = VADD( VMUL(inp_im, cos_starts), VMUL(inp_re, sin_starts) ); + p_trig_tab += 2; + B -= 4; + } + N_cplx -= NB; + /* normalize d->phase_state_i[]/d->phase_state_q[], that magnitude does not fade towards 0 ! */ + /* re-use product_re[]/product_im[] for normalization */ + // "starts := vals := vals / |vals|" + product_re = VADD( VMUL(cos_vals, cos_vals), VMUL(sin_vals, sin_vals) ); +#if 0 + // more spikes in spectrum! at PF_SHIFT_LIMITED_UNROLL_SIZE = 64 + // higher spikes in spectrum at PF_SHIFT_LIMITED_UNROLL_SIZE = 16 + product_im = _mm_rsqrt_ps(product_re); + cos_starts = cos_vals = VMUL(cos_vals, product_im); + sin_starts = sin_vals = VMUL(sin_vals, product_im); +#else + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 64 - but slower! + // spectrally comparable to shift_match_cc() with PF_SHIFT_LIMITED_UNROLL_SIZE = 128 - fast again + product_im = _mm_sqrt_ps(product_re); + cos_starts = cos_vals = VDIV(cos_vals, product_im); + sin_starts = sin_vals = VDIV(sin_vals, product_im); +#endif + } + // "phase_state := starts" + VSTORE( &d->phase_state_i[0], cos_starts ); + VSTORE( &d->phase_state_q[0], sin_starts ); +} + + +#else + +/*********************************************************************/ + +shift_limited_unroll_A_sse_data_t shift_limited_unroll_A_sse_init(float relative_freq, float phase_start_rad) { + assert(0); + shift_limited_unroll_A_sse_data_t r; + return r; +} +shift_limited_unroll_B_sse_data_t shift_limited_unroll_B_sse_init(float relative_freq, float phase_start_rad) { + assert(0); + shift_limited_unroll_B_sse_data_t r; + return r; +} +shift_limited_unroll_C_sse_data_t shift_limited_unroll_C_sse_init(float relative_freq, float phase_start_rad) { + assert(0); + shift_limited_unroll_C_sse_data_t r; + return r; +} + +void shift_limited_unroll_A_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_A_sse_data_t* d) { + assert(0); +} +void shift_limited_unroll_B_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_B_sse_data_t* d) { + assert(0); +} +void shift_limited_unroll_C_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_C_sse_data_t* d) { + assert(0); +} + +#endif + + +/*********************************************************************/ + +/**************/ +/*** ALGO I ***/ +/**************/ + +void shift_recursive_osc_update_rate(float rate, shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state) +{ + // constants for single phase step + float phase_increment_s = rate*PI; + float k1 = tanf(0.5f*phase_increment_s); + float k2 = 2*k1 /(1 + k1 * k1); + for (int j=1; ju_cos[j] = state->u_cos[j-1]; + state->v_sin[j] = state->v_sin[j-1]; + // small steps + tmp = state->u_cos[j] - k1 * state->v_sin[j]; + state->v_sin[j] += k2 * tmp; + state->u_cos[j] = tmp - k1 * state->v_sin[j]; + } + + // constants for PF_SHIFT_RECURSIVE_SIMD_SZ times phase step + float phase_increment_b = phase_increment_s * PF_SHIFT_RECURSIVE_SIMD_SZ; + while(phase_increment_b > PI) phase_increment_b-=2*PI; + while(phase_increment_b < -PI) phase_increment_b+=2*PI; + conf->k1 = tanf(0.5f*phase_increment_b); + conf->k2 = 2*conf->k1 / (1 + conf->k1 * conf->k1); +} + +void shift_recursive_osc_init(float rate, float starting_phase, shift_recursive_osc_conf_t *conf, shift_recursive_osc_t *state) +{ + if (starting_phase != 0.0F) + { + state->u_cos[0] = cosf(starting_phase); + state->v_sin[0] = sinf(starting_phase); + } + else + { + state->u_cos[0] = 1.0F; + state->v_sin[0] = 0.0F; + } + shift_recursive_osc_update_rate(rate, conf, state); +} + + +PF_TARGET_CLONES +void shift_recursive_osc_cc(const complexf *input, complexf* output, + int size, const shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state_ext) +{ + float tmp[PF_SHIFT_RECURSIVE_SIMD_SZ]; + float inp_i[PF_SHIFT_RECURSIVE_SIMD_SZ]; + float inp_q[PF_SHIFT_RECURSIVE_SIMD_SZ]; + shift_recursive_osc_t state = *state_ext; + const float k1 = conf->k1; + const float k2 = conf->k2; + for(int i=0;ik1; + const float k2 = conf->k2; + for(int i=0;ik1; + const float k2 = conf->k2; + for(int i=0;iu_cos[j] = state->u_cos[j-1]; + state->v_sin[j] = state->v_sin[j-1]; + // small steps + tmp = state->u_cos[j] - k1 * state->v_sin[j]; + state->v_sin[j] += k2 * tmp; + state->u_cos[j] = tmp - k1 * state->v_sin[j]; + } + + // constants for PF_SHIFT_RECURSIVE_SIMD_SSE_SZ times phase step + float phase_increment_b = phase_increment_s * PF_SHIFT_RECURSIVE_SIMD_SSE_SZ; + while(phase_increment_b > PI) phase_increment_b-=2*PI; + while(phase_increment_b < -PI) phase_increment_b+=2*PI; + conf->k1 = tanf(0.5f*phase_increment_b); + conf->k2 = 2*conf->k1 / (1 + conf->k1 * conf->k1); +} + + +void shift_recursive_osc_sse_init(float rate, float starting_phase, shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t *state) +{ + if (starting_phase != 0.0F) + { + state->u_cos[0] = cosf(starting_phase); + state->v_sin[0] = sinf(starting_phase); + } + else + { + state->u_cos[0] = 1.0F; + state->v_sin[0] = 0.0F; + } + shift_recursive_osc_sse_update_rate(rate, conf, state); +} + + +PF_TARGET_CLONES +void shift_recursive_osc_sse_inp_c(complexf* in_out, + int N_cplx, const shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t* state_ext) +{ + const __m128 k1 = LD_PS1( conf->k1 ); + const __m128 k2 = LD_PS1( conf->k2 ); + __m128 u_cos = VLOAD( &state_ext->u_cos[0] ); + __m128 v_sin = VLOAD( &state_ext->v_sin[0] ); + __m128 inp_re, inp_im; + __m128 product_re, product_im; + __m128 interl_prod_a, interl_prod_b; + __m128 * RESTRICT u = (__m128*)in_out; + + while (N_cplx) + { + //inp_i[j] = in_out[PF_SHIFT_RECURSIVE_SIMD_SSE_SZ*i+j].i; + //inp_q[j] = in_out[PF_SHIFT_RECURSIVE_SIMD_SSE_SZ*i+j].q; + UNINTERLEAVE2(VLOAD(u), VLOAD(u+1), inp_re, inp_im); /* inp_re = all reals; inp_im = all imags */ + + //we multiply two complex numbers - similar to shift_math_cc + //iof(in_out,PF_SHIFT_RECURSIVE_SIMD_SSE_SZ*i+j) = state.u_cos[j] * inp_i[j] - state.v_sin[j] * inp_q[j]; + //qof(in_out,PF_SHIFT_RECURSIVE_SIMD_SSE_SZ*i+j) = state.v_sin[j] * inp_i[j] + state.u_cos[j] * inp_q[j]; + product_re = VSUB( VMUL(inp_re, u_cos), VMUL(inp_im, v_sin) ); + product_im = VADD( VMUL(inp_im, u_cos), VMUL(inp_re, v_sin) ); + INTERLEAVE2( product_re, product_im, interl_prod_a, interl_prod_b); + VSTORE(u, interl_prod_a); + VSTORE(u+1, interl_prod_b); + u += 2; + + // update complex phasor - like incrementing phase + // tmp[j] = state.u_cos[j] - k1 * state.v_sin[j]; + product_re = VSUB( u_cos, VMUL(k1, v_sin) ); + // state.v_sin[j] += k2 * tmp[j]; + v_sin = VADD( v_sin, VMUL(k2, product_re) ); + // state.u_cos[j] = tmp[j] - k1 * state.v_sin[j]; + u_cos = VSUB( product_re, VMUL(k1, v_sin) ); + + N_cplx -= 4; + } + VSTORE( &state_ext->u_cos[0], u_cos ); + VSTORE( &state_ext->v_sin[0], v_sin ); +} + +#else + +void shift_recursive_osc_sse_update_rate(float rate, shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t* state) +{ + assert(0); +} + +void shift_recursive_osc_sse_init(float rate, float starting_phase, shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t *state) +{ + assert(0); +} + + +void shift_recursive_osc_sse_inp_c(complexf* in_out, + int N_cplx, const shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t* state_ext) +{ + assert(0); +} + +#endif + diff --git a/sddc_source/src/libsddc/Core/pffft/pf_mixer.h b/sddc_source/src/libsddc/Core/pffft/pf_mixer.h new file mode 100644 index 0000000..e4987a4 --- /dev/null +++ b/sddc_source/src/libsddc/Core/pffft/pf_mixer.h @@ -0,0 +1,283 @@ +/* +This software is part of pffft/pfdsp, a set of simple DSP routines. + +Copyright (c) 2014, Andras Retzler +Copyright (c) 2020 Hayati Ayguen +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. + * Neither the name of the copyright holder nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +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 ANDRAS RETZLER 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. +*/ + +#ifndef _PF_MIXER_H_ +#define _PF_MIXER_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + _____ _ + / ____| | | + | | ___ _ __ ___ _ __ | | _____ __ + | | / _ \| '_ ` _ \| '_ \| |/ _ \ \/ / + | |___| (_) | | | | | | |_) | | __/> < + \_____\___/|_| |_| |_| .__/|_|\___/_/\_\ + | | + |_| +*/ + +typedef struct complexf_s { float i; float q; } complexf; + +// ================================================================================= + +int have_sse_shift_mixer_impl(); + + +/*********************************************************************/ + +/**************/ +/*** ALGO A ***/ +/**************/ + +float shift_math_cc(complexf *input, complexf* output, int input_size, float rate, float starting_phase); + + +/*********************************************************************/ + +/**************/ +/*** ALGO B ***/ +/**************/ + +typedef struct shift_table_data_s +{ + float* table; + int table_size; +} shift_table_data_t; + +void shift_table_deinit(shift_table_data_t table_data); +shift_table_data_t shift_table_init(int table_size); +float shift_table_cc(complexf* input, complexf* output, int input_size, float rate, shift_table_data_t table_data, float starting_phase); + +/*********************************************************************/ + +/**************/ +/*** ALGO C ***/ +/**************/ + +typedef struct shift_addfast_data_s +{ + float dsin[4]; + float dcos[4]; + float phase_increment; +} shift_addfast_data_t; + +shift_addfast_data_t shift_addfast_init(float rate); +float shift_addfast_cc(complexf *input, complexf* output, int input_size, shift_addfast_data_t* d, float starting_phase); +float shift_addfast_inp_c(complexf *in_out, int N_cplx, shift_addfast_data_t* d, float starting_phase); + + +/*********************************************************************/ + +/**************/ +/*** ALGO D ***/ +/**************/ + +typedef struct shift_unroll_data_s +{ + float* dsin; + float* dcos; + float phase_increment; + int size; +} shift_unroll_data_t; + +shift_unroll_data_t shift_unroll_init(float rate, int size); +void shift_unroll_deinit(shift_unroll_data_t* d); +float shift_unroll_cc(complexf *input, complexf* output, int size, shift_unroll_data_t* d, float starting_phase); +float shift_unroll_inp_c(complexf* in_out, int size, shift_unroll_data_t* d, float starting_phase); + + +/*********************************************************************/ + +/**************/ +/*** ALGO E ***/ +/**************/ + +/* similar to shift_unroll_cc() - but, have fixed and limited precalc size + * idea: smaller cache usage by table + * size must be multiple of CSDR_SHIFT_LIMITED_SIMD (= 4) + */ +#define PF_SHIFT_LIMITED_UNROLL_SIZE 128 +#define PF_SHIFT_LIMITED_SIMD_SZ 4 + +typedef struct shift_limited_unroll_data_s +{ + float dcos[PF_SHIFT_LIMITED_UNROLL_SIZE]; + float dsin[PF_SHIFT_LIMITED_UNROLL_SIZE]; + complexf complex_phase; + float phase_increment; +} shift_limited_unroll_data_t; + +shift_limited_unroll_data_t shift_limited_unroll_init(float rate); +/* size must be multiple of PF_SHIFT_LIMITED_SIMD_SZ */ +/* starting_phase for next call is kept internal in state */ +void shift_limited_unroll_cc(const complexf *input, complexf* output, int size, shift_limited_unroll_data_t* d); +void shift_limited_unroll_inp_c(complexf* in_out, int size, shift_limited_unroll_data_t* d); + + +/*********************************************************************/ + +/**************/ +/*** ALGO F ***/ +/**************/ + +typedef struct shift_limited_unroll_A_sse_data_s +{ + /* small/limited trig table */ + float dcos[PF_SHIFT_LIMITED_UNROLL_SIZE+PF_SHIFT_LIMITED_SIMD_SZ]; + float dsin[PF_SHIFT_LIMITED_UNROLL_SIZE+PF_SHIFT_LIMITED_SIMD_SZ]; + /* 4 times complex phase */ + float phase_state_i[PF_SHIFT_LIMITED_SIMD_SZ]; + float phase_state_q[PF_SHIFT_LIMITED_SIMD_SZ]; + /* N_cplx_per_block times increment - for future parallel variants */ + float dcos_blk; + float dsin_blk; + /* */ + float phase_increment; +} shift_limited_unroll_A_sse_data_t; + +shift_limited_unroll_A_sse_data_t shift_limited_unroll_A_sse_init(float relative_freq, float phase_start_rad); +void shift_limited_unroll_A_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_A_sse_data_t* d); + + +/*********************************************************************/ + +/**************/ +/*** ALGO G ***/ +/**************/ + +typedef struct shift_limited_unroll_B_sse_data_s +{ + /* small/limited trig table */ + float dtrig[PF_SHIFT_LIMITED_UNROLL_SIZE+PF_SHIFT_LIMITED_SIMD_SZ]; + /* 4 times complex phase */ + float phase_state_i[PF_SHIFT_LIMITED_SIMD_SZ]; + float phase_state_q[PF_SHIFT_LIMITED_SIMD_SZ]; + /* N_cplx_per_block times increment - for future parallel variants */ + float dcos_blk; + float dsin_blk; + /* */ + float phase_increment; +} shift_limited_unroll_B_sse_data_t; + +shift_limited_unroll_B_sse_data_t shift_limited_unroll_B_sse_init(float relative_freq, float phase_start_rad); +void shift_limited_unroll_B_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_B_sse_data_t* d); + +/*********************************************************************/ + +/**************/ +/*** ALGO H ***/ +/**************/ + +typedef struct shift_limited_unroll_C_sse_data_s +{ + /* small/limited trig table - interleaved: 4 cos, 4 sin, 4 cos, .. */ + float dinterl_trig[2*(PF_SHIFT_LIMITED_UNROLL_SIZE+PF_SHIFT_LIMITED_SIMD_SZ)]; + /* 4 times complex phase */ + float phase_state_i[PF_SHIFT_LIMITED_SIMD_SZ]; + float phase_state_q[PF_SHIFT_LIMITED_SIMD_SZ]; + /* N_cplx_per_block times increment - for future parallel variants */ + float dcos_blk; + float dsin_blk; + /* */ + float phase_increment; +} shift_limited_unroll_C_sse_data_t; + +shift_limited_unroll_C_sse_data_t shift_limited_unroll_C_sse_init(float relative_freq, float phase_start_rad); +void shift_limited_unroll_C_sse_inp_c(complexf* in_out, int N_cplx, shift_limited_unroll_C_sse_data_t* d); + + + +/*********************************************************************/ + +/**************/ +/*** ALGO I ***/ +/**************/ + +/* Recursive Quadrature Oscillator functions "recursive_osc" + * see https://www.vicanek.de/articles/QuadOsc.pdf + */ +#define PF_SHIFT_RECURSIVE_SIMD_SZ 8 +typedef struct shift_recursive_osc_s +{ + float u_cos[PF_SHIFT_RECURSIVE_SIMD_SZ]; + float v_sin[PF_SHIFT_RECURSIVE_SIMD_SZ]; +} shift_recursive_osc_t; + +typedef struct shift_recursive_osc_conf_s +{ + float k1; + float k2; +} shift_recursive_osc_conf_t; + +void shift_recursive_osc_init(float rate, float starting_phase, shift_recursive_osc_conf_t *conf, shift_recursive_osc_t *state); +void shift_recursive_osc_update_rate(float rate, shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state); + +/* size must be multiple of PF_SHIFT_LIMITED_SIMD_SZ */ +/* starting_phase for next call is kept internal in state */ +void shift_recursive_osc_cc(const complexf *input, complexf* output, int size, const shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state); +void shift_recursive_osc_inp_c(complexf* output, int size, const shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state); +void gen_recursive_osc_c(complexf* output, int size, const shift_recursive_osc_conf_t *conf, shift_recursive_osc_t* state); + +/*********************************************************************/ + +/**************/ +/*** ALGO J ***/ +/**************/ + +#define PF_SHIFT_RECURSIVE_SIMD_SSE_SZ 4 +typedef struct shift_recursive_osc_sse_s +{ + float u_cos[PF_SHIFT_RECURSIVE_SIMD_SSE_SZ]; + float v_sin[PF_SHIFT_RECURSIVE_SIMD_SSE_SZ]; +} shift_recursive_osc_sse_t; + +typedef struct shift_recursive_osc_sse_conf_s +{ + float k1; + float k2; +} shift_recursive_osc_sse_conf_t; + +void shift_recursive_osc_sse_init(float rate, float starting_phase, shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t *state); +void shift_recursive_osc_sse_update_rate(float rate, shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t* state); +void shift_recursive_osc_sse_inp_c(complexf* in_out, int N_cplx, const shift_recursive_osc_sse_conf_t *conf, shift_recursive_osc_sse_t* state_ext); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sddc_source/src/libsddc/Core/r2iq.h b/sddc_source/src/libsddc/Core/r2iq.h new file mode 100644 index 0000000..7c5bb87 --- /dev/null +++ b/sddc_source/src/libsddc/Core/r2iq.h @@ -0,0 +1,50 @@ +#ifndef R2IQ_H +#define R2IQ_H +#include "license.txt" + +#define NDECIDX 7 //number of srate + +#include +#include +#include +#include + +#include "dsp/ringbuffer.h" + +struct r2iqThreadArg; + +class r2iqControlClass { +public: + r2iqControlClass(); + virtual ~r2iqControlClass() {} + + int getRatio() {return mratio [mdecimation];} + + void updateRand(bool v) { this->randADC = v; } + bool getRand() const { return this->randADC; } + + void setSideband(bool lsb) { this->sideband = lsb; } + bool getSideband() const { return this->sideband; } + + void setDecimate(int dec) {this->mdecimation = dec; } + + virtual void Init(float gain, ringbuffer* input, ringbuffer* obuffers) {} + virtual void TurnOn() { this->r2iqOn = true; } + virtual void TurnOff(void) { this->r2iqOn = false; } + virtual bool IsOn(void) { return this->r2iqOn; } + virtual void DataReady(void) {} + virtual float setFreqOffset(float offset) { return 0; }; + +protected: + int mdecimation ; // selected decimation ratio + // 64 Msps: 0 => 32Msps, 1=> 16Msps, 2 = 8Msps, 3 = 4Msps, 4 = 2Msps + // 128 Msps: 0 => 64Msps, 1 => 32Msps, 2=> 16Msps, 3 = 8Msps, 4 = 4Msps, 5 = 2Msps + bool r2iqOn; // r2iq on flag + int mratio [NDECIDX]; // ratio + +private: + bool randADC; // randomized ADC output + bool sideband; +}; + +#endif diff --git a/sddc_source/src/libsddc/Core/radio/BBRF103Radio.cpp b/sddc_source/src/libsddc/Core/radio/BBRF103Radio.cpp new file mode 100644 index 0000000..6e47b25 --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/BBRF103Radio.cpp @@ -0,0 +1,145 @@ +#include "RadioHandler.h" + +#define R820T_FREQ (32000000) // R820T reference frequency +#define R820T2_IF_CARRIER (4570000) + +const float BBRF103Radio::steps[BBRF103Radio::step_size] = { + 0.0f, 0.9f, 1.4f, 2.7f, 3.7f, 7.7f, 8.7f, 12.5f, 14.4f, 15.7f, + 16.6f, 19.7f, 20.7f, 22.9f, 25.4f, 28.0f, 29.7f, 32.8f, + 33.8f, 36.4f, 37.2f, 38.6f, 40.2f, 42.1f, 43.4f, 43.9f, + 44.5f, 48.0f, 49.6f +}; + +const float BBRF103Radio::if_steps[BBRF103Radio::if_step_size] = { + -4.7f, -2.1f, 0.5f, 3.5f, 7.7f, 11.2f, 13.6f, 14.9f, 16.3f, 19.5f, 23.1f, 26.5f, 30.0f, 33.7f, 37.2f, 40.8f +}; + +const float BBRF103Radio::hfsteps[3] = { + -20.0f, -10.0f, 0.0f +}; + +BBRF103Radio::BBRF103Radio(fx3class* fx3) + : RadioHardware(fx3) +{ + +} + +void BBRF103Radio::Initialize(uint32_t adc_rate) +{ + this->SampleRate = adc_rate; + Fx3->Control(STARTADC, adc_rate); +} + +rf_mode BBRF103Radio::PrepareLo(uint64_t freq) +{ + if (freq < 10 * 1000) return NOMODE; + if (freq > 1750 * 1000 * 1000) return NOMODE; + + if ( freq >= this->SampleRate / 2) + return VHFMODE; + else + return HFMODE; +} + +bool BBRF103Radio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + { + // switch to VHF Attenna + FX3UnsetGPIO(ATT_SEL0 | ATT_SEL1); + + // Initialize Tuner + return Fx3->Control(TUNERINIT, (uint32_t)R820T_FREQ); + } + + else if (mode == HFMODE ) // (mode == HFMODE || mode == VLFMODE) no more VLFMODE + { + // Stop Tuner + Fx3->Control(TUNERSTDBY); + + // switch to HF Attenna + return FX3SetGPIO(ATT_SEL0 | ATT_SEL1); + } + + return false; +} + +bool BBRF103Radio::UpdateattRF(int att) +{ + if (gpios & (ATT_SEL0 | ATT_SEL1)) { + // this is in HF mode + if (att > 2) att = 2; + if (att < 0) att = 0; + switch (att) + { + case 1: //11 + gpios |= ATT_SEL0 | ATT_SEL1; + break; + case 0: //01 + gpios |= ATT_SEL0; + gpios &= ~ATT_SEL1; + break; + case 2: //10 + default: + gpios |= ATT_SEL1; + gpios &= ~ATT_SEL0; + break; + } + return Fx3->Control(GPIOFX3, gpios); + } + else { + uint16_t index = att; + // this is in VHF mode + return Fx3->SetArgument(R82XX_ATTENUATOR, index); + } +} + +uint64_t BBRF103Radio::TuneLo(uint64_t freq) +{ + if (gpios & (ATT_SEL0 | ATT_SEL1)) { + // this is in HF mode + return 0; + } + else { + // this is in VHF mode + Fx3->Control(TUNERTUNE, freq); + return freq - R820T2_IF_CARRIER; + } +} + +int BBRF103Radio::getRFSteps(const float** steps ) +{ + if (gpios & (ATT_SEL0 | ATT_SEL1)) { + *steps = this->hfsteps; + return 3; + } + else + { + *steps = this->steps; + return step_size; + } +} + +int BBRF103Radio::getIFSteps(const float** steps ) +{ + if (gpios & (ATT_SEL0 | ATT_SEL1)) { + return 0; + } + else + { + *steps = this->if_steps; + return if_step_size; + } +} + +bool BBRF103Radio::UpdateGainIF(int attIndex) +{ + if (gpios & (ATT_SEL0 | ATT_SEL1)) { + // this is in HF mode + return false; + } + else { + // this is in VHF mode + return Fx3->SetArgument(R82XX_VGA, (uint16_t)attIndex); + } +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/radio/HF103Radio.cpp b/sddc_source/src/libsddc/Core/radio/HF103Radio.cpp new file mode 100644 index 0000000..bebd482 --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/HF103Radio.cpp @@ -0,0 +1,50 @@ +#include "RadioHandler.h" + +HF103Radio::HF103Radio(fx3class* fx3) + : RadioHardware(fx3) +{ + // initialize steps + for (uint8_t i = 0 ; i < step_size; i++) { + this->steps[step_size - i - 1] = -( + ((i & 0x01) != 0) * 0.5f + + ((i & 0x02) != 0) * 1.0f + + ((i & 0x04) != 0) * 2.0f + + ((i & 0x08) != 0) * 4.0f + + ((i & 0x010) != 0) * 8.0f + + ((i & 0x020) != 0) * 16.0f + ); + } +} + +rf_mode HF103Radio::PrepareLo(uint64_t freq) +{ + if (freq > 32 * 1000 * 1000) return NOMODE; + + return HFMODE; +} + +bool HF103Radio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + return false; + + return true; +} + +bool HF103Radio::UpdateattRF(int att) +{ + if (att > step_size - 1) att = step_size - 1; + if (att < 0) att = 0; + uint8_t d = step_size - att - 1; + + DbgPrintf("UpdateattRF %f \n", this->steps[att]); + + return Fx3->SetArgument(DAT31_ATT, d); +} + +int HF103Radio::getRFSteps(const float** steps ) +{ + *steps = this->steps; + + return step_size; +} diff --git a/sddc_source/src/libsddc/Core/radio/RX888R2Radio.cpp b/sddc_source/src/libsddc/Core/radio/RX888R2Radio.cpp new file mode 100644 index 0000000..a7d1e93 --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RX888R2Radio.cpp @@ -0,0 +1,178 @@ +#include "RadioHandler.h" + +#define R828D_FREQ (16000000) // R820T reference frequency +#define R828D_IF_CARRIER (4570000) + +#define HIGH_MODE 0x80 +#define LOW_MODE 0x00 + +#define GAIN_SWEET_POINT 18 +#define HIGH_GAIN_RATIO (0.409f) +#define LOW_GAIN_RATIO (0.059f) + +#define MODE HIGH_MODE + +const float RX888R2Radio::vhf_rf_steps[RX888R2Radio::vhf_rf_step_size] = { + 0.0f, 0.9f, 1.4f, 2.7f, 3.7f, 7.7f, 8.7f, 12.5f, 14.4f, 15.7f, + 16.6f, 19.7f, 20.7f, 22.9f, 25.4f, 28.0f, 29.7f, 32.8f, + 33.8f, 36.4f, 37.2f, 38.6f, 40.2f, 42.1f, 43.4f, 43.9f, + 44.5f, 48.0f, 49.6f}; + +const float RX888R2Radio::vhf_if_steps[RX888R2Radio::vhf_if_step_size] = { + -4.7f, -2.1f, 0.5f, 3.5f, 7.7f, 11.2f, 13.6f, 14.9f, 16.3f, 19.5f, 23.1f, 26.5f, 30.0f, 33.7f, 37.2f, 40.8f}; + +RX888R2Radio::RX888R2Radio(fx3class *fx3) + : RadioHardware(fx3) +{ + for (uint8_t i = 0; i < hf_rf_step_size; i++) + { + this->hf_rf_steps[hf_rf_step_size - i - 1] = -( + ((i & 0x01) != 0) * 0.5f + + ((i & 0x02) != 0) * 1.0f + + ((i & 0x04) != 0) * 2.0f + + ((i & 0x08) != 0) * 4.0f + + ((i & 0x010) != 0) * 8.0f + + ((i & 0x020) != 0) * 16.0f); + } + + for (uint8_t i = 0; i < hf_if_step_size; i++) + { + if (i > GAIN_SWEET_POINT) + this->hf_if_steps[i] = 20.0f * log10f(HIGH_GAIN_RATIO * (i - GAIN_SWEET_POINT + 3)); + else + this->hf_if_steps[i] = 20.0f * log10f(LOW_GAIN_RATIO * (i + 1)); + } +} + +void RX888R2Radio::Initialize(uint32_t adc_rate) +{ + SampleRate = adc_rate; + Fx3->Control(STARTADC, adc_rate); +} + +rf_mode RX888R2Radio::PrepareLo(uint64_t freq) +{ + if (freq < 10 * 1000) return NOMODE; + if (freq > 1750 * 1000 * 1000) return NOMODE; + + if ( freq >= this->SampleRate / 2) + return VHFMODE; + else + return HFMODE; +} + +bool RX888R2Radio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + { + // disable HF by set max ATT + UpdateattRF(0); // max att 0 -> -31.5 dB + + // switch to VHF Attenna + FX3SetGPIO(VHF_EN); + + // high gain, 0db + uint8_t gain = 0x80 | 3; + Fx3->SetArgument(AD8340_VGA, gain); + // Enable Tuner reference clock + uint32_t ref = R828D_FREQ; + return Fx3->Control(TUNERINIT, ref); // Initialize Tuner + } + else if (mode == HFMODE) + { + Fx3->Control(TUNERSTDBY); // Stop Tuner + + return FX3UnsetGPIO(VHF_EN); // switch to HF Attenna + } + + return false; +} + +bool RX888R2Radio::UpdateattRF(int att) +{ + if (!(gpios & VHF_EN)) + { + // hf mode + if (att > hf_rf_step_size - 1) + att = hf_rf_step_size - 1; + if (att < 0) + att = 0; + uint8_t d = hf_rf_step_size - att - 1; + + DbgPrintf("UpdateattRF %f \n", this->hf_rf_steps[att]); + + return Fx3->SetArgument(DAT31_ATT, d); + } + else + { + uint16_t index = att; + // this is in VHF mode + return Fx3->SetArgument(R82XX_ATTENUATOR, index); + } +} + +uint64_t RX888R2Radio::TuneLo(uint64_t freq) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + return 0; + } + else + { + // this is in VHF mode + Fx3->Control(TUNERTUNE, freq); + return freq - R828D_IF_CARRIER; + } +} + +int RX888R2Radio::getRFSteps(const float **steps) +{ + if (!(gpios & VHF_EN)) + { + // hf mode + *steps = this->hf_rf_steps; + return hf_rf_step_size; + } + else + { + *steps = this->vhf_rf_steps; + return vhf_rf_step_size; + } +} + +int RX888R2Radio::getIFSteps(const float **steps) +{ + if (!(gpios & VHF_EN)) + { + *steps = this->hf_if_steps; + return hf_if_step_size; + } + else + { + *steps = this->vhf_if_steps; + return vhf_if_step_size; + } +} + +bool RX888R2Radio::UpdateGainIF(int gain_index) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + uint8_t gain; + if (gain_index > GAIN_SWEET_POINT) + gain = HIGH_MODE | (gain_index - GAIN_SWEET_POINT + 3); + else + gain = LOW_MODE | (gain_index + 1); + + DbgPrintf("UpdateGainIF %d \n", gain); + + return Fx3->SetArgument(AD8340_VGA, gain); + } + else + { + // this is in VHF mode + return Fx3->SetArgument(R82XX_VGA, (uint16_t)gain_index); + } +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/radio/RX888R3Radio.cpp b/sddc_source/src/libsddc/Core/radio/RX888R3Radio.cpp new file mode 100644 index 0000000..807c23b --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RX888R3Radio.cpp @@ -0,0 +1,210 @@ +#include "RadioHandler.h" + +#define REFCLK_FREQ (27000000) // R820T reference frequency +#define IF_FREQ (20000000) + +#define HIGH_MODE 0x80 +#define LOW_MODE 0x00 + +#define GAIN_SWEET_POINT 18 +#define HIGH_GAIN_RATIO (0.409f) +#define LOW_GAIN_RATIO (0.059f) + +#define MODE HIGH_MODE + +const float RX888R3Radio::vhf_rf_steps[RX888R3Radio::vhf_rf_step_size] = { + 0.0f, 0.9f, 1.4f, 2.7f, 3.7f, 7.7f, 8.7f, 12.5f, 14.4f, 15.7f, + 16.6f, 19.7f, 20.7f, 22.9f, 25.4f, 28.0f, 29.7f, 32.8f, + 33.8f, 36.4f, 37.2f, 38.6f, 40.2f, 42.1f, 43.4f, 43.9f, + 44.5f, 48.0f, 49.6f}; + +const float RX888R3Radio::vhf_if_steps[RX888R3Radio::vhf_if_step_size] = { + -4.7f, -2.1f, 0.5f, 3.5f, 7.7f, 11.2f, 13.6f, 14.9f, 16.3f, 19.5f, 23.1f, 26.5f, 30.0f, 33.7f, 37.2f, 40.8f}; + +RX888R3Radio::RX888R3Radio(fx3class *fx3) + : RadioHardware(fx3) +{ + for (uint8_t i = 0; i < hf_rf_step_size; i++) + { + this->hf_rf_steps[hf_rf_step_size - i - 1] = -( + ((i & 0x01) != 0) * 0.5f + + ((i & 0x02) != 0) * 1.0f + + ((i & 0x04) != 0) * 2.0f + + ((i & 0x08) != 0) * 4.0f + + ((i & 0x010) != 0) * 8.0f + + ((i & 0x020) != 0) * 16.0f); + } + + for (uint8_t i = 0; i < hf_if_step_size; i++) + { + if (i > GAIN_SWEET_POINT) + this->hf_if_steps[i] = 20.0f * log10f(HIGH_GAIN_RATIO * (i - GAIN_SWEET_POINT + 3)); + else + this->hf_if_steps[i] = 20.0f * log10f(LOW_GAIN_RATIO * (i + 1)); + } +} + +void RX888R3Radio::Initialize(uint32_t adc_rate) +{ + SampleRate = adc_rate; + Fx3->Control(STARTADC, adc_rate); +} + +rf_mode RX888R3Radio::PrepareLo(uint64_t freq) +{ + if (freq < 10 * 1000) return NOMODE; + if (freq > 2150ll * 1000 * 1000) return NOMODE; + + if ( freq >= 220 * 1000 * 1000) + return VHFMODE; + else + return HFMODE; +} + +bool RX888R3Radio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + { + // disable HF by set max ATT + UpdateattRF(0); // max att 0 -> -31.5 dB + + // switch to VHF Attenna + FX3SetGPIO(VHF_EN); + + // high gain, 0db + uint8_t gain = 0x80 | 3; + Fx3->SetArgument(AD8340_VGA, gain); + // Enable Tuner reference clock + uint32_t ref = REFCLK_FREQ; + return Fx3->Control(TUNERINIT, ref); // Initialize Tuner + } + else if (mode == HFMODE) + { + Fx3->Control(TUNERSTDBY); // Stop Tuner + + return FX3UnsetGPIO(VHF_EN); // switch to HF Attenna + } + + return false; +} + +bool RX888R3Radio::UpdateattRF(int att) +{ + if (!(gpios & VHF_EN)) + { + // hf mode + if (att > hf_rf_step_size - 1) + att = hf_rf_step_size - 1; + if (att < 0) + att = 0; + uint8_t d = hf_rf_step_size - att - 1; + + DbgPrintf("UpdateattRF %f \n", this->hf_rf_steps[att]); + + return Fx3->SetArgument(DAT31_ATT, d); + } + else + { + // uint16_t index = att; + // this is in VHF mode + // return Fx3->SetArgument(R82XX_ATTENUATOR, index); + return false; + } +} + +#define M(x) ((x)*1000000) + +uint64_t RX888R3Radio::TuneLo(uint64_t freq) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + // set bpf + int sel; + // set preselector + if (freq > M(64) && freq <= M(128)) + sel = 0b001; // FM undersampling + else if (SampleRate < M(32)) + sel = 0b101; + else + sel = 0b011; + + Fx3->SetArgument(PRESELECTOR, sel); + + if (freq < M(64)) + return 0; + else if (freq < M(128)) + return M(64); + else if (freq < M(192)) + return M(64 * 2); + else if (freq < M(256)) + return M(64 * 3); + + return 0; + } + else + { + // this is in VHF mode + uint64_t targetVCO = freq + IF_FREQ; + + uint32_t hardwareVCO = targetVCO / 1000000; // convert to MHz + int offset = targetVCO % 1000000; + + DbgPrintf("Target VCO = %luHZ, hardware VCO= %dMHX, Actual IF = %dHZ\n", freq + IF_FREQ, hardwareVCO, IF_FREQ - offset); + + Fx3->Control(TUNERTUNE, hardwareVCO); + return freq - (IF_FREQ - offset); + } +} + +int RX888R3Radio::getRFSteps(const float **steps) +{ + if (!(gpios & VHF_EN)) + { + // hf mode + *steps = this->hf_rf_steps; + return hf_rf_step_size; + } + else + { + *steps = this->vhf_rf_steps; + return vhf_rf_step_size; + } +} + +int RX888R3Radio::getIFSteps(const float **steps) +{ + if (!(gpios & VHF_EN)) + { + *steps = this->hf_if_steps; + return hf_if_step_size; + } + else + { + *steps = this->vhf_if_steps; + return vhf_if_step_size; + } +} + +bool RX888R3Radio::UpdateGainIF(int gain_index) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + uint8_t gain; + if (gain_index > GAIN_SWEET_POINT) + gain = HIGH_MODE | (gain_index - GAIN_SWEET_POINT + 3); + else + gain = LOW_MODE | (gain_index + 1); + + DbgPrintf("UpdateGainIF %d \n", gain); + + return Fx3->SetArgument(AD8340_VGA, gain); + } + else + { + // this is in VHF mode + // return Fx3->SetArgument(R82XX_VGA, (uint16_t)gain_index); + return false; + } +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/radio/RX888Radio.cpp b/sddc_source/src/libsddc/Core/radio/RX888Radio.cpp new file mode 100644 index 0000000..d16cb07 --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RX888Radio.cpp @@ -0,0 +1 @@ +#include "RadioHandler.h" diff --git a/sddc_source/src/libsddc/Core/radio/RX999Radio.cpp b/sddc_source/src/libsddc/Core/radio/RX999Radio.cpp new file mode 100644 index 0000000..1cfeeef --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RX999Radio.cpp @@ -0,0 +1,118 @@ +#include "RadioHandler.h" + +#define ADC_FREQ (128u*1000*1000) +#define IF_FREQ (ADC_FREQ / 4) + +#define HIGH_MODE 0x80 +#define LOW_MODE 0x00 + +#define MODE HIGH_MODE + +RX999Radio::RX999Radio(fx3class *fx3) + : RadioHardware(fx3) +{ + // high mode gain = 0.409, start=-30 + // low mode gain = 0.059, start = -30 +#if (MODE == HIGH_MODE) + float ratio = 0.409f; +#else + float ratio = 0.059f; +#endif + for (uint8_t i = 0; i < if_step_size; i++) + { + this->if_steps[i] = -30.0f + ratio * (i + 1); + } +} + +void RX999Radio::Initialize(uint32_t adc_rate) +{ + SampleRate = adc_rate; + Fx3->Control(STARTADC, adc_rate); +} + + +rf_mode RX999Radio::PrepareLo(uint64_t freq) +{ + if (freq < 10 * 1000) return NOMODE; + if (freq > 6000ll * 1000 * 1000) return NOMODE; + + if ( freq >= this->SampleRate / 2) + return VHFMODE; + else + return HFMODE; +} + +bool RX999Radio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + { + // switch to VHF Attenna + FX3SetGPIO(VHF_EN); + + // Initialize VCO + + // Initialize Mixer + return Fx3->Control(TUNERINIT, (uint32_t)0); + } + else if (mode == HFMODE) + { + Fx3->Control(TUNERSTDBY); + return FX3UnsetGPIO(VHF_EN); // switch to HF Attenna + } + + return false; +} + + +bool RX999Radio::UpdateattRF(int att) +{ + return false; +} + +uint64_t RX999Radio::TuneLo(uint64_t freq) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + return 0; + } + else + { + int sel; + // set preselector + if (freq <= 120*1000*1000) sel = 0b111; + else if (freq <= 250*1000*1000) sel = 0b101; + else if (freq <= 300*1000*1000) sel = 0b110; + else if (freq <= 380*1000*1000) sel = 0b100; + else if (freq <= 500*1000*1000) sel = 0b000; + else if (freq <= 1000ll*1000*1000) sel = 0b010; + else if (freq <= 2000ll*1000*1000) sel = 0b001; + else sel = 0b011; + + Fx3->Control(TUNERTUNE, freq + IF_FREQ); + + Fx3->SetArgument(PRESELECTOR, sel); + // Set VCXO + return freq - IF_FREQ; + } +} + +int RX999Radio::getRFSteps(const float **steps) +{ + return 0; +} + +int RX999Radio::getIFSteps(const float **steps) +{ + *steps = this->if_steps; + return if_step_size; +} + +bool RX999Radio::UpdateGainIF(int gain_index) +{ + uint8_t gain = MODE | (gain_index + 1); + + DbgPrintf("UpdateGainIF %d \n", gain); + + return Fx3->SetArgument(AD8340_VGA, gain); +} diff --git a/sddc_source/src/libsddc/Core/radio/RXLucy.cpp b/sddc_source/src/libsddc/Core/radio/RXLucy.cpp new file mode 100644 index 0000000..3ac2ddf --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RXLucy.cpp @@ -0,0 +1,120 @@ +#include "RadioHandler.h" + +#define ADC_FREQ (64u*1000*1000) +#define IF_FREQ (ADC_FREQ / 4) + +#define HIGH_MODE 0x80 +#define LOW_MODE 0x00 + +#define MODE HIGH_MODE + +RXLucyRadio::RXLucyRadio(fx3class *fx3) + : RadioHardware(fx3) +{ + // initialize steps + for (uint8_t i = 0; i < if_step_size; i++) { + this->if_steps[if_step_size - i - 1] = -( + ((i & 0x01) != 0) * 0.5f + + ((i & 0x02) != 0) * 1.0f + + ((i & 0x04) != 0) * 2.0f + + ((i & 0x08) != 0) * 4.0f + + ((i & 0x010) != 0) * 8.0f + + ((i & 0x020) != 0) * 16.0f + ); + } + + for (uint8_t i = 0; i < step_size; i++) + { + this->steps[step_size - i - 1] = -1.0f * i; + } +} + +void RXLucyRadio::Initialize(uint32_t adc_rate) +{ + SampleRate = adc_rate; + Fx3->Control(STARTADC, adc_rate); +} + + +rf_mode RXLucyRadio::PrepareLo(uint64_t freq) +{ + if (freq < 35000ll * 1000) return NOMODE; + if (freq > 6000ll * 1000 * 1000) return NOMODE; + + if ( freq >= this->SampleRate / 2) + return VHFMODE; + else + return HFMODE; +} + + +bool RXLucyRadio::UpdateattRF(int att) +{ + if (att > step_size - 1) att = step_size - 1; + if (att < 0) att = 0; + uint8_t d = step_size - att - 1; + + DbgPrintf("UpdateattRF %f \n", this->steps[att]); + return Fx3->SetArgument(VHF_ATTENUATOR, d); +} +bool RXLucyRadio::UpdateGainIF(int att) //HF103 now +{ + if (att > if_step_size - 1) att = if_step_size - 1; + if (att < 0) att = 0; + uint8_t d = if_step_size - att - 1; + + DbgPrintf("UpdateattRF %f \n", this->if_steps[att]); + + return Fx3->SetArgument(DAT31_ATT, d); +} + +uint64_t RXLucyRadio::TuneLo(uint64_t freq) +{ + if (!(gpios & VHF_EN)) + { + // this is in HF mode + return 0; + } + else + { + Fx3->Control(TUNERTUNE, freq + IF_FREQ); + + // Set VCXO + return freq - IF_FREQ; + } + +} +bool RXLucyRadio::UpdatemodeRF(rf_mode mode) +{ + if (mode == VHFMODE) + { + // switch to VHF Attenna + FX3SetGPIO(VHF_EN); + + // Initialize VCO + + // Initialize Mixer + return Fx3->Control(TUNERINIT, (uint32_t)0); + } + else if (mode == HFMODE) + { + Fx3->Control(TUNERSTDBY); + return FX3UnsetGPIO(VHF_EN); // switch to HF Attenna + } + return false; +} + +int RXLucyRadio::getRFSteps(const float **steps) +{ + *steps = this->steps; + return step_size; +} + +int RXLucyRadio::getIFSteps(const float** steps) +{ + *steps = this->if_steps; + return if_step_size; +} + + + diff --git a/sddc_source/src/libsddc/Core/radio/RadioHardware.cpp b/sddc_source/src/libsddc/Core/radio/RadioHardware.cpp new file mode 100644 index 0000000..d21f36e --- /dev/null +++ b/sddc_source/src/libsddc/Core/radio/RadioHardware.cpp @@ -0,0 +1,22 @@ +#include "RadioHandler.h" + +bool RadioHardware::FX3SetGPIO(uint32_t mask) +{ + gpios |= mask; + + return Fx3->Control(GPIOFX3, gpios); +} + +bool RadioHardware::FX3UnsetGPIO(uint32_t mask) +{ + gpios &= ~mask; + + return Fx3->Control(GPIOFX3, gpios); +} + +RadioHardware::~RadioHardware() +{ + if (Fx3) { + FX3SetGPIO(SHDWN); + } +} \ No newline at end of file diff --git a/sddc_source/src/libsddc/Core/sddc_config.cpp b/sddc_source/src/libsddc/Core/sddc_config.cpp new file mode 100644 index 0000000..6612d18 --- /dev/null +++ b/sddc_source/src/libsddc/Core/sddc_config.cpp @@ -0,0 +1,8 @@ +#include "license.txt" +#include "sddc_config.h" + +bool saveADCsamplesflag = false; +uint32_t adcnominalfreq = DEFAULT_ADC_FREQ; +uint32_t MIN_ADC_FREQ = 50000000; // ADC sampling frequency minimum +uint32_t MAX_ADC_FREQ = 140000000; // ADC sampling frequency minimum +uint32_t N2_BANDSWITCH = 80000000; // threshold 5 or 6 SR bandwidths diff --git a/sddc_source/src/libsddc/Core/sddc_config.h b/sddc_source/src/libsddc/Core/sddc_config.h new file mode 100644 index 0000000..610d3d4 --- /dev/null +++ b/sddc_source/src/libsddc/Core/sddc_config.h @@ -0,0 +1,94 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include "license.txt" + +#include "../Interface.h" +#include // atan => PI +#include +#include +#include + +//#define _DEBUG // defined in VS configuration + +#ifdef __cplusplus +inline void null_func(const char *format, ...) { } +#define DbgEmpty null_func +#else +#define DbgEmpty { } +#endif + +// macro to call callback function with just status extHWstatusT +#define EXTIO_STATUS_CHANGE( CB, STATUS ) \ + do { \ + SendMessage(h_dialog, WM_USER + 1, STATUS, 0); \ + if (CB) { \ + DbgPrintf("<==CALLBACK: %s\n", #STATUS); \ + CB( -1, STATUS, 0, NULL );\ + }\ + }while(0) + +#ifdef VERBOSE_DEBUG + #define EnterFunction() \ + DbgPrintf("==>%s\n", __FUNCDNAME__) + + #define EnterFunction1(v1) \ + DbgPrintf("==>%s(%d)\n", __FUNCDNAME__, (v1)) +#else + #define EnterFunction() + #define EnterFunction1(v1) +#endif + +#ifdef _DEBUG +#define DbgPrintf (printf) +#else +#define DbgPrintf DbgEmpty +#endif + +#define VERSION (1.2) // Dll version number x.xx +#define SWVERSION "1.2.1" +#define SETTINGS_IDENTIFIER "sddc_1.06" +#define SWNAME "ExtIO_sddc.dll" + +#define QUEUE_SIZE 32 +#define WIDEFFTN // test FFTN 8192 + +#define FFTN_R_ADC (8192) // FFTN used for ADC real stream DDC tested at 2048, 8192, 32768, 131072 + +// GAINFACTORS to be adjusted with lab reference source measured with HDSDR Smeter rms mode +#define BBRF103_GAINFACTOR (7.8e-8f) // BBRF103 +#define HF103_GAINFACTOR (1.14e-8f) // HF103 +#define RX888_GAINFACTOR (0.695e-8f) // RX888 +#define RX888mk2_GAINFACTOR (1.08e-8f) // RX888mk2 + +enum rf_mode { NOMODE = 0, HFMODE = 0x1, VHFMODE = 0x2 }; + +#define HF_HIGH (32000000) // 32M +#define MW_HIGH ( 2000000) + +#define EXT_BLOCKLEN 512 * 64 /* 32768 only multiples of 512 */ + +#define RFDDCNAME ("NVIA L768M256") +#define RFDDCVER ("v 1.0") + +// URL definitions +#define URL1B "16bit SDR Receiver" +#define URL1 "http://www.hdsdr.de/" +#define URL_HDSR "http://www.hdsdr.de/" +#define URL_HDSDRA "http://www.hdsdr.de/" + +extern bool saveADCsamplesflag; +extern uint32_t adcnominalfreq; + +const uint32_t transferSize = 131072; +const uint32_t transferSamples = 131072 / sizeof(int16_t); + +const uint32_t DEFAULT_ADC_FREQ = 64000000; // ADC sampling frequency + +const uint32_t DEFAULT_TRANSFERS_PER_SEC = DEFAULT_ADC_FREQ / transferSamples; + +extern uint32_t MIN_ADC_FREQ; // ADC sampling frequency minimum +extern uint32_t MAX_ADC_FREQ; // ADC sampling frequency minimum +extern uint32_t N2_BANDSWITCH; // threshold 5 or 6 SR bandwidths +#endif // _CONFIG_H_ + diff --git a/sddc_source/src/libsddc/HWSDRtable.h b/sddc_source/src/libsddc/HWSDRtable.h new file mode 100644 index 0000000..9700aab --- /dev/null +++ b/sddc_source/src/libsddc/HWSDRtable.h @@ -0,0 +1,44 @@ + +/* +HWSDRtable.h v1.2 +Hardware detection of BBRF103 family SDRs ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| SDR | MODEL | GPIO | GPIO | GPIO | GPIO | GPIO | GPIO | GPIO | GPIO | USED BY | +| | # | 33 | 36 | 45 | 50 | 51 | 52 | 53 | 54 | | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| BBRF103 | 0x01 | - | - | - | pd* | - | - | - | LED | Oscar Steila | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| HF103 | 0x02 | - | - | - | - | pd* | - | - | LED | Oscar Steila | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| RX888 | 0x03 | - | - | pd | - | - | - | - | - | Justin Peng / Howard Su | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| RX888 r2 | 0x04 | - | pd | - | - | - | - | - | - | Justin Peng / Howard Su | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| RX999 | 0x05 | pd | - | - | - | - | - | - | - | Justin Peng / Howard Su | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| LUCY | 0x06 | - | - | - | - | - | pd+ | pd+ | - | Wiktor Starzak | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| --- | - | - | - | - | pd | pd | pd | pd | LED | Oscar Steila | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| SDR-HF | - | - | - | - | pu | pd | pd | pd | LED | Pieter Ibelings | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ +| | | - | - | | | | | | | ... | ++--------------+-------+------+------+------+------+------+------+------+------+----------------------------+ + +Where: + - floating no connection + pu 1k resistor pull-up to 3V3 + pd 1k resistor pull-down to GND + pd* 1k resistor pull-down to GND to be added with a patch + pd+ 1k resistor pull-down all pd+ are connected to the same pull-down + LED plus resistor to 3V3 connected to GPIO54 on FX3 SuperSpeed Kit + +The 1k value is low enough to be able to detect the resistor using the GPIO internal programmable pull-up pull-down vs a floating pin. +The value is high enough to not disturb use of GPIOs for other purpose after detection. + + + +*/ + + +// TODO \ No newline at end of file diff --git a/sddc_source/src/libsddc/Interface.h b/sddc_source/src/libsddc/Interface.h new file mode 100644 index 0000000..0a323bf --- /dev/null +++ b/sddc_source/src/libsddc/Interface.h @@ -0,0 +1,154 @@ +#pragma once + +#define FIRMWARE_VER_MAJOR 2 +#define FIRMWARE_VER_MINOR 1 + +// HF103 commands !!! +enum FX3Command { + // Start GPII engine and stream the data from ADC + // WRITE: UINT32 + STARTFX3 = 0xAA, + + // Stop GPII engine + // WRITE: UINT32 + STOPFX3 = 0xAB, + + // Get the information of device + // including model, version + // READ: UINT32 + TESTFX3 = 0xAC, + + // Control GPIOs + // WRITE: UINT32 + GPIOFX3 = 0xAD, + + // Write data to I2c bus + // WRITE: DATA + // INDEX: reg + // VALUE: i2c_addr + I2CWFX3 = 0xAE, + + // Read data from I2c bus + // READ: DATA + // INDEX: reg + // VALUE: i2c_addr + I2CRFX3 = 0xAF, + + // Reset USB chip and get back to bootloader mode + // WRITE: NONE + RESETFX3 = 0xB1, + + // Set Argument, packet Index/Vaule contains the data + // WRITE: (Additional Data) + // INDEX: Argument_index + // VALUE: arguement value + SETARGFX3 = 0xB6, + + // Start ADC with the specific frequency + // Optional, if ADC is running with crystal, this is not needed. + // WRITE: UINT32 -> adc frequency + STARTADC = 0xB2, + + // R82XX family Tuner functions + // Initialize R82XX tuner + // WRITE: NONE + TUNERINIT = 0xB4, + + // Tune to a sepcific frequency + // WRITE: UINT64 + TUNERTUNE = 0xB5, + + // Stop Tuner + // WRITE: NONE + TUNERSTDBY = 0xB8, + + // Read Debug string if any + // READ: + READINFODEBUG = 0xBA, +}; + +#define OUTXIO0 (1U << 0) // ATT_LE +#define OUTXIO1 (1U << 1) // ATT_CLK +#define OUTXIO2 (1U << 2) // ATT_DATA +#define OUTXIO3 (1U << 3) // SEL0 +#define OUTXIO4 (1U << 4) // SEL1 +#define OUTXIO5 (1U << 5) // SHDWN +#define OUTXIO6 (1U << 6) // DITH +#define OUTXIO7 (1U << 7) // RAND + +#define OUTXIO8 (1U << 8) // 256 +#define OUTXIO9 (1U << 9) // 512 +#define OUTXI10 (1U << 10) // 1024 +#define OUTXI11 (1U << 11) // 2048 +#define OUTXI12 (1U << 12) // 4096 +#define OUTXI13 (1U << 13) // 8192 +#define OUTXI14 (1U << 14) // 16384 +#define OUTXI15 (1U << 15) // 32768 +#define OUTXI16 (1U << 16) + +enum GPIOPin { + SHDWN = OUTXIO5, + DITH = OUTXIO6, + RANDO = OUTXIO7, + BIAS_HF = OUTXIO8, + BIAS_VHF = OUTXIO9, + LED_YELLOW = OUTXI10, + LED_RED = OUTXI11, + LED_BLUE = OUTXI12, + ATT_SEL0 = OUTXI13, + ATT_SEL1 = OUTXI14, + + // RX888r2 + VHF_EN = OUTXI15, + PGA_EN = OUTXI16, +}; + +enum RadioModel { + NORADIO = 0x00, + BBRF103 = 0x01, + HF103 = 0x02, + RX888 = 0x03, + RX888r2 = 0x04, + RX999 = 0x05, + RXLUCY = 0x06, + RX888r3 = 0x07, +}; + +enum ArgumentList { + // Set R8xx lna/mixer gain + // value: 0-29 + R82XX_ATTENUATOR = 1, + + // Set R8xx vga gain + // value: 0-15 + R82XX_VGA = 2, + + // Set R8xx sideband + // value: 0/1 + R82XX_SIDEBAND = 3, + + // Set R8xx harmonic + // value: 0/1 + R82XX_HARMONIC = 4, + + // Set DAT-31 Att + // Value: 0-63 + DAT31_ATT = 10, + + // Set AD8340 chip vga + // Value: 0-255 + AD8340_VGA = 11, + + // Preselector + // Value: 0-2 + PRESELECTOR = 12, + + // VHFATT + // Value: 0-15 + VHF_ATTENUATOR = 13, +}; + +#define _DEBUG_USB_ +#define MAXLEN_D_USB (100) + + diff --git a/sddc_source/src/libsddc/LICENSE.txt b/sddc_source/src/libsddc/LICENSE.txt new file mode 100644 index 0000000..e143754 --- /dev/null +++ b/sddc_source/src/libsddc/LICENSE.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) 2017-2020 Oscar Steila ik1xpvgmail.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.The MIT License (MIT) +The MIT License (MIT) + diff --git a/sddc_source/src/libsddc/libsddc/CMakeLists.txt b/sddc_source/src/libsddc/libsddc/CMakeLists.txt new file mode 100644 index 0000000..452f90e --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.13) + +if (MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) +endif (MSVC) + +include_directories("." "../Core") + +add_library(sddc SHARED + libsddc.cpp +) + +if (MSVC) + target_link_libraries(sddc PUBLIC Setupapi.lib) +else() + target_include_directories(sddc PUBLIC "${LIBUSB_INCLUDE_DIR}") + target_link_directories(sddc PUBLIC "${LIBUSB_LIBRARY_DIRS}") + target_link_libraries(sddc PUBLIC ${LIBUSB_LIBRARIES}) +endif (MSVC) + +target_include_directories(sddc PUBLIC "${LIBFFTW_INCLUDE_DIR}") +target_link_directories(sddc PUBLIC "${LIBFFTW_LIBRARY_DIRS}") +target_link_libraries(sddc PUBLIC ${LIBFFTW_LIBRARIES}) + +target_link_libraries(sddc PRIVATE SDDC_CORE) +set_target_properties(sddc PROPERTIES VERSION ${PROJECT_VERSION}) +set_target_properties(sddc PROPERTIES SOVERSION 0) + +# applications +add_executable(sddc_test sddc_test.c) +target_link_libraries(sddc_test PRIVATE sddc ${ASANLIB}) + +add_executable(sddc_stream_test sddc_stream_test.c wavewrite.c) +target_link_libraries(sddc_stream_test sddc ${ASANLIB}) + +add_executable(sddc_vhf_stream_test sddc_vhf_stream_test.c wavewrite.c) +target_link_libraries(sddc_vhf_stream_test sddc ${ASANLIB}) diff --git a/sddc_source/src/libsddc/libsddc/libsddc.cpp b/sddc_source/src/libsddc/libsddc/libsddc.cpp new file mode 100644 index 0000000..ba27f0b --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/libsddc.cpp @@ -0,0 +1,396 @@ +#include "libsddc.h" +#include "sddc_config.h" +#include "r2iq.h" +#include "RadioHandler.h" + +struct sddc +{ + SDDCStatus status; + RadioHandlerClass* handler; + uint8_t led; + int samplerateidx; + double freq; + + sddc_read_async_cb_t callback; + void *callback_context; +}; + +sddc_t *current_running; + +static void Callback(const float* data, uint32_t len) +{ +} + +class rawdata : public r2iqControlClass { + void Init(float gain, ringbuffer* buffers, ringbuffer* obuffers) override + { + idx = 0; + } + + void TurnOn() override + { + this->r2iqOn = true; + idx = 0; + } + +private: + int idx; +}; + +int sddc_get_device_count() +{ + return 1; +} + +int sddc_get_device_info(struct sddc_device_info **sddc_device_infos) +{ + auto ret = new sddc_device_info(); + const char *todo = "TODO"; + ret->manufacturer = todo; + ret->product = todo; + ret->serial_number = todo; + + *sddc_device_infos = ret; + + return 1; +} + +int sddc_free_device_info(struct sddc_device_info *sddc_device_infos) +{ + delete sddc_device_infos; + return 0; +} + +sddc_t *sddc_open(int index, const char* imagefile) +{ + auto ret_val = new sddc_t(); + + fx3class *fx3 = CreateUsbHandler(); + if (fx3 == nullptr) + { + return nullptr; + } + + // open the firmware + unsigned char* res_data; + uint32_t res_size; + + FILE *fp = fopen(imagefile, "rb"); + if (fp == nullptr) + { + return nullptr; + } + + fseek(fp, 0, SEEK_END); + res_size = ftell(fp); + res_data = (unsigned char*)malloc(res_size); + fseek(fp, 0, SEEK_SET); + if (fread(res_data, 1, res_size, fp) != res_size) + return nullptr; + + bool openOK = fx3->Open(res_data, res_size); + if (!openOK) + return nullptr; + + ret_val->handler = new RadioHandlerClass(); + + if (ret_val->handler->Init(fx3, Callback, new rawdata())) + { + ret_val->status = SDDC_STATUS_READY; + ret_val->samplerateidx = 0; + } + + return ret_val; +} + +void sddc_close(sddc_t *that) +{ + if (that->handler) + delete that->handler; + delete that; +} + +enum SDDCStatus sddc_get_status(sddc_t *t) +{ + return t->status; +} + +enum SDDCHWModel sddc_get_hw_model(sddc_t *t) +{ + switch(t->handler->getModel()) + { + case RadioModel::BBRF103: + return HW_BBRF103; + case RadioModel::HF103: + return HW_HF103; + case RadioModel::RX888: + return HW_RX888; + case RadioModel::RX888r2: + return HW_RX888R2; + case RadioModel::RX888r3: + return HW_RX888R3; + case RadioModel::RX999: + return HW_RX999; + default: + return HW_NORADIO; + } +} + +const char *sddc_get_hw_model_name(sddc_t *t) +{ + return t->handler->getName(); +} + +uint16_t sddc_get_firmware(sddc_t *t) +{ + return t->handler->GetFirmware(); +} + +const double *sddc_get_frequency_range(sddc_t *t) +{ + return nullptr; +} + +enum RFMode sddc_get_rf_mode(sddc_t *t) +{ + switch(t->handler->GetmodeRF()) + { + case HFMODE: + return RFMode::HF_MODE; + case VHFMODE: + return RFMode::VHF_MODE; + default: + return RFMode::NO_RF_MODE; + } +} + +int sddc_set_rf_mode(sddc_t *t, enum RFMode rf_mode) +{ + switch (rf_mode) + { + case VHF_MODE: + t->handler->UpdatemodeRF(VHFMODE); + break; + case HF_MODE: + t->handler->UpdatemodeRF(HFMODE); + default: + return -1; + } + + return 0; +} + +/* LED functions */ +int sddc_led_on(sddc_t *t, uint8_t led_pattern) +{ + if (led_pattern & YELLOW_LED) + t->handler->uptLed(0, true); + if (led_pattern & RED_LED) + t->handler->uptLed(1, true); + if (led_pattern & BLUE_LED) + t->handler->uptLed(2, true); + + t->led |= led_pattern; + + return 0; +} + +int sddc_led_off(sddc_t *t, uint8_t led_pattern) +{ + if (led_pattern & YELLOW_LED) + t->handler->uptLed(0, false); + if (led_pattern & RED_LED) + t->handler->uptLed(1, false); + if (led_pattern & BLUE_LED) + t->handler->uptLed(2, false); + + t->led &= ~led_pattern; + + return 0; +} + +int sddc_led_toggle(sddc_t *t, uint8_t led_pattern) +{ + t->led = t->led ^ led_pattern; + if (t->led & YELLOW_LED) + t->handler->uptLed(0, false); + if (t->led & RED_LED) + t->handler->uptLed(1, false); + if (t->led & BLUE_LED) + t->handler->uptLed(2, false); + + return 0; +} + + +/* ADC functions */ +int sddc_get_adc_dither(sddc_t *t) +{ + return t->handler->GetDither(); +} + +int sddc_set_adc_dither(sddc_t *t, int dither) +{ + t->handler->UptDither(dither != 0); + return 0; +} + +int sddc_get_adc_random(sddc_t *t) +{ + return t->handler->GetRand(); +} + +int sddc_set_adc_random(sddc_t *t, int random) +{ + t->handler->UptRand(random != 0); + return 0; +} + +/* HF block functions */ +double sddc_get_hf_attenuation(sddc_t *t) +{ + return 0; +} + +int sddc_set_hf_attenuation(sddc_t *t, double attenuation) +{ + return 0; +} + +int sddc_get_hf_bias(sddc_t *t) +{ + return t->handler->GetBiasT_HF(); +} + +int sddc_set_hf_bias(sddc_t *t, int bias) +{ + t->handler->UpdBiasT_HF(bias != 0); + return 0; +} + + +/* VHF block and VHF/UHF tuner functions */ +double sddc_get_tuner_frequency(sddc_t *t) +{ + return t->freq; +} + +int sddc_set_tuner_frequency(sddc_t *t, double frequency) +{ + t->freq = t->handler->TuneLO((int64_t)frequency); + + return 0; +} + +int sddc_get_tuner_rf_attenuations(sddc_t *t, const double *attenuations[]) +{ + return 0; +} + +double sddc_get_tuner_rf_attenuation(sddc_t *t) +{ + return 0; +} + +int sddc_set_tuner_rf_attenuation(sddc_t *t, double attenuation) +{ + //TODO, convert double to index + t->handler->UpdateattRF(5); + return 0; +} + +int sddc_get_tuner_if_attenuations(sddc_t *t, const double *attenuations[]) +{ + // TODO + return 0; +} + +double sddc_get_tuner_if_attenuation(sddc_t *t) +{ + return 0; +} + +int sddc_set_tuner_if_attenuation(sddc_t *t, double attenuation) +{ + return 0; +} + +int sddc_get_vhf_bias(sddc_t *t) +{ + return t->handler->GetBiasT_VHF(); +} + +int sddc_set_vhf_bias(sddc_t *t, int bias) +{ + t->handler->UpdBiasT_VHF(bias != 0); + return 0; +} + +double sddc_get_sample_rate(sddc_t *t) +{ + return 0; +} + +int sddc_set_sample_rate(sddc_t *t, double sample_rate) +{ + switch((int64_t)sample_rate) + { + case 32000000: + t->samplerateidx = 0; + break; + case 16000000: + t->samplerateidx = 1; + break; + case 8000000: + t->samplerateidx = 2; + break; + case 4000000: + t->samplerateidx = 3; + break; + case 2000000: + t->samplerateidx = 4; + break; + default: + return -1; + } + return 0; +} + +int sddc_set_async_params(sddc_t *t, uint32_t frame_size, + uint32_t num_frames, sddc_read_async_cb_t callback, + void *callback_context) +{ + // TODO: ignore frame_size, num_frames + t->callback = callback; + t->callback_context = callback_context; + return 0; +} + +int sddc_start_streaming(sddc_t *t) +{ + current_running = t; + t->handler->Start(t->samplerateidx); + return 0; +} + +int sddc_handle_events(sddc_t *t) +{ + return 0; +} + +int sddc_stop_streaming(sddc_t *t) +{ + t->handler->Stop(); + current_running = nullptr; + return 0; +} + +int sddc_reset_status(sddc_t *t) +{ + return 0; +} + +int sddc_read_sync(sddc_t *t, uint8_t *data, int length, int *transferred) +{ + return 0; +} diff --git a/sddc_source/src/libsddc/libsddc/libsddc.h b/sddc_source/src/libsddc/libsddc/libsddc.h new file mode 100644 index 0000000..3f2454c --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/libsddc.h @@ -0,0 +1,171 @@ +/* + * libsddc - low level functions for wideband SDR receivers like + * BBRF103, RX-666, RX888, HF103, etc + * + * Copyright (C) 2020 by Franco Venturi + * + * this program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * this program 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef __LIBSDDC_H +#define __LIBSDDC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef struct sddc sddc_t; + +struct sddc_device_info { + const char *manufacturer; + const char *product; + const char *serial_number; +}; + +enum SDDCStatus { + SDDC_STATUS_OFF, + SDDC_STATUS_READY, + SDDC_STATUS_STREAMING, + SDDC_STATUS_FAILED = 0xff +}; + +enum SDDCHWModel { + HW_NORADIO, + HW_BBRF103, + HW_HF103, + HW_RX888, + HW_RX888R2, + HW_RX999, + HW_RX888R3, +}; + +enum RFMode { + NO_RF_MODE, + HF_MODE, + VHF_MODE +}; + +enum LEDColors { + YELLOW_LED = 0x01, + RED_LED = 0x02, + BLUE_LED = 0x04 +}; + +/* basic functions */ +int sddc_get_device_count(); + +int sddc_get_device_info(struct sddc_device_info **sddc_device_infos); + +int sddc_free_device_info(struct sddc_device_info *sddc_device_infos); + +sddc_t *sddc_open(int index, const char* imagefile); + +void sddc_close(sddc_t *t); + +enum SDDCStatus sddc_get_status(sddc_t *t); + +enum SDDCHWModel sddc_get_hw_model(sddc_t *t); + +const char *sddc_get_hw_model_name(sddc_t *t); + +uint16_t sddc_get_firmware(sddc_t *t); + +const double *sddc_get_frequency_range(sddc_t *t); + +enum RFMode sddc_get_rf_mode(sddc_t *t); + +int sddc_set_rf_mode(sddc_t *t, enum RFMode rf_mode); + + +/* LED functions */ +int sddc_led_on(sddc_t *t, uint8_t led_pattern); + +int sddc_led_off(sddc_t *t, uint8_t led_pattern); + +int sddc_led_toggle(sddc_t *t, uint8_t led_pattern); + + +/* ADC functions */ +int sddc_get_adc_dither(sddc_t *t); + +int sddc_set_adc_dither(sddc_t *t, int dither); + +int sddc_get_adc_random(sddc_t *t); + +int sddc_set_adc_random(sddc_t *t, int random); + + +/* HF block functions */ +double sddc_get_hf_attenuation(sddc_t *t); + +int sddc_set_hf_attenuation(sddc_t *t, double attenuation); + +int sddc_get_hf_bias(sddc_t *t); + +int sddc_set_hf_bias(sddc_t *t, int bias); + + +/* VHF block and VHF/UHF tuner functions */ +double sddc_get_tuner_frequency(sddc_t *t); + +int sddc_set_tuner_frequency(sddc_t *t, double frequency); + +int sddc_get_tuner_rf_attenuations(sddc_t *t, const double *attenuations[]); + +double sddc_get_tuner_rf_attenuation(sddc_t *t); + +int sddc_set_tuner_rf_attenuation(sddc_t *t, double attenuation); + +int sddc_get_tuner_if_attenuations(sddc_t *t, const double *attenuations[]); + +double sddc_get_tuner_if_attenuation(sddc_t *t); + +int sddc_set_tuner_if_attenuation(sddc_t *t, double attenuation); + +int sddc_get_vhf_bias(sddc_t *t); + +int sddc_set_vhf_bias(sddc_t *t, int bias); + + +/* streaming functions */ +typedef void (*sddc_read_async_cb_t)(uint32_t data_size, uint8_t *data, + void *context); + +double sddc_get_sample_rate(sddc_t *t); + +int sddc_set_sample_rate(sddc_t *t, double sample_rate); + +int sddc_set_async_params(sddc_t *t, uint32_t frame_size, + uint32_t num_frames, sddc_read_async_cb_t callback, + void *callback_context); + +int sddc_start_streaming(sddc_t *t); + +int sddc_handle_events(sddc_t *t); + +int sddc_stop_streaming(sddc_t *t); + +int sddc_reset_status(sddc_t *t); + +int sddc_read_sync(sddc_t *t, uint8_t *data, int length, int *transferred); + +#ifdef __cplusplus +} +#endif + +#endif /* __LIBSDDC_H */ \ No newline at end of file diff --git a/sddc_source/src/libsddc/libsddc/wavehdr.h b/sddc_source/src/libsddc/libsddc/wavehdr.h new file mode 100644 index 0000000..2f96a2a --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/wavehdr.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2019 by Hayati Ayguen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __WAVEHDR_H +#define __WAVEHDR_H + +#include +#include +#include + +#pragma pack(push) +#pragma pack(1) + +typedef struct +{ + char ID[4]; + uint32_t size; +} chunk_hdr; + +typedef struct +{ + uint16_t wYear; /* 1601 through 30827 */ + uint16_t wMonth; /* 1..12 */ + uint16_t wDayOfWeek; /* 0 .. 6: 0 == Sunday, .., 6 == Saturday */ + uint16_t wDay; /* 1 .. 31 */ + uint16_t wHour; /* 0 .. 23 */ + uint16_t wMinute; /* 0 .. 59 */ + uint16_t wSecond; /* 0 .. 59 */ + uint16_t wMilliseconds; /* 0 .. 999 */ +} Wind_SystemTime; + + +typedef struct +{ + /* RIFF header */ + chunk_hdr hdr; /* ID == "RIFF" string, size == full filesize - 8 bytes (maybe with some byte missing...) */ + char waveID[4]; /* "WAVE" string */ +} riff_chunk; + +typedef struct +{ + /* FMT header */ + chunk_hdr hdr; /* ID == "fmt " */ + int16_t wFormatTag; + int16_t nChannels; + int32_t nSamplesPerSec; + int32_t nAvgBytesPerSec; + int16_t nBlockAlign; + int16_t nBitsPerSample; +} fmt_chunk; + +typedef struct +{ + /* auxi header - used by SpectraVue / rfspace / HDSDR / ELAD FDM .. */ + chunk_hdr hdr; /* ="auxi" (chunk rfspace) */ + Wind_SystemTime StartTime; + Wind_SystemTime StopTime; + uint32_t centerFreq; /* receiver center frequency */ + uint32_t ADsamplerate; /* A/D sample frequency before downsampling */ + uint32_t IFFrequency; /* IF freq if an external down converter is used */ + uint32_t Bandwidth; /* displayable BW if you want to limit the display to less than Nyquist band */ + int32_t IQOffset; /* DC offset of the I and Q channels in 1/1000's of a count */ + int32_t Unused2; + int32_t Unused3; + int32_t Unused4; + int32_t Unused5; +} auxi_chunk; + +typedef struct +{ + /* DATA header */ + chunk_hdr hdr; /* ="data" */ +} data_chunk; + +typedef struct +{ + riff_chunk r; + fmt_chunk f; + auxi_chunk a; + data_chunk d; +} waveFileHeader; + +#pragma pack(pop) + +#endif /* __WAVEHDR_H */ + +// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab + diff --git a/sddc_source/src/libsddc/libsddc/wavewrite.c b/sddc_source/src/libsddc/libsddc/wavewrite.c new file mode 100644 index 0000000..0b3fb14 --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/wavewrite.c @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2019 by Hayati Ayguen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "wavewrite.h" + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#define _USE_MATH_DEFINES + +#include // portable: uint64_t MSVC: __int64 + +int gettimeofday(struct timeval * tp, struct timezone * tzp) +{ + // Note: some broken versions only have 8 trailing zero's, the correct epoch has 9 trailing zero's + // This magic number is the number of 100 nanosecond intervals since January 1, 1601 (UTC) + // until 00:00:00 January 1, 1970 + static const uint64_t EPOCH = ((uint64_t) 116444736000000000ULL); + + SYSTEMTIME system_time; + FILETIME file_time; + uint64_t time; + + GetSystemTime( &system_time ); + SystemTimeToFileTime( &system_time, &file_time ); + time = ((uint64_t)file_time.dwLowDateTime ) ; + time += ((uint64_t)file_time.dwHighDateTime) << 32; + + tp->tv_sec = (long) ((time - EPOCH) / 10000000L); + tp->tv_usec = (long) (system_time.wMilliseconds * 1000); + return 0; +} +#endif + +#include + +#include "wavehdr.h" + +static waveFileHeader waveHdr; + +static uint32_t waveDataSize = 0; +int waveHdrStarted = 0; + + +static void waveSetCurrTime(Wind_SystemTime *p) +{ + struct timeval tv; + struct tm t; + + gettimeofday(&tv, NULL); + p->wMilliseconds = tv.tv_usec / 1000; + +#ifdef _WIN32 + t = *gmtime(&tv.tv_sec); +#else + gmtime_r(&tv.tv_sec, &t); +#endif + + p->wYear = t.tm_year + 1900; /* 1601 through 30827 */ + p->wMonth = t.tm_mon + 1; /* 1..12 */ + p->wDayOfWeek = t.tm_wday; /* 0 .. 6: 0 == Sunday, .., 6 == Saturday */ + p->wDay = t.tm_mday; /* 1 .. 31 */ + p->wHour = t.tm_hour; /* 0 .. 23 */ + p->wMinute = t.tm_min; /* 0 .. 59 */ + p->wSecond = t.tm_sec; /* 0 .. 59 */ +} + +static void waveSetStartTimeInt(time_t tim, double fraction, Wind_SystemTime *p) +{ + struct tm t = *gmtime( &tim ); + p->wYear = t.tm_year + 1900; /* 1601 through 30827 */ + p->wMonth = t.tm_mon + 1; /* 1..12 */ + p->wDayOfWeek = t.tm_wday; /* 0 .. 6: 0 == Sunday, .., 6 == Saturday */ + p->wDay = t.tm_mday; /* 1 .. 31 */ + p->wHour = t.tm_hour; /* 0 .. 23 */ + p->wMinute = t.tm_min; /* 0 .. 59 */ + p->wSecond = t.tm_sec; /* 0 .. 59 */ + p->wMilliseconds = (int)( fraction * 1000.0 ); + if (p->wMilliseconds >= 1000) + p->wMilliseconds = 999; +} + +void waveSetStartTime(time_t tim, double fraction) +{ + waveSetStartTimeInt(tim, fraction, &waveHdr.a.StartTime ); + waveHdr.a.StopTime = waveHdr.a.StartTime; /* to fix */ +} + + +void wavePrepareHeader(unsigned samplerate, unsigned freq, int bitsPerSample, int numChannels) +{ + int bytesPerSample = bitsPerSample / 8; + int bytesPerFrame = bytesPerSample * numChannels; + + memcpy( waveHdr.r.hdr.ID, "RIFF", 4 ); + waveHdr.r.hdr.size = sizeof(waveFileHeader) - 8; /* to fix */ + memcpy( waveHdr.r.waveID, "WAVE", 4 ); + + memcpy( waveHdr.f.hdr.ID, "fmt ", 4 ); + waveHdr.f.hdr.size = 16; + waveHdr.f.wFormatTag = 1; /* PCM */ + waveHdr.f.nChannels = numChannels; /* I and Q channels */ + waveHdr.f.nSamplesPerSec = samplerate; + waveHdr.f.nAvgBytesPerSec = samplerate * bytesPerFrame; + waveHdr.f.nBlockAlign = waveHdr.f.nChannels; + waveHdr.f.nBitsPerSample = bitsPerSample; + + memcpy( waveHdr.a.hdr.ID, "auxi", 4 ); + waveHdr.a.hdr.size = 2 * sizeof(Wind_SystemTime) + 9 * sizeof(int32_t); /* = 2 * 16 + 9 * 4 = 68 */ + waveSetCurrTime( &waveHdr.a.StartTime ); + waveHdr.a.StopTime = waveHdr.a.StartTime; /* to fix */ + waveHdr.a.centerFreq = freq; + waveHdr.a.ADsamplerate = samplerate; + waveHdr.a.IFFrequency = 0; + waveHdr.a.Bandwidth = 0; + waveHdr.a.IQOffset = 0; + waveHdr.a.Unused2 = 0; + waveHdr.a.Unused3 = 0; + waveHdr.a.Unused4 = 0; + waveHdr.a.Unused5 = 0; + + memcpy( waveHdr.d.hdr.ID, "data", 4 ); + waveHdr.d.hdr.size = 0; /* to fix later */ + waveDataSize = 0; +} + +void waveWriteHeader(unsigned samplerate, unsigned freq, int bitsPerSample, int numChannels, FILE * f) +{ + if (f != stdout) { + assert( !waveHdrStarted ); + wavePrepareHeader(samplerate, freq, bitsPerSample, numChannels); + fwrite(&waveHdr, sizeof(waveFileHeader), 1, f); + waveHdrStarted = 1; + } +} + +int waveWriteSamples(FILE* f, void * vpData, size_t numSamples, int needCleanData) +{ + size_t nw; + switch (waveHdr.f.nBitsPerSample) + { + case 0: + default: + return 1; + case 8: + /* no endian conversion needed for single bytes */ + nw = fwrite(vpData, sizeof(uint8_t), numSamples, f); + waveDataSize += sizeof(uint8_t) * numSamples; + return (nw == numSamples) ? 0 : 1; + case 16: + /* TODO: endian conversion needed */ + nw = fwrite(vpData, sizeof(int16_t), numSamples, f); + waveDataSize += sizeof(int16_t) * numSamples; + if ( needCleanData ) + { + /* TODO: convert back endianness */ + } + return (nw == numSamples) ? 0 : 1; + } +} + +int waveWriteFrames(FILE* f, void * vpData, size_t numFrames, int needCleanData) +{ + size_t nw; + switch (waveHdr.f.nBitsPerSample) + { + case 0: + default: + return 1; + case 8: + /* no endian conversion needed for single bytes */ + nw = fwrite(vpData, waveHdr.f.nChannels * sizeof(uint8_t), numFrames, f); + waveDataSize += waveHdr.f.nChannels * sizeof(uint8_t) * numFrames; + return (nw == numFrames) ? 0 : 1; + case 16: + /* TODO: endian conversion needed */ + nw = fwrite(vpData, waveHdr.f.nChannels * sizeof(int16_t), numFrames, f); + waveDataSize += waveHdr.f.nChannels * sizeof(int16_t) * numFrames; + if ( needCleanData ) + { + /* TODO: convert back endianness */ + } + return (nw == numFrames) ? 0 : 1; + } +} + + +int waveFinalizeHeader(FILE * f) +{ + if (f != stdout) { + assert( waveHdrStarted ); + waveSetCurrTime( &waveHdr.a.StopTime ); + waveHdr.d.hdr.size = waveDataSize; + waveHdr.r.hdr.size += waveDataSize; + /* fprintf(stderr, "waveFinalizeHeader(): datasize = %d\n", waveHdr.dataSize); */ + waveHdrStarted = 0; + if ( fseek(f, 0, SEEK_SET) ) + return 1; + if ( 1 != fwrite(&waveHdr, sizeof(waveFileHeader), 1, f) ) + return 1; + /* fprintf(stderr, "waveFinalizeHeader(): success writing header\n"); */ + return 0; + } + return 1; +} + +// vim: tabstop=8:softtabstop=8:shiftwidth=8:noexpandtab diff --git a/sddc_source/src/libsddc/libsddc/wavewrite.h b/sddc_source/src/libsddc/libsddc/wavewrite.h new file mode 100644 index 0000000..804c894 --- /dev/null +++ b/sddc_source/src/libsddc/libsddc/wavewrite.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2019 by Hayati Ayguen + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef __WAVEWRITE_H +#define __WAVEWRITE_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern int waveHdrStarted; + +/*! + * helper functions to write and finalize wave headers + * with compatibility to some SDR programs - showing frequency: + * raw sample data still have to be written by caller to FILE*. + * call waveWriteHeader() before writing anything to to file + * and call waveFinalizeHeader() afterwards, + * stdout/stderr can't be used, because seek to begin isn't possible. + * + */ + +void waveWriteHeader(unsigned samplerate, unsigned freq, int bitsPerSample, int numChannels, FILE * f); + +/* waveWriteFrames() writes (numFrames * numChannels) samples + * waveWriteSamples() + * both return 0, when no errors occured + */ +int waveWriteFrames(FILE* f, void * vpData, size_t numFrames, int needCleanData); +int waveWriteSamples(FILE* f, void * vpData, size_t numSamples, int needCleanData); /* returns 0, when no errors occured */ +void waveSetStartTime(time_t t, double fraction); +int waveFinalizeHeader(FILE * f); /* returns 0, when no errors occured */ + +#ifdef __cplusplus +} +#endif + +#endif /*__WAVEWRITE_H*/ diff --git a/sddc_source/src/main.cpp b/sddc_source/src/main.cpp new file mode 100644 index 0000000..9e4a4db --- /dev/null +++ b/sddc_source/src/main.cpp @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +SDRPP_MOD_INFO { + /* Name: */ "sddc_source", + /* Description: */ "SDDC source module for SDR++", + /* Author: */ "Ryzerth;pkuznetsov", + /* Version: */ 0, 1, 0, + /* Max instances */ 1 +}; + +ConfigManager config; + +const char* AGG_MODES_STR = "Off\0Low\0High\0"; + +class AirspyHFSourceModule : public ModuleManager::Instance { +public: + AirspyHFSourceModule(std::string name) { + this->name = name; + + sampleRate = 768000.0; + + handler.ctx = this; + handler.selectHandler = menuSelected; + handler.deselectHandler = menuDeselected; + handler.menuHandler = menuHandler; + handler.startHandler = start; + handler.stopHandler = stop; + handler.tuneHandler = tune; + handler.stream = &stream; + + refresh(); + + selectFirst(); + + core::setInputSampleRate(sampleRate); + + sigpath::sourceManager.registerSource("SDDC", &handler); + } + + ~AirspyHFSourceModule() { + + } + + void enable() { + enabled = true; + } + + void disable() { + enabled = false; + } + + bool isEnabled() { + return enabled; + } + + void refresh() { + devListTxt = ""; + + devCount = sddc_get_device_count(); + for (int i = 0; i < devCount; i++) { + // Open device + sddc_t* dev = sddc_open(i, "../sddc_source/res/firmwares/SDDC_FX3.img"); + if (dev == NULL) { continue; } + + // Get device name (check if implemented) + const char* name = sddc_get_hw_model_name(dev); + if (name == NULL) { + sddc_close(dev); + continue; + } + + // Add to list + char tmp[256]; + sprintf(tmp, "%s (%d)", name, i); + devNames.push_back(name); + devListTxt += name; + devListTxt += '\0'; + sddc_close(dev); + } + } + + void selectFirst() { + if (devCount != 0) { + selectById(0); + } + } + + void selectByName(std::string name) { + for (int i = 0; i < devCount; i++) { + if (devNames[i] == name) { + selectById(i); + break; + } + } + } + + void selectById(int id) { + if (id < 0 || id >= devCount) { + selectedDevName = ""; + return; + } + + devId = id; + selectedDevName = devNames[id]; + } + +private: + std::string getBandwdithScaled(double bw) { + char buf[1024]; + if (bw >= 1000000.0) { + sprintf(buf, "%.1lfMHz", bw / 1000000.0); + } + else if (bw >= 1000.0) { + sprintf(buf, "%.1lfKHz", bw / 1000.0); + } + else { + sprintf(buf, "%.1lfHz", bw); + } + return std::string(buf); + } + + static void menuSelected(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + core::setInputSampleRate(_this->sampleRate); + spdlog::info("AirspyHFSourceModule '{0}': Menu Select!", _this->name); + } + + static void menuDeselected(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + spdlog::info("AirspyHFSourceModule '{0}': Menu Deselect!", _this->name); + } + + static void start(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (_this->running) { return; } + if (_this->selectedDevName == "") { return; } + + // Start device + + _this->running = true; + spdlog::info("AirspyHFSourceModule '{0}': Start!", _this->name); + } + + static void stop(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (!_this->running) { + return; + } + _this->running = false; + _this->stream.stopWriter(); + + // Stop device + + _this->stream.clearWriteStop(); + spdlog::info("AirspyHFSourceModule '{0}': Stop!", _this->name); + } + + static void tune(double freq, void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + if (_this->running) { + // Tune device + } + _this->freq = freq; + spdlog::info("AirspyHFSourceModule '{0}': Tune: {1}!", _this->name, freq); + } + + static void menuHandler(void* ctx) { + AirspyHFSourceModule* _this = (AirspyHFSourceModule*)ctx; + float menuWidth = ImGui::GetContentRegionAvailWidth(); + + if (_this->running) { style::beginDisabled(); } + + ImGui::SetNextItemWidth(menuWidth); + if (ImGui::Combo(CONCAT("##_airspyhf_dev_sel_", _this->name), &_this->devId, _this->devListTxt.c_str())) { + // Select here + } + + if (ImGui::Combo(CONCAT("##_airspyhf_sr_sel_", _this->name), &_this->srId, _this->sampleRateListTxt.c_str())) { + _this->sampleRate = _this->sampleRateList[_this->srId]; + core::setInputSampleRate(_this->sampleRate); + // Select SR here + } + + ImGui::SameLine(); + float refreshBtnWdith = menuWidth - ImGui::GetCursorPosX(); + if (ImGui::Button(CONCAT("Refresh##_airspyhf_refr_", _this->name), ImVec2(refreshBtnWdith, 0))) { + _this->refresh(); + // Reselect and reset samplerate if it changed + } + + if (_this->running) { style::endDisabled(); } + + // All other controls + + } + + std::string name; + bool enabled = true; + dsp::stream stream; + double sampleRate; + SourceManager::SourceHandler handler; + bool running = false; + double freq; + int devId = 0; + int srId = 0; + sddc_t* openDev; + + int devCount = 0; + std::vector devNames; + std::string selectedDevName = ""; + std::string devListTxt; + std::vector sampleRateList; + std::string sampleRateListTxt; +}; + +MOD_EXPORT void _INIT_() { + json def = json({}); + def["devices"] = json({}); + def["device"] = ""; + config.setPath(options::opts.root + "/sddc_config.json"); + config.load(def); + config.enableAutoSave(); +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new AirspyHFSourceModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(ModuleManager::Instance* instance) { + delete (AirspyHFSourceModule*)instance; +} + +MOD_EXPORT void _END_() { + config.disableAutoSave(); + config.save(); +} \ No newline at end of file