SoapyMultiSDR
I'm seeing some interesting stuff done using multiple RTL-SDR's combined in the last few months; reviewing liquid's oversampled firpfbch2 and regular firpfbch (polyphase filterbank channelizer) it seems possible to utilize them to combine multiple streams in this manner.
I'm thinking instead of dirtying up the SoapyRTLSDR repo a SoapyMultiRTL repo would be a good one to set up and try.
Thoughts?
I was thinking about the same thing actually, though not for RTLs only. I'm not a big fan of trying to cram multiple devices into a single wrapper for MIMO reasons because its a pain from the development end. That puts the burden on the API user, which often just means more for loops. But I can see where the existing interface would be nice to just re-use for many devices, so...
What if there was a SoapyMulti support driver that worked kind of like the Osmo block top level does now. It would take a bunch of device args to list the internal devices, and then it would summon the underlying SoapySDR device pointers for the internal devices, and reimplement all of the settings calls to distribute them to the correct destination device. Streaming as well, which might get a little hairy with non-homogenous devices and different stream MTUs, but we can memcpy....
Hows that sound?
That's a more robust plan than what I had in mind -- If I read that correctly it would be simplest (configuration-wise) to only support devices of the same module so there's one configuration proxied to the devices with some sort of management for bandwidth, frequency, etc. between them?
Mixing devices would be neat but I'm guessing that would increase the complexity significantly :)
For the most part I imagine only supporting the same type of device. I think it would pretty much just work with heterogeneous devices, but some stream configurations might be awkward. I think the default args should be simple to reflect using the same type of device, but there could be an advanced mode to support heterogeneous stuff or added later. But basically the settings and streaming are going to look pretty generic, and I think there is a straightforward way to implement them.
The constructor Make the device 0, 1, and ... N-1 according to args specific.
The settings* All of the calls take channel indexes, so if the first device has two channels, then 0 and 1 get mapped to device 0, if the second device has 3 channels, then 2, 3, 4 gets mapped to device 1, etc... A vector of internal device pointers for each possible channel would handle this mapping pretty easily.
Streaming A stream is created with a list of channels. So we make streams with the internal devices converting the external channel number to the internal one for each device. A SoapyMultiSDR Stream object imply has pointers to N internal Stream objects.
The readStream call itself calls each internal stream readStream() call sequentially (could be in parallel with threads) on each of the buffers passed in. Even with homogeneous devices, we need to make sure the same amount is read off each channel or do some accounting. Same basic idea with writeStream.
Some hand-wavy fixes for the variable read amounts
- We could work with memcpy to save remainders when one stream gets more than the others and copy it back in at the start of the next read -- this involves some annoying accounting of course.
- Always fully fill the entire presented buffer, calling each stream until we get all the requested samples.
- If the input stream is at least MTU size, we can read with SOAPY_SDR_ONE_PACKET, though not always implemented by all devices.
SoapyMultiSDR sounds like a good name. I can go ahead and make the project. It sounds like its going to be more or less on-par with how I maintain SoapyRemote -- which is to add new matching calls when SoapySDR gets additions. Its going to be a bit annoying to go through all the calls, but I think I can plow through it in a weekend once I get some time :-)
Also global settings I was also thinking that some of the calls for global settings (like GPIO) for example might need to have a device index suffix appended to the available names. So device 0 GPIO bank foo will be renamed to foo0, so it doesnt conflict with device 1's foo GPIO which is foo1 now.
Nice, that all sounds good to me; I'll leave it to you to get the project rolling 👍
This sounds really cool!
I recently pulled in support for synchronized TRX triggers on the bladeRF, allowing N devices to be sample-synchronized (not phase-aligned, but a constant phase-offset). I could help in getting this up and running in SoapyMultiSDR as well.
To make this happen, I will need to think about how to integrate the following into Soapy:
- Configuring the reference clock master/slave
- Configuring the trigger signal master/slave
- Firing the synchronization trigger once the streams have been started on all devices
@guruofquality - I realize each of these is probably going to require some more thought on my end (and likely separate issue tracker items)... just wanted to chime in so you could delegate some tasks. ;)
I started the repo, its empty now, but it will be a good place to put any relevant issues: https://github.com/pothosware/SoapyMultiSDR
Presenting the argument naming convention and inception support, feel free to comment before I do something terrible :-)
Other than documenting this basic usage on the wiki, SoapyMultiSDR is basically functional from a settings standpoint. The streaming is currently very dumb about heterogeneous stuff happening. So I will have to do something smarter there. But if you wanted to start experimenting with using the driver, I think its in a good place. Here is an example probe:
- You can see that the find arguments specify the driver as multi, and specify indexed serials to identify the devices:
driver=multi,serial[0]=1234567890,serial[1]=etc - Calls that worked with keys for unordered maps, or device global component names are indexed to match their respective device,
key[index] - And calls that worked with single strings for device-wide arguments (like clock source, time source, or driver key) use a CSV format:
dev0Thing, dev1Thing
SoapySDRUtil --find
######################################################
## Soapy SDR -- the SDR abstraction library
######################################################
Found device 0
backend = libusb
device = 0x02:0x0D
driver = bladerf
instance = 0
serial = fc73b8339f9faed4e577b516adc19104
Found device 1
backend = libusb
device = 0x02:0x0F
driver = bladerf
instance = 1
serial = 7db5b78c31a3c05fa40055e813f91dc2
jblum@bigbear:~/build/SoapyMultiSDR$ SoapySDRUtil --probe="driver=multi,serial[0]=fc73b8339f9faed4e577b516adc19104,serial[1]=7db5b78c31a3c05fa40055e813f91dc2"
######################################################
## Soapy SDR -- the SDR abstraction library
######################################################
Probe device driver=multi,serial[0]=fc73b8339f9faed4e577b516adc19104,serial[1]=7db5b78c31a3c05fa40055e813f91dc2
[INFO] Making device 0...
[INFO] bladerf_open_with_devinfo()
[INFO] bladerf_get_serial() = fc73b8339f9faed4e577b516adc19104
[INFO] setSampleRate(1, 1.000000 MHz), actual = 1.000000 MHz
[INFO] setSampleRate(0, 1.000000 MHz), actual = 1.000000 MHz
[INFO] Making device 1...
[INFO] bladerf_open_with_devinfo()
[INFO] bladerf_get_serial() = 7db5b78c31a3c05fa40055e813f91dc2
[INFO] setSampleRate(1, 1.000000 MHz), actual = 1.000000 MHz
[INFO] setSampleRate(0, 1.000000 MHz), actual = 1.000000 MHz
----------------------------------------------------
-- Device identification
----------------------------------------------------
driver=bladeRF, bladeRF
hardware=bladeRF, bladeRF
fpga_size[0]=40
fpga_size[1]=40
fpga_version[0]=0.5.0
fpga_version[1]=0.5.0
fw_version[0]=1.9.0
fw_version[1]=1.9.0
serial[0]=fc73b8339f9faed4e577b516adc19104
serial[1]=7db5b78c31a3c05fa40055e813f91dc2
----------------------------------------------------
-- Peripheral summary
----------------------------------------------------
Channels: 2 Rx, 2 Tx
Timestamps: YES
Other Settings:
* XB200 Transverter - Device0 - bladeRF XB200 Transverter Board
[key=xb200[0], default=disabled, type=string, options=(disabled, 50M, 144M, 222M, auto1db, auto3db, auto, custom)]
* Sampling Mode - Device0 - Internal = Via RX/TX connectors, External = Direct sampling from J60/J61 connectors
[key=sampling_mode[0], default=internal, type=string, options=(internal, external)]
* Loopback Mode - Device0 - Enable/disable internal loopback
[key=loopback[0], default=disabled, type=string, options=(disabled, firmware, bb_txlpf_rxvga2, bb_txvga1_rxvga2, bb_txlpf_rxlpf, bb_txvga1_rxlpf, rf_lna1, rf_lna2, rf_lna3)]
* XB200 Transverter - Device1 - bladeRF XB200 Transverter Board
[key=xb200[1], default=disabled, type=string, options=(disabled, 50M, 144M, 222M, auto1db, auto3db, auto, custom)]
* Sampling Mode - Device1 - Internal = Via RX/TX connectors, External = Direct sampling from J60/J61 connectors
[key=sampling_mode[1], default=internal, type=string, options=(internal, external)]
* Loopback Mode - Device1 - Enable/disable internal loopback
[key=loopback[1], default=disabled, type=string, options=(disabled, firmware, bb_txlpf_rxvga2, bb_txvga1_rxvga2, bb_txlpf_rxlpf, bb_txvga1_rxlpf, rf_lna1, rf_lna2, rf_lna3)]
GPIOs: CONFIG[0], EXPANSION[0], CONFIG[1], EXPANSION[1]
----------------------------------------------------
-- RX Channel 0
----------------------------------------------------
Full-duplex: YES
Supports AGC: NO
Stream formats: CS16, CF32
Native format: CS16 [full-scale=2048]
Stream args:
* Buffer Count - Number of async USB buffers.
[key=buffers, units=buffers, default=32, type=int]
* Buffer Length - Number of bytes per USB buffer, the number must be a multiple of 1024.
[key=buflen, units=bytes, default=4096, type=int]
* Num Transfers - Number of async USB transfers. Use 0 for automatic
[key=transfers, units=bytes, default=0, type=int, range=[0, 32]]
Antennas: RX
Full gain range: [0, 61] dB
LNA gain range: [0, 6] dB
VGA1 gain range: [5, 30] dB
VGA2 gain range: [0, 30] dB
Full freq range: [237.5, 3800] MHz
RF freq range: [237.5, 3800] MHz
Sample rates: [0.16, 40] MHz
Filter bandwidths: [1.5, 28] MHz
----------------------------------------------------
-- RX Channel 1
----------------------------------------------------
Full-duplex: YES
Supports AGC: NO
Stream formats: CS16, CF32
Native format: CS16 [full-scale=2048]
Stream args:
* Buffer Count - Number of async USB buffers.
[key=buffers, units=buffers, default=32, type=int]
* Buffer Length - Number of bytes per USB buffer, the number must be a multiple of 1024.
[key=buflen, units=bytes, default=4096, type=int]
* Num Transfers - Number of async USB transfers. Use 0 for automatic
[key=transfers, units=bytes, default=0, type=int, range=[0, 32]]
Antennas: RX
Full gain range: [0, 61] dB
LNA gain range: [0, 6] dB
VGA1 gain range: [5, 30] dB
VGA2 gain range: [0, 30] dB
Full freq range: [237.5, 3800] MHz
RF freq range: [237.5, 3800] MHz
Sample rates: [0.16, 40] MHz
Filter bandwidths: [1.5, 28] MHz
----------------------------------------------------
-- TX Channel 0
----------------------------------------------------
Full-duplex: YES
Supports AGC: NO
Stream formats: CS16, CF32
Native format: CS16 [full-scale=2048]
Stream args:
* Buffer Count - Number of async USB buffers.
[key=buffers, units=buffers, default=32, type=int]
* Buffer Length - Number of bytes per USB buffer, the number must be a multiple of 1024.
[key=buflen, units=bytes, default=4096, type=int]
* Num Transfers - Number of async USB transfers. Use 0 for automatic
[key=transfers, units=bytes, default=0, type=int, range=[0, 32]]
Antennas: TX
Full gain range: [0, 56] dB
VGA1 gain range: [-35, -4] dB
VGA2 gain range: [0, 25] dB
Full freq range: [237.5, 3800] MHz
RF freq range: [237.5, 3800] MHz
Sample rates: [0.16, 40] MHz
Filter bandwidths: [1.5, 28] MHz
----------------------------------------------------
-- TX Channel 1
----------------------------------------------------
Full-duplex: YES
Supports AGC: NO
Stream formats: CS16, CF32
Native format: CS16 [full-scale=2048]
Stream args:
* Buffer Count - Number of async USB buffers.
[key=buffers, units=buffers, default=32, type=int]
* Buffer Length - Number of bytes per USB buffer, the number must be a multiple of 1024.
[key=buflen, units=bytes, default=4096, type=int]
* Num Transfers - Number of async USB transfers. Use 0 for automatic
[key=transfers, units=bytes, default=0, type=int, range=[0, 32]]
Antennas: TX
Full gain range: [0, 56] dB
VGA1 gain range: [-35, -4] dB
VGA2 gain range: [0, 25] dB
Full freq range: [237.5, 3800] MHz
RF freq range: [237.5, 3800] MHz
Sample rates: [0.16, 40] MHz
Filter bandwidths: [1.5, 28] MHz
[INFO] bladerf_close()
[INFO] bladerf_close()