5、PlutoSDR【入门软件无线电(SDR)】PySDR:使用 Python 的 SDR 和 DSP 指南

因为设备不同,本教程未实测,仅作为PlutoSDR参考

在这里插入图片描述
在本章中,我们将学习如何将Python API用于PlutoSDR,这是ADI公司的低成本SDR。我们将介绍PlutoSDR安装步骤,以使驱动程序/软件运行,然后讨论使用Python中的PlutoSDR进行发送和接收

软件/驱动程序安装

设置虚拟机
虽然本教程中提供的 Python 代码应该在 Windows、Mac 和 Linux 下工作,但下面的安装说明特定于 Ubuntu 22。如果您在按照ADI公司提供的说明在操作系统上安装软件时遇到问题,我建议您安装Ubuntu 22 VM并尝试以下说明。或者,如果您使用的是Windows 11,则使用Ubuntu 22的Linux(WSL)的Windows子系统往往运行良好,并且支持开箱即用的图形。
1、安装并打开 VirtualBox .
2、创建新的 VM。对于内存大小,我建议使用计算机 RAM 的 50%。
3、创建虚拟硬盘,选择 VDI,然后动态分配大小。15 GB 应该足够了。如果你想真正安全,你可以使用更多。
4、下载 Ubuntu 22 桌面版 .iso- https://ubuntu.com/download/desktop
5、启动 VM。它会要求您提供安装介质。选择 Ubuntu 22 桌面.iso文件。选择“安装 ubuntu”,使用默认选项,弹出窗口将警告您即将进行的更改。点击继续。选择名称/密码,然后等待 VM 完成初始化。完成后,VM 将重新启动,但应在重新启动后关闭 VM 电源。
6、进入 VM 设置(齿轮图标)。
7、在“系统>处理器”下,>至少选择 3 个 CPU。如果您有实际的视频卡,那么在显示器>视频内存中>选择更高的视频卡。
8、启动 VM。
9、我建议安装 VM 来宾添加功能。在 VM 中,转到“设备>插入来宾添加 CD”>在弹出框时点击运行。按照说明操作。重新启动 VM。可以通过“双向共享剪贴板”>“共享剪贴板”>设备启用共享剪贴板。

连接PlutoSDR

1、如果在系统首选项中运行 OSX,而不是在 VM 中运行 OSX,请启用“内核扩展”。然后安装 HoRNDIS(之后您可能需要重新启动)。
2、如果运行 Windows,请安装此驱动程序: https://github.com/analogdevicesinc/plutosdr-m2k-drivers-win/releases/download/v0.7/PlutoSDR-M2k-USB-Drivers.exe
3、如果运行Linux,你不用做任何特别的事情。
4、通过 USB 将 Pluto 插入主机。确保使用 Pluto上的中间USB端口,因为另一个仅用于电源。插入 Pluto应该创建一个虚拟网络适配器,即 Pluto看起来像一个USB以太网适配器。
5、在主机(不是 VM)上,打开终端或首选 ping 工具,然后 ping 192.168.2.1。如果这不起作用,请停止并调试网络接口。
6、在 VM 中,打开一个新终端
7、Ping 192.168.2.1.如果这不起作用,请在此处停止并调试。在ping时,拔下你的Pluto并确保ping停止,如果它一直ping,那么该IP地址的其他东西在网络上,你必须改变Pluto(或其他设备)的IP,然后再继续。
8、记下Pluto的IP地址,因为当我们开始使用Python中的Pluto时,您将需要它

安装 PlutoSDR 驱动程序

1、libiio,ADI公司用于接口硬件的“跨平台”库
2、libad9361-iio,AD9361是PlutoSDR内部的特定RF芯片
3、pyadi-iio,Pluto的Python API,这是我们的最终目标,但它取决于前两个库。

sudo apt-get install build-essential git libxml2-dev bison flex libcdk5-dev cmake python3-pip libusb-1.0-0-dev libavahi-client-dev libavahi-common-dev libaio-dev
cd ~
git clone --branch v0.23 https://github.com/analogdevicesinc/libiio.git
cd libiio
mkdir build
cd build
cmake -DPYTHON_BINDINGS=ON ..
make -j$(nproc)
sudo make install
sudo ldconfig

cd ~
git clone https://github.com/analogdevicesinc/libad9361-iio.git
cd libad9361-iio
mkdir build
cd build
cmake ..
make -j$(nproc)
sudo make install

cd ~
git clone --branch v0.0.14 https://github.com/analogdevicesinc/pyadi-iio.git
cd pyadi-iio
pip3 install --upgrade pip
pip3 install -r requirements.txt
sudo python3 setup.py install

测试 PlutoSDR 驱动程序

打开新终端(在 VM 中)并键入以下命令:

python3
import adi
sdr = adi.Pluto('ip:192.168.2.1') # or whatever your Pluto's IP is
sdr.sample_rate = int(2.5e6)
sdr.rx()

如果您到目前为止没有错误,请继续执行后续步骤。

更改 Pluto的 IP 地址

如果由于某种原因默认 IP 192.168.2.1 不起作用,因为您已经有一个 192.168.2.0 子网,或者因为您希望同时连接多个 Pluto,您可以使用以下步骤更改 IP:
1、编辑 PlutoSDR 大容量存储设备上的配置.txt文件(即插入 Pluto 后显示的 USB 驱动器外观)。输入所需的新 IP。
2、弹出大容量存储设备(不要拔下 Pluto的插头!在 Ubuntu 22 中,查看文件浏览器时,PlutoSDR 设备旁边有一个弹出符号。
3、等待几秒钟,然后通过拔下 Pluto并重新插入来重新供电。返回配置.txt以确定您的更改是否已保存。
请注意,此过程还用于将不同的固件映像刷新到Pluto上。有关更多详细信息,请参阅 https://wiki.analog.com/university/tools/pluto/users/firmware 。

“破解”PlutoSDR以增加RF范围

PlutoSDR的中心频率范围和采样率有限,但底层芯片能够实现更高的频率。按照以下步骤解锁芯片的完整频率范围。请记住,此过程由ADI公司提供,因此风险尽可能低。PlutoSDR的频率限制与ADI公司根据较高频率下的严格性能要求对AD9364进行“分档”有关。…作为SDR爱好者和实验者,我们不太关心上述性能要求。
打开终端(主机或虚拟机,没关系):

ssh root@192.168.2.1

默认密码为模拟密码。您应该会看到PlutoSDR欢迎屏幕。你现在已经SSH进入了Pluto本身的ARM CPU!如果您的 Pluto 固件版本为 0.31 或更低,请在 中键入以下命令:

fw_setenv attr_name compatible
fw_setenv attr_val ad9364
reboot

对于 0.32 及更高版本的使用:

fw_setenv compatible ad9364
reboot

您现在应该能够调谐到 6 GHz 和低至 70 MHz,更不用说使用高达 56 MHz 的采样率了! 耶!

接收

使用PlutoSDR的Python API进行采样非常简单。对于任何SDR应用程序,我们知道我们必须告诉它中心频率,采样率和增益(或是否使用自动增益控制)。可能还有其他细节,但这三个参数是SDR有足够的信息来接收样本所必需的。一些 SDR 有一个命令告诉它开始采样,而其他 SDR 如Pluto将在您初始化后立即开始采样。一旦 SDR 的内部缓冲区填满,最早的样本就会被丢弃。所有SDR API都有某种“接收样本”功能,对于Pluto,它是rx(),它返回一批样本。每批的具体样品数量由事先设置的缓冲区大小定义。
下面的代码假设你安装了Pluto的Python API。此代码初始化 Pluto,将采样速率设置为 1 MHz,将中心频率设置为 100 MHz,并在关闭自动增益控制的情况下将增益设置为 70 dB。请注意,设置中心频率、增益和采样率的顺序通常无关紧要。在下面的代码片段中,我们告诉Pluto,我们希望它每次调用 rx() 给我们 10,000 个样本。我们打印出前 10 个样本。

import numpy as np
import adi

sample_rate = 1e6 # Hz
center_freq = 100e6 # Hz
num_samps = 10000 # number of samples returned per call to rx()

sdr = adi.Pluto()
sdr.gain_control_mode_chan0 = 'manual'
sdr.rx_hardwaregain_chan0 = 70.0 # dB
sdr.rx_lo = int(center_freq)
sdr.sample_rate = int(sample_rate)
sdr.rx_rf_bandwidth = int(sample_rate) # filter width, just set it to the same as sample rate for now
sdr.rx_buffer_size = num_samps

samples = sdr.rx() # receive samples off Pluto
print(samples[0:10])

现在我们不打算对这些示例做任何有趣的事情,但是这本教程的其余部分充满了适用于 IQ 示例的 Python 代码,就像我们上面收到的一样。

接收增益 (Receive Gain)

Pluto可以配置为具有固定接收增益或自动接收增益。自动增益控制(AGC)将自动调整接收增益以保持强大的信号电平(对于任何好奇的人来说都是-12dBFS)。AGC不要与数字化信号的模数转换器(ADC)混淆。从技术上讲,AGC是一种闭环反馈电路,用于控制放大器的增益以响应接收信号。其目标是在输入功率水平变化的情况下保持恒定的输出功率水平。通常,AGC将调整增益以避免接收器饱和(即达到ADC范围的上限),同时允许信号“填充”尽可能多的ADC位。
PlutoSDR内部的射频集成电路(RFIC)有一个具有几种不同设置的AGC模块。(RFIC是一种用作收发器的芯片:它发送和接收无线电波。首先,请注意, Pluto上的接收增益范围为0到74.5 dB。当处于“手动”AGC模式时,AGC关闭,你必须告诉 Pluto使用什么接收增益,例如:

sdr.gain_control_mode_chan0 = "manual" # turn off AGC
gain = 50.0 # allowable range is 0 to 74.5 dB
sdr.rx_hardwaregain_chan0 = gain # set receive gain

如果要启用 AGC,则必须从以下两种模式中选择一种:
1、sdr.gain_control_mode_chan0 = “slow_attack”
2、sdr.gain_control_mode_chan0 = “fast_attack”
启用 AGC 后,您无需向 rx_hardwaregain_chan0 提供值。它将被忽略,因为 Pluto本身会调整信号的增益。Pluto有两种AGC模式:快速攻击和慢速攻击,如上面截取的代码所示。如果你仔细想想,两者之间的区别是直观的。快速攻击模式对信号的反应更快。换句话说,当接收信号改变电平时,增益值会变化得更快。调整信号功率水平可能很重要,特别是对于使用相同频率进行发送和接收的时分双工(TDD)系统。在这种情况下,将增益控制设置为快速攻击模式会限制信号衰减。无论哪种模式,如果没有信号而只有噪声,AGC将最大化增益设置;当信号确实出现时,它将短暂地使接收器饱和,直到AGC能够做出反应并降低增益。您始终可以通过以下方式实时检查当前增益水平:

sdr._get_iio_attr('voltage0','hardwaregain', False)

有关PlutoAGC的更多详细信息,例如如何更改高级AGC设置,请参阅https://wiki.analog.com/resources/tools-software/linux-drivers/iio-transceiver/ad9361

传输

在与 Pluto传输任何信号之前,请确保在 Pluto的TX端口和将充当接收器的任何设备之间连接SMA电缆。请务必始终从通过电缆传输开始,尤其是在您学习如何传输时,以确保 SDR 的行为符合您的预期。始终保持极低的发射功率,以免使接收器过压,因为电缆不会像无线通道那样衰减信号。如果您拥有衰减器(例如 30 dB),现在是使用它的好时机。如果您没有另一个 SDR 或频谱分析仪作为接收器,理论上您可以使用同一 Pluto 上的 RX 端口,但它可能会变得复杂。我建议拿起 10 美元的 RTL-SDR 作为接收 SDR。
发送与接收非常相似,只是不是告诉SDR接收一定数量的样本,而是给它一定数量的样本进行传输。我们将设置@ 1# ,而不是 rx_lo ,以指定要传输的载波频率。采样率在 RX 和 TX 之间共享,因此我们将像正常一样设置它。下面显示了一个完整的发射示例,其中我们以+100 kHz生成正弦波,然后以915 MHz的载波频率传输复数信号,使接收器看到915.1 MHz的载波。这样做真的没有实际的理由,我们可以将center_freq设置为 915.1e6 并传输一个 1 的数组,但我们想生成复杂的样本用于演示目的。

import numpy as np
import adi

sample_rate = 1e6 # Hz
center_freq = 915e6 # Hz

sdr = adi.Pluto("ip:192.168.2.1")
sdr.sample_rate = int(sample_rate)
sdr.tx_rf_bandwidth = int(sample_rate) # filter cutoff, just set it to the same as sample rate
sdr.tx_lo = int(center_freq)
sdr.tx_hardwaregain_chan0 = -50 # Increase to increase tx power, valid range is -90 to 0 dB

N = 10000 # number of samples to transmit at once
t = np.arange(N)/sample_rate
samples = 0.5*np.exp(2.0j*np.pi*100e3*t) # Simulate a sinusoid of 100 kHz, so it should show up at 915.1 MHz at the receiver
samples *= 2**14 # The PlutoSDR expects samples to be between -2^14 and +2^14, not -1 and +1 like some SDRs

# Transmit our batch of samples 100 times, so it should be 1 second worth of samples total, if USB can keep up
for i in range(100):
    sdr.tx(samples) # transmit the batch of samples once

下面是有关此代码的一些说明。首先,您希望模拟IQ样本,使其介于-1和1之间,但在传输它们之前,由于ADI公司实现 tx() 函数的方式,我们必须按2^14进行缩放。如果您不确定最小/最大值是多少,只需使用 print(np.min(samples), np.max(samples)) 打印出来或编写 if 语句以确保它们永远不会超过 1 或低于 -1(假设代码在 2^14 缩放之前)。就发射增益而言,范围为-90至0 dB,因此0 dB是最高发射功率。我们总是希望从低发射功率开始,然后根据需要逐步提高,因此默认情况下,我们将增益设置为-50 dB,这是向低端方向。不要仅仅因为您的信号没有显示而简单地将其设置为 0 dB;可能还有其他问题,你不想炸你的接收器。

重复传输样本

如果你想重复不断地传输同一组样本,而不是像我们上面那样在 Python 中使用 for/while 循环,你可以只用一行来告诉 Pluto 这样做:

sdr.tx_cyclic_buffer = True # Enable cyclic buffers

然后,您将像正常一样传输样本: sdr.tx(samples) 一次, Pluto将无限期地继续传输信号,直到调用 sdr 对象析构函数。要更改正在连续传输的样本,您不能简单地使用一组新样本再次调用 sdr.tx(samples) ,您必须首先调用 sdr.tx_destroy_buffer() ,然后调用 sdr.tx(samples) 。

合法无线传输

我能找到的唯一与实际上不是正在销售的产品的收音机相关的法规是针对在 AM/FM 频段中操作低功率 AM 或 FM 广播电台的。还有一个关于“自制设备”的部分,但它特别指出它不适用于由套件构建的任何东西,并且说使用SDR的传输设备是自制设备是牵强的。总之,FCC法规并不像“您只能在这些功率水平以下的这些频率上传输”那么简单,而是一套用于测试和合规性的庞大规则。无数次,学生问我允许他们用天线传输什么频率(在美国)。据我所知,简短的回答是没有。通常,当人们指出谈论发射功率限制的具体法规时,他们指的是FCC的“标题47,第15部分”(47CFR 15)法规。但这些是针对制造商制造和销售在ISM频段运行的设备的法规,这些法规讨论了如何对其进行测试。第 15 部分设备是指个人不需要许可证即可在其使用的任何频谱中操作设备,但设备本身必须经过授权/认证,以表明它们在营销和销售之前遵循 FCC 法规运行。第15部分法规确实规定了不同频谱的最大发射和接收功率水平,但实际上都不适用于使用SDR或其自制无线电传输信号的人。
另一种看待它的方法是说“好吧,这些不是第 15 部分的设备,但让我们遵循第 15 部分的规则,就好像它们是一样”。对于915 MHz ISM频段,规则是“在规定频段内辐射的任何发射的场强在30米处不得超过500微伏/米。本段中的排放限值基于使用平均探测器的测量仪器。如您所见,它并不像以瓦特为单位的最大发射功率那么简单。
现在,如果您有业余无线电(HAM)许可证,FCC允许您使用为业余无线电预留的某些频段。仍然有指导方针和最大发射功率需要遵循,但至少这些数字是以有效辐射功率的瓦数指定的。此信息图显示了哪些频段可供使用,具体取决于您的许可证类别(技术人员、一般和额外)。我建议任何有兴趣使用 SDR 进行传输的人获得他们的业余无线电许可证,请参阅 ARRL 的获得许可页面了解更多信息。

同时发送和接收

使用tx_cyclic_buffer技巧,您可以轻松地同时接收和发送,方法是启动发射器,然后接收。以下代码显示了在 915 MHz 频段发送 QPSK 信号、接收该信号并绘制 PSD 的工作示例。

import numpy as np
import adi
import matplotlib.pyplot as plt

sample_rate = 1e6 # Hz
center_freq = 915e6 # Hz
num_samps = 100000 # number of samples per call to rx()

sdr = adi.Pluto("ip:192.168.2.1")
sdr.sample_rate = int(sample_rate)

# Config Tx
sdr.tx_rf_bandwidth = int(sample_rate) # filter cutoff, just set it to the same as sample rate
sdr.tx_lo = int(center_freq)
sdr.tx_hardwaregain_chan0 = -50 # Increase to increase tx power, valid range is -90 to 0 dB

# Config Rx
sdr.rx_lo = int(center_freq)
sdr.rx_rf_bandwidth = int(sample_rate)
sdr.rx_buffer_size = num_samps
sdr.gain_control_mode_chan0 = 'manual'
sdr.rx_hardwaregain_chan0 = 0.0 # dB, increase to increase the receive gain, but be careful not to saturate the ADC

# Create transmit waveform (QPSK, 16 samples per symbol)
num_symbols = 1000
x_int = np.random.randint(0, 4, num_symbols) # 0 to 3
x_degrees = x_int*360/4.0 + 45 # 45, 135, 225, 315 degrees
x_radians = x_degrees*np.pi/180.0 # sin() and cos() takes in radians
x_symbols = np.cos(x_radians) + 1j*np.sin(x_radians) # this produces our QPSK complex symbols
samples = np.repeat(x_symbols, 16) # 16 samples per symbol (rectangular pulses)
samples *= 2**14 # The PlutoSDR expects samples to be between -2^14 and +2^14, not -1 and +1 like some SDRs

# Start the transmitter
sdr.tx_cyclic_buffer = True # Enable cyclic buffers
sdr.tx(samples) # start transmitting

# Clear buffer just to be safe
for i in range (0, 10):
    raw_data = sdr.rx()

# Receive samples
rx_samples = sdr.rx()
print(rx_samples)

# Stop transmitting
sdr.tx_destroy_buffer()

# Calculate power spectral density (frequency domain version of signal)
psd = np.abs(np.fft.fftshift(np.fft.fft(rx_samples)))**2
psd_dB = 10*np.log10(psd)
f = np.linspace(sample_rate/-2, sample_rate/2, len(psd))

# Plot time domain
plt.figure(0)
plt.plot(np.real(rx_samples[::100]))
plt.plot(np.imag(rx_samples[::100]))
plt.xlabel("Time")

# Plot freq domain
plt.figure(1)
plt.plot(f/1e6, psd_dB)
plt.xlabel("Frequency [MHz]")
plt.ylabel("PSD")
plt.show()

您应该看到如下所示的内容,假设您连接了正确的天线或电缆:在这里插入图片描述
慢慢调整 sdr.tx_hardwaregain_chan0 和 sdr.rx_hardwaregain_chan0 以确保接收到的信号按预期变弱/变强是一个很好的练习。

参考接口

有关可以调用的 sdr 属性和函数的完整列表,请参阅 https://github.com/analogdevicesinc/pyadi-iio/blob/master/adi/ad936x.py 。

Python练习

我没有提供要运行的代码,而是创建了多个练习,其中提供了 95% 的代码,其余代码相当简单,供您创建。这些练习并不意味着困难。他们缺少足够的代码来让你思考。

练习 1:确定 USB 吞吐量

让我们尝试从PlutoSDR接收样本,在这个过程中,看看我们每秒可以通过USB 2.0连接推送多少样本。
您的任务是创建一个 Python 脚本来确定 Python 中接收的速率样本,即计算收到的样本并跟踪时间以计算速率。然后,尝试使用不同的sample_rate和buffer_size,看看它如何影响可实现的最高速率。
请记住,如果您每秒接收的样本少于指定的sample_rate,则意味着您丢失/丢弃了一部分样本,这可能会在高sample_rate时发生。Pluto只使用USB 2.0。
以下代码将充当起点,但包含完成此任务所需的说明。

import numpy as np
import adi
import matplotlib.pyplot as plt
import time

sample_rate = 10e6 # Hz
center_freq = 100e6 # Hz

sdr = adi.Pluto("ip:192.168.2.1")
sdr.sample_rate = int(sample_rate)
sdr.rx_rf_bandwidth = int(sample_rate) # filter cutoff, just set it to the same as sample rate
sdr.rx_lo = int(center_freq)
sdr.rx_buffer_size = 1024 # this is the buffer the Pluto uses to buffer samples
samples = sdr.rx() # receive samples off Pluto

此外,为了计时某件事需要多长时间,您可以使用以下代码:

start_time = time.time()
# do stuff
end_time = time.time()
print('seconds elapsed:', end_time - start_time)

这里有几个提示可以帮助您入门。
提示 1:您需要将行“samples = sdr.rx()”放入一个运行多次(例如,100 次)的循环中。您必须计算每次调用 sdr.rx() 时获得的样本数,同时跟踪经过的时间。
提示 2:仅仅因为您正在计算每秒的样本,这并不意味着您必须执行 1 秒的接收样本。您可以将收到的样本数量除以经过的时间量。
提示 3:从 sample_rate = 10e6 开始,就像代码显示的那样,因为这个速率远远超过了 USB 2.0 可以支持的范围。您将能够看到有多少数据通过。然后你可以调整rx_buffer_size。让它变大很多,看看会发生什么。一旦你有一个工作脚本并摆弄了rx_buffer_size,试着调整sample_rate。确定在能够在 Python 中接收 100% 的样本(即,在 100% 占空比下接收样本)之前,您必须达到多低。
提示 4:在调用 sdr.rx() 的循环中,尽量少做,以免增加额外的执行时间延迟。不要做任何密集的事情,比如从循环内部打印。
作为本练习的一部分,您将了解 USB 2.0 的最大吞吐量。您可以在线查找以验证您的发现。
作为奖励,尝试更改center_freq和rx_rf_bandwidth,看看它是否会影响您可以从Pluto接收样本的速度。

练习 2:创建频谱图/瀑布图

在本练习中,您将创建一个频谱图,又名瀑布图,就像我们在频域章节末尾学到的那样。频谱图只是一堆堆叠在一起的FFT。换句话说,它是一个图像,一个轴代表频率,另一个轴代表时间。
在频域一章中,我们学习了执行FFT的Python代码。在本练习中,您可以使用上一练习中的代码片段,以及一些基本的 Python 代码。
1、尝试将sdr.rx_buffer_size设置为 FFT 大小,以便每次调用 sdr.rx() 时始终执行 1 FFT。
2、构建一个 2D 数组来保存所有 FFT 结果,其中每行为 1 FFT。一个用零填充的二维数组可以用:np.zeros((num_rows, fft_size))创建。使用以下命令访问数组的第 i 行:waterfall_2darray[i,:]。
3、plt.imshow() 是显示 2D 数组的便捷方法。它会自动缩放颜色。
作为延伸目标,使频谱图实时更新。

  • 16
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值