软件版本:VIVADO2017.4
操作系统:WIN10 64bit
硬件平台:适用米联客 ZYNQ系列开发板
米联客(MSXBO)论坛:www.osrc.cn答疑解惑专栏开通,欢迎大家给我提问!!
24.1 概述
MT9V034是美国Aptina公司推出的一款宽动态、低照度、具有全局快门的一款相机,常用于机器视觉领域的开发。本节课程将为大家讲解如何在ZYNQ平台下面驱动MT9V034,讲解了使用IIC对摄像头的分辨率进行修改。
MT9V034提供的测试历程输出显示分辨率是640X480。
24.2 MT9V034摄像头参数
MT9V034作为机器视觉领域的一款人气sensor,由于其宽动态、低照度、成像质量优秀等原因得以大量的采用。在我们的提供的源文件夹当中,包含了它的产品技术手册,MT9V034的主要参数如下表所示:
从表格中我们可以看到,MT9V034全分辨率输出752x480@60FPS,最大像素时钟27MHZ(典型值实际为24MHZ),10bit数据输出,功耗120uw(3.3V电源)。
MT9V034的驱动十分简单,如果不需要指定分辨率输出,以全分辨率752x480分辨率输出则不需要配置内部的寄存器,Sensor内部将以默认的配置输出。因此,在设计过程中,可以直接省略寄存器配置这一步,直接设计采集部分时序即可。但是,在实际应用中,有时候可能要对sensor的寄存器做一些相应的改动,因此本章加入ZYNQ IP自带的IIC接口,对sensor寄存器进行修改。为了验证IIC对寄存器配置有效,我们这里测试设置MT9V034输出的分辨率是640X480。
24.3 MT9V034图像采集传输系统
本章的框架大体思路是:MT9V034以默认分辨率(752x480@60FPS)采集输出,为了将采集的数据送至VDMA缓存,需要将视频流数据转换为AXI4-Stream信号,因此需要一个Video in to AXI4-stream IP完成这一功能,VDMA缓存之后,因为MT9V034输出的是一个不规则的分辨率,因此我们采取在1280x720图像尺寸的基础上截取640x480的区域来显示MT9V034采集的图像,这个功能是可以由PS端控制,最后由AXI4-Stream to video out重新由AXI信号转换为显示所需的视频流数据,这个IP所需要的时钟和时序分别由PLL和VTC提供。整体的电路框架如下图所示:
24.4 FPGA工程 IP配置
24.4.1采集输入IP
24.4.1.1 MT9V034时序
在MT9V034的数据手册中,找到其数据输出的时序描述:
MT9V034的数据输出与像素时钟pclk同步输出,当LV(实际为HS,MT公司将其称为Line Vaild信号)输出为高的时候,数据将在每个pclk同步输出。其时序图如下图所示:
24.6.1.2 MTSensor 自定义IP
MT9V034是属于输出灰度的Sensor(另外有输出Bayer的sensor),因此,将采集部分输入部分的8bit灰度数据用拼接为了24bit的数据即可。
接口说明
接口 |
I/O |
说明 |
cmos_clk_i |
I |
输入时钟,通常接24MHZ 或者25MHZ |
rst_n_i |
I |
低电平复位 |
cmos_pclk_i |
I |
摄像头时钟输入时钟 |
cmos_href_i |
I |
摄像头行同步输入, 高电平代表行数据有效 |
cmos_vsyns_i |
I |
摄像头场同步输入,上升沿代表场同步开始 |
cmos_data[7:0] |
I |
摄像头数据输入 |
cmos_xclk_o |
O |
摄像头工作,通常直接把cmos_clk_i连接到cmos_xclk_o |
rgb_o[23:0] |
O |
RGB输出数据 |
de |
O |
保留,暂时不配置 |
hs_o |
O |
输出的行数据有效 |
vs_o |
O |
输出的场同步信号 |
MSXBO_MTSensor IP源码
`timescale 1ns / 1ps // Company: cz123 milinker // Engineer: tjy // bbs:http://www.osrc.cn // taobao:http://osrc.taobao.com // Create Date: 2019/02/27 22:09:55 // Design Name: MSXBO_MTSensor // Module Name: MSXBO_MTSensor // Project Name: MSXBO_MTSensor // Copyright: msxbo Copyright (c) 2019 // Revision 0.01 - File Created // Additional Comments: // module MSXBO_MTSensor( input cmos_clk_i,//cmos senseor clock. input rst_n_i,//system reset.active low. input cmos_pclk_i,//input pixel clock. input cmos_href_i,//input pixel hs signal. input cmos_vsync_i,//input pixel vs signal. input [7:0]cmos_data_i,//data. output cmos_xclk_o,//output clock to cmos sensor. output [23:0] rgb_o, output de_o, output vs_o, output hs_o );
assign cmos_xclk_o = cmos_clk_i; reg cmos_href_r; reg cmos_vsync_r; reg [7:0]cmos_data_r;
always@(posedge cmos_pclk_i) if(!rst_n_i) begin cmos_data_r <= 24'd0; cmos_href_r <= 1'b0; cmos_vsync_r <= 1'b0; end else begin cmos_data_r <= cmos_data_i; cmos_href_r <= cmos_href_i; cmos_vsync_r<= cmos_vsync_i; end
assign de_o = cmos_href_r; assign vs_o = cmos_vsync_r; assign hs_o = cmos_href_r; assign rgb_o = {cmos_data_r,cmos_data_r,cmos_data_r};
endmodule |
24.4.2 VID in IP配置
添加Constant IP,设置为1,并与Video In to AXI4-Stream Ip的vid_io_in_ce管脚连接。
VID in IP配置
24.4.3 ZYNQ IP设置
需要在ZYNQ Processing System 中引出IIC接口,引出来的接口是PS端的IIC接口,是EMIO,因此可以分配给任意的EMIO使用这一接口。在BD文件当中,双击ZYNQ Processing System对其进行修改,按下图所示引出IIC接口。
勾选好了之后,按OK完成对IP的修改,之后再选中IIC接口,按Ctrl+T引出管脚,完成后的IP界面如下图所示:
24.4.4 VDMA配置
24.4.5 VTC IP设置
本章节采用1280*720@60FPS分辨率输出,故VTC部分的配置如下所示:
24.4.6 PLL时钟设置
本章节采用720P分辨率输出,故PLL时钟设置如下:
24.4.7 添加DUBUG信号
为了验证寄存器配置是否成功,我们在MTSensor这个模块中添加一些DEBUG信号方便我们调试验证。首先在BD文件中选中要观测的信号,然后右单击,选择debug选项(此处使用的工具为VIVADO 2017.4,其他版本根据情况选择相应的命令选项),如下图所示:
注意:调试观察信号的方法有很多种,可以使用ILA或system ILA,也可以添加Debug信号观察。
按照这种方法,再把rgb_o[23:0]也添加到要观测的信号中,并保存,然后和我们之前的设计一样,接下来就是Generate Product Output,生成顶层文件。然后如下图所示按钮开始综合:
综合完成后,选择第二项,如下图所示:
点击OK,然后等打开新的界面后,在最下方点击Debug选项(如果没有此选项,则点击Windows菜单下的debug命令)。选择Unsigned Debug Nets,右单击,选择Setup Debug命令,如下图所示:
单击Next按钮,直到出现如下图所示界面,按下图进行设置:
24.5 PS部分
在生成后bit文件后,将硬件导入到SDK,在SDK中生成一个新的空白工程,将提供例程中SDK工程的源文件复制,并粘贴到新建SDK工程。
24.5.1 IIC驱动
IIC的驱动比较简单,根据官方给我们提供的驱动进行修改就可以,在这里给大家介绍个小技巧,如何查看官方的驱动程序。首先我们在BSP里找到.mss文件。
双击此文件后,将跳转到此文件的页面中,如下图所示:
有些IP官方提供了驱动例程。上图红色箭头所示位置是ps端iic提供的驱动例程,点击这个Import Examples查看。下面是我们现在要用到的IIC的官方驱动例程,如下所示:
本章节编写的例程就是依据方框中的官方例程修改而来,感兴趣的可以看一下,这里就不多加以缀诉,修改后的IIC.h文件如下所示:
表5-1-1 IIC.h
#ifndef SRC_IIC_H_ #define SRC_IIC_H_
#include "xiicps.h" #include "xil_types.h"
#define Sensor_ID 0x48
struct config_table{ u16 addr; u8 data; };
int iic_init(void); int iic_read(XIicPs *InstancePtr,u16 addr,u8 *read_buf); int iic_write(XIicPs *InstancePtr,u16 addr,u8 data);
#endif /* SRC_IIC_H_ */ |
在上面的程序当中,Sensor_ID是摄像头的设备地址,此处需要注意的是XILINX的官方驱动中,设备地址是7bit模式,最后一位是读写位,将自动添加,因此我们定义的时候,要按照7bit模式定位,MT9V034的设备地址为0x90,将其转换为7bit时为:(1001 0000)2=(0100 1000)2=0x48。
在程序中,我们还定义了一个结构体config_table,它里面有两个元素addr和data,分别对应摄像头的寄存器地址和要写入的数据。此处定义的寄存器地址是16位,数据位为8位,实际上MT9V034的寄存器地址是8bit,数据位为16bit,这样定义的原因是考虑后期的兼容,而且MT9V034要配置的寄存器数量比较少,因此定义成16bit寄存器。
另外还定义了三个函数。通过其命令,可以知道这三个分别是IIC的初始化、读操作、写操作功能,具体在IIC.c中我们会进行详细的介绍。
然后再看到IIC.c,此文件就是IIC的驱动程序,如下表所示:
IIC.c
#include "IIC.h" #include "xiicps.h"
#define DeviceId XPAR_PS7_I2C_0_DEVICE_ID #define IIC_SCLK_RATE 40000 #define Config_done 0xffff
XIicPs Iic;
struct config_table MT9V034_config_table[]={ {0x0013,0x13}, {0x0301,0xe0}, {0x0402,0x80}, {Config_done,0x00} };
int iic_init(void) { int Status; XIicPs_Config *Config;
/* * Initialize the IIC driver so that it's ready to use * Look up the configuration in the config table, * then initialize it. */ Config = XIicPs_LookupConfig(DeviceId); if (NULL == Config) { return XST_FAILURE; }
Status = XIicPs_CfgInitialize(&Iic, Config, Config->BaseAddress); if (Status != XST_SUCCESS) { return XST_FAILURE; }
/* * Set the IIC serial clock rate. */ XIicPs_SetSClk(&Iic, IIC_SCLK_RATE);
return XST_SUCCESS;
}
int iic_write_byte(XIicPs *InstancePtr,u8 sensor_slave_addr,void *write_byte,int byte_num) { int Status;
Status = XIicPs_MasterSendPolled(InstancePtr, write_byte, byte_num,sensor_slave_addr);
if(Status != XST_SUCCESS){ xil_printf("IIC Send byte Failed!"); }
/* * Wait until bus is idle to start another transfer. */ while (XIicPs_BusIsBusy(InstancePtr));
return XST_SUCCESS; }
int iic_read_byte(XIicPs *InstancePtr,u8 sensor_slave_addr,void *write_byte,int byte_num) { int Status;
Status = XIicPs_MasterRecvPolled(InstancePtr, write_byte, byte_num,sensor_slave_addr);
if(Status != XST_SUCCESS){ xil_printf("IIC Read byte Failed!"); }
/* * Wait until bus is idle to start another transfer. */ while (XIicPs_BusIsBusy(InstancePtr));
return XST_SUCCESS; }
int iic_read(XIicPs *InstancePtr,u16 addr,u8 *read_buf) { u16 read_addr_ptr[2]; read_addr_ptr[0] = (addr >> 8); read_addr_ptr[1] = addr; if(iic_write_byte(InstancePtr,Sensor_ID,read_addr_ptr,2) != XST_SUCCESS) xil_printf("IIC Read ADDR Failed!"); if(iic_read_byte(InstancePtr,Sensor_ID,read_buf,1) != XST_SUCCESS) xil_printf("IIC Read data Failed!"); return XST_SUCCESS; }
int iic_write(XIicPs *InstancePtr,u16 addr,u8 data) { u8 buf[3]; buf[0] = (addr >> 8); buf[1] = addr; buf[2] = data; if(iic_write_byte(InstancePtr,Sensor_ID,buf,3) != XST_SUCCESS) xil_printf("IIC Write bytes Failed"); return XST_SUCCESS; }
int iic_config_init(void) { int Status; int i=0;
Status = iic_init(); if(Status != XST_SUCCESS) { xil_printf("I2C init Failed!"); }
while(MT9V034_config_table[i].addr != Config_done) { iic_write(&Iic,MT9V034_config_table[i].addr,MT9V034_config_table[i].data); i++; }
return XST_SUCCESS; } |
首先,我们看到下图部分:
这里定义了一个一维数组,类型为我们之前介绍过的结构体config_table,根据命名,实际上这就是一个寄存器配置表,从图上可以看到,配置的寄存器非常少,从MT9V034的数据手册中的寄存器列表里可知:
图中圈出来的部分就是我们程序中配置的部分,00为芯片的版本,03和04分别为行和宽,此处是设置为了640*480的输出。
Iic_init()这个函数比较简单,就是对IP进行初始化,初始化的过程也是遵照的XILINX的老套路,只是多了一个XIicPs_SetSClk()函数用于设置IIC的传输时钟速度。
iic_write_byte()这个函数实现了IIC写入字节的操作,其实现的主要功能由XIicPs_MasterSendPolled()这个函数实现,这个函数的原型如下所示:
我们可以看到,这是官方提供的一个函数,其具有四个参数,在函数的开始部分已经对其清楚的注释了,这四个参数分别为IIC的实例、发送数据的缓冲区、发送的字节数、从机的设备地址。因此我们重新封装的iic_write_byte的四个参数也是一样的意思。最后我们还观察到函数使用了一个检测忙状态函数来确保数据发送的完整性。
Iic_read_byte这个函数实现类型与写入字节函数基本一致,只是换了个功能子函数,这里就不再重复的介绍。
然后,我们再看到iic_write这个函数:
此函数也比较简单,此处定义了一个有三个8bit数据组成的一维数组,由于IIC最多一次发送8bit数据,因此将寄存器地址和数据分别拆分成了三个8bit的数据,最后使用之前介绍的iic_write_byte函数发送三次将一个完整的寄存器写入操作完成。
Iic_read功能与iic_write类似,因此不再重复的讲解。
最后就是我们摄像头初始化函数iic_config_init(),这个函数也比较简单,此处我们使用了一个while循环检测配置表中的最后一个元素,当配置完成后,程序跳出循环,完成写入寄存器操作,寄存器的写入操作由iic_write完成。
24.5.2 main函数
本章节程序对IIC和VDMA进行配置,程序比较简单。
main.c源码
#include "xil_io.h" #include "xparameters.h"
#define VDMA_BASEADDR XPAR_AXI_VDMA_1_BASEADDR
#define VIDEO_BASEADDR0 0x01000000 #define VIDEO_BASEADDR1 0x02000000 #define VIDEO_BASEADDR2 0x03000000
#define H_ACTIVE 1280 #define V_ACTIVE 720 #define H_STRIDE 1280
u32 i=0; #define SUM 3686400 //背景写黑 1280*720*4
void main(void) {
//设置内存中的背景 for(i=0;i Xil_Out16((VIDEO_BASEADDR0 + i), 0x00); Xil_Out16((VIDEO_BASEADDR1 + i), 0x00); Xil_Out16((VIDEO_BASEADDR2 + i), 0x00); }
iic_config_init();
//Xil_Out32((VDMA_BASEADDR + 0x030), 0x108B);// enable circular mode Xil_Out32((VDMA_BASEADDR + 0x030), 0x8B);// enable circular mode Xil_Out32((VDMA_BASEADDR + 0x0AC), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x0B0), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x0B4), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x0A8), (H_STRIDE*3)); // h offset (640 * 3) bytes Xil_Out32((VDMA_BASEADDR + 0x0A4), (H_ACTIVE*3)); // h size (640 * 3) bytes Xil_Out32((VDMA_BASEADDR + 0x0A0), V_ACTIVE); // v size (480) /********************从DDR读数据设置*****************/ Xil_Out32((VDMA_BASEADDR + 0x000), 0x8B); // enable circular mode Xil_Out32((VDMA_BASEADDR + 0x05c), VIDEO_BASEADDR0); // start address Xil_Out32((VDMA_BASEADDR + 0x060), VIDEO_BASEADDR1); // start address Xil_Out32((VDMA_BASEADDR + 0x064), VIDEO_BASEADDR2); // start address Xil_Out32((VDMA_BASEADDR + 0x058), (H_STRIDE*3)); // h offset (640 * 3) bytes Xil_Out32((VDMA_BASEADDR + 0x054), (H_ACTIVE*3)); // h size (640 * 3) bytes Xil_Out32((VDMA_BASEADDR + 0x050), V_ACTIVE); // v size (480)
while (1) ; }
|
24.6 验证测试
24.6.1 测试连接
开发板测试连接(MZ7XA、MZ7XB),如图所示:
24.6.2 测试结果
将程序下载入芯片之后,我们返回到VIVADO界面,然后单击Hardware Manager,之后单击Auto Connect自动扫描芯片。
扫描到芯片之后,打开界面如下图所示:
设置hs_o为高电平触发:
在SDK中点击运行后,在VIVADO中点击触发按钮:
从图中可以看到,摄像头每行的数据为640,证明我们的寄存器配置成功了,此时在屏幕上也显示了摄像头采集的图像,如下图所示: