Vivado搭建参考前一节Vivado基于IP核的视频处理框架搭建:
开源ISP介绍(1)——开源ISP的Vivado框架搭建-CSDN博客
导出Hardware
在vivado中导出Hardware文件,成功综合—实现—生成比特流后导出硬件.xsa文件。(注意导出时选择include bitstream选项)
.xsa是用于在Vitis中生成平台(Platform)的文件,平台可以看作是BSP板级支持包文件,嵌入式Vitis使用了分层的设计架构。
Platform工程
BSP文件如FPGA中使用的所有硬件用到的驱动文件(包括PS端的MCU、各个Xilinx官方IP的驱动以及自定义IP的驱动),创建平台后会在Vitis平台目录下生成一个比较重要的描述PS端外设信息的xparameters.h(xparameters_ps.h是描述PS端外设信息的文件)头文件,该文件中描述了Vivado BolockDesign综合后的IP基地址、器件、中断号等信息。
例如用到的AXI IIC IP核,在该文件的描述如下
后续在配置该外设时,就可以通过官方提供的XIic驱动函数XIic_LookupConfig查找器件ID,并得到该IP相应的基本地址等信息(该过程类似于Linux中的设备树文件解析)与配置结构体。
然后使用对应的配置驱动函数XIic_CfgInitialize对IP进行配置,如果有中断还需要配置中断相关的内容,Xinlix IP的配置大致都遵循这个步骤。
以上简单介绍了通过Vivado导出的.xsa文件在Vitis生成相关的BSP平台文件过程。
嵌入式Vitis源码分析
接下来创建一个Application Project
并将开源项目提供的src文件中的内容复制到Application项目中,阅读从main函数开始,大致流程为:配置系统中断——配置GPIO——配置SD文件系统挂载——配置摄像头——配置ISP视频处理相关——配置VDMA帧缓存——While循环,内容如下
int main()
{XAxiVdma cam_vdma_inst;XAxiVdma lcd_vdma_inst;XAxiVdma dvi_vdma_inst;IspContext isp_context = {0};sys_intr_init(); //配置中断相关gpio_init(); //配置GPIO相关fs_init = 0 == platform_init_fs();u32 status;//OV5640设置为DVP输出,设置输出的像素长宽u16 cmos_h_pixel = CAM_WIDTH; //ov5640 DVP 输出水平像素点数u16 cmos_v_pixel = CAM_HEIGHT; //ov5640 DVP 输出垂直像素点数u16 total_v_std = 0;status = ov5640_init(cmos_h_pixel, cmos_v_pixel, &total_v_std); //初始化ov5640,配置相关寄存器参数if(status == 0)xil_printf("OV5640 detected successful!\r\n");elsexil_printf("OV5640 detected failed!\r\n");xil_printf("cmos size %u x %u\r\n", cmos_h_pixel, cmos_v_pixel);cmos_set_exposure(total_v_std); //设置曝光AECcmos_set_gain(0x200); //设置增益AGCisp_context.base = ISP_BASE;isp_context.pfn_set_exposure = _set_exposure;isp_context.pfn_set_gain = _set_gain;isp_context.priv_data = NULL;isp_context.ae_target_luminance = 75<<(ISP_BITS-8);isp_context.ae_max_exposure = total_v_std;isp_context.ae_max_gain = 0x1ff; //512isp_context.ae_cur_exposure = total_v_std;isp_context.ae_cur_gain = 0x010;isp_context.ae_cur_isp_dgain = 0x010;isp_context.awb_cur_rgain = 0x010;isp_context.awb_cur_bgain = 0x010;init_camif_isp_vip(); //配置相关IP//配置VDMA的帧缓存run_triple_frame_buffer(&cam_vdma_inst, XPAR_AXIVDMA_0_DEVICE_ID,CAM_WIDTH, CAM_HEIGHT, cam_buff, 0, 0, BOTH); //RAWrun_triple_frame_buffer(&lcd_vdma_inst, XPAR_AXIVDMA_1_DEVICE_ID,LCD_WIDTH, LCD_HEIGHT, lcd_buff, 0, 0, BOTH); //LCDrun_triple_frame_buffer(&dvi_vdma_inst, XPAR_AXIVDMA_2_DEVICE_ID,DVI_WIDTH, DVI_HEIGHT, dvi_buff, 0, 0, BOTH); //DVIprintf("initialize ok\r\n");unsigned prev_frame_cnt = 0, prev_cam_int = 0, prev_isp_int = 0, prev_vip_int = 0;u64 prev_time = 0;XTime_GetTime(&prev_time);printf("prev_time %llu\n", prev_time);while(1) {u64 now_time = 0;do {unsigned curr_isp_int = isp_frame_int;XTime_GetTime(&now_time);while (curr_isp_int == isp_frame_int && now_time < prev_time + COUNTS_PER_SECOND){XTime_GetTime(&now_time);}//if (isp_frame_int % 2 == 0) {isp_ae_handler(&isp_context); //AE处理isp_awb_handler(&isp_context); //AWB处理//}} while (now_time < prev_time + COUNTS_PER_SECOND);prev_time = now_time;#define CYCLE_DEBUG_PRINT 0unsigned frame_cnt = XIL_CAMIF_mReadReg(CAMIF_BASE, CAMIF_REG_FRAME_CNT);unsigned cam_int = cam_frame_int, isp_int = isp_frame_int, vip_int = vip_frame_int;
#if CYCLE_DEBUG_PRINTprintf("%lu x %lu, fps %u, interrupt camif %u, isp %u, vip %u\n",XIL_CAMIF_mReadReg(CAMIF_BASE, CAMIF_REG_WIDTH),XIL_CAMIF_mReadReg(CAMIF_BASE, CAMIF_REG_HEIGHT),frame_cnt - prev_frame_cnt,cam_int - prev_cam_int,isp_int - prev_isp_int,vip_int - prev_vip_int);
#endifprev_frame_cnt = frame_cnt;prev_cam_int = cam_int;prev_isp_int = isp_int;prev_vip_int = vip_int;#if CYCLE_DEBUG_PRINTunsigned i, sum;printf("AEC HIST [");for (sum = 0, i = 0; i < ISP_REG_STAT_AE_HIST_SIZE; i+=4) {unsigned data = XIL_ISP_LITE_mReadReg(ISP_BASE, ISP_REG_STAT_AE_HIST_ADDR+i);sum += data;if (i >= 96*4 && i < 96*4 + 8*4)printf("%u, ", data);}printf("] total %u\n", sum);//sum may be error, because of reading hist in vsync timeprintf("AWB HIST [");for (sum = 0, i = 0; i < ISP_REG_STAT_AWB_HIST_SIZE; i+=4) {unsigned data = XIL_ISP_LITE_mReadReg(ISP_BASE, ISP_REG_STAT_AWB_HIST_ADDR+i);sum += data;if (i >= 96*4 && i < 96*4 + 8*4)printf("%u, ", data);}printf("] total %u\n", sum);//sum may be error, because of reading hist in vsync time
#endifkey_control(); //按键控制}return 0;
}
配置中断
在Vivado的设计中,将所有IP生成的中断信号通过一个中断控制IP核集中为一个然后接到了PS端的PS-PL中断上
在parameters.h中生成了该IP接入的中断号及其屏蔽位,这些中断统一由AXI Interrupt Controller这个IP核接管,然后输出一路irq接到GIC中断控制器上
在中断配置中需要将该IP核的中断与GIC中断进行连接,sys_intr_init()函数内容如下:
static XIntc axiIntc;
static XScuGic intc;int sys_intr_init(void)
{XIntc_Initialize(&axiIntc, XPAR_AXI_INTC_0_DEVICE_ID); //初始化AXI_INTC器件XIntc_Start(&axiIntc, XIN_REAL_MODE);int status;XScuGic_Config *intc_cfg;intc_cfg = XScuGic_LookupConfig(XPAR_SCUGIC_SINGLE_DEVICE_ID); //初始化GIC中断控制器if (NULL == intc_cfg) {return XST_FAILURE;}status = XScuGic_CfgInitialize(&intc, intc_cfg, intc_cfg->CpuBaseAddress); //配置GICif (status != XST_SUCCESS)return XST_FAILURE;XScuGic_Connect(&intc, XPS_FPGA0_INT_ID, (Xil_ExceptionHandler) XIntc_InterruptHandler, (void*)&axiIntc); //连接中断XScuGic_Enable(&intc, XPS_FPGA0_INT_ID);Xil_ExceptionInit(); //使能异常相关Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)XScuGic_InterruptHandler, (void*)&intc);Xil_ExceptionEnable();return XST_SUCCESS;
}
AXI Interrupt Controller与GIC已经连接上了,后续具体IP的中断配置只需要通过AXI Interrupt Controller提供的驱动函数XIntc_Connect进行连接就行。
GPIO配置
ZYNQ7000系列芯片有54个MIO,隶属于PS部分,使用时不需要添加引脚约束,对PL部分是不可见的分为Bank0与Bank1,可以接许多外设比如UART、SPI或GPIO等,另外可以引脚复用。在使用PS端MIO时需要对GPIO进行配置,这个配置过程与普通的ARM芯片过程一致。
而EMIO从物理属性来说属于PL端IO,但PS端可以通过软件映射连线来对EMIO进行操作,且使用效果与MIO一致,EMIO需要管脚约束。
因此MIO编号范围为0~53,EMIO编号范围为54~117
在Vivado中,配置了使能了MIO(PS端)和EMIO(PL端),并将相关的管脚配置到的PS与PL端的按键和LED灯上。(EMIO就是可扩展的MIO,当与PS直接相连的MIO不够用时,可以使用EMIO做“扩展”。)
通过查找正点原子对应IO引脚分配表,可以直接得到PS端GPIO按键和LED灯的编号,因为PS端的MIO是无需管脚约束,管脚与MIO映射都是对应好的。
而PL端是需要管脚约束的,如下,EMIO的位宽配置了5(0~4),但EMIO的编号是从54开始的,因此依次对应下去就是54、55、56、57、58五个EMIO编号。
因此在Vitis中声明了如下GPIO编号
GPIO的配置如下:
static void gpio_init()
{XGpioPs_Config *gpiops_cfg_ptr; //PS 端 GPIO 配置信息//根据器件 ID 查找配置信息gpiops_cfg_ptr = XGpioPs_LookupConfig(GPIOPS_ID);//初始化器件驱动XGpioPs_CfgInitialize(&gpiops_inst, gpiops_cfg_ptr, gpiops_cfg_ptr->BaseAddr);//Gpio LEDXGpioPs_SetDirectionPin(&gpiops_inst, PS_LED0, 1); //设置为输出XGpioPs_SetDirectionPin(&gpiops_inst, PS_LED1, 1);XGpioPs_SetDirectionPin(&gpiops_inst, PL_LED0, 1);XGpioPs_SetDirectionPin(&gpiops_inst, PL_LED1, 1);XGpioPs_SetOutputEnablePin(&gpiops_inst, PS_LED0, 1); //使能使出XGpioPs_SetOutputEnablePin(&gpiops_inst, PS_LED1, 1);XGpioPs_SetOutputEnablePin(&gpiops_inst, PL_LED0, 1);XGpioPs_SetOutputEnablePin(&gpiops_inst, PL_LED1, 1);XGpioPs_WritePin(&gpiops_inst, PS_LED0, 1); //设置输出高电平XGpioPs_WritePin(&gpiops_inst, PS_LED1, 1);XGpioPs_WritePin(&gpiops_inst, PL_LED0, 1);XGpioPs_WritePin(&gpiops_inst, PL_LED1, 1);//Gpio KEYXGpioPs_SetDirectionPin(&gpiops_inst, PS_KEY0, 0); //设置为输入XGpioPs_SetDirectionPin(&gpiops_inst, PS_KEY1, 0);XGpioPs_SetDirectionPin(&gpiops_inst, PL_RESET, 0);XGpioPs_SetDirectionPin(&gpiops_inst, PL_KEY0, 0);XGpioPs_SetDirectionPin(&gpiops_inst, PL_KEY1, 0);
}
挂载SD卡文件系统
代码如下
static int platform_init_fs()
{FRESULT status;TCHAR *Path = "0:/";BYTE work[FF_MAX_SS];//注册一个工作区(挂载分区文件系统)//在使用任何其它文件函数之前,必须使用f_mount函数为每个使用卷注册一个工作区status = f_mount(&fatfs, Path, 1); //挂载SD卡if (status != FR_OK) {xil_printf("Volume is not FAT formated; formating FAT\r\n");return -1;
// //格式化SD卡
// status = f_mkfs(Path, FM_FAT32, 0, work, sizeof work);
// if (status != FR_OK) {
// xil_printf("Unable to format FATfs\r\n");
// return -1;
// }
// //格式化之后,重新挂载SD卡
// status = f_mount(&fatfs, Path, 1);
// if (status != FR_OK) {
// xil_printf("Unable to mount FATfs\r\n");
// return -1;
// }}return 0;
}//SD卡写数据
static int sd_write_data(char *file_name,u32 src_addr,u32 byte_len)
{FIL fil; //文件对象UINT bw; //f_write函数返回已写入的字节数//打开一个文件,如果不存在,则创建一个文件f_open(&fil,file_name,FA_CREATE_ALWAYS | FA_WRITE);//移动打开的文件对象的文件读/写指针 0:指向文件开头f_lseek(&fil, 0);//向文件中写入数据f_write(&fil,(void*) src_addr,byte_len,&bw);//关闭文件f_close(&fil);return 0;
}
挂载成功后,就可以直接使用f_open、f_write、f_read等函数直接操作SD卡的文件读写了,例如通过操作f_write将DDR中保存的raw图写到SD卡中。
配置OV5640摄像头
配置Ov5640摄像头需通过SCCB总线(可看作I2C总线的裁剪版),在Vivado设计中使用了一个AXI IIC IP核连接了OV5640的SCCB总线
因此首先就需要配置该IP,配置的过程与Xilinx IP通用配置流程差不多
static int iic_init(void)
{int rc;//通过axi_iic器件ID查找器件XIic_Config *cfg_ptr = XIic_LookupConfig(XPAR_AXI_IIC_DEVICE_ID);if (!cfg_ptr) {xil_printf("[iic] XIic_LookupConfig() failed\r\n");return XST_FAILURE;}//配置axi_iic IPrc = XIic_CfgInitialize(&iic, cfg_ptr, cfg_ptr->BaseAddress);if (rc != XST_SUCCESS) {xil_printf("[iic] XIic_CfgInitialize() failed\r\n");return rc;}//设置相关中断XIic_SetSendHandler(&iic, &iic, (XIic_Handler) SendHandler); //设置相关中断处理函数XIic_SetRecvHandler(&iic, &iic, (XIic_Handler) ReceiveHandler);XIic_SetStatusHandler(&iic, &iic, (XIic_StatusHandler) StatusHandler);#ifdef XPAR_INTC_0_DEVICE_ID//AXI IIC的中断连接在AXI INTC器件上,sys_intr_inst()获取其实例XIntc_Connect(sys_intr_inst(), XPAR_INTC_0_IIC_0_VEC_ID, (XInterruptHandler) XIic_InterruptHandler, &iic); //连接公共中断函数XIntc_Enable(sys_intr_inst(), XPAR_INTC_0_IIC_0_VEC_ID);
#elseXScuGic_Connect(sys_intr_inst(), XPAR_FABRIC_AXI_IIC_IIC2INTC_IRPT_INTR, (Xil_InterruptHandler)XIic_InterruptHandler, &iic);XScuGic_Enable(sys_intr_inst(), XPAR_FABRIC_AXI_IIC_IIC2INTC_IRPT_INTR);
#endifreturn XST_SUCCESS;
}static void SendHandler(XIic *InstancePtr)
{TransmitComplete = 0;
}static void ReceiveHandler(XIic *InstancePtr)
{ReceiveComplete = 0;
}static void StatusHandler(XIic *InstancePtr, int Event)
{}
设置ov5640 I2C从设备的地址,注意ov5640的器件地址只有7位,最低位是读写标志位。
常用I2C接口通用器件的器件地址是由种类型号,及寻址码组成的,共7位。
格式为:D7 D6 D5 D4 D3 D2 D1 D0
1、器件类型:D7-D4 共4位决定的。这是由半导公司生产时就已固定此类型的了,也就是说这4位已是固定的。
2、用户自定义地址码:D3-D1共3位。这是由用户自己设置的,通常的作法如EEPROM这些器件是由外部IC的3个引脚所组合电平决定的(用常用的名字如A0,A1,A2)。这也就是寻址码。所以为什么同一IIC总线上同一型号的IC只能最多共挂8片同种类芯片的原因了。
3、最低一位就是R/W位,,“0”表示写,“1”表示读(通常读写信号中写上面有一横线,表示低电平)。所以I2C设备通常有两个地址,即读地址和写地址。
而OV5640厂家提供的0x78地址实际上就是上述D7~D0,因此实际上它的器件地址应该是D7~D1,最低位在具体读写的时候设置。
而有些厂家提供I2C器件地址是【0 D7~D1】即没带读写控制位,因此需要仔细阅读DataSheet
#define OV5640_ID 0x78 //OV5640的ID
iic_set_slave_addr(OV5640_ID>>1); static int iic_set_slave_addr(u8 addr)
{int rc;rc = XIic_SetAddress(&iic, XII_ADDR_TO_SEND_TYPE, addr);if (rc != XST_SUCCESS) {xil_printf("XIic_SetAddress FAILURE\n");return rc;}return XST_SUCCESS;
}
OV5640内部寄存器是2字节位宽(16bit)的,因此需要封装好I2C读写16位寄存器的操作函数,源码如下所示
static volatile u8 TransmitComplete;
static volatile u8 ReceiveComplete;static u8 sccb_read_reg16(u16 addr )
{u8 TxBuffer[2] = {addr >> 8, addr & 0x00FF};u8 RxBuffer[1] = {0};int Status = iic_write_data(TxBuffer, sizeof(TxBuffer));if (Status == XST_SUCCESS) {Status = iic_read_data(RxBuffer, sizeof(RxBuffer));}if (Status != XST_SUCCESS) {return 0;}return RxBuffer[0];
}static int iic_write_data(const u8 *WriteBuffer, int len)
{int Status;unsigned timecnt = 1000000;TransmitComplete = 1;iic.Stats.TxErrors = 0;Status = XIic_Start(&iic);if (Status != XST_SUCCESS) {printf("iic_write_data XIic_Start fail %d\n", Status);return XST_FAILURE;}Status = XIic_MasterSend(&iic, (u8*)WriteBuffer, len);if (Status != XST_SUCCESS) {printf("iic_write_data XIic_MasterSend fail %d\n", Status);return XST_FAILURE;}while (((TransmitComplete) || (XIic_IsIicBusy(&iic) == TRUE)) && --timecnt) {usleep(1);//xil_printf(".");}if (TransmitComplete) {printf("iic_write_data timeout!!!\n");}Status = XIic_Stop(&iic);if (Status != XST_SUCCESS) {printf("iic_write_data XIic_Stop fail %d\n", Status);return XST_FAILURE;}return XST_SUCCESS;
}static int iic_read_data(u8 *BufferPtr, int len)
{int Status;unsigned timecnt = 1000000;ReceiveComplete = 1;Status = XIic_Start(&iic);if (Status != XST_SUCCESS) {printf("iic_read_data XIic_Start fail %d\n", Status);return XST_FAILURE;}Status = XIic_MasterRecv(&iic, BufferPtr, len);if (Status != XST_SUCCESS) {printf("iic_read_data XIic_MasterRecv fail %d\n", Status);return XST_FAILURE;}while (((ReceiveComplete) || (XIic_IsIicBusy(&iic) == TRUE)) && --timecnt) {usleep(1);//xil_printf(".");}if (ReceiveComplete) {printf("iic_read_data timeout!!!\n");}Status = XIic_Stop(&iic);if (Status != XST_SUCCESS) {printf("iic_read_data XIic_Stop fail %d\n", Status);return XST_FAILURE;}return XST_SUCCESS;
}
后续即可通过sccb_read_reg16()和sccb_write_reg16()函数对OV5640内部寄存器进行读写操作。
ov5640_init()函数内容如下:
int ov5640_init(u16 cmos_h_pixel, u16 cmos_v_pixel, u16* ptr_total_v_std)
{u16 cam_id = 0;usleep(20000); //OV5640上电到开始配置sccb至少等待20mssccb_init(); //初始化SCCB串行线(IIC的阉割版)//读OV5640摄像头IDcam_id = sccb_read_reg16(0x300b); //LSB 0x40cam_id |= sccb_read_reg16(0x300a) << 8; //MSB 0x56//cam_id = 0x5640;if(cam_id != 0x5640) { //获取到正确的OV5640 IDprintf("cam_id Invalid %04X\r\n", cam_id);return 1;}else{//先对寄存器进行软件复位,使寄存器恢复初始值//寄存器软件复位后,需要延时1ms才能配置其它寄存器sccb_write_reg16(0x3008,0x82); //Bit[7]:复位 Bit[6]:电源不休眠//延时1msusleep(1000);//根据输出的格式选择不同的配置if (1280 == cmos_h_pixel && 960 == cmos_v_pixel) {ov5640_init_96mhz_raw_960p_45fps();}if (1920 == cmos_h_pixel && 1080 == cmos_v_pixel) {ov5640_init_96mhz_raw_1080p_30fps();}if (2592 == cmos_h_pixel && 1944 == cmos_v_pixel) {ov5640_init_96mhz_raw_5mp_15fps();}if (ptr_total_v_std) {*ptr_total_v_std = total_v_std;}return 0;
}
开源项目中配置的RAW输出尺寸为2592x1944,因此会选择对应的函数进行配置。
OV5640内部寄存器的详解参考往期博客:摄像头配置——OV5640配置输出RAW格式-CSDN博客
ISP及图像处理IP配置
这里主要配置以下几个IP核
配置的内容为:复位相关IP——配置IP的中断屏蔽位——Disable彩条显示——使能ISP Pipeline中的处理模块——配置gama LUT查找表参数——配置2DNR降噪参数——配置BLC——配置AE统计区域——配置两个xil_vip IP核——结束IP核复位——连接并使能相关中断——重新配置相关中断屏蔽位
代码如下:
static void init_camif_isp_vip()
{//复位相关IPXIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_RESET, 1);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_RESET, 1);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_RESET, 1);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_RESET, 1);//设置IP的中断屏蔽XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_INT_MASK, 0xffff);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_INT_MASK, 0xffff);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_INT_MASK, 0xffff);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_INT_MASK, 0xffff);usleep(100000);//不使能彩条显示XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_COLORBAR_EN, 0);//配置ISP Pipeline中使能的模块unsigned int isp_top_en = 0;isp_top_en |= ISP_REG_TOP_EN_BIT_DPC_EN; isp_top_en |= ISP_REG_TOP_EN_BIT_BLC_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_BNR_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_DGAIN_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_DEMOSIC_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_WB_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_CCM_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_CSC_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_GAMMA_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_2DNR_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_EE_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_STAT_AE_EN;isp_top_en |= ISP_REG_TOP_EN_BIT_STAT_AWB_EN;XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_TOP_EN, isp_top_en); //使能ISP相关模块isp_init_gamma(ISP_BASE); //配置gama变换的LUTisp_init_2dnr(ISP_BASE); //配置2dNR相关参数XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_R, 19<<(ISP_BITS-8)); //配置黑电平相关参数XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_GR, 19<<(ISP_BITS-8));XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_GB, 19<<(ISP_BITS-8));XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_B, 19<<(ISP_BITS-8));// XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_R, 32<<(ISP_BITS-8)); //配置黑电平相关参数
// XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_GR, 32<<(ISP_BITS-8));
// XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_GB, 32<<(ISP_BITS-8));
// XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_BLC_B, 32<<(ISP_BITS-8));XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_NR_LEVEL, 2); //配置降噪强度XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_DGAIN_GAIN, 0x10);//1.0x 配置ISP Dgain参数XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_STAT_AE_RECT_X, CAM_WIDTH/4); //配置AE统计区域参数XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_STAT_AE_RECT_Y, CAM_HEIGHT/4);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_STAT_AE_RECT_W, CAM_WIDTH/2);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_STAT_AE_RECT_H, CAM_HEIGHT/2);//LCD VIPunsigned int vip_top_en = 0;//这里直接相除用的不是浮点数?unsigned scale_h = CAM_WIDTH / LCD_WIDTH;unsigned scale_v = CAM_HEIGHT / LCD_HEIGHT;//vip_top_en |= VIP_REG_TOP_EN_BIT_HIST_EQU_EN;//vip_top_en |= VIP_REG_TOP_EN_BIT_SOBEL_EN;vip_top_en |= VIP_REG_TOP_EN_BIT_YUV2RGB_EN; //YUV转RGBvip_top_en |= VIP_REG_TOP_EN_BIT_CROP_EN; //使能裁剪功能vip_top_en |= VIP_REG_TOP_EN_BIT_OSD_EN; //使能图层叠加if (scale_h > 1 && scale_v > 1) {vip_top_en |= VIP_REG_TOP_EN_BIT_DSCALE_EN; //若LCD显示尺寸小于ISP处理输出的图像尺寸说明需要缩小}XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_TOP_EN, vip_top_en);vip_init_osd(VIP_LCD_BASE, 16, 16, 0xff0000, 0x888888); //叠加显示的字符数据if (vip_top_en & VIP_REG_TOP_EN_BIT_DSCALE_EN) { //裁剪以显示在LCD中unsigned scale_val = scale_h < scale_v ? scale_h : scale_v; //最大化裁剪XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_X, (CAM_WIDTH-LCD_WIDTH*scale_val)/2);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_Y, (CAM_HEIGHT-LCD_HEIGHT*scale_val)/2);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_W, LCD_WIDTH*scale_val);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_H, LCD_HEIGHT*scale_val);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_DSCALE_H, scale_val-1);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_DSCALE_V, scale_val-1);} else {XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_X, (CAM_WIDTH-LCD_WIDTH)/2);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_Y, (CAM_HEIGHT-LCD_HEIGHT)/2);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_W, LCD_WIDTH);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_CROP_H, LCD_HEIGHT);}//DVI VIPvip_top_en = 0;scale_h = CAM_WIDTH / DVI_WIDTH;scale_v = CAM_HEIGHT / DVI_HEIGHT;//vip_top_en |= VIP_REG_TOP_EN_BIT_HIST_EQU_EN;//vip_top_en |= VIP_REG_TOP_EN_BIT_SOBEL_EN;//vip_top_en |= VIP_REG_TOP_EN_BIT_YUV444TO422_EN;vip_top_en |= VIP_REG_TOP_EN_BIT_YUV2RGB_EN;vip_top_en |= VIP_REG_TOP_EN_BIT_CROP_EN;vip_top_en |= VIP_REG_TOP_EN_BIT_OSD_EN;if (scale_h > 1 && scale_v > 1) {vip_top_en |= VIP_REG_TOP_EN_BIT_DSCALE_EN;}XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_TOP_EN, vip_top_en);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_HIST_EQU_MIN, 20);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_HIST_EQU_MAX, 200);vip_init_osd(VIP_DVI_BASE, 16, 16, 0xffffff, 0x888888);if (vip_top_en & VIP_REG_TOP_EN_BIT_DSCALE_EN) { //裁剪以显示在DVI中unsigned scale_val = scale_h < scale_v ? scale_h : scale_v;XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_X, (CAM_WIDTH-DVI_WIDTH*scale_val)/2);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_Y, (CAM_HEIGHT-DVI_HEIGHT*scale_val)/2);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_W, DVI_WIDTH*scale_val);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_H, DVI_HEIGHT*scale_val);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_DSCALE_H, scale_val-1);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_DSCALE_V, scale_val-1);} else {XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_X, (CAM_WIDTH-DVI_WIDTH)/2);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_Y, (CAM_HEIGHT-DVI_HEIGHT)/2);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_W, DVI_WIDTH);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_CROP_H, DVI_HEIGHT);}XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_RESET, 0); //复位结束XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_RESET, 0);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_RESET, 0);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_RESET, 0);printf("vi_reset = %08lX\n", XIL_CAMIF_mReadReg(CAMIF_BASE, CAMIF_REG_RESET));printf("isp_reset = %08lX\n", XIL_ISP_LITE_mReadReg(ISP_BASE, ISP_REG_RESET));printf("vip_reset = %08lX\n", XIL_VIP_mReadReg(VIP_LCD_BASE, VIP_REG_RESET));printf("vip_reset = %08lX\n", XIL_VIP_mReadReg(VIP_DVI_BASE, VIP_REG_RESET));camera_intr_init(); //使能并连接相关中断XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_INT_MASK, ~CAMIF_REG_INT_MASK_BIT_FRAME_DONE);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_INT_MASK, ~ISP_REG_INT_MASK_BIT_FRAME_START);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_INT_MASK, ~VIP_REG_INT_MASK_BIT_FRAME_DONE);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_INT_MASK, ~VIP_REG_INT_MASK_BIT_FRAME_DONE);
}
配置VDMA IP核的帧缓存地址
该项目使用了3个VDMA IP核
第一个VDMA IP核用来解耦摄像头输出和ISP处理输入,这两个模块所提供的时钟一个是96MHz,另一个是120MHz因此帧率是不一样的,需要VDMA作为缓冲区来解耦二者(在实际设计中这里应该使用相同的时钟),另外一个功能是将RAW保存在DDR中可以在Vitis中通过挂载SD卡并将RAW数据保存下来,便于ISP算法的开发和离线ISP调试。
第二个VDMA IP用于用于解耦xil_vip处理输出和axis_to_video输入,以匹配不同的帧率,LCD输出的帧率为800x480@60 FPS,而xil_vip处理输出的帧率为21帧左右【120MHz/(2884x1968)=21帧】,使用VDMA可以匹配不同的帧率,
第三个VDMA IP也是用于解耦视频处理输出和显示输出通路的,DVI显示以1280x720@60FPS输出,而xil_vip处理输出的帧率为21帧,通过VDMA即可配置两种不同的帧率。
static int cam_buff = XPAR_PS7_DDR_0_S_AXI_BASEADDR+0x4000000; //RAW8
static int lcd_buff = XPAR_PS7_DDR_0_S_AXI_BASEADDR+0x5000000; //RGB888
static int dvi_buff = XPAR_PS7_DDR_0_S_AXI_BASEADDR+0x6000000; //RGB888run_triple_frame_buffer(&cam_vdma_inst, XPAR_AXIVDMA_0_DEVICE_ID,CAM_WIDTH, CAM_HEIGHT, cam_buff, 0, 0, BOTH); //RAW
run_triple_frame_buffer(&lcd_vdma_inst, XPAR_AXIVDMA_1_DEVICE_ID,LCD_WIDTH, LCD_HEIGHT, lcd_buff, 0, 0, BOTH); //LCD
run_triple_frame_buffer(&dvi_vdma_inst, XPAR_AXIVDMA_2_DEVICE_ID,DVI_WIDTH, DVI_HEIGHT, dvi_buff, 0, 0, BOTH);
run_triple_frame_buffer()函数是三帧缓存VDMA读写驱动函数,配置好后VDMA会自动开始传输帧缓冲中的图像帧数据。
while循环
主函数的配置部分基本完成,接下来是while循环,在while循环中每隔1s计算一次自动曝光和自动白平衡值。
自动曝光(Auto Exposure,AE)
自动曝光是一个反馈控制系统,需要不断判断当前图像亮度是否为合适的亮度,并不断使图像亮度接近计算出来的最合适亮度,图像亮度需要控制摄像头的AEC相关寄存器和AGC相关寄存器,即亮度由曝光量(Exposure Time)和增益()决定
曝光量设置
OV5640的快门控制着曝光时间。快门的单位是行周期。快门值对每个输出分辨率都有限制。ov5640参考手册中手动配置曝光量的解释如下:
要手动更改曝光值,必须首先同时设置0x3503[0],其中0x3503[0]将启用手动曝光控制。在自动曝光模式下,寄存器0x350C/0x350D中的额外曝光值(大于1帧)会自动发生变化。在手动曝光模式下,这些寄存器将不会自动更改。在寄存器0x3500~0x3502中手动设置的曝光必须小于{0x380E,0x380F} + {0x350C,0x350D}中的最大曝光值。寄存器0x3500~0x3502中的曝光值以行*16为单位,低4位(0x3502[3:0])是行的分数位,{0x380E + 0x380F} + {0x350C,0x350D}中的最大值以行为单位。如果手动设置的曝光值小于一个预定义的帧周期(例如,15帧/秒中的1/15秒),则无需更改0x380E/0x380F。如果曝光值需要设定超过预定的帧周期,换句话说,如果帧周期需要延长以延长曝光时间,则需要先设定0x380E/0x380F的最大帧值,则曝光可以相应设置为寄存器0x3500~0x3502
因此曝光量的设置如下:
int cmos_set_exposure(unsigned exposure)
{static unsigned int cmos_exposure = 0x300;if (exposure == cmos_exposure) {return 0;}//printf("cmos_exposure %u -> %u\n", cmos_exposure, exposure);cmos_exposure = exposure;sccb_write_reg16(0x3500, (exposure>>12)&0x0ff);// Exposure [19:16]sccb_write_reg16(0x3501, (exposure>>4)&0x0ff);// Exposure [15:8]sccb_write_reg16(0x3502, (exposure&0x0f)<<4);// Exposure [7:0]if (exposure < total_v_std) {sccb_write_reg16(0x380e, total_v_std>>8); //垂直总像素大小高5位sccb_write_reg16(0x380f, total_v_std&0xff); //垂直总像素大小低8位} else {sccb_write_reg16(0x380e, exposure>>8); //垂直总像素大小高5位sccb_write_reg16(0x380f, exposure&0xff); //垂直总像素大小低8位}return 0;
}
增益设置
要手动更改增益,首先设置寄存器位0x3503[1]以启用手动控制,然后更改针对手动增益的0x350A/0x350B中的值,OV5640的最大增益为64倍。、
设置增益代码如下:
int cmos_set_gain(unsigned gain)
{static unsigned int cmos_gain = 0xa0;if (gain == cmos_gain) {return 0;}//printf("cmos_gain 0x%03X -> 0x%03X\n", cmos_gain, gain);cmos_gain = gain;sccb_write_reg16(0x350a, (gain>>8)&0x3);// Real gain[9:8]sccb_write_reg16(0x350b, gain&0x0ff);// Real gain[7:0]return 0;
}
因此通过调整曝光量和增益就能实现对自动曝光的设置,自动曝光的反馈控制如下:
void isp_ae_handler(IspContext *context)
{UINTPTR base = context->base;unsigned cur_exposure = context->ae_cur_exposure;unsigned cur_gain = context->ae_cur_gain;unsigned cur_isp_dgain = context->ae_cur_isp_dgain;unsigned tar_luma = context->ae_target_luminance;unsigned max_exposure = context->ae_max_exposure;unsigned max_gain = context->ae_max_gain;unsigned long long pix_cnt = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AE_PIX_CNT_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AE_PIX_CNT_H) << 32);unsigned long long luma_sum = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AE_SUM_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AE_SUM_H) << 32);unsigned tar_factor = (pix_cnt * tar_luma * 16 + luma_sum / 2) / luma_sum;tar_factor = tar_factor > 2*16 ? 2*16 : tar_factor;unsigned tar_exposure = cur_exposure;unsigned tar_gain = cur_gain;unsigned tar_isp_dgain = cur_isp_dgain;unsigned expo_diff, gain_diff, isp_dgain_diff;if (tar_factor > 18) {expo_diff = (((cur_exposure * tar_factor) >> 4) - cur_exposure) >> 1;expo_diff = expo_diff > 0 ? expo_diff : 1;gain_diff = (((cur_gain * tar_factor) >> 4) - cur_gain) >> 1;gain_diff = gain_diff > 0 ? gain_diff : 1;isp_dgain_diff = (((cur_isp_dgain * tar_factor) >> 4) - cur_isp_dgain) >> 1;isp_dgain_diff = isp_dgain_diff > 0 ? isp_dgain_diff : 1;if (cur_exposure < max_exposure) {if (cur_exposure + expo_diff > max_exposure)tar_exposure = max_exposure;elsetar_exposure = cur_exposure + expo_diff;}else if (cur_gain < max_gain) {if (cur_gain + gain_diff > max_gain)tar_gain = max_gain;elsetar_gain = cur_gain + gain_diff;}else if (cur_isp_dgain < 0x0ff) {if (cur_isp_dgain + isp_dgain_diff > 0x0ff)tar_isp_dgain = 0xff;elsetar_isp_dgain = cur_isp_dgain + isp_dgain_diff;}}else if (tar_factor < 14) {expo_diff = (cur_exposure - ((cur_exposure * tar_factor) >> 4)) >> 1;expo_diff = expo_diff > 0 ? expo_diff : 1;gain_diff = (cur_gain - ((cur_gain * tar_factor) >> 4)) >> 1;gain_diff = gain_diff > 0 ? gain_diff : 1;isp_dgain_diff = (cur_isp_dgain - ((cur_isp_dgain * tar_factor) >> 4)) >> 1;isp_dgain_diff = isp_dgain_diff > 0 ? isp_dgain_diff : 1;if (cur_isp_dgain > 16) {if (cur_isp_dgain < 16 + isp_dgain_diff)tar_isp_dgain = 16;elsetar_isp_dgain = cur_isp_dgain - isp_dgain_diff;}else if (cur_gain > 16) {if (cur_gain < 16 + gain_diff)tar_gain = 16;elsetar_gain = cur_gain - gain_diff;}else if (cur_exposure > 1) {if (cur_exposure < 1 + expo_diff)tar_exposure = 1;elsetar_exposure = cur_exposure - expo_diff;}}if (cur_exposure != tar_exposure || cur_gain != tar_gain || cur_isp_dgain != tar_isp_dgain) {printf("ALG_AEC: Exposure:%u Gain:0x%04X ISP_DGain:0x%02X (pixcnt:%llu sum_luma:%llu, avg_luma:%llu, target_luma:%u)\n",tar_exposure, tar_gain, tar_isp_dgain, pix_cnt, luma_sum, (luma_sum+pix_cnt/2)/pix_cnt, tar_luma);if (cur_exposure != tar_exposure)context->pfn_set_exposure(tar_exposure, context->priv_data);if (cur_gain != tar_gain)context->pfn_set_gain(tar_gain, context->priv_data);if (cur_isp_dgain != tar_isp_dgain)XIL_ISP_LITE_mWriteReg(base, ISP_REG_DGAIN_GAIN, tar_isp_dgain);context->ae_cur_exposure = tar_exposure;context->ae_cur_gain = tar_gain;context->ae_cur_isp_dgain = tar_isp_dgain;}
}
在主函数中,为isp_contex结构体中的函数指针赋值,然后在isp_ae_handler中即可实现对Ov5640的曝光量和增益进行手动设置。
isp_context.pfn_set_exposure = _set_exposure;isp_context.pfn_set_gain = _set_gain;
自动白平衡(Auto White Balance,AWB)
自动白平衡调整R通道和B通道的增益使得图像颜色在不同光照下都具有颜色恒常性。ISP内部设置了AWB增益的寄存器,通过AXI-Lite向ISP IP核写入R和B通道的增益即可实现自动白平衡,增益的计算也是在嵌入式中进行。
void isp_awb_handler(IspContext *context)
{UINTPTR base = context->base;unsigned cur_rgain = context->awb_cur_rgain;unsigned cur_bgain = context->awb_cur_bgain;unsigned long long pix_cnt = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_PIX_CNT_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_PIX_CNT_H) << 32);unsigned long long sum_r = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_R_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_R_H) << 32);unsigned long long sum_g = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_G_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_G_H) << 32);unsigned long long sum_b = XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_B_L) | ((unsigned long long)XIL_ISP_LITE_mReadReg(base, ISP_REG_STAT_AWB_SUM_B_H) << 32);unsigned tar_rgain = ((sum_g << 4) + sum_r / 2) / sum_r;unsigned tar_bgain = ((sum_g << 4) + sum_b / 2) / sum_b;if (cur_rgain < tar_rgain) {if (tar_rgain - cur_rgain >= 10) {tar_rgain = cur_rgain + (tar_rgain - cur_rgain) / 5;} else {tar_rgain = cur_rgain + 1;}} else if (cur_rgain > tar_rgain) {if (cur_rgain - tar_rgain >= 10) {tar_rgain = cur_rgain - (cur_rgain - tar_rgain) / 5;} else {tar_rgain = cur_rgain - 1;}}if (cur_bgain < tar_bgain) {if (tar_bgain - cur_bgain >= 10) {tar_bgain = cur_bgain + (tar_bgain - cur_bgain) / 5;} else {tar_bgain = cur_bgain + 1;}} else if (cur_bgain > tar_bgain) {if (cur_bgain - tar_bgain >= 10) {tar_bgain = cur_bgain - (cur_bgain - tar_bgain) / 5;} else {tar_bgain = cur_bgain - 1;}}if (cur_rgain != tar_rgain || cur_bgain != tar_bgain) {printf("ALG_AWB RGain:0x%02X BGain:0x%02X (pixcnt:%llu sum_rgb:%llu,%llu,%llu, avg_rgb:%llu,%llu,%llu)\n",tar_rgain, tar_bgain, pix_cnt, sum_r, sum_g, sum_b, sum_r/pix_cnt, sum_g/pix_cnt, sum_b/pix_cnt);XIL_ISP_LITE_mWriteReg(base, ISP_REG_WB_GGAIN, 0x10);XIL_ISP_LITE_mWriteReg(base, ISP_REG_WB_RGAIN, tar_rgain);XIL_ISP_LITE_mWriteReg(base, ISP_REG_WB_BGAIN, tar_bgain);context->awb_cur_rgain = tar_rgain;context->awb_cur_bgain = tar_bgain;}
}
3A算法在后续章节会进行补充。
按键控制
项目中按键控制是通过在while循环中轮询控制的,各个按键的功能如下:
PL_RESET:复位相关IP,重置视频处理Pipeline
PL_KEY0:保存原始二进制RAW图到SD卡中
PL_KEY1:保存DVI通路中二进制RGB图到SD卡中
PS_KEY0:未定义功能的按键
PS_KEY1:使能xil_camif IP 核中的彩条显示,按下后会将数据通路切换未彩条。
static void key_control()
{{static int reset = 0;if (reset != !XGpioPs_ReadPin(&gpiops_inst, PL_RESET)) { //PL RESET按键reset = !XGpioPs_ReadPin(&gpiops_inst, PL_RESET);printf("reset = %d\n", reset);XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_RESET, reset);XIL_ISP_LITE_mWriteReg(ISP_BASE, ISP_REG_RESET, reset);XIL_VIP_mWriteReg(VIP_LCD_BASE, VIP_REG_RESET, reset);XIL_VIP_mWriteReg(VIP_DVI_BASE, VIP_REG_RESET, reset);}}if (fs_init && 0 == XGpioPs_ReadPin(&gpiops_inst, PL_KEY0)) { //PL KEY0:保存RAW图数据printf("writing dump_cam_raw.raw ... ");Xil_DCacheInvalidateRange(cam_buff, CAM_WIDTH * CAM_HEIGHT);sd_write_data("dump_cam_raw.raw", cam_buff, CAM_WIDTH * CAM_HEIGHT);printf("done\n");}if (fs_init && 0 == XGpioPs_ReadPin(&gpiops_inst, PL_KEY1)) { //PL KEY1:保存RGB数据printf("writing dump_dvi_rgb888.rgb ... ");Xil_DCacheInvalidateRange(dvi_buff, DVI_WIDTH * DVI_HEIGHT * 3);sd_write_data("dump_dvi_rgb888.rgb", dvi_buff, DVI_WIDTH * DVI_HEIGHT * 3);printf("done\n");}if (fs_init && 0 == XGpioPs_ReadPin(&gpiops_inst, PS_KEY0)) { //PS KEY0}{static int colorbar_en = 0;if (colorbar_en != !XGpioPs_ReadPin(&gpiops_inst, PS_KEY1)) { //彩条显示colorbar_en = !XGpioPs_ReadPin(&gpiops_inst, PS_KEY1);printf("colorbar_en = %d\n", colorbar_en);XIL_CAMIF_mWriteReg(CAMIF_BASE, CAMIF_REG_COLORBAR_EN, colorbar_en);}}
}
开源ISP 嵌入式Vits端的搭建讲解结束,以上仅是跑通开源ISP,后续章节会对具体IP 和算法的硬件实现进行讲解,并改进相关算法。
基于zynq7020最终实现的ISP效果如下所示