From 58618ad29abf3863328484363dc50765ba766f21 Mon Sep 17 00:00:00 2001 From: Ryan Thompson Date: Fri, 7 Mar 2025 15:19:24 -0600 Subject: [PATCH 1/4] added setting for oversample feature --- bladeRF_Settings.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/bladeRF_Settings.cpp b/bladeRF_Settings.cpp index 3ccd211..b6b0a04 100644 --- a/bladeRF_Settings.cpp +++ b/bladeRF_Settings.cpp @@ -1069,6 +1069,13 @@ SoapySDR::ArgInfoList bladeRF_SoapySDR::getSettingInfo(void) const if (_isBladeRF1) setArgs.push_back(xb200SettingArg); + SoapySDR::ArgInfo oversampleArg; + oversampleArg.key = "oversample"; + oversampleArg.value = "false"; + oversampleArg.name = "Oversample"; + oversampleArg.description = "Enable oversampling in FPGA"; + oversampleArg.type = SoapySDR::ArgInfo::BOOL; + // Sampling mode SoapySDR::ArgInfo samplingModeArg; samplingModeArg.key = "sampling_mode"; @@ -1199,6 +1206,15 @@ SoapySDR::ArgInfoList bladeRF_SoapySDR::getSettingInfo(void) const setArgs.push_back(biasTeeRx); + // Oversampling + SoapySDR::ArgInfo oversample; + oversample.key = "oversample"; + oversample.value = "false"; + oversample.name = "Enable oversampling"; + oversample.description = "Enables oversampling in FPGA"; + oversample.type = SoapySDR::ArgInfo::BOOL; + setArgs.push_back(oversample); + return setArgs; } @@ -1234,6 +1250,14 @@ std::string bladeRF_SoapySDR::readSetting(const std::string &key) const return "false"; } else if (key == "biastee_rx") { return "false"; + } else if (key == "oversample") { + bladerf_feature feature; + int ret = bladerf_get_feature(_dev, &feature); + if (ret != 0) { + SoapySDR::logf(SOAPY_SDR_ERROR, "bladerf_get_feature(BLADERF_FEATURE_OVERSAMPLE) returned %s", _err2str(ret).c_str()); + throw std::runtime_error("readSetting() " + _err2str(ret)); + } + return feature == BLADERF_FEATURE_OVERSAMPLE ? "true" : "false"; } SoapySDR_logf(SOAPY_SDR_WARNING, "Unknown setting '%s'", key.c_str()); @@ -1552,6 +1576,17 @@ void bladeRF_SoapySDR::writeSetting(const std::string &key, const std::string &v } } } + else if (key == "oversample") { + bool enable = (value == "true"); + int ret = bladerf_enable_feature(_dev, BLADERF_FEATURE_OVERSAMPLE, enable); + if (ret != 0) + { + SoapySDR::logf(SOAPY_SDR_ERROR, "bladerf_enable_feature(OVERSAMPLE, %s) returned %s", + value.c_str(), _err2str(ret).c_str()); + throw std::runtime_error("writeSetting() " + _err2str(ret)); + } + SoapySDR::logf(SOAPY_SDR_INFO, "bladerf_enable_feature(OVERSAMPLE, %s)", value.c_str()); + } else { throw std::runtime_error("writeSetting(" + key + ") unknown setting"); From 25864e23e40ab858985f666e6a062e4ebe208198 Mon Sep 17 00:00:00 2001 From: Ryan Thompson Date: Mon, 10 Mar 2025 16:15:28 -0500 Subject: [PATCH 2/4] replace stream arg "meta" for "format" allowing direct format assignment --- bladeRF_SoapySDR.hpp | 1 + bladeRF_Streaming.cpp | 80 +++++++++++++++++++++++++++++-------------- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/bladeRF_SoapySDR.hpp b/bladeRF_SoapySDR.hpp index c05c985..d52a64b 100644 --- a/bladeRF_SoapySDR.hpp +++ b/bladeRF_SoapySDR.hpp @@ -379,6 +379,7 @@ class bladeRF_SoapySDR : public SoapySDR::Device std::string _xb200Mode; std::string _samplingMode; std::string _loopbackMode; + bladerf_format _sample_format; bladerf *_dev; diff --git a/bladeRF_Streaming.cpp b/bladeRF_Streaming.cpp index 2e394fa..6423553 100644 --- a/bladeRF_Streaming.cpp +++ b/bladeRF_Streaming.cpp @@ -74,16 +74,15 @@ SoapySDR::ArgInfoList bladeRF_SoapySDR::getStreamArgsInfo(const int, const size_ xfersArg.range = SoapySDR::Range(0, 32); streamArgs.push_back(xfersArg); - SoapySDR::ArgInfo metaArg; - xfersArg.key = "meta"; - xfersArg.value = "auto"; - xfersArg.name = "Meta mode"; - xfersArg.description = "Timestamp and burst streaming mode.\n" - "Automatic: meta in single channel mode, meta off in dual channel mode"; - xfersArg.type = SoapySDR::ArgInfo::STRING; - xfersArg.options = {"auto", "meta", "normal"}; - xfersArg.optionNames = {"Automatic", "Metadata Streams", "Normal Streams"}; - streamArgs.push_back(metaArg); + SoapySDR::ArgInfo formatArg; + formatArg.key = "format"; + formatArg.value = "sc16_meta"; + formatArg.name = "Sample Format"; + formatArg.description = "Sample format (sc16, sc16_meta, sc8, sc8_meta, sc16_packed)"; + formatArg.type = SoapySDR::ArgInfo::STRING; + formatArg.options = {"sc16", "sc16_meta", "sc8", "sc8_meta", "sc16_packed"}; + formatArg.optionNames = {"16-bit", "16-bit with Metadata", "8-bit", "8-bit with Metadata", "Packed 16-bit"}; + streamArgs.push_back(formatArg); return streamArgs; } @@ -97,29 +96,42 @@ SoapySDR::Stream *bladeRF_SoapySDR::setupStream( auto channels = channels_; if (channels.empty()) channels.push_back(0); - //meta mode, automatically on in single channel mode - auto metaMode = (args.count("meta") == 0)? "auto" : args.at("meta"); - bladerf_format sync_format = BLADERF_FORMAT_SC16_Q11; - if (metaMode == "meta") sync_format = BLADERF_FORMAT_SC16_Q11_META; - if (metaMode == "normal") sync_format = BLADERF_FORMAT_SC16_Q11; + auto sampleFormat = (args.count("format") == 0)? "sc16_meta" : args.at("format"); + + if (sampleFormat == "sc16") { + _sample_format = BLADERF_FORMAT_SC16_Q11; + } else if (sampleFormat == "sc16_meta") { + _sample_format = BLADERF_FORMAT_SC16_Q11_META; + } else if (sampleFormat == "sc8") { + _sample_format = BLADERF_FORMAT_SC8_Q7; + } else if (sampleFormat == "sc8_meta") { + _sample_format = BLADERF_FORMAT_SC8_Q7_META; + } else if (sampleFormat == "sc16_packed") { + _sample_format = BLADERF_FORMAT_SC16_Q11_PACKED; + } else { + std::stringstream err; + err << "Invalid sample format: '" << sampleFormat << "'\n" + << "Valid formats: [sc16, sc16_meta, sc8, sc8_meta, sc16_packed]"; + throw std::runtime_error(err.str()); + } //check the channel configuration bladerf_channel_layout layout; if (channels.size() == 1 and (channels.at(0) == 0 or channels.at(0) == 1)) { layout = (direction == SOAPY_SDR_RX)?BLADERF_RX_X1:BLADERF_TX_X1; - if (metaMode == "auto") sync_format = BLADERF_FORMAT_SC16_Q11_META; } else if (channels.size() == 2 and channels.at(0) == 0 and channels.at(1) == 1) { layout = (direction == SOAPY_SDR_RX)?BLADERF_RX_X2:BLADERF_TX_X2; - if (metaMode == "auto") sync_format = BLADERF_FORMAT_SC16_Q11; } else { throw std::runtime_error("setupStream invalid channel selection"); } + SoapySDR::logf(SOAPY_SDR_INFO, "Sample format: %s", bladerf_format_to_string(_sample_format)); + //check the format if (format == SOAPY_SDR_CF32) {} else if (format == SOAPY_SDR_CS16) {} @@ -145,7 +157,7 @@ SoapySDR::Stream *bladeRF_SoapySDR::setupStream( int ret = bladerf_sync_config( _dev, layout, - sync_format, + _sample_format, numBuffs, bufSize, numXfers, @@ -353,6 +365,8 @@ int bladeRF_SoapySDR::readStream( for (size_t i = 0; i < 2 * numElems; i++) { output[i] = float(_rxConvBuff[i])/2048; + if (_sample_format == BLADERF_FORMAT_SC8_Q7 || _sample_format == BLADERF_FORMAT_SC8_Q7_META) + output[i] = float(((int8_t*)(_rxConvBuff))[i])/128; } } else if (not _rxFloats and _rxChans.size() == 2) @@ -361,10 +375,17 @@ int bladeRF_SoapySDR::readStream( int16_t *output1 = (int16_t *)buffs[1]; for (size_t i = 0; i < 4 * numElems;) { - *(output0++) = _rxConvBuff[i++]; - *(output0++) = _rxConvBuff[i++]; - *(output1++) = _rxConvBuff[i++]; - *(output1++) = _rxConvBuff[i++]; + if (_sample_format == BLADERF_FORMAT_SC8_Q7 || _sample_format == BLADERF_FORMAT_SC8_Q7_META) { + *(output0++) = ((int8_t*)(_rxConvBuff))[i++]; + *(output0++) = ((int8_t*)(_rxConvBuff))[i++]; + *(output1++) = ((int8_t*)(_rxConvBuff))[i++]; + *(output1++) = ((int8_t*)(_rxConvBuff))[i++]; + } else { + *(output0++) = _rxConvBuff[i++]; + *(output0++) = _rxConvBuff[i++]; + *(output1++) = _rxConvBuff[i++]; + *(output1++) = _rxConvBuff[i++]; + } } } else if (_rxFloats and _rxChans.size() == 2) @@ -373,10 +394,17 @@ int bladeRF_SoapySDR::readStream( float *output1 = (float *)buffs[1]; for (size_t i = 0; i < 4 * numElems;) { - *(output0++) = float(_rxConvBuff[i++])/2048; - *(output0++) = float(_rxConvBuff[i++])/2048; - *(output1++) = float(_rxConvBuff[i++])/2048; - *(output1++) = float(_rxConvBuff[i++])/2048; + if (_sample_format == BLADERF_FORMAT_SC8_Q7 || _sample_format == BLADERF_FORMAT_SC8_Q7_META) { + *(output0++) = float(((int8_t*)(_rxConvBuff))[i++])/128; + *(output0++) = float(((int8_t*)(_rxConvBuff))[i++])/128; + *(output1++) = float(((int8_t*)(_rxConvBuff))[i++])/128; + *(output1++) = float(((int8_t*)(_rxConvBuff))[i++])/128; + } else { + *(output0++) = float(_rxConvBuff[i++])/2048; + *(output0++) = float(_rxConvBuff[i++])/2048; + *(output1++) = float(_rxConvBuff[i++])/2048; + *(output1++) = float(_rxConvBuff[i++])/2048; + } } } From ae19000a6279c16e9092dee5a2ae0b9ce5997d09 Mon Sep 17 00:00:00 2001 From: Ryan Thompson Date: Fri, 7 Mar 2025 16:29:06 -0600 Subject: [PATCH 3/4] added example and oversample feature support instructions --- README.md | 16 +++++++- example.c | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 example.c diff --git a/README.md b/README.md index 2900f52..f145efd 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,18 @@ ## Licensing information -* LGPLv2.1: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt +*LGPLv2.1: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt + +## Adding OVERSAMPLE Support + +1. Install libbladeRF [link](https://github.com/Nuand/bladeRF) +2. Install Soapy [link](https://github.com/pothosware/SoapySDR/wiki/BuildGuide#unix-instructions) at tag 0.8.0 +3. Install SoapyBladeRF plugin [link](https://github.com/pothosware/SoapyBladeRF/wiki) at master +4. Build example and run + + ```bash + gcc -std=c99 ../example.c -L/usr/local/lib -lSoapySDR + ./a.out + ``` + +5. gnuradio-companion 3.10.9.2 will prioritize the local v0.8.0 Soapy lib after install diff --git a/example.c b/example.c new file mode 100644 index 0000000..750c328 --- /dev/null +++ b/example.c @@ -0,0 +1,116 @@ +#include +#include +#include //printf +#include //free +#include + +int main(void) +{ + size_t length; + + //enumerate devices + SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length); + for (size_t i = 0; i < length; i++) + { + printf("Found device #%d: ", (int)i); + for (size_t j = 0; j < results[i].size; j++) + { + printf("%s=%s, ", results[i].keys[j], results[i].vals[j]); + } + printf("\n"); + } + SoapySDRKwargsList_clear(results, length); + + //create device instance + //args can be user defined or from the enumeration result + SoapySDRKwargs args = {}; + SoapySDRKwargs_set(&args, "driver", "bladerf"); + SoapySDRDevice *sdr = SoapySDRDevice_make(&args); + SoapySDRKwargs_clear(&args); + + if (sdr == NULL) + { + printf("SoapySDRDevice_make fail: %s\n", SoapySDRDevice_lastError()); + return EXIT_FAILURE; + } + + //query device info + char** names = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_RX, 0, &length); + printf("Rx antennas: "); + for (size_t i = 0; i < length; i++) printf("%s, ", names[i]); + printf("\n"); + SoapySDRStrings_clear(&names, length); + + names = SoapySDRDevice_listGains(sdr, SOAPY_SDR_RX, 0, &length); + printf("Rx gains: "); + for (size_t i = 0; i < length; i++) printf("%s, ", names[i]); + printf("\n"); + SoapySDRStrings_clear(&names, length); + + SoapySDRRange *ranges = SoapySDRDevice_getFrequencyRange(sdr, SOAPY_SDR_RX, 0, &length); + printf("Rx freq ranges: "); + for (size_t i = 0; i < length; i++) printf("[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum); + printf("\n"); + free(ranges); + + //apply settings + if (SoapySDRDevice_writeSetting(sdr, "oversample", "true") != 0) + { + printf("Failed to enable oversample: %s\n", SoapySDRDevice_lastError()); + } + if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, 0, 122e6) != 0) + { + printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError()); + } + if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, 0, 912.3e6, NULL) != 0) + { + printf("setFrequency fail: %s\n", SoapySDRDevice_lastError()); + } + + // Setup stream arguments + SoapySDRKwargs streamArgs = {}; + char *key = "format"; + char *val = "sc8"; + streamArgs.size = 1; + streamArgs.keys = (char**)malloc(sizeof(char*)); + streamArgs.vals = (char**)malloc(sizeof(char*)); + streamArgs.keys[0] = key; + streamArgs.vals[0] = val; + + SoapySDRStream *rxStream = SoapySDRDevice_setupStream(sdr, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, &streamArgs); + + // Free the allocated memory + free(streamArgs.keys); + free(streamArgs.vals); + + if (rxStream == NULL) + { + printf("setupStream fail: %s\n", SoapySDRDevice_lastError()); + SoapySDRDevice_unmake(sdr); + return EXIT_FAILURE; + } + SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0); //start streaming + + //create a re-usable buffer for rx samples + complex float buff[1024]; + + //receive some samples + for (size_t i = 0; i < 10; i++) + { + void *buffs[] = {buff}; //array of buffers + int flags; //flags set by receive operation + long long timeNs; //timestamp for receive buffer + int ret = SoapySDRDevice_readStream(sdr, rxStream, buffs, 1024, &flags, &timeNs, 100000); + printf("ret=%d, flags=%d, timeNs=%lld\n", ret, flags, timeNs); + } + + //shutdown the stream + SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0); //stop streaming + SoapySDRDevice_closeStream(sdr, rxStream); + + //cleanup device handle + SoapySDRDevice_unmake(sdr); + + printf("Done\n"); + return EXIT_SUCCESS; +} From 417cb487353b6d3a4efe353a3936da2d599a0e45 Mon Sep 17 00:00:00 2001 From: Ryan Thompson Date: Fri, 4 Apr 2025 12:51:23 -0500 Subject: [PATCH 4/4] added example grc --- tb.grc | 274 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 tb.grc diff --git a/tb.grc b/tb.grc new file mode 100644 index 0000000..4a67063 --- /dev/null +++ b/tb.grc @@ -0,0 +1,274 @@ +options: + parameters: + author: '' + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: '' + description: '' + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: tb + max_nouts: '0' + output_language: python + placement: (0,0) + qt_qss_theme: '' + realtime_scheduling: '' + run: 'True' + run_command: '{python} -u {filename}' + run_options: prompt + sizing_mode: fixed + thread_safe_setters: '' + title: Not titled yet + window_size: (1000,1000) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: freq + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: '' + label: '' + min_len: '200' + orient: QtCore.Qt.Horizontal + rangeType: float + start: 70e6 + step: 5e6 + stop: 6e9 + value: '2420000000' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [288, 12.0] + rotation: 0 + state: enabled +- name: gain + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: '' + label: '' + min_len: '200' + orient: QtCore.Qt.Horizontal + rangeType: float + start: '-10' + step: '1' + stop: '60' + value: '38' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [424, 12.0] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: 61.44e6 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [184, 12] + rotation: 0 + state: enabled +- name: bladeRF_source_0 + id: bladeRF_source + parameters: + affinity: '' + alias: '' + bias_tee0: 'False' + bias_tee1: 'False' + buffers: '512' + buflen: '8192' + bw: samp_rate/2 + comment: '' + dac: '10000' + dc_calibration: LPF_TUNING + dc_offset_mode0: '0' + dc_offset_mode1: '0' + device_id: '0' + feature: default + fpga_image: '' + fpga_reload: 'False' + freq: freq + gain0: gain + gain1: '10' + gain_mode0: 'False' + gain_mode1: 'False' + if_gain0: '20' + if_gain1: '20' + in_clk: ONBOARD + iq_balance_mode0: '0' + iq_balance_mode1: '0' + lpf_mode: disabled + maxoutbuf: '0' + metadata: 'False' + minoutbuf: '0' + nchan: '1' + out_clk: 'False' + ref_clk: 10e6 + sample_format: 16bit_packed + sample_rate: samp_rate + sampling: internal + show_pmic: 'False' + smb: 38.4e6 + tamer: internal + trigger0: 'False' + trigger1: 'False' + trigger_role0: master + trigger_role1: master + trigger_signal0: J51_1 + trigger_signal1: J51_1 + use_dac: 'False' + use_ref_clk: 'False' + verbosity: verbose + xb200: none + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [40, 164.0] + rotation: 0 + state: disabled +- name: fosphor_glfw_sink_c_0 + id: fosphor_glfw_sink_c + parameters: + affinity: '' + alias: '' + comment: '' + freq_center: freq + freq_span: samp_rate + maxoutbuf: '0' + minoutbuf: '0' + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 396.0] + rotation: 0 + state: enabled +- name: qtgui_sink_x_0 + id: qtgui_sink_x + parameters: + affinity: '' + alias: '' + bw: samp_rate + comment: '' + fc: freq + fftsize: '4096' + gui_hint: '' + maxoutbuf: '0' + minoutbuf: '0' + name: '""' + plotconst: 'True' + plotfreq: 'True' + plottime: 'True' + plotwaterfall: 'True' + rate: '10' + showports: 'False' + showrf: 'False' + type: complex + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 460.0] + rotation: 0 + state: disabled +- name: soapy_source_0 + id: soapy_source + parameters: + affinity: '' + agc0: 'False' + agc1: 'False' + alias: '' + amp_gain0: '0' + ant0: RX + ant1: RX + balance0: '0' + balance1: '0' + bw0: samp_rate + bw1: samp_rate + center_freq0: freq + center_freq1: freq + clock_rate: '0' + clock_source: '' + comment: '' + correction0: '0' + correction1: '0' + dc_offset0: '0' + dc_offset1: '0' + dc_removal0: 'False' + dc_removal1: 'False' + dev: driver=rtlsdr + dev_args: '' + devname: bladerf + gain_mode0: Overall + gain_mode1: Overall + ifgr_gain: '59' + lna_gain0: '10' + lna_gain1: '10' + maxoutbuf: '0' + minoutbuf: '0' + mix_gain0: '10' + nchan: '1' + nco_freq0: '0' + nco_freq1: '0' + overall_gain0: gain + overall_gain1: gain + pga_gain0: '24' + pga_gain1: '24' + rf_gain0: '18' + rfgr_gain: '9' + rxvga1_gain: '5' + rxvga2_gain: '0' + samp_rate: samp_rate + sdrplay_agc_setpoint: '-30' + sdrplay_biastee: 'False' + sdrplay_dabnotch: 'False' + sdrplay_if_mode: Zero-IF + sdrplay_rfnotch: 'False' + settings0: '' + settings1: '' + stream_args: format=sc8_meta + tia_gain0: '0' + tia_gain1: '0' + tune_args0: '' + tune_args1: '' + tuner_gain0: '10' + type: fc32 + vga_gain0: '10' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [24, 460.0] + rotation: 0 + state: enabled + +connections: +- [bladeRF_source_0, '0', fosphor_glfw_sink_c_0, '0'] +- [soapy_source_0, '0', fosphor_glfw_sink_c_0, '0'] +- [soapy_source_0, '0', qtgui_sink_x_0, '0'] + +metadata: + file_format: 1 + grc_version: 3.10.9.2