PROJECT NOTES AND GUIDELINES Student Name: Labib Sharrar PYTHON PROGRAMMING FOR THE USRP Stage 1: Installation of the USRP Toolchain and the Python API 1. Install an Ubuntu 22.04 virtual machine. For the virtual machine allocate at least 4 GB RAM (6 GB to be safe), 40 GB of hard disk space and at least 4 CPU cores. Once the Ubuntu virtual machine is installed, Guest Additions in the operating system. This will allow USB access for the USRP boards. Refer to the following video tutorials for precise instructions: https://www.youtube.com/watch?v=x5MhydijWmc&t=953s&ab_channel=ProgrammingKnowledge https://www.youtube.com/watch?v=zdkl16oAS1k&ab_channel=ProgrammingKnowledge2 2. Once all the installations and setups are complete based on the above tutorials, open a terminal in the virtual OS, type the following commands: sudo apt-get update sudo apt-get install python3-pip 3. Next, install a series of libraries by using the command below: sudo apt-get -y install autoconf automake build-essential ccache cmake cpufrequtils doxygen ethtool fort77 g++ gir1.2-gtk-3.0 git gobjectintrospection gpsd gpsd-clients inetutils-tools libasound2-dev libboostall-dev libcomedi-dev libcppunit-dev libfftw3-bin libfftw3-dev libfftw3doc libfontconfig1-dev libgmp-dev libgps-dev libgsl-dev liblog4cpp5-dev libncurses5 libncurses5-dev libpulse-dev libqt5opengl5-dev libqwt-qt5-dev libsdl1.2-dev libtool libudev-dev libusb-1.0-0 libusb-1.0-0-dev libusb-dev libxi-dev libxrender-dev libzmq3-dev libzmq5 ncurses-bin python3-cheetah python3-click python3-click-plugins python3-click-threading python3-dev python3-docutils python3-gi python3-gi-cairo python3-gps python3-lxml python3-mako python3-numpy python3-numpy-dbg python3-opengl python3-pyqt5 python3-requests python3-scipy python3-setuptools python3-six python3sphinx python3-yaml python3-zmq python3-ruamel.yaml swig wget 4. Install the PyBOMBS library which is good for building the USRP toolchain from source. Type the following commands in the terminal: pip3 install --upgrade git+https://github.com/gnuradio/pybombs.git pybombs auto-config pybombs recipes add-defaults pybombs prefix init ~/gnuradio source ~/gnuradio/setup_env.sh 5. Finally, type the following to install the UHD library: pybombs install uhd 6. Do not close the terminal once the installation is complete. Type python3 and then import uhd. If there is no error, then the installation is complete. 7. Go to the home/gnuradio/setup_env.sh file, copy all the code and add them to the ~/.bashrc file. To do this, type nano ~/.bashrc. This will take you to the bashrc.sh file on Ubuntu. Press Ctrl+v+w to go to the end to file. Then paste the copied lines from home/gnuradio/setup_env.sh file. Press Ctrl+o to save and Ctrl+x to exit. 8. After doing the above step, close the terminal and open a new one. Type python3 and try to import uhd. If there are no errors, then the USRP toolchain and the Python API and fully installed. Stage 2: Coding the for the USRP 1. Open Visual Studio Code and install the python extension and code runner. 2. In the IDE type the following: import uhd usrp = uhd.usrp.MultiUSRP() samples = usrp.recv_num_samps(10000, 100e6, 1e6, [0], 50) # units: N, Hz, Hz, list of channel IDs, dB print(samples[0:10]) The codes shown here are mostly for reception by the USRP. The “recv_num_samps()”, above is Python code that tunes the USRP to 100 MHz, using a sample rate of 1 MHz, and grabs 10,000 samples off the USRP, using a receive gain of 50 dB. The [0] is telling the USRP to use its first input port, and only receive one channel worth of samples (for a B210 to receive on two channels at once, for example, you could use [0, 1]). If you are trying to receive at a high rate but are getting overflows (O’s are showing up in your console). Instead of usrp = uhd.usrp.MultiUSRP(), use: usrp = uhd.usrp.MultiUSRP("num_recv_frames=1000") This makes the receive buffer much larger (the default value is 32), helping to reduce overflows. The actual size of the buffer in bytes depends on the USRP and type of connection, but simply setting num_recv_frames to a value much higher than 32 tends to help. However, for more serious applications, it is recommended that the following code be used. import uhd import numpy as np usrp = uhd.usrp.MultiUSRP() num_samps = 10000 # number of samples received center_freq = 100e6 # Hz sample_rate = 1e6 # Hz gain = 50 # dB usrp.set_rx_rate(sample_rate, 0) usrp.set_rx_freq(uhd.libpyuhd.types.tune_request(center_freq), 0) usrp.set_rx_gain(gain, 0) # Set up the stream and receive buffer st_args = uhd.usrp.StreamArgs("fc32", "sc16") st_args.channels = [0] metadata = uhd.types.RXMetadata() streamer = usrp.get_rx_stream(st_args) recv_buffer = np.zeros((1, 1000), dtype=np.complex64) # Start Stream stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.start_cont) stream_cmd.stream_now = True streamer.issue_stream_cmd(stream_cmd) # Receive Samples samples = np.zeros(num_samps, dtype=np.complex64) for i in range(num_samps//1000): streamer.recv(recv_buffer, metadata) samples[i*1000:(i+1)*1000] = recv_buffer[0] # Stop Stream stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.stop_cont) streamer.issue_stream_cmd(stream_cmd) print(len(samples)) print(samples[0:10]) 3. In order to transmit signals from the USRP, the following code can be used: import uhd import numpy as np usrp = uhd.usrp.MultiUSRP() samples = 0.1*np.random.randn(10000) + 0.1j*np.random.randn(10000) # create random signal duration = 10 # seconds center_freq = 915e6 sample_rate = 1e6 gain = 20 # [dB] start low then work your way up usrp.send_waveform(samples, duration, center_freq, sample_rate, [0], gain) Similar to the recv_num_samps() convenience function, UHD provides the send_waveform() function to transmit a batch of samples. If you specify a duration (in seconds) longer than the provided signal, it will simply repeat it. It helps to keep the values of samples between -1.0 and 1.0. 4. If you have connected an external 10 MHz and PPS source to your USRP, you will want to make sure to call these two lines after initializing your USRP: usrp.set_clock_source("external") usrp.set_time_source("external") If you are using an onboard GPSDO, you will instead use: usrp.set_clock_source("gpsdo") usrp.set_time_source("gpsdo") On the frequency sync side there’s not much else to do; the LO used in the USRP’s mixer is now going to be tied to the external source or GPSDO. But on the timing side, you may wish to command the USRP to start sampling exactly on the PPS, for example. This can be done with the following code: # copy the receive example above, everything up until # Start Stream # Wait for 1 PPS to happen, then set the time at next PPS to 0.0 time_at_last_pps = usrp.get_time_last_pps().get_real_secs() while time_at_last_pps == usrp.get_time_last_pps().get_real_secs(): time.sleep(0.1) # keep waiting till it happens- if this while loop never finishes then the PPS signal isn't there usrp.set_time_next_pps(uhd.libpyuhd.types.time_spec(0.0)) # Schedule Rx of num_samps samples exactly 3 seconds from last PPS stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done) stream_cmd.num_samps = num_samps stream_cmd.stream_now = False stream_cmd.time_spec = uhd.libpyuhd.types.time_spec(3.0) # set start time (try tweaking this) streamer.issue_stream_cmd(stream_cmd) # Receive Samples. recv() will return zeros, then our samples, then more zeros, letting us know it's done waiting_to_start = True # keep track of where we are in the cycle (see above comment) nsamps = 0 i = 0 samples = np.zeros(num_samps, dtype=np.complex64) while nsamps != 0 or waiting_to_start: nsamps = streamer.recv(recv_buffer, metadata) if nsamps and waiting_to_start: waiting_to_start = False elif nsamps: samples[i:i+nsamps] = recv_buffer[0][0:nsamps] i += nsamps If it seems like it’s not working, but is not throwing any errors, try changing that 3.0 number from anything between 1.0 and 5.0. You can also check the metadata after the call to recv(), simply check if metadata.error_code != uhd.types.RXMetadataErrorCode.none:. For debugging sake, you can verify the 10 MHz signal is showing up to the USRP by checking the return of usrp.get_mboard_sensor("ref_locked", 0). If the PPS signal isn’t showing up, you’ll know it because the first while loop in the code above will never finish. C++ PROGRAMMING FOR THE USRP Stage 1: C++ coding for the USRP 1. Include the following headers in the .cpp file: #include <uhd/utils/thread_priority.hpp> #include <uhd/utils/safe_main.hpp> #include <uhd/usrp/multi_usrp.hpp> #include <uhd/exception.hpp> #include <uhd/types/tune_request.hpp> #include <boost/program_options.hpp> #include <boost/format.hpp> #include <boost/thread.hpp> #include <iostream> 2. Defines a safe wrapper that places a catch-all around main. If an exception is thrown, it prints to stderr and returns. int UHD_SAFE_MAIN(int argc, char *argv[]) { ... } 3. Set the scheduling priority on the current thread. Same as set_thread_priority but does not throw on failure. uhd::set_thread_priority_safe(); 4. Create variables as shown below: std::string std::string std::string std::string double double double double device_args("addr=192.168.10.2"); subdev("A:0"); ant("TX/RX"); ref("internal"); rate(1e6); freq(915e6); gain(10); bw(1e6); 5. Creating USRP Object uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(device_args); 6. Set the clock source for the usrp device. This sets the source for a 10 MHz reference clock. usrp->set_clock_source(ref) 7. Set the RX frontend specification. The subdev spec maps a physical part of a daughter-board to a channel number. Set the subdev spec before calling into any methods with a channel number. usrp->set_rx_subdev_spec(subdev) 8. Get a printable summary for this USRP configuration. usrp->get_pp_string() 9. Set the RX sample rate. The rate is in Samples Per Second. usrp->set_rx_rate(rate) 10. Gets the RX sample rate. Returns the rate is in Samples Per Second. usrp->get_rx_rate() 11. Create a tune request, with the RF frequency in Hz. Set the RX center frequency. uhd::tune_request_t tune_request(freq) usrp->set_rx_freq(tune_request) 12. Get the RX center frequency. Returns the frequency in Hz. usrp->get_rx_freq() 13. Set the RX gain value for the specified gain element. For an empty name, distribute across all gain elements. Sets the gain in dB. usrp->set_rx_gain(gain) 14. Get the RX gain value for the specified gain element. For an empty name, sum across all gain elements. Returns the gain in dB. usrp->get_rx_gain() 15. Set the RX bandwidth on the frontend. Sets the bandwidth in Hz. usrp->set_rx_bandwidth(bw) 16. Get the RX bandwidth on the frontend. Returns the bandwidth in Hz. usrp->get_rx_bandwidth() 17. Select the RX antenna on the frontend. usrp->set_rx_antenna(ant) 18. Get the selected RX antenna on the frontend. Returns the antenna name. usrp->get_rx_antenna() 19. Exiting: return EXIT_SUCCESS; 20. Full C++ Example Code: #include #include #include #include #include #include #include #include #include <uhd/utils/thread_priority.hpp> <uhd/utils/safe_main.hpp> <uhd/usrp/multi_usrp.hpp> <uhd/exception.hpp> <uhd/types/tune_request.hpp> <boost/program_options.hpp> <boost/format.hpp> <boost/thread.hpp> <iostream> int UHD_SAFE_MAIN(int argc, char *argv[]) { uhd::set_thread_priority_safe(); std::string std::string std::string std::string double double double double device_args("addr=192.168.10.2"); subdev("A:0"); ant("TX/RX"); ref("internal"); rate(1e6); freq(915e6); gain(10); bw(1e6); //create a usrp device std::cout << std::endl; std::cout << boost::format("Creating the usrp device with: %s...") % device_args << std::endl; uhd::usrp::multi_usrp::sptr usrp = uhd::usrp::multi_usrp::make(device_args); // Lock mboard clocks std::cout << boost::format("Lock mboard clocks: %f") % ref << std::endl; usrp->set_clock_source(ref); //always select the subdevice first, the channel mapping affects the other settings std::cout << boost::format("subdev set to: %f") % subdev << std::endl; usrp->set_rx_subdev_spec(subdev); std::cout << boost::format("Using Device: %s") % usrp->get_pp_string() << std::endl; //set the sample rate if (rate <= 0.0) { std::cerr << "Please specify a valid sample rate" << std::endl; return ~0; } // set sample rate std::cout << boost::format("Setting RX Rate: %f Msps...") % (rate / 1e6) << std::endl; usrp->set_rx_rate(rate); std::cout << boost::format("Actual RX Rate: %f Msps...") % (usrp>get_rx_rate() / 1e6) << std::endl << std::endl; // set freq std::cout << boost::format("Setting RX Freq: %f MHz...") % (freq / 1e6) << std::endl; uhd::tune_request_t tune_request(freq); usrp->set_rx_freq(tune_request); std::cout << boost::format("Actual RX Freq: %f MHz...") % (usrp>get_rx_freq() / 1e6) << std::endl << std::endl; // set the rf gain std::cout << boost::format("Setting RX Gain: %f dB...") % gain << std::endl; usrp->set_rx_gain(gain); std::cout << boost::format("Actual RX Gain: %f dB...") % usrp->get_rx_gain() << std::endl << std::endl; // set the IF filter bandwidth std::cout << boost::format("Setting RX Bandwidth: %f MHz...") % (bw / 1e6) << std::endl; usrp->set_rx_bandwidth(bw); std::cout << boost::format("Actual RX Bandwidth: %f MHz...") % (usrp>get_rx_bandwidth() / 1e6) << std::endl << std::endl; // set the antenna std::cout << boost::format("Setting RX Antenna: %s") % ant << std::endl; usrp->set_rx_antenna(ant); std::cout << boost::format("Actual RX Antenna: %s") % usrp->get_rx_antenna() << std::endl << std::endl; return EXIT_SUCCESS; }