[转帖]USB学习总结
转贴自:http://www.mcublog.com/more.asp?name=olivercheung&id=5818张大海 2005年12月23日
这段时间,我学习了USB2.0相关的一些知识,用到的芯片是ISP1581。它是由PHILIPS公司出口的一种目前使用很广的USB2.0器件。
涉及到的参考资料:
1. USB2.0 协议(中英文版,中文版为周力功《USB2.0与OTG规范及开发指南》)
2. ISP1581 DataSheet(中、英文版,其中中文版为周力功翻译)
3. ISP1581 编程指南(中、英文版,其中中文版为周力功翻译)
4. 固件例程
一、USB2.0 协议
学习USB的起点是USB的协议,千万不要颠倒了顺序。我开始看的时候就是从ISP1581 DataSheet开始看起的,后来觉得无论自己如何理解器件的说明书,都不能够看懂找来的FIREWARE的源程序,就只好再返回USB协议了。
其实USB的协议中最重要的是第1,4(4.7),5,8,9章,这里初学者必看的章节,其中的重中之重却是第8,9两章,只要把这两章的内容真的懂清楚了,再看固件的程序就比较好懂了。
协议中主要涉及到以下几个难点:
1.包与令牌的区别:
令牌(token)是包的一种,其他的三种包分别是数据包,握手包,特殊包。很显然,从名字就可以发现,令牌包是作用是用于主机向设备发出一些控制命令,也正是这些控制命令到达具体的器件后,在上体的器件上会产生各种中断源,而其他的一些包都不会产生中断。换句话说,只要中断产生,必须是收到了令牌包。
2.控制事务
控制事务是最基本,也是最重要的USB事务,只要明白了控制事务的机制,就会理解其他各种数据流过程中相应的事务了,如批量事务,中断事务,同肯事务等。当然,有些为也会把事务分为三大类事务:SETUP事务,IN事务和OUT事务。但是从本质上说,四种传输事务都是由这三种基本事务所构成的。
比如说SETUP事务,它是由一个SETUP事务,大于等于0个IN/OUT事务以及1个OUT/IN事务所组成的。而每个SETUP事务中都会涉及到3种类型的包:SETUP包,DATA包和握手包。对IN和OUT事务来说,与SETUP事务中不同的是其令牌包不是SETUP令牌,而是IN和OUT令牌。除此以处,没有什么大的不同。
在这里尤其要注意的是控制传输过程中的状态阶段里,设备必须要向主机报告该传输前面的建立和数据阶段的执行结果,这一部分可参考协议中8.5.3的部分。
3.USB设备的结构(即CH9)。
本部分主要说了三个大问题:USB的状态,USB的标准设备请求和USB的标准描述符。
下面主要说明后两个问题:
1)USB的标准设备请求
这是主机通过默认的控制通道(端点)EP0,通过令牌(SETUP,IN,OUT)来对设备发出的命令。请记住,不论是这个请求的发送,还是对这个请求的回答(数据或握手)都必须在默认的控制通道内进行。
对所有的主机发送的请求来说 ,设备都会收到相同结构的一个请求数据结构,这个数据结构指明了下一阶段中数据传输的方向,及请求的接收方,不同的请求接收方可能会对同一个请求做出不同的应答,同时也指出了具体的请求索引值,设备(固件程序)通过这个请求索引值来确定主机的请求,这也从一个方面说明了为什么是标准的设备请求,因为这些请求已经被USB协议所定义,只需要知道其索引号就可以明确地知道是什么请求,当然厂商也可以定义一些自定义的请求,这就需要厂商自己做一些请求的索引及相关的约定了。
2)设备描述符
设备描述符对一个具体的设备来说是十分重要的,它说明了设备的类型,生产厂商,产品编号,使用的USB协议,供电方式,接口的数量,每个接口支持的端点数目,每个端点的属性,每个端点支持的最大缓冲区等重要信息。这些信息是由固件在编程时就确定好的,已经存放在固件程序里,当USB总线枚举时,设备需要把这些信息提供给主机(即响应一些主机的请求时发送给主机的数据)。
二、ISP1581 DataSheet
在初步理解了USB2.0的规范后,下一步应该看的是具体的芯片资料,这里我选择的是ISP1581。之所以采用这个芯片,一是工作上的需要,因为我需要看懂公司以前的一些资料,另一个原因是,关于这个芯片,周力功公司有全中文和资料(数据手册和编程指南)。
在ISP1581的数据手册中,对我们开发人员来说有用的资料大概可以分为两部分:
l 功能描述
l 寄存器描述
下面分别对这两点进行说明:
1.功能描述
在这个部分当中,最重要的是SIE这部分。Philips串行接口引擎(SIE)控制完成所有USB 协议层的功能,这些功能完全由硬件来实现而不需要固件的参与。SIE模块主要完成以下功能:同步方式的识别,并行/串行转换,位填充/解除填充,CRC 校验/产生,分组标识(PID)校验/产生,地址识别和握手评估/产生。
因此实际上,SIE是完成了USB协议当中第8章的大部分功能。在阅读完这部分后,可以发现,在整个USB的数据通讯过程中,CRC码是由SIE自动产生的,不需要固件的参与;地址的识别是由SIE自动进行的,也不需要固件的参与;PID的工作也是由SIE硬件完成的。因此凡是涉及到以上这几部分的操作都不需要人工参与,都是由ISP1581的硬件完成的。这样就在编程时,解除了我们的一些困惑:如果产生/检验/发送以上这几部分数据。
唯一需要我们参与的是在控制传输的过程中,我们需要在SETUP事务的状态阶段进行握手程序的编写,这里要用到的是ISP1581的状态寄存器。该寄存器可以专门发送在SETUP事务的IN和OUT包时,需要向主机发送的握手信号(可参考USB2.0协议的第8章的相应部分)。而且这个部分也是整个USB固件开发过程中非常重要的一环,理解了这部分,就可以编写SETUP事务的相应程序了。
2.寄存器描述
在数据手册中,所有的寄存器被分成了四个部分:需要初始化的寄存器、数据流寄存器、DMA寄存器、中断寄存器。但是我把它分成以下6大类:
1) 地址寄存器——USB设备的地址
2) 中断相关的寄存器
a) 中断配置寄存器——设置中断触发时的电平/边沿,极性以及在何种情况下被触发。
b) 中断使能寄存器——使能每个端点的中断(IN,OUT)以及端点0的SETUP中断。
3) 工作模式寄存器——设置当前USB设备的工作模式:挂起,唤醒,复位,重连接等。
4) 端点相关寄存器
a) 端点索引寄存器
如果对每个端点进行访问的话,必须要先通过此寄存器进行索引,然后对相应的功能进行操作。这涉及到的操作有:
端点的MaxPacketSize,端点的类型,端点缓冲区长度,数据端口,控制功能等。
即,如果要对某个端口进行以上之一种的操作,必须先对该端口进行索引,然后再操作相应的寄存器。
b) 控制功能寄存器——端点缓存清0,端点有效,终止端点,端点的应答状态等。
c) 数据端口寄存器——用于访问被索引的端点的FIFO。
IN端点:自动加1,满后发送数据;当数据量小时,可由控制功能寄存器启动发送。
OUT端点:读完后自动清空缓冲区;可通控制功能寄存器强制清空。
d) 缓冲区长度寄存器——决定被索引的端点的当前包的大小,即DATACOUNT的值。
当配置完MaxPacketSize后自动装载DTACOUNT的值。
如有需要,可以设置一个较小的值。
复位时的长度为0。
IN:
OUT:DTACOUNT自动初始化为主机发送的数据字节数。
e) MaxPacketSize寄存器——不能对端点0进行设置。
f) 端点类型寄存器
——同步,批量,中断
——全能FIFO
——双缓冲设置
——空包
5) 通用寄存器
中断源寄存器——是否发生中断,是什么中断源引起的中断(通过读此寄存器进行判断)
——每个端点缓存都对应一个专门的中断位:EPnTX,EPnRX。
——总线的状态也会产生中断。
——DAM中断(详细的DMA中断在DMA中断源寄存器中列出)
——置1清除。
6) DMA相关寄存器
共有四种方式的DMA读写模式——GDMA(通用DMA读写方式,从机),MDAM(主机方式),MDMA(ATAPI),UDMA(ATAPI)。
其中:GDMA可以工作在两种模式下——计数器模式和EOT模式。
a) DMA配置寄存器——配置各种模式,UDMA,MDMA的时序,是否使用计数器,DREQ时序,握手信号以及DMA总线的宽度。
b) DMA硬件寄存器——DMA的控制信号的极性,DMA的模式(主,从),数据的交换方式(大端,小端)。
c) DMA命令寄存器——四种方式的读写。
d) DMA传输计数器寄存器——一次DMA传输的字节数及未被传送的字节数。
e) DAM选通时间寄存器——仅用MDMA模式。
f) 任务文件寄存器——ATA。
g) DMA中断源寄存器
h) DMA中断使能寄存器
i) DMA端点寄存器。
三、固件例程
在看完了以上的文档后,其实对USB的固件的编写仍然很不清楚,只是大概知道了关于USB的一些基本的知识,对一些规范条文的理解还不准确,甚至还存在错误。因此有必要参考一下相关的例程,看一下这些理论在固件编程当中是如何体现和运用的。
这里我选择了三个例程:一个是D12 for 51的例程;一个是由PHILIPS for others提供的例程;最后一个是周力功的例程(实际上也还是由PHILIPS的例程改编而来的,但是是针对51单片机开发的)。
对于PHILIPS提供的那个例程,总的来说还是比较复杂,因为它针对的不是51的单片机,有些命令看不懂,而最简单的MCU是51系列的,所以先由简单而起,我先阅读的是D12的固件例程。
这个D12的例程实际上与PHILIPS的例程在算法实现上是一致的,但是远比后者简单,只有几个文件,看起来也比较容易。主要让人不解的是那些乱七八糟的状态机,没有任何注释,看了一天后,总算有了点眉目。有以下体会:
1. 看一个USB的例程首先应该从它的ISR开始看起,这是因为USB上的所有事务都是通过中断的方式告诉MCU,这样MCU才能够去执行相应的操作。
2. 加深了对1581的中断产生的理解。当1581收到来自主机的一个令牌时,将产生一个中断。
3. 所有的数据传输都是同主机发起的,因此,在数据的发送阶段,如果数据的长度大于包的最大值,必须多次发送的情况下,主机每收到一个设备发送的数据包后,如果还需要再让设备发送数据,则要继续向设备发送IN令牌,从而引起1581中断,当1581收到这个中断后,才组织发送第二包的数据等。
4. 每次发送数据前,都必须首先确定需要发送的数量量的大小,如果需要发送的数据数量小于最大的包的大小时,需要重新设置缓冲区的大小,这样当写入的数据缓冲区并达到设置的缓冲区大小时,会被自动发送。
接下来,我看的是周力功1581的程序。这程序定得比较简洁,完全是以一种全新的方式进行,省去了许多令人头痛的状态机,在程序写作的思路上也很清晰地体现了USB规范的精神,是学习USB的比较好的资料。
总的来说,整个程序的触发过程是这样的:
当1581收到主机的一个令牌时,触发MCU的中断,MCU进入外部中断服务例程。在中断程序中,读中断源寄存器的值,备份到设备中断标志中,并退出中断,程序回到主程序main()。在main()中的while()语句中,一直不停地对设备中断标志进行查询,当发现有标志被置位后,进入相应的处理函数。在这个处理函数中,进一步对中断标志位进行判别,现在以SETUP为例,说明具体的运行方法。
当SETUP令牌触发中断后,接下来的数据包不会引发中断,因此,接下来我们需要直接读RX的缓冲区,即把作为数据包的USB请求读出来。再根据其bmRequestType的值来判断是什么类型的请求:标准请求/厂商请求。再根据bRequest的值确定是具体的什么请求,并调用在CHAP_9.C中定义的标准请求处理函数。
对于标准请求来说,对GET_XXX类的请求,先向端点发送所请求的数据,并等待主机的下一个IN的令牌所引起的中断,然后再发送握手信号。
对SET_XXX类的请求来说,直接对1581的寄存器进行操作,等待主机的OUT令片,然后向主机返回握手信号。
四、总结
以上总结了我最近二个星期以来学习USB的一点体会,觉得USB的一些东西真的是很难,在没有固件例程的情况下要想独立地开发出一个固件程序来几乎是根本不可能的,所以固件的例程在学习当中起到了很重要的作用,它为我们学习USB提供了一个范例,也起到了验证自己在学习USB规范的过程中对规范的理解是否正确的作用。
页:
[1]