开源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循环,内容如下

cpp 复制代码
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()函数内容如下:

cpp 复制代码
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的配置如下:

cpp 复制代码
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卡文件系统

代码如下

cpp 复制代码
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通用配置流程差不多

cpp 复制代码
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

cpp 复制代码
#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位寄存器的操作函数,源码如下所示

cpp 复制代码
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()函数内容如下:

cpp 复制代码
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核复位------连接并使能相关中断------重新配置相关中断屏蔽位

代码如下:

cpp 复制代码
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即可配置两种不同的帧率。

cpp 复制代码
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

因此曝光量的设置如下:

cpp 复制代码
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倍。、

设置增益代码如下:

cpp 复制代码
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;
}

因此通过调整曝光量和增益就能实现对自动曝光的设置,自动曝光的反馈控制如下:

cpp 复制代码
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的曝光量和增益进行手动设置。

cpp 复制代码
	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通道的增益即可实现自动白平衡,增益的计算也是在嵌入式中进行。

cpp 复制代码
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 核中的彩条显示,按下后会将数据通路切换未彩条。

cpp 复制代码
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效果如下所示

相关推荐
想成为风筝13 小时前
HALCON算子函数 Filter(过滤)(1)
图像处理·深度学习
CUGLin15 小时前
遥感图像处理二(ENVI5.6 Classic)
图像处理·人工智能·计算机视觉
GOTXX17 小时前
【自动驾驶】单目摄像头实现自动驾驶3D目标检测
图像处理·人工智能·python·目标检测·机器学习·3d·自动驾驶
小成晓程19 小时前
opencv——( 二值化函数 、自适应二值化函数、腐蚀函数、膨胀函数、仿射变换函数、透视变换函数)
图像处理·opencv·计算机视觉
Learning改变世界1 天前
Matlab(三)——图像处理
开发语言·图像处理·matlab
liuming19922 天前
Halcon中lines_gauss(Operator)算子原理及应用详解
图像处理·人工智能·深度学习·计算机视觉·视觉检测
AI程序员-李明宇2 天前
AI 的时代,新科技和新技术如何推动跨学科的整合?
图像处理·人工智能·开源·语音识别·ai编程·agi·杨立昆
GOTXX2 天前
实时视频插帧RIFE
图像处理·人工智能·python·计算机视觉·音视频·实时音视频·超分辨率重建
超甜的布丁mm2 天前
【深度学习】手机SIM卡托缺陷检测【附链接】
图像处理·深度学习·算法·智能手机·视觉检测·卷积神经网络
∑狸猫不是猫2 天前
数字图像处理(17):RGB与HSL互转
图像处理·计算机视觉·fpga开发