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、网络、存储)。

相关推荐
仲南音2 天前
Xilinx FPGA——时钟IP核MMCM、PLL
fpga开发
I'm a winner2 天前
基于Xilinx FPGA的LVDS高速串行通信系统(三)--数据训练及握手机制【文末源码】
fpga开发
upper20203 天前
coremark移植坑之-nostartfiles -nostdlib参数
学习·fpga开发·coremark移植·riscv设计
Eloudy3 天前
Vivado 纯命令行构建 FPGA 项目教程
fpga开发
Rambo.xia3 天前
AXI跨时钟域与数据一致性——CDC FIFO深度不够、写后读冒险、多Master仲裁、乱序返回,出了Bug根本查不出来
fpga开发·bug
upper20203 天前
FPGA部署卷积神经网络识别MINST图片
fpga开发·卷积神经网络·minst·minst识别·部署神经网络·神经网络识别图片
你是我的解忧王子3 天前
嵌入式 ADC采样 原始数据分析上层(平均值 波动率 正态分布 线性补偿 系统误差)
fpga开发·数据挖掘·数据分析
9527华安4 天前
FPGA纯verilog代码ISP图像处理培训教程,基于IMX214,提供工程源码+视频教程+FPGA开发板
图像处理·fpga开发·isp·imx214·mipi
千寻xun5 天前
二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据
fpga开发·nvme·nvme ssd
千寻xun5 天前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议