From b23f9ef4948d31a55714bcff0637eec05bc0ea6a Mon Sep 17 00:00:00 2001
From: erickshepherdNI <erickshepherd@ni.com>
Date: Wed, 12 Feb 2020 13:26:50 -0600
Subject: [PATCH 36/62] Add TwinRX support to phase alignment script

---
 tools/gr-usrptest/apps/uhd_phase_alignment.py | 76 ++++++++++++++-----
 1 file changed, 57 insertions(+), 19 deletions(-)

diff --git a/tools/gr-usrptest/apps/uhd_phase_alignment.py b/tools/gr-usrptest/apps/uhd_phase_alignment.py
index 532aec2ed..edf12a231 100755
--- a/tools/gr-usrptest/apps/uhd_phase_alignment.py
+++ b/tools/gr-usrptest/apps/uhd_phase_alignment.py
@@ -114,6 +114,8 @@ def parse_args():
                        help="Set LO export {True, False} for each channel with a comma-separated list.")
     parser.add_argument("--lo-source",
                        help="Set LO source {None, internal, companion, external} for each channel with a comma-separated list. None skips this channel.")
+    parser.add_argument("--twinrx", type=bool, default=False,
+                        help="Set if the device is a TwinRX")
     # Signal Source
     parser.add_argument("--source-plugin", type=str, default="default",
                         help="Select source plugin. This can either be one of"
@@ -244,19 +246,6 @@ def setup_usrp(args):
         return None
     # At this point, we can assume our device has valid and locked clock and PPS
 
-    # Set the LO source and export
-    if (args.lo_export is not None) and (args.lo_source is not None):
-        (args.lo_source, args.lo_export) = normalize_lo_source_export_sel(args)
-        for chan, lo_source, lo_export in zip(args.channels, args.lo_source, args.lo_export):
-            if lo_export == "True":
-                logger.info("LO export enabled on channel %s", chan)
-                usrp.set_rx_lo_export_enabled(True, "all", chan)
-                usrp.set_tx_lo_export_enabled(True, "all", chan)
-            if lo_source != "None":
-                logger.info("Channel %s source set to %s", chan, lo_source)
-                usrp.set_rx_lo_source(lo_source, "all", chan)
-                usrp.set_tx_lo_source(lo_source, "all", chan)
-
     # Determine channel settings
     # TODO: Add support for >2 channels! (TwinRX)
     if len(args.channels) != 2:
@@ -268,6 +257,21 @@ def setup_usrp(args):
         usrp.set_rx_rate(args.rate, chan)
         usrp.set_rx_gain(args.gain, chan)
 
+    # Set the LO source and export
+    if (args.lo_export is not None) and (args.lo_source is not None):
+        (args.lo_source, args.lo_export) = normalize_lo_source_export_sel(args)
+        for chan, lo_source, lo_export in zip(args.channels, args.lo_source, args.lo_export):
+            if lo_export == "True":
+                logger.info("LO export enabled on channel %s", chan)
+                usrp.set_rx_lo_export_enabled(True, "all", chan)
+                if args.twinrx is False:
+                    usrp.set_tx_lo_export_enabled(True, "all", chan)
+            if lo_source != "None":
+                logger.info("Channel %s source set to %s", chan, lo_source)
+                usrp.set_rx_lo_source(lo_source, "all", chan)
+                if args.twinrx is False:
+                    usrp.set_tx_lo_source(lo_source, "all", chan)
+
     # Actually synchronize devices
     # We already know we have >=2 channels, so don't worry about that
     if args.sync in ['default', "pps"]:
@@ -317,14 +321,37 @@ def generate_time_spec(usrp, time_delta=0.05):
     return usrp.get_time_now() + uhd.types.TimeSpec(time_delta)
 
 
-def tune_usrp(usrp, freq, channels, delay=CMD_DELAY):
-    """Synchronously set the device's frequency"""
+def tune_usrp(usrp, freq, channels, lo_source, delay=CMD_DELAY):
+    """Synchronously set the device's frequency.
+       If a channel is using an internal LO it will be tuned first
+       and every other channel will be manually tuned based on the response.
+       This is to account for the internal LO channel having an offset in the actual DSP frequency.
+       Then all channels are synchronously tuned."""
+
+    treq = uhd.types.TuneRequest(freq)
+    lo_source_channel = -1
+    for chan, lo in zip(channels, lo_source):
+        if lo == "internal":
+            lo_source_channel = chan
+    if lo_source_channel != -1:
+        treq.dsp_freq = (usrp.set_rx_freq(uhd.types.TuneRequest(freq), lo_source_channel)).actual_dsp_freq
+        treq.target_freq = freq
+        treq.rf_freq = freq
+        treq.rf_freq_policy = uhd.types.TuneRequestPolicy(ord('M'))
+        treq.dsp_freq_policy = uhd.types.TuneRequestPolicy(ord('M'))
+        for chan, lo_source in zip(channels, lo_source):
+            if lo_source == "internal":
+                continue
+            usrp.set_rx_freq(treq, chan)
     usrp.set_command_time(generate_time_spec(usrp, time_delta=delay))
     for chan in channels:
-        usrp.set_rx_freq(uhd.types.TuneRequest(freq), chan)
+        usrp.set_rx_freq(treq, chan)
+    usrp.clear_command_time()
+    time.sleep(delay)
+
 
 
-def recv_aligned_num_samps(usrp, streamer, num_samps, freq, channels=(0,)):
+def recv_aligned_num_samps(usrp, streamer, num_samps, freq, lo_source, channels=(0,)):
     """
     RX a finite number of samples from the USRP
     :param usrp: MultiUSRP object
@@ -338,7 +365,7 @@ def recv_aligned_num_samps(usrp, streamer, num_samps, freq, channels=(0,)):
     result = np.empty((len(channels), num_samps), dtype=np.complex64)
 
     # Tune to the desired frequency
-    tune_usrp(usrp, freq, channels)
+    tune_usrp(usrp, freq, channels, lo_source)
 
     metadata = uhd.types.RXMetadata()
     buffer_samps = streamer.get_max_num_samps() * 10
@@ -444,6 +471,7 @@ def check_results(alignment_stats, drift_thresh, stddev_thresh):
     success = True  # Whether or not we've exceeded a threshold
     msg = ""
     for freq, stats_list in sorted(alignment_stats.items()):
+        band_success = True
         # Try to grab the test frequency for the frequency band
         try:
             test_freq = stats_list[0].get("test_freq")
@@ -462,6 +490,7 @@ def check_results(alignment_stats, drift_thresh, stddev_thresh):
             mean_deg = run_dict.get("mean", 0.) * 180 / np.pi
             stddev_deg = run_dict.get("stddev", 0.) * 180 / np.pi
             if stddev_deg > stddev_thresh:
+                band_success = False
                 success = False
 
             msg += "{:.2f}MHz<-{:.2f}MHz: {:.3f} deg +- {:.3f}\n".format(
@@ -472,8 +501,11 @@ def check_results(alignment_stats, drift_thresh, stddev_thresh):
         # Report the largest difference in mean values of runs
         max_drift = calc_max_drift(mean_list)
         if max_drift > drift_thresh:
+            band_success = False
             success = False
         msg += "--Maximum drift over runs: {:.2f} degrees\n".format(max_drift)
+        if band_success is False:
+            msg += "Failure!\n"
         # Print a newline to separate frequency bands
         msg += "\n"
 
@@ -536,6 +568,7 @@ def main():
         if args.easy_tune:
             # Round to the nearest MHz
             tune_freq = np.round(tune_freq, -6)
+
         # Request the SigGen tune to our test frequency plus some offset away
         # the device's LO
         src_gen.tune(tune_freq + args.tone_offset, current_power)
@@ -548,7 +581,7 @@ def main():
             for i in range(NUM_RETRIES):
                 # Tune to a random frequency in each of the frequency bands...
                 tune_away_freq = npr.uniform(tune_away_start, tune_away_stop)
-                tune_usrp(usrp, tune_away_freq, args.channels)
+                tune_usrp(usrp, tune_away_freq, args.channels, args.lo_source)
                 time.sleep(args.skip_time)
 
                 logger.info("Receiving samples, take %d, (%.2fMHz -> %.2fMHz)",
@@ -559,6 +592,7 @@ def main():
                                                streamer,
                                                nsamps,
                                                tune_freq,
+                                               args.lo_source,
                                                args.channels)
                 if samps.size >= nsamps:
                     break
@@ -602,6 +636,10 @@ def main():
                      tune_freq/1e6,
                      calc_max_drift(run_means) * 180 / np.pi,
                      max(run_stddevs) * 180. / np.pi)
+        if args.drift_threshold < calc_max_drift(run_means) * 180 / np.pi:
+            logger.info("Drift threshold of %.1f has been exceeded by %.1f degrees", args.drift_threshold, (calc_max_drift(run_means) * 180 / np.pi) - args.drift_threshold)
+        if args.stddev_threshold < max(run_stddevs) * 180. / np.pi:
+            logger.info("Max stddev threshold of %.2f has been exceeded by %.2f degrees", args.stddev_threshold, (max(run_stddevs) * 180. / np.pi) - args.stddev_threshold)
         all_alignment_stats[freq_start] = alignment_stats
         # Increment the power level for the next run
         current_power += args.power_step
-- 
2.20.1

