网上找的个人认为比较有用的资料,和大家共享
第1章 USB通信
3.1 USB概述
USB是英语“Universal Serial Bus”的缩写,中文是通用串行数据总线的意思,用于PC机与各种外部设备的通信连接(现在也可以用于外设之间的通信连接)。USB规范是Inter、IBM、Microsoft、NEC等7家世界著名的大公司联合推出的。目前主要有USB1.1和USB2.0两个版本,USB1.1支持1.5Mb/s的低速传输和12Mb/s的全速传输;而USB2.0除了支持前两种传输以外,还支持480Mb/s的高速传输。
一.USB的特点及其应用
a) 使用方便
USB设备与PC机的连接非常简单,只要把它插在PC机的USB接口上(或者相应的连接线上)。可以对它进行热插拔和即插即用,也就是说,所有USB设备都可以随时插入计算机系统,或者从计算机系统中拔除,用不着关闭计算机、添加/删减设备、重新启动计算机。PC机可以自动识别设备的类型,进行相关设置。
b) 数据传输速度快
早期的USB1.1版本就支持1.5Mb/s的低速传输和12Mb/s的全速传输;而USB2.0除了支持前两种传输以外,还支持480Mb/s的高速传输,这比我们前面介绍的RS -232C 的传输速度(10kb/s)快多了。
c) 易于扩展
USB总线标准中,USB总线结构是一个通过集线器进行树状扩展的结构,这个树的根就是PC机,而树的枝点可以连接各个USB设备。(包括集线器的各个端口在内)USB总线最多可以连接127个设备。另外USB设备可以从USB总线总线上得到电源,也可以自己提供电源,这样就可以给USB设备的设计提供非常大的灵活性。
d) 应用范围广
由USB总线的上述优点,所以它一经诞生,就得到非常迅速的发展,应用领域非常广:它既可以用于键盘、鼠标等非常慢速的外部设备,也可以用于硬盘、光驱等高速的存储设备;可以传输不允许出现差错的控制信息,也可以传送允许有差错但是必须同步传输的音像信息。
二.USB的发展历史及其前景
1.发展历史
1994年Intel等公司提出了USB0.7版本,这标志这USB正式诞生,经过一系列完善,在1996年1月,USB1.0版本正式公布,1998年9月USB1.1版本公布后USB开始大量进入PC机系统,当然,这也得力于WINDOWS98操作系统对USB设备的支持,以及从这时开始,PC机主板上对USB设备的支持。在2000年4月,USB2.0版本正式公布,它最大的亮点是增加了480Mb/s的高速传输方式,使得USB得到进一步广泛的扩展。2001年12月又推出了USB OTG版本,它其实是某些USB设备在另外的场合可以作为USB主机。
2.前景展望
WINDOWS操作系统是从WINDOWS 95的第2版开始支持USB设备的,WINDOWS98、WINDOWS ME都支持USB1.1,虽然在这些操作系统下,可以使用USB2.0的设备,但是不能充分发挥这些设备的高速特性;WINDUWS XP、WINDOWS 2000则支持USB2.0版本。
三.USB系统结构
1.USB主机
USB主机就是PC机中进行USB总线传输的部件和程序的总称,包括USB主控制器、驱动程序和客户软件。在主控制器上包含主控制器本身和根集线器,PC机上的USB接口就是直接连到根集线器上的;驱动程序包括USB主控制器驱动程序、根集线器驱动程序和USB设备驱动程序,客户软件则是用于界面功能的应用程序,它的功能根据具体的USB设备的不同而不同,可能是播放音乐、可能是文件传送等等。
在系统中,USB主机是唯一的,不能有两个USB主机,所以USB总线不能用于连接两台计算机。
2.USB设备
USB设备包括集线器和功能设备。
集线器(又称为HUB)用于扩展USB总线的枝点,它的上行端口用于连接根集线器或上一级的集线器,它的下行端口用于连接USB功能设备或下一级的集线器。虽然集线器可以进行级连,但是不包括根集线器在内,集线器最多可以有5层结构。
功能设备就是象U盘、USB鼠标之类的实现具体功能的设备,通常它具有单一的功能,如果它不止一个功能,从主机的观点来看,它就相当于永远接有多个功能设备的USB集线器。
3.USB接口和电缆
USB接口和电缆连接用于连接集线器和USB功能设备,连接器分成A型连接器和B型连接器两种,当然相应的电缆插头也有A型和B型两种。
USB电缆有4根导线,依照编号分别是:VUSB(1号管脚、红色导线)、D-(2号管脚、白色导线)、D+(3号管脚、绿色导线)、GND(4号管脚、黑色导线),其中VUSB 和GND是USB的电源线,D-和D+是一对差分数据线。另外电源的管脚比数据线的管脚要长一点,这保证在连接USB设备时,电源信号先于数据信号到达USB设备。
3.2 USB技术规范
一.USB通信模型结构
在网络课程中,把计算机通信分成7层结构,在逻辑上,两台计算机的同一层之间相互连接、相互进行数据传送,而实际上是一台计算机的各层之间进行数据传送,只有最低的物理层相互连接、相互进行数据传送。
在USB系统中则是把通信分成3个层:最上层是功能层,中间是USB设备层,最下面是USB总线接口层。
图2-1 USB通信层次图
如图2-1所示,主机的客户软件在逻辑上负责和USB设备的功能单元进行通信,实现其具体的功能。它包含界面应用程序和USB设备驱动程序两个部分。
USB系统软件包括USB总线驱动程序USB主控制器驱动程序和非USB主机软件。USB系统软件负责和USB逻辑设备进行通信、管理USB数据传输。
USB总线接口包括主控制器和根集线器,完成实际的USB数据传输。
在USB设备上,USB总线接口负责完成串行通信;USB逻辑设备是端点的集合,所谓端点也就是USB设备中的实际物理单元,USB设备中每一个端点都有一个唯一的地址,USB主机通过设备地址、端点地址就可以与某一个端点进行通信;功能单元又是接口的集合,所谓接口是由若干个端点构成的。比如一个带MP3功能的U盘,当需要进行文件传输时,主机的客户软件(在逻辑上)将与这个设备的存储接口进行数据传送,当播放MP3时将使用音频接口。
一个USB设备包含若干个配置(如:能够进行不同速率传送的USB设备,不同速率下就对应不同的配置),一个配置包含若干个接口(如:带MP3的U盘就应该有音频接口和数据接口),一个接口又包含若干个端点(如:一个数据接口可能有数据端点和状态端点等)。
二.USB的数据传输
在USB通信系统中,所有通信过程都是由主机发起的,在客户软件需要进行一次数据传送时,它向USB总线驱动程序发出一个IRQ(I/O请求包),请求进行一次数据传送;USB总线驱动程序相应这个请求,并且将要传送的数据转换成若干个具有USB格式的事务处理,传给主控制器驱动程序;主控制器驱动程序又把若干事物处理进行组合,形成一系列以数据帧为单位的事物处理列表(要把一帧内容安排在1ms内发送);USB主控制器读取事物处理列表,以信息包为单位,发送给USB设备。
USB设备的USB总线接口接收到信息包后,把它分解后传给相应的端点。
在USB总线接口层上,USB的数据传输属于同步串行通信,采用反向非归零的NRZI编码方式进行数据传输,也就是:没有电平跳变表示“1”,有电平跳变表示“0”。采用这种NRZI编码时,如果传送多个“1”,就一直是一种电平,容易使接收方失去同步,所以规定在连续6个“1”之后,发送器强制插入一个“0”,在接收方,如果收到连续6个“1”,就删除后面的“0”。
D-、D+引脚上的一队差分信号,相位相差180度,这样可以减少噪声的干扰。
三.USB的事务处理
USB的事务处理是USB主机与USB设备进行数据传送的基本单位,它本身又是由若干信息包组成的。当应用程序需要与USB设备进行一次数据传送时,由USB总线驱动程序把要传送的数据转换成若干个事物处理,再由USB主控制器驱动程序把其它应用程序要传送的事物处理进行合理的组合,由此形成一帧信息。
1.USB事务处理的构成
USB事务处理通常有3个阶段:令牌阶段、数据阶段和握手阶段。
令牌阶段由USB主机发出的一个令牌包构成,任何一个事务处理都必须有令牌包,它指示了事务处理的功能。(同时这也说明USB数据传送总是由USB主机发起的)。
数据阶段由若干个数据包组成,数据的传输方向可以是从USB主机到USB设备的下传,也可以是从USB设备到USB主机的上传。数据包分成DATA0数据包和DATA1数据包,如果数据阶段传送多个数据包,则交替传送DATA0数据包和DATA1数据包。有些事务处理没有数据阶段。
握手阶段由握手包构成。握手包分成ACK包、NAK包、STALL包。ACK包由数据接收方发出,表示正常应答,没有发现差错;NAK包由USB设备发出,表示暂时无法发送/接收数据,但是(在USB主机不干预的情况下)最终可以完成任务;STALL包由USB设备发出,它表示USB设备没有能力发送/接收数据(通常是在某个端点被停止、或某个请求不被USB设备支持时,USB设备发出)。
2.USB事务处理的分类
USB事务处理分成IN事务处理、OUT事务处理、PING事务处理、SETUP事务处理、SOF事务处理等。
IN事务处理用于完成从USB设备到USB主机的数据传送。它由IN令牌包、USB数据包、ACK握手包组成。
OUT事务处理用于完成从USB主机到USB设备的数据传送。它由IN令牌包、USB数据包、ACK握手包组成。
PING事务处理是USB2.0协议中新的事务处理,它USB主机询问USB设备是否可以接收数据:以往当USB主机要进行OUT事务处理时,如果USB设备没有准备好,则USB设备返回NAK握手包。在这以后USB主机将继续尝试OUT事务处理,由于OUT事务处理中的数据包可能含有很多的数据,这样就会占用很多带宽。PING事务处理中只有令牌包和握手包,长度很短,当USB主机要进行OUT事务处理时,首先发出一个PING事务处理。如果USB设备返回ACK握手包(表明USB设备有能力接收数据),则USB主机进行OUT事务处理;如果USB设备返回NAK握手包(表明USB设备暂时无法接收数据),则在这以后USB主机继续尝试PING事务处理,直到USB设备返回ACK握手包。
SETUP事务处理用于USB主机向USB设备传送具有特定的USB数据格式的数据,这种USB设备接收这些数据,从而为进行控制传输作好准备工作。SETUP事务处理由SETUP令牌包、一个8字节的USB数据包、ACK握手包组成。
四.USB的信息包
在USB总线上传递的有4种信息包:令牌包、数据包、握手包和专用包。各种信息包在开始时首先都是由7个“0”一个“1”组成的同步序列;然后是一个字节的表示包编号的包ID;下面是传送的具体信息,可能有若干个字节;然后是CRC校验码;最后是包的结束标志(EOP)。
1.令牌包
所有的事物处理都是由令牌包开始的,令牌包又分成表示帧开始的SOF令牌包、主机USB设备进行配置的SETUP令牌包、主机向USB发送数据的OUT令牌包和PING令牌包、主机接收USB设备数据的IN令牌包。除了SOF令牌包之外,其它几个令牌包的格式如下:
字段名 |
PID |
ADDR |
ENDP |
CRC5 |
位数 |
8 |
7 |
4 |
5 |
PID是包标识字段,其低4位是指明信息包的种类,高4位是低4位的取反。比如:OUT令牌包的PID字段是11100001b、IN令牌包的PID字段是10010110b。
2.数据包
数据包可以是主机发出的,也可以是USB设备发出的,它有共有4种类型;DATA0、DATA1、DATA2、MDATA,其中DATA0和DATA1支持差错控制机制,在进行多个数据包传送时,这两种数据包是交替传送的,配合握手机制,保证数据的正确传输。
数据包由PID字段(包的标识字段)、数据字段和16位CRC字段组成。
数据字段可以是多个字节,但是低速传输最多8个字节;全速传输最多1023个字节;高速传输最多1024个字节。
3.握手包
握手包是用来确认信息被接收的情况。
握手包包括ACK确认包、NACK非确认包和STALL停止包;
ACK包表示接收方正确地进行了接收。
NACK包在IN传输时表示USB设备当前没有数据传给主机,在OUT传输时表示USB设备当前不能接收数据。
STALL包表示USB设备没有能力接收或发送数据。
握手包的结构比较简单,它仅仅由8位PID字段构成。
五.USB的信号和电源
在USB总线的数据传送是在USB数据线上的D+和D-信号线,按照NRZI编码进行的,通常D+和D-互为反向,而且用没有电平跳变表示“1”,有电平跳变表示“0”。除此以外,USB通信格式中还定义了以下几种信号状态:
差分数据“0”:(D-)-(D+)>200mV,且D->2V
差分数据“1”:(D+)-(D-)>200mV,且D+>2V
单端“0”(SE0):D+、D-<0.8V
单端“1”(SE1):D+、D->0.8V
J状态:低速时同差分数据“ 0 ” ,全速时同差分数据“ 1 ”
K状态:低速时同差分数据“ 1 ” ,全速时同差分数据“ 0 ”
空闲状态:D->2.7V,且D+<0.8V,或D+>2.7V,且D-<0.8V
SOP(包的开始):由空闲状态转入K状态
EOP(包结束信号):持续两位,后跟一位J状态
设备连接:空闲时间持续2mS以上
设备断开:SE0状态持续2.5μS以上
复位信号:SE0状态持续10mS以上
六.USB的传输类型
USB根据不同的USB设备定义了4种数据传输类型:块传输、中断传输、同步传输和控制传输。
块传输针对大量的、无传输时间要求的数据传输,当USB带宽紧张时,这种传输就会减少进行,但是这种数据传输如果发现差错,就要重新进行。低速方式没有块传输。
中断传输针对数量小、但是必须周期处理的数据传输,这种数据传输方式特别适合键盘、鼠标之类的设备,由于在以往的外部设备中,它们是采用中断方式进行数据传送的,所以在USB传送类型中把这种传送方式叫做中断传输。在USB的带宽中,为中断传输保留了固定的带宽。
同步传输针对音像播放一类数据传送进的,这类数据传送允许有一些差错,但是必须及时进行数据传送。在USB的带宽中,也为同步传输保留了固定的带宽。
控制传输是针对传输数量少,对传输时间、传输速度都没有特别要求但是必须保证正确性的数据传输。USB采用差错控制机制和重试机制来保证控制传输的正确性。
1.块传输
通常进行块传输的事务处理包括三个阶段:令牌阶段、数据阶段和握手阶段。
如果主机要接收数据时,它发出一个IN令牌包,如果USB设备发现它有差错,就不进行任何应答。如果收到正确的IN令牌包,就根据情况返回DATAx数据包、NAK握手包或STALL握手包。如果主机接收到有效的DATAx数据包,就向USB设备回应一个ACK握手包;如果主机收到的数据包有差错或收到NAK握手包,就会重新尝试这个事务处理。
在全速方式下,如果主机要发送数据时,它将发出一个OUT令牌包和一个数据包。如果USB设备成功接收了这个令牌包和数据包,就返回一个ACK握手包;如果暂时无力接收数据,则返回NAK握手包,则主机以后会重新尝试这个事务处理;如果USB设备的OUT端点已经被停止了,则返回STALL握手包,主机收到该握手包后,就认为该端口有错误,以后就不会尝试这个事务处理了;如果USB设备接收的令牌包和数据包有差错,就丢弃它,不进行任何应答,主机在没有收到ACK握手包,出现超时后,会重新尝试这个事务处理。
在高速方式下,主机首先发出PING包,如果主机回应ACK握手包,主机后再发起OUT事务处理。
在进行块传输时,除了用校验方式外,还采用数据触发机制保证数据传输的正确性:
在USB主机和USB设备中都有一个触发位,主机和USB设备传送的数据包交替为DATA0和DATA1数据包,在进行USB设备初始化时,USB主机要用SETUP事务处理把自己和USB设备的块传输端点的触发位清“0”。
当进行OUT事务处理时,USB主机首先发送DATA0数据包,如果USB设备正确接收了数据,就会使自己的触发位置“1”,并且返回ACK握手包。USB主机收到正确的ACK握手包后,把自己的触发位置“1”,接下去发送的是DATA1数据包,……。如果USB设备没有正确接收数据,就不会改变自己的触发位,也不会发出ACK握手包。
如果USB设备正确收到了DATA0数据包,而返回的握手包出现了差错,USB主机没有收到ACK握手包,也就不会改变主机的触发位。这样,主机的触发位和设备的触发位就不一致了。在重试中,就会仍然发送DATA0数据包。如果主机在接收ACK握手包时出现差错,则在重试中,仍然发送DATA0数据包,而USB设备发现这个数据包有与自己的触发位不一致,就丢弃这个DATA0数据包,并回应ACK握手包,USB主机收到这个ACK握手包后,把自己的触发位置“1”,于是USB主机和USB设备的触发位又一致了。
块传输数据包的最大长度在全速方式下可以是8字节、16字节、32字节或64字节;在高速方式下只能是512字节(USB主机在对USB设备进行初始配置时,要从USB设备读取该值)。如果要传送的数据多于数据包的最大长度,则必须用多个事务处理进行传送。
2.中断传输
中断传输的事务处理包括IN传输和OUT传输,具有令牌、数据、握手三个阶段。中断传输也采用数据触发机制保证数据传输的正确性,其工作过程与块传输相同。
中断数据包的最大长度,在低速方式下必须小于等于8个字节;在全速方式下必须小于等于64字节;在高速方式下必须小于等于1024字节。
3.同步传输
同步传输只用于全速方式和高速方式,它包括IN传输和OUT传输。同步传输的事务处理只有令牌阶段和数据阶段,没有握手阶段。它不使用应答机制和数据触发机制。
同步数据包的最大长度,在全速方式下必须小于等于1023字节;在高速方式下必须小于等于1024字节。
4. 控制传输
控制传输包含建立阶段、数据阶段和状态阶段。在建立阶段USB主机用SETUP事务处理向USB设备发送8个字节的数据,这8个字节数据具有USB定义的格式
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bmRequestType |
1 |
指明控制请求的特性: D7=1,数据由主机到设备 D7=0,数据由设备到主机 D6~D5:类型 0标准 1设备类 2供应商 3保留 D4~D0:接收方 0设备 1接口 2端点 3其它 4~31保留 |
1 |
bRwquest |
1 |
控制请求的请求号 |
2 |
wValue |
2 |
控制请求的参数 |
4 |
wIndex |
2 |
控制请求的参数 |
6 |
wLength |
2 |
数据阶段的字节数 |
控制传输的数据阶段可以没有,也可以由若干个IN事务处理或OUT事务处理组成,负责传送具有USB格式的数据或厂商自己定义格式的数据。
控制传输的数据阶段也采用数据触发机制保证数据传输的正确性。
控制传输的状态阶段用于USB设备向USB主机报告前面的传输情况。
控制传输的状态阶段由一个IN事务处理或一个OUT事务处理组成,而且一定是采用DATA1数据包,其数据传输的方向刚好与数据阶段的传输方向相反(如果没有数据阶段,就采用IN事务处理)。
如果数据阶段是OUT传输,状态阶段就是IN事务处理,USB设备以0长度的数据包、NAK握手包或STALL握手包回应。如果数据阶段是IN传输,状态阶段就是OUT事务处理,主机向USB设备发送任意长度的数据包,USB设备以ACK握手包、NAK握手包或STALL握手包回应。
七.USB设备的接入和断开过程
当USB设备接到USB集线器下行端口后,集线器首先为它提供电源,当集线器检测到D+、D-之间的一定的值以后,就认为有设备接入,从USB设备接入到电源能稳定工作的时间称为“上电状态”。
接着集线器采用中断IN管道向主机报告有设备接入,在100毫秒以后,主机向集线器发出SetPortStatus请求,复位这个USB设备,从电源稳定到完成复位任务的时间称为“连接状态”。
接着主机使用缺省地址——0号地址向USB设备发出GetDescriptor请求,USB设备返回设备描述符前半段,主机从而知道设备的类型等信息,主机向USB设备发出SeyAddress请求,为USB设备分配一个唯一的地址,以后USB设备就使用这个新的地址了,从USB设备复位到USB设备获得地址分配的时间称为“缺省状态”。
主机使用新地址向USB设备发出GetDescriptor(Device)请求,USB设备返回设备描述符的全部内容,主机从而知道更多设备的信息;主机向USB设备发出GetDescriptor(Configuration)请求,读取其全部配置信息;主机根据以上信息,选择合适的USB设备驱动程序;主机向USB设备发出SetConfiguration(x)请求,为设备选择一个合适的配置,以后主机就可以正常地运行客户软件,进行数据传送了,从USB设备获得地址到USB设备完成配置的时间称为“地址配置状态”。
当USB设备完成配置后,USB设备就处于“配置状态”。
当USB设备进入配置状态后,如果3ms内没有USB总线活动,就进入比较省电的“挂起状态”,以后如果在USB总线上有任何信号,都会使USB设备回到配置状态。
当USB设备从集线器下行端口断开后,集线器会先禁止该端口;然后采用中断IN管道向主机报告有设备断开;主机发出GetportStatus请求给集线器,了解详细的信息;主机处理断开操作,收回分配的地址、释放占用的内存。
八.描述符
USB用描述符的形式描述USB设备、配置、接口、端点和字段串。USB主机通过读取从USB设备中读取这些描述符,从而知道USB设备的各种情况。下面分别介绍这些描述符。
1. 设备描述符
设备描述符的结构如下:
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bLength |
1 |
描述符长度:12H |
1 |
bDescriptorType |
1 |
描述符类型:01H |
2 |
bodUSB |
2 |
USB的版本号(BCD码) |
4 |
bDeviceClass |
1 |
类代码 |
5 |
bDeviceSubClass |
1 |
子类代码 |
6 |
bDeviceProtocol |
1 |
协议代码 |
7 |
bMaxPacketSize0 |
1 |
端点0的最大数据包长度 |
8 |
idVendor |
2 |
供应商ID |
10 |
idProduct |
2 |
产品ID |
12 |
dcbDevice |
2 |
设备的版本号(BCD码) |
14 |
iManufacturer |
1 |
供应商字符串描述符的索引 |
15 |
iProduct |
1 |
产品字符串描述符的索引 |
16 |
iSerialNumber |
1 |
产品序列号字符串描述符的索引 |
17 |
bNumConfigurations |
1 |
所支持的配置数 |
其中bodUSB字段是用DCB格式表示的版本号,例如版本2.13,则该字段应该是0213H。
bDeviceClass字段表示USB设备所属的设备类,当该值为0,表示USB设备的各个接口分属各个设备类;如果该值为1~FEH,表示具体的设备,如04H表示显示设备,如果该值为FFH表示自定义的设备类。
bDeviceSubClass字段表示对USB设备类的子类,例如:bDeviceClass字段为04H,bDeviceSubClass字段为01H表示CRT显示器;bDeviceSubClass字段为02H表示平板显示器。
bDeviceProtocol字段表示USB设备所采用的设备类协议。例如对于音频设备类,bDeviceProtocol字段为01H,表示单声道;该字段为02H,表示立体声。
bMaxPacketSize0字段表示端点0的最大数据包长度,对于低速设备,该值为8;对于全速设备,该值可以为8、16、32或64;对于高速只能是64。
2. 配置描述符
PC机可以用Setcnfiguration请求设置USB的配置情况,可以用Getcnfiguration请求读取当前的配置。
配置描述符格式如下:
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bLength |
1 |
描述符长度:09H |
1 |
bDescriptorType |
1 |
描述符类型:02H |
2 |
wTotalLength |
2 |
配置信息总长度 |
4 |
bNumInterfaces |
1 |
支持的接口数 |
5 |
bConfigurationValue |
1 |
配置值 |
6 |
iConfiguration |
1 |
配置字符串描述符的索引值 |
7 |
bmAttributes |
1 |
配置特性 |
8 |
bMaxPower |
1 |
所需的最大总线电流 |
其中wTotalLength字段是配置信息的总长度,包括配置描述符、接口描述符、端点描述符、设备类定义描述符和供应商自定义描述符。
bConfigurationValue字段指明配置值,即Setcnfiguration请求的参数值或Getcnfiguration请求的返回值。
bmAttributes字段按位指明配置的进一步的信息。如USB设备需要使用总线电源,D6位应为1;如USB设备支持远程唤醒功能,D5位应为1。D0~D4始终为0,D7始终为1。
bMaxPower字段指明该配置下的最大总线电流(以2mA为单位)。
3. 接口描述符
接口是端点的集合,负责完成特定的功能。接口可以包含一个或多个可替换的设置,这样,就可以在USB设备处于配置状态时,改变当前接口所包含的端点数和特性。PC机可以用SetInterface(x)请求选择设置;用GetInterface请求读取当前的设置值。
接口描述符格式如下:
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bLength |
1 |
描述符长度:09H |
1 |
bDescriptorType |
1 |
描述符类型:04H |
2 |
bInterfaceNumber |
1 |
接口号 |
3 |
bAltemateSetting |
1 |
可替换设置值 |
4 |
bNumEndpoints |
1 |
所使用的端点数 |
5 |
bDeviceClass |
1 |
类代码 |
6 |
bDeviceSubClass |
1 |
子类代码 |
7 |
bDeviceProtocol |
1 |
协议代码 |
8 |
iInterface |
1 |
接口字符串描述符的索引值 |
USB主机不能用GetDescriptor请求单独读取接口描述符,它只能做为USB配置信息的一部分在GetDescriptor(Configuration)请求中返回,同时它自身所包含的各个端点的描述符的也会一块儿返回。
bInterfaceNumber字段是接口号。
bAltemateSetting字段是可替换的设置值,可用于SetInterface请求的参数,或GetInterface请求的返回值。
bNumEndpoints字段是除端点0以外的端点数,如果某接口只使用端点0,则该值应该为0。
iInterface字段指出字符串的索引值,如果没有,该值为0。
4. 端点描述符
USB的端点是USB设备中实际的物理单元,USB主机也不能单独读取端点描述符,它只能做为USB配置信息的一部分在GetDescriptor(Configuration)请求中返回。除端点0以外的端点,都有一个端点描述符。
端点描述符的格式如下:
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bLength |
1 |
描述符长度:07H |
1 |
bDescriptorType |
1 |
描述符类型:05H |
2 |
bEndpointAdderss |
1 |
端点号及传输方向 |
3 |
bmAttributes |
1 |
端点特性 |
4 |
wMaxPackerSize |
2 |
最大数据包长度 |
6 |
bInterval |
1 |
访问间隔 |
bEndpointAdderss字段表示端点号及传输方向,该值的D0~D3位是端点号;D7位表示传输方向:D7=0表示输出端口,D7=1表示输入端口,因控制端口是双向的,所以忽略D7位;D6~D4位保留(必须为0)。
bmAttributes字段指出端点的特性。D1、D0位表示所支持传输类型:00表示控制传输,01表示同步传输,10表示批量传输,11表示中断传输;如果是同步传输,D5~D2位进一步指出其类型,如果不是同步传输,D5~D2位应该为0。
wMaxPackerSize字段的D10~D0位指明端点所支持的最大数据包长度;对于高速传输的同步端点和中断端点,该字段的D12、D11指明每小帧所包含的事物处理数:00表示1次,01表示2次,10表示3次,11保留;D13~D15位保留(必须为0)。
bInterval字段表示数据传输的间隔,对于低速中断端点,该字段的取值范围是10~255,表示10~255ms;对于全速中断端点,该字段的取值范围是1~255,表示1~255ms;对于高速中断端点,该字段的取值范围是1~16,表示 。对于全速的同步端点,该字段的取值范围是1~16,表示 ;对于高速的同步端点,该字段的取值范围是1~16,表示 。
5. 字符串描述符
字符串用来存放一些文本信息。在其它描述符中常常包含字符串描述符的索引值,它指出引用的是第几个描述符。
字符串描述符的格式如下:
地址偏移量 |
字段名 |
字节数 |
说明 |
0 |
bLength |
1 |
描述符长度:N+2 |
1 |
bDescriptorType |
1 |
描述符类型:03H |
2 |
bString |
N |
字符串(UNICODE) |
九. 设备请求
USB定义了11种标准的USB设备请求,用于完成USB设备的配置操作。除了这些标准的USB设备请求外,供应商还可以定义一些专用的设备请求。
各种USB设备请求都只能通过控制管道来进行传输,当USB接收到无效的请求或不支持的请求,将在该事务处理的数据阶段和状态阶段返回STALL握手包。下面把几个常用的USB请求。
1. GetStatus请求
GetStatus请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
10000000b |
00H |
0 |
0 |
2 |
设备状态 |
10000001b |
接口 |
接口状态 |
|||
10000010b |
端点 |
端点状态 |
GetStatus请求用于读取USB设备、接口、端点的状态。
wIndex字段中“接口”的取值就是接口描述符中的bInterfaceNumber字段值,“端点”的取值就是端点描述符中的bEndpointAdderss字段值。如果该端点或接口不存在,则返回STALL握手包。
如果是读取设备状态,则在数据阶段返回的格式如下:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
保留(置0) |
远程唤醒 |
自供电 |
|||||
D15 |
D14 |
D13 |
D12 |
D11 |
D10 |
D9 |
D8 |
保留(置0) |
如果是读取端点设备状态,则在数据阶段返回的格式如下:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
保留(置0) |
停止 |
||||||
D15 |
D14 |
D13 |
D12 |
D11 |
D10 |
D9 |
D8 |
保留(置0) |
如果是读取接口设备状态,则在数据阶段返回的两个字节全为0。
2. ClaerFeaturs请求
ClaerFeaturs请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
00000000b |
01H |
特性选择符 |
0 |
0 |
无 |
00000001b |
接口 |
||||
00000010b |
端点 |
ClaerFeaturs请求用于清除或禁止USB设备、接口、端点的某些特性。
当特性选择符为0时,对应USB端点,它将清除USB端点的停止状态。
当特性选择符为1时,对应USB设备,它将清除USB设备的远程唤醒功能。
当特性选择符为2时,对应USB设备,它将清除USB设备的测试状态。
3. SetFeature请求
SetFeaturs请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
|
00000000b |
03H |
特性选择符 |
0 |
测试选择符 |
0 |
无 |
00000001b |
接口 |
- |
||||
00000010b |
端点 |
- |
SetFeaturs请求用于设置或使能USB设备、接口、端点的某些特性。
特性选择符的取值同上面的ClaerFeaturs请求;如果设置进入USB设备的测试状态,则测试选择符进一步指明测试的内容(01H:Test_J、02H:Test_K、03H:Test_SE0_NAK、04H:Test_Packet、05H:Test_Force_Enable)。
4. SetAddress请求
SetAddress请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
00000000b |
05H |
设备地址 |
0 |
0 |
无 |
SetAddress请求用于为USB设备分配一个地址,以后USB设备将启用该地址与USB主机通信。
5. GetDescriptor请求
GetDescriptor请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
10000000b |
06H |
类型和索引 |
0或语言ID |
描述符长度 |
描述符 |
GetDescriptor请求用于从USB设备读取一个描述符。
wValue字段由两个字节组成,高字节用于指示读取什么描述符(即描述符bDescriptorType字段的内容)。如果是读取字符串描述符,低字节表示字符串的索引值,读取其它描述符时,低字节应该置0。
如果是读取字符串描述符,wIndex字段指明字符串语言的ID,读取其它描述符时,低字节应该置0。
注意:接口描述符、端点描述符设备等都作为配置信息,随配置描述符一并读出。
6. SetDescriptor请求
SetDescriptor请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
00000000b |
07H |
类型和索引 |
0或语言ID |
描述符长度 |
描述符 |
SetDescriptor请求用于更新USB设备已有的描述符或添加新的描述符。
wValue字段的意义同GetDescriptor请求的wValue字段。
SetDescriptor请求只用于设备描述符、配置描述符和字符串描述符。接口描述符、端点描述符都应该作为配置的一部分在SetDescriptor(Configuration)请求中设置。
7. GetConfiguration请求
GetConfiguration请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
10000000b |
08H |
0 |
0 |
1 |
配置值 |
GetConfiguration请求,用于返回USB设备的配置值:在USB处于地址状态时,返回0;在USB处于配置状态时,返回配置值。
8. SetConfiguration请求
SetConfiguration请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
00000000b |
09H |
配置值 |
0 |
0 |
无 |
SetConfiguration请求,用于给USB设备选择一个配置。
9. GetInterface请求
GetInterface请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
10000001b |
0AH |
0 |
接口 |
1 |
设置值 |
GetInterface请求,用于读取USB设备某个接口当前的设置值。
10. SetInterface请求
SetInterface请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
00000001b |
0BH |
设置值 |
接口 |
0 |
无 |
SetInterface请求,用于给USB设备某个接口选择一个可替换的设置值。
11. SynchFrame请求
SynchFrame请求在Setup事务中的格式如下:
bmRequestType |
bRequest |
wValue |
wIndex |
wLength |
数据阶段 |
10000010b |
0CH |
0 |
端点 |
2 |
帧号 |
SynchFrame请求,用于USB设备的同步端点,读取开始帧的帧号。
3.3 CH372芯片的内置固件模式
一. 概述
CH372恒沁公司开发的USB 总线的通用设备接口芯片。在USB设备方,CH372 具有8 位数据总线和读、写、片选控制线以及中断输出,可以方便地挂接到单片机的系统总线上;在计算机系统中,CH372 的配套软件提供了简洁易用的操作接口,与本地端的单片机通讯就如同读写文件。CH372 内置了USB 通讯中的底层协议,具有内置固件模式和外置固件模式。在内置固件模式下,CH372 自动处理默认端点0 的所有事务,本地端单片机只要负责数据交换。
在外置固件模式下,由外部单片机根据需要自行处理各种USB 请求,从而可以实现符合各种USB 类规范的设备。
CH372提供一对主端点和一对辅助端点,支持控制传输、批量传输、中断传输。CH372 芯片内部具有5 个物理端点:
端点0 是默认端点,支持上传和下传,上传和下传缓冲区各是8 个字节;端点1 包括上传端点和下传端点,上传和下传缓冲区各是8 个字节,上传端点的端点号是81H,下传端点的端点号是01H;
端点2 包括上传端点和下传端点,上传和下传缓冲区各是64 个字节,上传端点的端点号是82H,下传端点的端点号是02H。
在内置固件模式下,端点1 的下传端点被禁止,端点1 的上传端点作为中断端点,端点2 的上传端点作为批量数据发送端点,端点2 的下传端点作为批量数据接收端点。
二. CH372的引脚
Vcc、GND:电源线和地线。
V3:3.3V的电源线。
X0、X1:连接12MHz石英晶振。
D0~D7:与单片机连接的数据线。
UD+、UD-:USB总线的数据线,其中UD+可以包含可内控的上拉电阻。
WR、RD:读写控制信号线。
CS:片选信号线。
INT:中断申请信号线。
A0:地址线。A0=1对应命令口,A0=0对应数据口。
三. CH372的命令
单片机可以向CH372写入各种控制命令,从而控制CH372的工作。在内置固件模式下,CH372有以下几个命令:
代码 |
名称 |
输入数据 |
输出数据 |
功能 |
05H |
RESET_ALL |
|
(等40ms) |
CH372复位 |
06H |
CHECK_EXIST |
任意数据 |
按位取反 |
测试CH372 |
12H |
SET_USB_ID |
VID低8位 |
|
设置USB 的厂商识别码VID和产品识别码PID |
VID高8位 |
||||
PID低8位 |
||||
PID高8位 |
||||
15H |
SET_USB_MODE |
模式代码 |
(等20uS)操作状态 |
设置CH372的工作模式 |
22H |
GET_STATUS |
|
中断状态 |
获取中断状态并取消请求 |
23H |
UNLOCK_USB |
|
|
释放中断缓冲区 |
28H |
RD_USB_DATA |
|
数据长度 |
从当前的USB中断的端点缓冲区读取数据块并释放当前缓冲区 |
数据流 |
||||
2AH |
WR_USB_DATA5 |
数据长度 |
|
向USB端点1的上传缓冲区写入数据 |
数据流 |
||||
2BH |
WR_USB_DATA7 |
数据长度 |
|
向USB端点2的上传缓冲区写入数据 |
数据 |
如果操作状态是51H表示操作成功,如果操作状态是5FH表示操作失败。
模式代码为00H 时切换到未启用的USB 设备方式(上电或复位后的默认方式);
模式代码为01H 时切换到已启用的USB 设备方式,外部固件模式;模式代码为02H 时切换到已启用的USB 设备方式,内置固件模式。
在USB 设备方式下,未启用是指USB 总线D+的上拉电阻被禁止,相当于断开USB 设备;启用是指USB总线D+的上拉电阻有效,相当于连接USB 设备,从而使USB 主机能够检测到USB 设备的存在。通过设置是否启用,可以模拟USB 设备的插拔事件。
通常情况下,设置USB 工作模式在20uS 时间之内完成,完成后输出操作状态。
中断状态为09H表示端点1发送完数据;中断状态为0AH表示端点2发送完数据;中断状态为02H表示端点2收到数据。
为了防止缓冲区覆盖,CH372 向单片机请求中断前首先锁定当前缓冲区,暂停USB 通讯,直到单片机通过UNLOCK_USB 命令释放当前缓冲区,或者通过RD_USB_DATA 命令读取数据后才会释放当前缓冲区。由于RD_USB_DATA 命令完成时自动释放缓冲区,所以只有上传成功中断退出前需要执行UNLOCK_USB 命令。
RD_USB_DATA命令从当前USB 中断的端点缓冲区中读取数据块并释放当前缓冲区。首先读取的输出数据是数据块长度,也就是后续数据流的字节数。数据块长度的有效值是0 至64,如果长度不为0,则单片机必须将后续数据从CH372 逐个读取完;读取数据后,CH372 自动释放USB 当前缓冲区,从而可以继续接收USB 主机发来的数据。
WR_USB_DATA5命令向USB 端点1 的上传缓冲区写入数据块,在内置固件模式下,USB 端点1 就是中断端点。首先写入的输入数据是数据块长度,也就是后续数据流的字节数。数据块长度的有效值是0 至8。
WR_USB_DATA7命令向USB 端点2 的上传缓冲区写入数据块,在内置固件模式下,USB 端点2 就是批量端点。首先写入的输入数据是数据块长度,也就是后续数据流的字节数。数据块长度的有效值是0 至64。
四. 本地端的编程
单片机通过8 位并口对CH372 芯片进行读写,所有操作都是由一个命令码、若干个输入数据和若干个输出数据组成,部分命令不需要输入数据,部分命令没有输出数据。
命令操作步骤如下:
①、在A0=1 时向命令端口写入命令代码;
②、如果该命令具有输入数据,则在A0=0 时依次写入输入数据,每次一个字节;
③、如果该命令具有输出数据,则在A0=0 时依次读取输出数据,每次一个字节;
④、命令完成,可以暂停或者转到①继续执行下一个命令。
CH372 芯片专门用于处理USB 通讯,在接收到数据后或者发送完数据后,CH372 以中断方式通知单片机进行处理。
单片机通过CH372 芯片接收数据的处理步骤如下:
①、当CH372 接收到USB 主机发来的数据后,首先锁定当前USB 缓冲区,防止被后续数据覆盖,然后将INT引脚设置为低电平,向单片机请求中断;
②、单片机进入中断服务程序,首先执行GET_STATUS 命令获取中断状态;
③、CH372 在GET_STATUS 命令完成后将INT引脚恢复为高电平,取消中断请求;
④、由于通过上述GET_STATUS 命令获取的中断状态是“下传成功”,所以单片机执行RD_USB_DATA 命令从CH372 读取接收到的数据;
⑤、CH372 在RD_USB_DATA 命令完成后释放当前缓冲区,从而可以继续USB 通讯;
⑥、单片机退出中断服务程序。
单片机通过CH372 芯片发送数据的处理步骤如下:
①、单片机执行WR_USB_DATA 命令向CH372 写入要发送的数据;
②、CH372 等待USB 主机在需要时取走数据;
③、当USB 主机取走数据后,CH372 首先锁定当前USB 缓冲区,防 止重复发送数据,然后将INT引脚设置为低电平,向单片机请求中断;
④、单片机进入中断服务程序,首先执行GET_STATUS 命令获取中断状态;
⑤、CH372 在GET_STATUS 命令完成后将INT引脚恢复为高电平,取消中断请求;
⑥、由于通过上述GET_STATUS 命令获取的中断状态是“上传成功”,所以单片机执行WR_USB_DATA 命令向CH372 写入另一组要发送的数据,如果没有后续数据需要发送,那么单片机不必执行WR_USB_DATA 命令;
⑦、单片机执行UNLOCK_USB 命令;
⑧、CH372 在UNLOCK_USB 命令完成后释放当前缓冲区,从而可以继续USB 通讯;
⑨、单片机退出中断服务程序;
⑩、如果单片机已经写入了另一组要发送的数据,那么转到②,否则结束。
五. 有关的API函数
CH372 在计算机端提供了应用层接口,应用层接口是由CH372 动态链接库DLL 提供的面向功能应用的API,所有API 在调用后都有操作状态返回,但不一定有应答数据。
CH372 动态链接库提供的API 包括:设备管理API、数据传输API、中断处理API。
有关API 参数的说明请参考CH372DLL.H 或者CH375DLL.H。
设备管理API:
打开设备:CH375OpenDevice
关闭设备:CH375CloseDevice
获得驱动程序版本号:CH375GetDrvVersion
获取USB 设备描述符:CH375GetDeviceDescr
获取USB 配置描述符:CH375GetConfigDescr
复位USB 设备:CH375ResetDevice
数据传输API:
读取数据块(数据上传):CH375ReadData
写出数据块(数据下传):CH375WriteData
放弃数据块读操作:CH375AbortRead
放弃数据块写操作:CH375AbortWrite
中断处理API:
读取中断数据:CH375ReadInter
放弃中断数据读操作:CH375AbortInter
设定中断服务程序:CH375SetIntRoutine
1. CH375OpenDevice
HANDLE WINAPI CH375OpenDevice(ULONG iIndex);
//打开CH375设备,返回句柄, iIndex对应一个CH375设备。
2. CH375CloseDevice
VOID WINAPI CH375CloseDevice(ULONG iIndex );
// 关闭CH375设备指定CH375设备序号
3. CH375WriteData
BOOL WINAPI CH375WriteData(ULONG iIndex, PVOID iBuffer, PULONG ioLength ); // 写出数据块,iBuffer指向一个缓冲区, ioLength放置准备写出的数据指向长度单元,输入时为准备写出的长度,返回后为实际写出的长度。
4. CH375ReadData
BOOL WINAPI CH375ReadData( ULONG, PVOID oBuffer, PULONG ioLength); // 读取数据块, oBuffer指向一个足够大的缓冲区,用于保存读取的数据,ioLength指定CH375设备序号指向长度单元,输入时为准备读取的长度,返回后为实际读取的长度。
5. CH375SetTimeout
BOOL WINAPI CH375SetTimeout (ULONG iIndex,ULONG iWriteTimeout,ULONG iReadTimeout);//设置USB数据读写的超时,iWriteTimeout 指定USB写出数据块的超时时间,以毫秒mS为单位,iReadTimeout 指定USB读取数据块的超时时间,以毫秒mS为单位。
六. 上位机编程示例
下面的程序把当前的时间发送给USB设备,而当USB设备用中断传输方式上传数据后,把该数据显示在程序的标题栏上。
//---------------------------------------------------------------------------
#include
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
typedef void( CALLBACK * mPCH375_INT_ROUTINE ) (PUCHAR iBuffer);
HANDLE WINAPI (*CH375OpenDevice)(unsigned long);
void WINAPI (*CH375CloseDevice)(unsigned long);
bool WINAPI (*CH375WriteData)(ULONG,PVOID,PULONG);
bool WINAPI (*CH375ReadData)(ULONG,PVOID,PULONG);
bool WINAPI (*CH375SetTimeout)(ULONG,ULONG,ULONG);
bool WINAPI (*CH375SetIntRoutine)(ULONG,mPCH375_INT_ROUTINE);
HINSTANCE h;
void CALLBACK k0(unsigned char *p)
{AnsiString k(p[5]);
if(*p==0x 5a )
{
Form1->Label1->Caption="按键"+k;
}
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
FARPROC lpF;
h=LoadLibrary("CH375DLL.DLL");
if(h==NULL){Application->MessageBoxA("不能加载动态链接库CH375DLL.DLL","ERROR!",MB_OK);return;}
lpF=GetProcAddress(h,"CH375SetTimeout");
CH375SetTimeout=(bool WINAPI (_cdecl*)(ULONG,ULONG,ULONG))lpF;
lpF=GetProcAddress(h,"CH375OpenDevice");
CH375OpenDevice=(HANDLE WINAPI (_cdecl*)(unsigned long))lpF;
lpF=GetProcAddress(h,"CH375CloseDevice");
CH375CloseDevice=(void WINAPI (_cdecl*)(unsigned long))lpF;
lpF=GetProcAddress(h,"CH375WriteData");
CH375WriteData=(bool WINAPI (_cdecl*)(ULONG,PVOID,PULONG))lpF;
lpF=GetProcAddress(h,"CH375ReadData");
CH375ReadData=(bool WINAPI (_cdecl*)(ULONG,PVOID,PULONG))lpF;
lpF=GetProcAddress(h,"CH375SetIntRoutine");
CH375SetIntRoutine=(bool WINAPI (_cdecl*)(ULONG,mPCH375_INT_ROUTINE))lpF;
CH375OpenDevice(0);
CH375SetTimeout(0,1000,1000);
CH375SetIntRoutine(0,k0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
CH375SetIntRoutine(0,NULL);
CH375CloseDevice(0);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char tbuf[8];
unsigned long v0=7,v1=8;
tbuf[0] = 0x92;
tbuf[1] = 255 - 0x92;
CH375WriteData(0, tbuf, &v0);
for(int i = 0;i<8;i++)tbuf[i] = i + 8;
CH375WriteData(0, tbuf, &v1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
char tbuf[8];
unsigned long v0=7,v1=8;
struct time ti;
tbuf[0] = 0x92;
tbuf[1] = 255 - 0x92;
CH375WriteData(0, tbuf, &v0);
tbuf[0]=18;
tbuf[1]=18;
gettime(&ti);
tbuf[2]=ti.ti_sec%10;
tbuf[3]=ti.ti_sec/10;
tbuf[4]=ti.ti_min%10;
tbuf[5]=ti.ti_min/10;
tbuf[6]=ti.ti_hour%10;
tbuf[7]=ti.ti_hour/10;
CH375WriteData(0, tbuf, &v1);
}
七. 下位机编程示例
#include
#include
#define uchar unsigned char
#define uint unsigned int
#define D372 XBYTE[0xbcff]
#define C372 XBYTE[0xbdff]
#define CMD_L 0x07
#define USB_INT_EP2_OUT 0x02
#define USB_INT_EP2_IN 0x 0a
sbit DCLK=P1^5;
sbit DIN=P1^6;
sbit LOAD=P1^7;
sbit DOUT=P3^3;
code unsigned char B451[27]={0x 3f ,0x06,0x5b,0x 4f ,0x66,0x6d,0x7d,
0x07,0x 7f ,0x 6f ,0x77,0x 7c ,0x58,0x5e,0x79,0x71,0x00,0x46,
0x40,0x41,0x39,0x 0f ,0x08,0x76,0x38,0x73,0x80};/*数码管的字段码*/
unsigned char cmd372;
unsigned char dbuf[8]={16,16,16,16,16,16,16,16},dls=0x00;
/*显示缓冲区*/
unsigned char kbuf=0xff;
/*键盘缓冲区*/
void wait(uchar x)/*延时x毫秒*/
{uchar i;
while(x-->0)
for(i=0;i<114;i++);
}
void CMD451(int x)/*向451写入命令 */
{uchar i;
for(i=0;i<12;i++)
{
if(x&0x0001)DIN=1;
else DIN=0;
DCLK=0;
DCLK=1;
x=x>>1;
}
LOAD=0;
wait(1);
LOAD=1;
}
uchar r451()/*读451*/
{
uchar i,x=0;
DOUT=1;
for(i=0;i<7;i++)
{ x=x<<1;
if(DOUT==1)x++;
DCLK=0;
wait(1);
DCLK=1;
wait(1);
}
return x;
}
void nop451()/*451空操作*/
{CMD451(0);
}
void rst451()/*451复位*/
{CMD451(0x201);
}
void sl451()/*451左移*/
{CMD451(0x300);
}
void sr451()/*451右移*/
{CMD451(0x302);
}
void rl451()/*451左环移*/
{CMD451(0x301);
}
void rr451()/*451右环移*/
{CMD451(0x303);
}
void ss451(char x)/*设定451系统参数*/
{CMD451(0x400|x);
}
void sd451(char x)/*设定451显示参数*/
{CMD451(0x500|x);
}
void ls451(char x)/*设定闪烁*/
{CMD451(0x600|x);
}
void load451(uchar addr,uchar x)/*加载数据*/
{int y=((int)addr)<<8;
CMD451(0x800|y|x);
}
uchar rkey451()/*读取键盘*/
{CMD451(0x700);
return r451();
}
void Init451()/*451的初始化*/
{DIN=0;
wait(1);
DIN=1;
wait(1);
rst451();
ss451(0x03);
}
void Reset372()/*CH372复位*/
{
C372=0x05;
wait(50);
}
bit Check372(uchar x)/*检查CH372工作是否正常*/
{
C372=0x06;
D372=x;
if(D372==~x)return 1;
return 0;
}
void SetID372(uint VID,uint PID)/*设置厂商识别码和产品识别码*/
{
C372=0x12;
D372=VID%256;
D372=VID/256;
D372=PID%256;
D372=PID/256;
}
bit SetMode372(uchar x)/*设置372的工作模式*/
{
C372=0x15;
D372=x;
wait(1);
if(D372==0x51)return 1;
return 0;
}
uchar GetInt372()/*获取372中断的原因*/
{C372=0x22;
return D372;
}
void ULBuf372()/*释放当前USB缓冲区*/
{C372=0x23;
}
uchar RD372(char *p)/*372读取数据*/
{uchar a,i;
C372=0x28;
a=D372;
//p++;
for(i=1;i<=a;i++)
{*p=D372;
p++;
}
return a;
}
void WR5_372(uchar x,uchar *p)/*372写中断端点*/
{uchar i;
C372=0x 2a ;
D372=x;
for(i=0;i
{D372=*p;
p++;
}
}
void WR7_372(uchar x,uchar *p)/*372写批量端点*/
{uchar i;
C372=0x2b;
D372=x;
for(i=0;i
{D372=*p;
p++;
}
}
void Delay50ms( )
{
unsigned char i, j;
for ( i=200; i!=0; i-- ) for ( j=250; j!=0; j-- );
}
void Init372()/*372的初始化*/
{Reset372();
if(Check372(34))
dbuf[0]=25;
else
dbuf[0]=0x0e;/*显示错误ch372*/
dls=0x01;
if(!SetMode372(2))
dbuf[0]=0x0e;
}
void Show()
{uchar i;
for(i=0;i<8;i++)
load451(i,B451[dbuf[i]]);
ls451(dls);
}
void Init()
{Init451();
Init372();
IE=0x85;
IP=0x04;
}
void main()
{
uchar c;
Delay50ms( );
Delay50ms( );
Delay50ms( );
Init();
/*while(1){
C372=0x 0a ;
D372=0x20;
c=XBYTE[0xbcff];
if((c&0x20)==0x20)break;
}*/
while(1)
Show();
}
void int0() interrupt 0/*接收到USB数据时,进入该中断服务程序*/
{
uchar i,j,bufin372[64],tl;
i=GetInt372();
switch(i)
{
case USB_INT_EP2_OUT:
tl=RD372(&bufin372);
if(tl==CMD_L)/*如果是命令*/
{
if(bufin372[0]!=~bufin372[1])return;
switch(bufin372[0])
{
case 0x92:
cmd372=0x92;
break;
case 0x95:
C372=0x2b;
D372=1;
D372=kbuf;
kbuf=0xff;
break;
}
}
else
{ if(tl==0) return;
if(cmd372==0x92)
for(j=0;j<8;j++)
dbuf[j]=bufin372[j];
}
break;
case USB_INT_EP2_IN:
ULBuf372();
break;
case 0x09:
ULBuf372();
break;
}
}
void int1() interrupt 2/*键盘上有键按下时,进入该中断服务程序*/
{uchar x;
idata uchar T_buf[8];
EX1=0;
IE1=0;
x=rkey451();
kbuf=x&0x 1f ;
{
T_buf[0]=0x 5a ;
T_buf[1]=0xa5;
T_buf[2]='L';
T_buf[3]='v';
T_buf[4]='0';
T_buf[5]=kbuf;
WR5_372(8,T_buf);
dbuf[0]=kbuf;
}
EX1=1;
}
3.4 CH372芯片的外置固件模式
CH372的外部固件模式是一种自由度高,而编程难度比较大的操作模式,在这种模式下,USB设备的许多应答信号是由单片机编程处理的,这如同在VB环境下使用API函数进行编程——程序可以完成各种复杂的任务,但程序比较复杂。
一.外部固件模式下的操作命令
如前所述,CH372默认进入内部固件模式,可以用SET_USB_MODE命令使CH372进入外部固件模式。在外部固件模式下,另外有以下命令:
代码 |
名称 |
输入数据 |
输出数据 |
功能 |
13H |
SET_USB_ADDR |
地址值 |
|
设置USB地址 |
18H |
SET_ENDP2 |
工作方式 |
等待4us |
设置端点0接收器 |
19H |
SET_ ENDP3 |
工作方式 |
等待4us |
设置端点0发送器 |
1AH |
SET_ ENDP4 |
工作方式 |
等待4us |
设置端点1接收器 |
1BH |
SET_ ENDP5 |
工作方式 |
等待4us |
设置端点1发送器 |
1CH |
SET_ ENDP6 |
工作方式 |
等待4us |
设置端点2接收器 |
1DH |
SET_ ENDP7 |
工作方式 |
等待4us |
设置端点2发送器 |
0AH |
GET_TOGGLE |
数据1AH |
同步状态 |
获取当前OUT事务同步状态 |
29H |
WR_USB_DATA3 |
数据长度 |
|
向USB端点0的上传缓冲区写入数据 |
数据 |
其中工作方式如下表所示:
工作方式字节 |
名称 |
说明 |
D7、D6 |
同步触发标志 |
如果D7为1,则D6是新的触发标志 |
00、01:保持当前同步触发标志 |
||
D5、D4 |
保留 |
必须为0 |
D3~D0 |
事务响应方式 |
1101保留 |
1110返回NAK |
||
1111返回STALL |
||
0000~1000=设备就绪 对于端点0和端点1的发送器,该值同时表示发送的数据长度。对于OUT返回ACK,对于IN返回数据 |
1. 设置USB地址命令
该命令用来设置USB设备的地址。在外部固件模式下,USB设备的地址是在初始化时,USB主机给USB设备分配的。单片机需要在读取该地址后,用该命令向CH372写入该地址,以后CH372将采用该地址与USB主机通信。
2. 设置端点0接收器命令
该命令设置端点0的接收器,命令中的工作方式字节,指定新的工作方式。
3. 设置端点0发送器命令
该命令设置端点0的发送器,命令中的工作方式字节,指定新的工作方式。
4. 设置端点1接收器命令
该命令设置端点1的接收器,命令中的工作方式字节,指定新的工作方式。
5. 设置端点1发送器命令
该命令设置端点1的发送器,命令中的工作方式字节,指定新的工作方式。
6. 设置端点2接收器命令
该命令设置端点2的接收器,命令中的工作方式字节,指定新的工作方式。
7. 设置端点2发送器命令
该命令设置端点2的发送器,命令中的工作方式字节,指定新的工作方式。
8. 获取同步状态命令
该命令用来获取当前OUT事务的同步状态,其输出数据的D4位若为0则表示不同步。在控制传输中, OUT中断服务程序中,应该用该命令获取同步状态,如果不同步,则忽略。
9. 写端点0上传缓冲区命令
该命令向端点0的上传缓冲区写入数据,先写入的是数据长度,然后写入数据本身,最多8个字节。
二.外部固件模式下的各端点
1. 端点0
端点0的接收缓冲区和发送缓冲区各有8个字节,SETUP事务和OUT事务都利用接收缓冲区,但使用不同的事务应答方式。
当端点0成功完成SETUP事务后,先把端点0的发送器和接收器的同步触发位置成“1”,然后才向单片机发出中断请求,让单片机读取SETUP数据并进行处理。
当CH372完成OUT事务后,会自动切换端点0接收器的同步触发标志。
当CH372完成IN事务后,会自动切换端点0发送器的同步触发标志。
对于控制读操作,通常不需要考虑同步触发标志位的问题。
对于控制写操作,由于CH372不管收到的数据与同步触发标志位是否同步,都会以中断的方式通知单片机,所以应该在中断服务程序中,用GET_TOGGLE命令获取同步状态,如果同步就实际读取该数据,如果不同步,则忽略该数据。
SET_ENDP2命令不影响SETUP事务的响应方式。对于SETUP事务,如果没有释放USB缓冲区,则返回NAK;如果没有释放了USB缓冲区,则返回ACK。
如果SET_ENDP3命令后,执行WR_USB_DATA3命令,则自动把对IN事务的响应方式设为DATA,数据长度由WR_USB_DATA3命令确定。
如果WR_USB_DATA3命令后,执行SET_ENDP3命令,则发送器将保持数据不变,但是按照SET_ENDP3命令设定的事务响应方式进行。如果IN事务的响应方式设为DATA,则数据的长度由SET_ENDP3命令确定。
2. 端点1
端点1的发送缓冲区和接收缓冲区也是各8个字节。
当端点1完成OUT事务后,也会自动切换端点1接收器的同步触发标志。
当端点1完成IN事务后,也会自动切换端点1接收器的同步触发标志。
端点1在完成OUT事务后,会自动分析同步触发标志的情况:如果不同步,则不向单片机发出中断申请;如果同步,才向单片机发出中断申请,这样单片机就只会收到同步的OUT事务。
如果SET_ENDP5命令后,执行WR_USB_DATA5命令,则自动把对IN事务的响应方式设为DATA,数据长度由WR_USB_DATA5命令确定。
如果WR_USB_DATA5命令后,执行SET_ENDP5命令,则发送器将保持数据不变,但是按照SET_ENDP5命令设定的事务响应方式进行。如果IN事务的响应方式设为DATA,则数据的长度由SET_ENDP5命令确定。
3. 端点2
端点2的发送缓冲区和接收缓冲区各是64个字节。
当端点2完成OUT事务后,也会自动切换端点2接收器的同步触发标志。
当端点2完成IN事务后,也会自动切换端点2接收器的同步触发标志。
端点2在完成OUT事务后,会自动分析同步触发标志的情况:如果不同步,则不向单片机发出中断申请;如果同步,才向单片机发出中断申请,这样单片机就只会收到同步的OUT事务。
如果SET_ENDP7命令后,执行WR_USB_DATA7命令,则自动把对IN事务的响应方式设为DATA,数据长度由WR_USB_DATA7命令确定。
如果WR_USB_DATA7命令后,执行SET_ENDP7命令,则发送器将保持数据不变,但是按照SET_ENDP7命令设定的事务响应方式进行。如果IN事务的响应方式设为DATA,则数据的长度由SET_ENDP7命令确定。
三.外部固件模式下的单片机程序流程
1. 初始化
在单片机复位后,设置CH372进入外部固件模式,并设置中断。
2. 中断处理
当CH372发出中断请求后,单片机响应中断,在中断服务程序中,首先使用GET_STATUS命令获取中断的原因,再根据具体原因,分别进行以下处理:
⑴如果是端点1、端点2的OUT事务引起中断,则使用RD_USB_DATA命令读取数据。
⑵如果是端点1、端点2的IN事务引起中断,则使用UNLOCK_USB命令释放缓冲区。
⑶如果是端点0的SETUP事务引起中断,先使用RD_USB_DATA命令读取数据,再根据该数据进行以下处理:
① 如果是USB请求CLEAR_FEATURE,则根据请求中FEATURE以及端点号的分析处理,对于ENDPOINT_HALT,用SETENDP命令。
② 如果是USB请求GET_DESCRIPTOR,使用WR_USB_DATA3返回描述符的前8个字节,并保存该请求命令,为传送后续部分做好准备。
③ 如果是是USB请求SET_ADDERSS,那么保存该命令。
④ 如果是USB请求SET_CONFIG,那么保存设置值。通知主程序初始化成功与否。
⑤ 如果是USB请求GET_CONFIG,那么使用WR_USB_DATA3返回当前的配置值。
⑥ 如果是USB请求GET_INTERFACE,那么使用WR_USB_DATA3返回当前的接口值。
⑦ 如果是USB请求GET_STATUS,那么使用WR_USB_DATA3返回当前的状态值。
⑧ 其余的USB请求根需要进行处理,如果不支持,则用SET_ENDP2或SET_ENDP3设置应答为STALL。
⑷如果是端点0的OUT事务引起中断,则用RD_USB_DATA命令读取数据。
⑸如果是端点0的IN事务引起中断,则根据保存的USB请求命令,进行如下处理:
① 如果是USB请求GET_DESCRIPTOR,则用命令WR_USB_DATA3返回描述符的后续部分。
② 如果是是USB请求SET_ADDERSS,则用命令SET_USB_ADDR 设置USB的地址。
③ 任何USB请求,在中断退出之前,都应该使用UNLOCK_USB,释放接收缓冲区。
⑹如果是USB总线复位,则清配置值等。
⑺对于任何一个USB中断都必须对应一个UNLOCK_USB或RD_USB_DATA命令。