Lattice LFCPNX-100 Fpga开发+源码:基于I2c协议的IMU驱动控制

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 核。它解决了三个关键问题:

  1. 低延迟 I2C 读取 :通过 imu 控制器高效获取传感器字节流。

  2. 精确时间戳:利用 PTP 时钟和 CDC 电路,为每个采样点打上纳秒级时间标签。

  3. 标准化输出:将变长的 IMU 数据打包成 AXI-Stream,方便集成到更大的数据通路(如 DMA、网络、存储)。

相关推荐
GateWorld3 小时前
LCD显示技术完全指南:原理·制造·驱动·FPGA实现之基础三
fpga开发·lcd显示·minilvds·fpga点屏
cjie2214 小时前
图像缩放因子的计算
计算机视觉·fpga开发
XINVRY-FPGA10 小时前
XC7Z010-2CLG400I Xilinx Zynq-7000 FPGA
arm开发·嵌入式硬件·算法·fpga开发·硬件工程·dsp开发·fpga
XINVRY-FPGA11 小时前
XCZU11EG-2FFVC1156I Xilinx Zynq UltraScale+ MPSoC EG FPGA
图像处理·嵌入式硬件·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
unicrom_深圳市由你创科技1 天前
USB通信在FPGA上怎么实现?
fpga开发
GateWorld1 天前
LCD显示技术完全指南:原理·制造·驱动·FPGA实现之基础二
fpga开发·lcd显示·minilvds·fpga点屏
禾刀围玉2 天前
基于FPGA的卷积神经网络实现-Step2 卷积模块设计
人工智能·fpga开发·cnn
fantasy_arch2 天前
fpga demo测试hello world
fpga开发
黑猫学长呀2 天前
存储宝典第6篇:测试机台的PE板和PPB板有什么区别?
测试工具·fpga开发·ssd·芯片测试·ate·存储芯片·测试机台