精华内容
下载资源
问答
  • 国产USB2881采集卡开发例子,采集多路模拟量,可用于实际项目的开发,数据可以直接使用和处理,不需要经过转化
  • Linux USB设备驱动设计本文是Linux内核&内核驱动开发的第一篇专栏文章。0、前言目前无论是在PC、手持终端,还是在嵌入式领域,USB设备都被广泛应用。本文暂时抛开大量USB协议、概念等细节,从最基础的框架入手...

    Linux USB设备驱动设计

    本文是Linux内核&内核驱动开发的第一篇专栏文章。

    0、前言

    目前无论是在PC、手持终端,还是在嵌入式领域,USB设备都被广泛应用。本文暂时抛开大量USB协议、概念等细节,从最基础的框架入手介绍如何设计设备驱动。在丰富细节功能的同时,逐步介绍涉及到的概念。

    本文适合对Linux驱动、USB设备有所了解的开发人员,也适合USB产品设计者。

    表0-1 术语、概念、缩略语

    717355f8b37714124fde2279838e95da.png

    1、准备工作

    要开始USB设备驱动设计,首先需要有个USB设备。对于刚开始进行设备驱动开发学习的同学而言,不太容易找到现成的物理USB设备来做为练习使用;即是手头上有现成的优盘、音视频小设备,也会因为不清楚厂商的设计细节,无法拿来练手。出于这种目的,我专门针对模拟器设计了一个USB Sample的设备,将它实现一个通用输入输出功能(General IO)设备。基本功能比较简单:可进行基本设置与信息获取(速率,环回),可以将数据送出,并获取其接收到的数据。

    如何在模拟器上模拟出一个USB设备,不是本文的主要内容(或许在下一篇专栏文章中介绍)。需要强调的是,使用软件方式模拟设备的做法在很多场景下非常有用,能够大大加速产品设计、开发进度;能够在真正投产(流片)之前,便完成功能设计、验证等工作。

    设备模拟完成之后,启动GUEST操作系统,可以通过lsusb查看系统中所有的USB设备,如下表1-1所示。

    表1-1模拟USB设备

    / # lsusb
    Bus 001 Device 004: ID efb8:f201
    Bus 001 Device 001: ID 1d6b:0001
    Bus 001 Device 002: ID efb8:f102
    Bus 001 Device 003: ID 0409:55aa

    在USB规范以及Linux内核,USB设备对象相关概念关系可以用下图来表示,辅助理解。如下图1-1所示。

    f17502ad65256a99c8426b1b7aaab63c.png

    图1-1设备-配置-接口-端点

    某些设备,会有多种配置、多种接口;多数设备则往往只有一种配置,一个接口,若干个端点。这里,配置可以理解为设备的工作模式,接口专指功能,端点表征传输通道。举例说明这种功能,某一个USB视音频卡,只存在一个配置,接口0负责视频处理传输,接口1负责音频处理传输;两个接口分别有三个端点:控制端点、输入端点、输出端点。所以在Linux内核驱动中,probe函数接受的是usb_interface对象而不是usb_device对象:接口才表征了一个独立的功能。

    USB Sample设备(以下简称usample设备)设计功能如下:设备只包含一个配置(Configuration),该配置中只包含一个接口(Interface),该接口中除控制端点之外,还包含两个端点(Endopint),端点1的属性为入向(in)批传输类型(bulk transfer),端点2的属性为出向(out)批传输类型;传输的最大包长均为64字节。

    2、设备探测

    系统启动时,HCD(Host Controller Driver)驱动会对挂载在USB总线上的设备进行枚举(热插拔同样会触发这个过程),对发现的设备将会创建一个usb_device设备对象(device,interface,endpoint三者之间的关系见第一章描述)并记录下来。通过lsusb命令,我们可以看到设备信息:本例usample是处于第1号总线的第004号设备,其Vendor厂商ID为efb8,Product产品ID为f201(每个厂商都有向IEEE组织申请的唯一ID,这两个组合可以用于确定设备类型)。此处,efb8以及f201为我们在模拟设备时指定的ID,取的数值偏大用来避开真实产品ID。

    代码2-1 usample设备ID定义

    #define PCI_VENDOR_ID_LTRIANGLE           0xefb8
    #define PCI_DEVICE_ID_LTRIANGLE_USAMPLE   0xf201

    USB驱动中,往往先要定义一个设备ID表,用于内核匹配使用。Usample驱动文件中定义的设备ID表如下所示:

    代码2-2驱动支持设备列表

    static const struct usb_device_id id_table[] = {
    { .idVendor = PCI_VENDOR_ID_LTRIANGLE, 
    .idProduct = PCI_DEVICE_ID_LTRIANGLE_USAMPLE,
    .match_flags = USB_DEVICE_ID_MATCH_VENDOR|USB_DEVICE_ID_MATCH_PRODUCT, 
    },
    };
    MODULE_DEVICE_TABLE(usb, id_table);

    当内核驱动被加载或者新USB设备被发现时,内核将对USB设备信息与设备驱动ID Table中的信息进行比对,如果匹配则使用驱动中的probe探测函数对设备进行探测。Match_flags表明需要匹配厂商ID以及产品ID。

    当内核驱动被加载或者新的设备被发现时,内核将会做探测动作,行为可以用如下伪代码来描述:

    新驱动模块插入与新设备被发现时,内核动作如下述伪代码描述。for_each_*表示遍历内核对象列表。

    代码2-3驱动与设备注册流程

    register_usb_driver(usb_driver) {
      for_each_device(usb_device) {
     if(match(usb_device<idVendor, idProduct>, usb_driver.id_table))
          usb_driver->probe(usb_device);
      }
    }
    register_usb_device(usb_device) {
      for_each_driver(usb_driver) {
     if(match(usb_device<idVendor, idProduct>, usb_driver.id_table))
          usb_driver->probe(usb_device);
      }
    }

    新注册驱动或者设备时,内核遍历设备或者驱动,进行ID匹配、探测处理。因此,我们并不用担心驱动注册或者设备发现两个事件发生的先后时序关系。

    Linux下的USB设备驱动已经被抽象为一个名为usb_driver的结构体,如下所示。提供usb_driver结构体中的函数接口实现,即可为内核usbcore所用,实现特定USB设备的管理。

    代码2-4 USB Sample驱动结构定义

    static struct usb_driver usample_driver = {
        .name =     "usample",
        .probe =    usample_probe,
        .disconnect =   usample_disconnect,
        .suspend =  usample_suspend,
        .resume =   usample_resume,
        .id_table = id_table,
        .supports_autosuspend = 1,
    };
    module_usb_driver(usample_driver);

    Name为驱动名称,在内核中应当唯一,不应有同名驱动存在。Probe函数接口,用于给设备驱动提供决策,是否愿意管理该USB设备下的一个接口(interface)。这里我们可以看出,usb_driver面对的对象粒度是接口,而不是设备(device)。Disconnect:当设备被删除(拔出)或者驱动被卸载时,会调用该接口以解除设备对象与内核之间的联系。后面的例子中,我们会看到该接口实现中应当做哪些事情。Suspend/resume:系统电源管理接口;当系统进入休眠/退出休眠状态时,分别调用该接口通知到设备驱动程序。前面已经说明,id_table用来给内核USB核心驱动决定探测到的设备(接口)是否可以提交当前驱动来处理。

    当驱动被加载时,内核匹配到了设备的驱动,探测函数被调用。内核USB核心在设备枚举阶段,已经获取到了USB设备有关的基础信息,并用各种结构体将设备对象封装描述起来。探测函数需要获得设备/接口/端点的信息(结构体对象),以被后用。这里,需要注意区分如下结构体对象:

    • struct usb_interface:USB设备接口对象。
    • struct usb_host_interface:主机端的设备接口对象封装。
    • struct usb_interface_descriptor:USB设备接口描述符,记录与USB协议相关的物理属性。
    • struct usb_endpoint_descriptor:USB设备端点描述符,记录与USB协议相关的物理属性,如端点地址、支持的最大包长等。
    • struct usb_device:注意与接口的区别,一个USB设备可能包含多个功能接口。
    • struct usb_sample:usample驱动私有数据结构,用于实现设备功能。

    为什么有了usb_interface,还要在其中进一步抽象出usb_host_interface?这是为了将与设置(altsetting)有关的属性,再做一层抽象与封装,可以尽量简化usb_interface的复杂度。

    usample_probe要做的工作,简单来说,即是创建私有usample设备对象,并通过给出的usb_interface,找到其中可用于批传输的端口记录在设备对象中。后续真正和usample设备进行通讯的,就是该设备所包含的功能端点对象。

    代码2-5设备探测过程

    static int usample_probe(struct usb_interface *interface,
     const struct usb_device_id *id)
    {
     struct usb_sample *dev = NULL;
     struct usb_host_interface *iface_desc;
     struct usb_endpoint_descriptor *endpoint;
        size_t buffer_size;
     int i;
     int retval = -ENOMEM;
        /* allocate memory for our device state and initialize it */
        dev = kzalloc(sizeof(*dev), GFP_KERNEL);
        dev->udev = usb_get_dev(interface_to_usbdev(interface));
        dev->interface = interface;
        /* set up the endpoint information */
        /* use only the first bulk-in and bulk-out endpoints */
        iface_desc = interface->cur_altsetting;
     for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
            endpoint = &iface_desc->endpoint[i].desc;
    
     if (!dev->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) {
                /* we found a bulk in endpoint */
                buffer_size = usb_endpoint_maxp(endpoint);
                dev->bulk_in_size = buffer_size;
                dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
                dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
            }
     if (!dev->bulk_out_endpointAddr &&
                usb_endpoint_is_bulk_out(endpoint)) {
                /* we found a bulk out endpoint */
                dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
            }
        }
     if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) {
            dev_err(&interface->dev,
                "Could not find both bulk-in and bulk-out endpointsn");
        }
        /* save our data pointer in this interface device */
        usb_set_intfdata(interface, dev);
        /* we can register the device now, as it is ready */
        retval = usb_register_dev(interface, &usample_class);
     if (retval) {
            /* something prevented us from registering this driver */
            dev_err(&interface->dev,
                "Not able to get a minor for this device.n");
            usb_set_intfdata(interface, NULL);
    }
    ...
    }

    关键流程为:创建usb_sample对象,根据interface获得usb_device对象指针,遍历altsetting中所有的端点,查找其中的bulk_in_endpoint及bulk_out_endpoint,记录已被后用。最后,通过usb_register_dev注册设备(接口)。由于USB设备并不直接对用户态呈现文件接口(除通过sysfs文件系统导出的驱动信息除外),在注册设备中还提供了usample_class结构体对象,用于辅助创建字符驱动设备。

    当驱动模块加载之后,内核输出如下信息:

    # modprobe usample
    usample 1-2.1:1.0: USB Sample Version 04.00 found at address 4
    usample 1-2.1:1.0: USB Sample device now attached to usample-0
    usbcore: registered new interface driver usample

    3、字符设备驱动

    一般情况下,我们要为USB设备创建一个设备节点,以被操作系统、应用程序所用。其外在表现为形如/dev/usample0这样的设备节点,可以供应用程序通过如read/write/ioctl等系统调用接口使用。根据设备类型的不同,我们可以创建字符设备、块设备、网络设备等。我们将usample设备对系统呈现为一个字符设备,提供常用的读写控制接口。

    示例3-1字符设备节点

    / # ll /dev/usample0 
    crw-rw----    1 root     0         180,   0 Mar 22 14:33 /dev/usample0

    “crw-rw----”中的”c”表示该设备为字符设备,主测设备号(major)为180,次设备号(minor)为0。

    USB CORE可以帮助我们创建这个设备节点,只需要在通过usb_register_dev注册USB接口时,指定一个usb_class_driver的对象即可:

    代码3-1 USB类驱动

    static struct usb_class_driver usample_class = {
        .name =         "usample%d",
        .fops =         &usample_fops,
        .minor_base =   USAMPLE_MINOR,
    };
    
    static int usample_probe(struct usb_interface *interface,
     const struct usb_device_id *id) {
        ......
    /* we can register the device now, as it is ready */
        retval = usb_register_dev(interface, &usample_class);

    USB CORE会根据usb_class_driver中指定的名称格式,字符驱动file_operations对象,以及起始次设备号来创建字符设备。

    代码3-2 usample字符设备驱动

    static const struct file_operations usample_fops = {
        .owner =        THIS_MODULE,
        .read =         usample_read,
        .write =        usample_write,
        .open =         usample_open,
        .unlocked_ioctl = usample_ioctl,
        .release =      usample_release,
        .llseek =    noop_llseek,
    };

    Usample字符设备驱动提供了5个主要接口:open、release(close)、read、write、ioctl接口,这5个接口也和C库中的同名接口一一对应。

    Usample_open接口实现USB设备(usb_sample/usb_interface)与设备节点产生关联,为后续其他接口做好准备,如下所示。

    代码3-3设备打开动作

    static int usample_open(struct inode *inode, struct file *file)
    {
     struct usb_sample *dev;
     struct usb_interface *interface;
     int subminor, r;
        subminor = iminor(inode);
        interface = usb_find_interface(&usample_driver, subminor);
     if (!interface) {
            printk("USB Sample: %s - error, can't find device for minor %dn", __func__, subminor);
     return -ENODEV;
        }
        dev = usb_get_intfdata(interface);
     if (!dev) {
     return -ENODEV;
        }
        /* save our object in the file's private structure */
        file->private_data = dev;
     return 0;
    }

    首先通过iminor(inode)获得设备节点(即/dev/usample0)的次设备号,然后利用usb_find_interface()接口来获得USB类驱动关联创建设备节点的usb_interface接口对象。在probe探测流程中,我们通过usb_set_intfdata()将内核usb_interface对象和自定义的usb_sample对象关联起来,这里再通过usb_get_intfdata()将自定义sample设备对象取出,记录到设备节点的私有数据字段中(file->private_data)。

    4、USB通讯

    到目前为止,我们在做的都是建立起USB设备驱动框架,并没有涉及到到USB设备用户功能的位置。最重要的read/write以及ioctl接口,开始需要和设备进行交互了。在这里,我们回顾一下USB传输类型。

    表4-1 USB传输类型

    8c618dbc4513dd3dbb9098d08174cfcd.png

    一个端点只能工作在一种传输模式下;通常我们把工作在什么模式下的端点,叫做什么端点,如控制端点、批量端点、同步端点、中断端点。入本文设计的USAMPLE设备具有三个端点:控制端点0、批量输入端点1、批量输出端点2,未使用同步传输和中断传输。

    控制传输

    我们为USAMPLE设备设计了三个私有配置/获取功能,分别为设置速率、设置环回、获得属性,分别通过USB控制传输中的request来进行区别。

    代码4-1 USB请求:速率,环回,属性

    #define USB_SAMPLE_REQ_SET_SPEED         20
    #define USB_SAMPLE_REQ_SET_LOOPBACK      21
    #define USB_SAMPLE_REQ_GET_PROPERTIES    22

    我们通过控制传输方式,设置USAMPLE设备的环回属性,以及获取设备属性。

    代码4-2 出向环回设置

    static long usample_set_loopback(struct usb_sample *dev, int loop)
    {
     int ret = -1;
     int pipe;
     struct usb_device * udev = dev->udev;
        pipe = usb_sndctrlpipe(udev, 0);
        ret = usb_control_msg(udev, pipe, USB_SAMPLE_REQ_SET_LOOPBACK, USB_TYPE_VENDOR | USB_DIR_OUT, loop, 0, 0, 0, USB_CTRL_MSG_TIMEOUT);
     if (ret < 0) {
            printk("Set loopback error: %dn", ret);
     goto error;
        }
        ret = 0;
    error:
     return ret;
    }

    主机与端点之间存在一个逻辑通信通道,称为管道,它由一个32位的整形数值来表示。如下所示:

    表4-2管道位段定义

    6c8bfe286a838b764b8d464f22293cd4.png

    usb_sndctrlpipe()函数接受usb_device设备指针,以及端点编号,来组建一个出向管道标识。控制命令通过usb_control_msg()送出;该接口在驱动中承担非常重要的作用,因此在这里结合示例专门介绍一下其用法,以及注意点。

    代码4-3控制传输接口

    int usb_control_msg(struct usb_device *dev, unsigned int pipe,
        __u8 request, __u8 requesttype, __u16 value, __u16 index,
     void *data, __u16 size, int timeout);

    Dev参数为USB设备对象指针,这个容易理解,它是设备在软件上的描述。Pipe表示为主机HOST端到目标设备端点的通道,注意有方向性。Request为USB控制请求,USB定义了标准请求类型,如设置地址、获取配置等。Requesttype为请求类型分别为标准/类别/厂商几种,value为USB消息值,index为USB消息索引,data/size为需要发送给USB设备的数据,timeout为消息完成超时时间,单位为毫秒。

    • Pipe参数带有方向,Requesttype参数也带有方向,这两个方向必须一致。即,使用上usb_sndctrlpipe()与USB_DIR_OUT匹配,usb_rcvctrlpipe()与USB_DIR_IN匹配。
    • 如果控制消息带数据的话,数据必须存放在可DMA地址空间。简单来说,data缓冲空间必须是通过类似kmalloc(NSZ, __GFP_DMA)或者dma_mem_alloc()这样的接口申请而来。否则, USB CORE将给出“transfer buffer not dma capable”的失败信息。

    下面的例子为通过控制传输获取设备属性。

    代码4-4获取设备属性

    static long usample_get_properties(struct usb_sample *dev, void * arg)
    {
     int ret = -1;
     int pipe;
     struct usb_device * udev = dev->udev;
     struct usample_properties * prop = kzalloc(sizeof(struct usample_properties), GFP_KERNEL|__GFP_DMA);
     if(!prop) {
            ret = -ENOMEM;
     goto error;
        }
        pipe = usb_rcvctrlpipe(udev, 0);
        ret = usb_control_msg(udev, pipe, USB_SAMPLE_REQ_GET_PROPERTIES, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, prop, sizeof(*prop), USB_CTRL_MSG_TIMEOUT);
     if (ret < 0) {
            printk("Get properites error: %dn", ret);
     goto error;
        }
        ret = copy_to_user(arg, prop, sizeof(*prop));
        kfree(prop);
    error:
     return ret;
    }

    批量传输:接收数据

    对于多数的USB设备而言,仅仅使用控制传输是不够的,稍微大一些的数据量还是得使用批量传输方式来使用进行通讯。在软件处理上,收数据相对比较简单一些,usample设备读接口实现如下所示。

    代码4-5入向批量传输动作

    static ssize_t usample_read(struct file *file, char __user * buffer, size_t count, loff_t *ppos)
    {
     struct usb_sample *dev;
     int retval = 0;
     int bytes_read;
        dev = file->private_data;
        /* do a blocking bulk read to get data from the device */
        retval = usb_bulk_msg(dev->udev,
                      usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                      dev->bulk_in_buffer,
                      min(dev->bulk_in_size, count),
                      &bytes_read, 10000);
        /* if the read was successful, copy the data to userspace */
     if (!retval) {
     if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
                retval = -EFAULT;
     else
                retval = bytes_read;
        }
     return retval;
    }

    相对于usb_control_msg()函数,usb_bulk_msg()函数接口参数少了很多,主要是相对于控制传输而言,批量传输没有将控制消息中的字段细分出来。

    代码4-5批量传输发送接口

    int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
     void *data, int len, int *actual_length, int timeout);

    usb_bulk_msg()接口也是最关键的接口之一。usb_dev为USB设备对象,pipe为HOST端到目标设备端点的管道,组成见表4-2所述。data/len为欲发送数据的缓冲地址、长度。actual_length用于接收HCD实际发送(接收)数据的长度。timeout为等待消息超时时间。

    • Data所指向的缓冲区也应当具备DMA属性。

    批量传输:发送数据

    向USB设备批量发送数据,在处理流程上往往也复杂一些。使用usb_bulk_msg()可以完成这样的工作,但是为了更好的效率,我们使用另外一套流程来实现发送过程。在继续介绍之前,先介绍如下概念/数据结构。

    1. struct urb:USB Request Block,USB请求块。记录USB设备,数据地址/长度等一系列信息。该结构体比较复杂,一般情况下我们往往只需要关注在几种情况下如何使用即可。
    2. struct usb_anchor:USB Anchor,USB锚,顾名思义是用来做锚定/固定用的。在处理流程中,可以将动态申请的URB锚定到这种设备对象上,从而脱离主程序流程的管控,实现异步收发处理。

    批量传输接口实现如下。发送数据包含了如下步骤:(1)通过usb_alloc_urb()申请一个URB描述符;(2)通过usb_alloc_coherent()接口申请支持一致性的内存缓冲;(3)通过usb_fill_bulk_urb()接口填充URB信息,包括管道、数据缓冲、发送回调等;(4)将URB固定到一个锚上;(5)提交URB到USB CORE的发送队列上;(6)释放URB。

    代码4-6出现批量传输

    static ssize_t usample_write(struct file *file, const char __user * user_buffer,
                 size_t count, loff_t *ppos)
    {
     struct usb_sample *dev;
     int retval = 0, r;
     struct urb *urb = NULL;
     char *buf = NULL;
        dev = file->private_data;
        /* create a urb, and a buffer for it, and copy the data to the urb */
        urb = usb_alloc_urb(0, GFP_KERNEL);
     if (!urb) {
     goto error;
        }
        buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL,
                     &urb->transfer_dma);
     if (!buf) {
     goto error;
        }
     if (copy_from_user(buf, user_buffer, count)) {
     goto error;
        }
        /* initialize the urb properly */
        usb_fill_bulk_urb(urb, dev->udev,
                  usb_sndbulkpipe(dev->udev,
                  dev->bulk_out_endpointAddr),
                  buf, count, usample_write_bulk_callback, dev);
        urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
        usb_anchor_urb(urb, &dev->submitted);
        /* send the data out the bulk port */
        retval = usb_submit_urb(urb, GFP_KERNEL);
     if (retval) {
            dev_err(&dev->udev->dev, "%s - failed submitting write urb, error %dn",
                __func__, retval);
     goto error;
        }
        /* release our reference to this urb, the USB core will eventually free it entirely */
        usb_free_urb(urb);
    error:
     return retval;
    }

    代码中是一个简化之后的USB批量消息发送实现;我们注意到和接收消息处理有几大不同:所做的工作多了,流程复杂了不少,但是没有了timeout超时这个选项。这意味着,发送过程可以尽量不被堵塞。USB设备的发送速度和CPU处理速度是无法匹配的,如果每次发一个报文,都需要等待完成,那么这个流程很低效,严重影响应用程序性能。将数据打包,提交给USB CORE慢慢处理,这是个很不错的方式。为何批量接收消息可以接受等待、超时的做法,而发送则不采用这种方式?主要的原因是,接收本来就不受系统/驱动控制,只能被动等待;而发送过程我们却可以有能力采用更好的方式来实现。

    URB对象采用了引用计数设计,因此usb_free_urb()并不是立即释放内存,而是先降低引用计数,直到引用计数为0才真正释放内存。因此,当前流程中通过usb_submit_urb()提交URB的同时会增加引用计数,真正发送出去之后,会再一次调用usb_free_urb()来尝试释放内存。

    示例程序中,为了通过简单方式描述发包流程,没有对发送的URB数量做限制;如果应用程序大量往外发数据,而USB设备又不能及时处理的话,会缓冲大量的URB,进而造成系统内存耗尽。因此,在实际实现中,我们用信号量来控制最大URB缓冲数目;超发数据之后,应用程序还是要被阻塞的。

    usb_fill_bulk_urb()函数用于填充批传输URB包,其原型和usb_bulk_msg()比较相似,唯独增加了完成通知回调函数。

    代码4-7填充URB接口

    static inline void usb_fill_bulk_urb(struct urb *urb,
     struct usb_device *dev,
     unsigned int pipe,
     void *transfer_buffer,
     int buffer_length,
    				     usb_complete_t complete_fn,
     void *context);

    USB CORE将驱动提交的URB发送成功之后,在释放URB之前检查complete回调接口是否为空;如果不为空,则调用该回调参数。

    代码4-8 发送完成回调函数

    static void usample_write_bulk_callback(struct urb *urb)
    {
     struct usb_sample *dev;
     int status = urb->status;
        dev = urb->context;
        /* sync/async unlink faults aren't errors */
     if (status && !(status == -ENOENT || status == -ECONNRESET || status == -ESHUTDOWN)) {
            dev_dbg(&dev->interface->dev, "nonzero write bulk status received: %dn", status);
        }
        /* free up our allocated buffer */
        usb_free_coherent(urb->dev, urb->transfer_buffer_length,
                  urb->transfer_buffer, urb->transfer_dma);
    }

    发送完成之后,USB CORE通过urb->complete通知到驱动。在一般的实现中,驱动首先检查URB状态,如果不成功,则给出错误提示。此后,通过usb_free_coherent()接口释放消息数据缓冲。

    这里需要注意:URB由驱动申请,USB CORE释放;而URB所用的数据缓冲,都是驱动通过usb_alloc_coherent()及usb_free_coherent()来申请、释放。

    5、应用程序

    还有一种方式,可以不编写内核USB驱动程序也可以和设备进行通讯,即通过libusb库来完成与设备交互的过程,在纯用户态对设备的支持可称为用户态驱动。内核驱动与用户驱动各有优缺点,一般要根据应用场景、设备功能复杂度来确定。

    Usample提供了内核驱动,包装出了字符设备,因此还实现了用户态控制程序。对设备的特有控制,一般都是使用ioctl来实现。

    代码5-1内核ioctl接口

    static long usample_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
     struct usb_sample *dev;
        u16 bcdDevice;
     char buf[30];
     long ret;
        dev = file->private_data;
     switch (cmd) {
     case IOCMD_USAMPLE_SET_SPEED:
            ret = usample_set_speed(dev, arg);
     break;
     case IOCMD_USAMPLE_SET_LOOP:
            ret = usample_set_loopback(dev, arg);
     break;
     case IOCMD_USAMPLE_GET_PROPERTIES:
            ret = usample_get_properties(dev, (void *)arg);
     break;
     default:
            ret = -ENOTTY;
     break;
        }
     return ret;
    }

    在注册USB接口时,usb_class_driver中指定的file_operations::unlocked_ioctl接口为 usample_ioctl接口,第一个参数为struct file对象,关联/dev/usample0设备节点;cmd为IO命令码,arg为参数。驱动通过ioctl系统调用,来实现设备自定义功能接口对用户进程的呈现。

    代码5-2用户态应用程序

    snprintf(name, sizeof(name), "/dev/usample%d", 0);
    fd = open(name, O_RDWR);
    if(fd < 0)    {
        printf("Can not open device %s: return value %d.n", name, fd);
     return -1;
    }
    if(properties)    {
     struct usample_properties props;
        ret = ioctl(fd, IOCMD_USAMPLE_GET_PROPERTIES, &props);
     if(ret < 0)
        {
            printf("USample: get properties failed: return %d, %d/%s.n", ret, errno, strerror(errno));
        }
     else
        {
            printf("USample: get properties successfully.n");
            printf("    Speed   : %dn", props.speed);
            printf("    Loopback: %dn", props.loopback);
        }
    }
    close(fd);

    6、构建与测试

    驱动构建

    内核模块Makefile文件如下所示:

    代码5-1驱动Makefile文件

    GFLAGS := -I$(QMOD_ROOT)/include
    EXTRA_CFLAGS += $(GFLAGS) $(SEARCH_DIR)
    
    obj-m := usample.o
    usample-objs := usample_linux.o

    在内核模块编译时,除了继承内核的CFLAGS,我们往往还需要指定自己的源码头文件搜索路径,或者设定额外的编译选项,这个时候可以通过EXTRA_CFLAGS来传递。该变量只在当前模块编译时生效,不影响其他内核、其他模块的编译。

    Makefile中通过obj-m来告知内核编译体系当前模块目标,并生成同名.ko文件。如果该模块源自多个目标,则使用<KMODNAME>-objs := objs-list...的方式逐一指定。

    构建时,可输入如下命令:

    $ export KERNEL_SRC=/path/to/kernel/source
    $ export SRC=/path/to/usample_linux
    $ make -C $(KERNEL_SRC) M=$(SRC) 

    MAKE将转到内核目录,然后对M参数所指定的内核模块源码目录进行构建。编译成功之后,usampe内核模块目录树如下所示。

    work@usample_linux$ tree
    .
    ├── built-in.o
    ├── Makefile
    ├── modules.order
    ├── Module.symvers
    ├── usample.ko
    ├── usample_linux.c
    ├── usample_linux.o
    ├── usample.mod.c
    ├── usample.mod.o
    └── usample.o

    Usample_linux.c及Makefile是该设备驱动的两个源文件。

    usample.mod.c是内核模块构建体系编译出来的封装数据结构,包括如下内容:struct module __this_module,会封装模块名称、模块加载时的init_module函数入口,以及卸载接口cleanup_module。当模块被加载(insmod)/卸载(rmmod)时,这两个函数将被调用。此外,它还包含了当前模块所引用的内核函数接口。内核禁止非GPL类声明的模块使用某些函数接口,比如你如果使用MODULE_LICENSE("XIXIGP")声明,会遇到如下编译错误:

    FATAL: modpost: GPL-incompatible module usample.ko uses GPL-only symbol 'usb_wait_anchor_empty_timeout'

    构建完成之后,可以加载内核模块进行测试了。

    # modprobe usample
    # dmesg
    usample: loading out-of-tree module taints kernel.
    usample 1-2.1:1.0: USB Sample Version 04.00 found at address 4
    usample 1-2.1:1.0: USB Sample device now attached to usample-0
    usbcore: registered new interface driver usample
    / # 
    / # ls -l /dev/usample0 
    crw-rw----    1 root     0         180,   0 Mar 22 14:33 /dev/usample0
    / # 

    注意到USB设备名称编号(1-2.1:1.0)比较长,它是按照USB Controller - USB Hub / Port - USB Device - USB Interface这种方式逐级编号的,和当前USB设备的拓扑结构有关。

    用户态构建

    编译用户态程序可通过gcc来完成:

    gcc -Wall usample-main.c -o usample

    运行测试程序,设置传输速率、设置环回,写入字符并再次从设备将字符读取出来:

    / # usample -s 1000
    USample: set speed 1000 successfully.
    / # usample -l 1
    USample: set loopback 1 successfully.
    / # usample -p
    USample: get properties successfully.
        Speed   : 1000
        Loopback: 1
    / # 
    / # echo "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" > /dev/usample0 
    / # cat /dev/usample0 
    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
    / #

    7、总结

    本文介绍了实现一个USB设备驱动的结构与流程,涉及到的Linux内核USB驱动相关接口函数。

    USB设备驱动可以在逻辑上分为分为两个层级:

    • usb_driver。它是通用USB设备行为针对特定USB设备的具体实现,处理设备的发现、探测、卸载以及电源管理有关的动作。
    • usb_class_driver。它是面向特定USB设备功能的功能设计,系统中的功能性设备(鼠标键盘,存储,网络,视音频等)在这一层体现(包括字符设备在内的其他方式)。

    理解内核中设备层次模型,对理解设备驱动、进行设备驱动开发有很大的帮助。下一篇文件可能会尝试介绍内核设备管理模型。

    8、作者简介

    刘强,电子科学与技术&物理电子学专业。在大型通讯企业工作,从事嵌入式系统开发十年有余。在Linux内核&驱动开发方面积累了丰富的经验;对体系架构、操作系统有较为深厚的积累,在系统级性能优化做了一些颇有成效的工作。现在开始逐步将工作中的小积累,尝试变化成文字,一方面共享交流、结识更多的同行,另一方面也是加深自身的理解。

    展开全文
  • USB 打印机 模拟 LPT 接口

    千次阅读 2010-02-02 13:46:00
    由于工控机没有带LPT接口 所以就用了个USB转LPT的线连接POS机。 好了问题来了,原先打印是通过直接向LPT端口发送打印内容的,现在变成USB接口输出不了。 在查了大量资料后发现一个曲线救国的方法: 1.将本地的USB...

         最近由于工作原因,接触POS打印机。由于工控机没有带LPT接口 所以就用了个USB转LPT的线连接POS机。

         好了问题来了,原先打印是通过直接向LPT端口发送打印内容的,现在变成USB接口输出不了。

     

         在查了大量资料后发现一个曲线救国的方法:

         1.将本地的USB打印机设置成共享

         2.在run下输入 NET USE LPT1: //127.0.0.1/[共享的打印机名字] /Persistent:YES

     

         OK~再试一下直接打印到LPT1~~就大功告成了。

     

    展开全文
  • 摘 要:在控制系统中经常用到一些模拟信号,通常使用数模转换器输出所...可以通过USB接口来控制D/A转换器,使其输出要求的模拟量电压或模拟量电流。  USB接口作为微处理器常用的外部总线接口,目前已经得到了广泛的应
  • 一个winform程序来来演示怎样使用内部时钟连续测量模拟量,我用到的NI采集设备是NI USB-6366。
  • VB与数据采集卡模拟量输入(USB口),例程,里面有程序等,拿来直接了可以调试,运行并进行分析。作为例程,希望对大家有帮助
  • 硬件设备:CP1H-XA40DT-D(PLC),CP1W-MAD42(模拟量输入输出模块),USB电缆,S8VK-C06024(开关电源)软件:CX-Programmer案例简介:使用CP1H-XA40DT-D带CP1W-MAD42模块实现输入采集输出模拟量的功能。1. 系统概述,...

    硬件设备:CP1H-XA40DT-D(PLC),CP1W-MAD42(模拟量输入输出模块),USB

    电缆,S8VK-C06024(开关电源)

    软件:CX-Programmer

    案例简介:使用CP1H-XA40DT-D带CP1W-MAD42模块实现输入采集输出模拟

    量的功能。


    1. 系统概述,硬件搭建和接线

    (1) 将PLC接到DC24V直流电源上,USB电缆线和电脑连接,如图 1-1所示:

    7fc82c1d5efdb7b8b8b984e909abfd35.png

    (2) 将CP1W-MAD42 连接至CP1H,如图1-2所示:

    882b18dc28d3648cd9f8e087b0c6483a.png
    b104fac3dd3ab9211d573deb8aa7616e.png

    2.操作步骤

    (1)硬件设置:

    a.模拟量模块CP1W-MAD42端子排列如图2-1所示:

    591e7870b7ebead9f38aa24842b0417d.png

    b.模拟量模块的布线如图2-2所示:

    91124ddce1eee9915512cd1268c44782.png

    c. CP1W-MAD42输入地址分配说明:模拟量量程在 n+1、n+2中设置,模拟量

    输入1~4的值保存在m+1、m+2、m+3、m+4CH中,模拟量输出 1~2的值保存

    在n+1、n+2CH中。如图2-3所示:

    f02115f7ac6a1d5a1a3c85f470073deb.png

    注: n是分配给CPU单元或最后一个扩展单元的最后输出字, m是分配给CPU

    单元或最后一个扩展单元的最后输入字。

    本案例中使用的地址分配如下表所示:

    54e726cb76d95a4af377bf34abfc9211.png

    (2)软件操作:

    a. MAD42的设置通过量程代码写入,如图 2-4所示:

    0891299e4619310c57304f5bcbc120a3.png
    b0b782e7421f7afc358150c043b10f06.png

    量程代码设置如图2-5所示:

    22a687dfc0294d5b2b547bcdd45b03e0.png
    a2c6d6eb075d97fac439b25b33196875.png

    本案例使用模拟量输入1:-10V~+10V,使用平均化;模拟量输入3:4-20mA,

    使用平均化;模拟量输出 1:-10V~+10V;模拟量输出2:4-20mA;写入的量程代码如图2-6所示:

    cd30d934fd032f10572a7a9e3bbb0f47.png

    即在102中赋值880C Hex,在103中赋值8C0E Hex。

    b. 编程说明

    程序如图2-7所示:

    d367431ba0e3e5606565b8ced9e0d0a3.png

    注:从 电源接通开始到最初的转换数据保存到输入字为止, 要耗费2 个周期50ms

    左右。因此编写 TIM指令,当在电源打开同步开始运行时,等待转换数据成为

    有效的程序。完成初始化处理后,模拟量输入数据将变为 0000。

    c. 设置完成,在编程模式下将设置传送到 PLC,PLC断电重启,设置生效;再

    将程序传送到PLC。

    3、现象和结论:

    (1)切换到监视模式

    (2)在102CH依次写入设定值-6000(-10V)、&0(0V)、&6000(10V),可看

    到模c.拟输入1读取到的值如图 3-1所示;

    在103CH依次写入设定值&0(4mA)、&6000(12mA)、&6000(120mA),可

    看到模拟输入1读取到的值如图3-1所示

    ab655faeb079ffc43fd11e388b42bff3.png

    输入通道的值和输出的数字量基本保持一致。

    4、注意事项:

    (1)模拟量输入模块在电流输入下使用时,必须将电压输入端子和电流输入端

    子短路。

    (2)当输入范围设为 1~5V且电压降至0.8V以下或当输入范围设为 4~20mA

    且电流降至3.2mA 以下时,将启用断线检测功能。断线检测功能启用后,转换

    数据

    将被设为8,000 Hex。

    (3)当输入超过指定范围时, AD 转换数据将在下限或上限处保持不变。

    (4)CP1W-MAD42模块的分辨率是 12000。

    (5)不使用的输入也要进行设置,就像案例中的输入 2、4,要对应的写入 0。

    (6)对于模拟量输入,当均值计算位设为 1 时,最后8 个输入的平均值( 移动

    平均值)将作为转换数据输出。

    (7)不使用的输入,应短接“ +(VIN)”和“-(COM)”端子。

    来源:网络,版权归原作者所有,侵删

    展开全文
  • 从键盘到高吞吐磁盘驱动器,各种器件都能够采用这种低成本接口进行平稳运行的即插即用连接,用户基本不用花太多心思在上面。新的USB 3.0在保持与USB 2.0的兼容性的同时,还提供了下面的几项增强功能:极大提高了...
  •  USB 通用串行总线(英文:Universal Serial Bus,简称USB)是连接外部装置的一个串口汇流排标准,在计算机上使用广泛,但也可以用在机顶盒和游戏机上,补充标准On-The-Go( OTG)使其能够用于在便携装置之间直接...
  • 西门子S7-1200 模拟量如何编程

    万次阅读 2019-05-10 16:25:35
    你还在为安装自动化软件发愁吗?...本操作系统是win 7 64位 第一次使用注意事项: 1、把电脑的第一启动项设置成USB启动,不同电脑有不同方式,具体百度。 2、插入硬盘,打开电脑,等待加载,输入密码...

     

     

    你还在为安装自动化软件发愁吗?请加WX:HFTC7003

    移动硬盘: 品牌东芝 1TB内存, 硬盘里面是操作系统不要删除,以免无法启动操作系统。 可以在硬盘里面单独建一个文件夹,存放资料。
    本操作系统是win 7 64位 第一次使用注意事项: 1、把电脑的第一启动项设置成USB启动,不同电脑有不同方式,具体百度。 2、插入硬盘,打开电脑,等待加载,输入密码。 3、使用驱动精灵来匹配电脑驱动,更新完成后,和正常的电脑一模一样。 4、如果想使用原操作系统,USB孔不要插任何东西,直接启动即可。

    独立的操作系统,省去安装过程。 免安装各种工业软件,即插即用。 不影响原电脑系统,随时切换。 提供大量视频教程,边学边用。 提供大量资料,做事不用求人。

     

     

     

     

     

     

    展开全文
  • 爱特梅尔公司推出结合了USB控制器和高性能模拟功能的全新AVR微控制器产品,型号为ATmega16UA和ATmega32U4。这些器件可降低电池供电设备如游戏外设的系统成本。  虽然电池供电设备能够通过USB连接进行充电,然而,...
  • 双氙商品详情相关推荐商品标签云买过的人评价-[FX3U功能扩展板]FX3U-232-BD RS232串行通信扩展板,1通道FX3U-422-BD RS422串行通信扩展板,...USB-BD USB通信扩展板(适用于FX系列PLC)-[FX3G功能扩展板]FX3G-232-BD R...
  • 硬件准备: 除了开发板和USB线之外,还要购买面包板、跳线、可变电阻器(0-100...A3针是读取模拟量的ADC接口。leonardo另一端连计算机。 代码如下: int analogPin = A3; int iVal = 0; void setup() { Serial...
  •  通用序列汇流排(USB)规范自1996年发布以来,截至2012年为止已累积超过三十五亿个电脑周边设备的USB连接装置出货。2010年,当第一批支援USB 3.0规格的装置上市,销售就达到约一百万个,2012年更一举增长至五...
  • 通过调谐和接收距离你所在地点250英里以外飞机无线电信号,你就可以追踪飞机航线,而完成这个任务,仅需要随处就能购买到的廉价USB电视棒。本文将介绍如何追踪飞机飞行航道以及规律,同时介绍项目中用到的软件、硬件...
  • 引用 地狱贵公子 的 Alcor(安国) AU6983 4G三驱三启成功! 三驱是指将U盘分为3个驱动器,插电脑上你会看到一下子多出来三个盘符;...(即把一个分区模拟成一个USB接口的光驱,里面放入模拟的光盘,即IS...
  • 阿贝克传感器近期推出新型SCXT系列传感器数据采集系统,该采集系统,具有强大的数据分析能力,从传感器得到的电信号经过采集器处理后汇总到USB接口上,通过USB传输给上位机软件。 本系统由信号采集器和上位机软件...
  • USB通讯设置

    千次阅读 2015-06-08 22:53:55
    1.PMAC示意图 2.PMAC与拓展板及接口板的连接 ...模拟量,2脉冲量,3,PWM但是如果输出PWM,必须外接硬件电路,即ACC-8FS。 3通讯方式 PMAC与上位机有三种通讯模式: 1. 以太网 100Mbps 2. USB2.0 480M
  • 艾德克斯IT-M7700系列在家电行业谐波模拟的应用 2019-09-18 引言 随着电力电子技术的发展,各种电力电子装置设备及开关电源产品等已被广泛使用。技术高速发展的同时,也对于用电环境造成比较严重的污染,市电网络中...
  • 摘 要:在控制系统中经常用到一些模拟信号,通常使用数模转换器输出所...可以通过USB接口来控制D/A转换器,使其输出要求的模拟量电压或模拟量电流。  USB接口作为微处理器常用的外部总线接口,目前已经得到了广泛的应
  • 在制作USB下载线时要特别注意对Mega8的熔丝位的设置,很多朋友由于没很好的设置熔丝位,导致花费大量时间而含恨!由于该设计是模拟USB时序的下载器,更要注意对USB数据线电平的3.3V钳位!
  • 摘要:本文利用AVR单片机ATmega8软USB虚拟RS232接口技术,通过单片机内置A/D转换器对模拟量数据采集,取代以RS232标准接口模拟量数据采集器。利用现有软件资源,实现USB软接口多点模拟量数据采集功能。本文硬件电路...
  • 目前,USB接口的使用越来越广泛,许多设备上都提供了USB接口。传统的USB控制器需要使用... MAX3420是Maxim公司推出的一款全速USB外设,包括必要的数字逻辑和模拟电路,支持USB2.O协议。MAX3420  内建全速收发器,支
  • usb_bootloader.zip

    2020-04-29 21:45:33
    1: 插入电脑USB接口 2: 把升级固件拖到设备盘符 3: 升级完成 抛弃繁琐的USB DFU,抛弃落后的串口升级,让我们来谈谈U盘升级STM32 1. 为什么设计这个BOOT LOADER 在电子产品开发过程中,为了满足市场需要,经常是...
  • arduino usb midi 调音台

    2018-01-15 21:27:09
    功能是实现了10个推子的调音台,会用Arduino的人看一眼程序就知道电路是啥样的了,就是10个模拟量输入。提过想实现其他的控制功能或midi键盘,只需要简单修改一下就行了。 在此公布出来给广大midi爱好者,希望更多人...
  • 的整合单片机、混合信号、模拟器件和闪存解决方案的供应商--Microchip Technology Inc.(美国微芯科技公司)近日宣布推出全新汽车级4端口USB84604 IC,拓展其USB2控制器集线器产品线。全新USB84604 UCH2采用...
  • 1 引 言 在智能仪器、信号处理以及工业自动控制等领域,都存在着数据的测量与控制问题,常常需要将外部的温度、压力、流量、位移等模拟量进行采集。目前常用的数据采集方式是通过数据采集板卡,常用的有ISA总线,...
  • 摘要:利用ADuC845单片数据采集器件和CH341 USB接口器件构成的数据采集与控制系统,具有10个24位的A/D转换器输入通道,60 Hz范围内有20位有效...ADuC845完成模拟量数据采集、开关量的输入输出、控制电压和PWM控制信号输

空空如也

空空如也

1 2 3 4 5 ... 12
收藏数 231
精华内容 92
关键字:

usb模拟量