雙MIPI攝像頭圖像系統(tǒng)設(shè)計(jì)
介紹
FPGA 的一大優(yōu)勢(shì)是我們可以實(shí)現(xiàn)并行圖像處理數(shù)據(jù)流。雖然任務(wù)比較重,但是我們不需要昂貴的 FPGA,我們可以使用成本低廉范圍中的一個(gè),例如 Spartan 7 或 Artix 7。對(duì)于這個(gè)項(xiàng)目,將展示如何設(shè)計(jì)一個(gè)簡(jiǎn)單的圖像處理應(yīng)用程序,該應(yīng)用程序平行處理兩個(gè)攝像頭。
本項(xiàng)目主要使用 Digilent PCAM 擴(kuò)展板。PCAM 擴(kuò)展板為最多四個(gè) PCAMS 提供接口。所以只需要有FMC接口的開發(fā)板都可以完成本項(xiàng)目移植。
Vivado
為了讓系統(tǒng)快速啟動(dòng)和運(yùn)行,我們將從賽靈思的一個(gè)示例項(xiàng)目開始設(shè)計(jì)。要打開參考項(xiàng)目,我們需要首先創(chuàng)建一個(gè)針對(duì)自己開發(fā)板上 FPGA 的項(xiàng)目。
打開項(xiàng)目后,創(chuàng)建一個(gè)新的BD。
打開BD后,在BD中添加一個(gè) MIPI CSI2 IP。
要打開參考設(shè)計(jì),右鍵單擊 CSI2 IP并選擇打開 IP 示例設(shè)計(jì)。
我們將使用這個(gè)參考項(xiàng)目。首先要做的是移除 DSI 輸出路徑。這將為我們的圖像處理平臺(tái)釋放 FPGA 中的邏輯資源。
下一步是添加以下元素以創(chuàng)建第二條圖像處理通道。
完成的設(shè)計(jì)應(yīng)如下所示:
除了 CSI2 IP 中的設(shè)置外,第二個(gè)圖像處理通道與第一個(gè)相同。
原始 CSI2 IP 設(shè)置
添加的 CSI2 IP 中的設(shè)置
VDMA 內(nèi)存設(shè)置
Sensor Demosaic設(shè)置
AXI4 Stream Switch
時(shí)鐘有不同的上行和下行時(shí)鐘
完成BD設(shè)計(jì)接下來就是針對(duì)硬件進(jìn)行管腳約束。
一旦完成,我們就可以生成和構(gòu)建項(xiàng)目并導(dǎo)出 XSA 用于軟件開發(fā)。
該設(shè)備的利用率如下:
軟件開發(fā)
導(dǎo)出 XSA 后,我們可以創(chuàng)建一個(gè)新的 Vitis 項(xiàng)目,其中包含 hello world 應(yīng)用程序。
從 hello world 應(yīng)用程序 BSP 設(shè)置中,我們可以導(dǎo)入 MIPI CSI2 示例項(xiàng)目。
我們需要對(duì)這個(gè)項(xiàng)目進(jìn)行一些更改。
首先是通過 IIC 與傳感器通信并設(shè)置傳感器。板上的 CSI2 Sensor與FPGA 的 I2C 并沒有直接連接。通過一個(gè)I2C BUFFER,與四個(gè)sensor連接,因?yàn)閟ensor的地址是一樣的。
這可以在 fucntion_prototpye.c 中提供的傳感器配置函數(shù)中進(jìn)行更改。
所以我們?cè)谂渲眠\(yùn)行之前需要選擇多路復(fù)用器。
externintSensorPreConfig(intpcam5c_mode){
u32Index,MaxIndex,MaxIndex1,MaxIndex2;
intStatus;
SensorIicAddr=SENSOR_ADDRESS;
u8SP701mux_addr=0x75;
u8SP701mux_ch=0x40;
u8PCAM_FMC_addr=0x70;
u8PCAM_FMC_ch=0x01;
Status=XIic_SetAddress(&IicAdapter,XII_ADDR_TO_SEND_TYPE,SP701mux_addr);
if(Status!=XST_SUCCESS){
returnXST_FAILURE;
}
WriteBuffer[0]=SP701mux_ch;
Status=AdapterWriteData(1);
if(Status!=XST_SUCCESS){
printf("sp701muxfailed
");
returnXST_FAILURE;
}
Status=XIic_SetAddress(&IicAdapter,XII_ADDR_TO_SEND_TYPE,PCAM_FMC_addr);
if(Status!=XST_SUCCESS){
returnXST_FAILURE;
}
WriteBuffer[0]=PCAM_FMC_ch;
Status=AdapterWriteData(1);
if(Status!=XST_SUCCESS){
printf("pcammuxfailed
");
returnXST_FAILURE;
}
Status=XIic_SetAddress(&IicAdapter,XII_ADDR_TO_SEND_TYPE,SensorIicAddr);
if(Status!=XST_SUCCESS){
returnXST_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){
returnXST_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){
returnXST_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){
returnXST_FAILURE;
}
}
if(Status!=XST_SUCCESS){
xil_printf("Error:inWritingentrystatus=%x
",Status);
returnXST_FAILURE;
}
returnXST_SUCCESS;
}
由于我們添加了第二個(gè) Demosaic,我們還需要更新其配置。
intdemosaic()
{
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);
returnXST_SUCCESS;
}
最后階段是設(shè)置第二個(gè) DMA,這里必須注意 DDR3地址管理以確保幀不會(huì)相互重疊。
intvdma_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);
returnXST_SUCCESS;
}
我們還需要注釋掉 DSI 和TPG等函數(shù)使用的任何代碼。
主代碼也需要更新,以便在串口命令下控制 AXI Switch。
/******************************************************************************
*Copyright(C)2018-2022Xilinx,Inc.Allrightsreserved.
*SPDX-License-Identifier:MIT
*******************************************************************************/
/*****************************************************************************/
/**
*
*@filexmipi_sp701_example.c
*
*
*MODIFICATIONHISTORY:
*
*VerWhoDateChanges
*---------------------------------------------------------------------
*X.XXXXYY/MM/DD
*1.00RHe19/09/20Initialrelease.
*
*
******************************************************************************/
/*****************************IncludeFiles*********************************/
#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"
/**************************ConstantDefinitions*****************************/
#definePAGE_SIZE16
#defineXAXIS_SWITCH_DEVICE_IDXPAR_AXIS_SWITCH_0_DEVICE_ID
#defineIIC_BASE_ADDRESSXPAR_IIC_2_BASEADDR
#defineEEPROM_TEST_START_ADDRESS0x80
#defineIIC_SWITCH_ADDRESS0x74
#defineIIC_ADV7511_ADDRESS0x39
//XV_tpg_Config*tpg1_Config;XV_tpg_Config*tpg1_Config;
//XV_tpgtpg1;
//XV_tpgtpg1;
typedefu8AddressType;
typedefstruct{
u8addr;
u8data;
u8init;
}HDMI_REG;
#defineNUMBER_OF_HDMI_REGS16
HDMI_REGhdmi_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}
};
u8EepromIicAddr;/*VariableforstoringEepromIICaddress*/
intIicLowLevelDynEeprom();
u8EepromReadByte(AddressTypeAddress,u8*BufferPtr,u8ByteCount);
u8EepromWriteByte(AddressTypeAddress,u8*BufferPtr,u8ByteCount);
/****************i************TypeDefinitions*******************************/
typedefu8AddressType;
/**************************VariableDefinitions*****************************/
externXIicIicFmc,IicAdapter;/*IICdevice.*/
//HDMIIIC
intIicLowLevelDynEeprom()
{
u8BytesRead;
u32StatusReg;
u8Index;
intStatus;
u32i;
EepromIicAddr=IIC_SWITCH_ADDRESS;
Status=XIic_DynInit(IIC_BASE_ADDRESS);
if(Status!=XST_SUCCESS){
returnXST_FAILURE;
}
xil_printf("
AfterXIic_DynInit
");
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;Indexfor(Index=0;Indexfor(i=0;i<1000;i++)?{};?//?IIC?delay
?if(BytesRead!=1){
returnXST_FAILURE;
}
}
returnXST_SUCCESS;
}
/*****************************************************************************/
/**
*ThisfunctionwritesabufferofbytestotheIICserialEEPROM.
*
*@paramBufferPtrcontainstheaddressofthedatatowrite.
*@paramByteCountcontainsthenumberofbytesinthebuffertobe
*written.Notethatthisshouldnotexceedthepagesizeofthe
*EEPROMasnotedbytheconstantPAGE_SIZE.
*
*@returnThenumberofbyteswritten,avaluelessthanthatwhichwas
*specifiedasaninputindicatesanerror.
*
*@noteone.
*
******************************************************************************/
u8EepromWriteByte(AddressTypeAddress,u8*BufferPtr,u8ByteCount)
{
u8SentByteCount;
u8WriteBuffer[sizeof(Address)+PAGE_SIZE];
u8Index;
/*
*Atemporarywritebuffermustbeusedwhichcontainsboththeaddress
*andthedatatobewritten,puttheaddressinfirstbaseduponthe
*sizeoftheaddressfortheEEPROM
*/
if(sizeof(AddressType)==2){
WriteBuffer[0]=(u8)(Address>>8);
WriteBuffer[1]=(u8)(Address);
}elseif(sizeof(AddressType)==1){
WriteBuffer[0]=(u8)(Address);
EepromIicAddr|=(EEPROM_TEST_START_ADDRESS>>8)&0x7;
}
/*
*Putthedatainthewritebufferfollowingtheaddress.
*/
for(Index=0;IndexreturnSentByteCount-sizeof(Address);
}
/******************************************************************************
*
*ThisfunctionreadsanumberofbytesfromtheIICserialEEPROMintoa
*specifiedbuffer.
*
*@paramBufferPtrcontainstheaddressofthedatabuffertobefilled.
*@paramByteCountcontainsthenumberofbytesinthebuffertoberead.
*Thisvalueisconstrainedbythepagesizeofthedevicesuch
*thatupto64Kmaybereadinonecall.
*
*@returnThenumberofbytesread.Avaluelessthanthespecifiedinput
*valueindicatesanerror.
*
*@noteNone.
*
******************************************************************************/
u8EepromReadByte(AddressTypeAddress,u8*BufferPtr,u8ByteCount)
{
u8ReceivedByteCount;
u8SentByteCount;
u16StatusReg;
/*
*PositiontheReadpointertospecificlocationintheEEPROM.
*/
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));
/*
*Receivethedata.
*/
ReceivedByteCount=XIic_DynRecv(IIC_BASE_ADDRESS,EepromIicAddr,
BufferPtr,ByteCount);
/*
*ReturnthenumberofbytesreceivedfromtheEEPROM.
*/
returnReceivedByteCount;
}
/*****************************************************************************/
/**
*
*MainfunctiontoinitializeinteropsystemandreaddatafromAR0330sensor
*@paramNone.
*
*@return
*-XST_SUCCESSifMIPIInteropwassuccessful.
*-XST_FAILUREifMIPIInteropfailed.
*
*@noteNone.
*
******************************************************************************/
intmain(){
intStatus;
intpcam5c_mode=1;
intusr_entry,prev_sel;
intdefault_input;
intdsi_hdmi_select=0;
Xil_ICacheDisable();
Xil_DCacheDisable();
XAxis_SwitchAxisSwitch;
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("
******************************************************
");
xil_printf("
**SP701ExampleDesign**");
Status=IicLowLevelDynEeprom();
if(Status!=XST_SUCCESS){
xil_printf("ADV7511IICprogrammingFAILED
");
returnXST_FAILURE;
}
xil_printf("ADV7511IICprogrammingPASSED
");
//InitializeFMC,AdapterandSensorIIC
Status=InitIIC();
if(Status!=XST_SUCCESS){
xil_printf("
IICinitializationFailed
");
returnXST_FAILURE;
}
xil_printf("IICInitializtionDone
");
//InitializeFMCInterruptSystem
Status=SetupFmcInterruptSystem(&IicFmc);
if(Status!=XST_SUCCESS){
xil_printf("
InterruptSystemInitializationFailed
");
returnXST_FAILURE;
}
xil_printf("FMCInterruptSystemInitializationDone
");
//SetupIICInterruptHandlers
SetupIICIntrHandlers();
xil_printf("IICInterruptHandlersSetupDone
");
Status=SetFmcIICAddress();
if(Status!=XST_SUCCESS){
xil_printf("
FMCIICAddressSetupFailed
");
returnXST_FAILURE;
}
xil_printf("FmcIICAddressSet
");
//InitializeAdapterInterruptSystem
Status=SetupAdapterInterruptSystem(&IicAdapter);
if(Status!=XST_SUCCESS){
xil_printf("
InterruptSystemInitializationFailed
");
returnXST_FAILURE;
}
xil_printf("AdapterInterruptSystemInitializationDone
");
//SetAddressofAdapterIIC
Status=SetAdapterIICAddress();
if(Status!=XST_SUCCESS){
xil_printf("
AdapterIICAddressSetupFailed
");
returnXST_FAILURE;
}
xil_printf("AdapterIICAddressSet
");
Status=InitializeCsiRxSs();
if(Status!=XST_SUCCESS){
xil_printf("CSIRxSsInitfailedstatus=%x.
",Status);
returnXST_FAILURE;
}
dsi_hdmi_select=0;
//usingdefault_inputvartocomparesameoptionselection
default_input=1;
//SetupDSI();
resetIp();
EnableCSI();
GPIOSelect(dsi_hdmi_select);
Status=demosaic();
if(Status!=XST_SUCCESS){
xil_printf("
DemosaicFailed
");
returnXST_FAILURE;
}
CamReset();
//PreconifgureSensor
Status=SensorPreConfig(pcam5c_mode);
if(Status!=XST_SUCCESS){
xil_printf("
SensorPreConfigurationFailed
");
returnXST_FAILURE;
}
xil_printf("
Sensor1isPreConfigured
");
WritetoReg(0x30,0x08,0x02);
//PreconifgureSensor
Status=SensorPreConfig1(pcam5c_mode);
if(Status!=XST_SUCCESS){
xil_printf("
SensorPreConfigurationFailed
");
returnXST_FAILURE;
}
xil_printf("
Sensor2isPreConfigured
");
WritetoReg(0x30,0x08,0x02);
Status=vdma_hdmi();
if(Status!=XST_SUCCESS){
xil_printf("
Vdma_hdmiFailed
");
returnXST_FAILURE;
}
Status=vtpg_hdmi();
if(Status!=XST_SUCCESS){
xil_printf("
VtpgFailed
");
returnXST_FAILURE;
}
Sensor_Delay();
xil_printf("
PipelineConfigurationCompleted
");
while(1){
xil_printf("
PleaseSelectCamera(1or2)+ENTER:");
usr_entry=getchar();
charb;
scanf("%c",&b);//ThiswilltakeENTERkey
switch(usr_entry){
case'1':
xil_printf("
SwitchingtoCamera1
");
XAxisScr_RegUpdateDisable(&AxisSwitch);
XAxisScr_MiPortDisableAll(&AxisSwitch);
XAxisScr_MiPortEnable(&AxisSwitch,0,0);
XAxisScr_RegUpdateEnable(&AxisSwitch);
break;
case'2':
xil_printf("
SwitchingtoCamera1
");
XAxisScr_RegUpdateDisable(&AxisSwitch);
XAxisScr_MiPortDisableAll(&AxisSwitch);
XAxisScr_MiPortEnable(&AxisSwitch,0,1);
XAxisScr_RegUpdateEnable(&AxisSwitch);
break;
default:
xil_printf("
Selectionisunavailable.Pleasetryagain
");
break;
}
}
returnXST_SUCCESS;
}
測(cè)試
我們可以在連接到 HDMI 輸出時(shí)運(yùn)行應(yīng)用程序并在顯示器上看到圖像。
使用應(yīng)用程序選擇圖像。
參考
https://www.hackster.io/
總結(jié)
該項(xiàng)目展示了一個(gè)MIPI攝像頭接入FPGA的簡(jiǎn)單、快捷的方式,同時(shí)可以學(xué)習(xí)一下軟件的導(dǎo)入工程的方式,簡(jiǎn)單的基于MicroBlaze系統(tǒng)要學(xué)會(huì)自己寫控制代碼,也許這就是新一代“FPGA打工人”需要掌握的一項(xiàng)新技術(shù)吧~(doge~不是)
示例工程
https://github.com/ATaylorCEngFIET/Hackster/tree/master
https://github.com/ATaylorCEngFIET/SP701_Imaging_Vivado
審核編輯 :李倩
-
FPGA
+關(guān)注
關(guān)注
1630文章
21794瀏覽量
605127 -
攝像頭
+關(guān)注
關(guān)注
60文章
4860瀏覽量
96092 -
圖像系統(tǒng)
+關(guān)注
關(guān)注
0文章
10瀏覽量
7075
原文標(biāo)題:示例工程
文章出處:【微信號(hào):Open_FPGA,微信公眾號(hào):OpenFPGA】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論