开发嵌入式USB主控端所需的程序代码基本上是一个相当耗费脑力的工作,只要想到我们必须将原本在具有丰富资源与大型操作系统上运作的多功能个人计算机以4到8K的程序代码取代,以便能够与各种独立的USB设备沟通,就能了解这个任务有多么艰巨。
那么为什么要开发嵌入式USB主控端呢?答案非常简单,因为这是目前计算机接口设备的主流规格,如果您想要让嵌入式系统能够与记忆卡、指纹扫描仪、打印机、鼠标、键盘、音频数字化设备,甚至马克杯保温器沟通,那么USB就是应该选择的方向。
USB主控端控制器
主板
早期的USB主控端控制器使用PCI总线与主板沟通,但目前USB功能已经内建到主板上的芯片组中,这些采用PCI接口为基础的控制器负责了许多USB的经常性工作,例如对PC以及多个USB设备间的USB封包进行顺序安排与优先权判断,一个USB信息交换可以涵盖数十个甚至上百个封包,这些都必须由主控端控制器来处理,这些控制器分别为低速与全速设备用的UHCH/OHCI,以及高速设备用的EHCI。
嵌入式系统
嵌入式主控端比起主板而言复杂度较低、尺寸较小,同时成本也较便宜,尺寸较小的原因是它们使用了比PCI更简单的接口,设计较简单则是因为采用封包而非信息交换方式,另一方面,则由嵌入式韧体而非控制器逻辑来处理何时以及如何发送每个封包。
虽然乍看之下有些限制,但先让我们考虑可能采用嵌入式主控端的情况,常见的目的是读写USB记忆卡,通常称为UMD,也就是USB记忆设备。直到提供更小尺寸同时价格更低连接器的UMD出现前,Compact Flash记忆卡是与大容量内存介接最受欢迎的方式,在这个应用上经济规模产生了效用,现在我们已经可以用相当便宜的价格为嵌入式系统添加数GBytes的的记忆空间。
如果必须与其中某个特定设备沟通,例如一个UMD,那么并不一定需要PC所提供的各种广泛支持,基本上,由于PC无法预测可能插入的扩充功能,同时在同一时间也可能必须处理多个USB设备,因此需要大量的驱动程序储存空间、强大的处理效能以及复杂的驱动器堆栈来应付,但在嵌入式系统中,所需进行的编码则是针对预先选定的设备,因此这类型嵌入式做法的名称可以称为单点式解决方案。
第一个列举步骤
要对如何进行嵌入式主控端项目提出讨论,我们首先检讨USB主控端进行USB设备列举的步骤,列举动作是USB主控端找出特定USB设备特性与要求的沟通程序,以USB规格来说,这是每个设备都必须的共通步骤,因此以下的讨论可以适用于每个USB主控端应用的前端动作,在列举动作后,主控端会对设备进行组态并开始加以运作,基本上相当清楚地,这就牵涉到了随设备特性不同的额外程序代码。
《图一 将USB设备插入具备嵌入式主控端的微控制器系统,软件就能够与设备相互沟通并取得它的特性。》 |
|
图一中的程序代码是由Keil ARM7电路板、MCB2130以及包含Maxim公司MAX3421E主控端控制器的配接板所产生,ARM7使用SCLK频率高达26MHz的SPI总线与MAX3421E缓存器组沟通,Keil电路板则配备有可以用来与执行终端应用程序PC联机的串行端口。
这篇文章将描述如图一中程序代码各项事件顺序,此外,图三也显示了产生图一中程序代码的总线交通LeCory(CATC)总线的追踪结果。
《图二 USB封包的总线追踪结果以及启动并评估要求的C语言程序代码。》 |
|
主控端传送动作的详细介绍
图二为一个USB数据封包,也是USB讯息基本单位的总线动作追踪结果,虽然图中所指的为MAX3421E所采用的缓存器名称,但任何嵌入式主控端控制器都会拥有类似的缓存器。
要发出这个封包,微控制器会遵行以下的步骤:
- ●微控制器在功能地址缓存器中加载接口设备的地址,主控端使用设定地址指令为每个周边指定一个独特地址,这个步骤只有在改变地址时才需要重复执行。
- ●微控制器在传送缓存器中加载封包辨识码PID “IN”以及端点编号,并加载HXFR缓存器来发送封包,主控端控制器接着等待响应,并将任何接收的数据储存到先进先出(FIFO)缓冲器中并进行字节总数的更新、错误检查以及在最后更新结果码,并发出传送完成的中断要求。
- ●微控制器检查结果码,最常见的结果为成功与NAK忙碌,当然也可能有各种错误情况,例如总线逾时无响应、CRC检查码错误以及设备占据太多时间等。
- ●如果设备为忙碌中,主控端会重送IN要求并持续直到设备发出数据响应,当主控端显示HRSL=ACK时,微控制器会读取一个字节总数缓存器并由数据输入FIFO取得所指出的字节数。
- 所有的封包都会以这些步骤进行,只有在数据上有些微不同,例如传送动作可能不包含任何数据,或者是将送出数据加载数据输出FIFO、写入字节总数并以OUT封包的PID写入HXFR缓存器。
等待设备联机后并加以重置
USB主控端在没有设备插入的状态下,USB的D+与D-接脚会通过15KΩ的电阻连接到地,USB设备透过在其中一个数据线与3.3V间连接一个内部1500Ω电阻来标示本身的存在。
- ●拉升D-代表低速设备(1.5Mbps);
- ●拉升D+代表全速设备(12Mbps)。
因此主控端控制器就能够侦测这些总线的变化,并透过旗标位与中断,例如MAX3421E中的CONDETIRQ来指出设备插入或拔出的动作发生。
在侦测到连接动作后,主控端会发出一个USB总线重置指令,定义为最短50ms的单端零值,也就是两个数据线都为低电位,之后回复到静止总线状态,接着设备必须在发出Set_Address需求前最少等待10ms,以便让设备有足够的重置回复时间。
在这些动作完成后,主控端会发出一系列的CONTROL传送动作来进行USB设备的列举步骤。
关于CONTROL传送
CONTROL传送包含了USB的运作码,使用如表一中的格式,典型的主控端CONTROL传送动作包含在FIFO中填入8个目标字节,接着使用SETUP封包辨识码PID来发送封包。
(表一) 填入这些8字节来对USB设备发出CONTROL要求
字节编号 |
域名 |
代表意义 |
0 |
bmRequestType |
要求型态 |
1 |
bRequest |
实际要求内容 |
2 |
wValueL |
依要求不同
|
3 |
wValueH |
4 |
wIndexL |
依要求不同
|
5 |
wIndexH |
6 |
wLengthL |
依要求不同
|
7 |
wLengthH |
要求数据字节总数
Transfer0:取得设备描述符
- 主控端使用取得描述符要求来与USB设备进行沟通,这些要求可以参考USB规格书中的第9章,这个特殊要求的型态为“设备”,要进行这个要求,我们将8字节的字符串写入主控端控制器的数据FIFO,接着将周边地址设为0,例如设定缓存器PERADDR=0并发送出这个封包。
- {0x80,0x06,0x00,0x01,0x00,0x00,0x08,0x00};
虽然USB设备的描述符为18个字节长度,但这个要求只要求8个字节,原因是全速设备允许能够在Endpoint 0端点拥有四个可能的maxPacketSize值,分别为8、16、32或64 Bytes,由于事先无法得知设备的maxPacketSize,因此要扮演合适的主控端,在取得正确的数值前必须不能要求超过8个字节,虽然由设备传回的设备描述符为18个字节长度,但是聪明的USB架构设计师在描述符的第8个字节安排了我们所需的数字:EP0 maxPacketSize,在取得这个数字后,我们就可以藉以调整接下来对这个设备的EP0要求长度。
关于USB传送长度
USB传送动作的字节总数由以下法则决定:
- ●主控端要求如wLengthH/L字段中的长度;
- ●周边送出较小的要求以及可用字节总数。
周边以较短或空数据封包来代表记录的结束,较短封包为低于maxPacketSize的数值。
在主控端开始进行传送动作后,它会等待设备响应或总线逾时发生,接着进行响应的评估、设定结果码并对微控制器发出中断,由周边送入的数据储存在FIFO中,图一中设备描述符的前8个字节透过首先读取字节总数缓存器,接着由RCVFIFO读取并输出8个字节。
由设备读取送入数据的程序必须处理两个细节,首先设备被允许在数据尚未准备完成时送出NAK(Negative ACKnowledge),主控端只需重复送出要求直到取得数据为止,至于设备可允许持续送出NAK的时间长度则由用户决定,就算时间长达数个小时也依然符合USB规格。
第二个细节则包含多重封包数据传送,这对拥有长度比它的endpoint 0 maxPacketSize描述符还长的设备相当普遍,因此主控端必须发出多个IN要求并将这些由多个封包所取得的数据加以组合。
Transfer1:设定地址
由于在第一个要求时设定地址为0,因此这时必须给予每个设备一个独一无二的地址,虽然规格并没有严格要求应该在这个步骤前发出另一个USB总线重置信号,但这带来了一个相当重要的观点,那就是知道PC会进行这样的动作,虽然不知道为什么,但为了安全性还是加以跟随。
我们可以将周边地址设定为1到127中的任何一个数值,如果我们要透过USB集线器与超过一个设备沟通,那么这些独一无二地址就可以让设备在逻辑上有效分隔。
Transfer2:再次取得设备描述符
重复这个步骤的原因是这时会有两个不同情形,也就是目前我们已经取得maxPacketSize的数值,同时USB设备也位于不同的地址,这时要求的长度为18个字节。
- ●PERADDR=7
- BYTE Get_Descriptor_Device[8] =
- {0x80,0x06,0x00,0x01,0x00,0x00,0x12,0x00};
图一中显示了完整的设备描述符,同时也显示出设备在送出数据前所送出的NAK次数(10/0),这两个数字指出了NAK在CONTROL传送数据阶段与状态阶段的次数,列出的有关组态、VendorID以及ProductID等信息则由18个设备描述符中取得。
Transfer3~6:取得字符串描述字节
设备可以提供也可以不提供描述设备的字符串,主控端透过发出取得描述符字符串要求来取得这些信息,由以下设备描述符字节我们可以看出,字符串是否存在可以由iManufacturer、iProduct以及iSerialNumber等位置中是否有非0的值来表示。
Transfer4:取得组态描述符
组态描述符为9个字节长度,它包含了包括组态描述符本身加上其后描述符,例如接口、端点以及选用类别描述符的总字节数,主控端发出取得组态描述符指令要求,要求长度为9个字节,如图一中我们可以看出,第三与第四个描述符字节(20 00)指出整个组态描述符以及其他附属描述的总长度,请注意USB采Little-Endian设计,因此0x20 0x00代表32个字节。
Transfer5:再次取得组态描述符
主控端接着发出相同的要求,但要求长度为0x20,图一显示了32个字节长度的完全组态数据,接下来的信息则使用于USB规格第9章中所描述的方式由32个字节数据进行译码,由于每个描述符都以长度字节开头,因此主控端可以简单地透过将长度字节加到一个指针器,接着以链结串行的方式取出数据来取得各个独立的描述字节。
数据交替
当主控端或周边送出多重封包数据时,会使用两个数据PID,分别为DATA0与DATA1,这些为能够协助USB进行错误侦测的机制之一,在每个端点上都有一个用来决定使用那一个DATA PID的数据交替位,在重置后的第一个数据封包,不管是送到或由端点送出,都使用DATA0发送,而在两端,也就是发送与接收端同意数据正确后,也就是发出或收到ACK交握信号时,就会切换它的数值,因此接下来的数据封包发送通常就拥有交替的PID值,如DATA0、DATA1、DATA0等,因此如果接收到数据封包的数据PID不符合交替值时,就代表发生错误。
主控端控制器提供了各种协助管理数据交替的机制,部分需要主控端韧体在程序代码中维持交替值,并为每个数据传送动作默认交替值,其他则可能自动管理交替动作并在发生交替值不符时发生警告,不管那种情况,在进行端点切换时都必须先储存目前的交替状态。
以下为管理数据交替的法则:
- ●在CONTROL传送SETUP与STATUS阶段的交替值为预先设定,因此主控端程序并不需要加以初始化;
- ●BULK传送的交替值依以上描述进行,也就是收到ACK信号时让数据交替值进行变动;
- ●主控端控制器芯片通常维持了相同端点的连续传送动作数据交替;
- ●如果主控端先传送数据到端点endpoint 1,接着再切换到端点endpoint 2,那么它首先必须储存endpoint 1的交替状态,并重载所储存的endpoint 2交替状态。
电源考虑
USB主控端透过VBUS接线提供USB周边5V的电源,PC拥有提供给每个USB端口500mA电流的能力,但嵌入式系统则通常没有这样充裕的电源。
如果USB设备提出采用总线供电的要求,那么也会在它的组态描述符中标示出由VBUS联机所需的最大电流,PC主控端使用这项信息来加总设备要求的电源总额,并在电源要求无法符合时对用户发出警告,例如采用总线供电的集线器可以对每个下属端口供应100mA的电流,如果设备表示本身为总线供电并要求250mA,那么当它插入这样规格的集线器时,将会得到一个错误讯息窗口来建议将它插入其他端口。
主控端基本上并不会实际测量VBUS的电流,而是使用信赖式系统设计,由周边来告知本身的电源要求,大部分的PC主控端都在VBUS上加有保险丝,并将限额安排在高于500mA限制之上来保护电源系统。
以上的描述基本上为理论状态,接下来讨论现实的情况,如果有人在嵌入式主控端的A型连接器插入一个1A的马克杯保温器的话,那么会发生什么情形?由于这样的设备只想由VBUS电源取得电力,很明显地并不像正式的USB设备会告知本身的电源要求,但基于USB已经成为相当普遍的标准,所以必须要有任何东西都可能插入的心理准备,因此嵌入式主控端必须对VBUS上连接器上5V电源的电流加以限制,一个完成这个目标的方法是采用限流开关,首先,它扮演VBUS电源的开关,会在过电流时自动让负载脱机,并透过FLAG输出接脚来表示这个情况。
当USB总线重置动作不正确时,最少会有一个USB设备进入锁定状态,这个情况通常发生在USB主控端电源启动前设备已经插入的情况,因此以0.2秒的时间切换电源可以产生确保的重置动作。
《图三 这个CATC(LeCroy)总线追踪图显示了本篇文章中所描述的主机端列举步骤与过程。》 |
|
列举动作之外
在执行以上描述的步骤后,主控端韧体会继续检查标准USB类别,例如人机接口设备或大容量储存设备的描述符,接着执行设备特定程序代码来实际操作这些设备,不管设备所支持的类别为何,文章中所描述的列举步骤都一样通用。
结语
在执行以上描述的步骤后,主控端韧体会继续检查标准USB类别,例如人机接口设备或大容量储存设备的描述符,接着执行设备特定程序代码来实际操作这些设备,不管设备所支持的类别为何,文章中所描述的列举步骤都一样通用。
使用USB设备再也不是能够具有无限资源以及大量内建多种设备型式驱动程序PC的专利,目前大部分的接口设备都采用USB型式,因此开发适合小型嵌入式系统的USB主控端连接程序就变得相当有意义,虽然市场上已经有许多主控端控制器可以来完成低阶的工作,但也必须撰写主控端韧体来控制这些芯片,如果需要的是将介接设备限制在目标产品的USB单点式解决方案,那么韧体本身就可以相当简单。