I just wanted to share my experience getting two Rx streams at different rates working with the B210 in case someone else tries to do the same thing in the future.
The only other resource I found on this ( http://lists.ettus.com/pipermail/usrp-users_lists.ettus.com/2015-April/041655.html) seemed to have conflicting information, and no real resolution. I also didn't see anything like this described in the knowledge base, although perhaps I was looking in the wrong place :) Following some breadcrumbs in the UHD source, I was able to get the attached sample to work reliably. The important points: -> There's no need to have multiple 'multi_usrp' objects because device.make() will return the same object if it's already setup anyway -> Both streams need to be read in the same thread (there were comments that hinted at this in recv_packet_demuxer_3000) -> The streams need to be kept in sync by keeping the number of sample read by each stream the same ratio as the sample rates (otherwise one stream will get behind and the other will overflow). Cheers, William
// // Copyright 2010-2012,2014-2015 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. // #include <uhd/types/tune_request.hpp> #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/utils/static.hpp> #include <uhd/usrp/multi_usrp.hpp> #include <uhd/exception.hpp> #include <uhd/property_tree.hpp> #include <boost/thread/thread.hpp> #include <boost/program_options.hpp> #include <boost/math/special_functions/round.hpp> #include <boost/math/common_factor.hpp> #include <boost/format.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string.hpp> #include <boost/filesystem.hpp> #include <iostream> #include <fstream> #include <csignal> namespace po = boost::program_options; /*********************************************************************** * Signal handlers **********************************************************************/ static bool stop_signal_called = false; void sig_int_handler(int){stop_signal_called = true;} /*********************************************************************** * Utilities **********************************************************************/ //! Change to filename, e.g. from usrp_samples.dat to usrp_samples.00.dat, // but only if multiple names are to be generated. std::string generate_out_filename(const std::string &base_fn, size_t n_names, size_t this_name) { if (n_names == 1) { return base_fn; } boost::filesystem::path base_fn_fp(base_fn); base_fn_fp.replace_extension( boost::filesystem::path( str(boost::format("%02d%s") % this_name % base_fn_fp.extension().string()) ) ); return base_fn_fp.string(); } /*********************************************************************** * recv_to_file function **********************************************************************/ template<typename samp_type> void recv_to_file( uhd::usrp::multi_usrp::sptr usrp, const std::string &cpu_format, const std::string &wire_format, const std::string &file, const std::string &file2, size_t samps_per_buff1, size_t samps_per_buff2, int num_requested_samples, float settling_time ){ int num_total_samps = 0; int num_total_samps2 = 0; //create a receive streamer for channel 1 std::vector<size_t> rx_channel_nums; rx_channel_nums.push_back(0); uhd::stream_args_t stream_args(cpu_format,wire_format); stream_args.channels = rx_channel_nums; uhd::rx_streamer::sptr rx_stream = usrp->get_rx_stream(stream_args); //create a receive streamer for channel 2 std::vector<size_t> rx_channel_nums2; rx_channel_nums2.push_back(1); uhd::stream_args_t stream_args2(cpu_format,wire_format); stream_args2.channels = rx_channel_nums2; uhd::rx_streamer::sptr rx_stream2 = usrp->get_rx_stream(stream_args2); // Prepare buffers for received samples and metadata uhd::rx_metadata_t md, md2; std::vector<samp_type> buff1(samps_per_buff1); std::vector<samp_type> buff2(samps_per_buff2); // Create one ofstream object per channel std::ofstream outfile1; std::ofstream outfile2; outfile1.open(file.c_str(), std::ofstream::binary); outfile2.open(file2.c_str(), std::ofstream::binary); bool overflow_message = true; float timeout = settling_time + 0.1f; //expected settling time + padding for first recv //setup streaming uhd::stream_cmd_t stream_cmd((num_requested_samples == 0)? uhd::stream_cmd_t::STREAM_MODE_START_CONTINUOUS: uhd::stream_cmd_t::STREAM_MODE_NUM_SAMPS_AND_DONE ); stream_cmd.num_samps = num_requested_samples; stream_cmd.stream_now = false; stream_cmd.time_spec = uhd::time_spec_t(settling_time); rx_stream->issue_stream_cmd(stream_cmd); rx_stream2->issue_stream_cmd(stream_cmd); while(not stop_signal_called and (num_requested_samples > num_total_samps or num_requested_samples == 0)){ size_t num_rx_samps1 = rx_stream->recv(&buff1.front(), buff1.size(), md, timeout); size_t num_rx_samps2 = rx_stream2->recv(&buff2.front(), buff2.size(), md2, timeout); timeout = 0.1f; //small timeout for subsequent recv if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { std::cout << boost::format("Timeout while streaming") << std::endl; break; } if (md.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW){ if (overflow_message){ overflow_message = false; std::cerr << boost::format( "Got an overflow indication. Please consider the following:\n" " Your write medium must sustain a rate of %fMB/s.\n" " Dropped samples will not be written to the file.\n" " Please modify this example for your purposes.\n" " This message will not appear again.\n" ) % (usrp->get_rx_rate()*sizeof(samp_type)/1e6); } continue; } if (md.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ throw std::runtime_error(str(boost::format( "Receiver error %s" ) % md.strerror())); } if (md2.error_code == uhd::rx_metadata_t::ERROR_CODE_TIMEOUT) { std::cout << boost::format("Timeout while streaming") << std::endl; break; } if (md2.error_code == uhd::rx_metadata_t::ERROR_CODE_OVERFLOW){ if (overflow_message){ overflow_message = false; std::cerr << boost::format( "Got an overflow indication. Please consider the following:\n" " Your write medium must sustain a rate of %fMB/s.\n" " Dropped samples will not be written to the file.\n" " Please modify this example for your purposes.\n" " This message will not appear again.\n" ) % (usrp->get_rx_rate()*sizeof(samp_type)/1e6); } continue; } if (md2.error_code != uhd::rx_metadata_t::ERROR_CODE_NONE){ throw std::runtime_error(str(boost::format( "Receiver error %s" ) % md2.strerror())); } num_total_samps += num_rx_samps1; num_total_samps2 += num_rx_samps2; //std::cout << boost::format("Got %d samps") % num_rx_samps << std::endl; // if(outfile1.is_open()) outfile1.write((const char*)&buff1.front(), num_rx_samps1*sizeof(samp_type)); if(outfile2.is_open()) outfile2.write((const char*)&buff2.front(), num_rx_samps2*sizeof(samp_type)); } std::cout << boost::format("Received %d samples on channel 1.") % num_total_samps << std::endl; std::cout << boost::format("Received %d samples on channel 2.") % num_total_samps2 << std::endl; // Shut down receiver stream_cmd.stream_mode = uhd::stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS; rx_stream->issue_stream_cmd(stream_cmd); rx_stream2->issue_stream_cmd(stream_cmd); // Close files outfile1.close(); outfile2.close(); } /*********************************************************************** * Main function **********************************************************************/ int UHD_SAFE_MAIN(int argc, char *argv[]){ uhd::set_thread_priority_safe(); //receive variables to be set by po std::string rx_args, file1, file2, type, rx_ant, rx_subdev, rx_channels; size_t total_num_samps, spb; double rx_rate1, rx_rate2, rx_freq, rx_gain, rx_bw; float settling; //setup the program options po::options_description desc("Allowed options"); desc.add_options() ("help", "help message") ("rx-args", po::value<std::string>(&rx_args)->default_value(""), "uhd receive device address args") ("file1", po::value<std::string>(&file1)->default_value("usrp_samples_1.dat"), "name of the file to write binary samples to") ("file2", po::value<std::string>(&file2)->default_value("usrp_samples_2.dat"), "name of the file to write binary samples to") ("nsamps", po::value<size_t>(&total_num_samps)->default_value(0), "total number of samples to receive") ("settling", po::value<float>(&settling)->default_value(float(0.2)), "settling time (seconds) before receiving") ("spb", po::value<size_t>(&spb)->default_value(10000), "samples per buffer, 0 for default") ("rx-rate1", po::value<double>(&rx_rate1), "rate of receive incoming samples") ("rx-rate2", po::value<double>(&rx_rate2), "rate of receive incoming samples") ("rx-freq", po::value<double>(&rx_freq), "receive RF center frequency in Hz") ("rx-gain", po::value<double>(&rx_gain), "gain for the receive RF chain") ("rx-ant", po::value<std::string>(&rx_ant), "receive antenna selection") ("rx-subdev", po::value<std::string>(&rx_subdev), "receive subdevice specification") ("rx-bw", po::value<double>(&rx_bw), "analog receive filter bandwidth in Hz") ("rx-int-n", "tune USRP RX with integer-N tuning") ; po::variables_map vm; po::store(po::parse_command_line(argc, argv, desc), vm); po::notify(vm); std::string otw = "sc16"; type = "float"; //print the help message if (vm.count("help")){ std::cout << boost::format("UHD TXRX Loopback to File %s") % desc << std::endl; return ~0; } //create a usrp device std::cout << boost::format("Creating the receive usrp device with: %s...") % rx_args << std::endl; uhd::usrp::multi_usrp::sptr rx_usrp = uhd::usrp::multi_usrp::make(rx_args); std::cout << std::endl; //Lock mboard clocks rx_usrp->set_clock_source("internal"); //always select the subdevice first, the channel mapping affects the other settings if (vm.count("rx-subdev")) rx_usrp->set_rx_subdev_spec(rx_subdev); std::cout << boost::format("First Rx Device: %s") % rx_usrp->get_pp_string() << std::endl; //set the receive sample rate std::cout << boost::format("Setting 1st instance RX Rate: %f Msps...") % (rx_rate1/1e6) << std::endl; rx_usrp->set_rx_rate(rx_rate1,0); rx_rate1 = rx_usrp->get_rx_rate(0); std::cout << boost::format("Actual 1st instance RX Rate: %f Msps...") % (rx_rate1/1e6) << std::endl << std::endl; std::cout << boost::format("Setting 2nd instance RX Rate: %f Msps...") % (rx_rate2/1e6) << std::endl; rx_usrp->set_rx_rate(rx_rate2,1); rx_rate2 = rx_usrp->get_rx_rate(1); std::cout << boost::format("Actual 2nd instance RX Rate: %f Msps...") % (rx_rate2/1e6) << std::endl << std::endl; std::cout << boost::format("1st instance RX Rate After setting second: %f Msps...") % (rx_usrp->get_rx_rate(0)/1e6) << std::endl << std::endl; std::cout << "Configuring RX Channel 1" << std::endl; //set the receive center frequency if (not vm.count("rx-freq")){ std::cerr << "Please specify the center frequency with --rx-freq" << std::endl; return ~0; } std::cout << boost::format("Setting RX Freq: %f MHz...") % (rx_freq/1e6) << std::endl; uhd::tune_request_t rx_tune_request(rx_freq); rx_usrp->set_rx_freq(rx_tune_request, 0); std::cout << boost::format("Actual RX Freq: %f MHz...") % (rx_usrp->get_rx_freq(0)/1e6) << std::endl << std::endl; //Check Ref and LO Lock detect std::vector<std::string> rx_sensor_names = rx_usrp->get_rx_sensor_names(0); if (std::find(rx_sensor_names.begin(), rx_sensor_names.end(), "lo_locked") != rx_sensor_names.end()) { uhd::sensor_value_t lo_locked = rx_usrp->get_rx_sensor("lo_locked",0); std::cout << boost::format("Checking RX: %s ...") % lo_locked.to_pp_string() << std::endl; UHD_ASSERT_THROW(lo_locked.to_bool()); } if (total_num_samps == 0){ std::signal(SIGINT, &sig_int_handler); std::cout << "Press Ctrl + C to stop streaming..." << std::endl; } //reset usrp time to prepare for transmit/receive std::cout << boost::format("Setting device timestamp to 0...") << std::endl; rx_usrp->set_time_now(uhd::time_spec_t(0.0)); // Determine the number of samples per buffer for the second channel double rate_ratio = rx_rate2 / rx_rate1; int spb2 = (int)(spb * rate_ratio); std::cout << boost::format("Using %d samples for first channel and %d for the second") % spb % spb2 << std::endl; //recv to file recv_to_file<std::complex<float> >(rx_usrp, "fc32", otw, file1, file2, spb, spb2, total_num_samps, settling); //clean up transmit worker stop_signal_called = true; //rx1_thread.join_all(); //finished std::cout << std::endl << "Done!" << std::endl << std::endl; return EXIT_SUCCESS; }
_______________________________________________ USRP-users mailing list USRP-users@lists.ettus.com http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com