跳到主要内容

IMU 数据接收和 TCP 转发

本方案演示如何接收、解析和转发来自 STMicroelectronics LSM6DSR IMU 的高频振动数据。系统通过 ESP32 捕获 64 个样本的批次数据,频率为 6664 Hz,通过串行链路以二进制帧形式传输,并使用 CycBox 将信号解码为工程单位,同时将原始遥测数据转发到下游 TCP 服务器。

IMU 数据接收

本方案的功能

  • 同步二进制帧 使用 AA 55 前缀并使用 CRC16-CCITT 校验和验证 780 字节数据包。
  • 解析 64 个样本批次 包含 3 轴加速度计和 3 轴陀螺仪数据(每个样本 12 字节)。
  • 校正微秒级抖动 通过将 MCU 内部时钟与 CycBox 引擎时间戳对齐。
  • 将原始 LSB 值转换为物理单位:加速度为 m/s²,角速度为度/秒 (dps)。
  • 将原始二进制帧转发到远程 TCP 服务器 127.0.0.1:8080 用于存档或进一步处理。

端到端数据流

设备和线路协议

LSM6DSR 是一个高性能 6 轴 IMU。在此配置中,它以最大输出数据率 (ODR) 6664 Hz 运行。为了处理高数据吞吐量,ESP32 固件读取内部硬件 FIFO 并将 64 个样本聚合为一个数据包。

下表定义了设备使用的 780 字节二进制帧布局。

偏移大小字段描述
02Sync固定的 AA 55 前缀。
21Type数据包类型 ID(IMU 批次为 0x01)。
31Count批次中的样本数(固定为 64)。
42Seq16 位小端序列计数器。
64ts_us32 位小端 MCU 微秒时间戳。
10768Data64 个 12 字节的样本:{ax, ay, az, gx, gy, gz}(int16 小端)。
7782CRCCRC16-CCITT (False) 校验和。

CycBox 配置

该设置使用两个连接:一个用于设备接收的串口传输,一个用于数据输出的 TCP 客户端。

连接 0:设备接收

此连接处理与 ESP32 的串行通信,并执行初始帧同步和校验和验证。

{
"app": {
"app_transport": "serial_port_transport",
"app_codec": "frame_codec"
},
"serial_port_transport": {
"serial_port_transport_baud_rate": 115200,
"serial_port_transport_port": "/dev/ttyACM0"
},
"frame_codec": {
"frame_codec_length_mode": "fixed",
"frame_codec_checksum_algo": "crc16_ccitt",
"frame_codec_prefix": "AA 55",
"frame_codec_fixed_payload_size": 776
}
}

连接 1:TCP 转发

此连接充当下游接收器,接收 Lua 脚本转发的原始帧。

{
"app": {
"app_transport": "tcp_client_transport",
"app_codec": "passthrough_codec"
},
"tcp_client_transport": {
"tcp_client_transport_host": "127.0.0.1",
"tcp_client_transport_port": 8080
}
}

Lua 脚本

Lua 脚本是集成的核心。它筛选来自串口的消息,验证数据,执行批次到样本的扩展,并将原始帧路由到 TCP 连接。

-- LSM6DSR FIFO IMU 突发数据包解析器和转发器
-- 串口上的帧编码 (连接 0):解析来自 LSM6DSR 传感器的 776 字节批处理 IMU 有效载荷。
-- TCP 客户端 (连接 1):直通编码器用于转发原始帧。
-- 解码每个批次的 64 个加速度计/陀螺仪样本 (6664 Hz) 并将 MCU 微秒时间戳对齐到接收时间。
-- 来自串口的传入原始帧通过 TCP 连接转发。
--
-- 数据包布局 (仅有效载荷 - 776 字节):
-- [1] type
-- [2] count: 64
-- [3-4] seq (LE uint16)
-- [5-8] ts_us (LE uint32, MCU µs 时钟)
-- [9-776] 64 × {ax, ay, az, gx, gy, gz} (int16 LE, 12 字节/样本)
--
-- 刻度 (LSM6DSR):
-- 加速度 ±2 g → 0.061 mg/LSB → ~0.000598 m/s²
-- 陀螺仪 ±2000 dps → 70 mdps/LSB → 0.07 dps

local PAYLOAD_SIZE = 776
local SAMPLE_SIZE = 12

local INTERVAL_US = 1000000 / 6664

-- LSM6DSR: 0.061 mg/LSB 在 ±2 g
local ACCEL_SCALE = 0.061e-3 * 9.80665
-- LSM6DSR: 70 mdps/LSB 在 ±2000 dps
local GYRO_SCALE = 0.07

local anchor_recv_us = nil
local anchor_mcu_us = nil

local function mcu_delta(current, anchor)
local d = current - anchor
if d < 0 then
d = d + 4294967296
end
return d
end

function on_receive()
-- 仅处理并转发来自串行连接 (ID 0) 的消息
if message.connection_id ~= 0 then
return false
end

local p = message.payload

if p == nil or #p ~= PAYLOAD_SIZE then
return false
end

if message.checksum_valid == false then
log("warn", "IMU: 校验和无效。")
return false
end

-- 将原始完整的线路帧转发到 TCP 连接 (ID 1)
if message.frame then
send_after(message.frame, 0, 1)
end

local ptype = read_u8(p, 1)
local count = read_u8(p, 2)
local seq = read_u16_le(p, 3)
local ts_us = read_u32_le(p, 5)

local recv_us = tonumber(message.timestamp)
local newest_mcu_us = (ts_us + (count - 1) * INTERVAL_US) % 4294967296

if anchor_recv_us == nil then
anchor_recv_us = recv_us
anchor_mcu_us = newest_mcu_us
log("info", string.format("IMU: 锚点已设置 — recv=%s mcu=%u", tostring(recv_us), newest_mcu_us))
end

local oldest_us = anchor_recv_us + mcu_delta(ts_us, anchor_mcu_us)

for i = 0, count - 1 do
local off = 9 + i * SAMPLE_SIZE

local ax = read_i16_le(p, off)
local ay = read_i16_le(p, off + 2)
local az = read_i16_le(p, off + 4)
local gx = read_i16_le(p, off + 6)
local gy = read_i16_le(p, off + 8)
local gz = read_i16_le(p, off + 10)

local ts = math.floor(oldest_us + i * INTERVAL_US)

message:add_float_value("ax", ax * ACCEL_SCALE, ts)
message:add_float_value("ay", ay * ACCEL_SCALE, ts)
message:add_float_value("az", az * ACCEL_SCALE, ts)

message:add_float_value("gx", gx * GYRO_SCALE, ts)
message:add_float_value("gy", gy * GYRO_SCALE, ts)
message:add_float_value("gz", gz * GYRO_SCALE, ts)
end

message:add_int_value("count", count)
message:add_int_value("seq", seq)
message:add_int_value("mcu_ts_us", ts_us)

return true
end

下游服务契约

TCP 出口

  • 端点: 127.0.0.1:8080
  • 协议: 原始二进制(直通)。
  • 帧架构: 服务器接收协议部分中定义的确切 780 字节帧,包括 AA 55 标头和尾部 CRC。

操作注意事项

  • 波特率限制: 在 6664 Hz、12 字节/样本的频率下,原始数据速率约为 640 kbps。配置的 115200 波特率不足以实时传输完整的 6.6 kHz 数据。为了完整速率操作,串行波特率必须增加到至少 921600 或使用高速传输(如 ESP32 源代码中指示的 USB Serial JTAG)。
  • 时间戳漂移: Lua 脚本将第一个数据包锚定到系统时钟。虽然 mcu_delta 处理微秒溢出,但 ESP32 晶振与 CycBox 主机时钟之间可能会发生长期漂移。对于长期捕获,应定期重新同步锚点。
  • TCP 背压: 如果 127.0.0.1:8080 处的 TCP 服务器变得无响应,passthrough_codec 将在内部缓冲区满后丢弃数据包以优先确保实时引擎稳定性。

重现此方案

  1. 硬件: ESP32 通过 SPI 连接到 LSM6DSR 传感器。
  2. 设备固件: 用提供的 lsm6dsr_fifo.c 源代码编译并刷入 ESP32,确保选择了 LSM6DSR_MODE_FIFO
  3. 下游: 启动 TCP 监听器 (例如 nc -l 8080 > imu_data.bin)。
  4. CycBox 设置: 应用 JSON 配置创建串行和 TCP 连接,然后将 Lua 脚本粘贴到管道编辑器中。
  5. 验证: 监控设备消息历史记录;您应该会看到连接 0 上的 RX 帧和连接 1 上的立即 TX 回显。

陷阱和建议

  • CRC 验证: frame_codec 在 Lua 脚本运行前验证 CRC16-CCITT。如果 message.checksum_valid 为 false,脚本会记录警告并退出,防止损坏的数据被转发。
  • 缩放因子: ACCEL_SCALEGYRO_SCALE 特定于 ±2g 和 ±2000 dps 范围。如果在 ESP32 固件中更改了传感器灵敏度,Lua 脚本中的这些常量也必须更新。
  • 资源使用: 以高频率解析每帧 64 个样本的计算量很大。确保主机环境有足够的 CPU 周期以避免处理延迟。