转自
http://www.baiheee.com/OpenSource/Easy%20USB%2051%20Programer/Easy%20USB%2051%20Programer2.htm
USB设备的开发一般包括主机端(上位机)驱动程序的开发(如果您的USB设备符合某一标准设备类且主机端已经提供了此类设备的驱动程序的话,则可以省掉此步骤)和USB设备端驱动程序的开发,有时还可能包括主机端应用程序的设计工作。
1、设备系统需求分析
设备系统需求分析是进行USB设备设计的第一步,通过对USB设备功能特性和USB主机端操作系统的分析,可以获得实现该USB设备的软硬件设计需求。
在该阶段,设计者需要充分了解该设备的应用环境(如USB主机的软件、硬件平台),这样以用来确定是否需要提供USB主机端相关软件工作,以便该设备能得到广泛地应用。为了提供合理的软硬件设计方案,设计者还需要充分了解市场上的USB接口芯片,不同的USB接口芯片在USB协议上有着不同程度的支持,比如,对数据包地址的硬件自动识别、CRC16和CRC5的自动生成等等。
当然,在确定具体的软硬件需求时,产品的开发费用和开发周期也是必须考虑的因素。
2、设备硬件需求
通过设备系统需求分析,以及对市场上USB接口芯片的充分了解,设计者必须确定相应的设备硬件结构以及可能采用的硬件。在选择器件时,需要考虑到器件体积、功耗等,因为,小的设备功耗,有利于采用总线供电模式。必须通过设备系统的功耗来确定是否需要提供本地电源。
3、设备软件需求
在确定了设备的硬件结构以后,该设备的软件结构就会同时产生。不同的硬件平台,可能需要不同程度的软件支持。
4、设备硬件设计
在选定USB控制芯片以后,如果是带USB接口的单片机,则是一般单片机应用系统的开发;反之,就是如何把USB接口芯片与单片机应用系统融合的问题。一般USB接口芯片都支持多种并行总线结构(复用/非复用),可以方便的与多种单片机接口。硬件设计中要注意的就是USB接口芯片的时钟速度比较高,所以匹配网络的设计以及PCB布线要特别关注。
5、设备软件设计
USB设备的软件设计主要包括两部分:一是USB设备端的软件开发,主要完成USB协议处理与数据交换以及其它应用功能程序。二是主机端的程序,由USB通信程序(驱动程序)和用户服务程序两部分组成,主机端用户服务程序通过USB通信程序(驱动程序)与系统USBDI(USB Device Interface)通信,由系统完成USB协议的处理与数据传输。主机端程序的开发难度比较大,程序员不仅要熟悉USB协议,如果需要开发主机端驱动程序的话,还要熟悉主机端操作系统(如常用的WINDOWS系统)体系结构并能熟练运用DDK工具(驱动程序开发工具)。
6、设备调试
要快捷、成功的开发一个USB设备,正确、合理的调试方法是必不可少的环节。调试基本分三步进行:首先对外部设备(单片机部分)借助PC调试软件(常用的有:bus hound、USB MONITOR,某些芯片生产商还提供针对特定芯片的调试软件)将设备端的USB协议(主要有描述符请求、端口配置、地址设置以及基本数据交换)调通(当然我们还可以使用USB分析仪等开发设备,但此类设备一般比较昂贵)。然后,用调试好的USB设备接口来开发、调试PC软件,这一步相对比较容易。最后,加上USB设备端的其它用户程序,对整个完整的系统进行系统调试。
了解USB的通讯过程
USB的两根信号线负责与总线上的设备交换数据。这些电缆形成了所有设备必须共享的惟一的一条传输通路。 RS-232有一条Tx线用来传输一个方向的数据,一条Rx线用来传输另一个方向的数据。与RS-232不同,USB的一对电线只传输一个不同的信号,不 同方向的信号要按顺序来传输。
因为所有的传输共享一个数据通道,所以每一个事务必须包括事务的源和目的地址。每个设备有一个由 主机分配的惟一的地址,所有的数据都是流向主机或从主机获取。每个事务都是以主机发送数据块开始的,这个数据块包括接收设备的地址以及设备中被称为终端的 一个特殊位置。一个设备发送的每个数据是为了响应从主机接到的请求而发送的接收到的数据或状态信息。
USB通信分为两类,根据它们被用于原始配置还是应用中。在配置通信中主机通知设备,设备收到通知后准备好交换数据。大部分这类通信发生在上电或连接时主机检测到外设的时候。应用通信出现在主机的应用程序与一个检测到的外设交换数据的时候。这些是实现设备应用的通信。例如,对鼠标来说,应用通信是发送点击动作给主机,主机端应用程序接收到这个动作后执行相应动作。
配置通信
在检测过程中,设备的固件对主机的一系列标准请求做出响应。设备必须识别出每一个请求,返回被请求的信息。
在PC上,Windows执行检测工作,所以不涉及用户编程的问题。然而,为了完成检测工作,Windows必须有两个可用的文件:一个识别这个设备驱动程序的文件名和位置的INF文件和设备驱动程序本身,如果您的项目符合某种通用设备类,则操作系统可能已经提供了此类设备的所需的这两个文件,而不需要开发人员自己提供。
根据设备以及设备将被如何使用,设备驱动可能是如下两者之一;Wlndows自带的或芯片或外设厂商提供的。INF文件是一个文本文件,通常你可以对驱动提供考提供例子,稍加修改就可以得到了。
应用通信
在主机已经与设备交换了检测信息并且设备驱动已经被分配并载入后,应用通信过程可以非常顺利地进行下去了。在主机上,应用程序可以使用标准Windows API功能来读和写设备。在外设上,传输数据通常需要把要发送的数据放在USB控制器的传输缓冲器中,当一个硬件中断发出数据已经到达的信号时从接收缓冲器中读取接收到的数据,并且在完成传输时确保外设准备好下一次传输。
主机与设备建立通信的过程
主机端的USB集线器监视着它的每个端口的信号线的电压,当USB设备插入主机时,信号线的电平会发生变化,此时主机知道有新设备插入了。
当主机检测到设备的插入后会首选重启这个设备,接着主机发出Get_Port_Status请求来验证设备是否已经重启,设备重启后主机通过检测根信号线的电平状态判断设备的速度。
主机发送第一次Get_Descriptor(wValue字段的高字节为0x01,表示设备描述符)请求取得设备描述符,设备描述符提供了设备的多种信息,包括:设备通讯终端0的最大包的大小,设备支持的配置号以及有关这个设备的其它信息,主机通过对这些信息的分析以确定接下来的通信动作。
设备描述符里规定了设备一个或多个配置描述符,主机再次或多次发出Get_Descriptor(wValue字段的高字节为0x02,表示配置描述符)指令来读取这些配置描述符,第一次只读出配置描述符的前9个字节,这9个字节里包含了配置描述符和它的所有从属描述符(接口描述符、端点描述符)的总长度,然后主机根据这个长度读出设备的所有配置描述符(当然包括其所有从属描述符)。
在读取完配置描述符后,若之间读取的设置描述中指定了相关字符串描述符(用来描述厂商、产品和设备序列号信息的)的索引,主机将发出若干次Get_Descriptor(wValue字段的高字节为0x03,表示字符串描述符)命令来获得这些字符串描述,此时主机将会弹出窗口,展示发现新设备的信息,产商、产品描述、型号等。
在主机已经从它的描述符中知道了能够知道的所有信息后,便开始为这个设备安装驱动程序。
加载了USB设备驱动以后,主机发送Set_Configuration命令请求为该设备选择一个合适的配置。
至此,USB枚举过程结束,设备可以正常使用了。
USB命令(请求)和USB描述符
一、USB命令
在USB规范里,对命令一词提供的单词为“Request”,但这里为了更好的理解主机与设备之间的主从关系,将它定义成“命令”。
所有的USB设备都要求对主机发给自己的控制命令作出响应,USB规范定义了11个标准命令,它们分别是:Clear_Feature、Get_Configuration、Get_Descriptor、Get_Interface、Get_Status、Set_Address、Set_Configuration、Set_Descriptor、Set_Interface、Set_Feature、Synch_Frame。所有USB设备都必须支持这些命令(个别命令除外,如Set_Descriptor、Synch_Frame)。
不同的命令虽然有不同的数据和使用目的,但所有的USB命令结构是一样的。下表所示为USB命令的结构:
表1、USB命令的结构 |
||||
偏移量 |
域 |
长度(字节) |
值 |
描述 |
0 |
bmRequestType |
1 |
位图 |
请求特征: |
1 |
bRequest |
1 |
值 |
命令类型编码值(见表3) |
2 |
wValue |
2 |
值 |
根据不同的命令,含义也不同 |
4 |
wIndex |
2 |
索引或偏移 |
根据不同的命令,含义也不同,主要用于传送索引或偏 移 |
6 |
wLength |
2 |
|
如有数据传送阶段,此为数据字节数。 |
下表列出了USB的11种标准命令
表2、USB的11种标准命令 |
||||||
命令 |
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
Data |
Clear_Feature |
00000000B |
CLEAR_FEATURE |
特性选择符 |
零 |
零 |
无 |
Get_Configuration |
10000000B |
GET_CONFIGURATION |
零 |
零 |
一 |
配置值 |
Get_Descriptor |
10000000B |
GET_DESCRIPTOR |
描述表种类(高字节,见表5)和索引(低字节) |
零或语言标志 |
描述表长 |
描述表 |
Get_Interface |
10000001B |
GET_INTERFACE |
零 |
接口号 |
一 |
可选设置 |
Get_Status |
10000000B |
GET_STATUS |
零 |
零(返回设备状态) |
二 |
设备, |
Set_Address |
00000000B |
SET_ADDRESS |
设备地址 |
零 |
零 |
无 |
Set_Configuration |
00000000B |
SET_CONFIGURATION |
配置值(高字节为0,低字节表示要设置的配置值) |
零 |
零 |
无 |
Set_Descriptor |
00000000B |
SET_DESCRIPTOR |
描述表种类(高字节,见表5)和索引(低字节) |
零或语言标志 |
描述表长 |
描述表 |
Set_Feature |
00000000B |
SET_FEATURE |
特性选择符(1表示设备,0表示端点) |
零 |
零 |
无 |
Set_Interface |
00000001B |
SET_INTERFACE |
可选设置 |
接口号 |
零 |
无 |
Synch_Frame |
100000010B |
SYNCH_FRAME |
零 |
端点号 |
二 |
帧号 |
其中bRequest为命令编码值,含意见表3:
表3、USB标准命令的编码值 |
|
bRequest |
Value |
GET_STATUS |
0 |
CLEAR_FEATURE |
1 |
为将来保留 |
2 |
SET_FEATURE |
3 |
为将来保留 |
4 |
SET_ADDRESS |
5 |
GET_DESCRIPTOR |
6 |
SET_DESCRIPTOR |
7 |
GET_CONFIGURATION |
8 |
SET_CONFIGURATION |
9 |
GET_INTERFACE |
10 |
SET_INTERFACE |
11 |
SYNCH_FRAME |
12 |
二、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设备没有端点描述符,则它仅仅用默认管道与主机进行数据传输。
1、设备描述符
设备描述符给出了USB设备的一般信息,包括对设备及在设备配置中起全程作用的信息,包括制造商标识号ID、产品序列号、所属设备类号、默认端点的最大包长度和配置描述符的个数等。一个USB设备必须有且仅有一个设备描述符。设备描述符是设备连接到总线上时USB主机所读取的第一个描述符,它包含了14个字段,结构如下:
表4、USB设备描述符的结构 |
||||
偏移量 |
域 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
此描述表的字节数 |
1 |
bDecriptorType |
1 |
常量 |
描述符的类型(此处应为0x01,即设备描述符) |
2 |
bcdUSB |
2 |
BCD码 |
此设备与描述表兼容的USB设备说明版本号(BCD 码) |
4 |
bDeviceClass |
1 |
类 |
设备类码: |
5 |
bDeviceSubClass |
1 |
子类 |
子类挖码 |
6 |
bDevicePortocol |
1 |
协议 |
协议码 |
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域高字节的取值含义):
表5、USB描述符的类型值 |
||
类型 |
描述符 |
描述符值 |
标准描述符 |
设备描述符(Device Descriptor) |
0x01 |
配置描述符(Configuration Descriptor) |
0x02 |
|
字符串描述符(String Descriptor) |
0x03 |
|
接口描述符(Interface Descriptor) |
0x04 |
|
端点描述符(EndPont Descriptor) |
0x05 |
|
类描述符 |
集线器类描述符(Hub Descriptor) |
0x29 |
人机接口类描述符(HID) |
0x21 |
|
厂商定义的描述符 |
|
0xFF |
设备类代码bDeviceClass可查下表:
表6、设备的类别(bDeviceClass) |
||
值(十进制) |
值(十六进制) |
说明 |
0 |
0x00 |
接口描述符中提供类的值 |
2 |
0x02 |
通信类 |
9 |
0x09 |
集线器类 |
220 |
0xDC |
用于诊断用途的设备类 |
224 |
0xE0 |
无线通信设备类 |
255 |
0xFF |
厂商定义的设备类 |
下表列出了一个USB鼠标的设备描述符的例子,供大家分析一下:
表7、一种鼠标的设备描述符示例 |
|
字段 |
描述符值(十六制) |
bLength |
0x12 |
bDecriptorType |
0x01 |
bcdUSB |
x0110 |
bDeviceClass |
0x00 |
bDeviceSubClass |
0x00 |
bDevicePortocol |
0x00 |
bMaxPacketSize0 |
0x08 |
idVendor |
0x045E(Microsoft Corporation) |
idProduct |
0x0047 |
bcdDevice |
0x300 |
iManufacturer |
0x01 |
iProduct |
0x03 |
iSerialNumber |
0x00 |
bNumConfigurations |
0x01 |
2、配置描述符
配置描述符中包括了描述符的长度(属于此描述符的所有接口描述符和端点描述符的长度的和)、供电方式(自供电/总线供电)、最大耗电量等。如果主机发出USB标准命令Get_Descriptor要求得到设备的某个配置描述符,那么除了此配置描述符以外,此配置包含的所有接口描述符与端点描述符都将提供给USB主机。
表8、USB配置描述符的结构 |
||||
偏移量 |
域 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
此描述表的字节数长度。 |
1 |
bDescriptorType |
1 |
常量 |
配置描述表类型(此处为0x02) |
2 |
wTotalLength |
2 |
数字 |
此配置信息的总长(包括配置,接口,端点和设备类及厂商定义的描述符) |
4 |
bNumInterfaces |
1 |
数字 |
此配置所支持的接口个数 |
5 |
bCongfigurationValue |
1 |
数字 |
在SetConfiguration()请求中用作参数来选定此配置。 |
6 |
iConfiguration |
1 |
索引 |
描述此配置的字串描述表索引 |
7 |
bmAttributes |
1 |
位图 |
配置特性: |
8 |
MaxPower |
1 |
mA |
在此配置下的总线电源耗费量。以 2mA 为一个单位。 |
下面是一种硬盘的配置描述符示例:
表9、一种硬盘的配置描述符示例 |
|
字段 |
描述符值(十六进制) |
bLength |
0x09 |
bDescriptorType |
0x02 |
wTotalLength |
0x01F |
bNumInterfaces |
0x01 |
bCongfigurationValue |
0x01 |
iConfiguration |
0x00 |
bmAttributes |
0x0C |
MaxPower |
0x32 |
3、接口描述符
配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。
如果一个配置描述符不止支持一个接口描述符,并且每个接口描述符都有一个或多个端点描述符,那么在响应USB主机的配置描述符命令时,USB设备的端点描述符总是紧跟着相关的接口描述符后面,作为配置描述符的一部分被返回。接口描述符不可直接用Set_Descriptor和Get_Descriptor来存取。
如果一个接口仅使用端点0,则接口描述符以后就不再返回端点描述符,并且此接口表现的是一个控制接口的特性,它使用与端点0相关联的默认管道进行数据传输。在这种情况下bNumberEndpoints域应被设置成0。接口描述符在说明端点个数并不把端点0计算在内。
表10、USB接口描述符的结构 |
||||
偏移量 |
域 |
大小 |
值 |
说明 |
0 |
bLength |
1 |
数字 |
此表的字节数 |
1 |
bDescriptorType |
1 |
常量 |
接口描述表类(此处应为0x04) |
2 |
bInterfaceNumber |
1 |
数字 |
接口号,当前配置支持的接口数组索引(从零开始)。 |
3 |
bAlternateSetting |
1 |
数字 |
可选设置的索引值。 |
4 |
bNumEndpoints |
1 |
数字 |
此接口用的端点数量,如果是零则说明此接口只用缺省控制管道。 |
5 |
bInterfaceClass |
1 |
类 |
接口所属的类值: |
6 |
bInterfaceSubClass |
1 |
子类 |
子类码 |
7 |
bInterfaceProtocol |
1 |
协议 |
协议码:bInterfaceClass 和bInterfaceSubClass 域的值而定.如果一个接口支持设备类相关的请求此域的值指出了设备类说明中所定义的协议. |
8 |
iInterface |
1 |
索引 |
描述此接口的字串描述表的索引值。 |
对于bInterfaceClass字段,表示接口所属的类别,USB协议根据功能将不同的接口划分成不的类,其具体含义如下表所示:
表11、USB协议定义的接口类别(bInterfaceClass) |
|
值(十六进制) |
类别 |
0x01 |
音频类 |
0x02 |
CDC控制类 |
0x03 |
人机接口类(HID) |
0x05 |
物理类 |
0x06 |
图像类 |
0x07 |
打印机类 |
0x08 |
大数据存储类 |
0x09 |
集线器类 |
0x0A |
CDC数据类 |
0x0B |
智能卡类 |
0x0D |
安全类 |
0xDC |
诊断设备类 |
0xE0 |
无线控制器类 |
0xFE |
特定应用类(包括红外的桥接器等) |
0xFF |
厂商定义的设备 |
4、端点描述符
端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。
除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。
表12、USB端点描述符的结构 |
||||
偏移量 |
域 |
大小 |
值 |
说明 |
0 |
bLength |
1 |
数字 |
此描述表的字节数长度 |
1 |
bDescriptorType |
1 |
常量 |
端点描述表类(此处应为0x05) |
2 |
bEndpointAddress |
1 |
端点 |
此描述表所描述的端点的地址、方向: |
3 |
bmAttributes |
1 |
位图 |
此域的值描述的是在bConfigurationValue域所指的配置下端点的特性。 |
4 |
wMaxPacketSize |
2 |
数字 |
当前配置下此端点能够接收或发送的最大数据包的大小。 |
6 |
bInterval |
1 |
数字 |
周期数据传输端点的时间间隙。 |
下表是一种鼠标的端点描述符的示例,该端点是一个中断端点:
表13、一种鼠标的端点描述符示例 |
|
域 |
值(十六进制) |
bLength |
0x07 |
bDescriptorType |
0x05 |
bEndpointAddress |
0x81 |
bmAttributes |
0x03 |
wMaxPacketSize |
0x04 |
bInterval |
0x0A |
5、字符串描述符
字符串描述符是一种可选的USB标准描述符,描述了如制商、设备名称或序列号等信息。如果一个设备无字符串描述符,则其它描述符中与字符串有关的索引值都必须为0。字符串使用的是Unicode编码。
主机请示得到某个字符串描述符时一般分成两步:首先主机向设备发出USB标准命令Get_Descriptor,其中所使用的字符串的索引值为0,设备返回一个字符串描述符,此描述符的结构如下:
表14、USB字符串描述符(响应主机请求时返回的表示语言ID的字符串描述符) |
||||
偏移量 |
域 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
N+2 |
此描述表的字节数 |
1 |
bDescriptorType |
1 |
常量 |
字串描述表类型(此处应为0x03) |
2 |
wLANGID[0] |
2 |
数字 |
语言标识(LANGID) |
|
… |
… |
… |
… |
N |
wLANGID[x] |
2 |
数字 |
语言标识(LANGID) |
该字符串描述符双字节的语言ID的数组,wLANGID[0]~wLANGID[x]指明了设备支持的语言,具体含义可查看USB_LANGIDs.pdf。
主机根据自己需要的语言,再次向设备发出USB标准命令Get_Descriptor,指明所要求得到的字符串的索引值和语言。这次设备所返回的是Unicode编号的字符串描述符,其结构如下:
表15、Unicode字符串描述符(响应主机请求时真正表示字符串编码的字符串描述符) |
||||
偏移量 |
域 |
大小 |
值 |
描述 |
0 |
bLength |
1 |
数字 |
此描述表的字节数(bString域的数值N+2) |
1 |
bDescriptorType |
1 |
常量 |
字串描述表类型(此处应为0x03) |
2 |
bString |
N |
数字 |
UNICODE 编码的字串 |
bString域为设备实际返回的以UNICODE编码的字符串流,我们在编写设备端硬件驱动的时候需要将字符串转换为UNICODE编码,您可以通过一些UNICODE转换工具进行转换。这里推荐由百合电子工作室开发的一款USB描述符生成工具“USB Unicode 字符串描述符生成器”,它专门为编写设备端驱动程序的需要而定制,可以非常方便将您需要的字符串转换成UNICODE格式,进而导入您的C或汇编程序代码中,以下是它的界面:
USB Unicode 字符串描述符生成器-生成C语言格式
USB Unicode 字符串描述符生成器-生成汇编格式