Discussion:
[USRP-users] Synchronizing USRP Front Ends via gnu-radio
Linnenkamp, Nicholas
2014-04-14 16:19:48 UTC
Permalink
Thought I would share some information with the group.

I recently have been working on trying to synchronize two USRP front ends using timed commands. While there are examples from the web that indicate that it should be relatively easy and intuitive, I actually found it to be quite a challenge when trying to adapt those commands to python for the sake of harmony with gnu-radio. I was actually running into several different problems that made diagnosing issues with synchronization quite difficult.

http://files.ettus.com/uhd_docs/manual/html/sync.html

While the above web page is a very good place to start and was very helpful in synchronizing 10MHz clocks and PPS outputs, it did not immediately lead to time synchronized front ends. I was interested in finding a way to get the front ends to start sampling at the exact same time in top_block.py in which the commands indicated on the web page do not work.

The two issues that presented problems are outlined below:


1. uhd.time_spec_t structures cannot be added together in python.

2. You must wait after issuing the clock and pps synchronization commands for "set_command_time" to work.

The first issue presents a problem because you need to be able to read the time off the device and calculate a time in the future that you wish your commands to be executed. Without the ability to do a simple "uhd.time_spec_t + 1" command I fiddled around for a while trying to figure out how to get this to happen in python. It appears that it might be a gnu-radio problem because the "+" operator was not exported via swig.

The second issue presented a problem because without a short wait after executing several commands they do not work as advertised and will actually report bad data. While in retrospect this should have been intuitive, during the early portions of debugging, failures to synchronize some data actually pointed me in the wrong direction as well as caused other problems to pop up. I will just point out that setting a command execution time a hundred thousand seconds in the future because you read the time before it stabilized results in having to power-off the device to regain control.

I'm sharing my python code from a hand-modified top_block.py so that others can benefit. I didn't include all of the initialization, just only parts that I modified. The example below shows how to synchronize the front end of two X310's with two daughter-cards each so that they start sampling at the same time. The process should be very similar for other USRP devices such as the N200, etc. My comments are in line with the code.

# Moved the source creations of the two devices so that they are near each other

self.Add(self.wxgui_fftsink2_0.win)
self.uhd_usrp_source_1 = uhd.usrp_source(
device_addr="addr=192.168.40.2",
stream_args=uhd.stream_args(
cpu_format="fc32",
channels=range(2),
),
)

self.uhd_usrp_source_0 = uhd.usrp_source(
device_addr="addr=192.168.50.2",
stream_args=uhd.stream_args(
cpu_format="fc32",
channels=range(2),
),
)

# Setting the first X310s to get cock from GPSDO as well as setting time to 0.0

self.uhd_usrp_source_1.set_clock_source("gpsdo", 0)
self.uhd_usrp_source_1.set_time_source("gpsdo", 0)
self.uhd_usrp_source_1.set_time_unknown_pps(uhd.time_spec())
self.uhd_usrp_source_1.set_samp_rate(samp_rate)
self.uhd_usrp_source_1.set_gain(gain1, 0)
self.uhd_usrp_source_1.set_gain(gain2, 1)

# Setting the second X310 to get clock from first X310 as well as setting time to 0.0

self.uhd_usrp_source_0.set_clock_source("external", 0)
self.uhd_usrp_source_0.set_time_source("external", 0)
self.uhd_usrp_source_0.set_time_unknown_pps(uhd.time_spec())
self.uhd_usrp_source_0.set_samp_rate(samp_rate)
self.uhd_usrp_source_0.set_gain(gain1, 0)
self.uhd_usrp_source_0.set_gain(gain2, 1)

# Go to sleep to let the clocks settle for at least 1 second (bad problems happen if you don't wait)

time.sleep(1)

# Ask each USRP what time it thinks it was when it saw its last PPS.

cmd_time = self.uhd_usrp_source_1.get_time_last_pps()
cmd_time0 = self.uhd_usrp_source_0.get_time_last_pps()

# Convert the "uhd.time_spec_t" to a "double" so that you can do something with it in python

real_seconds = uhd.time_spec_t.get_real_secs(cmd_time)
real_seconds0 = uhd.time_spec_t.get_real_secs(cmd_time0)

# Optional you can print out the time that you saw your last PPS. Mine always reported 2.999999995 and 0.9999999995

#print real_seconds
#print real_seconds0

# Add one second to indicate when in the future you want your command to execute

future_real_seconds = real_seconds + 1
future_real_seconds0 = real_seconds0 + 1

# Optional but you could print out that time just to be sure that python accepted the double and added 1 properly

#print future_real_seconds
#print future_real_seconds0

# We now have to convert the "double" back to a "uhd.time_spec_t"

future_cmd_time = uhd.time_spec_t(future_real_seconds)
future_cmd_time0 = uhd.time_spec_t(future_real_seconds0)

# We are going to indicate to the X310s when they are going to start issuing the stream commands that we are going to queue

self.uhd_usrp_source_1.set_command_time(future_cmd_time)
self.uhd_usrp_source_0.set_command_time(future_cmd_time0)

# We are going to set the center frequency on all the daughter-cards which is last step to start streaming. These will be executed at "future_cmd_time" and "future_cmd_time0"

self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(freq1,lo_offset), 0)
self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(freq2,lo_offset), 1)

self.uhd_usrp_source_0.set_center_freq(uhd.tune_request(freq1,lo_offset), 0)
self.uhd_usrp_source_0.set_center_freq(uhd.tune_request(freq2,lo_offset), 1)

# For completeness we indicate to the USRPs that any further commands sent should not be queued

self.uhd_usrp_source_1.clear_command_time()
self.uhd_usrp_source_0.clear_command_time()

# Final connections are made in gnu-radio to wxgui fft sinks

##################################################
# Connections
##################################################
self.connect((self.uhd_usrp_source_1, 0), (self.wxgui_fftsink2_0_0, 0))
self.connect((self.uhd_usrp_source_1, 1), (self.wxgui_fftsink2_1_0, 0))
self.connect((self.uhd_usrp_source_0, 0), (self.wxgui_fftsink2_0, 0))
self.connect((self.uhd_usrp_source_0, 1), (self.wxgui_fftsink2_1, 0))

I hope that this example will save others some pain from trying to figure out how to synchronize front ends via gnu-radio.

Nicholas
Martin Braun
2014-04-14 16:56:09 UTC
Permalink
Post by Linnenkamp, Nicholas
Thought I would share some information with the group.
Nicholas,

thanks a lot for sharing this!

Martin
Post by Linnenkamp, Nicholas
I recently have been working on trying to synchronize two USRP front
ends using timed commands. While there are examples from the web that
indicate that it should be relatively easy and intuitive, I actually
found it to be quite a challenge when trying to adapt those commands to
python for the sake of harmony with gnu-radio. I was actually running
into several different problems that made diagnosing issues with
synchronization quite difficult.
http://files.ettus.com/uhd_docs/manual/html/sync.html
While the above web page is a very good place to start and was very
helpful in synchronizing 10MHz clocks and PPS outputs, it did not
immediately lead to time synchronized front ends. I was interested in
finding a way to get the front ends to start sampling at the exact same
time in top_block.py in which the commands indicated on the web page do
not work.
1. uhd.time_spec_t structures cannot be added together in python.
2. You must wait after issuing the clock and pps synchronization
commands for “set_command_time” to work.
The first issue presents a problem because you need to be able to read
the time off the device and calculate a time in the future that you wish
your commands to be executed. Without the ability to do a simple
“uhd.time_spec_t + 1” command I fiddled around for a while trying to
figure out how to get this to happen in python. It appears that it
might be a gnu-radio problem because the “+” operator was not exported
via swig.
The second issue presented a problem because without a short wait after
executing several commands they do not work as advertised and will
actually report bad data. While in retrospect this should have been
intuitive, during the early portions of debugging, failures to
synchronize some data actually pointed me in the wrong direction as well
as caused other problems to pop up. I will just point out that setting
a command execution time a hundred thousand seconds in the future
because you read the time before it stabilized results in having to
power-off the device to regain control.
I’m sharing my python code from a hand-modified top_block.py so that
others can benefit. I didn’t include all of the initialization, just
only parts that I modified. The example below shows how to synchronize
the front end of two X310’s with two daughter-cards each so that they
start sampling at the same time. The process should be very similar for
other USRP devices such as the N200, etc. My comments are in line with
the code.
/# Moved the source creations of the two devices so that they are near
each other/
/ /
/ self.Add(self.wxgui_fftsink2_0.win)/
/ self.uhd_usrp_source_1 = uhd.usrp_source(/
/ device_addr="addr=192.168.40.2",/
/ stream_args=uhd.stream_args(/
/ cpu_format="fc32",/
/ channels=range(2),/
/ ),/
/ )/
/ /
/ self.uhd_usrp_source_0 = uhd.usrp_source(/
/ device_addr="addr=192.168.50.2",/
/ stream_args=uhd.stream_args(/
/ cpu_format="fc32",/
/ channels=range(2),/
/ ),/
/ )/
/ /
/# Setting the first X310s to get cock from GPSDO as well as setting
time to 0.0/
/ /
/ self.uhd_usrp_source_1.set_clock_source("gpsdo", 0)/
/ self.uhd_usrp_source_1.set_time_source("gpsdo", 0)/
/ self.uhd_usrp_source_1.set_time_unknown_pps(uhd.time_spec())/
/ self.uhd_usrp_source_1.set_samp_rate(samp_rate)/
/ self.uhd_usrp_source_1.set_gain(gain1, 0)/
/ self.uhd_usrp_source_1.set_gain(gain2, 1)/
/ /
/# Setting the second X310 to get clock from first X310 as well as
setting time to 0.0/
/ /
/ self.uhd_usrp_source_0.set_clock_source("external", 0)/
/ self.uhd_usrp_source_0.set_time_source("external", 0)/
/ self.uhd_usrp_source_0.set_time_unknown_pps(uhd.time_spec())/
/ self.uhd_usrp_source_0.set_samp_rate(samp_rate)/
/ self.uhd_usrp_source_0.set_gain(gain1, 0)/
/ self.uhd_usrp_source_0.set_gain(gain2, 1)/
/ /
/# Go to sleep to let the clocks settle for at least 1 second (bad
problems happen if you don’t wait)/
/ /
/ time.sleep(1)/
/ /
/# Ask each USRP what time it thinks it was when it saw its last PPS./
/ /
/ cmd_time = self.uhd_usrp_source_1.get_time_last_pps()/
/ cmd_time0 = self.uhd_usrp_source_0.get_time_last_pps()/
/ /
/# Convert the “uhd.time_spec_t” to a “double” so that you can do
something with it in python/
/ /
/ real_seconds = uhd.time_spec_t.get_real_secs(cmd_time)/
/ real_seconds0 = uhd.time_spec_t.get_real_secs(cmd_time0)/
/ /
/# Optional you can print out the time that you saw your last PPS. Mine
always reported 2.999999995 and 0.9999999995/
/ /
/ #print real_seconds/
/ #print real_seconds0/
/ /
/# Add one second to indicate when in the future you want your command
to execute/
/ /
/ future_real_seconds = real_seconds + 1/
/ future_real_seconds0 = real_seconds0 + 1/
/ /
/# Optional but you could print out that time just to be sure that
python accepted the double and added 1 properly/
/ /
/ #print future_real_seconds/
/ #print future_real_seconds0/
/ /
/# We now have to convert the “double” back to a “uhd.time_spec_t”/
/ /
/ future_cmd_time = uhd.time_spec_t(future_real_seconds)/
/ future_cmd_time0 = uhd.time_spec_t(future_real_seconds0)/
/ /
/# We are going to indicate to the X310s when they are going to start
issuing the stream commands that we are going to queue/
/ /
/ self.uhd_usrp_source_1.set_command_time(future_cmd_time)/
/ self.uhd_usrp_source_0.set_command_time(future_cmd_time0)/
/ /
/# We are going to set the center frequency on all the daughter-cards
which is last step to start streaming. These will be executed at
“future_cmd_time” and “future_cmd_time0”/
/ /
/
self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(freq1,lo_offset), 0)/
/
self.uhd_usrp_source_1.set_center_freq(uhd.tune_request(freq2,lo_offset), 1)/
/ /
/
self.uhd_usrp_source_0.set_center_freq(uhd.tune_request(freq1,lo_offset), 0)/
/
self.uhd_usrp_source_0.set_center_freq(uhd.tune_request(freq2,lo_offset), 1)/
/ /
/# For completeness we indicate to the USRPs that any further commands
sent should not be queued/
/ /
/ self.uhd_usrp_source_1.clear_command_time()/
/ self.uhd_usrp_source_0.clear_command_time()/
/ /
/# Final connections are made in gnu-radio to wxgui fft sinks/
/ /
/ ##################################################/
/ # Connections/
/ ##################################################/
/ self.connect((self.uhd_usrp_source_1, 0),
(self.wxgui_fftsink2_0_0, 0))/
/ self.connect((self.uhd_usrp_source_1, 1),
(self.wxgui_fftsink2_1_0, 0))/
/ self.connect((self.uhd_usrp_source_0, 0),
(self.wxgui_fftsink2_0, 0))/
/ self.connect((self.uhd_usrp_source_0, 1),
(self.wxgui_fftsink2_1, 0))/
/ /
I hope that this example will save others some pain from trying to
figure out how to synchronize front ends via gnu-radio.
Nicholas
_______________________________________________
USRP-users mailing list
http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com
Marcus Müller
2014-04-14 21:52:22 UTC
Permalink
Hi Nicholas,
Post by Linnenkamp, Nicholas
1. uhd.time_spec_t structures cannot be added together in python.
Well, it's not like there is an + operator defined in UHD at all, so
it can't be exported by SWIG. There is, however, an += operator; I've
switched to using that, it works fine.

UHD in C++ got around explicitely defining the binary + operator for
time_spec_t by defining a += operator in the class itself and using
boost/operator.hpp . Seemingly, SWIG does not like this...

Well, I've decided to try and fix this from within GNU Radio. Try my
branch by using

git pull https://github.com/marcusmueller/gnuradio.git
https://github.com/marcusmueller/gnuradio.git

(without the line break). For me it works fine, I can now do something
like

t = uhd.time_spec_t(666.0)
sum = t + t

doing something like t + 1.0 still does not work, but I'm okay with
that -- there's a reason UHD treats times different than "normal" numbers.

Greetings,
Marcus

Continue reading on narkive:
Loading...