用ThinkRF实时频谱分析仪进行实时数据采集

本文结构

  • 概述
  • 数据采集系统与过程
  • 块捕获
  • 流捕获
  • 扫描捕获

概述

ThinkRF R55x0/R57x0实时数据采集可通过三种方式完成:

  • 根据用户请求采集单个数据块;
  • 流捕获,但内存有限;
  • 通过一个或多个捕获的复杂扫描设置。

捕获由控制器引擎处理,该控制器引擎为用户提供了使用基于可编程仪器标准命令(SCPI)协议创建的命令定义和执行捕获方法的手段。该方法可以以抽取和(或)触发为条件。

本文详细解释了捕获方法和用于每种方法的SCPI命令,并只关注数据采集方面。它假设用户已经知道如何连接和通信R55x0/R57x0。有关VRT协议、SCPI命令和用法的详细信息,请参考R55x0/R57x0程序员指南

数据采集系统与过程

图1说明了整个R55x0/R57x0功能层次结构,包括数据采集和控制,从用户的应用程序端开始,到R55x0/R57x0中的功能层。


图1:R55x0/R57x0对设备的功能层次和用户应用端接口方法

R55x0 / R57x0是可用于网络的设备,它使用ThinkRF提供的API(某些在图中列出)通过网络或通过用户自己的或第三方应用程序来通信控制接口和数据。所有应用程序,无论使用哪种编程语言,都将使用SCPI命令与设备接口,并遵循VRT协议解码数据,包括其相关的上下文信息。

R55x0 / R57x0具有128 MB的板载快速数据存储内存。 每个捕获方法都将受到内存大小的限制,如下面的部分所述。为了减少不必要的存储,数据采集可以进一步:

  • 使用触发事件进行条件调节,无论是通过使用外部脉冲或同步字输入的复杂同步扫描设置,还是通过简单但功能强大的内部频率电平检测触发引擎(请参见R55x0 / R57x0的应用笔记74-0046触发功能);
  • 使用抽取进行下采样,支持的速率为4到1024(2的幂次)。

使用触发器方法时,捕获在触发器事件发生后开始。

定义与过程

要捕获的数据“块”本质上是连续的(从时间上和频域上都是这样),因此被称为“跟踪捕获”。 但是,从一个块到另一个块的数据将是不连续的,因为它是一个“不同”块。 块大小范围为256个样本,最大范围由设备的内存大小(如上所述)和RFE数据格式确定。

典型的捕获设置包括:

  • 确定最适合应用程序的捕获方法;
  • 直接以独立模式或通过网络发送适当的设备和数据配置SCPI命令(或使用API函数);

•最后,根据所使用的方法发出适当的捕获开始命令。

R55x0 / R57x0捕获控制器启动数据捕获并将数据存储到板载内存,并插入适当的VRT标头和尾部。 换句话说,捕获的数据被分成VRT数据包,数据包大小通过“每包样本数”(SPP)参数指定。 一旦有完整的数据包可用,嵌入式固件便开始处理并将VRT数据包发送给最终用户。 固件还会创建任何关联的VRT上下文数据包并将其发送回用户。值得注意的是,VRT标头中包含基于系统时钟的捕获数据包的时间戳,其单位是纳秒(ns)。

捕获设置要求

在成功连接到RTSA之后和开始捕获之前,需要进行以下设置:

  • 通过发出*RST命令重置系统
  • 使用以下命令请求数据采集系统锁定:SYSTem:LOCK:REQuest? ACQuisition
  • 通过以下方式刷新RTSA的内部缓冲区,只有在收到采集锁定请求后才发出此命令。::SYSTem:FLUSh

块捕获

对于连续数据进行单个块捕获,捕获的样本总数(单个块)由SPP(:TRACe:SPPacket)和每块或PPB的包数确定(:TRACe:BLOCk:PACKets)。 SPP大小受VRT协议限制,(SPP * PPB)块受设备内存限制。 当块数据捕获命令(:TRACe:BLOCk:DATA?)发出后,R55x0 / R57x0将开始捕获并将样本总数存储到缓冲区中。如前所述,数据被分成VRT数据包,每个数据包的大小为SPP,其中插入了5个标头符和一个尾标符。指令:TRACe:BLOCk:DATA? 无论是否更改任何配置,都必须再次发出命令以启动另一个捕获块。因此,单个块捕获内的样本从一个包到另一个包是连续的,但是在发出连续的块捕获命令之间不是必需的。

以下示例说明了如何执行单个块捕获以及如何通过触发进行另一个捕获。注意,示例是用python语言编写的。在示例中使用了SCPI命令,并且在某些scpiset()旁边注释了thinkRF的PyRF API函数,以指示等效函数的可用性。

示例1–单块捕获

#####
# Capture a block of 131072 ZIF samples (I14Q14) with the VRT packet
# size set to 4096 and 32 packets requested
#####

# import required libraries
import sys
from pyrf.devices.thinkrf import WSA
from pyrf.util import collect_data_and_context

# Parameters for configuring the RTSA for a block capture
SPP = 4096
PPB = 32
CENTER_FREQ = 2450 * 1e6
RFE_MODE = 'ZIF'
DEC_RATE = 0 # for no decimation

# Define the RTSA device
dut = WSA()

# connect to RTSA device with a given IP address
dut.connect(sys.argv[1])

# reset device to default settings
dut.scpiset(":SYSTEM:LOCK:REQ? ACQUISITION") # dut.request_read_perm()
dut.scpiset(":*RST") # dut.reset()
dut.scpiset(":SYSTEM:FLUSH") # dut.flush()
# set RFE mode to ZIF, which yields I14Q14 data
dut.scpiset(":INPUT:MODE " + RFE_MODE) # dut.rfe_mode(RFE_MODE)
# does some device configuration, such as set frequency
dut.scpiset(":FREQ:CENTER " + CENTER_FREQ) # dut.freq(CENTER_FREQ)
# uncomment to set the desired decimation rate, default is off
#dut.scpiset(":SENSE:DEC " + str(DEC_RATE)) # dut.decimation(DEC_RATE)
# configure and capture the required block of data
dut.scpiset(":TRACE:SPP " + str(SPP)) # dut.capture(SPP, PPB)
dut.scpiset(":TRACE:BLOCK:PACKETS " + str(PPB)
dut.scpiset(":TRACE:BLOCK:DATA?")
# read the block of data and any context packets from the R55x0/R57x0
for i in range(PPB):
data, context = collect_data_and_context(dut) 

示例2–带频率电平触发器的大数据块捕获

#####
# Perform a large capture of 32,768,000 SH samples (16384 * 2000),
# conditional to a level trigger setting of range 2400 MHz – 2500 MHz
# with an amplitude of -70 dBm
#####
# import required libraries
import sys
from pyrf.devices.thinkrf import WSA
from pyrf.util import collect_data_and_context
# set a large block size 16384 * 2000 or 32 Msamples
SPP = 16384
PPB = 2000
RFE_MODE = 'SH'
CENTER_FREQ = 2450 * 1e6
#TRIGGER_SET = {'type': 'LEVEL','fstart': 2400 * 1e6,'fstop': 2500 *
1e6, 'amplitude': -70}
# define the RTSA device
dut = WSA()
# connect to RTSA device with a given IP address
dut.connect(sys.argv[1])
# reset device to default settings
dut.scpiset(":SYSTEM:LOCK:REQ? ACQ") # dut.request_read_perm()
dut.scpiset(":*RST") # dut.reset()
dut.scpiset(":SYSTEM:FLUSH") # dut.flush()
# set RFE mode to SH, which yields I14 data
dut.scpiset(":INPUT:MODE " + RFE_MODE) #dut.rfe_mode(RFE_MODE)
# does some device configuration, such as set frequency
dut.scpiset(":FREQ:CENTER " + CENTER_FREQ) # dut.freq(CENTER_FREQ)
# configure the trigger setting and enable it
dut.scpiset(":TRIGGER:LEVEL 2400 MHz, 2500 MHz, -70 dBm")
dut.scpiset(":TRIGGER:TYPE LEVEL") # dut.trigger(TRIGGER_SET)
# configure and capture the required data
dut.scpiset(":TRACE:SPP %s" % SPP) # dut.capture(SPP, PPB)
dut.scpiset(":TRACE:BLOCK:PACKETS %s" % PPB)
dut.scpiset(":TRACE:BLOCK:DATA?")
# read the data and any context packets from the R55x0/R57x0
for i in range(PPB):
data, context = collect_data_and_context(dut) 

流捕获

使用流捕获时,只要有可用数据,数据包就会从R55x0 / R57x0中“推”出去(与块捕获模式相反,后者是根据用户的请求“拉”数据)。由于它是流式传输,因此仅需要指定SPP,而无需指定PPB。发送所有设备和数据捕获配置命令后,发出:

TRACE:STREAM:START(不是:TRACE:BLOCK:DATA?)

命令开始流式传输,并发出下列命令来停止

TRACE:STREAM:STOP

在发生内存溢出之前,数据采样是持续的,而内存溢出这种状况会非常快地出现。

由于R55x0 / R57x0用于数据捕获的125 MHz的快速系统时钟速率但是其存储空间是有限的,并且网络接口的传输速率比系统时钟的传输速率慢得多,因此必然会发生数据溢出。之后,捕获系统将尽力维持捕获直到主机的传输速率无法与捕获速率匹配。有此限制,建议仅以高抽取率(例如16或更高)使用流捕获,以减慢采样率。 该速率因您的网络和计算机处理系统而异。

注意:发出流停止命令后,很有可能网络套接字中剩余有数据包。 因此,应用程序对这些数据包的清理非常重要。这可以通过简单地执行几秒钟的循环来检查套接字中的数据,直到套接字句柄不返回任何数据来完成。否则,仅在收到VRT扩展上下文数据包中的此ID时,才使用”START ID”并进行数据捕获。

实例–流捕获

#####
# Perform a stream capture of SH data with a decimation rate of 16
#####
# import required libraries
import sys
import msvcrt
from pyrf.devices.thinkrf import WSA
from pyrf.util import collect_data_and_context
# set the VRT packet size
SPP = 4096
CENTER_FREQ = 2450 * 1e6
RFE_MODE = 'SH'
DEC_RATE = 16
STARTID = 1234
# define the RTSA device
dut = WSA()
# connect to RTSA device with a given IP address
dut.connect(sys.argv[1])
# reset device to default settings
dut.scpiset(":SYSTEM:LOCK:REQ? ACQ") # dut.request_read_perm()
dut.scpiset(":*RST") # dut.reset()
dut.scpiset(":SYSTEM:FLUSH") # dut.flush()
# set RFE mode to ZIF, which yields I14Q14 data
dut.scpiset(":INPUT:MODE %s" % RFE_MODE)) # dut.rfe_mode(RFE_MODE)
# does some device configuration, such as set frequency
dut.scpiset(":FREQ:CENTER " + CENTER_FREQ) # dut.freq(CENTER_FREQ)
# configure the VRT packet size
dut.scpiset(":TRACE:SPP %s" % SPP) # dut.spp(SPP)
# set the decimation rate to slow down the capture rate
dut.scpiset(":SENSE:DEC %d" % DEC_RATE)) # dut.decimation(DEC_RATE)
# Start the stream capture
dut.scpiset(":TRACE:STREAM:START %d", STARTID)
# loop to get the start ID that mark the beginning of this stream
while True:
packet = dut.read()
if packet.is_context_packet() and packet.fields.get('streamid') ==
STARTID:
print('Start ID received. Start data capture next.')
break
# read the stream data and any context packets from the R55x0/R57x0
total_pkts = 0
while True:
data, context = collect_data_and_context(dut)
# optional, just to indicate the stream capture is still running
total_pkts = total_pkts + 1
if total_pkts % 100 == 0:
print('.')
# Add your conditional code here so to stop the stream
# capture or just Ctrl+C to exit the program. For example:
# when detect a key stroke, exit
if msvcrt.kbhit():
dut.scpiset(":TRACE:STREAM:STOP") # dut.stream_stop()
print
break
print "Total packets captured: %d" % total_pkts 

扫描捕获

扫描捕获控件提供了定义和执行简单或复杂扫描的功能,其中每个扫描条目都定义了捕获和设备配置,就像块捕获一样。要捕获条目中的数据块,与块方法一样使用:SWEEP:ENTRY:SPP  和:SWEEP:ENTRY:PPB。 一旦创建了所有扫描条目,请发出:SWEEP:LIST:START来开始扫描和捕获。当达到迭代次数(:SWEEP:LIST:ITERATION)或发出了:SYSTEM:ABORT或:SWEEP:LIST:STOP命令时,引擎将停止。迭代默认为0,这意味着无限循环。

如果为条目定义了触发器,则仅在发生触发器事件时才返回捕获的数据。 否则,当达到触发:DWELL时间时,触发将中止,并执行下一个扫描条目。扫描期间,R55x0 / R57x0内部缓冲区可能溢出,此时扫描引擎将暂停。 一旦有足够的空间容纳下一个“数据块”或更多数据,引擎将恢复扫描。

注意:发出流停止命令后,很有可能网络套接字中剩余有数据包。 因此,应用程序对这些数据包的清理非常重要。这可以通过简单地执行几秒钟的循环来检查套接字中的数据,直到套接字句柄不返回任何数据来完成。否则,仅在收到VRT扩展上下文数据包中的此ID时,才使用”START ID”并进行数据捕获。

示例–具有多个条目的扫描捕获

#####
# Create multiple sweep entries with different configuration per
# entries as an example of the sweep capabilities.
#
# This example makes use of pyRF functions as well as direct SCPI
# commands
#####
# import required libraries
import sys
import time
from pyrf.devices.thinkrf import WSA
from pyrf.numpy_util import compute_fft
from pyrf.util import collect_data_and_context
# setup RTSA and connect
dut = WSA()
dut.connect(sys.argv[1])
# create some variables
STARTID = 1234
ITERATION = 1
# use thinkRF's pyRF functions to initialize the unit, instead of
# direct SCPI here
dut.abort()
dut.flush()
dut.reset()
dut.request_read_perm()
# clear any potential existing list
dut.scpiset("SWEEP:ENTRY:DELETE ALL") # dut.sweep_clear()
# create first entry
dut.scpiset("SWEEP:ENTRY:NEW")
dut.scpiset("SWEEP:ENTRY:MODE DD")
dut.scpiset("SWEEP:ENTRY:SPP 2048")
dut.scpiset("SWEEP:ENTRY:SAVE 0")
# create second entry
dut.scpiset("SWEEP:ENTRY:MODE ZIF")
dut.scpiset("SWEEP:ENTRY:FREQ:CENTER 62.5 MHZ, 8000 MHZ")
dut.scpiset("SWEEP:ENTRY:FREQ:STEP 40 MHZ")
dut.scpiset("SWEEP:ENTRY:SPP 2048")
dut.scpiset("SWEEP:ENTRY:PPB 10")
dut.scpiset("SWEEP:ENTRY:DEC 8")
dut.scpiset("SWEEP:ENTRY:SAVE 0")
# create third entry, but using the pyRF function
s = SweepEntry(
fstart=62.5 * M,
fstop=8000 * M,
fstep=25 * M,
fshift=0,
decimation=1,
spp=4096,
ppb=100,
rfe_mode='SH')
dut.sweep_add(s)
# set iteration, 0 for infinite
dut.sweep_iterations(ITERATION)
# start sweeping
dut.scpiset("SWEEP:LIST:START %d" STARTID) #dut.sweep_start(STARTID)
# loop to get the start ID that mark the beginning of this sweep
while True:
packet = dut.read()
if packet.is_context_packet() and packet.fields.get('sweepid') ==
STARTID:
print('Start ID received. Start data capture next.')
break
while True:
# Collect an IF data at a time
# if PPB is greater than 1, packet stitching is needed
data, context = collect_data_and_context(dut)
print hex(data.stream_id), context
# add some conditional code here to stop the sweep & exit the
# loop or set iteration to a non-zero value
# dut.scpiset("SWEEP:LIST:STOP") 

发表回复