基于EBAZ4205矿板的图像处理:03摄像头采集HDMI输出视频图像
先看效果
项目简介
- 我是使用的EBAZ4205矿板,超级大电工的转接板和我自己买的一块没有xclk的ov5640完成的该项目,没有设备需自备。
- 我就是跑通了正点原子的开源代码(下文会提供路径和链接),我感觉直接把正点原子的代码下载下来,然后改一下引脚就可以。但是,我建议照着他们的代码和文档,自己搭一遍(我也是这么做的),因为一是我的vivado版本是2021.2,和他的vivado版本不同,二也是为了学习嘛。
- 本项目,设置OV5640输出分辨率为1280*720 ,PCLK = 72Mhz
项目内容
我的代码和正点原子的只有三处不同
- 许多IP版本不一致,所以建议自己搭一遍,然后照着正点原子的设置去改。
- video timing controller因IP版本不一致而带来的引脚不一致:我的vivado的vtc是6.2版本的,它多了一个sof_state,但经过我的实际测试,它空着不接即可。不会影响现有功能。
- 引脚定位不一致:下文我会给出我的xdc代码
代码
这里只提供xdc的代码,其他代码请直接看正点原子的开源代码
开源代码链接及其路径:领航者v2/2_Embedded_System/ZYNQ_SDK_7010/26_ov5640_hdmi
XDC
#----------------------摄像头接口的时钟---------------------------
#72M
create_clock -period 13.888 -name cam_pclk_0 [get_ports cam_pclk_0]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_0_IBUF_BUFG]
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets cam_pclk_0_IBUF]
set_property PACKAGE_PIN M17 [get_ports cam_vsync_0]
set_property IOSTANDARD LVCMOS33 [get_ports cam_vsync_0]
set_property PACKAGE_PIN J20 [get_ports cam_pclk_0]
set_property IOSTANDARD LVCMOS33 [get_ports cam_pclk_0]
set_property PACKAGE_PIN G19 [get_ports cam_pwdn_0]
set_property IOSTANDARD LVCMOS33 [get_ports cam_pwdn_0]
set_property PACKAGE_PIN N20 [get_ports cam_href_0]
set_property IOSTANDARD LVCMOS33 [get_ports cam_href_0]
set_property PACKAGE_PIN M18 [get_ports cam_rst_n_0]
set_property IOSTANDARD LVCMOS33 [get_ports cam_rst_n_0]
set_property PACKAGE_PIN H20 [get_ports {cam_data_0[7]}]
set_property PACKAGE_PIN K19 [get_ports {cam_data_0[6]}]
set_property PACKAGE_PIN J19 [get_ports {cam_data_0[5]}]
set_property PACKAGE_PIN L20 [get_ports {cam_data_0[4]}]
set_property PACKAGE_PIN L19 [get_ports {cam_data_0[3]}]
set_property PACKAGE_PIN L17 [get_ports {cam_data_0[2]}]
set_property PACKAGE_PIN L16 [get_ports {cam_data_0[1]}]
set_property PACKAGE_PIN M20 [get_ports {cam_data_0[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[7]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[5]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {cam_data_0[0]}]
set_property PACKAGE_PIN H16 [get_ports UART_0_0_rxd]
set_property PACKAGE_PIN H17 [get_ports UART_0_0_txd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_rxd]
set_property IOSTANDARD LVCMOS33 [get_ports UART_0_0_txd]
set_property PACKAGE_PIN B19 [get_ports {TMDS_0_tmds_data_p[2]}]
set_property PACKAGE_PIN C20 [get_ports {TMDS_0_tmds_data_p[1]}]
set_property PACKAGE_PIN D19 [get_ports {TMDS_0_tmds_data_p[0]}]
set_property PACKAGE_PIN F19 [get_ports TMDS_0_tmds_clk_p]
#cam_scl:
set_property -dict {PACKAGE_PIN P18 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[0]}]
#cam_sda:
set_property -dict {PACKAGE_PIN M19 IOSTANDARD LVCMOS33} [get_ports {emio_sccb_tri_io[1]}]
set_property PULLUP true [get_ports {emio_sccb_tri_io[1]}]
代码研读
既然是学习,就不能浅尝辄止,下面是一些我学习时的笔记。
API函数:run_vdma_frame_buffer()
这个函数将根据ID配置VDMA的读写路径,ID就是直接上xparameters.h找到你在vivado中配置的VDMA ID即可。
#define XPAR_AXIVDMA_0_DEVICE_ID XPAR_AXI_VDMA_0_DEVICE_ID
参数列表
位置 | 参数 | 含义 |
---|---|---|
1 | InstancePtr | 是XAxiVdma数据结构的句柄 |
2 | DeviceId | 当前VDMA的设备ID |
3 | hize | 帧的水平大小,以像素为单位 |
4 | vsize | 帧的垂直大小。 |
5 | buf_base_addr | VDMA写入和读取帧的缓冲区地址 |
6 | number_frame_count | 指定中断应该在多少帧之后到来 |
7 | enable_frm_cnt_intr | 置1时启用帧计数中断。 |
8 | select | 可设置读取通道、写入通道或读取和写入通道。 |
返回值
XST_SUCCESS 和 -XST_FAILURE
本项目设置
run_vdma_frame_buffer(&vdma, VDMA_ID, vd_mode.width, vd_mode.height,
frame_buffer_addr,0,0,BOTH);
其中,选择关闭帧计数中断,和中断计数,并将通道选择为both,即为读写通道
frame_buffer_adder的计算
这个是重点
unsigned int const frame_buffer_addr = (XPAR_PS7_DDR_0_S_AXI_BASEADDR
+ 0x1000000);
其中XPAR_PS7_DDR_0_S_AXI_BASEADDR比较好理解,它就是我们在建立BD文件时,为这个VDMA分配的帧缓存空间的起始地址
/* Definitions for peripheral PS7_DDR_0 */
#define XPAR_PS7_DDR_0_S_AXI_BASEADDR 0x00100000
#define XPAR_PS7_DDR_0_S_AXI_HIGHADDR 0x0FFFFFFF
而加上0x1000000是为了给ps端运行留一些内存,让他使用的。
API函数:DisplayInitialize()
这个API函数能够用来配置我们放置在BD中的动态时钟的,并且它能够通过当前的video timing contraller 的ID获知,当前使用的是什么格式的视频流,进而查表后输出相应的时钟频率。
最终要的是,它能够返回一个dispCtrl结构句柄,我们可以使用这个句柄完成各种操作。
DisplayInitialize(&dispCtrl, DISP_VTC_ID, DYNCLK_BASEADDR);
第二个参数就是当前的video timing contraller 的ID,然后他会把得到的视频格式信息写入dispCtrl结构句柄中,给后续流程使用。
typedef struct {
u32 dynClkAddr;/*Physical Base address of the dynclk core*/
XVtc vtc; /*VTC driver struct*/
VideoMode vMode; /*Current Video mode*/
double pxlFreq; /* Frequency of clock currently being generated */
DisplayState state; /* Indicates if the Display is currently running */
} DisplayCtrl;
typedef struct {
char label[64]; /* Label describing the resolution */
u32 width; /*Width of the active video frame*/
u32 height; /*Height of the active video frame*/
u32 hps; /*Start time of Horizontal sync pulse, in pixel clocks (active width + H. front porch)*/
u32 hpe; /*End time of Horizontal sync pulse, in pixel clocks (active width + H. front porch + H. sync width)*/
u32 hmax; /*Total number of pixel clocks per line (active width + H. front porch + H. sync width + H. back porch) */
u32 hpol; /*hsync pulse polarity*/
u32 vps; /*Start time of Vertical sync pulse, in lines (active height + V. front porch)*/
u32 vpe; /*End time of Vertical sync pulse, in lines (active height + V. front porch + V. sync width)*/
u32 vmax; /*Total number of lines per frame (active height + V. front porch + V. sync width + V. back porch) */
u32 vpol; /*vsync pulse polarity*/
double freq; /*Pixel Clock frequency*/
} VideoMode;
static const VideoMode VMODE_480x272 = {
.label = "480x272@60Hz",
.width = 480,
.height = 272,
.hps = 482,
.hpe = 523,
.hmax = 525,
.hpol = 0,
.vps = 274,
.vpe = 284,
.vmax = 286,
.vpol = 0,
.freq = 9
};
static const VideoMode VMODE_640x480 = {
.label = "640x480@60Hz",
.width = 640,
.height = 480,
.hps = 656,
.hpe = 752,
.hmax = 799,
.hpol = 0,
.vps = 490,
.vpe = 492,
.vmax = 524,
.vpol = 0,
.freq = 25.12
};
结构句柄dispCtrl
这个句柄相当重要,它的成员变量有:动态CLK的地址dynClkAddr,当前视频流vtc结构体vtc,视频格式vMode,当前视频流的pclk,和用于控制这个结构句柄指向的视频流的工作状态state
typedef struct {
u32 dynClkAddr;/*Physical Base address of the dynclk core*/
XVtc vtc; /*VTC driver struct*/
VideoMode vMode; /*Current Video mode*/
double pxlFreq; /* Frequency of clock currently being generated */
DisplayState state; /* Indicates if the Display is currently running */
} DisplayCtrl;
通过调整state就能控制视频流是否工作。
typedef enum {
DISPLAY_STOPPED = 0,
DISPLAY_RUNNING = 1
} DisplayState;
dispPtr->state = DISPLAY_RUNNING
API函数 DisplayInitialize
用于初始化dispCtrl结构句柄,注意它会缺省地将视频输出模式设置为800X480,所以需要用DisplaySetMode对其进行配置