毫米波雷达||网口接收雷达原始数据
目录
1、采集雷达原始数据
2、通过网口传输数据
参考文献:
1、采集雷达原始数据
像TI或加特兰雷达都有相应的采集板可以用于雷达原始数据获取,一般通过LVDS高速接口和网口传输数据,需要比如256*128*12*4*20 31.5Mbps(即采样点*啁啾数*天线数*复采样*20Hz)即>31.5Mbps的高速接口才可以实时采集20Hz刷新率的数据。当然通过雷达UART串口也可以获取一点ADC数据,不过受限于串口最大传输速率3.125Mbps,无法实时的获取ADC数据,只能获得单个frame的ADC原始数据。
采用官方自带的软件采集雷达ADC原始数据很方便,但是不够灵活,也不便于实时处理。所以考虑跳过官方软件通过python实时采集ADC原始数据并处理。
以TI雷达为例,官方文档中介绍了配置DCA1000EVM并通过以太网将ADC数据从毫米波传感器捕获到PC的方法。雷达板和采集板之间通过LVDS高速接口连接,采集板通过网口传输数据到PC。
注:LVDS(Low Voltage Differential Signaling,低压差分信号)是一种高速串行数据传输技术,LVDS接口具有以下特点:高速传输能力、低噪声、低功耗、抗电磁干扰和传输距离远。
LVDS 到以太网流:–原始模式:在此模式下,所有 LVDS 数据均按原样捕获并通过以太网接口流式传输。DCA1000EVM遵循 UDP 协议,支持14个预定义命令。DCA1000EVM的配置和状态通过配置端口进行通信。数据端口用于传输原始模式/数据分离模式数据。
原始模式下UDP数据格式如下图,包括三个部分:数据包序号、字节大小和雷达原始数据部分。以太网(Ethernet)数据帧长度必须在46~1500字节之间,这是以太网的物理特性决定的。这个1500字节为链路层的最大传输单元MTU,实际是网络层IP数据报的长度限制。因为IP数据报首部20字节,UDP数据报首部8字节,所以UDP数据报的数据区最大长度为1472字节。下图中UDP数据报数据区的最大长度即为1472字节,当然一帧雷达原始数据远比这大,所以会分为若干个传输。由于UDP特性,某一个数据传送中丢失时,无法重传,不过由于数据中包含序号信息可以将该部分数据置0。
2、通过网口传输数据
使用DCA 1000,数据以UDP数据包的形式通过以太网传输。DCA 1000上的FPGA已使用目的地址192.168.33.30进行编程(该IP地址即为静态ip地址,用于采集板发送数据,而采集板接收配置命令的为FPGA绑定的地址192.168.33.180)。因此,我们必须将目标PC配置为与FPGA的预期目的地址匹配的静态IP地址,以便从EVM接收数据。
网口接收雷达ADC原始数据的流程如下:
- 重置雷达板和采集板;
- 通过UART向雷达发送配置,初始化雷达并配置相应参数:
def serialConfig(configFileName): # Open the serial ports for the configuration and the data ports # Windows CLIport = serial.Serial('COM9', 115200) Dataport = serial.Serial('COM10', 921600) # Read the configuration file and send it to the board config = [line.rstrip('\r\n') for line in open(configFileName)] for i in config: CLIport.write((i+'\n').encode()) print(i) time.sleep(0.01) return CLIport, Dataport
TI雷达需要一个发送配置的串口CLIport和一个接收数据的串口Dataport,CLIport读取cfg配置文件逐行发送配置参数。
- 通过网口UDP向采集板发送配置指令:
def __init__(self, static_ip='192.168.33.30', adc_ip='192.168.33.180', data_port=4098, config_port=4096): # Create configuration and data destinations self.cfg_dest = (adc_ip, config_port) self.cfg_recv = (static_ip, config_port) self.data_recv = (static_ip, data_port) # Create sockets self.config_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.data_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # Bind data socket to fpga self.data_socket.bind(self.data_recv) # Bind config socket to fpga self.config_socket.bind(self.cfg_recv)
UDP因其简单性和低延迟性,在需要快速传输且对可靠性要求不高的场景中非常有用,当然也要注意数据丢失等问题。首先,基于UDP协议的网口通信通过UDP套接字完成,套接字(socket)可以看作是两个程序在网络中进行通信的接口,它允许程序通过网络发送和接收数据,就好像是在不同计算机或不同进程之间直接通信一样。在python中使用socket模块创建UDP套接字,
socket.AF_INET
表示套接字使用IPv4地址族,socket.SOCK_DGRAM
表示套接字类型为UDP数据报,socket.IPPROTO_UDP
是UDP协议的协议号。然后是绑定套接字,将套接字和IP与端口关联起来,这样套接字就可以接收发送到该地址的数据包。注意:这里adc_ip对应采集板上FPGA的地址,static_ip是将计算机配置为与采集板目的地址匹配的静态IP地址,还有config_port接收配置的端口号和data_port发送数据的端口号。也就是说,cfg_dest指向发送配置的ip端口,cfg_recv则指向接收数据的ip和配置端口,data_recv则指向接收数据的ip端口。 - 通过网口UDP向采集板发送开始采集指令;
- 通过UART向雷达发送启动雷达命令即“sensorStart”;
- 通过网口UDP循环接收数据包+解析出原始数据+后续数据实时处理:
def _read_data_packet(self): """Helper function to read in a single ADC packet via UDP Returns: int: Current packet number, byte count of data that has already been read, raw ADC data in current packet """ data, addr = self.data_socket.recvfrom(MAX_PACKET_SIZE) packet_num = struct.unpack('<1l', data[:4])[0] byte_count = struct.unpack('>Q', b'\x00\x00' + data[4:10][::-1])[0] packet_data = np.frombuffer(data[10:], dtype=np.uint16) return packet_num, byte_count, packet_data
def read(self, timeout=1): """ Read in a single packet via UDP """ # Configure self.data_socket.settimeout(timeout) # Frame buffer ret_frame = np.zeros(UINT16_IN_FRAME, dtype=np.uint16) # Wait for start of next frame while True: packet_num, byte_count, packet_data = self._read_data_packet() if byte_count % BYTES_IN_FRAME_CLIPPED == 0: packets_read = 1 ret_frame[0:UINT16_IN_PACKET] = packet_data break # Read in the rest of the frame while True: packet_num, byte_count, packet_data = self._read_data_packet() packets_read += 1 if byte_count % BYTES_IN_FRAME_CLIPPED == 0: self.lost_packets = PACKETS_IN_FRAME_CLIPPED - packets_read return ret_frame curr_idx = ((packet_num - 1) % PACKETS_IN_FRAME_CLIPPED) try: ret_frame[curr_idx * UINT16_IN_PACKET:(curr_idx + 1) * UINT16_IN_PACKET] = packet_data except: pass if packets_read > PACKETS_IN_FRAME_CLIPPED: packets_read = 0
_read_data_packet()
: 通过UDP读取单个ADC数据包,返回当前数据包编号、已读取的字节数和当前数据包中的原始ADC数据。recvfrom(MAX_PACKET_SIZE),MAX_PACKET_SIZE即单个UDP数据包的最大大小。上面图中,一个UDP数据包包含三个部分,即函数_read_data_packet
返回的解析数据包编号、解析字节计数、解析ADC数据。不过一帧ADC原始数据可能有几M大小,需要多个UDP数据包才能传输完。所以通过read函数
通过循环读取单个UDP数据包,返回一个完整的数据帧。它会先读取一帧的开始部分,在第二个循环中读取帧的剩余部分,有数据包编号packet_num,可以知道当前UDP数据包在一帧中的位置,所以对于数据包丢失的情况,一帧数据对应位置的数据即为0。@staticmethod def organize(raw_frame, num_chirps, num_rx, num_samples): """Reorganizes raw ADC data into a full frame Returns: ndarray: Reformatted frame of raw data of shape (num_chirps, num_rx, num_samples) """ ret = np.zeros(len(raw_frame) // 2, dtype=complex) # Separate IQ data ret[0::2] = raw_frame[0::4] + 1j * raw_frame[2::4] ret[1::2] = raw_frame[1::4] + 1j * raw_frame[3::4] return ret.reshape((num_chirps, num_rx, num_samples))
organize函数将解析出的一帧数据组织为一帧完整的原始ADC数据,包括实部和虚部。(IQ数据的排列因雷达型号而异,这里是2I2Q+2I2Q,而有的是4I4Q。)
- 通过UART向雷达发送停止雷达命令即“sensorStop”;
- 通过网口UDP向采集板发送停止采集指令,并关闭用于发送配置和接收数据的两个UDP套接字。
参考文献:
《使用低速串行总线的实时 ADC原始数据采集方法》-TI官方文档
《DCA1000EVMDataCaptureCard》-TI官方文档
作者:Royzw