30.1 USB简介
30.1.1 什么是USB?
USB是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,USB就是简写,中文叫通用串行总线。最早出现在1995年,伴随着奔腾机发展而来。自微软在Windows 98中加入对USB接口的支持后,USB接口才推广开来,USB设备也日渐增多,如数码相机、摄像头、扫描仪、游戏杆、打印机、键盘、鼠标等等,其中应用最广的就是摄像头和U盘了。
USB包括老旧的USB 1.1标准和时下正流行的USB 2.0标准。传统的USB 1.1最高传输速率为12Mbps,一般厂商将其符合USB 1.1标准的产品称为“全速USB”。而高速USB 2.0最初推出时最高传输速率仅为240Mbps,后来USB2.0推广组(USB Promoter Group)在1999年10月将该速率提高到480Mbps,比传统的USB 1.1快40倍。
USB2.0向下兼容USB 1.1,当然USB1.1设备也“向上兼容”USB 2.0,但是无法实现USB2.0的传输能力,并自动以低速传输。USB 2.0连接线的最大长度为5米,但如果用五个USB适配器,则最大长度可达30米。
最新一代是USB 3.1,传输速度为10Gbit/s,三段式电压5V/12V/20V,最大供电100W ,新型Type C插型不再分正反。
USB采用四线电缆,其中两根是用来传送数据的串行通道,另两根为下游(Downstream)设备提供电源,对于高速且需要高带宽的外设,USB以全速12Mbps的传输数据;对于低速外设,USB则以1.5Mbps的传输速率来传输数据。USB总线会根据外设情况在两种传输模式中自动地动态转换。USB是基于令牌的总线。类似于令牌环网络或FDDI基于令牌的总线。USB主控制器广播令牌,总线上设备检测令牌中的地址是否与自身相符,通过接收或发送数据给主机来响应。USB通过支持悬挂/恢复操作来管理USB总线电源。USB系统采用级联星型拓扑,该拓扑由三个基本部分组成:主机(Host),集线器(Hub)和功能设备。
主机,也称为根,根结或根Hub,它做在主板上或作为适配卡安装在计算机上,主机包含有主控制器和根集线器(Root Hub),控制着USB总线上的数据和控制信息的流动,每个USB系统只能有一个根集线器,它连接在主控制器上。
集线器是USB结构中的特定成分,它提供叫做端口(Port)的点将设备连接到USB总线上,同时检测连接在总线上的设备,并为这些设备提供电源管理,负责总线的故障检测和恢复。集线可为总线提供能源,亦可为自身提供能源(从外部得到电源),自身提供能源的设备可插入总线提供能源的集线器中,但总线提供能源的设备不能插入自身提供能源的集线器或支持超过四个的下游端口中,如总线提供能源设备的需要超过100mA电源时,不能同总线提供电源的集线器连接。
USB介绍: http://www.usb.org/home
30.1.2 USB设备主要优点总结
1. 可以热插拔
用户在使用外接设备时,不需要关机再开机等动作,而是在电脑工作时,直接将USB插上使用。
2. 携带方便
USB设备大多以“小、轻、薄”见长,对用户来说,随身携带大量数据时,很方便。当然USB硬盘是首要之选了。
3. 标准统一。
大家常见的是IDE接口的硬盘,串口的鼠标键盘,并口的打印机扫描仪,可是有了USB之后,这些应用外设统统可以用同样的标准与个人电脑连接,这时就有了USB硬盘、USB鼠标、USB打印机等等。
4. 可以连接多个设备。
USB在个人电脑上往往具有多个接口,可以同时连接几个设备,如果接上一个有四个端口的USB HUB时,就可以再连上四个USB设备,以此类推 (注:最高可连接至127个设备,扩展到一定数量时需要外加电源)
30.1.3 USB电器接口定义
一般的排列方式是:红白绿黑从左到右
定义:
- 红色-USB电源: 标有-VCC、Power、5V、5VSB字样
- 白色-USB数据线:(负)-DATA-、USBD-、PD-、USBDT-
- 绿色-USB数据线:(正)-DATA+、USBD+、PD+、USBDT+
- 黑色-地线: GND、Ground
30.1.4 USB的插入检测机制
USB端口的D+和D-均用一个15k的电阻接地,当无设备接入时,均处于低电平;在设备端在D+(表示高速设备或者全速设备)或者D-(表示低速设备)接了一个1.5k的上拉电阻到+3.3v,一旦将设备接入,USB端口的D+或者D-其中一个被拉高为3v,系统识别到外部设备接入。
注意:高速设备首先会被识别为全速设备,然后再通过集线器和设备二者的确认最后切换到高速模式下。
在高速模式下,采用的是电流传输模式,这个时候上拉电阻需要从D+上断开。
usb主机检测到USB设备插入后,就要对设备进行枚举了。枚举的作用就是从设备是那个读取一些信息,知道设备是什么样的设备,如果通信,这样主机就可以根据这些信息选择合适的驱动程序。调试USB设备,很重要的一点就是USB枚举过程,只要枚举成功了,那就成功一大半了。
当设备没有枚举成功时(可以通过一个10K的电阻将USB的电源端和D+或者D-连接起来,电脑会发现一个无法识别的设备,这个设备的PID和VID都是0,根据每个特性可以简单的判定设备的枚举是否成功。
30.2 USB标准描述符
USB协议为USB设备定义了一套描述设备功能和属性的有固定结构的描述符,包括标准的描述符即设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符。USB设备通过这些描述符向USB主机汇报设备的各种各样属性,主机通过对这些描述符的访问对设备进行类型识别、配置并为其提供相应的客户端驱动程序。
USB设备通过描述符反映自己的设备特性。USB描述符是由特定格式排列的一组数据结构组成。
在USB设备枚举过程中,主机端的协义软件需要解析从USB设备读取的所有描述符信息。在USB主向设备发送读取描述符的请求后,USB设备将所有的描述符以连续的数据流方式传输给USB主机。主机从第一个读到的字符开始,根据双方规定好的数据格式,顺序地解析读到的数据流。
USB描述符包含标准描述符、类描述符和厂商特定描述3种形式。任何一种设备必须遵循USB标准描述符(除了字符串描述符可选外)。
在USB1.X中,规定了5种标准描述符:设备描述符(Device Descriptor)、配置描述符(Configuration Descriptor)、接口描述符(Interface Descriptor)、端点描述符(Endpoint Descriptor)和字符串描述符(String Descriptor)。
每个USB设备只有一个设备描述符,而一个设备中可包含一个或多个配置描述符,即USB设备可以有多种配置。设备的每一个配置中又可以包含一个或多个接口描述符,即USB设备可以支持多种功能(接口),接口的特性通过描述符提供。
在USB主机访问USB设备的描述符时,USB设备依照设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符顺序将所有描述符传给主机。一设备至少要包含设备描述符、配置描述符和接口描述符,如果USB设备没有端点描述符,则它仅仅用默认管道与主机进行数据传输。
30.2.1 设备描述符
/* USB_DT_DEVICE: Device descriptor */
struct usb_device_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__le16 bcdUSB;
__u8 bDeviceClass;
__u8 bDeviceSubClass;
__u8 bDeviceProtocol;
__u8 bMaxPacketSize0;
__le16 idVendor;
__le16 idProduct;
__le16 bcdDevice;
__u8 iManufacturer;
__u8 iProduct;
__u8 iSerialNumber;
__u8 bNumConfigurations;
} __attribute__ ((packed));
//USB设备信息与驱动端匹配成功的时候调用。
static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数
{
printk("识别到USB光谱仪设备,正在进行设备初始化.\n");
printk("该设备的厂商ID=%#x,设备ID:%#x\n",id->idVendor,id->idProduct);
/*通过接口获取设备信息*/
struct usb_device *dev = interface_to_usbdev(intf);
printk("bcdUSB = %#x\n",dev->descriptor.bcdUSB); //从USB设备描述符中获取USB版本
printk("vidUSB = %#x\n",dev->descriptor.idVendor); //从USB设备描述符中获取厂商ID
printk("pidUSB = %#x\n",dev->descriptor.idProduct);//从USB设备描述符中获取产品ID
/*-------------------------------*/
}
设备描述符给出了USB设备的一般信息,包括对设备及在设备配置中起全程作用的信息,包括制造商标识号ID、产品序列号、所属设备类号、默认端点的最大包长度和配置描述符的个数等。一个USB设备必须有且仅有一个设备描述符。设备描述符是设备连接到总线上时USB主机所读取的第一个描述符,它包含了14个字段,结构如下:
USB设备描述符的结构 | ||||
偏移量 | 域 | 大小 | 值 | 描述 |
0 | bLength | 1 | 数字 | 此描述表的字节数 |
1 | bDecriptorType | 1 | 常量 | 描述符的类型(此处应为0x01,即设备描述符) |
2 | bcdUSB | 2 | BCD码 | 此设备与描述表兼容的USB设备说明版本号(BCD 码) |
4 | bDeviceClass | 1 | 类 | 设备类码: 如果此域的值为0则一个设置下每个接口指出它自己的类,各个接口各自独立工作。 |
5 | bDeviceSubClass | 1 | 子类 | 子类码 这些码值的具体含义根据bDeviceClass 域来看。 |
6 | bDevicePortocol | 1 | 协议 | 协议码 这些码的值视bDeviceClass 和 bDeviceSubClass 的值而定。 如果设备支持设备类相关的协议,此码标志了设备类的值。如果此域的值为零,则此设备不支持设备类相关的协议,然而,可能它的接口支持设备类相关的协议。如果此域的值为FFH,此设备使用厂商定义的协议。 |
7 | bMaxPacketSize0 | 1 | 数字 | 端点0的最大包大小(仅8,16,32,64为合法值) |
8 | idVendor | 2 | ID | 厂商标志(由USB-IF组织赋值) |
10 | idProduct | 2 | ID | 产品标志(由厂商赋值) |
12 | bcdDevice | 2 | BCD码 | 设备发行号(BCD 码) |
14 | iManufacturer | 1 | 索引 | 描述厂商信息的字符串描述符的索引值。 |
15 | iProduct | 1 | 索引 | 描述产品信息的字串描述符的索引值。 |
16 | iSerialNumber | 1 | 索引 | 描述设备序列号信息的字串描述符的索引值。 |
17 | bNumConfigurations | 1 | 数字 | 可能的配置描述符数目 |
其中bDescriptorType为描述符的类型,其含义可查下表(此表也适用于标准命令Get_Descriptor中wValue域高字节的取值含义):
USB描述符的类型值 | ||
类型 | 描述符 | 描述符值 |
标准描述符 | 设备描述符(Device Descriptor) | 0x01 |
配置描述符(Configuration Descriptor) | 0x02 | |
字符串描述符(String Descriptor) | 0x03 | |
接口描述符(Interface Descriptor) | 0x04 | |
端点描述符(EndPont Descriptor) | 0x05 | |
类描述符 | 集线器类描述符(Hub Descriptor) | 0x29 |
人机接口类描述符(HID) | 0x21 | |
厂商定义的描述符 | 0xFF |
设备类代码bDeviceClass可查下表:
设备的类别(bDeviceClass) | ||
值(十进制) | 值(十六进制) | 说明 |
0 | 0x00 | 接口描述符中提供类的值 |
2 | 0x02 | 通信类 |
9 | 0x09 | 集线器类 |
220 | 0xDC | 用于诊断用途的设备类 |
224 | 0xE0 | 无线通信设备类 |
255 | 0xFF | 厂商定义的设备类 |
下表列出了一个USB鼠标的设备描述符的例子,供大家分析一下:
一种鼠标的设备描述符示例 | |
字段 | 描述符值(十六制) |
bLength | 0x12 |
bDecriptorType | 0x01 |
bcdUSB | 0x110 |
bDeviceClass | 0x00 |
bDeviceSubClass | 0x00 |
bDevicePortocol | 0x00 |
bMaxPacketSize0 | 0x08 |
idVendor | 0x045E(Microsoft Corporation) |
idProduct | 0x0047 |
bcdDevice | 0x300 |
iManufacturer | 0x01 |
iProduct | 0x03 |
iSerialNumber | 0x00 |
bNumConfigurations | 0x01 |
- Linux内核中定义的设备描述符结构
struct usb_device_descriptor { 描述符长度 描述符类型编号 版本号 分配的设备类code 分配的子类code 分配的协议code 最大包大小 厂商编号 产品编号 设备出厂编号 描述厂商字符串的索引 描述产品字符串的索引 描述设备序列号字符串的索引 可能的配置数量 } _ _attribute_ _ ((packed)); |
30.2.2 配置描述符
配置描述符中包括了描述符的长度(属于此描述符的所有接口描述符和端点描述符的长度的和)、供电方式(自供电/总线供电)、最大耗电量等。主果主机发出USB标准命令Get_Descriptor要求得到设备的某个配置描述符,那么除了此配置描述符以外,此配置包含的所有接口描述符与端点描述符都将提供给USB主机。
USB配置描述符的结构 | ||||
偏移量 | 域 | 大小 | 值 | 描述 |
0 | bLength | 1 | 数字 | 此描述表的字节数长度。 |
1 | bDescriptorType | 1 | 常量 | 配置描述表类型(此处为0x02) |
2 | wTotalLength | 2 | 数字 | 此配置信息的总长(包括配置,接口,端点和设备类及厂商定义的描述符) |
4 | bNumInterfaces | 1 | 数字 | 此配置所支持的接口个数 |
5 | bCongfigurationValue | 1 | 数字 | 在SetConfiguration()请求中用作参数来选定此配置。 |
6 | iConfiguration | 1 | 索引 | 描述此配置的字串描述表索引 |
7 | bmAttributes | 1 | 位图 | 配置特性: D7: 保留(设为一) |
8 | MaxPower | 1 | mA | 在此配置下的总线电源耗费量。以 2mA 为一个单位。 |
下面是一种硬盘的配置描述符示例:
一种硬盘的配置描述符示例 | |
字段 | 描述符值(十六进制) |
bLength | 0x09 |
bDescriptorType | 0x02 |
wTotalLength | 0x01F |
bNumInterfaces | 0x01 |
bCongfigurationValue | 0x01 |
iConfiguration | 0x00 |
bmAttributes | 0x0C |
MaxPower | 0x32 |
- Linux内核中定义的配置描述符结构
struct usb_config_descriptor { 描述符长度 描述符类型编号 配置所返回的所有数据的大小 配置所支持的接口数 命令需要的参数值 描述该配置的字符串的索引值 供电模式的选择 设备从总线提取的最大电流 } _ _attribute_ _ ((packed)); |
30.2.3 接口描述符
配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
如果一个配置描述符不止支持一个接口描述符,并且每个接口描述符都有一个或多个端点描述符,那么在响应USB主机的配置描述符命令时,USB设备的端点描述符总是紧跟着相关的接口描述符后面,作为配置描述符的一部分被返回。接口描述符不可直接用Set_Descriptor和Get_Descriptor来存取。
如果一个接口仅使用端点0,则接口描述符以后就不再返回端点描述符,并且此接口表现的是一个控制接口的特性,它使用与端点0相关联的默认管道进行数据传输。在这种情况下bNumberEndpoints域应被设置成0。接口描述符在说明端点个数并不把端点0计算在内。
USB接口描述符的结构 | ||||
偏移量 | 域 | 大小 | 值 | 说明 |
0 | bLength | 1 | 数字 | 此表的字节数 |
1 | bDescriptorType | 1 | 常量 | 接口描述表类(此处应为0x04) |
2 | bInterfaceNumber | 1 | 数字 | 接口号,当前配置支持的接口数组索引(从零开始)。 |
3 | bAlternateSetting | 1 | 数字 | 可选设置的索引值。 |
4 | bNumEndpoints | 1 | 数字 | 此接口用的端点数量,如果是零则说明此接口只用缺省控制管道。 |
5 | bInterfaceClass | 1 | 类 | 接口所属的类值: 零值为将来的标准保留。 如果此域的值设为FFH,则此接口类由厂商说明。 所有其它的值由USB 说明保留。 |
6 | bInterfaceSubClass | 1 | 子类 | 子类:这些值的定义视bInterfaceClass域而定。 如果bInterfaceClass域的值为零则此域的值必须为零。 bInterfaceClass域不为FFH则所有值由USB 所保留。 |
7 | bInterfaceProtocol | 1 | 协议 | 协议码:bInterfaceClass 和bInterfaceSubClass 域的值而定.如果一个接口支持设备类相关的请求此域的值指出了设备类说明中所定义的协议. |
8 | iInterface | 1 | 索引 | 描述此接口的字串描述表的索引值。 |
对于bInterfaceClass字段,表示接口所属的类别,USB协议根据功能将不同的接口划分成不的类,其具体含义如下表所示:
USB协议定义的接口类别(bInterfaceClass) | |
值(十六进制) | 类别 |
0x01 | 音频类 |
0x02 | CDC控制类 |
0x03 | 人机接口类(HID) |
0x05 | 物理类 |
0x06 | 图像类 |
0x07 | 打印机类 |
0x08 | 大数据存储类 |
0x09 | 集线器类 |
0x0A | CDC数据类 |
0x0B | 智能卡类 |
0x0D | 安全类 |
0xDC | 诊断设备类 |
0xE0 | 无线控制器类 |
0xFE | 特定应用类(包括红外的桥接器等) |
0xFF | 厂商定义的设备 |
- Linux内核中定义的接口描述符结构
struct usb_interface_descriptor { 描述符长度 _ _u8 bDescriptorType; //描述符类型 接口的编号 _ _u8 bAlternateSetting; //备用的接口描述符编号 该接口使用的端点数,不包括端点0 _ _u8 bInterfaceClass; //接口类型 _ _u8 bInterfaceSubClass; //接口子类型 _ _u8 bInterfaceProtocol; //接口所遵循的协议 _ _u8 iInterface; //描述该接口的字符串索引值 } _ _attribute_ _ ((packed)); |
30.2.4 端点描述符
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。
除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。
static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数
{
/*获取端点描述符*/
struct usb_endpoint_descriptor *endpoint = &interface->endpoint[0].desc;
switch(endpoint->bmAttributes)
{
case 0:printk("设备支持控制传输.\n");break;
case 1:printk("设备支持同步传输.\n");break;
case 2:printk("设备支持批量传输.\n");break;
case 3:printk("设备支持中断传输.\n");break;
}
}
struct usb_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bEndpointAddress;
__u8 bmAttributes;
__le16 wMaxPacketSize;
__u8 bInterval;
__u8 bRefresh;
__u8 bSynchAddress;
} __attribute__ ((packed));
USB端点描述符的结构 | ||||
偏移量 | 域 | 大小 | 值 | 说明 |
0 | bLength | 1 | 数字 | 此描述表的字节数长度 |
1 | bDescriptorType | 1 | 常量 | 端点描述表类(此处应为0x05) |
2 | bEndpointAddress | 1 | 端点 | 此描述表所描述的端点的地址、方向: |
3 | bmAttributes | 1 | 位图 | 此域的值描述的是在bConfigurationValue域所指的配置下端点的特性。 00=控制传送 01=同步传送 10=批传送 11=中断传送 所有其它的位都保留。 |
4 | wMaxPacketSize | 2 | 数字 | 当前配置下此端点能够接收或发送的最大数据包的大小。 对于实进传输,此值用于为每帧的数据净负荷预留时间。在实际运行时,管道可能不完全需要预留的带宽,实际带宽可由设备通过一种非USB定义的机制汇报给主机。对于中断传输,批量传输和控制传输,端点可能发送比之短的数据包 |
6 | bInterval | 1 | 数字 | 周期数据传输端点的时间间隙。 此域的值对于批传送的端点及控制传送的端点无意义。对于同步传送的端点此域必需为1,表示周期为1ms。对于中断传送的端点此域值的范围为1ms到255ms。 |
下表是一种鼠标的端点描述符的示例,该端点是一个中断端点:
一种鼠标的端点描述符示例 | |
域 | 值(十六进制) |
bLength | 0x07 |
bDescriptorType | 0x05 |
bEndpointAddress | 0x81 |
bmAttributes | 0x03 |
wMaxPacketSize | 0x04 |
bInterval | 0x0A |
一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:
- 控制CONTROL 控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个 USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.
- 中断INTERRUPT 每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.
- 批量BULK批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB 协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USB Mass Storage和USB网络设备上。
- 等时ISOCHRONOUS 等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。
控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。
端点在linux内核中使用结构 struct usb_host_endpoint 来描述,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。 - Linux内核中定义的端点描述符结构
struct usb_endpoint_descriptor { 描述符长度 描述符类型 端点地址:0~3位是端点号,第7位是方向(0-OUT,1-IN) 端点属性:bit[0:1] 的值为00表示控制,为01表示同步,为02表示批量,为03表示中断 本端点接收或发送的最大信息包的大小 轮询数据传送端点的时间间隔 对于批量传送的端点以及控制传送的端点,此域忽略 //对于同步传送的端点,此域必须为1 _ _u8 bRefresh; _ _u8 bSynchAddress; } _ _attribute_ _ ((packed)); |
30.2.5 字符串描述符
字符串描述符是一种可选的USB标准描述符,描述了如制商、设备名称或序列号等信息。如果一个设备无字符串描述符,则其它描述符中与字符串有关的索引值都必须为0。字符串使用的是Unicode编码。
主机请示得到某个字符串描述符时一般分成两步:首先主机向设备发出USB标准命令Get_Descriptor,其中所使用的字符串的索引值为0,设备返回一个字符串描述符,此描述符的结构如下:
USB字符串描述符(响应主机请求时返回的表示语言ID的字符串描述符) | ||||
偏移量 | 域 | 大小 | 值 | 描述 |
0 | bLength | 1 | N+2 | 此描述表的字节数 |
1 | bDescriptorType | 1 | 常量 | 字串描述表类型(此处应为0x03) |
2 | wLANGID[0] | 2 | 数字 | 语言标识(LANGID) 码0 |
… | … | … | … | … |
N | wLANGID[x] | 2 | 数字 | 语言标识(LANGID)码X |
该字符串描述符双字节的语言ID的数组,wLANGID[0]~wLANGID[x]指明了设备支持的语言,具体含义可查看USB_LANGIDs.pdf。
主机根据自己需要的语言,再次向设备发出USB标准命令Get_Descriptor,指明所要求得到的字符串的索引值和语言。这次设备所返回的是Unicode编号的字符串描述符,其结构如下:
Unicode字符串描述符(响应主机请求时真正表示字符串编码的字符串描述符) | ||||
偏移量 | 域 | 大小 | 值 | 描述 |
0 | bLength | 1 | 数字 | 此描述表的字节数(bString域的数值N+2) |
1 | bDescriptorType | 1 | 常量 | 字串描述表类型(此处应为0x03) |
2 | bString | N | 数字 | UNICODE 编码的字串 |
bString域为设备实际返回的以UNICODE编码的字符串流,我们在编写设备端硬件驱动的时候需要将字符串转换为UNICODE编码,可以通过一些UNICODE转换工具进行转换。这里推荐由百合电子工作室开发的一款USB描述符生成工具“USB Unicode 字符串描述符生成器”,它专门为编写设备端驱动程序的需要而定制,可以非常方便将需要的字符串转换成UNICODE格式,进而导入C或汇编程序代码中。
- Linux内核中定义的字符串描述符结构
struct usb_string_descriptor { 描述符长度 描述符类型 _ _le16 wData[1]; } _ _attribute_ _ ((packed)); |
30.3 HID描述符
30.3.1 HID描述符介绍
USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。 它是 USB 协议最早支持的一种设备类。 HID 设备可以作为低速、全速、高速设备用。由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。
在 USB 协议中, HID 设备的定义放置在接口描述符中, USB 的设备描述符和配置描述符中不包含 HID 设备的信息。因此,对于某些特定的 HID 设备,可以定义多个接口,只有其中一个接口为 HID 设备类即可。
当定义一个设备为 HID 设备时,其设备描述符应为:
bDeviceClass=0 bDeviceSubClass=0 bDeviceProtocol=0 |
其接口描述符应该:
bInterfaceClass=0x03 |
- 另外(接口描述符)对无引导的 HID 设备,子类代码 bInterfaceSubClass 应置 0,此时 bInterfaceProtocol 无效,置零即可。即为:
bInterfaceClass=0x03 bInterfaceSubClass=0 bInterfaceProtocol=0 |
对支持引导的 USB 设备,子类代码 bInterfaceSubClass 应置 1,此时 bInterfaceProtocol 可以为 1 或 2, 1 表示键盘接口, 3 表示鼠标接口。其参考设置如下:
bInterfaceClass=0x03 bInterfaceSubClass=1 bInterfaceProtocol=1 或 2 |
HID 设备支持 USB 标准描述符中的五个:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符。除此之外, HID 设备还有三种特殊的描述符: HID 描述符、报告描述符、物理描述符。一个 USB 设备只能支持一个 HID 描述符,但可以支持多个报告描述符,而物理描述符则可以有也可以没有。
30.3.2 HID 描述符
HID 描述符用于识别 HID 设备中所包含的额外描述符,例如报告描述符或物理描述符等。
其格式如下:
字段名 | 长度/字节 | 地址偏移量 | 说明 |
bLength | 1 | 0 | 报告描述符长度(字节) |
bDescriptorType | 1 | 1 | 描述符类型: 0x21 |
bcdHID | 2 | 2 | HID版本号(BCD 码) |
bCountryCode | 1 | 4 | 国家/地区代码 |
bNumDescriptor | 1 | 5 | 支持的其他类型描述符数量 |
bDescriptorType | 1 | 6 | 类别描述符的类型 |
wDescriptorLength | 2 | 7 | 报告描述符的总长度 |
bDescriptorType | 1 | 9 | 用于识别描述符类型的常数 |
wDescriptorLength | 2 | 10 | 描述符的总长度 |
- 各字段含义如下:
- bLength: HID 描述符长度。
- bDescriptorType: HID 描述符类型,值为 0x21。
- bcdHID: HID 设备所遵循的 HID 版本号,为 4 位 16 进制的 BCD 码数据。 1.0 即 0x0100, 1.1 即 0x01
- bCountryCode: HID 设备国家/地区代码。
- bNumDescriptor: HID 设备支持的其01, 2.0即 0x0200。他设备描述符的数量。由于 HID 设备至少需要包括一个报告描述符,故其值至小为 0x01。
- bDescriptorType: HID 描述符附属的类别描述符长度。
- bDescriptorType/wDescriptorLength:可选字段,用于表示 HID 描述符附属的类别描述符类型及长度。
30.3.3 报告描述符
HID 设备的报告描述符是一种数据报表,主要用于定义 HID 设备和 USB 主机之间的数据交换格式, HID 设备报告描述符的类型值为 0x22。
报告描述符使用自定义的数据结构,用于传输特定的数据包。
例如对于键盘,需要在数据包中指明按键的值,报告描述符把这些数据打包发给主机,主机对发来的数据进行处理。它有四个组成部分,其格式如下:
0 ~1 2~ 3 4 5 6 7 8~ 23 (共24位,占4个字节) bSize bType bTag [data] |
- 各字段含义:
- bSize:占用两个位, 指示数据部分,即[data]字段的长度, 00bà表没有数据字节, 01bà表只有一个数据字节, 10b 表示有两个数据字节, 11b 表有 4 个数据字节。
- bType:数据项类型,用于指明数据项的类型。 00bà主数据类型, 01bà全局数据类型, 10bà局部数据类型, 11bà保留。
- bTag:数据项标签,用于指明数据项的功能。报告描述符需要包含的数据项标签有:输入输出数据项标签、用法数据项标签、用法页数据项标签、逻辑最小和最大值数据项标签、报告大小数据项标签以及报告计数数据项标签。
- [data]:数据字节,随着前面 bSize 定义的大小而变化。
30.3.4 物理描述符
HID 设备的物理描述符主要用于报告物理设备的激活信息,其类型值为 0x23,它是可选的,对大部分设备不需要使用此描述符。
30.4 linux内核下USB相关的API函数与数据结构
前面介绍了USB相关一些基础概念与重要的数据结构,接下来就分析在linux内核中如何编写一个USB 驱动程序,编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。
驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口。
USB构成框图:
30.4.1 USB设备注册与注销
//注册USB设备 #define usb_register(driver) \ usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) //注销USB设备 void usb_deregister(struct usb_driver *driver) //需要添加的头文件 #include <linux/usb.h> |
30.4.2 USB设备注册框架示例
#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> //定义USB的IDTAB static const struct usb_device_id usbtest[] = { {//148f:760b USB_DEVICE(0x148f, 0x760b),/*360WIFI的制造商ID和产品ID */ }, }; //USB设备信息与驱动端匹配成功的时候调用。 static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数 { printk("USB 驱动匹配成功!\n"); return 0; } //USB断开的时候调用 static void test_disconnect(struct usb_interface *intf) { printk("USB 设备释放成功!\n"); } //定义USB驱动结构体 static struct usb_driver usbtest_driver = { .name = "linux_usb_drv", .id_table = usbtest, .probe = test_probe, .disconnect = test_disconnect }; static int __init usbtest_init(void) { //注册USB设备驱动 usb_register(&usbtest_driver); return 0; } module_init(usbtest_init); static void __exit usbtest_exit(void) { //注销USB设备驱动 usb_deregister(&usbtest_driver); } module_exit(usbtest_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL"); |
30.4.3 struct usb_driver数据结构
struct usb_driver { const char *name; //USB设备的名字,名称可以随便填写。 int (*probe) (struct usb_interface *intf,资源探索函数,当usB驱动端与设备端匹配成功的时候调用。 void (*disconnect) (struct usb_interface *intf); //USB设备断开时调用。 const struct usb_device_id *id_table; //USB设备的匹配ID列表 …………….. }; |
列出的为必填成员。
- struct usb_device_id 设备ID结构原型如下:
struct usb_device_id { /* 确定设备信息去和结构体中哪几个字段匹配来判断驱动的适用性,比如是否是HID协议等*/ __u16match_flags; /*用于特定于产品的匹配 */ __u16idVendor; /*USB设备的制造商ID,须向www.usb.org申请*/ __u16idProduct; // USB设备的产品ID,有制造商自定 __u16bcdDevice_lo; /* USB设备的产品版本号最低值*/ __u16bcdDevice_hi; /* 和最高值,以BCD码来表示。*/ /* 分别定义设备的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中. 这些值指定这个设备的行为, 包括设备上所有的接口 */ __u8bDeviceClass; __u8bDeviceSubClass; __u8bDeviceProtocol; /* 分别定义单个接口的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中 */ __u8bInterfaceClass; __u8bInterfaceSubClass; __u8bInterfaceProtocol; /* 这个值不用来匹配驱动的, 驱动用它来在 USB 驱动的探测回调函数中区分不同的设备 该成员一般来保存一个结构体指针,存放该设备特殊的数据 */ kernel_ulong_tdriver_info; |
- 填充struct usb_device_id结构体中的__u16match_flags成员用到的宏定义:
#define USB_DEVICE_ID_MATCH_VENDOR0x0001 /*供应商厂家ID*/ #define USB_DEVICE_ID_MATCH_PRODUCT0x0002产品ID*/ |
- 快速填充usb_device_id结构体的相关宏:
USB_DEVICE_ID_MATCH_INT_INFO --根据接口信息 USB_DEVICE_ID_MATCH_DEV_INFO --根据设备的信息 USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION --根据设备制造信息和版本 USB_DEVICE_ID_MATCH_DEV_RANGE --根据设备版本 USB_DEVICE_ID_MATCH_DEVICE 根据设备制造信息 |
- struct usb_device_id结构体填充示例1—(摘自DM9620-USB网卡)
static const struct usb_device_id products[] = { { USB_DEVICE(0x07aa, 0x9601),/* Corega FEther USB-TXC */ .driver_info = (unsigned long)&dm9620_info, }, { USB_DEVICE(0x0a46, 0x9601),/* Davicom USB-100 */ .driver_info = (unsigned long)&dm9620_info, }, } |
- struct usb_device_id结构体填充示例2------->(摘自内核自带的鼠标驱动)
static struct usb_device_id usb_mouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { }/* 结束 */ }; |
- USB鼠标USB驱动端结构体填充示例:
static struct usb_driver usb_mouse_driver = { .name= "usbmouse", /*鼠标的名称-不是匹配使用*/ .probe= usb_mouse_probe,资源探测函数-匹配成功调用*/ .disconnect= usb_mouse_disconnect,/*当鼠标断开连接调用*/ .id_table= usb_mouse_id_table,匹配驱动使用*/ }; |
30.4.4 获取当前设备信息
struct usb_device *interface_to_usbdev(struct usb_interface *intf) |
用法示例:
static int usb_driver_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *dev = interface_to_usbdev(intf); /*设备与驱动匹配成功时,在_probe函数里获取设备的信息*/ } |
30.4.5 创建数据传输管道
管道是USB设备通信的通道,内核中提供了创建管道的宏,从以下内核定义宏中我们可以分析出,管道是一个 int 型的变量,由设备号、端点地址、端点类型组合而成。
//控制CONTROL #define usb_sndctrlpipe(dev, endpoint)\ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint)) //默认输出 #define usb_rcvctrlpipe(dev, endpoint)\ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //输入 //中断INTERRUPT #define usb_sndisocpipe(dev, endpoint)\ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvisocpipe(dev, endpoint)\ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 //批量BULK #define usb_sndbulkpipe(dev, endpoint)\ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvbulkpipe(dev, endpoint)\ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 //等时ISOCHRONOUS #define usb_sndintpipe(dev, endpoint)\ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvintpipe(dev, endpoint)\ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 static inline unsigned int __create_pipe(struct usb_device *dev,unsigned int endpoint) { return (dev->devnum << 8) | (endpoint << 15); } |
端点是有四种的,对应着管道也就有四种,同时端点是有IN也有OUT,相应的管道也就有两个方向,于是二四得八,上面就出现了八个创建管道的宏。有了struct usb_device结构体,也就是说知道了设备地址,再加上端点号,就可以根据需要创建指定的管道。__create_pipe宏只是一个幕后的角色,用来将设备地址和端点号放在管道正确的位置上。
- 示例:
struct usb_device *dev = interface_to_usbdev(intf); struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint;/* 定义端点描述符 */ interface = intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; int pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /*创建管道*/ |
30.4.6 动态分配urb
urb(USB Request Block)Linux内核中USB驱动实现上的一个数据结构,用于组织每一次的USB设备驱动的数据传输请求。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) :这个urb的iso包的数量 @mem_flags:要分配的内存类型,请参阅kmalloc()以获取列表 |
示例:
static struct urb *myurb= usb_alloc_urb(0, GFP_KERNEL); |
30.4.7 urb数据结构初始化(控制)
static inline void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context) |
30.4.8 urb数据结构初始化(中断)
static inline void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval) |
30.4.9 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) |
30.4.10 urb数据结构初始化(等时)
等时urb 没有和中断、控制、批量urb 类似的初始化函数,因此它们在提交到USB核心之前,需要在驱动程序中手动的初始化。例如:
urb->dev = dev; urb->context = uvd; urb->pipe = usb_rcvisocpipe(dev,uvd->video_endp-1); urb->interval = 1; urb->transfer_flags = URB_IOS_ASAP; urb->transfer_buffer = can->sts_buf[i]; urb_complete = konicawc_isoc_irq; urb->number_of_packets = FRAMES_PRE_DESC; urb->transfer_buffer_lenth = FRAMES_PRE_DESC; for (j=0; j < FRAMES_PRE_DESC; j++){ urb->ios_frame_desc[j].offset = j; urb->ios_frame_desc[j].length = 1; } |
30.5 编写USB鼠标驱动(中断传输方式)
30.5.1 USB驱动注册框架代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
/*
[ 25.845000] usb 1-2.2: new high-speed USB device number 6 using s5p-ehci
[ 25.950000] usb 1-2.2: New USB device found, idVendor=0661, idProduct=294b
[ 25.950000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 25.950000] usb 1-2.2: Product: EZ-USB
[ 25.950000] usb 1-2.2: Manufacturer: Cypress
[ 726.360000] usb 1-2.2: new high-speed USB device number 7 using s5p-ehci
[ 726.475000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5
[ 726.480000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370
[ 726.480000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 726.480000] usb 1-2.2: Product: 802.11 n WLAN
[ 726.480000] usb 1-2.2: Manufacturer: Ralink
[ 726.485000] usb 1-2.2: SerialNumber: 1.0
*/
//定义USB的IDTAB
static const struct usb_device_id tiny4412_usb_id[] =
{
{USB_DEVICE(0x148f,0x5370)},
{USB_DEVICE(0x0661,0x294b)},
{}
};
/*
MODULE_DEVICE_TABLE 有两个功能。
一是:将设备加入到外设队列中,
二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。
该宏定义在<linux/module.h>下
这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table
局部变量,这个变量指向第二个参数
*/
MODULE_DEVICE_TABLE (usb,tiny4412_usb_id);
//USB设备信息与驱动端匹配成功的时候调用。
static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数
{
printk("tiny4412 probe success .\n");
return 0;
}
//USB断开的时候调用
static void test_disconnect(struct usb_interface *intf)
{
printk("tiny4412 usb drv disconnect success.\n");
}
//定义USB驱动结构体
static struct usb_driver tiny4412_usb_driver = {
.name = "tiny4412_usb_drv",
.id_table = tiny4412_usb_id,
.probe = test_probe,
.disconnect = test_disconnect
};
static int __init tiny4412_usb_init(void)
{
//注册USB设备驱动
usb_register(&tiny4412_usb_driver);
printk("tiny4412 usb drv install success.\n");
return 0;
}
static void __exit tiny4412_usb_exit(void)
{
//注销USB设备驱动
usb_deregister(&tiny4412_usb_driver);
printk("tiny4412 usb drv exit success.\n");
}
module_init(tiny4412_usb_init);
module_exit(tiny4412_usb_exit);
MODULE_AUTHOR("xiaolong");
MODULE_LICENSE("GPL");
运行示例: 拔插USB WIFI 弹出的提示信息。
[root@wbyq code]# insmod linux_usb_drv.ko
[ 19.160000] usbcore: registered new interface driver tiny4412_usb_drv
[ 19.160000] tiny4412 usb drv install success.
[root@wbyq code]# [ 25.430000] usb 1-2.2: new high-speed USB device number 5 using s5p-ehci
[ 25.545000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5
[ 25.550000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370
[ 25.550000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 25.550000] usb 1-2.2: Product: 802.11 n WLAN
[ 25.550000] usb 1-2.2: Manufacturer: Ralink
[ 25.555000] usb 1-2.2: SerialNumber: 1.0
[ 25.570000] tiny4412 probe success .
30.5.2 编写USB鼠标驱动
[root@wbyq linux-3.5]# make menuconfig
由于内核自带了usb鼠标驱动,所以需要去除:
Device Drivers ---> HID support ---> USB HID support ---> <> USB HID transport layer //HID传输层 |
去掉之后,再重新编译内核,烧写内核。
- 鼠标驱动代码: 该模板适用于键盘驱动。
#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/input.h> #include <linux/hid.h> /* 本程序为USB鼠标驱动程序,要安装本驱动,需要先将内核自带的USB驱动程序卸载掉 */ //定义USB的IDTAB 24ae:2002 static const struct usb_device_id tiny4412_usb_id[] = { {//148f:7601 USB_DEVICE(0x148f,0x7601),/*360WIFI的制造商ID和产品ID */ USB_DEVICE(0x24ae,0x2002),/*当前鼠标的ID*/ }, }; //USB鼠标的ID static struct usb_device_id usb_mouse_id[] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { }/* 终止进入 */ }; int size; static unsigned char *buf =NULL; static struct urb *myurb=NULL; dma_addr_t buf_phy; /*USB中断处理程序*/ static void usb_complete(struct urb *urb) { int i; for(i=0;i<size;i++) { printk("0x%x ",buf[i]); } printk("\n"); /* 重新提交异步请求*/ usb_submit_urb(myurb, GFP_KERNEL); } //USB设备信息与驱动端匹配成功的时候调用。 static int usb_probe(struct usb_interface *intf,const struct usb_device_id *id) { printk("USB驱动匹配成功! ID: 0x%X,0x%X\n",id->idVendor,id->idProduct); /*通过接口获取设备信息*/ struct usb_device *dev = interface_to_usbdev(intf); /*获取当前接口设置*/ struct usb_host_interface *interface=intf->cur_altsetting; /*获取端点描述符*/ struct usb_endpoint_descriptor *endpoint = &interface->endpoint[0].desc; /*中断传输:创建输入管道*/ int pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /*从端点描述符中获取传输的数据大小 */ size = endpoint->wMaxPacketSize; printk("设备传输数据包大小:%d\n",size); /*分配数据传输缓冲区*/ buf = usb_alloc_coherent(dev,size,GFP_ATOMIC,&buf_phy); /*分配新的urb,urb是usb设备驱动中用来描述与usb设备通信所用的基本载体和核心数据结构*/ myurb = usb_alloc_urb(0,GFP_KERNEL); /*中断方式初始化urb*/ usb_fill_int_urb(myurb,dev,pipe,buf,size,usb_complete,NULL,endpoint->bInterval); myurb->transfer_dma = buf_phy; myurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /*为端点提交异步传输请求*/ usb_submit_urb(myurb, GFP_KERNEL); return 0; } //USB断开的时候调用 static void usb_disconnect(struct usb_interface *intf) { struct usb_device *dev = interface_to_usbdev(intf); usb_kill_urb(myurb); usb_free_urb(myurb); usb_free_coherent(dev,size,buf, buf_phy); printk("USB 设备释放成功!\n"); } //定义USB驱动结构体 static struct usb_driver tiny4412_usb_driver = { .name = "tiny4412_usb", .id_table = usb_mouse_id, .probe = usb_probe, .disconnect = usb_disconnect }; static int __init tiny4412_usb_init(void) { //注册USB设备驱动 usb_register(&tiny4412_usb_driver); return 0; } static void __exit tiny4412_usb_exit(void) { 注销USB设备驱动 usb_deregister(&tiny4412_usb_driver); } module_init(tiny4412_usb_init); module_exit(tiny4412_usb_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL"); |
30.6 编写USB光谱仪驱动(批量传输方式)
USB光谱仪的USB接口支持使用批量方式传输数据,当前程序里使用的是同步方式提交请求。
整体驱动思路:
(1). 在驱动层里先定义光谱仪设备的设备ID和厂商ID,当设备插入时,ID匹配成功,就会调用probe函数,在probe函数里完成设备信息探测,比如: 端点数据传输方向,数据传输大小,传输方式等等。
探测成功后,就注册一个字符设备,创建设备节点,方便应用程序调用驱动完成设备控制。
(2). 驱动层向应用层提供了read和write接口函数,方便根据预先定义的结构体进行数据通信。
具体情况可以参考–下面的应用层程序示例代码。
30.6.1 头文件定义支持的命令
#ifndef SPECTROMETER_H
#define SPECTROMETER_H
#define Get_Module_Information 0x01000000UL
#define Get_Spectrometer_Information 0x02000000UL
#define Get_Module_Property 0x03000000UL
#define Get_Data_Position_Property 0x04000000UL
#define Get_Data_Count_Property 0x05000000UL
#define Get_Data_Transmit_Property 0x06000000UL
#define Get_Data_Trigger_Offset_Property 0x07000000UL
#define Get_Exposure_Property 0x08000000UL
#define Get_Gain_Property 0x08000000UL
#define Get_Ad_Offset_Property 0x0A000000UL
#define Get_Wavelength_Property 0x0C000000UL
#define Get_Image_Size 0x0D000000UL
#define Get_Capture_Mode 0x0F000000UL
#define Set_Capture_Mode 0x10000000UL
#define Get_Data_Position 0x11000000UL
#define Set_Data_Position 0x12000000UL
#define Get_Data_Count 0x13000000UL
#define Set_Data_Count 0x14000000UL
#define Get_Data_Transmit 0x15000000UL
#define Set_Data_Transmit 0x16000000UL
#define Get_Data_TriggerOffset 0x17000000UL
#define Set_Data_TriggerOffset 0x18000000UL
#define Get_Exposure_Time 0x19000000UL
#define Set_Exposure_Time 0x1A000000UL
#define Get_Exposure_Cycle 0x1B000000UL
#define Set_Exposure_Cycle 0x1C000000UL
#define Get_Trigger_Mode 0x1D000000UL
#define Set_Trigger_Mode 0x1E000000UL
#define Get_Trigger_Polarity 0x1F000000UL
#define Set_Trigger_Polarity 0x20000000UL
#define Get_Trigger_Output 0x21000000UL
#define Set_Trigger_Output 0x22000000UL
#define Get_Gain 0x25000000UL
#define Set_ain 0x26000000UL
#define Get_Ad_Offset 0x27000000UL
#define Set_Ad_OffSet 0x28000000UL
#define Get_Calibration_Coefficient 0x29000000UL
#define Set_Calibration_Coefficient 0x3B000000UL
#define Get_Cooling_Temperature 0x2A000000UL
#define Capture_Start 0x2F000000UL
#define Fire_Trigger 0x30000000UL
#define Capture_Stop 0x31000000UL
#define Read_Eeprom_User_Area 0x33000000UL
#define Write_Eeprom_User_Area 0x34000000UL
#define Get_Status_Request 0x3D000000UL
#define Get_Defective_Pixel 0x3E000000UL
#pragma pack(1)
struct DEV_CMD
{
unsigned char buff[500];读写数据缓冲区
unsigned int write_len;写数据长度
unsigned int read_len;读数据长度
};
#define IOCTL_CMD_RW 0x39654128
#endif
30.6.2 应用层程序示例
打开光谱仪设备节点,获取设备的信息或者设置设备信息。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "spectrometer_cmd_list.h"
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define DEV_NAME "/dev/spectrometer_usb_drv"
int main(int argc,char **argv)
{
/*1. 打开设备文件*/
int fd=open(DEV_NAME,2);
if(fd<0)
{
printf("%s设备文件打开失败.\n",DEV_NAME);
return 0;
}
/*2. 读写光谱仪设备*/
struct DEV_CMD dev_cmd;
memset(&dev_cmd,0,sizeof(struct DEV_CMD));
dev_cmd.write_len=8; //写数据长度
dev_cmd.read_len=500; //读数据长度,如果只想写不想读. 这里长度填0即可.
//写入的命令.赋值 01 00 00 00
dev_cmd.buff[0]=0x01;
dev_cmd.buff[1]=0x00;
dev_cmd.buff[2]=0x00;
dev_cmd.buff[3]=0x00;
dev_cmd.buff[4]=0x00;
dev_cmd.buff[5]=0x00;
dev_cmd.buff[6]=0x00;
dev_cmd.buff[7]=0x00;
//提交数据
if(ioctl(fd,IOCTL_CMD_RW,&dev_cmd))
{
printf("ioctl执行失败.\n");
}
else
{
printf("写成功: %d字节.\n",dev_cmd.write_len);
if(dev_cmd.read_len>0)
{
printf("读成功: %d字节.\n",dev_cmd.read_len);
printf("读取数据如下:\n");
int i;
for (i = 0; i < dev_cmd.read_len; i++)
{
printf("%#x ",dev_cmd.buff[i]);
}
printf("\n");
}
}
//关闭设备
close(fd);
return 0;
}
30.6.3 支持的命令详情
Contents | RequestCommand | DataSize | Data | Note | ||||||
Get Module Information | 0x00000001 | 0x00000000 | ||||||||
Get Spectrometer Information | 0x00000002 | 0x00000000 | ||||||||
Get Module Property | 0x00000003 | 0x00000000 | ||||||||
Get Data Position Property | 0x00000004 | 0x00000000 | ||||||||
Get Data Count Property | 0x00000005 | 0x00000000 | ||||||||
Get Data Transmit Property | 0x00000006 | 0x00000000 | ||||||||
Get Data Trigger Offset Property | 0x00000007 | 0x00000000 | ||||||||
Get Exposure Property | 0x00000008 | 0x00000000 | ||||||||
Get Gain Property | 0x00000009 | 0x00000000 | ||||||||
Get Ad Offset Property | 0x0000000A | 0x00000000 | ||||||||
Get Wavelength Property | 0x0000000C | 0x00000000 | ||||||||
Get Image Size | 0x0000000D | 0x00000000 | ||||||||
Get Capture Mode | 0x0000000F | 0x00000000 | ||||||||
Set Capture Mode | 0x00000010 | 0x00000002 | Capture Mode | Capture Mode | ||||||
Get Data Position | 0x00000011 | 0x00000000 | ||||||||
Set Data Position | 0x00000012 | 0x00000004 | Data Position | |||||||
Get Data Count | 0x00000013 | 0x00000000 | ||||||||
Set Data Count | 0x00000014 | 0x00000004 | Data Count | |||||||
Get Data Transmit | 0x00000015 | 0x00000000 | ||||||||
Set Data Transmit | 0x00000016 | 0x00000004 | Data Transmit | |||||||
Get Data TriggerOffset | 0x00000017 | 0x00000000 | ||||||||
Set Data TriggerOffset | 0x00000018 | 0x00000004 | Trigger Offset | |||||||
Get Exposure Time | 0x00000019 | 0x00000000 | ||||||||
Set Exposure Time | 0x0000001A | 0x00000008 | Exposure Time | |||||||
Get Exposure Cycle | 0x0000001B | 0x00000000 | ||||||||
Set Exposure Cycle | 0x0000001C | 0x00000008 | Exposure Cycle | |||||||
Get Trigger Mode | 0x0000001D | 0x00000000 | ||||||||
Set Trigger Mode | 0x0000001E | 0x00000004 | Trigger Mode | Trigger Mode | ||||||
Get Trigger Polarity | 0x0000001F | 0x00000000 | ||||||||
Set Trigger Polarity | 0x00000020 | 0x00000004 | Trigger Polarity | Trigger Polarity | ||||||
Get Trigger Output | 0x00000021 | 0x00000000 | ||||||||
Set Trigger Output | 0x00000022 | 0x00000004 | Trigger Output | Trigger Output | ||||||
Get Gain | 0x00000025 | 0x00000004 | Gain Mode | Gain Mode | ||||||
Set Gain | 0x00000026 | 0x00000008 | Gain Mode | Gain | Gain Mode | |||||
Get Ad Offset | 0x00000027 | 0x00000000 | ||||||||
Set Ad OffSet | 0x00000028 | 0x00000004 | Ad Offset | |||||||
Get Calibration Coefficient | 0x00000029 | 0x00000004 | Index | Index | ||||||
Set Calibration Coefficient | 0x0000003B | 0x00000034 | Index | Coefficient A0 | Coefficient B1 | Coefficient B2 | Coefficient B3 | Coefficient B4 | Coefficient B5 | Index |
Get Cooling Temperature | 0x0000002A | 0x00000000 | ||||||||
Capture Start | 0x0000002F | 0x00000002 | Capture Mode | |||||||
Fire Trigger | 0x00000030 | 0x00000000 | ||||||||
Capture Stop | 0x00000031 | 0x00000000 | ||||||||
Read Eeprom User Area | 0x00000033 | 0x00000004 | Address | Length | ||||||
Write Eeprom User Area | 0x00000034 | 0x00000008 | Address | Length | Data | The address that can be specified is 0-511. | ||||
Get Status Request | 0x0000003D | 0x00000000 | Cooled only | |||||||
Get Defective Pixel | 0x0000003E | 0x00000000 |
30.6.4 驱动层程序示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/io.h>
#include "spectrometer_cmd_list.h"
#include <asm/uaccess.h>
#include <linux/bcd.h>
#include <linux/uaccess.h>
/*容纳所有设备特定内容的结构 */
struct usb_spectrometer
{
struct usb_device *udev; /* 此设备的USB设备 */
struct usb_interface *interface; /*该设备的接口*/
struct usb_anchor submitted; /* 万一需要撤回提交*/
struct urb *bulk_in_urb; /*用urb读取数据*/
unsigned char *bulk_in_buffer; /* 接收数据的缓冲区 */
size_t bulk_in_size; /*接收缓冲区的大小 */
size_t bulk_in_filled; /* 缓冲区中的字节数 */
size_t bulk_in_copied; /* 已经复制到用户空间 */
__u8 bulk_in_endpointAddr; /* 端点中的批量地址 */
__u8 bulk_out_endpointAddr; /*批量输出端点的地址 */
int errors; /* 最后一个请求被取消 */
bool ongoing_read; /* 读正在进行*/
bool processed_urb; /* 表示尚未处理 */
};
static struct usb_spectrometer *dev;
/*
[ 25.845000] usb 1-2.2: new high-speed USB device number 6 using s5p-ehci
[ 25.950000] usb 1-2.2: New USB device found, idVendor=0661, idProduct=294b
[ 25.950000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 25.950000] usb 1-2.2: Product: EZ-USB
[ 25.950000] usb 1-2.2: Manufacturer: Cypress
[ 726.360000] usb 1-2.2: new high-speed USB device number 7 using s5p-ehci
[ 726.475000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5
[ 726.480000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370
[ 726.480000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[ 726.480000] usb 1-2.2: Product: 802.11 n WLAN
[ 726.480000] usb 1-2.2: Manufacturer: Ralink
[ 726.485000] usb 1-2.2: SerialNumber: 1.0
*/
//定义USB的IDTAB
static const struct usb_device_id tiny4412_usb_id[] =
{
{USB_DEVICE(0x148f,0x5370)},
{USB_DEVICE(0x0661,0x294b)},
{}
};
/*
MODULE_DEVICE_TABLE 有两个功能。
一是:将设备加入到外设队列中,
二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。
该宏定义在<linux/module.h>下
这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table
局部变量,这个变量指向第二个参数
*/
MODULE_DEVICE_TABLE (usb,tiny4412_usb_id);
static int usb_dev_open(struct inode *inode, struct file *file)
{
printk("open:USB光谱仪设备.\n");
printk("命令结构大小_drv:%lu\n",sizeof(struct DEV_CMD));
return 0;
}
//读写命令
static long usb_dev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long argv)
{
//转换指针类型
struct DEV_CMD *buff=(struct DEV_CMD *)argv;
struct DEV_CMD dev_cmd;
int ret=0;
int actual_length=0;
int err=0;
printk("读写:USB光谱仪设备.\n");
//命令符合要求
if(IOCTL_CMD_RW==cmd)
{
//拷贝应用层的数据到本地
if(copy_from_user(&dev_cmd,buff,sizeof(struct DEV_CMD)))
{
printk("write: copy_from_user error...\n");
return -2;
}
/*同步提交写请求*/
ret=usb_bulk_msg(dev->udev,
usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr),
dev_cmd.buff,dev_cmd.write_len,&actual_length,HZ*20);
if(ret<0)
{
printk("同步提交写请求错误.错误值:%d\n",ret);
return -3;
}
dev_cmd.write_len=actual_length; //成功写入的长度
printk("write:len=%d\n",actual_length);
//读取的长度大于0.就表示需要读取数据
if(dev_cmd.read_len>0)
{
//buff清0
memset(dev_cmd.buff,0,sizeof(dev_cmd.buff));
/*同步提交读请求*/
ret = usb_bulk_msg(dev->udev,
usb_rcvbulkpipe(dev->udev,dev->bulk_in_endpointAddr),
dev_cmd.buff,dev_cmd.read_len, &actual_length,HZ*20);
if(ret<0)
{
printk("同步提交读请求错误.错误值:%d\n",ret);
return -4;
}
//实际读取的长度
dev_cmd.read_len=actual_length;
printk("read:len=%d\n",actual_length);
//将数据拷贝到应用层
err=copy_to_user(buff,&dev_cmd,sizeof(struct DEV_CMD));
if(err)
{
printk("read:read cmd error!!!\n");
return err;
}
}
}
return 0;
}
static int usb_dev_release(struct inode *inode, struct file *file)
{
printk("release:USB光谱仪设备.\n");
return 0;
}
static const struct file_operations usb_dev_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl=usb_dev_unlocked_ioctl,
.open = usb_dev_open,
.release = usb_dev_release,
};
static struct miscdevice usb_dev_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "spectrometer_usb_drv",
.fops = &usb_dev_fops,
};
//USB设备信息与驱动端匹配成功的时候调用。
static int test_probe(struct usb_interface *interface,const struct usb_device_id *id) //资源探索函数
{
int i=0;
size_t buffer_size;
struct usb_device *dev_info;
unsigned char *bcdUSB_p;
struct usb_host_interface *host_inter;
struct usb_endpoint_descriptor *endpoint;
int size;
dev=kzalloc(sizeof(*dev), GFP_KERNEL);
dev->udev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
printk("识别到USB光谱仪设备,正在进行设备初始化.\n");
/*通过接口获取设备信息*/
dev_info = interface_to_usbdev(interface);
bcdUSB_p=(unsigned char *)&dev_info->descriptor.bcdUSB;
printk("设备与描述表兼容的USB设备说明版本号=0x%x%x\n", bcd2bin(*bcdUSB_p),bcd2bin(*(bcdUSB_p+1))); //从USB设备描述符中获取USB版本
printk("厂商ID = %#x\n",dev_info->descriptor.idVendor); //从USB设备描述符中获取厂商ID
printk("设备ID = %#x\n",dev_info->descriptor.idProduct);//从USB设备描述符中获取产品ID
printk("设备类%#x\n",interface->cur_altsetting->desc.bInterfaceClass); //从USB设备获取设备类
printk("设备从类%#x\n",interface->cur_altsetting->desc.bInterfaceSubClass);//从USB设备获取设备从类
printk("设备协议%#x\n",interface->cur_altsetting->desc.bInterfaceProtocol);//从USB设备获取设备协议
printk("驱动名称:%s\n",interface->dev.driver->name);
printk("总线名称:%s\n",interface->dev.driver->bus->name);
/*获取当前接口设置*/
host_inter=interface->cur_altsetting;
/*获取端点描述符*/
for(i=0;i<host_inter->desc.bNumEndpoints;i++)
{
endpoint = &host_inter->endpoint[i].desc;
printk("端点号[%d]:%d\n",i,endpoint->bEndpointAddress&0xFF);
if(endpoint->bEndpointAddress&1<<7)
{
printk("端点[%d] 输入端点(设备到主机)\n",i);
}
else
{
printk("端点[%d] 输出端点(主机到设备)\n",i);
}
switch(endpoint->bmAttributes)
{
case 0:printk("端点[%d] 设备支持控制传输.\n",i);break;
case 1:printk("端点[%d] 设备支持同步传输.\n",i);break;
case 2:printk("端点[%d] 设备支持批量传输.\n",i);break;
case 3:printk("端点[%d] 设备支持中断传输.\n",i);break;
}
/*从端点描述符中获取传输的数据大小 */
size = usb_endpoint_maxp(endpoint);
printk("端点[%d] 传输的数据大小:%d\n",i,size);
//输入端点
if(!dev->bulk_in_endpointAddr &&usb_endpoint_is_bulk_in(endpoint))
{
/* 批量输入端点 */
buffer_size = usb_endpoint_maxp(endpoint);
dev->bulk_in_size = buffer_size;
dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
printk("probe:dev->bulk_in_size=%lu\n",dev->bulk_in_size);
printk("probe:dev->bulk_in_endpointAddr=%d\n",dev->bulk_in_endpointAddr);
dev->bulk_in_buffer = kmalloc(buffer_size,GFP_KERNEL);
if(!dev->bulk_in_buffer)
{
printk("无法分配bulk_in_buffer");
break;
}
dev->bulk_in_urb = usb_alloc_urb(0,GFP_KERNEL);
if(!dev->bulk_in_urb)
{
printk("无法分配bulk_in_urb");
break;
}
}
//输出端点
if(!dev->bulk_out_endpointAddr &&usb_endpoint_is_bulk_out(endpoint))
{
/* 批量输出端点 */
dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
printk("probe:dev->bulk_out_endpointAddr=%d\n",dev->bulk_out_endpointAddr);
}
}
/*向内核注册一个杂项字符设备*/
if(misc_register(&usb_dev_miscdev)==0)
{
printk("USB光谱仪设备节点注册成功:/dev/%s主设备号:10,次设备号:%d\n",
usb_dev_miscdev.name,usb_dev_miscdev.minor);
}
return 0;
}
//USB断开的时候调用
static void test_disconnect(struct usb_interface *intf)
{
printk("USB光谱仪设备已断开.\n");
/*从内核注销一个杂项字符设备*/
misc_deregister(&usb_dev_miscdev);
}
//定义USB驱动结构体
static struct usb_driver tiny4412_usb_driver = {
.name = "spectrometer_usb_drv",
.id_table = tiny4412_usb_id,
.probe = test_probe,
.disconnect = test_disconnect
};
static int __init tiny4412_usb_init(void)
{
printk("正在安装USB光谱仪驱动.\n");
//注册USB设备驱动
usb_register(&tiny4412_usb_driver);
return 0;
}
static void __exit tiny4412_usb_exit(void)
{
//注销USB设备驱动
usb_deregister(&tiny4412_usb_driver);
printk("USB光谱仪驱动卸载成功.\n");
}
module_init(tiny4412_usb_init);
module_exit(tiny4412_usb_exit);
MODULE_AUTHOR("xiaolong");
MODULE_LICENSE("GPL");