Davinci视频采集驱动文档

Davinci视频采集驱动文档

概述

Davinci的视频采集接口的驱动涉及到内容包括I2C,AD芯片,V4L2,视频采集等内容。下面主要分成视频采集接口描述,I2C和A/D芯片,V4L2采集驱动以及V4L2应用程序编程。

 

名词解释:

A-low:

YUV: 在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄像机进行摄像,然后把摄得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。

Auto focus:

Auto white balance: ut

Auto exposure:

ITU-R BT.656:

标准BT.656并行数据结构:

BT.656并行接口除了传输4:2:2的YCbCr视频数据流外,还有行、列同步所用的控制信号。如图所示,一帧图像数据由一个625行、每行1 728字节的数据块组成。其中,23~311行是偶数场视频数据,336~624行是奇数场视频数据,其余为垂直控制信号。

BT.656每行的数据结构如图所示

图中,每行数据包含水平控制信号和YCbCr--视频数据信号。视频数据信号排列顺序为Cb-Y-Cr-Y。每行开始的288字节为行控制信号,开始的4字节为EAV信号(有效视频结束),紧接着280个固定填充数据,最后是4字节的SAV信号(有效视频起始)

SAVEAV信号有3字节的前导:FFFF00;最后1字节XY表示该行位于整个数据帧的位置及如何区分SAVEAVXY字节各比特位含义见图。

图中,最高位bit7为固定数据1;F=0表示偶数场,F=1表示奇数场;V=0表示该行为有效视频数据,V=1表示该行没有有效视频数据;H=0表示为SAV信号,H=1表示为EAV信号;P3~P0为保护信号,由F、V、H信号计算生成;P3=V异或H;P2=F异或H;P1=F异或V;P0=F异或V异或H。

CCIR 656:

REC656:

Optical black clamp:

Low-Pass Filter: 

Culling: 

CFA:

Dark Fram write:

      SDTV/LDTV/HDTV:数字电视(Digital TV)包括数字HDTV、数字SDTV和数字LDTV三种。三者区别主要在于图像质量和信道传输所占带宽的不同。从视觉效果来看,数字 HDTV(1000线以上)为高清晰度电视(High Definition Television)的简称,图象质量可达到或接近35mm宽银幕电影的水平;SDTV(500-600线)即标准清晰度电视,主要是对应现有电视的分辨率量级,其图象质量为演播室水平;LDTV(200-300线)即普通清晰度电视,主要是对应现有VCD的分辨率量级。因为电视全数字化是今后的趋势,所以目前提HDTV以及SDTV、LDTV如无特别说明,均指全数字体制。


 一 视频采集接口VPFE

Davinci芯片提供一个视频采集接口VPFE主要可以接CMOS/CCD/video decoder等,还有一个视频后端处理接口VPBE主要是接视频输出设备。这里主要讨论视频采集接口VPFE。VPFE接口的结构框图如下图所示:

涉及的模块主要有:

  • l         CCDC控制器
  • l         Preview 预览引擎
  • l         Resizer模块
  • l         H3A模块
  • l         Histogram模块

 

1.       CCDC控制器 

CCDC控制器主要从CMOS/CCD中接收原始的视频数据,并且可以支持多种YUV视频格式。

2.       preview预览引擎

预览引擎主要是传输从CMOS/CCD中原始的视频数据到YCbCr 422的显示设备或者编码器。通常预览引擎的数据输出到外部的显示/压缩设备如NTSC/PAL模拟编码器或者LCD上。

3. Resize模块

  Resizer模块可以对图像进行裁剪和缩放功能。

二 I2C和A/D芯片

Davinci内置了I2C控制器和I2C总线,一般视频前端处理的A/D芯片都是挂载在I2C总线上,通过Davinci的I2C控制器对A/D芯片的I2C从设备进行读写操作。

Davinci的I2C控制器的内部框图如下图所示:

这里I2C只有2根信号线:SCL和SDA。SCL信号线产生clock时钟,SDA数据线通过内部的ICXSR/ICDXR和ICRSR/ICDRR发生和接收数据。

对于I2C在发送和接收数据的时候会产生START位和STOP位。当SCL为高时,SDA由高变低的时候产生START位;当SCL为低时,SDA由低变高的时候产生STOP位。另外I2C支持的数据格式有:7-bit地址模式,10-bit地址模式和Free data格式模式。

I2C外设可以产生下面几种中断事件:

I2C中断

发生事件

丢失仲裁中断AL

当I2C仲裁丢失或者非法的START/STOP位发生

无应答中断NACK

当I2C从接收器中没有接收到应答信号

寄存器可以访问中断ARDY

当先前的编程地址,数据和命令已经执行和状态位已经更新了,I2C产生ARDY中断。这个中断让CPU知道I2C寄存器已经可以访问了。

接收中断ICRINT,ICRRDY

当ICRSR寄存器接收的数据已经拷贝到ICDRR寄存器中的时候发送接收中断。可以让CPU来查询ICRRDY位来从ICDRR中读接收数据。

发送中断ICXINT和ICXRDY

可以让CPU来查询ICXRDY位来往ICDXR中发送数据。

停止中断SCD

当STOP位发生

AAS中断

当I2C发现它的slave地址或者地址全为0。

2.1 Davinci的I2C控制器驱动

Davinci的I2C控制器驱动包括I2C总线注册,I2C总线读写操作,I2C总线中断处理几个部分,主要代码集中在/driver/i2c/busses/i2c-davinci.c,

把I2C控制器注册到I2C总线上是把davinci的I2C适配器的数据结构注册到linux 内核的I2C子系统中。I2C适配器的数据结构struct i2c_davinci_adap

static struct i2c_adapter i2c_davinci_adap = {

         .owner = THIS_MODULE,

         .name = "DAVINCI I2C adapter",

         .id = I2C_ALGO_EXP,

         .algo = &i2c_davinci_algo,

         .algo_data = NULL,

         .client_register = NULL,

         .client_unregister = NULL,

};
static int __init i2c_davinci_init(void)

{

…………………….

init_waitqueue_head(&i2c_davinci_dev.cmd_wait);

status = (int)request_region(I2C_BASE, I2C_IOSIZE, MODULE_NAME);

i2c_set_adapdata(&i2c_davinci_adap, &i2c_davinci_dev);

       status = i2c_add_adapter(&i2c_davinci_adap);

request_irq(IRQ_I2C, i2c_davinci_isr, 0, "i2c",&i2c_davinci_dev);

   …………………..

driver_register(&davinci_i2c_driver)

       platform_device_register(&davinci_i2c_device)

return 0;

}

1. 初始化等待队列i2c_davinci_dev.cmd_wait

2. 为I2C寄存器组分配地址空间。这里可能有问题????????

3. 把 davinci的I2C的适配器挂入linux内核的I2C子系统中。

4. 注册I2C中断。

5. 注册linux设备模型。

 

I2C的读写操作主要的数据结构struct i2c_davinci_alog:

static struct i2c_algorithm i2c_davinci_algo = {

         .name = "DAVINCI I2C algorithm",

         .id = I2C_ALGO_EXP,

         .master_xfer = i2c_davinci_xfer,

         .smbus_xfer = NULL,

         .slave_send = NULL,

         .slave_recv = NULL,

         .algo_control = NULL,

         .functionality = i2c_davinci_func,

};

I2C的主要操作在i2c_davinci_xfer函数中。这个函数主要实现代码片断如下:

static int i2c_davinci_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
…………………..

if ((ret = i2c_davinci_wait_for_bb(1, adap)) < 0)

              return ret;
…………………..
for (count = 0; count < num; count++) {

          i2c_davinci_xfer_msg(adap, &msgs[count],(count == (num - 1)));

   }
…………………..
}

1. 判断I2C是否在忙

2. 通过i2c_davinci_xfer_msg函数来实现。 

i2c_davinci_xfer > i2c_davinci_xfer_msg

static int 2c_davinci_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, int stop)
{
      dev->regs->icsar = msg->addr; -----------------------------------1
     if (msg->len == 0) {  ------------------------------------------------2
              dev->buf = &zero_byte;
              dev->buf_len = 1;
       } else {
              dev->buf = msg->buf;

              dev->buf_len = msg->len;
       }
       dev->regs->iccnt = dev->buf_len;
     at = dev->regs->icivr; ----------------------------------------------------3
     if (msg->flags & I2C_M_RD) -------------------------------------------------------4
              dev->regs->icimr |= DAVINCI_I2C_ICIMR_ICRRDY_MASK;
       else
              dev->regs->icimr |= DAVINCI_I2C_ICIMR_ICXRDY_MASK;
     dev->regs->icmdr = flag;     ---------------------------------------  5
   wait_event_timeout (dev->cmd_wait, dev->cmd_complete, DAVINCI_I2C_TIMEOUT);---6
……………..

}

1. 设置slave地址。

2. 设置数据包的长度。

3. 读ICIVR中断向量寄存器表示清0。

4. 使能接收和发送寄存器。

5. 设置ICMDR寄存器。设置IRS,MST和STT位,具体含义看datasheet。

6. 等待数据读写完成。

当读写的数据完成的时候,会产生中断,进入中断服务例程ISR。

static irqreturn_t 2c_davinci_isr(int this_irq, void *dev_id, struct pt_regs *reg)
{
while ((stat = dev->regs->icivr) != 0) { ------------------------------------------------1
        switch(stat) {
case DAVINCI_I2C_ICIVR_INTCODE_RDR:  -----------------------------------2
                     if (dev->buf_len) {
                            *dev->buf++ = dev->regs->icdrr;
                            dev->buf_len--;
                            if (dev->buf_len) {
                                   continue;
                            } else {
                                   dev->regs->icimr &= ~DAVINCI_I2C_ICIMR_ICRRDY_MASK;
                            }
                     }
                     break;
              case DAVINCI_I2C_ICIVR_INTCODE_TDR: -----------------------------------3
                     if (dev->buf_len) {
                            dev->regs->icdxr = *dev->buf++;
                            dev->buf_len--;
                            if (dev->buf_len)

                                   continue;
                            else {
                                   dev->regs->icimr &= ~DAVINCI_I2C_ICIMR_ICXRDY_MASK;

                            }
                     }
                     break;
case DAVINCI_I2C_ICIVR_INTCODE_RAR:  --------------------------------------------4
                        /*i2c_warn("i2c: RAR detected");*/
                        dev->regs->icstr |= DAVINCI_I2C_ICSTR_ARDY_MASK;
                     i2c_davinci_complete_cmd(dev);
                        break;
 }
}
}

1. 当有中断发生时候,icivr中断向量寄存器会告诉我们发生了那个中断。

2. 当接收中断发生的时候,从ICDRR寄存器中取数据,取完后清中断请求。

3. 发送中断处理。

4. 这里的中断说明先前的寄存器读写已经完成。这时候会唤醒等待队列i2c_davinci_dev.cmd_wait.

注意:中断处理程序要在ARDY中断(或者异常中断发生)才会唤醒等待队列。

2.2 ADV7180芯片驱动

ADV7180芯片的驱动主要是通过I2C来操作ADV7180本身的寄存器来完成工作的。首先需要把ADV7180做为I2C的从设备注册到I2C总线上。

ADV7180芯片寄存器的读写都是通过I2C来操作的。I2C读写操作如下图所示:

对于写操作,开始位START后,先写入7bit slave地址和一个LSB位,在ACK答应位之后,写入SUB地址和数值,止到STOP位。也就是一个START位后,写入地址和n个数据止到STOP位。

对于读操作,开始位START后,先写入7bit slave地址和一个LSB位,在ACK应答位之后,写入SUB ADDR和等待一个应答位。在开始另个START位之后,才是读出的数据。

static int i2c_write_reg(struct i2c_client *client, u8 reg, u8 val)
{

       int err = 0;

       struct i2c_msg msg[1];

       unsigned char data[2];

       if (!client->adapter) {

              err = -ENODEV;

       } else {

              msg->addr = client->addr;

              msg->flags = 0;

              msg->len = 2;  //这里长度是2,即1个START位,发送2个数据,一个是reg,另一个是val。

              msg->buf = data;

              data[0] = reg;

              data[1] = val;

              err = i2c_transfer(client->adapter, msg, 1);

       }

       return err;
}

static int i2c_read_reg(struct i2c_client *client, u8 reg, u8 * val)
{

       int err = 0;

       struct i2c_msg msg[1];

       unsigned char data[1];

       if (!client->adapter) {

              err = -ENODEV;

       } else {

              msg->addr = client->addr;

              msg->flags = 0;

              msg->len = 1;

              msg->buf = data;

              data[0] = reg;

              err = i2c_transfer(client->adapter, msg, 1);

              if (err >= 0) {
                     msg->flags = I2C_M_RD;

                     err = i2c_transfer(client->adapter, msg, 1);

                     if (err >= 0) {

                            *val = data[0];
                     }

              }
       }
       return err;
}

三  V4L2采集驱动

      对于视频采集,为了让上层应用程序能兼容很多不同的采集卡或者摄像头等设备,内核提供了一组同一的标准接口API,即V4L接口。应用程序只需要按照V4L接口标准来编写程序,那么就可以支持所以采用V4L驱动的设备。V4L是一套针对视频采集,音频radio等音视频设备的标准API。

在V4L的基础上,从Linux2.5内核开始发展了V4L2标准。V4L2的可扩展性和灵活性都得到大大的提高,并且能够支持更多的设备。V4L2对VL4进行了彻底的改造,很多关键的API发生了变化,所以V4L2和V4L不兼容。

Davinci的视频采集驱动采用V4L2 API来编写驱动。V4L2 API支持3种采集的方法:read/write, MMAP和USER POINTERS。我们的驱动只支持mmap的方式。

V4L2 API除了传统的字符设备的方法集外,还有很多的操作是特定的ioctl操作来实现的。对于mmap采集来说,比较常用的几个ioctl操作如下:

Ioctl操作

用法

VIDIOC_QUERYCAP

查询设备参数

VIDIOC_CROPCAR

设备图像裁剪和缩放的能力

VIDIOC_S_CROP

获取或者设置当前裁剪方框的大小

VIDIOC_REQBUFS

初始化mmap映射的内存

VIDIOC_QUERYBUFS

内存初始化后查询buffer的状态

VIDIOC_QBUFS

把buffer挂入驱动incoming队列

VIDIOC_DQBUFS

从驱动outcoming队列中取buffer

VIDIOC_STREAMON

开始capture

VIDIOC_STREAMOFF

结束capture

1.  VIDIOC_REQBUFS初始化buffer

 这个ioctl操作主要目的是初始化mmap需要用到的buffers。V4L2为了简化驱动编程,抽象了一个buffer的管理buffer的文件/driver/video_buffer.c 文件中。这里会调用到video_buffer.c文件中相关函数来完成buffers的初始化。

l         初始化1个video_queue->stream队列。

l         初始化1个vpfe->dma_queue队列。

l         通过__get_free_pages来配置buffers,这里要分配几个buffer,可以由用户程序来控制。

l         分配struct videobuf_buffer,n个地址空间。

2.  Mmap操作

这里mmap操作调用video-buffer.c文件中video_mmap_mapper()函数来实现。对于mmap操作,驱动程序需要为映射的地址范围建立合适的页表,这里主要通过 remap_pfn_range和nopage操作来实现。

3.  VIDIOC_QBUF

这个ioctl操作主要实现初始化的时候把空buffers挂入queue->strream队列中和vpfe->dma_queue队列中;当应用程序读了1个buffer数据之后,把这个buffer归还。

4.  VIDIOC_STEAMON:

这个ioctl操作主要是开始capture操作。

l         首先调用videobuf_streamon()函数。遍历queue->stream队列,找到组成队列的大结构struct videobuf_buffer。

l         从vpfe->dma_queue队列中取出队列成员vpfe->nextFrm = vpfe->curFrm。 

l         设置vpfe->curFrm->state = STATE_ACTIVE。

l         设置AD芯片和配置CCDC控制

l         设置CCDC的输入地址为vpfe->curFrm->boff

l         使能CCDC控制器。

5.  VIDIOC_DQBUF:

应用程序通过这个ioctl调用来获取那个buffer数据已经准备好了,从驱动返回v4l2_buffer->index值。

l         从queue->stream队列中取一个video_buffer成员。

l         通过videobuf_waiton函数进行阻塞等待 

l         中断处理之后,把这个video_buffer成员从队列中删除,然后把video_buffer->index返回用户空间。

6.  中断处理

中断处理要注意“场“的概念。因为视频输入源的摄像头有分PROGRESSIVE和INTERLACED之分。PROGRESSIVE的摄像头一帧只有一场(field),而INTERLACED的摄像头一帧有2场,其中field=1即偶场,先执行,然后才是field=0的奇场,当奇场完成是需要唤醒等待队列,然后VIDIOC_DQBUF调用返回参数完成数据的读取。

       下面是INTERLACED的摄像头的中断处理过程:

if (fid == 1) {
                     if (!list_empty(&vpfe->dma_queue)

                         && vpfe->curFrm == vpfe->nextFrm) {

                            vpfe->nextFrm = list_entry(vpfe->dma_queue.next, --------------------1

                                   struct videobuf_buffer, queue);

                            list_del(&vpfe->nextFrm->queue);

                            vpfe->nextFrm->state = STATE_ACTIVE;

                            ccdc_setfbaddr(

                                   (unsigned long)vpfe->nextFrm->boff);

                     }
    if (fid == 0) {                     -------------------------------------------------2

                     if (vpfe->curFrm != vpfe->nextFrm) {

                            vpfe->curFrm->state = STATE_DONE; 

                            wake_up_interruptible(&vpfe->curFrm->done);

                            vpfe->curFrm = vpfe->nextFrm;

                     }

1.       一帧数据,一般先是field=1的偶场先完成。在偶场中,取出下一帧buffer,并设置下一帧的ccdc输出地址。

2.       接着是field=0的奇场完成,这时候把当前buffer的状态标记为STATE_DONE,并唤醒等待队列,这时候VIDIOC_DQBUF调用返回v4l2-buffer->index参数,用户程序就可以读取数据了。最后把下一帧设置成当前帧。

四.V4L2应用程序编程

V4L2应用程序编程主要还是需要按照V4L2 API来完成。

 1.       设备初始化

if (-1 == ioctl (fd, VIDIOC_QUERYCAP, &cap)) {

                if (EINVAL == errno) {

                        fprintf (stderr, "%s is no V4L2 device\n",

                                 dev_name);

                        exit (EXIT_FAILURE);

                } else {

                        errno_exit ("VIDIOC_QUERYCAP");

                }

        }

if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {

                fprintf (stderr, "%s is no video capture device\n",

                         dev_name);

                exit (EXIT_FAILURE);

        }

 

if (!(cap.capabilities & V4L2_CAP_STREAMING)) {

                     fprintf (stderr, "%s does not support streaming i/o\n",

                             dev_name);

                     exit (EXIT_FAILURE);

              }

 

fmt.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        fmt.fmt.pix.width       = 765

        fmt.fmt.pix.height      = 576

        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

        fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

 

        if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))

                errno_exit ("VIDIOC_S_FMT");

2.       buffer请求和mmap系统调用

req.count               = 4;

        req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        req.memory              = V4L2_MEMORY_MMAP;

 

       if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {

                if (EINVAL == errno) {

                        fprintf (stderr, "%s does not support "

                                 "memory mapping\n", dev_name);

                        exit (EXIT_FAILURE);

                } else {

                        errno_exit ("VIDIOC_REQBUFS");

                }

        }

 

        if (req.count < 2) {

                fprintf (stderr, "Insufficient buffer memory on %s\n",

                         dev_name);

                exit (EXIT_FAILURE);

        }

 

        buffers = calloc (req.count, sizeof (*buffers));

 

        if (!buffers) {

                fprintf (stderr, "Out of memory\n");

                exit (EXIT_FAILURE);

        }

 

        for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {

                struct v4l2_buffer buf;

 

                CLEAR (buf);

 

                buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;

                buf.memory      = V4L2_MEMORY_MMAP;

                buf.index       = n_buffers;

 

                if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))

                        errno_exit ("VIDIOC_QUERYBUF");//buf.m.offset

 

                buffers[n_buffers].length = buf.length;

                buffers[n_buffers].start =

                        mmap (NULL /* start anywhere */,

                              buf.length,

                              PROT_READ | PROT_WRITE /* required */,

                              MAP_SHARED /* recommended */,

                              fd, buf.m.offset);

 

                if (MAP_FAILED == buffers[n_buffers].start)

                        errno_exit ("mmap");

        }

3.       select系统调用来查询buffer是否可读

               fd_set fds;
                        struct timeval tv;
                        int r;
                        FD_ZERO (&fds);
                        FD_SET (fd, &fds);
     /* Timeout. */
                        tv.tv_sec = 2;
                        tv.tv_usec = 0;
                        r = select (fd + 1, &fds, NULL, NULL, &tv);
                        if (-1 == r) {
                                if (EINTR == errno)
                                        continue;
                                errno_exit ("select");

4.       读数据

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                   buf.memory = V4L2_MEMORY_MMAP;
                  if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
                 exit();
              }
                assert (buf.index < n_buffers);
               process_image (buffers[buf.index].start);
              if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
                     errno_exit ("VIDIOC_QBUF");

注意:这里的VIDIOC_DQBUF调用主要是从驱动中返回v4l2_buffer->index这个参数,有了这个参数就知道那个buffer可以读了。通过process_image读出数据。数据读完之后,需要调用VIDIOC_QBUF来把buffer返回给驱动。

转载于:https://www.cnblogs.com/woshitianma/archive/2013/04/26/3045256.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值