Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Are RSP1A parameters for the SoapySDR function calls documented anywhere? #69

Open
w0dz opened this issue Apr 26, 2023 · 17 comments
Open
Assignees

Comments

@w0dz
Copy link

w0dz commented Apr 26, 2023

I have written my own Python code using SoapySDR to talk to an RSP1A, but figuring out what the parameters are has been a real problem. The SoapySDR docs show all the high level function calls, but the specifics are up to the driver. And the SoapySDR docs say "they're the same as the C calls except for the two notations below, so go look at the header file." That's not very helpful, since the actual Python parameters passed into the functions are not described anywhere. I had to guess, for example, that the setGain function wanted "IFGR" or "RFGR" for the two gain reduction parameters. And only by some severe poking around did I learn that that overloaded function works differently if you use the short form without a parameter name. And what about AGC response times for attack and decay, and AGC threshold? Where do they go? And how long do I have to wait for these commands to "take"? Pretty hard to use an API when it's not documented anywhere.

@fventuri
Copy link
Collaborator

@w0dz - first of all I am really sorry to hear that you are having problems using this SoapySDRPlay3 module with Python.

The SoapySDRPlay3 module is an attempt to provide an interface between the SDRplay APIs (whose specification guide you can find here: https://www.sdrplay.com/docs/SDRplay_API_Specification_v3.07.pdf), and the SoapySDR Device API (which is described here: https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.hpp).
As such it tries to find a trade-off between the complexity of the SDRplay API and the design of SoapySDR.
In some cases, like the specific parameters for the AGC that you mention, I decided to use SDRplay defaults since they seemed reasonable to me, and there's not really an obvious way to select them via the SoapySDR API (perhaps using the writeSettings() method?).

This said, here are a few notes that I hope you'll find useful:

  • the names of the gains and their ranges can be retrieved using the SoapySDR Device API, with the methods listGains() (https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.hpp#L692) and getGainRange() (https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.hpp#L768)
  • if you haven't started streaming yet (i.e. before calling activateStream()), all the changes are effective immediately; on the other hand if you change a value while streaming, then the SoapySDRPlay3 module has to invoke the function sdrplay_api_Update(), and in some cases (changes to the center frequency, sample rate, and gains) there's some delay between the call to sdrplay_api_Update() and the parameter being actually changed. The good news is that this module has code to wait for the value to be changed before returning from the function (see here for example: https://github.com/pothosware/SoapySDRPlay3/blob/master/Settings.cpp#L627-L637)
  • most of the SoapySDR API calls take a parameter called direction (which should always be set to SOAPY_SDR_RX), a channel number (which should always be 0, unless you are using an RSPduo in dual tuner mode), and a few parameters, typically strings or numbers, that you should be able to pass directly from Python

Hope this helps.

73,
Franco K4VZ

@fventuri fventuri self-assigned this Apr 27, 2023
@w0dz
Copy link
Author

w0dz commented Apr 27, 2023

HI Franco,
I want to be able to set the AGC time constant to slow, medium or fast based on mode (fast for CW, medium for SSB, slow for AM) and the AGC Threshold. From what you are saying, SoapySDR apparently doesn't let me do this. There's no way the SDR could know what mode I am using since that is determined after reading in the buffer, demodulating and filtering. So there needs to be a way to access the register that lets me set that. Can you tell me more about "writeSettings", as maybe this is how to do it? Specifically, I need access to this structure:

typedef struct
{
sdrplay_api_AgcControlT enable; // default: sdrplay_api_AGC_50HZ
int setPoint_dBfs; // default: -60
unsigned short attack_ms; // default: 0
unsigned short decay_ms; // default: 0
unsigned short decay_delay_ms; // default: 0
unsigned short decay_threshold_dB; // default: 0
int syncUpdate; // default: 0
} sdrplay_api_AgcT;

@fventuri
Copy link
Collaborator

Since SoapySDR aims to be a general purpose interface for all sorts of SDRs and the designers of the SoapySDR API couldn't foresee all the possible configuration settings, they introduced the Settings API (https://github.com/pothosware/SoapySDR/blob/master/include/SoapySDR/Device.hpp#L1204-L1310), which is the catch-all API for those parameters that do not naturally fit elsewhere, like the specific settings for the SDRplay AGC. For instance the writeSettings() method has this description:

     * Write an arbitrary setting on the device.
     * The interpretation is up the implementation.

You can list the available settings using the method getSettingInfo().

What you could do is to follow the code that is already there for the AGC set point (https://github.com/pothosware/SoapySDRPlay3/blob/master/Settings.cpp#L1425-L1432), and add similar blocks for all the other settings you would like to control.
If you were to decide to do that, there are three methods that would need to be changed:

Franco

@w0dz
Copy link
Author

w0dz commented Apr 28, 2023

I guess what I'm really asking is if the Python API has a hook in it to allow writes to the registers. I would rather not edit your C code and thus have a custom version.

Brian

@fventuri
Copy link
Collaborator

Brian, my bad, I misunderstood your earlier question about writeSetting().

The way SoapySDR provides a Python API is by 'Python bindings', courtesy of SWIG (https://github.com/pothosware/SoapySDR/wiki/PythonSupport). SWIG is used to (almost) automatically generate a Python 'interface' from the C/C++ header files that describe the classes, methods, arguments, etc of SoapySDR.
This means that these bindings 'expose' a Python module that is almost exactly what the C/C++ library is (with a few minor differences because of the way Python works, described in the last section of the Wiki page I linked above).

In other words if something exists in SoapySDR C/C++ API, then it exists in Python, but if it is not the C/C++ API, it is not in the Python bindings either.

I hope this helps,
Franco

@w0dz
Copy link
Author

w0dz commented Apr 29, 2023

OK, but I can't FIND the Python bindings! I can find a list of functions, none of which have the parameters, and I had to hunt for literally days to find examples that had a hint of what I wanted for the other stuff (such as "IFGR" and "RFGR" for the parameters in the setGain function. Where is such a document?

Brian

@fventuri
Copy link
Collaborator

To clarify what I wrote above, Python bindings are just a mechanism to translate from the Python function calling convention (for instance almost everything in Python is a PyObject) to the C/C++ calling convention; say for instance a C++ function expects a string as one of its arguments, then the Python bindings take care of extracting the string from the PyObject, and passing it to the C++ function underneath. Similarly they take care of wrapping the return value from the C/C++ function into another PyObject, to make Python happy.

Coming to your question about the name of the gains, as I said at the beginning of this discussion, the SoapySDR API (C/C++ or Python; it's the same) provides the method listGains() that returns those gain names.
As a matter of fact the very example provided in the Python Support Wiki page (https://github.com/pothosware/SoapySDR/wiki/PythonSupport#basic-example) has this piece of code:

#query device info
print(sdr.listAntennas(SOAPY_SDR_RX, 0))
print(sdr.listGains(SOAPY_SDR_RX, 0))
freqs = sdr.getFrequencyRange(SOAPY_SDR_RX, 0)
for freqRange in freqs: print(freqRange)

where they show how to get all the info you need from the device.

Regarding how say RFGR works, then we are entering in the specific details of the SDRplay RSP hardware and API - for instance in this case the way to go from RFGR to actual gain reduction is via some gain reduction tables that are in chapter 5 (page 38) of the SDRplay API Specification Guide (https://www.sdrplay.com/docs/SDRplay_API_Specification_v3.07.pdf).

Franco

@w0dz
Copy link
Author

w0dz commented Apr 29, 2023

I know all about the gain tables. I cannot find a one-one mapping of SoapySDR calls to those of the RSP1A. For example, please show me how I would use SoapySDR to set the AGC threshold to -60dBm. Or the attack or decay times. Those are settable in C, but I can't find any such reference in Python.

Brian

@fventuri
Copy link
Collaborator

Brian,
since the setting agc_setpoint is available in writeSetting(), you can set it to say -30 by writing something like this in Python:

sdr.writeSetting('agc_setpoint', -30)

The exact line where that value gets assigned to the structure you mentioned earlier is this one (https://github.com/pothosware/SoapySDRPlay3/blob/master/Settings.cpp#L1427):

      chParams->ctrlParams.agc.setPoint_dBfs = stoi(value);

For the other parameters, like attack and decay, they are not implemented in writeSettings(), but you could replicate the code used for 'agc_setpoint' for them.

Franco

@w0dz
Copy link
Author

w0dz commented Apr 29, 2023

Thanks Franco! You've been a big help. I really appreciate the fast response and detailed info. One other question - in order to add the attack and decay settings, don't I have to edit and recompile your SoapySDRPlay code? I don't think I have everything I need to do that.

Brian

@fventuri
Copy link
Collaborator

Brian, glad to help!

The steps to build SoapySDRPlay from source and install it are very simple, and you can find them in the Wiki here: https://github.com/pothosware/SoapySDRPlay3/wiki#building-soapy-sdr-play

The part that could be a little tricky is to make SoapySDR (and all the applications that use it like SoapySDRUtil) can see and use the version you built from source, not the current one (I imagine you use the one that comes by default with your Linux distribution, but I am not sure).

One way to validate that is, before you make any other changes to the code, add a statement like this right before line 1799 in the source file Settings.cpp (https://github.com/pothosware/SoapySDRPlay3/blob/master/Settings.cpp#L1799):

    SoapySDR_log(SOAPY_SDR_INFO, "W0DZ version");

and then after building and installing your own version, run say SoapySDRUtil --probe="deriver=sdrplay" (see here: https://github.com/pothosware/SoapySDRPlay3/wiki#probing-soapy-sdr-play) to make sure you do see the line 'W0DZ version' on the terminal.

Once you see that message, then you should have a good grasp of the whole process, and adding that additional code for the other AGC settings should be pretty straightforward.

Franco

@w0dz
Copy link
Author

w0dz commented May 1, 2023

Actually, now that I recall, I did compile the code. At least I see it on the machine. I had to build a new OS image from scratch, so I rebuilt pretty much everything. So I will have to experiment. Odd not to have the slow/medium/fast stuff in Python. You might consider adding it at some point.

@fventuri
Copy link
Collaborator

fventuri commented May 2, 2023

If I remember correctly, the fact that only agc_setpoint is available in writeSetting() is for historical reasons; this module SoapySDRPlay3 started as just fixes to make the previous version (SDRPlay2 - https://github.com/pothosware/SoapySDRPlay2) work with the new version (3.X) of the SDRplay API.
Since that original version had only the control for agc_setpoint, I just carried that over to the new version without thinking much of it.

Now that I am starting to become more familiar with the way AGC works in the SDRplay API, I see that they have three predefined settings: 5Hz (slow), 50Hz (medium), and 100Hz (fast), so perhaps it makes more sense to replace that agc_setpoint control with another control called say agc_speed (or something like that) that can be either 5Hz (or 200ms), 50Hz (or 20ms), or 100Hz (or 10ms), similar to what I think you were proposing (correct me if I misunderstood you).

Franco

@w0dz
Copy link
Author

w0dz commented May 2, 2023 via email

@fventuri
Copy link
Collaborator

fventuri commented May 2, 2023

Brian,
as far as I know a good document on how the AGC implemented by SDRplay works (or used to work) is this reference note from 2015: https://www.sdrplay.com/docs/SDRplay_AGC_technote_r2p2.pdf - unfortunately I was not able to find in it mentions of the various parameters regarding the speed of the AGC response (attack, decay, decay delay, etc).

The SDRuno User Manual (https://www.sdrplay.com/docs/SDRplay_SDRuno_User_Manual.pdf) on page 63 has the following section:

SDRuno 1.3 saw the introduction of an updated API with an improved IF AGC scheme. This has more
configuration and allows you to better condition the IF AGC to their signal environment. Further
improvements to better align the gain change with the correct point in the IQ stream has also helped to
remove the bouncing effect that was seen in previous versions

Attack ms - Time taken for the AGC to reach 95% of the target value after in increase in the signal power
Decay ms - Time taken for the AGC to reach 95% of the target value after a reduction in the signal power
Decay Delay ms - Amount by which the power level has to fall before the decay delay timer is activated
Decay Threshold (dB) - Time after the power levels has reduced by an amount ≥ to the decay threshold,
before the AGC loop starts the decay process.
Tuner IF AGC Setpoint (dBfs) - Sets the target level power at which AGC routine will attempt adjust the
power on the ADC input. A larger value will position the signal near the top of the ADC range. A lower
value will reduce the signal power and hence levels at the ADC input.

I also found this morning this piece of code in the RSPTCPServer in the SDRplay GitHub repository (https://github.com/SDRplay/RSPTCPServer/blob/master/rsp_tcp.c#L908-L912), where they set those parameters to the same time constants shown in the picture in the SDRuno manual.

I think you are right that it may make more sense to have instead a setting called say agc_settings (instead of just agc_setpoint), where the user could pass a comma separated list of setpoint,attack,decay,decay_delay,decay_threshold so they can all be controlled.

Given that initial sentence in the SDRuno user manual about an "improved IF AGC scheme", it is perhaps possible that they kept the old way of controlling the loop bandwidth just as a legacy, and we are now supposed to use sdrplay_api_AGC_CTRL_EN and set the appropriate parameters. Just a wild guess.

Franco

@fventuri
Copy link
Collaborator

fventuri commented May 8, 2023

Good news Brian!
It looks like the upcoming version of SoapySDR (version 0.9) will have a page with Python documentation and online documentation for all APIs (see announcement here: pothosware/SoapySDR#403).

Franco

@w0dz
Copy link
Author

w0dz commented May 8, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants