罗技摄像头C270与嵌入式LINUX

2019-07-12 22:30发布

http://www.eefocus.com/marianna/blog/13-06/294567_a5fc8.html 现在,假如你的手上有一只摄像头,它是罗技高清网络摄像头webcam-C270,还有一块cortexA8开发板,这块开发板来自FriendlyARM,已经预装了linux系统,版本号是最新提供的linux-3.0.8,图形界面是Qtopia-2.2.0,交叉编译器是arm-linux-gcc-4.5.1。主机是Fedora9 摄像头和开发板,这两样东西安安静静的躺在了你的手里,准备就绪,状态良好。而你的任务,就是要让摄像头正常的工作在开发板上,并且完成一些简单的任务,比如说将图像显示在Qtopia的界面上,并判断当前的图像中有没有阿拉伯数字。 (虽然说C270并不支持linux系统,可是支持linux系统的摄像头又有几只呢?即使C270不支持linux系统,不代表linux系统不支持C270^_^ 插上摄像头试试 在开发板的终端窗口,输入“cat /proc/kmsg &”,来显示内核打印信息。 FriendlyARM最新提供的光盘,里面是包含了C270的驱动程序和应用程序。将C270连接在开发板的USB口上面,内核会打印出下面的信息: <6>[960.933564] usb 1-1.3: new high speed USB device number 7 using s5p-ehci <6>[961.262234] uvcvideo: Found UVC 1.00 device  (046d:0825) <6>[961.362302] input: UVC Camera (046d:0825) as /devices/platform/s5p-ehci/usb1/1-1/1-1.3/1-1.3:1.0/input/input5 内核信息打印有七个日志级别,数字越低,级别越高。低于当前信息的打印级别的信息就不会被显示出来。<6>[961.262234]的意思是,打印出的信息级别是KERN_INFO(提示信息),这个信息在系统的961.262234这个tick时间被执行。 1-1.3:1.0的意思是,摄像头使用的根集线器编号为1,集线器端口号为1,集线器(摄像头使用)端口号为3,配置为1,接口为0input: UVC Camera说明linux内核认出了这个设备,而且知道这个设备是UVC标准摄像头。 如果摄像头被正常的识别和驱动,打开名为“USB 摄像头”的应用程序(FriendlyARM提供),其界面上就会显示出图像。 摄像头如何被识别 如果摄像头没有找到正确的驱动,开发者如何确认这一点呢?如果没有驱动或者安装了错误的驱动,开发者如何安装正确的驱动呢? 驱动一个LED灯,也就是cortexA8的一个IO口,驱动可以编译进内核,也可以通过模块的方式加载。然后在/dev下形成设备文件。用户程序通过读写设备文件,来控制这个IO口。这样的流程,并不适合USB设备。“USB设备是一个非常复杂的东西,官方USB文档中有详细的描述。幸运的是,Linux内核提供了一个称为USB核心(USB Core)的子系统来处理大部分的复杂性。”——摘自《LINUX设备驱动程序》。 USB设备通过USB Core和驱动交换数据,用户程序读写驱动的数据,因此USB Core作为中间层,需要把设备和驱动,正确的对应起来。每个USB设备插入主机的时候,主机都会请求设备的device descriptor。设备描述符device descriptor提供了USB协议版本、厂商ID、产品ID等信息。 厂商IDidVendor)和产品IDidProduct),是USB Core把设备和驱动联系起来的关键。USB设备具备自身的idVendoridProduct;驱动程序也会定义自己的idVendoridProduct。如果USB Core从设备请求到到的ID,正好符合驱动程序的定义,那么USB Core就知道这个ID的设备使用的是这个ID的驱动程序。 idProductidVendor16位的数值。在内核打印信息中出现的046d0825这两个数,就是idVendoridProduct。在/sys/bus/usb/drivers/usb/1-1.3/这个目录下,查看idProductidVendor这两个文件,也会发现它们的值是046d0825046d是罗技公司的idVendor 什么是UVC设备 罗技C270是一个标准的UVC设备,需要UVC driver Its important for me for a webcam to be UVC compatible where UVC is the USB Video Class, and defines a standard/specification for devices capable of streaming video. For example, being UVC compatible was a logo requirement for Windows Vista which helped make this class of device popular, and fortunately there is good support under GNU/Linux。” http://forums.opensuse.org/blogs/oldcpu/logitech-c270-webcam-opensuse-110博主Oldcpu是欧洲的一名航天器操作工程师,同时也是一名linux爱好者。Oldcpu为这个C270准备的系统是GNU/Linux (openSUSE),而驱动C270的关键是“UVC compatible”。 The USB Device Class Definition for Video Devices, or USB Video Class, defines video streaming functionality on the Universal Serial Bus. Much like nearly all mass storage devices (USB flash disks, external SATA disk enclosures, ...) can be managed by a single driver because they conform to the USB Mass Storage specification, UVC compliant peripherals only need a generic driver. The UVC specification covers webcams, digital camcorders, analog video converters, analog and digital television tuners, and still-image cameras that support video streaming for both video input and output. 这段文字来自http://www.ideasonboard.org/uvc/。在网页下方,列出了UVC支持的webcam型号,其中以046d作为idVendor的,就是罗技摄像头。 046d:0825(logitech HD Webcam C270)出现在这张表中。 UVC是在linux-2.6.38版本时加入内核的,那么更早的版本没有集成UVC。好在FriendlyARM提供内核版本是linux-3.0.8,里面集成了UVC驱动。在内核源代码的“Documentation/video4linux/uvcvideo.txt”中,是有关于UVC的说明。 UVC驱动程序的位置  根据Documentation/video4linux/uvcvideo.txt给出的信息,UVC设备不需要编写单独的驱动,它在用户空间提供了类似驱动的接口。如果用户需要实现ioctl功能,就需要在用户空间调用这个接口。 linux-3.0.8源代码的driver目录下,执行find . -name *uvc* -type f,发现,uvc文件集中在./usb/gadget/./media/video/uvc/这两个目录。USB gadget虽然有UVC部分,但它并不是UVC driver。真正的UVC driver,是./media/video/uvc/ ./media/video/uvc/目录下有12个文件: uvc*.c     8个)  Kconfig Makefile modules.builtin modules.order Makefile用来指定生成目标文件的规则。Kconfig用在定义生成的目标文件在make menuconfig时选项名称。.c文件是UVC的实现。   UVC驱动程序的Makefile  uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o  uvc_ctrl.o uvc_status.o uvc_isight.o ifeq ($(CONFIG_MEDIA_CONTROLLER),y)         uvcvideo-objs  += uvc_entity.o endif obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o 在这个Makefile中,每个.c文件生成一个.o文件。除了uvc_entity.o外的共7.o文件,链接成一个uvcvideo-objs 如果条件“($(CONFIG_MEDIA_CONTROLLER),y)”成立,则把uvc_entity.o也链接进去,生成一个uvcvideo-objs。生成的uvcvideo-objs,就是uvcvideo.o UVC驱动程序的Kconfig config USB_VIDEO_CLASS         tristate "USB Video Class (UVC)"         ---help---           Support for the USB Video Class (UVC).  Currently only video           input devices, such as webcams, are supported. Device Drivers -> Multimedia support -> Video capture adapters -> V4L USB Devices -> USB Video Class (UVC),这个选项就是在make menuconfig的时候对应的Kconfig中的内容。 如果用户选择了此选项,那么Makefile产生的uvcvideo.o就会被编译进内核。 FriendlyARM提供的开发板设置,这一项是[*],也就是将uvcvideo.o默认静态编译在内核中。 Linux下的编译环境,没有像Window下有那么多可爱的按钮让你按下去,没有工程的概念,也没有后台帮你把所有的事情都搞定了。Linux的编译过程很麻烦,因为你得自己使用makefile文件告诉它该怎么编译。这是linux的可恨之处,也是linux的可爱之处~ UVC驱动程序的uvc_driver.c 为了方便调试,首先需要将静态编译模块UVC Driver,改成动态加载的模块。要不然,每次修改UVC Driver的时候,都需要重新编译内核。 (如何将静态编译的模块,改成动态加载的模块呢?在编译内核make menuconfig的时候,将Kconfig的对应项由“*”修改为“M”,重新编译内核make zImage并使用新的zImage引导系统。源文件driver/media/video/uvc/目录下将生成uvcvideo.ko文件,将uvcvideo.ko拷贝到嵌入式开发板的/lib/modules/3.0.8-FriendlyARM下,在终端输入指令modprobe uvcvideo。如果终端指示找不到uvcvideo这个文件,则需要先进入上一级目录,执行depmod。) 修改uvc_driver.c,在其中加入调试语句,就能跟踪UVC摄像头的驱动步骤。uvc_driver.c是一个module文件,因为它具备了两个很典型的module函数: static int __init uvc_init(void) {         int result;         result = usb_register(&uvc_driver.driver);         if (result == 0)                 printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ") ");         return result; } static void __exit uvc_cleanup(void) {         usb_deregister(&uvc_driver.driver); } uvc_init是模块装载函数,仅仅执行了usb_register(&uvc_driver.driver)这个操作。如果usb_register成功,会打印内核信息,并返回0,如果不成功,则返回错误代码。一般来说,模块初始化的“__init”函数中,除了注册设备外,不进行任何动作,而需要进行的实质初始化动作,放在“open”函数中。这是因为模块注册之后,用户很少会去卸载它,如果“__init”函数中包含了对资源的占领,那么在它并不工作的时候,也无法释放这些资源。 被“__init”注册的struct uvc_driver定义在driver/media/video/uvcvideo.h中: struct uvc_driver {         struct usb_driver driver; }; (好吧,虽然打着uvc_driver的旗号,尼玛就是一个普通的USB设备好不?~)知道了uvc_driver是什么,就能够理解在uvc_driver.c中定义的struct uvc_driver uvc_driver struct uvc_driver uvc_driver = {         .driver = {                 .name           = "uvcvideo",                 .probe          = uvc_probe,                 .disconnect     = uvc_disconnect,                 .suspend        = uvc_suspend,                 .resume         = uvc_resume,                 .reset_resume   = uvc_reset_resume,                 .id_table       = uvc_ids,                 .supports_autosuspend = 1,         }, }; .name是要注册进USB Core的驱动名字,它会显示在/sys/bus/usb/drivers/下。但它并不是应用程序将要读写的设备文件。当UVC设备插入的时候,会调用.uvc_probe。当UVC设备拔出的时候,会调用.uvc_disconnect。内核需要.id_table用来判断插入的设备,是否自身适用。 定义.id_tableuvc_ids列表中,并没有[046d:0825]这一项,但是探测回调函数.uvc_probe确实又被调用了。这是为什么呢? const struct struct usb_device_id *id_table指向struct usb_device_id表的指针,该表中包含了一列该驱动程序可以支持的所有不同类型的USB设备。如果没有设置该变量,USB驱动程序中的探测回调函数不会被调用。如果想要驱动程序对于系统中的每一个USB设备都被条用,创建一个只设置driver_info字段的条目。fromLINUX设备驱动程序》 虽然uvc_ids列表中的36项都没有[046d:0825],但是最后一项是:  { USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, 0) }, USB_INTERFACE_INFO定义在linux/usb.h: #define USB_INTERFACE_INFO(cl, sc, pr)          .match_flags = USB_DEVICE_ID_MATCH_INT_INFO,          .bInterfaceClass = (cl),          .bInterfaceSubClass = (sc),          .bInterfaceProtocol = (pr) .match_flags定义的USB_DEVICE_ID_MATCH_INT_INFO,说明只要USB设备的接口描述符bInterfaceClassbInterfaceSubClassbInterfaceProtocol这三个变量,符合uvc_driver.c中定义的USB_CLASS_VIDEO10,那么驱动也是可以被这个USB设备使用的。这三个变量的意义,由USB协会定义。USB_CLASS_VIDEO定义在usb.h中。当把uvc_ids列表中的最后一项注释掉之后,摄像头也不再能被识别。 UVC摄像头被正常识别之后,uvc_probe函数就会被调用。为了正常使用uvc_probe里面的uvc_trace函数(定义在driver/media/video/uvcvideo.h中),需要将uvc_trace_param这个变量置为0x07FF。(意思是不管uvc你有什么信息,尽管全都显示出来吧~) 当修改了uvc_trace_param后,原来被隐藏的UVC内核调试信息就会被显示出来。(你会发现这时候内核会打印出一大串信息,终端会刷刷刷的刷屏,弄得俺差点以为是segment错误)而这一大串信息都来自probe函数(或者由它调用的函数)。 UVC驱动程序的uvc_driver.cprobe函数 当插上摄像头时,usb core会自动调用probe函数,这个函数很重要。probe函数会完成很多事情。(既然是调试,就让我们让它完成更多的事情,来帮助理解probe是怎么工作的) static int uvc_probe(struct usb_interface *intf, const struct usb_device_id *id) 上面就是probe函数头了,它传递了两个参数,这两个参数由usb core直接赋值,用户不用关心如何被赋值,只需要关心如何使用它们就好了。 probe函数需要完成的工作有: NO.1:从一个struct usb_interface获取一个控制的struct usb_device struct usb_device *udev = interface_to_usbdev(intf); probe函数被调用时,usb core仅仅传输了usb_interfaceusb_device_id这两个参数。通过usb_interface就可以获取到当前的usb_device了。usb_device就是我们的罗技摄像头C270了,设备描述符、配置描述符等等都可以通它来取得。 为嘛还要通过函数interface_to_usbdev获取usb_device,而不通过参数直接传递呢? 一个usb_interface代表了一个基本功能,而每个USB驱动程序控制一个usb_interface。所有的USB驱动程序都用usb_interface来和usb core进行通信,所以驱动程序就用usb_interface来做入口参数啦。 usb_device通常具备一个或多个usb_host_config;一个usb_host_config通常具备一个或多个usb_interface。(usb_device是爷爷,usb_host_config是爸爸,usb_interface是孩子) NO.2(这一步是俺加的)打印设备描述符/配置描述符/接口描述符的内容。 知道了usb_device之后,就可以知道它包含的关于摄像头C270更多的内容。usb_device有一项是struct usb_device_descriptor,也就是摄像头C270的设备描述符。 usb_device包含的配置有两个,一个是config,一个是actconfig,从字面上就能理解它们的区别了,前者用来存储所有的配置,后者用来存储当前的配置。罗技摄像头C270只有一个配置,所以configactconfig也是一样的。 actconfig包含了struct usb_interface,实际上罗技摄像头C2704个。虽然有4个可用,但是目前活动的只有1个,一般来说是interface 0。活动的interface 0会传输设备描述符、配置描述符,而其他3个接口描述符也会和配置描述符一起被传输过来。 使用printk函数将它们的内容打印出来,打印的时候注意检查指针是否为NULL printk(KERN_NOTICE "maria_debug: bLength=0x%04x ", udev->descriptor.bLength); printk(KERN_NOTICE "maria_debug: bLength=0x%04x ",  udev->actconfig->desc.bLength);   intf_t = udev->actconfig->interface[i]; intf_desc_t = &(intf_t->altsetting->desc); printk(KERN_NOTICE "maria_debug: bLength=0x%04x ", intf_desc_t->bLength); 就能得到下面的三张表: O f f s e t F i e l d   S i z e  V a l u e D e s c r i p t i o n 0 bLength 1 0x12 描述符长度为18 1 bDescriptorType 1 0x01 这是设备描述符 2 bcdUSB 2 0x0200 2.00的USB协议版本 4 bDeviceClass  1 0xEF Miscellaneous Device Class  5 bDeviceSubClass 1 0x02 Common Class  6 bDeviceProtocol  1 0x01 Interface Association Descriptor 7 bMaxPacketSize0 1 0x40 Control endpoint packet最大长度64字节 8 idVendor 2 0x046D 厂商id 10 idProduct 2