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