Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1251536
  • 博文数量: 548
  • 博客积分: 7597
  • 博客等级: 少将
  • 技术积分: 4224
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-15 13:21
个人简介

嵌入式软件工程师&&太极拳

文章分类

全部博文(548)

文章存档

2014年(10)

2013年(76)

2012年(175)

2011年(287)

分类: 嵌入式

2012-02-29 17:47:45

Davinci视频采集驱动文档

概述

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

 

名词解释:

A-low:

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

Auto focus:

Auto white balance: ut

Auto exposure:

ITU-R :

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

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

 

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

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

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

图中,最高位bit7为固定数据1F=0表示偶数场,F=1表示奇数场;V=0表示该行为有效视频数据,V=1表示该行没有有效视频数据;H=0表示为SAV信号,H=1表示为EAV信号;P3P0为保护信号,由FVH信号计算生成;P3=V异或HP2=F异或HP1=F异或VP0=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三种。三者区别主要在于图像质量和信道传输所占带宽的不同。从视觉效果来看,数字 HDTV1000线以上)为高清晰度电视(High Definition Television)的简称,图象质量可达到或接近35mm宽银幕电影的水平;SDTV500-600线)即标准清晰度电视,主要是对应现有电视的分辨率量级,其图象质量为演播室水平;LDTV200-300线)即普通清晰度电视,主要是对应现有VCD的分辨率量级。因为电视全数字化是今后的趋势,所以目前提HDTV以及SDTVLDTV如无特别说明,均指全数字体制。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

视频采集接口VPFE

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

涉及的模块主要有:

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模块可以对图像进行裁剪和缩放功能。

 

I2CA/D芯片

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

 

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

这里I2C只有2根信号线:SCLSDASCL信号线产生clock时钟,SDA数据线通过内部的ICXSR/ICDXRICRSR/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中读接收数据。

发送中断ICXINTICXRDY

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

停止中断SCD

STOP位发生

AAS中断

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

 

2.1 DavinciI2C控制器驱动

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

 

I2C控制器注册到I2C总线上是把davinciI2C适配器的数据结构注册到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. davinciI2C的适配器挂入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寄存器。设置IRSMSTSTT位,具体含义看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,即1START位,发送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的可扩展性和灵活性都得到大大的提高,并且能够支持更多的设备。V4L2VL4进行了彻底的改造,很多关键的API发生了变化,所以V4L2V4L不兼容。

Davinci的视频采集驱动采用V4L2 API来编写驱动。V4L2 API支持3种采集的方法:read/write, MMAPUSER 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需要用到的buffersV4L2为了简化驱动编程,抽象了一个buffer的管理buffer的文件/driver/video_buffer.c 文件中。这里会调用到video_buffer.c文件中相关函数来完成buffers的初始化。

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

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

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

l         分配struct videobuf_buffern个地址空间。

 

 

2.  Mmap操作

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

 

3.  VIDIOC_QBUF

这个ioctl操作主要实现初始化的时候把空buffers挂入queue->strream队列中和vpfe->dma_queue队列中;当应用程序读了1buffer数据之后,把这个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.  中断处理

中断处理要注意“场“的概念。因为视频输入源的摄像头有分PROGRESSIVEINTERLACED之分。PROGRESSIVE的摄像头一帧只有一场(field),而INTERLACED的摄像头一帧有2场,其中field=1即偶场,先执行,然后才是field0的奇场,当奇场完成是需要唤醒等待队列,然后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.       一帧数据,一般先是field1的偶场先完成。在偶场中,取出下一帧buffer,并设置下一帧的ccdc输出地址。

2.       接着是field0的奇场完成,这时候把当前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返回给驱动。

阅读(1323) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~