Adventures in USRP tuning
These days, USRP radios are prevalent in our corner of radio science. Where I'm writing from at MIT Haystack Observatory, we have many USRP flavors in use: N200, B210, B210 mini, and X310. Readers who are otherwise unfamiliar with USRPs may recognize the name from an earlier post on the EISCAT 3D demonstrator array, which is using new custom N3x0 units. As a product line, I like USRPs because they are flexible, have the performance I want, and can be obtained at a reasonable price. Perhaps the most important feature, though, is that the USRPs run on and integrate well with open source software. At least, that's the key to this adventure.
We recently acquired some X310s with UBX daughterboards to test them out as transmit units for geospace radar. Things were working reasonably well: set the center frequency of 440 MHz, feed the radio a waveform of our choosing, and the RF transmission would appear at the output as desired. Well, almost as desired. You see, the output at the tuning frequency is known to have a spurious tone, so directly tuning to the center frequency on transmit or receive is not recommended. In practice, the unwanted DC component is not so significant that it ruins the transmitted waveform. But being the perfectionist type, I wanted to see if I could eliminate it anyway. The typical solution is to tune to an offset frequency and do the final frequency shift digitally. The UHD software layer that runs the USRPs makes this easy; all one has to do is request a particular offset. However, requesting 440 MHz + 10 MHz offset (to keep the spurious signal well outside our waveform band) resulted in... an actual center frequency of 439999999.991 Hz and an offset of 10000000.009 Hz. Hmm. That gets rid of the DC spur, but now the tuning is subtly off. A 9 millihertz offset might work for most applications, but we want phase coherence over many pulses with a receiver that doesn't necessarily have the same small offset from 440 MHz. Time to investigate. Anyone not interested in the details of USRP tuning should move right along.
After carefully reading the UHD source code (yay open source!) and experimenting with settings, I now think I know what is going on. Given a desired center frequency and local oscillator (LO) offset, the first thing the UHD software will do is calculate the desired RF frequency as (center_freq + lo_offset). It will then ask the daughterboard to try to tune that frequency, resulting in an actual RF frequency that may be slightly different. Then given the actual RF frequency and the desired center frequency, the software will calculate the desired digital signal processing (DSP) frequency as (actual_rf_freq - center_freq). Then it will ask the mainboard to set that digital frequency offset using the FPGA, resulting in an actual DSP frequency that may be slightly different.
How this actually plays out depends on both on the USRP mainboard (X310, N200, B210, etc.) and on the tuner daughterboard in use (UBX, TwinRX, BasicRX, etc.). (Disclaimer: I'm going to make general statements here assuming that it works for all USRPs, but I haven't checked that and in actuality the details could be a little different. YMMV.) The common component for all of the radios is the DSP frequency correction on board the FPGA of the mainboard, so I'll start there. This is accomplished through a CORDIC stage on the FPGA, so the possible frequency steps end up being the mainboard clock rate divided by 2^32. So for the X3xx series with a clock rate of 200 MHz, this means possible DSP offsets must be a multiple of (200e6/2^32) = .046566 Hz. A DSP offset of 2 MHz, for instance, is not exactly possible (closest is 2.0000000018626451 MHz), but 1.5625 MHz, 3.125 MHz, 6.25 MHz, 12.5 MHz are (among many others). Of course, it is also necessary at a minimum to keep the DSP offset within half the bandwidth of the analog low-pass filter used by the daughterboard, so offsets can't be arbitrarily large.
The other component is the RF tuning done by the daughterboard card, and for those there seems to be much more variety in architecture. Many will at some point involve a phase-locked loop (PLL) that tracks a desired frequency given a smaller reference, usually the daughterboard clock frequency divided by an integer divider. That reference frequency is then multiplied by either an integer factor (N) or a fractional factor of the form (N + K/4095), depending on the PLL mode used. UHD will let you adjust any of the following if the device supports it: the PLL mode between integer and fractional, the daughterboard clock frequency, and the integer divider that gives the PLL reference from the clock frequency. The latter is done by setting the tuning step size, which only applies in integer mode of the PLL. For specifics about what each daughterboard supports, the best resource I found is the source code itself.
So practically, there are 6 settings that I'm aware of that help you control USRP tuning:
- Desired center frequency (center_freq)
- Desired offset frequency (lo_offset)
- Mainboard clock rate (master_clock_rate=X)
- Daughterboard clock rate (dboard_clock_rate=X)
- PLL mode (mode_n=[integer/fractional])
- Integer-mode tuning step size (int_n_step=X)
from gnuradio import uhdThe clock rates are set as part of the device string:
u = uhd.usrp_source( device_addr=','.join([ "addr=192.168.10.2", "master_clock_rate=200e6", "dboard_clock_rate=20e6", ]), ... )The remaining settings are given as a tune_request object and passed to the set_center_freq method:
req = uhd.tune_request( center_freq, lo_offset, args=uhd.device_addr(','.join([ "mode_n=integer", "int_n_step=100e3", ])), ) u.set_center_freq(req, ch_num)
Returning to the inciting case, we now have a better way of setting a tuning offset while still resulting in a center frequency of exactly 440 MHz using the X310 and UBX daughterboard. First, set the daughterboard clock rate to 20 MHz, both because it is recommended and because it allows us to control the input frequency into the PLL. Then, set the PLL mode to "integer" and give a tuning step size of 100 kHz, which is possible because (20e6/100e3) = 200 gives a small integer divider for getting the PLL reference frequency from the daughterboard clock frequency. Now we know we can exactly tune any RF frequency that is a multiple of 100 kHz. Finally, knowing that the master clock rate is 200 MHz so that possible DSP offsets must be a multiple of (200e6/2^32), choose a desired LO offset of 12.5 MHz (=200e6/16). This gives a desired RF frequency of 452.5 MHz, which is a multiple of 100 kHz. This results in a 440 MHz tuning that is exact. Incidentally, we now know that the original direct exact tuning of 440 MHz was a bit of a fluke: it just so happens that 440 MHz is an exact fractional multiple of the default UBX clock rate of 50 MHz, i.e. (8 + 3276/4095)*50e6 = 440e6.
If you've made it this far, hopefully you will find this information useful! Now go forth and tune some USRPs.