ECE 132A: Lab 1
Due: Saturday, February 14, 2026 at 11:59 PM via Gradescope
- Successfully connect to the RemoteRF platform and make a reservation for a Pluto SDR.
- Write a Python script which configures a Pluto SDR and sets its key system parameters.
- Generate a complex exponential transmit signal at a desired frequency.
- Transmit the generated complex exponential from the Pluto SDR.
- Fetch receive samples from the Pluto SDR in Python (a near-perfect copy of your transmit signal).
- Plot the FFT of the received samples.
- Estimate the frequency of the received complex exponential signal to observe carrier frequency offset when transmitting and receiving with separate SDRs.
Part I: Getting Things Set Up
Throughout this quarter, you will be using RemoteRF, a platform that allows you to remotely interface with software-defined radios (SDRs) through the UCLA network.
Put simply, a server is running in my lab that has physically connected to it multiple SDRs, each of which you may remotely access via Python through a simple API we have created.
To begin using one of these SDRs connected to RemoteRF, you must download a package via pip and then create an account through a command line interface.
After making an account, you can make a reservation for a particular SDR, allowing you (and only you) to access that SDR during your reservation period.
Upon making a reservation, a token will be issued to you, which can then be inserted into your Python code, allowing you to remotely access the SDR as if it were physically connected to your local machine.
Behind the scenes, all commands and data will be transferred between the you and the SDR over the UCLA network via the RemoteRF platform.
In this lab, you will get things set up so that you may begin using the RemoteRF platform.
Given we will be using the RemoteRF platform throughout the quarter, this is an important first step.
Begin by following through the tutorial on this page.
Include in your lab report the FFT output when running ‘‘A First Script using RemoteRF’’.
Part II: Observing Carrier Frequency Offset
Following Part I, you should have a script that transmits and receives a complex exponential. When crafting this complex exponential in complex baseband (in Python), we can specify its exact frequency of 100 kHz, in this case. Within the transmitter of the SDR, this complex exponential gets shifted up in frequency by the SDR’s specified carrier frequency (e.g., 915 MHz), meaning the complex exponential is now 100 kHz above the carrier frequency. Then, at the receiver, the signal gets shifted back down in frequency by the SDR’s specified carrier frequency. If all works perfectly, the complex exponential observed at the output of the receiver (i.e., at baseband) will be at 100 kHz.
In practice, however, the transmitter and receiver do not always have exactly the same carrier frequency, even though they may be set to the same value.
This is because neither the transmitter or the receiver can synthesize a carrier at exactly the desired carrier frequency; there always exists some frequency offset.
In other words, in setting the Pluto SDR transmitter to a desired carrier frequency via sdr.tx_lo = int(tx_carrier_freq_Hz), the Pluto cannot perfectly realize the desired carrier frequency.
For example, if the desired carrier frequency is 915 MHz, the transmitter may generate a carrier at 915.1 MHz while the receiver may generate a carrier at 914.9 MHz.
In this context, carrier frequency offset (CFO) refers to the difference between the transmitter’s realized carrier frequency and the receiver’s realized carrier frequency; in this case the CFO would be 915.1 MHz minus 914.9 MHz, which equals 0.2 MHz, or simply 200 kHz.
Consequently, the baseband signal at the output of the receiver will be shifted in frequency by this difference (e.g., 200 kHz in this running example).
While this frequency shift may seem minor, even a small degree of CFO can lead to poor communication system performance.
For this reason, virtually all modern wireless communication systems correct for CFO in order to function properly.
Practically, CFO is always present but is rarely known a priori, meaning it must first be estimated in order for it to be corrected.
CFO estimation and correction will not be explored much (if at all) in this course but is explored in more detail in courses such as ECE 230B Digital Communication Systems.
In this part, we will observe CFO by transmitting with one SDR and receiving with another SDR.
To do so, you will need to make two separate reservations—one for each SDR.
Make sure that the two SDRs you reserve are over-the-air (OTA), not loopback.
Repeat the experiment you did in Part I, but adapt your code so that you transmit with one SDR and receive with another, i.e., define sdr_tx and sdr_rx instead of just sdr, as noted at the bottom of this page.
After running your modified script, if you zoom in on the plot of the FFT, you will see the peak is not centered at exactly 100 kHz.
Use zero-padding when taking the FFT to interpolate and identify the location of the peak.
You may need to oversample by a factor of 4 or more.
Where is it centered?
Write some code to automatically find the location of the peak and estimate the CFO—its deviation from 100 kHz is the CFO.
Zoom in on the FFT output and include such in your lab report, along with the value of CFO you observe.
Include the CFO in the title of the plot.
- A plot of the FFT output when completing Part I.
- A plot of the FFT output (zoomed in) when completing Part II, with the estimated CFO in the title.
- Python code used in completing this lab.