双MIPI摄像头图像系统设计

双MIPI摄像头图像系统设计

5899537f95aad84711b484d9d9e73059.png

介绍

FPGA 的一大优势是我们可以实现并行图像处理数据流。虽然任务比较重,但是我们不需要昂贵的 FPGA,我们可以使用成本低廉范围中的一个,例如 Spartan 7 或 Artix 7。对于这个项目,将展示如何设计一个简单的图像处理应用程序,该应用程序平行处理两个摄像头。

本项目主要使用 Digilent PCAM 扩展板。PCAM 扩展板为最多四个 PCAMS 提供接口。所以只需要有FMC接口的开发板都可以完成本项目移植。

777fa7ada2d721b30bf2683f711f819c.png

Vivado

为了让系统快速启动和运行,我们将从赛灵思的一个示例项目开始设计。要打开参考项目,我们需要首先创建一个针对自己开发板上 FPGA 的项目。

2161e9010380271be9646134b081df92.png

打开项目后,创建一个新的BD。

f3b1363f586c1ae600a717178e0070d0.png

打开BD后,在BD中添加一个 MIPI CSI2 IP。

37039a8d4fe3f4386b1a8b84c7c5ff87.png

要打开参考设计,右键单击 CSI2 IP并选择打开 IP 示例设计。

d1efb1630f3addff87074941acb33a13.png

我们将使用这个参考项目。首先要做的是移除 DSI 输出路径。这将为我们的图像处理平台释放 FPGA 中的逻辑资源。

下一步是添加以下元素以创建第二条图像处理通道。

  • CSI2 IP Block

  • Register Slices & concatenation

  • Sensor Demosaic

  • VDMA

  • AXI Switch

完成的设计应如下所示:

05bfee573da2b5cd241ef6fd343b60f5.png

除了 CSI2 IP 中的设置外,第二个图像处理通道与第一个相同。

5e987b227dd64c753a346c957ba34429.png

原始 CSI2 IP 设置

8c03503043af17af789013e1d7dab927.png

添加的 CSI2 IP 中的设置

9e8c9eb15a55dcc9bacbdbc9b5ae0af0.png

VDMA 内存设置

3cf216e8f3c3eee9bdfd967ced75edd5.png

Sensor Demosaic设置

d0ffbfa8ed203f5736e0493e67701e34.png

AXI4 Stream Switch

a603aa8d35bec023291984fd26974bf2.png

时钟有不同的上行和下行时钟

0ced2e54be528130c05338cfe8b275fa.png df353af52fa961494b29be13018f82fc.png

完成BD设计接下来就是针对硬件进行管脚约束。

一旦完成,我们就可以生成和构建项目并导出 XSA 用于软件开发。

该设备的利用率如下:

5db5fb15d9bc4094fb017e807651c005.png

软件开发

导出 XSA 后,我们可以创建一个新的 Vitis 项目,其中包含 hello world 应用程序。

从 hello world 应用程序 BSP 设置中,我们可以导入 MIPI CSI2 示例项目。

bc0b37cde535ba9351a20761d09b36dc.png

我们需要对这个项目进行一些更改。

首先是通过 IIC 与传感器通信并设置传感器。板上的 CSI2 Sensor与FPGA 的 I2C 并没有直接连接。通过一个I2C BUFFER,与四个sensor连接,因为sensor的地址是一样的。

这可以在 fucntion_prototpye.c 中提供的传感器配置函数中进行更改。

所以我们在配置运行之前需要选择多路复用器。

extern int SensorPreConfig(int pcam5c_mode) {


  u32 Index, MaxIndex, MaxIndex1, MaxIndex2;
  int Status;
  SensorIicAddr = SENSOR_ADDRESS;

  u8 SP701mux_addr = 0x75;
    u8 SP701mux_ch = 0x40;

    u8 PCAM_FMC_addr = 0x70;
    u8 PCAM_FMC_ch = 0x01;


    Status = XIic_SetAddress(&IicAdapter, XII_ADDR_TO_SEND_TYPE, SP701mux_addr);
    if (Status != XST_SUCCESS) {
   return XST_FAILURE;
    }

    WriteBuffer[0] = SP701mux_ch;
    Status = AdapterWriteData(1);
   if (Status != XST_SUCCESS) {
     printf("sp701 mux failed\n\r");
     return XST_FAILURE;
   }

    Status = XIic_SetAddress(&IicAdapter, XII_ADDR_TO_SEND_TYPE, PCAM_FMC_addr);
    if (Status != XST_SUCCESS) {
   return XST_FAILURE;
    }

    WriteBuffer[0] = PCAM_FMC_ch;
    Status = AdapterWriteData(1);
   if (Status != XST_SUCCESS) {
     printf("pcam mux failed\n\r");
     return XST_FAILURE;
   }



  Status = XIic_SetAddress(&IicAdapter, XII_ADDR_TO_SEND_TYPE, SensorIicAddr);
  if (Status != XST_SUCCESS) {
 return XST_FAILURE;
  }


  WritetoReg(0x31, 0x03, 0x11);
  WritetoReg(0x30, 0x08, 0x82);

  Sensor_Delay();


  MaxIndex = length_sensor_pre;
  for(Index = 0; Index < (MaxIndex - 0); Index++)
  {
    WriteBuffer[0] = sensor_pre[Index].Address >> 8;
 WriteBuffer[1] = sensor_pre[Index].Address;
 WriteBuffer[2] = sensor_pre[Index].Data;

    Sensor_Delay();

 Status = AdapterWriteData(3);
 if (Status != XST_SUCCESS) {
   return XST_FAILURE;
 }
  }


  WritetoReg(0x30, 0x08, 0x42);


  MaxIndex1 = length_pcam5c_mode1;

  for(Index = 0; Index < (MaxIndex1 - 0); Index++)
  {
    WriteBuffer[0] = pcam5c_mode1[Index].Address >> 8;
 WriteBuffer[1] = pcam5c_mode1[Index].Address;
 WriteBuffer[2] = pcam5c_mode1[Index].Data;

    Sensor_Delay();

 Status = AdapterWriteData(3);
 if (Status != XST_SUCCESS) {
   return XST_FAILURE;
 }
  }


  WritetoReg(0x30, 0x08, 0x02);
  Sensor_Delay();
  WritetoReg(0x30, 0x08, 0x42);


  MaxIndex2 = length_sensor_list;

  for(Index = 0; Index < (MaxIndex2 - 0); Index++)
  {
    WriteBuffer[0] = sensor_list[Index].Address >> 8;
 WriteBuffer[1] = sensor_list[Index].Address;
 WriteBuffer[2] = sensor_list[Index].Data;

    Sensor_Delay();

 Status = AdapterWriteData(3);
   if (Status != XST_SUCCESS) {
  return XST_FAILURE;
   }
  }


  if(Status != XST_SUCCESS) {
    xil_printf("Error: in Writing entry status = %x \r\n", Status);
    return XST_FAILURE;
  }

  return XST_SUCCESS;

}

由于我们添加了第二个 Demosaic,我们还需要更新其配置。

int demosaic()
{
  demosaic_Config = XV_demosaic_LookupConfig(DEMOSAIC_DEVICE_ID);
  XV_demosaic_CfgInitialize(&InstancePtr, demosaic_Config,
                             demosaic_Config->BaseAddress);
  XV_demosaic_Set_HwReg_width(&InstancePtr, 1920);
  XV_demosaic_Set_HwReg_height(&InstancePtr, 1080);
  XV_demosaic_Set_HwReg_bayer_phase(&InstancePtr, 0x3);
  XV_demosaic_EnableAutoRestart(&InstancePtr);
  XV_demosaic_Start(&InstancePtr);

  demosaic_Config1 = XV_demosaic_LookupConfig(DEMOSAIC_DEVICE1_ID);
  XV_demosaic_CfgInitialize(&InstancePtr1, demosaic_Config1,
                             demosaic_Config1->BaseAddress);
  XV_demosaic_Set_HwReg_width(&InstancePtr1, 1920);
  XV_demosaic_Set_HwReg_height(&InstancePtr1, 1080);
  XV_demosaic_Set_HwReg_bayer_phase(&InstancePtr1, 0x3);
  XV_demosaic_EnableAutoRestart(&InstancePtr1);
  XV_demosaic_Start(&InstancePtr1);
  return XST_SUCCESS;

}

最后阶段是设置第二个 DMA,这里必须注意 DDR3地址管理以确保帧不会相互重叠。

int vdma_hdmi() {

  InitVprocSs_CSC(1);

  ResetVDMA();

  RunVDMA(&AxiVdma, XPAR_AXI_VDMA_0_DEVICE_ID, HORIZONTAL_RESOLUTION, \
    VERTICAL_RESOLUTION, srcBuffer, FRAME_COUNTER, 0);

  RunVDMA(&AxiVdma1, XPAR_AXI_VDMA_1_DEVICE_ID, HORIZONTAL_RESOLUTION, \
    VERTICAL_RESOLUTION, srcBuffer1, FRAME_COUNTER, 0);

  return XST_SUCCESS;

}

我们还需要注释掉 DSI 和TPG等函数使用的任何代码。

主代码也需要更新,以便在串口命令下控制 AXI Switch。

/******************************************************************************
* Copyright (C) 2018 - 2022 Xilinx, Inc.  All rights reserved.
* SPDX-License-Identifier: MIT
*******************************************************************************/

/*****************************************************************************/
/**
*
* @file xmipi_sp701_example.c
*
* <pre>
* MODIFICATION HISTORY:
*
* Ver   Who    Date     Changes
* ----- ------ -------- --------------------------------------------------
* X.XX  XX     YY/MM/DD
* 1.00  RHe    19/09/20 Initial release.
* </pre>
*
******************************************************************************/
/***************************** Include Files *********************************/

#include "xparameters.h"
#include "xiic.h"
#include "xil_exception.h"
#include "function_prototype.h"
#include "pcam_5C_cfgs.h"
#include "xstatus.h"
#include "sleep.h"
#include "xiic_l.h"
#include "xil_io.h"
#include "xil_types.h"
//#include "xv_tpg.h"
#include "xil_cache.h"
#include "stdio.h"
#include "xaxis_switch.h"



/************************** Constant Definitions *****************************/


#define PAGE_SIZE   16
#define XAXIS_SWITCH_DEVICE_ID  XPAR_AXIS_SWITCH_0_DEVICE_ID

#define IIC_BASE_ADDRESS XPAR_IIC_2_BASEADDR

#define EEPROM_TEST_START_ADDRESS 0x80

#define IIC_SWITCH_ADDRESS 0x74
#define IIC_ADV7511_ADDRESS 0x39
//XV_tpg_Config  *tpg1_Config;XV_tpg_Config  *tpg1_Config;
//XV_tpg    tpg1;
//XV_tpg    tpg1;
typedef u8 AddressType;

typedef struct {
 u8 addr;
 u8 data;
 u8 init;
} HDMI_REG;

#define NUMBER_OF_HDMI_REGS  16
HDMI_REG hdmi_iic[NUMBER_OF_HDMI_REGS] = {
 {0x41, 0x00, 0x10},
 {0x98, 0x00, 0x03},
 {0x9A, 0x00, 0xE0},
 {0x9C, 0x00, 0x30},
 {0x9D, 0x00, 0x61},
 {0xA2, 0x00, 0xA4},
 {0xA3, 0x00, 0xA4},
 {0xE0, 0x00, 0xD0},
 {0xF9, 0x00, 0x00},
 {0x18, 0x00, 0xE7},
    {0x55, 0x00, 0x00},
    {0x56, 0x00, 0x28},
    {0xD6, 0x00, 0xC0},
    {0xAF, 0x00, 0x4},
 {0xF9, 0x00, 0x00}
};

u8 EepromIicAddr;  /* Variable for storing Eeprom IIC address */

int IicLowLevelDynEeprom();

u8 EepromReadByte(AddressType Address, u8 *BufferPtr, u8 ByteCount);
u8 EepromWriteByte(AddressType Address, u8 *BufferPtr, u8 ByteCount);



/****************i************ Type Definitions *******************************/

typedef u8 AddressType;

/************************** Variable Definitions *****************************/

extern XIic IicFmc, IicAdapter ; /*  IIC device. */

//HDMI IIC
int IicLowLevelDynEeprom()
{
  u8 BytesRead;
  u32 StatusReg;
  u8 Index;
  int Status;
  u32 i;
  EepromIicAddr = IIC_SWITCH_ADDRESS;
  Status = XIic_DynInit(IIC_BASE_ADDRESS);
  if (Status != XST_SUCCESS) {
 return XST_FAILURE;
  }
  xil_printf("\r\nAfter XIic_DynInit\r\n");
  while (((StatusReg = XIic_ReadReg(IIC_BASE_ADDRESS,
    XIIC_SR_REG_OFFSET)) &
    (XIIC_SR_RX_FIFO_EMPTY_MASK |
    XIIC_SR_TX_FIFO_EMPTY_MASK |
    XIIC_SR_BUS_BUSY_MASK)) !=
    (XIIC_SR_RX_FIFO_EMPTY_MASK |
    XIIC_SR_TX_FIFO_EMPTY_MASK)) {

  }


  EepromIicAddr = IIC_ADV7511_ADDRESS;
  for ( Index = 0; Index < NUMBER_OF_HDMI_REGS; Index++)
  {
    EepromWriteByte(hdmi_iic[Index].addr, &hdmi_iic[Index].init, 1);
  }

  for ( Index = 0; Index < NUMBER_OF_HDMI_REGS; Index++)
  {
    BytesRead = EepromReadByte(hdmi_iic[Index].addr, &hdmi_iic[Index].data, 1);
    for(i=0;i<1000;i++) {}; // IIC delay
 if (BytesRead != 1) {
      return XST_FAILURE;
 }
  }


  return XST_SUCCESS;

}


/*****************************************************************************/
/**
* This function writes a buffer of bytes to the IIC serial EEPROM.
*
* @param BufferPtr contains the address of the data to write.
* @param ByteCount contains the number of bytes in the buffer to be
*  written. Note that this should not exceed the page size of the
*  EEPROM as noted by the constant PAGE_SIZE.
*
* @return The number of bytes written, a value less than that which was
*  specified as an input indicates an error.
*
* @note  one.
*
******************************************************************************/
u8 EepromWriteByte(AddressType Address, u8 *BufferPtr, u8 ByteCount)
{
  u8 SentByteCount;
  u8 WriteBuffer[sizeof(Address) + PAGE_SIZE];
  u8 Index;

  /*
   * A temporary write buffer must be used which contains both the address
   * and the data to be written, put the address in first based upon the
   * size of the address for the EEPROM
   */
  if (sizeof(AddressType) == 2) {
 WriteBuffer[0] = (u8) (Address >> 8);
 WriteBuffer[1] = (u8) (Address);
  } else if (sizeof(AddressType) == 1) {
 WriteBuffer[0] = (u8) (Address);
 EepromIicAddr |= (EEPROM_TEST_START_ADDRESS >> 8) & 0x7;
  }

  /*
   * Put the data in the write buffer following the address.
   */
  for (Index = 0; Index < ByteCount; Index++) {
 WriteBuffer[sizeof(Address) + Index] = BufferPtr[Index];
  }

  /*
   * Write a page of data at the specified address to the EEPROM.
   */
  SentByteCount = XIic_DynSend(IIC_BASE_ADDRESS, EepromIicAddr,
    WriteBuffer, sizeof(Address) + ByteCount,
    XIIC_STOP);

  /*
   * Return the number of bytes written to the EEPROM.
   */
  return SentByteCount - sizeof(Address);

}


/******************************************************************************
*
* This function reads a number of bytes from the IIC serial EEPROM into a
* specified buffer.
*
* @param BufferPtr contains the address of the data buffer to be filled.
* @param ByteCount contains the number of bytes in the buffer to be read.
*  This value is constrained by the page size of the device such
*  that up to 64K may be read in one call.
*
* @return The number of bytes read. A value less than the specified input
*  value indicates an error.
*
* @note  None.
*
******************************************************************************/
u8 EepromReadByte(AddressType Address, u8 *BufferPtr, u8 ByteCount)
{
  u8 ReceivedByteCount;
  u8 SentByteCount;
  u16 StatusReg;

  /*
   * Position the Read pointer to specific location in the EEPROM.
   */
  do {
 StatusReg = XIic_ReadReg(IIC_BASE_ADDRESS, XIIC_SR_REG_OFFSET);
    if (!(StatusReg & XIIC_SR_BUS_BUSY_MASK)) {
   SentByteCount = XIic_DynSend(IIC_BASE_ADDRESS, EepromIicAddr,
       (u8 *) &Address, sizeof(Address), XIIC_REPEATED_START);
    }

  } while (SentByteCount != sizeof(Address));
  /*
   * Receive the data.
   */
  ReceivedByteCount = XIic_DynRecv(IIC_BASE_ADDRESS, EepromIicAddr,
                                            BufferPtr, ByteCount);

  /*
   * Return the number of bytes received from the EEPROM.
   */

  return ReceivedByteCount;

}


/*****************************************************************************/
/**
 *
 * Main function to initialize interop system and read data from AR0330 sensor

 * @param  None.
 *
 * @return
 *   - XST_SUCCESS if MIPI Interop was successful.
 *   - XST_FAILURE if MIPI Interop failed.
 *
 * @note   None.
 *
 ******************************************************************************/
int main() {
  int Status;
  int pcam5c_mode = 1;
  int usr_entry ,prev_sel;
  int default_input;
  int dsi_hdmi_select = 0;

  Xil_ICacheDisable();
  Xil_DCacheDisable();
  XAxis_Switch AxisSwitch;
  XAxis_Switch_Config *ASWConfig;

  ASWConfig = XAxisScr_LookupConfig(XAXIS_SWITCH_DEVICE_ID);
  XAxisScr_CfgInitialize(&AxisSwitch, ASWConfig,ASWConfig->BaseAddress);
  XAxisScr_RegUpdateDisable(&AxisSwitch);
  XAxisScr_MiPortDisableAll(&AxisSwitch);
  XAxisScr_MiPortEnable(&AxisSwitch, 0, 0);
  XAxisScr_RegUpdateEnable(&AxisSwitch);

  xil_printf("\n\r******************************************************\n\r");
  xil_printf("\n\r**           SP701 Example Design            **");

  Status = IicLowLevelDynEeprom();
  if (Status != XST_SUCCESS) {
    xil_printf("ADV7511 IIC programming FAILED\r\n");
    return XST_FAILURE;
  }
  xil_printf("ADV7511 IIC programming PASSED\r\n");


  //Initialize FMC, Adapter and Sensor IIC
  Status = InitIIC();
  if (Status != XST_SUCCESS) {
 xil_printf("\n\r IIC initialization Failed \n\r");
 return XST_FAILURE;
  }
  xil_printf("IIC Initializtion Done \n\r");

  //Initialize FMC Interrupt System
  Status = SetupFmcInterruptSystem(&IicFmc);
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rInterrupt System Initialization Failed \n\r");
    return XST_FAILURE;
  }
  xil_printf("FMC Interrupt System Initialization Done \n\r");

  //Set up IIC Interrupt Handlers
  SetupIICIntrHandlers();
  xil_printf("IIC Interrupt Handlers Setup Done \n\r");

  Status =  SetFmcIICAddress();
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rFMC IIC Address Setup Failed \n\r");
 return XST_FAILURE;
  }
  xil_printf("Fmc IIC Address Set\n\r");

  //Initialize Adapter Interrupt System
  Status = SetupAdapterInterruptSystem(&IicAdapter);
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rInterrupt System Initialization Failed \n\r");
    return XST_FAILURE;
  }
  xil_printf("Adapter Interrupt System Initialization Done \n\r");

  //Set Address of Adapter IIC
  Status =  SetAdapterIICAddress();
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rAdapter IIC Address Setup Failed \n\r");
 return XST_FAILURE;
  }
  xil_printf("Adapter IIC Address Set\n\r");

  Status = InitializeCsiRxSs();
  if (Status != XST_SUCCESS) {
    xil_printf("CSI Rx Ss Init failed status = %x.\r\n", Status);
 return XST_FAILURE;
  }


  dsi_hdmi_select = 0;
  //using default_input var to compare same option selection
  default_input = 1;
  //SetupDSI();
  resetIp();
  EnableCSI();
  GPIOSelect(dsi_hdmi_select);

  Status = demosaic();
  if (Status != XST_SUCCESS) {
 xil_printf("\n\rDemosaic Failed \n\r");
 return XST_FAILURE;
  }

  CamReset();

  //Preconifgure Sensor
  Status = SensorPreConfig(pcam5c_mode);
  if (Status != XST_SUCCESS) {
 xil_printf("\n\rSensor PreConfiguration Failed \n\r");
 return XST_FAILURE;
  }
  xil_printf("\n\rSensor 1 is PreConfigured\n\r");
  WritetoReg(0x30, 0x08, 0x02);

  //Preconifgure Sensor
  Status = SensorPreConfig1(pcam5c_mode);
  if (Status != XST_SUCCESS) {
 xil_printf("\n\rSensor PreConfiguration Failed \n\r");
 return XST_FAILURE;
  }
  xil_printf("\n\rSensor 2 is PreConfigured\n\r");
  WritetoReg(0x30, 0x08, 0x02);


  Status = vdma_hdmi();
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rVdma_hdmi Failed \n\r");
 return XST_FAILURE;
  }

  Status = vtpg_hdmi();
  if (Status != XST_SUCCESS) {
    xil_printf("\n\rVtpg Failed \n\r");
 return XST_FAILURE;
  }


  Sensor_Delay();
  xil_printf("\n\rPipeline Configuration Completed \n\r");

  while(1) {

    xil_printf("\r\nPlease Select Camera(1 or 2) + ENTER:");


    usr_entry = getchar();

   char b;
   scanf("%c", &b);// This will take ENTER key



 switch(usr_entry) {

   case '1':
    xil_printf("\n\rSwitching to Camera 1\n\r");
    XAxisScr_RegUpdateDisable(&AxisSwitch);
    XAxisScr_MiPortDisableAll(&AxisSwitch);
    XAxisScr_MiPortEnable(&AxisSwitch, 0, 0);
    XAxisScr_RegUpdateEnable(&AxisSwitch);
    break;

   case '2':
    xil_printf("\n\rSwitching to Camera 1\n\r");
    XAxisScr_RegUpdateDisable(&AxisSwitch);
    XAxisScr_MiPortDisableAll(&AxisSwitch);
    XAxisScr_MiPortEnable(&AxisSwitch, 0, 1);
    XAxisScr_RegUpdateEnable(&AxisSwitch);
    break;

   default:
    xil_printf("\n\rSelection is unavailable. Please try again\n\r");
    break;
 }

  }
  return XST_SUCCESS;

}

测试

我们可以在连接到 HDMI 输出时运行应用程序并在显示器上看到图像。

使用应用程序选择图像。

b851c7966964f01c2eff4078ac911dc3.png b56c09adfdc6dc681b8aff2b56b653e0.png 0f7e235ca85af8061114d92f408c2990.png 252799c7f032ded356a564094a4859a6.png

参考

https://www.hackster.io/

总结

该项目展示了一个MIPI摄像头接入FPGA的简单、快捷的方式,同时可以学习一下软件的导入工程的方式,简单的基于MicroBlaze系统要学会自己写控制代码,也许这就是新一代“FPGA打工人”需要掌握的一项新技术吧~(doge~不是)

5af13d168e1d5dcc549eb713b9f04a0a.png

示例工程

https://github.com/ATaylorCEngFIET/Hackster/tree/master

https://github.com/ATaylorCEngFIET/SP701_Imaging_Vivado

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值