1、概述
根据实际项目需求,设计和开发一个基于I2C总线的IMU驱动控制,实现FPGA对 IMU(BMI088)的数据采集,并实际验证。
2、 逻辑视图
把系统功能分解,模块划分,梳理出数据流和控制流如下:

核心模块:
-
IMU 控制器:通过 I2C 读取 IMU 原始数据(加速度/角速度),产生采样有效信号和包起始/结束标志。
-
数据打包模块:将 IMU 数据按照自定义协议(如 UDP/RoCE)打包成 AXI-Stream 流。
-
10GbE MAC+PHY:将以太网帧发送到 SFP+ 光模块。
3、IMU控制器
这边采用的是ICM-42688-P型号,这是 是 TDK InvenSense 推出的一款 6 轴惯性测量单元(IMU),专为无人机和机器人应用而设计。集成了一个 16 位三轴加速度计和一个汽车级 16 位三轴陀螺仪;

4、IMU控制器+FSM
imu_top 是一个可配置的惯性测量单元(IMU)数据采集与打包模块,实现 IMU 数据与图像数据的精确时间对齐,并通过 10GbE 网络发送,主要完成以下任务:
-
通过 I2C 接口读取外部 IMU 传感器(如加速度计、陀螺仪)的原始数据(8-bit 字节流)。
-
为每一次完整的采样(一个数据包)打上来自 PTP(精确时间协议) 的 64 位时间戳(秒 + 纳秒)。
-
将 [时间戳 + IMU 数据] 打包成 AXI-Stream 格式输出,供后续模块(如网络打包、DMA 等)使用。
-
提供 APB 从接口,允许外部处理器(或软核)配置内部寄存器(例如设置采样率、滤波参数等)。
具体代码如下:
// SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//功能:
//通过I2C接口读取IMU传感器(如加速度计/陀螺仪)的原始数据,为每一帧(或每一次采样)打上来自PTP(精确时间协议)的64位时间戳,然后将数据打包成AXI-Stream格式输出。同时支持APB总线配置内部寄存器
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
module imu_top
import apb_pkg::*;
import regmap_pkg::*;
#(
parameter RAM_DEPTH = 128, //内部可以缓存 128个采样点
parameter W_DATA = 64,
localparam W_KEEP = W_DATA/8
) (
// PTP PTP 时钟域
input i_ptp_clk, //PTP 时钟(通常来自10GbE恢复时钟)
input i_ptp_rst, //PTP 域复位
input [63:0] i_ptp_ts, //PTP 时间戳(秒+纳秒)
// Register Interface APB 配置接口
input i_aclk, //APB/IMU 采样时钟(通常50MHz)
input i_arst, //APB 域复位
input apb_m2s i_apb_m2s, //APB 主到从信号(地址、数据、控制)
output apb_s2m o_apb_s2m, //APB 从到主信号(就绪、读数据)
// AXIS Interface AXIS 输出
output [W_DATA-1:0] o_axis_tdata, //输出数据(打包后的IMU+时间戳)
output o_axis_tvalid, //数据有效
output o_axis_tlast, //帧/包结束标志
output o_axis_tuser, //用户定义(本模块置0)
output [W_KEEP-1:0] o_axis_tkeep, //每个字节有效掩码(64位对应8个字节)
input i_axis_tready, //下游模块准备好接收
// Interrupt IMU控制和I2C
input [1:0] i_start, //启动采样触发信号(比如来自VSYNC)
//I2C Ports
input scl_i,
output scl_o,
input sda_i,
output sda_o
);
logic imu_data_valid; //数据有效
logic [7:0] imu_data; //原始IMU数据 8bit
logic eop; //End of Packet
logic sop; //表示一帧数据的开始(例如一次完整采样的第一个字节)
logic sample_last; //本采样点最后一个字节
logic [63:0] imu_ts;
logic imu_ts_vld;
logic imu_sop_ptp_sync;
logic [7:0] axis_tdata;
logic axis_tvalid;
imu #(
.NUM_INST ( 1 ),
.RAM_DEPTH ( RAM_DEPTH )
) imu_inst (
.i_aclk ( i_aclk ),
.i_arst ( i_arst ),
.scl_i ( scl_i ),
.sda_i ( sda_i ),
.scl_o ( scl_o ),
.sda_o ( sda_o ),
.i_start ( i_start ),
.o_data ( imu_data ), //原始IMU数据 8bit
.o_data_valid ( imu_data_valid ), //数据有效
.o_busy ( ),
.o_sample_last ( sample_last ), ////本采样点最后一个字节
.o_eop ( eop ), //End of Packet
.o_sop ( sop ), //表示一帧数据的开始(例如一次完整采样的第一个字节)
.o_bus_en ( ),
.i_apb_m2s ( i_apb_m2s ),
.o_apb_s2m ( o_apb_s2m )
);
// Append PTP timestamp to each sample 跨时钟域模块
//将i_aclk域的脉冲sop转换成i_ptp_clk域的单周期脉冲imu_sop_ptp_sync。
//作用:告诉PTP域"有一个新的IMU采样开始了,请记录当前时间"。
pulse_sync imu_data_pulse_ptp_sync_inst (
.src_clk ( i_aclk ),
.src_rst ( i_arst ),
.dst_clk ( i_ptp_clk ),
.dst_rst ( i_ptp_rst ),
.i_src_pulse ( sop ),
.o_dst_pulse ( imu_sop_ptp_sync )
);
//跨时钟域模块 因为PTP时间戳来自独立时钟域(i_ptp_clk),而IMU数据在i_aclk域,需要将采样启动事件(sop)同步到PTP域,再把PTP时间戳采样回来。
//当imu_sop_ptp_sync有效时,将PTP时间寄存器i_ptp_ts的值"抓拍"并传递到i_aclk域,输出imu_ts_vld和imu_ts。
reg_cdc # (
.NBITS ( 64 ),
.REG_RST_VALUE ( '0 )
) u_ptp_cdc (
.i_a_clk ( i_ptp_clk ),
.i_a_rst ( i_ptp_rst ),
.i_a_val ( imu_sop_ptp_sync ),
.i_a_reg ( i_ptp_ts ),
.i_b_clk ( i_aclk ),
.i_b_rst ( i_arst ),
.o_b_val ( imu_ts_vld ),
.o_b_reg ( imu_ts )
);
typedef enum logic [3:0] {
IMU_IDLE,
IMU_WAIT_TS,
IMU_TS,
IMU_DATA,
IMU_SOP
} imu_fsm_t;
//状态机 imu_fsm_t ,状态机在i_aclk域运行,负责将IMU数据与时间戳组合成AXIS流。
imu_fsm_t imu_state;
logic [2:0] cnt;
logic [7:0] imu_data_r;
always_ff @ (posedge i_aclk) begin
if (i_arst) begin
imu_state <= IMU_IDLE; //空闲,等待sop(IMU采样开始)
cnt <= '0;
imu_data_r <= '0;
end else begin
imu_state <= imu_state;
case (imu_state)
IMU_IDLE: begin ////空闲,等待sop(IMU采样开始)
if (sop) begin
imu_state <= IMU_WAIT_TS;
imu_data_r <= imu_data;
end
cnt <= '0;
end
IMU_WAIT_TS: begin //等待reg_cdc返回有效时间戳(imu_ts_vld)
if (imu_ts_vld) begin
imu_state <= IMU_TS;
end
end
IMU_TS: begin //输出64位时间戳(8个字节,每个周期输出1字节,共8周期)
cnt <= cnt + 1;
if (cnt == 3'd7) begin
imu_state <= IMU_SOP;
end
end
IMU_SOP: begin //输出IMU数据的第一个字节(在sop时锁存的imu_data_r)
imu_state <= IMU_DATA;
end
IMU_DATA: begin //连续输出后续IMU数据(imu_data_valid有效时)直到eop拉高,回到IDLE
if (eop) begin
imu_state <= IMU_IDLE;
end
end
endcase
end
end
always_comb begin
case (imu_state)
IMU_IDLE: begin //空闲,等待sop(IMU采样开始)
axis_tdata = '0;
axis_tvalid = '0;
end
IMU_WAIT_TS: begin //等待reg_cdc返回有效时间戳(imu_ts_vld)
axis_tdata = '0;
axis_tvalid = '0;
end
IMU_TS: begin //输出64位时间戳(8个字节,每个周期输出1字节,共8周期)
axis_tdata = imu_ts[cnt*8+:8];
axis_tvalid = '1;
end
IMU_SOP: begin //输出IMU数据的第一个字节(在sop时锁存的imu_data_r)
axis_tdata = imu_data_r;
axis_tvalid = '1;
end
IMU_DATA: begin //连续输出后续IMU数据(imu_data_valid有效时)直到eop拉高,回到IDLE
axis_tdata = imu_data;
axis_tvalid = imu_data_valid;
end
endcase
end
//通用AXIS宽度转换+缓冲模块
//它将输入的8-bit数据流收集到内部FIFO中,当凑满64位时,输出一个64位的AXIS包。
//同时传递tlast(这里接sample_last,即采样点最后一个字节标志)和tkeep(指示输出64位中哪些字节有效)
//因为IMU数据长度不一定正好是8的倍数,tkeep可以标记最后一个非完整64位字中的有效字节。
//个典型的数据包结构(假设IMU一次采样输出12字节):
// [ 时间戳 0-7 ] [ IMU字节0 ] [ IMU字节1 ] ... [ IMU字节11 ]
// └───────── 8字节 ─────────┘└────────── 12字节 ──────────┘
axis_buffer #(
.IN_DWIDTH ( 8 ),
.OUT_DWIDTH ( W_DATA ),
.BUF_DEPTH ( 16 ),
.WAIT2SEND ( 0 ),
.DUAL_CLOCK ( 0 )
) u_axis_buffer (
.in_clk ( i_aclk ),
.in_rst ( i_arst ),
.out_clk ( i_aclk ),
.out_rst ( i_arst ),
.i_axis_rx_tvalid ( axis_tvalid ),
.i_axis_rx_tdata ( axis_tdata ),
.i_axis_rx_tlast ( sample_last ),
.i_axis_rx_tuser ( '0 ),
.i_axis_rx_tkeep ( '1 ),
.o_axis_rx_tready ( ),
.o_axis_tx_tvalid ( o_axis_tvalid ),
.o_axis_tx_tdata ( o_axis_tdata ),
.o_axis_tx_tlast ( o_axis_tlast ),
.o_axis_tx_tuser ( o_axis_tuser ),
.o_axis_tx_tkeep ( o_axis_tkeep ),
.i_axis_tx_tready ( i_axis_tready )
);
endmodule
5、总结
imu_top 是一个高度模块化、参数化、支持跨时钟域的 IMU 数据采集 IP 核。它解决了三个关键问题:
-
低延迟 I2C 读取 :通过
imu控制器高效获取传感器字节流。 -
精确时间戳:利用 PTP 时钟和 CDC 电路,为每个采样点打上纳秒级时间标签。
-
标准化输出:将变长的 IMU 数据打包成 AXI-Stream,方便集成到更大的数据通路(如 DMA、网络、存储)。