使用串口 API
1 概述
禾赛部分激光雷达型号支持使用串口 API 指令与电脑主机进行通讯,即通过 RS232 协议发送命令报文(CMD datagram),并通过 RS485 协议接收返回报文(ACK datagram),以实现激光雷达控制、操作、查询等目的。
注:当前串口 API 指令仅适用于 JT16 激光雷达,详细指令信息请参考 JT16 串口 API 手册。
2 使用方法
串口 API 指令的使用方法和基本操作步骤如下:
-
将激光雷达连接至主机,确保其能够与主机正常建立通信(具体方法详见配置串口连接);
-
从主机通过串口向激光雷达发送 API 指令,接收来自激光雷达的返回值,并根据 API 手册中的说明进行返回值解析,确认 API 指令 发送操作是否成功。
2.1 串口查询
2.1.1 Windows 系统
若使用 Windows 系统,可打开“设备管理器”查看端口。
为有效区分串口,可先插入 RS485 线束 USB 端口,查看其端口号,再插入 RS232 端口,查看新识别出的端口号。
2.1.2 Ubuntu 串口查询
若使用 Ubuntu 系统,可在命令行输入ls /dev/ttyUSB*查看当前端口。
为有效区分串口,可先插入 RS485 线束 USB 端口,查看其端口号,再插入 RS232 端口,查看新识别出的端口号。
注:
1. Ubuntu 版本完整端口号为/dev/ttyUSB*,通过脚本连接时应全部填入;
2. 若因权限缺失连接失败,可输入sudo chmod 666 /dev/ttyUSB*打开对应串口权限;
ls /dev/ttyUSB*
## 查询当前 USB 串口,预期显示如 /dev/ttyUSB1 /dev/ttyUSB2
sudo chmod 666 /dev/ttyUSB1 && sudo chmod 666 /dev/ttyUSB2
## 打开串口权限
用户可参考配置串口连接-配置流程 部分相关介绍获取更详细的操作方法。
2.2 串口 API 数据结构
2.2.1 命令报文 (CMD datagram) 数据结构
主机通过 RS232 通讯向激光雷达发送命令报文(CMD datagram)。命令报文的数据结构如下图所示:
其中,CRC 校验码使用 CRC-32/MPEG-2 算法,计算从 Data Length 到 Check ID 的部分,用户可使用网络上公开的 CRC 在线工具计算,或使用本文此处提供的脚本进行计算。CRC 计算结果以小端序(低位在前)填入命令报文。
2.2.2 返回报文 (ACK datagram) 数据结构
命令报文(CMD datagram)发送后,主机会通过 RS485 通讯接收到自激光雷达端返回的报文(ACK datagram)。返回报文的数据结构如下图所示:
注:与命令报文一样,点云包也通过 RS485 通讯传输。因此,用户需要根据返回报文(ACK datagram)中帧头(Frame Header)和帧尾(End of Frame)的特征将其从 RS485 数据流中筛选出来。解析返回报文“Fault Code”字段,若该字段为 0,则表示命令发送成功; 若为其他值,则需参照上图中该字段“Description”部分的对应内容进行排查。
3 实例介绍
本文将分别介绍两种不同的串口 API 指令发送方法以及相应实例,用户可根据实际情况选择其中一种进行使用。
注:在使用串口 API 指令前,需要先结合产品手册确认该指令的配置项对所使用的激光雷达有效!
3.1 使用 LidarUtilities 发送串口 API 指令
LidarUtilities 软件的获取及使用方式可参考使用 LidarUtiilities 一文介绍。
将 JT16 激光雷达上电并连接至主机。以 Windows 系统为例,打开 Lidar Utilities,选中产品型号为 JT16,配置好 RS485 端口、RS232 端口、波特率(注意标品版波特率为 3000000,非标品版波特率为 3125000,若不能分辨可分别尝试),点击 Connect,连接成功后效果如图所示。
此处以发送设置转速串口 API 指令为例,下图展示了该指令的命令报文具体配置参数:
注:详细指令说明参见 JT16 API 手册中 "2.2 Switch motor speed" 部分信息。
发送串口命令的方法可参见下图所示:
-
切换至“Send Serial Command”页面;
-
在 “Send Command - Command Code” 部分输入 03 (CMD ID);
-
Payload 部分输入 0A 01 (CMD DATA,10Hz),中间需要留空格;
-
点击“Send”发送命令。
注:该指令仅用作使用方法实例展示,由于 JT16 当前暂不支持将转速配置为 10Hz,故返回报文中“Fault Code” 显示为 1。若使用支持配置的指令,预期返回报文的“Fault Code” 应为 0。
3.2 使用脚本发送串口 API 指令
此处同样以发送设置转速串口 API 指令为例,用户可使用以下脚本实现对应的指令发送操作。该脚本主要进行了 CMD 命令组包与发送。用户配置 RS232 端口和波特率,以及需要发送的命令字和命令内容后,脚本会自动计算 Data Length 和 CRC 校验和,完成组包和发送。用户可通过是否实现命令预期效果或抓取 ACK 报文查看“Fault Code”字段确认是否运行成功。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# """
# - Simplified version: build and send CMD frame only (no ACK listening)
# - Configurable CMD parameters, compute CRC32/MPEG-2 (little-endian)
# """
import serial
# -------------------- USER CONFIGURATION AREA --------------------
# Serial settings
RS232_PORT = "COM4" # RS232 port
RS232_BAUD = 9600 # RS232 baud rate
# RS485_PORT = "COM5" #RS485 port
# RS485_BAUD = 3000000 # RS485 baud rate
# CMD parameters
CMD_ID = 0x03 # Command ID
CMD_DATA = bytes.fromhex('0A 01') # Command data segment
# -------------------- USER CONFIGURATION AREA --------------------
# Frame constants--------------------------------------------------
CHECK_ID = bytes.fromhex('95 B3 2C 0A') # 4-byte check ID
FRAME_HEADER_CMD = b"\x24\x4C\x44\x43\x4D\x44\x2C" # CMD header
FRAME_END = b"\xEE\xFF" # Frame tail
# -----------------------------------------------------------------
# -------------------- CRC32/MPEG-2 CALCULATION -----------------
def init_crc32_mpeg2_table() -> list[int]:
# """Initialize CRC32/MPEG-2 lookup table"""
crc_table =[0]*256
for i in range(256):
crc = i << 24
for _ in range(8):
crc = (crc << 1) ^ 0x04C11DB7 if crc & 0x80000000 else crc << 1
crc_table[i] = crc & 0xFFFFFFFF
return crc_table
CRC_TABLE = init_crc32_mpeg2_table()
def crc32_mpeg2(data: bytes) -> int:
# """Compute CRC32/MPEG-2 checksum (unsigned 32-bit)"""
if not data:
return 0xFFFFFFFF
crc = 0xFFFFFFFF
for b in data:
crc = ((crc << 8) ^ CRC_TABLE[(crc >> 24) ^ b]) & 0xFFFFFFFF
return crc
def build_cmd(cmd_id: int, cmd_data: bytes, check_id: bytes) -> bytes:
# """
# Automatically build CMD frame (with CRC)
# :param cmd_id: Command ID (1 byte)
# :param cmd_data: Command data segment
# :param check_id: 4-byte check ID
# :return: Complete CMD frame (header + payload + CRC + tail)
# """
# 1. Build base payload (length + CMD_ID + CMD_DATA)
data_len = 1 + len(cmd_data) # CMD_ID(1 byte) + CMD_DATA length
payload = bytes([data_len, cmd_id]) + cmd_data
# 2. Align to 4-byte boundary (whole payload + check_id)
total_payload_len = len(payload) + len(check_id)
pad_len = (4 - total_payload_len % 4) % 4
payload += b"\x00" * pad_len
# 3. Append CHECK_ID
payload += check_id
# 4. Compute CRC (little-endian)
crc_value = crc32_mpeg2(payload)
crc_bytes = crc_value.to_bytes(4, byteorder="little") # low byte first
# 5. Assemble full frame
full_cmd = FRAME_HEADER_CMD + payload + crc_bytes + FRAME_END
return full_cmd
# -------------------- MAIN (construct + send CMD only) ---------
def main():
# 1. Build CMD frame
try:
cmd_frame = build_cmd(CMD_ID, CMD_DATA, CHECK_ID)
print("========== Build CMD frame ==========")
print(f"Hex: {cmd_frame.hex(' ').upper()}")
print(f"Length: {len(cmd_frame)} bytes")
print("=========================================")
except Exception as e:
print(f"Failed to build CMD frame: {e}")
return
# 2. Send CMD frame (RS232)
try:
with serial.Serial(
port=RS232_PORT,
baudrate=RS232_BAUD,
timeout=1,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS
) as ser_232:
ser_232.reset_input_buffer()
ser_232.reset_output_buffer()
ser_232.write(cmd_frame)
ser_232.flush()
print("CMD frame sent")
except serial.SerialException as e:
print(f"Failed to send CMD frame: {e}")
if __name__ == "__main__":
main()
注:该脚本仅用作使用方法实例展示。JT16 目前暂不支持将转速配置为 10Hz。