1.场景
基于特权A7系列开发板,采用OV5640摄像头实时采集图像数据,并将其经过USB3.0传输到上位机显示。这是验证数据流能力的很好的项目。其中,用到的软件版本,如下表所示,基本的硬件情况如下。该项目对应FPGA工程源码,qt工程源码,以及USB固件的下载地址
软件 | 版本 |
QT | 5.15.0 |
Vivado | 2020.2 |
FX3 SDK | 1.3.4 |
器件 | 型号 | 厂商 |
FPGA | XLNX-XC7A35T-FTG256 | 赛灵思 |
DDR3 | MICT-MT41K128M16JT-96 | 镁光 |
USB控制芯片 | CYUSB3014-BZXI | 赛普拉斯 |
摄像头 | OV5640 | 豪威科技 |
2.架构
如图,所示为该小项目的基本架构。其硬件部分由摄像头采集模组、DDR3存储芯片、FPGA芯片、USB控制芯片组成。软件部分包括FPGA数据采集处理代码、CYUSB3014应用固件、QT上位机软件三个部分组成。其中,FPGA数据采集处理代码主要包含了摄像头配置模块(含图示SCCB驱动)、摄像头数据捕获模块、DDR3读写控制模块、图像数据编码模块、USB读写控制模块;CYUSB应用固件包括下行USB控制传输模式实现、上行块传输模式实现两个部分;QT上位机包括USB通信模块和图像数据解码模块,RGB565图像数据显示模块。
项目实现了将采集得到的图像数据,经过USB传输线,传输至上位机进行画面实时显示的功能。具体为:①FPGA摄像头配置模块通过SCCB接口驱动协议完成摄像头的寄存器配置,使之DVP接口产生正确信号;②FPGA摄像头数据捕获模块通过DVP接口采集实时图像数据;③FPGA DDR3读写控制模块将采集到底数据先存入DDR3缓冲,并将数据从DDR3读出至数据编码模块;④FPGA数据编码模块对图像数据进行编码,做加入帧头的操作(暂时没做);⑤FPGA USB读写控制模块产生USB控制芯片的读写信号,将图像数据发送到USB控制芯片;⑥CYUSB3014应用固件通过块传输的传输模式,将图像数据发送至上位机;⑦上位机USB通信模块用于读取USB的传输数据;⑧上位机图像解码模块用于将读取到的编码过的图像数据进行解码;⑨上位机RGB565图像显示模块将解码得到的像素数据进行显示。最终完成了将摄像头采集到的图像数据进行实时显示的功能。
3.FPGA技术点
3.1SCCB协议
与IIC总线非常接近,可参考FPGA IIC接口通信
SCCB(Serial Camera Control Bus,串行摄像头控制总线)是由OV(OmniVision的简称)公司定义和发展的三线式串行总线,该总线控制着摄像头大部分的功能,包括图像数据格式、分辨率以及图像处理参数等。该协议是兼容IIC协议的。为了节约管脚,OV公司采用的都是两线是类似于IIC的时钟线SIO_C和数据线SIO_D。
SSCB协议描述:
- Start传输开始标志:在时钟线SIO_C为高电平期间,SIO_D完成从高到低的跳变,代表着传输开始。
- Stop传输结束标志:在时钟线SIO_C为高电平期间,SIO_D由低到高完成跳变,代表着传输结束。
- 传输时序写:传输开始+ID Addr+R编号+数据DATA+传输结束
ID ADDRESS是由7位器件地址和1位读写控制位构成(0:写 1:读);上图中的第9位X表示Don’t Care(不必关心位),该位是由从机(此处指摄像头)发出应答信号来响应主机表示当前ID Address、Sub-address和Write Data是否传输完成,但是从机有可能不发出应答信号,因此主机(此处指FPGA)可不用判断此处是否有应答,直接默认当前传输完成即可.Sub-address为8位寄存器地址,在摄像头的数据手册中定义了0x00~0xAC共173个寄存器(大概数),有些寄存器是可改写的,有些是只读的,只有可改写的寄存器才能正确写入。WriteData为8位写数据,每一个寄存器地址对应8位的配置数据
4.传输时序读:传输开始+相1(ID ADDR)+相2(寄存器编号)+结束传输
- 第一部分是写器件地址和寄存器地址,即先进行一次虚写操作,通过这种虚写操作使地址指针指向虚写操作中寄存器地址的位置,当然虚写操作也可以通过前面介绍的写传输协议来完成。其中ID Address代表的是期间地址,bit0为0(写)。Sub—address代表是寄存器地址。
- 第二部分是读器件地址(ID Address bit0为1代表读)和读数据(Read Data),此时读取到的数据才是寄存器地址对应的数据。上图中的NA位由主机(这里指FPGA)产生,由于SCCB总线不支持连续读写,因此NA位必须为高电平。
3.2OV5640模组
与多数主流CMOS图像传感器一样,OV5640通过寄存器配置工作参数,方式就是SCCB协议,寄存器的内容很多,下面讲几个关键的,其余可查阅手册了解使用。
1.图像窗口设置
ISP 输入窗口设置(ISP Input Size)允许用户设置整个传感器显示区域(physical pixel size,2632*1951,其中 2592*1944 像素是有效的),开窗范围从 0*0~2632*1951 都可以任意设置。也就是上图中的 X_ADDR_ST(寄存器地址 0x3800、0x3801)、Y_ADDR_ST(寄存器地址 0x3802、0x3803)、X_ADDR_END(寄存器地址 0x3804、0x3805)和 Y_ADDR_END(寄存器地址 0x3806、0x3807)寄存器。该窗口设置范围中的像素数据将进入 ISP 进行图像处理。
ISP(Image Signal Processor),即图像处理,主要作用是对前端图像传感器输出的信号做后期处理,主要功能有线性纠正、噪声去除、坏点去除、内插、白平衡、自动曝光控制等,依赖于ISP才能在不同的光学条件下都能较好的还原现场细节,ISP技术在很大程度上决定了摄像机的成像质量。
预缩放窗口设置(pre-scaling size)允许用户在 ISP 输入窗口的基础上进行裁剪,用于设置将进行缩放的窗口大小,该设置仅在 ISP 输入窗口内进行 X/Y 方向的偏移。可以通过 X_OFFSET(寄存器地址 0x3810、0x3811)和 Y_OFFSET(寄存器地址 0x3812、0x3813)进行配置。
输出大小窗口设置(data output size)是在预缩放窗口的基础上,经过内部 DSP 进行缩放处理,并将处理后的数据输出给外部的图像窗口,图像窗口控制着最终的图像输出尺寸。可以通过 X_OUTPUT_SIZE(寄存器地址 0x3808、0x3809)和 Y_OUTPUT_SIZE(寄存器地址 0x380A、0x380B)进行配置。注意:当输出大小窗口与预缩放窗口比例不一致时,图像将进行缩放处理(图像变形),仅当两者比例一致时,输出比例才是 1:1(正常图像)。
右侧 data output size 区域,才是 OV5640 输出给外部的图像尺寸,也就是显示在显示器或者液晶屏上面的图像大小。输出大小窗口与预缩放窗口比例不一致时,会进行缩放处理,在显示器上面看到的图像将会变形。
实际配置的图像分辨率中,将3808=0x02 3809=0x80,0x0280=10’640; 380a=01 380b=e0,0x01e0=480。
2.输出图像格式设置
OV5640 支持多种不同的数据像素格式,包括 YUV(亮度参量和色度参量分开表示的像素格式)、RGB(其中 RGB 格式包含 RGB565、RGB555 等)以及 RAW(原始图像数据),通过寄存器地址 0x4300 配置成不同的数据像素格式。将寄存器 0x4300 寄存器的 Bit[7:4]设置成 0x6 ,将寄存器 0x4300 寄存器的 Bit[3:0]设置成0x1即可将输出像素格式设置为RGB565。
3.3DVP接口
DVP接口就是digital video port的简称,即数字视频端口。常见的视频采集接口有LVDS、MIPI,DVP是速度较慢的并行传输的接口,在高速的传感器上已经很少能见到该接口了,下面简单介绍一下DVP,下图为VGA帧的DVP接口时序图和接口管脚图。
说明1:不同的分辨率,上图中的每一个上升沿和每一个下降沿的时间是固定的,时间的单位是tp(指的是输出一个时钟像素数据所需要的时间),当配置摄像头的输出格式为8位raw时,tp=tPCLK,当为YUV\RGB的时候,tp=2*tPCLK。因此输出一帧图像所需要的时间与tPCLK是息息相关的。而决定像素时钟PCLK的是XCLK(控制芯片输出给驱动sensor的时钟)与寄存器的配置。因此,决定摄像头输出的帧频大小取决于XCLK与相关时钟倍频寄存器的配置。
说明2:时序关系,当场同步信号的有效信号到来时,开始一帧数据的传输,当行同步的有效信号到来时,开始一行数据的传输,每行数据之间有传输间隔,帧与帧之间除了包含480个行同步有效信号外还有其他的数据无效时间。
VSYNC:场同步信号,由摄像头输出,用于标志一帧数据的开始与结束。上图中VSYNC的高电平作为一帧的同步信号,在低电平时输出的数据有效。需要注意的是场同步信号是可以通过设置某寄存器进行取反的,即低电平同步高电平有效。
HREF/HSYNC:行同步信号,由摄像头输出,用于标志一行数据的开始与结束。上图中的HREF和HSYNC是由同一引脚输出的,只是数据的同步方式不一样。本次实验使用的是HREF格式输出,当HREF为高电平时,图像输出有效,可以通过寄存器进行配置。
D[9:0]:数据信号,由摄像头输出,在RGB格式输出中,只有高8位D[9:2]是有效的;
XCLK:控制芯片输出给驱动sensor的时钟。
PCLK:像素时钟,每一个时钟输出一个或者半个像素数据。
SCL、SDA :IIC(SCCB)用来配置sensor寄存器的接口。
3.4DDR3读写接口
参考DDR3应用总结
DDR3读写接口使用的赛灵思的MIG IP核。为了匹配DDR3的读写速度,使用两个FIFO用于写入DDR3和读出DDR3。由于该平台设计中,读出DDR3的速度大约是USB3.0的传输速度(实测平均速度在370MB/s),远远大于摄像头的数据输出速度(不超过50MB\s)。故使用乒乓操作切换两块缓冲区,每一块缓冲区大小为一帧图像的大小;对于写操作,写完第一块缓冲区bank1之后,就切换到第二块缓冲区bank2继续写;由于读取速度大于写入速度,因此当读完bank1之后,去当前没有写操作的bank中读取数据;可能产生同一帧图片读取多次的情况,但保证了用户在上位机正常观看画面。
3.5 USB3.0接口
参考USB3.0赛普拉斯方案
上行数据流使用块传输模式传输实时像素数据,实际测试。平均速度可达370MB\s。
4.QT技术点
多线程 参考QThread线程创建与使用
QT工程代码粘贴如下:
主线程图像显示代码Raw_Usb,app:
#include "raw_usb.h"
#include "ui_raw_usb.h"
#include <mythread.h>
#include <QThread>
#include <QDebug>
#include <QImage>
#include <QPixmap>
Raw_Usb::Raw_Usb(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Raw_Usb)
{
ui->setupUi(this);
ui->label_Show->resize(640,480);
connect(ui->startBtn,&QPushButton::clicked,this,&Raw_Usb::SlotStart);
connect(ui->stopBtn,&QPushButton::clicked,this,&Raw_Usb::SlotStop);
//链接线程中的数据来更新界面的图像显示
connect(thr,&MyThread::refreshUi,this,&Raw_Usb::responseUi,Qt::BlockingQueuedConnection);
}
Raw_Usb::~Raw_Usb()
{
delete ui;
}
void Raw_Usb::SlotStart()
{
thr->start();
ui->startBtn->setEnabled(false);
ui->stopBtn->setEnabled(true);
}
void Raw_Usb::SlotStop()
{
if(thr->isRunning())
{
thr->stop();
ui->startBtn->setEnabled(true);
ui->stopBtn->setEnabled(false);
}
}
//成熟槽函数,接收内存区指针来显示图片
void Raw_Usb::responseUi(uchar * addrBuf)
{
QPixmap pix;
QImage img=QImage(addrBuf,640,480,QImage::Format_RGB16);
pix=pix.fromImage(img);
ui->label_Show->setPixmap(pix);
}
子线程数据接收代码mythread.app
#include "mythread.h"
#include <QDebug>
#include <CyAPI.h>
#include <raw_usb.h>
#include <QFile>
#include <QImage>
#include <QPixmap>
#include <QByteArray>
#include <QTimer>
MyThread::MyThread(QObject *parent) :
QThread(parent)
{
stopped = false;
}
void MyThread::run()
{
/*创建设备对象,通过发送带参信号Cam_Data_Usb显示USB设备相关信息*/
CCyUSBDevice *Cam_Data_Usb=new CCyUSBDevice; //创建一个设备对象
/*******************USB设备相关准备工作**************************/
uchar *recieve_Buffer=new uchar[640*480*2]; //定义接收缓存,FPGA--上位机,输入in
LONG recieveLen =640*480*2; //定义函数参数字节长度
bool flag_Recieve; //定义bool型标志
UCHAR buik_Recieve = 0x81; //定义bulk传输发送接收端点
for (int i=0;i< Cam_Data_Usb->DeviceCount(); i++) {
Cam_Data_Usb->Open(i); //准备工作就绪,开启第i号设备
}
CCyUSBEndPoint *recieve_Endpt=Cam_Data_Usb->EndPointOf(buik_Recieve);//指定端点,使能传输
while(!stopped) {
flag_Recieve=recieve_Endpt->XferData(recieve_Buffer, recieveLen);//使能接收
if(flag_Recieve){
emit; refreshUi(recieve_Buffer);
}
}
delete Cam_Data_Usb;
delete []recieve_Buffer;//使用完之后释放内存空间
stopped = false;
}
void MyThread::stop()
{
stopped=true;
}
MyThread::~MyThread()
{
}