Can anyone shed some light how Soapy driver sets frequencies?

I was pretty surprised it is possible with the soapy driver to get two receivers on different frequencies(running at 700kS/s via wdsp library). As they have the same LO I’m guessing the adc is actually running at a much faster rate (maybe max?) and the fpga is giving us the relevant chunk of the total bandwidth.

Coming back to single RX use case this creates an uncertainty when LO will actually move during frequency change. What will it be initially? (judging by leakage it is probably set right on target when first started and frequency is requested).

I’ve found information somewhere we can have two receivers on different frequencies as long as they are within one 61mhz block which would suggest the adc is running at max rate.

So based on that with one receiver it should be possible to park the LO at let’s say 429.9MHz, calibrate the rx and have the entire 430 to 440mhz available to move within without having LO move (and mess with calibration).

Does anyone know if this is right? If so what soapy api to use?

Edit: Just to clarify, the library I’m using on top WDSP has a feature where I can move the frequency within the sdr bandwidth while keeping the sdr at one frequency . They call it CTUN. I’m looking for the exact same thing but at soapy/lime level. (as WDSP is limited to 700kS/s on my hw).

Soapy has such comment above its API:

/*******************************************************************
 * Frequency API
 ******************************************************************/

/*!
 * Set the center frequency of the chain.
 *  - For RX, this specifies the down-conversion frequency.
 *  - For TX, this specifies the up-conversion frequency.
 *
 * The default implementation of setFrequency() will tune the "RF"
 * component as close as possible to the requested center frequency.
 * Tuning inaccuracies will be compensated for with the "BB" component.
 *
 * The args can be used to augment the tuning algorithm.
 *  - Use "OFFSET" to specify an "RF" tuning offset,
 *    usually with the intention of moving the LO out of the passband.
 *    The offset will be compensated for using the "BB" component.
 *  - Use the name of a component for the key and a frequency in Hz
 *    as the value (any format) to enforce a specific frequency.
 *    The other components will be tuned with compensation
 *    to achieve the specified overall frequency.
 *  - Use the name of a component for the key and the value "IGNORE"
 *    so that the tuning algorithm will avoid altering the component.
 *  - Vendor specific implementations can also use the same args to augment
 *    tuning in other ways such as specifying fractional vs integer N tuning.
 *
 * \param device a pointer to a device instance
 * \param direction the channel direction RX or TX
 * \param channel an available channel on the device
 * \param frequency the center frequency in Hz
 * \param args optional tuner arguments
 * \return an error code or 0 for success

Which seems one can use this OFFSETT to accomplish this, but I found no examples online how to actually use it, what the argument should look like etc.

With the soapy library, you can set the center frequency to 435 mhz and if you have a sample rate of 20 mhz , you can access frequencies from 425 mhz to 445 mhz by down converting any frequency or many frequencies in that range.

The WDSP library is very complex - it may be easier to just use the Soapy
routines directly. The fftReceive.cpp program at -

could be modified to output many frequencies.

Thank you for answering… That already better than 90% other posts I ever posted on this forum :slight_smile:

But I’m looking for examples of how to do it with Soapy. Not the information it can be done. It is clear that it can be done, but I never found any examples let alone explanation how it works. Sure one can spend a week of work going throught the source. If this was my job or I had more spare time I wprobably would, but my hope (probably futile seeing how this guy: Dual RX with SoapySDR on different frequencies - #6 by KarlL never got an answer in March 2018) is that 5 years later this knowledge is slightly more widely disseminated and someone will feel like sharing.

I have no difficulty with WDSP. That part has already been written and works great with different hardware. I just need to give it/receive samples. It is up to me to decide how. Soapy or any other way. Soapy seems the most mature (and a lot of the code is already written and works for other SDRs that support soapy). So all I’m trying to do it to make it work with Lime.

What this post is specifically about is how Soapy sets frequencies. How does it decice LO frequency when two RX (or an RX and a TX channel) request different frequencies? How do I force a single LO ? Should I use the OFFSETT and the RF method or the BB method? BTW I’m using C not C++, but if someone has C++ code that also would be very useful.

TX and RX sides have their own synthesisers and are supposed to only share the reference frequency so why is it that the TX LO jumps around when the RX frequency is changed with soapy?

Unfortunately (unless I missed something) the file you mention - fftReceive.cpp doesn’t seem to use the API I ask about. It setts an offset with NCO by using some API unkown to me (“liquid”?). I have no interest in adding another dependency. I’d like to figure out how SOAPY does what it does so I can use it effectively.

SoapySDR can receive RX signals but, it has no way to decode the RX signals that is what liquid does and I assume that is what WDSP does.
Is your Soapy some thing different than SoapySDR ?

Setting the Soapy RX center frequency is easy -

device->setFrequency(SOAPY_SDR_RX, channel, fc);

, but there is some work around it to actually use it.

I don’t think you understood my question.

Lime SDR has multiple frequency components. Some are shared between channels, some are shared for the entire device, some are shared only between RX and TX.

When you set frequency like this:
device->setFrequency(SOAPY_SDR_RX, channel, fc);
and all you are using in one channel it will set the LO (in Lime driver called RF - it is shared between two RX channels) to 435.000MHz indeed.

But if you have two RX channels and you set one to lets say 435MHz and the other to 440MHz it may set:
RX1: LO-435MHz, NCO: 0Hz
RX2: LO-435MHz,NCO: +5MHz
Because the LO (also called RF) is shared. But the problem is there are many ways this can be set. It can be like I showed above, or it might be like this:
RX1: LO-430MHz, NCO: +5MHz
RX2: LO-430MHz,NCO: +10MHz
Or any other value. I’m asking how is it choosing the component frequencies and how to set them static while allowing to tune the others.

There is this API:

/*!
 * Tune the center frequency of the specified element.
 *  - For RX, this specifies the down-conversion frequency.
 *  - For TX, this specifies the up-conversion frequency.
 *
 * Recommended names used to represent tunable components:
 *  - "CORR" - freq error correction in PPM
 *  - "RF" - frequency of the RF frontend
 *  - "BB" - frequency of the baseband DSP
 *
 * \param device a pointer to a device instance
 * \param direction the channel direction RX or TX
 * \param channel an available channel on the device
 * \param name the name of a tunable element
 * \param frequency the center frequency in Hz
 * \param args optional tuner arguments
 * \return an error code or 0 for success
 */
SOAPY_SDR_API int SoapySDRDevice_setFrequencyComponent(SoapySDRDevice *device, const int direction, const size_t channel, const char *name, const double frequency, const SoapySDRKwargs *args);

But the problem is when you set one component like this there is no tuning process. It just sets one component that’s all you get. Normally when you set a frequency you get tuning which adjusts the settings so you get the correct frequency. When you use this API there is no tuning of the component you set. There cannot be, because there is no way to specify the target.

Then we have this API I quoted at the very beginning that promises to do EXACTLY what I wanted. To set RF to my chosen frequency and tune other components to match the target. But it is not working

I tried:

SoapySDRKwargs args = {};
args.size = 0;
args.keys = NULL;
args.vals = NULL;
SoapySDRKwargs_set(&args, "OFFSET", "-1e6");
int rc = SoapySDRDevice_setFrequency(soapy_device, SOAPY_SDR_RX, rx->adc, f, &args);

It returns a success, but all it does is it sets the RF to the target and does the same thing as if I never sent the OFFSET with two RXs.

Edit: By now I looked at the source and it is clear this function with OFFSET is not supported by SoapyLMS7 driver.

Ok, if you are using the limeSDR - when you set the center frequency for one RX or TX channel you set them both, but that does not stop you from transmitting and receiving on more than one frequency in the ± 0.5 sample rate range. The Sdrplay RspDuo lets you split the channels, not the LimeSDR.
Examples of transmitting on two frequencies at once are simpleTXTwo.cpp and simpleTXTwoFFT.cpp (these should work with limeSDR if it is selected as the transmit device). These are available at rfspace.

RX and TX are separated, so you can receive on one frequency and transmit on a completely different frequency.

The sampling rates between RX and TX are not fully separated, it basically needs to be multiple of 2 if I remember correctly. So eg both RX and TX are 30MS/s, or one is 15 and the other 30. I expect that internally they are the same, but then some decimation is done. As a result, first set both sampling rates, then check their values, as setting one may reset the other, then start streaming.

When it comes to setting the frequency (for RX or TX) in dual channel, the LimeSuite code (SoapySDR is just a wrapper around LimeSuite) most probably tries to make sure both frequencies can be reached at the same time, but if you set channel 1 to 1GHz then channel 2 to 2GHz, I expect that you’ll either get an error, or channel 1 will be modified to be closer to channel 2.

So based on that with one receiver it should be possible to park the LO at let’s say 429.9MHz, calibrate the rx and have the entire 430 to 440mhz available to move within without having LO move (and mess with calibration).
Does anyone know if this is right? If so what soapy api to use?

If you’re interested in 430-440, I would set the LO to 435 and then the NCO to whatever is necessary. Even though you only want 700kS/s, I expect you’ll need at least 10MS/s for the 10MHz band you’re interested in. So similarly to the post you mentioned, I expect you to be able to do something like

setFrequency(RX, 0, "RF", 435e6, null);
setFrequency(RX, 1, "RF", 435e6, null);
setFrequency(RX, 0, "BB", -1e6, null); // shift by -1MHz to listen to 434MHz
setFrequency(RX, 1, "BB", +4e6, null); // shit by +4MHz to listen to 439MHz

If you want channel 1 to handle 430-440 and channel 2 420-430, you set the LO to 430, sampling rate to 20MS/s, then set the NCO of channel 1 to be between 0 and 10MHz, and NCO of channel 2 between 0 and -10MHz.

Thank you for the answer. Yes that is more or less what I’m doing(plus a lot of oversampling). I’m using only one TX channel so currently that is not using the NCO. Perhaps I will too for faster frequency switching. I was confused the OFFSET didn’t work.

As for how the SDR chooses the LO after quickly skimming the code it appears it tries to put the LO in the center between the two requested frequencies.

The idea with parking the LO outside the band was that it will not cause any noise in the band (so it is easier to filter out)

It makes sense to try to put the LO in the center of the 2 frequencies, but indeed if you want to avoid having some DC offset, and you can sample fast enough, parking the LO outside your band of interest is a good idea.

When using NCO for faster frequency switching, you may want to check Frequency hopping transmit [FIXED] - #13 by KarlL as well as Max NCO, relation to sampling rate, and NCO effect for more general info on what can be done with NCO.

1 Like

Thank @KarlL for pointing me towards these two threads. BTW, did you ever discover what was meant when @Zack said using NCO close to its max/min setting will result in the prformance not being very good?

If anything the NCO should get better not worse the further we get as the filter has the easier job of removing the negative image and the leaking NCO frequency.
As we’re talking about the NCO, could someone please clarify what units are these in the LM7002M datasheet about the filter used by the NCO? Magnitude has a unit of dB, but Frequerncy is unitless. Is this a ratio of NCo frequency or what?

Are you referring to the following?

The maximum frequency NCO can generate is Fsampling/2, but performance will be not very good.

It makes sense that as you get closer to Fsampling/2 (Nyquist Frequency), the precision decreases.

As for the graph, as the “frequency” goes from 0 to 0.5, I expect it (without reading the datasheet) to be the ratio of the sampling rate to the Nyquist rate. As you shouldn’t go above Fsampling/2, it only goes to 0.5.

Are you referring to the following?

The maximum frequency NCO can generate is Fsampling/2, but performance will be not very good.

Yes, thanks.