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

news2024/12/13 4:43:02

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");
	else
		xil_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);   //设置曝光AEC
	cmos_set_gain(0x200);             //设置增益AGC

	isp_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;   //512
	isp_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);    //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);    //DVI

	printf("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 0
		unsigned 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_PRINT
		printf("%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);
#endif
		prev_frame_cnt = frame_cnt;
		prev_cam_int = cam_int;
		prev_isp_int = isp_int;
		prev_vip_int = vip_int;

#if CYCLE_DEBUG_PRINT
		unsigned 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 time
		printf("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
#endif
		key_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);    //配置GIC
    if (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 LED
	XGpioPs_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 KEY
	XGpioPs_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 IP
	rc = 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);
#else
	XScuGic_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);
#endif

	return 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至少等待20ms

	sccb_init();    //初始化SCCB串行线(IIC的阉割版)

    //读OV5640摄像头ID
    cam_id  = sccb_read_reg16(0x300b);       //LSB  0x40
    cam_id |= sccb_read_reg16(0x300a) << 8;  //MSB  0x56
	//cam_id = 0x5640;
    
    if(cam_id != 0x5640) { //获取到正确的OV5640 ID
    	printf("cam_id Invalid %04X\r\n", cam_id);
    	return 1;
    }else{
		//先对寄存器进行软件复位,使寄存器恢复初始值
		//寄存器软件复位后,需要延时1ms才能配置其它寄存器
		sccb_write_reg16(0x3008,0x82); //Bit[7]:复位 Bit[6]:电源不休眠

		//延时1ms
		usleep(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()
{
	//复位相关IP
	XIL_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变换的LUT
	isp_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 VIP
	unsigned 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转RGB
	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; //若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 VIP
	vip_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; //RGB888

run_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;
			else
				tar_exposure = cur_exposure + expo_diff;
		}
		else if (cur_gain < max_gain) {
			if (cur_gain + gain_diff > max_gain)
				tar_gain = max_gain;
			else
				tar_gain = cur_gain + gain_diff;
		}
		else if (cur_isp_dgain < 0x0ff) {
			if (cur_isp_dgain + isp_dgain_diff > 0x0ff)
				tar_isp_dgain = 0xff;
			else
				tar_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;
			else
				tar_isp_dgain = cur_isp_dgain - isp_dgain_diff;
		}
		else if (cur_gain > 16) {
			if (cur_gain < 16 + gain_diff)
				tar_gain = 16;
			else
				tar_gain = cur_gain - gain_diff;
		}
		else if (cur_exposure > 1) {
			if (cur_exposure < 1 + expo_diff)
				tar_exposure = 1;
			else
				tar_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.coloradmin.cn/o/2253874.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣-图论-2【算法学习day.52】

前言 ###我做这类文章一个重要的目的还是给正在学习的大家提供方向和记录学习过程&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会做的非常详细&#xff0c;只会提供思路和一些关键点&#xff0c;力扣上的大佬们的题解质量是非…

【PlantUML系列】序列图(二)

目录 一、参与者 二、消息交互顺序 三、其他技巧 3.1 改变参与者的顺序 3.2 使用 as 重命名参与者 3.3 注释 3.4 页眉和页脚 一、参与者 使用 participant、actor、boundary、control、entity 和 database 等关键字来定义不同类型的参与者。例如&#xff1a; Actor&…

如何利用内链策略提升网站的整体权重?

内链是谷歌SEO中常常被低估的部分&#xff0c;实际上&#xff0c;合理的内链策略不仅能帮助提升页面间的关联性&#xff0c;还可以增强网站的整体权重。通过正确的内链布局&#xff0c;用户可以更流畅地浏览你的网站&#xff0c;谷歌爬虫也能更快地抓取到更多页面&#xff0c;有…

zotero中pdf-translate插件和其他插件的安装

1.工具–》插件 2.找插件 3.点击之后看到一堆插件 4.找到需要的&#xff0c;例如pdf-translate 5.点击进入&#xff0c;需要看一下md文档了解下&#xff0c;其实最重要的就是找到特有的(.xpi file) 6.点击刚刚的蓝色链接 7.下载并保存xpi文件 8.回到zotero&#xff0c;安装并使…

Datax遇到的坑

公司数据中台产品&#xff0c;要使用airflow调datax任务实现离线作业的同步。 一、python版本问题 执行python ..datax.py .json时 报错 在运行 Python 脚本时&#xff0c;代码中使用了 Python 2 的 print语法&#xff0c;当前的环境是 Python 3。在 Python 3 中&#xff0…

容易被遗忘的测试用例

网络服务器启动了吗&#xff1f;应用程序服务器启动了吗&#xff1f;数据库上线了吗&#xff1f;测试数据是否预先加载到数据库中&#xff1f;每当我们准备开始测试应用程序时&#xff0c;一切都应该已经准备妥当。 然而&#xff0c;当测试开始后&#xff0c;我们可能会漏掉一些…

机器学习与深度学习-2-Softmax回归从零开始实现

机器学习与深度学习-2-Softmax回归从零开始实现 1 前言 内容来源于沐神的《动手学习深度学习》课程&#xff0c;本篇博客对于Softmax回归从零开始实现进行重述&#xff0c;依旧是根据Python编程的PEP8规范&#xff0c;将沐神的template代码进行简单的修改。近期有点懒散哈哈哈…

文本生成类(机器翻译)系统评估

在机器翻译任务中常用评价指标&#xff1a;BLEU、ROGUE、METEOR、PPL。 这些指标的缺点&#xff1a;只能反应模型输出是否类似于测试文本。 BLUE&#xff08;Bilingual Evaluation Understudy&#xff09;&#xff1a;是用于评估模型生成的句子(candidate)和实际句子(referen…

保护数字资产:iOS 加固在当前安全环境中的重要性

随着互联网和手机的发展&#xff0c;APP在我们的日常生活中已经变得无处不在&#xff0c;各大平台的应用程序成为了黑客攻击的主要目标。尤其在 2024 年&#xff0c;随着数据泄露和隐私侵犯事件的频发&#xff0c;手机应用的安全问题再次成为公众关注的焦点。近期&#xff0c;多…

基于HTML和CSS的校园网页设计与实现

摘要 随着计算机、互联网与通信技术的进步&#xff0c;Internet在人们的学习、工作和生活中的地位也变得越来越高&#xff0c;校园网站已经成为学校与学生&#xff0c;学生与学生之间交流沟通的重要平台&#xff0c;对同学了解学校内发生的各种事情起到了重要的作用。学校网站…

Secured Finance 推出 TVL 激励计划以及基于 FIL 的稳定币

Secured Finance 是新一代 DeFi 2.0 协议&#xff0c;其正在推出基于 FIL 的稳定币、固定收益市场以及具有吸引力的 TVL 激励计划&#xff0c;以助力 Filecoin 构建更强大的去中心化金融生态体系&#xff0c;并为 2025 年初 Secured Finance 协议代币的推出铺平道路。Secure Fi…

WebRover :一个功能强大的 Python 库,用于从 Web 内容生成高质量的数据集,专为训练大型语言模型和 AI 应用程序而设计。

2024-11-30 &#xff0c;由Area-25团队开发的一个专门用于生成高质量网络内容数据集的Python库。该数据集旨在为大型语言模型&#xff08;LLM&#xff09;和人工智能应用的训练提供丰富的数据资源。 数据集地址&#xff1a;WebRover Dataset|自然语言处理数据集|AI模型训练数据…

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器(模块化编写)

基于ZYNQ-7000系列的FPGA学习笔记7——按键控制蜂鸣器&#xff08;模块化编写&#xff09; 1. 实验要求2. 功能分析3. 模块设计4. 波形图4.1 按键消抖模块4.2 按键控制蜂鸣器模块 5.代码编写5.1 rtl代码5.2 测试代码 6. 代码仿真7. 添加约束文件并分析综合 在上期的内容中&…

Android 分词的两种方式

前言&#xff1a; 本文分别介绍了原生和三方(Jieba)两种分词方式的使用和注意事项 1、安卓原生BreakIterator分词 比较简单&#xff0c;但是效果不太行 /*** 功能&#xff1a;原生分词* 参数&#xff1a;text&#xff1a;需要分词的语句* 返回值&#xff1a;return&#xf…

python之Django连接数据库

文章目录 连接Mysql数据库安装Mysql驱动配置数据库信息明确连接驱动定义模型在模型下的models.py中定义表对象在settings.py 中找到INSTALLED_APPS添加创建的模型 测试testdb.py中写增删改查操作urls.py添加请求路径启动项目进行测试 连接Mysql数据库 安装Mysql驱动 pip inst…

JavaWeb学习(1)(同步或异步请求、依赖jQuery简单实现Ajax技术)

目录 一、Web的基本流程与页面局部刷新。 &#xff08;1&#xff09;web开发时基本流程。 &#xff08;2&#xff09;页面的"全局刷新"与"局部刷新"。 二、Ajax技术。 &#xff08;1&#xff09;基本介绍。 &#xff08;2&#xff09;基本特点。 1、与服务…

spark sql 环境安装,java 默认路径和 安装配置!

yum安装java 查看默认路径 update-alternatives --config java # Java 环境变量 export JAVA_HOME/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64/jreexport PATH$JAVA_HOME/bin:$PATH# Spark 环境变量 export SPARK_HOME/home/vagrant/soft/sparkexport PATH…

网络层总结

网络层任务&#xff1a; 分组 从源主机 经多个网络/多段链路 传输到目的主机 两种重要的功能&#xff1a; 分组转发、 路由选择 网络层向其上层提供的两种服务 —— 面向连接的虚电路服务、无连接的数据报服务 面向连接的虚电路服务&#xff1a; 可靠通…

python学习笔记15 python中的类

上一篇我们介绍了python中的库 &#xff0c;学习了一些常见的内置库。详细内容可点击–>python学习笔记14 python中的库&#xff0c;常见的内置库&#xff08;random、hashlib、json、时间、os&#xff09; 这一篇我们来看一下python中的类 创建一个类 class 类的名称():de…

MySQL数据集成到广东省追溯平台的销售信息同步方案

销售信息同步--外购上报流程2&#xff1a;MySQL数据集成到广东省特殊食品电子追溯平台 在现代数据驱动的业务环境中&#xff0c;确保销售信息的准确性和及时性至关重要。本文将分享一个具体的技术案例&#xff0c;展示如何通过轻易云数据集成平台&#xff0c;将MySQL中的销售信…