开源ISP介绍(2)————嵌入式Vitis搭建

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效果如下所示

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/486250.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能-自动驾驶领域

目录 引言自动驾驶与人工智能的结合为什么自动驾驶领域适合发表文章博雅智信的自动驾驶辅导服务结语 引言 自动驾驶技术的崛起是当代交通行业的一场革命。通过结合先进的人工智能算法、传感器技术与计算机视觉&#xff0c;自动驾驶不仅推动了技术的进步&#xff0c;也使得未来…

Kubernetes 深入浅出系列 | 容器编排与作业调度之Deployment

目录 概述Deployment 的更新原理实验 概述 Kubernetes 中&#xff0c;Deployment 控制器是用于管理应用程序生命周期的核心对象。Deployment 通过管理 ReplicaSet 来间接控制 Pod&#xff0c;确保在任何时刻都能维持指定数量的 Pod 副本。这种间接管理使得 Deployment 功能比 …

Java——异常机制(上)

1 异常机制本质 (异常在Java里面是对象) (抛出异常&#xff1a;执行一个方法时&#xff0c;如果发生异常&#xff0c;则这个方法生成代表该异常的一个对象&#xff0c;停止当前执行路径&#xff0c;并把异常对象提交给JRE) 工作中&#xff0c;程序遇到的情况不可能完美。比如…

如何查看电脑的屏幕刷新率?

1、按一下键盘的 win i 键&#xff0c;打开如下界面&#xff0c;选择【系统】&#xff1a; 2、选择【屏幕】-【高级显示设置】 如下位置&#xff0c;显示屏幕的刷新率&#xff1a;60Hz 如果可以更改&#xff0c;则选择更高的刷新率&#xff0c;有助于电脑使用起来界面更加流…

WAT绕过姿势

一.空格字符绕过 两个空格代替⼀个空格&#xff0c;⽤ Tab 代替空格&#xff0c;%a0空格 %20 %09 %0a %0b %0c %0d %a0 %00 /**/ /*!*/ select * from users where id1 /*!union*//*!select*/1,2,3,4; %09 TAB 键&#xff08;⽔平&#xff09;%0a 新建⼀⾏%0c 新的⼀⻚%0d …

Ubuntu 环境美化

一、终端选择 zsh 参考文章使用 oh-my-zsh 美化终端 Oh My Zsh 是基于 zsh 命令行的一个扩展工具集&#xff0c;提供了丰富的扩展功能。 先安装zsh再安装Oh My Zsh 1.zsh安装 sudo apt-get install zsh 2.设置默认终端为 zsh chsh -s /bin/zsh 3.安装 oh-my-zsh 官网&…

QT的ui界面显示不全问题(适应高分辨率屏幕)

//自动适应高分辨率 QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);一、问题 电脑分辨率高&#xff0c;默认情况下&#xff0c;打开QT的ui界面&#xff0c;显示不全按钮内容 二、解决方案 如果自己的电脑分辨率较高&#xff0c;可以尝试以下方案&#xff1a;自…

docker报错ls: cannot access SURF: Transport endpoint is not connected

docker挂载nfs文件夹/CMADAAS/DATA。它大部分时间都可用&#xff0c;只是有时会断开连接。重新挂载后&#xff0c;实际挂载的文件夹将再次可用。 问题是我将此文件夹放入docker卷中以使其可供我的应用程序使用&#xff1a;/SURF。当我启动容器时&#xff0c;该卷可用。 但是&…

AJAX三、XHR,基本使用,查询参数,数据提交,promise的三种状态,封装-简易axios-获取省份列表 / 获取地区列表 / 注册用户,天气预报

一、XMLHttpRequest基本使用 XMLHttpRequest&#xff08;XHR&#xff09;对象用于与服务器交互。 二、XMLHttpRequest-查询参数 语法: 用 & 符号分隔的键/值对列表 三、XMLHttpRequest-数据提交 核心步骤 : 1. 请求头 设置 Content-Type 2. 请求体 携带 符合要求 的数…

【Ubuntu】URDC(Ubuntu远程桌面助手)安装、用法,及莫名其妙进入全黑模式的处理

1、简述 URDC是Ubuntu远程桌面助手的简称。 它可以: 实时显示桌面:URDC支持通过Windows连接至Ubuntu设备(包括x86和ARM架构,例如Jetson系列、树莓派等)的桌面及光标。远程操控双向同步剪切板多客户端连接:同一Ubuntu设备最多可同时被三台Windows客户端连接和操控,适用于…

MVC基础——市场管理系统(一)

文章目录 项目地址一、创建项目结构1.1 创建程序以及Controller1.2 创建View1.3 创建Models层,并且在Edit页面显示1.4 创建Layou模板页面1.5 创建静态文件css中间件二、Categories的CRUD2.1 使用静态仓库存储数据2.2 将Categorie的列表显示在页面中(List)2.3 创建_ViewImport.…

KV Shifting Attention Enhances Language Modeling

基本信息 &#x1f4dd; 原文链接: https://arxiv.org/abs/2411.19574&#x1f465; 作者: Mingyu Xu, Wei Cheng, Bingning Wang, Weipeng Chen&#x1f3f7;️ 关键词: KV shifting attention, induction heads, language modeling&#x1f4da; 分类: 机器学习, 自然语言处…

spring下的beanutils.copyProperties实现深拷贝

spring下的beanutils.copyProperties方法是深拷贝还是浅拷贝&#xff1f;可以实现深拷贝吗&#xff1f; 答案&#xff1a;浅拷贝。 一、浅拷贝深拷贝的理解 简单说拷贝就是将一个类中的属性拷贝到另一个中&#xff0c;对于BeanUtils.copyProperties来说&#xff0c;你必须保…

沈阳工业大学《2024年827自动控制原理真题》 (完整版)

本文内容&#xff0c;全部选自自动化考研联盟的&#xff1a;《沈阳工业大学827自控考研资料》的真题篇。后续会持续更新更多学校&#xff0c;更多年份的真题&#xff0c;记得关注哦~ 目录 2024年真题 Part1&#xff1a;2024年完整版真题 2024年真题

Milvus Cloud 2.5:向量数据库的新里程碑与全文检索的革新

Milvus Cloud 2.5:向量数据库的新里程碑与全文检索的革新 各位同仁,大家好!我是大禹智库的向量数据库高级研究员王帅旭,也是《向量数据库指南》的作者。今天,我怀着激动的心情,为大家带来 Milvus Cloud 2.5 最新版本的深度解读。这个版本不仅标志着我们在向量数据库领域…

【金猿CIO展】复旦大学附属中山医院计算机网络中心副主任张俊钦:推进数据安全风险评估,防范化解数据安全风险,筑牢医疗数据安全防线...

‍ 张俊钦 本文由复旦大学附属中山医院计算机网络中心副主任张俊钦撰写并投递参与“数据猿年度金猿策划活动——2024大数据产业年度优秀CIO榜单及奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 数据要素时代&#xff0c;医疗数据已成为医院运营与决策的重要基石…

数据链路层总结

- - 链路、物理链路&#xff1a;两节点间物理线路&#xff08;有线、无线&#xff09;&#xff0c;中间没有任何其他的交换节点 数据链路、逻辑链路&#xff1a; 链路 协议需要的硬件、软件 网络适配器(网卡)&#xff1a;包含物理层、数据链路层 网络适配器软件驱动程…

回文串相关算法的总结

1. 题型 最长回文子串回文子串的个数 2. 暴力求解 枚举出所有的子串&#xff0c;然后再判断这些子串是否是回文。假设字符串的长度为 n。我们可以看出前者会用 O ( n 2 ) O(n^2) O(n2) 的时间枚举出所有的子串 s [ l i ⋯ r i ] s[l_i\cdots r_i] s[li​⋯ri​] 然后再用 …

ORB-SLAM2 ---- 非线性优化在SLAM中的应用(一)

文章目录 一、为什么要讲非线性优化二、运动模型和观测模型三、最大似然估计四、SLAM中最小二乘的应用五、总结 一、为什么要讲非线性优化 相信大家在学习一段时间SLAM后&#xff0c;会发现两个问题。第一个是代码能看懂&#xff0c;但是不知道为什么这样做&#xff08;特别是优…

论文概览 |《Urban Analytics and City Science》2023.03 Vol.50 Issue.3

本次给大家整理的是《Environment and Planning B: Urban Analytics and City Science》杂志2023年3月第50卷第3期的论文的题目和摘要&#xff0c;一共包括18篇SCI论文&#xff01; 论文1 A new kind of search 一种新型的搜索 【摘要】 ChatGPT (2022) was first launched o…