分类: 嵌入式
2009-12-23 20:54:30
USB
前些天做了一个能在PC机上用串口和USB发送数据,再经自己制作的USB电路板把发送的数据返回给PC机的实验。期间查阅了很多资料,现在整理整理,把我们搜集的一些资料和自己的一些经验给大家分享一下。
下面的安排是这样的,先给大家介绍USB的起源、主机与设备之间的角色分配,经一翻基本概念的介绍后,给大家讲解USB的通讯协议,协议讲解完了后,通过简单的程序分析给大家解释USB收发数据的流程。最后把我们“蓝博芯科电子工作室”的这个USB原理图放到符录中,大家可以参考这个原理图自己动手做一个USB模块。
由于作者水平有限,如有任何问题,欢迎登录或发送邮件到niebing@ranbe.cn指导与交流。
一、USB是怎么来的
USB是由Compaq, DEC, IBM, Intel, Microsoft和NEC等多家美国和日本公司共同开发的一种新的外设连接技术。早在 1995年,这些公司就成立了一个称为“通用串行总线应用论坛”(Universal Serial Bus工mplementer's ForumUSB-1F)的组织,旨在促进PC总线的标准化,加速新标准的制订和产品开发该组织的目标是发展一种兼容低速和高速的技术,从而可以为广大用户提供一种可共享的、可扩充的、使用方便的串行总线。该总线应独立于主计算机系统,并在整个计算机系统结构中保持一致。为了实现上述目标,USB-IF 发布了一种称为通用串行总线的串行技术规范,简称为USB。由于微软从Windows98开始加入了对 USB的支持,使 USB 技术得到了飞速发展和极为广泛的普及。现在,USB 已成为微机巨普遍认同的一种事实上的接口标准,支持这一标准的各种新产品正在大量涌现。
USB的显著特点是:易于使用、对用户隐藏技术实现细节、可以应用于不同的领域、具有足够的带宽以适应多媒体应用的要求、具有高可靠性、设备与系统相互独立。
二、角色分配
USB是一个主一从式总线协议,即在 USB总线上有一个主设备和若干个从设备。这个主设备称为主机,而从设备称为 USB设备。主机对 USB总线拥有绝对的主控权,总线上的一切数据传输都由主机控制。在整个USB系统中只允许有一个主机。主机中的USB接口称之为 USB主控制器。而根集线器是集成在主机系统中的。在USB规范中,USB主机被定义为控制 USB的软件和硬件的集合。如果不是很严格的话,可以认为USB主机就是PC机的硬件和相应的驱动程序。主机在USB中的责任是:
(1) 检测USB设备的插入和移出。
在上电时 ,主机必须识别所有己经连接的USB设备。在识别过程中,主机为每个设备分配一个地址,并从设备获取其他配置信息.在上电后,无论何时当一个设备被连接或断开时,主机都能察觉到该事件的发生,并向设备表中加入任何新连接的设备或删除任何断开的设备。
(2) 在主机与USB设备之间管理数据流。
主机负责控制总线上的数据流。一般来说,USB通信总是由主机发起,并由主机管理整个数据传输过程。若多个外设希望同时传输数据,主机控制器按如下方式处理这个问题:它把数据通道分为1毫秒的帧,然后给每个设备分配每一帧的一部分。主机应该保证那些必须以固定速率进行的传输在每一帧中确实能得到它们所需要的时间量。在设备识别过程中,设备驱动将申请传输所需要的带宽。如果无法分配所需的带宽,主机就不允许与设备进行通信。不需要固定速率的传输使用帧的其他部分,并且可能需要等待。
(3) 进行错误检查。
主机也有错误检查的责任。它往发送的数据中加入错误校验码。当设备收到数据时,它按校验算法对数据执行计算,然后把结果与接收到的错误校验码进行比较。如果二者不同,则设备就不确认所收到的数据,于是主机就知道它需要重新发送(USB 也支持不允许重新发送的传输类型, 目的是保持一个稳定的传输速率)。主机对从设备接收到的数据也采用相同的方法进行错误处理。主机也可能收到其他错误指示符,指示设备现在不能发送/接收数据,这时主机就通知应用程序以采取合适的动作。
(4) 提供电源。
除了两个信号线,USB还有一个+5V的电源线和地线。大部分外设都可以从该线上得到所有所需的电源。在上电或连接时,主机给所有设备提供电源,当需要时还可以使这些设备工作在省电模式下。每个满负荷供电的设备需要高达500mA的电流。在一些电池供电的PC的端口和集线器上只支持低功耗的设备,它们的工作电流被限制在100mA以内。若设备自己有独立的电源供应,则只在刚开始与主机通信时使用总线电源。
USB设备分为集线器和功能设备。下图给出了一个典型的集线器的示意图。
集线器具有一个上行端口(Upstream Port)和若干个下行端口。上行端口用于连接主机或上级集线器,下行端口用于连接下级集线器或直接连接设备。通过集线器可实现USB总线的多级连接。在连接到USB总线的初期以及电源断开与重新接通时,除上行端口外的所有其他端口是不能使用的(当然它们也可以被单独设置为可用或不可用)。另外,集线器可以发现下行端口上的设备插入或移出操作,并为下行设备分配电源。每一个下行端口都可以分别配置为全速或低速,集线器可以把低速端口与全速率信号分离开来。
功能设备是指一个可以从USB总线上接收或发送数据或控制信息的USB设备。一个功能设备由一个独立的外围设备而实现,它通过一根电缆接到集线器上的某一端口。但是,一个物理组件也可以包含多个功能设备和一个嵌入式集线器,而且仅用一根USB电缆连接到上级集线器,这被称为复合设备。对主机而言,复合设备呈现为永远都连接着一个或多个USB设备的集线器。每一个功能设备都包含了用来描述其能力和所需资源的配置信息。在使用一个功能设备之前,必须由主机来对其进行配置。这种配置操作包括分配USB带宽和为该功能设备选择特定的配置选项。功能设备的例子包括鼠标、键盘、打印机、MODEM、活动硬盘、数字相机等。与主机不同的是,设备不能主动发起一次USB通信。相反,它必须等待主机并响应主机发起的通信(一个例外是远程唤醒特征,这个特征使一个设备能向主机申请通信)。设备在USB中的责任是:
(1) 检测与自己的通信
每个外设始终监测着总线上的通信,若通信的设备地址与设备的地址不同,则设备忽略这次通信。如果地址相同,设备就把通信的数据保存在它的接收缓冲器中,并产生一个中断来发出数据已经到达的信号。在几乎所有的芯片中,这个步骤都是自动完成的,它被内置于硬件中。
(2) 对标准请求的响应
在上电和连接到带电系统时,设备必须在识别过程中对主机发出的请求做出响应。当收到请求时,设备把要发送的响应信息放置在它的传输缓冲器中。在某些情况下,如设定一个地址和配置,设备除了发出响应信息外还要采取其他动作。然而设备不必执行每一个请求,它只需要以一种可以理解的方式对请求做出响应。例如,当主机请求使用一个设备不支持的配置时,设备用一个指示符来响应,通知主机该请求不被支持。
(3) 错误检查
像主机一 样,设备在要发送的数据后面加入错误校验码。在接收到数据时,设备进行错误校验计算,如果检测到错误就请求重新传输。这些功能内置在硬件中,不需要软件实现。
(4) 管理电源
如果设备不从总线获得电源供应,它就必须自己供电。当没有总线活动时,设备必须进入它的低功耗挂起状态,但继续监视总线,当总线活动恢复时退出挂起状态。当主机进入低功耗状态时(如Windows9 8的待机状态),所有与总线的通信都停止了。与之相对应,连接到总线的设备检测到总线活动停止了3毫秒时,它们也必须进入挂起状态并且限制从总线中获取电流。主机也可能请求挂起与一个特定设备的通信。当总线活动恢复后,设备必须退出挂起状态。不支持远程唤醒特征的设备在挂起状态下从总线取出的电流不会超过500mA。有远程唤醒特征的设备并且该特征被主机使能后,这个极限是2.5mA.
强调一点:请不要有思维的定势,比如有的读者阅读这份资料的目的是想用单片机开发一个USB,所以,他老把单片机当作主机,但实际上下面说的主机都是指我们的PC机,上面我们对主机和设备的概念已经作过解释,这里不再赘述。这虽然是个小问题,但有时候往往这种小问题会把你搞蒙。
三、相关的基本概念
1、 差分信号技术
USB数据线里面有两个传输线,一根为D+,一根为D-为什么用这种名称,不知道读者想过没有。其实这个名称的由来与USB采用的传输技术是有关系的,即差分信号技术。
差分信号技术的特点:使用两条线路表达一个比特位,即用两条线路传输信号的压差作为判断1还是0的依据。其优点是具有极强的抗干扰性。倘若遭受外界强烈干扰,两条线路对应的电平同样会出现大幅度提升或降低的情况,但二者的电平改变方向和幅度几乎相同,电压差值就可始终保持相对稳定,因此数据的准确性并不会因干扰噪声而有所降低。
2、 USB通信的格式
3、 USB总线接口
总线接口的功能除了传送和接收数据信号以外,逻辑上还包括识别设备的当前惟一地址。设备的地址是在设备插入到总线上时,由USB主机分配的,范围从0~127,其中0为所有的设备在没有分配惟一地址时使用的默认地址。当总线上有包传输时,设备的总线接口收到此包,通过解析其中的设备地址判断此包是否发送给自己的,如果不是则忽略此包,否则判断此包是发送给哪个端点的,并将整理后的包传送到上面的协议层的相应端点。
4、USB设备的端点
端点是一个USB设备中唯一可寻址的部分,是主机与设备之间通讯的来源或目的。在设备出厂时已经有了的。所有的传输都是传送到一个设备端点(device endpoint),或是由一个设备端点发出。通常设备端点是内存的一个区块,或是控制器芯片内的一个缓存器,用来作为数据的缓冲区。存储在设备端点的可能是接收到的数据,或是等待要送出的数据。主机也有接收与传送数据的缓冲区,不过主机并没有端点,而是当作与设备端点通信的出发点(starting point)。
每一个设备的惟一地址,有一个端点号码以及方向。端点号码可以是0~15。方向如果是输入(IN),表示流向主机,如果是输出(OUT),表示流出主机。如果是作为控制传输的端点,必须设置成双向传输,所以每个端点会有一对输入与输出端点,来分享同一个端点号码。每个设备都必须将端点0设置成控制端点。除此之外,很少需要额外的控制端点。
四、USB通讯协议
1、包
包(Packet)是USB系统中信息传输的基本单元,所有数据都是经过打包后在总线上传输的。USB包由五部分组成,即同步字段(SYNC)、包标识符字段(PID)、数据字段、循环冗余校验字段(CRC)和包结尾字段(EOP),包的基本格式如下图:
a. SYNC字段由8位组成,作为每个数据封包的前导,用来产生同步作用,使USB设备与总线的包传输率同步,它的数值固定为00000001。
b. PID字段用来表示数据封包的类型。PID字段如下图所示:
各种封包的类型与规范
c. 数据字段是用来携带主机与设备之间要传递的信息,其内容和长度根据包标识符、传输类型的不同而各不相同。并非所有的USB包都必须有数据字段,例如握手包、专用包和SOF令牌包就没有数据字段。在USB包中,数据字段可以包含设备地址、端点号、帧序列号以及数据等内容。在总线传输中,总是首先传输字节的最低位,最后传输字节的最高位。
d. CRC字段由不同数目的位所组成。其中重要的数据封包采用CRC16的数据域(16个位),而其余的封包类型则采用CRC5的数据域(5个位)。
e. 包结尾字段即发送方在包的结尾发出包结尾信号。它表现为差分线路的两根数据线保持2比特低位时间和1比特空闲位时间。USB主机根据EOP判断数据包的结束。
2、封包格式
封包格式是指什么呢?上面我们说过了包由五部分组成,即同步字段(SYNC)、包标识符字段(PID)、数据字段、循环冗余校验字段(CRC)和包结尾字段(EOP),这只是USB包的一个整体的概念,没有说明USB具体有哪些包,这些包具体的格式是什么。这里要讲的封包格式就是具体化这个“包”的概念。
①起始(SOF)封包
SOF封包属于令牌封包的一种,但具有独自的PID类型名:SOF。这个封包常用于等时传输,并不应用于低速设备。格式如下:
②令牌(token)封包
由于USB的数据交换是由PC主机端所激活的,所以在每一个数据交换中必须以SYNC、PID、ADDR、ENDP与CRC5这5个数据域组合而成的令牌封包为起始。格式如下:
③数据(data)封包
数据封包含有4个域:SYNC、PID、DATA与CRC16。DATA数据域的位值是根据USB设备的传输速度及传输类型而定,且须以8字节为基本单位。也就是,若传输的数据不足8字节,或传输到最后所剩余的也不足8字节,仍须传输8字节的数据域。格式如下:
④握手(Handshake)封包
握手封包仅包含SYNC和一个PID数据域,格式如下:
⑤特殊(special)封包
PRE是主机从高速传输变成低速传输时送来的封包。格式如下:
3、事务
事务处理(Transaction):在USB上数据信息的一次接收或发送的处理过程。读者要了解USB,那么,应该就要对主机和设备之间进行USB数据传输的整体过程有个了解,那么,下面要介绍的,就能让你了解这个“整体的过程”了,让你站在一定的高度俯视USB传输。
(1)输入(IN)事务处理
输入事务处理表示USB主机从总线上的某个USB设备接收一个数据包的过程。
①正常的输入事务处理
注意,上图与前面的图片不同,前面的图片代表数据包里的有序的一串数据,这里不然,这里的意思是:第一步,主机发送IN令牌包。第二步,设备发送数据封包。第三步,主机发送应答信号。下同。
②设备忙时的输入事务处理
③设备出错时的输入事务处理
(2)输出(OUT)事务处理
①正常的输出事务处理
②设备忙时的输出事务处理
③设备出错时的输入事务处理
(3)设置(SETUP)事务处理
①正常的设置事务处理
②设备忙时的设置事务处理
③设备出错时的设置事务处理
(4)帧起始(SOF)事务处理
(5)帧结束(EOF)事务处理
5、控制传输
在USB的传输中,制定了4种传输类型:控制传输、中断传输、批量传输以及等时传输。
控制传输是USB传输中最重要的传输。它包含3种类型:控制读取、控制写入以及无数据控制。这3种控制传输类型又分为2~3个阶段:设置阶段、数据阶段(无数据控制没有此阶段)以及状态阶段。
阶段一:设置阶段
主机从USB设备获取配置信息,并设置设备的配置值。
设置阶段的数据交换包含了SETUP令牌封包、紧随其后的DATA0数据封包以及ACK握手封包。它的作用是执行一个设置(概念含糊)的数据交换,并定义此控制传输的内容。
阶段二:数据传输阶段
数据传输阶段用来传输主机与设备之间的数据。控制读取——将数据从设备移到主机上;控制写入——将数据从主机传到设备上。
阶段三:状态阶段
状态阶段用来表示整个传输的过程已完全结束。
状态阶段传输的方向必须与数据阶段的方向相反,即原来是IN令牌封包,这个阶段应为OUT令牌封包;反之,原来是OUT令牌封包,这个阶段应为IN令牌封包。对于控制读取而言,主机会送出OUT令牌封包,其后再跟着0长度的DATA1封包。而此时,设备也会做出相对应的动作,送ACK握手封包、NAK握手封包或STALL握手封包。相对地对于控制写入传输,主机会送出IN令牌封包,然后设备送出表示完成状态阶段的0长度的DATA1封包,主机再做出相对应的动作:送ACK握手封包、NAK握手封包或STALL握手封包。
6、设备枚举
(1)设备描述符
USB描述符就好像是USB外围设备的“身份证”一样,详细地记录着外围设备相关的一切信息。为了描述不同的数据,就需以不同类型的USB描述符来加以描述,它共有以下几种类型:
①设备描述符 ②配置描述符
③接口描述符 ④端点描述符
(2)USB设备请求
在USB接口的通信协议中,由于主机是取得绝对的主控权,因此,主机与设备之间就必须遵循某种已沟通的特定命令格式,以达到通信的目的。而这个命令格式就是USB规范书中所制定的“设备请求”
数据请求的数据格式内容
几个主要的设备请求
①Clear Feature ②Get Descriptor
③Set Address ④Set Configuration
(3)设备列举
设备列举可以简单地概括为这样的一个过程:主机通过USB设备请求来取得设备描述符并对该设备进行配置。该过程可以简化为如下5个步骤:
第一步,使用预设的地址0取得设备描述符。
第二步,设置设备的新地址。
第三步,使用新地址取得设备描述符。
第四步,取得配置描述符。
第五步,设置配置描述符。
设备列举使用的是控制传输。上述的5个步骤必须符合控制传输的基本架构,第一步、第三步和第四步使用的是控制读取,第二步和第五步使用的是无数据控制。
我们来看一下给D12写命令、写数据以及读数据是怎么实现的。不知道读者有没有做过单片机外部数据存储器扩展这个实验。如果做过的话,那对于理解D12写命令、写数据以及读数据会方便一些。如果没有做过,那也不要紧,我会详细的解释这个过程的。
比如拿向D12写F3(设置模式,你先不用管这个功能是什么)这个命令来说,第一步,地址总线(P0口+P2口)选中你的目标地址(如0x6003那么此刻P0为03,P2口就为60了);第二步,你是读数据还是写数据,这里我们是写数据,那么就应该是P3.6(外部数据存储器的写选通)变成低电平;第三步,你读什么数据,或是写什么数据,这里是写F3,那么P0口会变成你要写的数据F3,在思考的读者会想到,这步矛盾了啊,P0这会这样一变,那它充当的部分“地址总线”角色不就变了吗?的确是这样的,你很聪明,想法很不错,但你再怎么算,也算不过芯片设计者,单片机还有一个“ALE地址锁存允许”脚,这个脚会把刚才的地址功能自动锁存好。上面说的这些可千万注意了哈,都是单片机自动完成的,不用你来控制P3.6=0等等,只要有一句XBYTE[6003]=0XF3;上面的这些步骤就自动完成。这下,整个过程明白了吗?明白了才怪,还差最后一步,别被我搞蒙了哈。这里你写进去的数字F3,是不错,可是人家D12不知道你写的是数据还是命令啊。这个时候就是由D12的A0脚起作用了,A0脚的功能就是用来选择是命令还是数据的,一般把这个脚接到锁存器上,锁存器的另一端接到P0.0脚,这也是我上面举例6003的原因,6003的最后一位P0.0为1,即写命令。
五、把工作落实到实处
看了前面的东西,读者可能还是感觉太抽象了。从这里开始,我们来读程序,从程序中理解整体USB的传输流程,最终把工作“落实到实处”。
来举一个例子。举的这个例子是这样的,假设我在PC机写一个简单的VB程序(这个程序我们不在这里介绍,有兴趣的读者可以去下载“蓝博芯科”的VB实例看一下),这个VB程序里只设了有一个“发送”按键,还有两个文本框,一个是“输入数据框”,用于用户输入要发送的数据的框体,另一个是“数据接收框”,用于显示接收到的数据。当我们按下这个发送按键的时候,把用户在“输入数据框”中的数据发送到USB设备中,USB设备接收到了数据后,再把接收到的数据返还给PC机,即在PC机的“数据接收框”中显示。接下来,我分步骤给大家说明这个过程:
1、设备连接
2、USB设备上电
USB插入PC机时,PC机给USB设备上电,这时USB设备上的单片机便运行主函数main(),主函数作一些初始化后,循环等待PC机发现自己。
3、主机检测到设备
设备连接到总线一,主机通过检测设备在总线的上拉电阻检测到有新的设备连接并获取该设备是全速设备还是低速设备,然后向该端口发送一个复位信号。设备是怎么样知道有一个复位信号过来了呢。首先是由D12发现的这一“情况”,它会把这个信号保存到它的“中断寄存器”中(注意,D12的这个寄存器可是很重要的,D12的所有中断信息都是通过读这个寄存器来识别的),接着,D12给单片机发送一个中断信号,单片机接到中断信号后,要确定是什么中断,这时单片机会调用一个fn_usb_isr()函数。这个函数的作用是读D12的中断寄存器,判断这此刻发现的中断是什么类型的中断。这个函数具体为:
void fn_usb_isr()
{
unsigned int i_st;
bEPPflags.bits.in_isr = 1;
i_st = D12_ReadInterruptRegister();
if(i_st != 0)
{
if(i_st & D12_INT_BUSRESET)
{
bus_reset();
bEPPflags.bits.bus_reset = 1;
}
if(i_st & D12_INT_EOT)
dma_eot();
if(i_st & D12_INT_SUSPENDCHANGE)
bEPPflags.bits.suspend = 1;
if(i_st & D12_INT_ENDP0IN)
ep0_txdone();
if(i_st & D12_INT_ENDP0OUT)
ep0_rxdone();
if(i_st & D12_INT_ENDP1IN)
ep1_txdone();
if(i_st & D12_INT_ENDP1OUT)
ep1_rxdone();
if(i_st & D12_INT_ENDP2IN)
ep2_txdone();
if(i_st & D12_INT_ENDP2OUT)
ep2_rxdone();
}
bEPPflags.bits.in_isr = 0;
}
这个函数有的地方你可能看不懂,没关系,你只要大体的知道这个函数的后面那部分就只是在通过if语句,判断当某条件成立时,便调用相应的子程序就行了。这些条件就是在判断是什么中断,调用这些子程序就是在调用对应于某个中断的处理函数。这里要调用的是bus_reset()函数,并让标志位bEPPflags.bits.bus_reset 置1。
4、设备默认状态
设备接收到复位信号后,PC机就使用默认地址(00H)来对其进行寻址。
5、地址分配
PC机分配一个空闲的地址,以后,设备就只对该地址进行响应。
6、读取USB设备描述符
主机读取USB的描述符,确认USB设备的属性。USB怎么把这个描述符发给PC机呢。上面我们说过,main()最后在那一直循环等待,他可是不只光等着,不干活的,他在循环的工作之一是在调用一个叫usbserve()的函数。跟踪usbserve()这个函数的轨迹,我们最终发现了他调用了read_write_register()函数,看到了这个函数你就会明白USB设备描述符是怎么被发上去的了。
void read_write_register(void)
{
// unsigned char i;
if(ControlData.DeviceRequest.bmRequestType & (unsigned char)USB_ENDPOINT_DIRECTION_MASK) {
if(ControlData.DeviceRequest.wIndex == GET_FIRMWARE_VERSION &&
ControlData.DeviceRequest.wValue == 0 &&
ControlData.DeviceRequest.wLength == 1)
get_firmware_version();
else
if(ControlData.DeviceRequest.wIndex == GET_BUFFER_SIZE &&
ControlData.DeviceRequest.wValue == 0 &&
ControlData.DeviceRequest.wLength == 4)
get_buffer_size();
else
stall_ep0();
} // if read register
else{
if(ControlData.DeviceRequest.wIndex == SETUP_DMA_REQUEST &&
ControlData.DeviceRequest.wValue == 0 &&
ControlData.DeviceRequest.wLength == 6)
{
if(ControlData.dataBuffer[5]==0x81)
D12_WriteEndpoint(5, ControlData.dataBuffer[3], EpBuf);
single_transmit(0, 0);
}
else
stall_ep0();
} // if write register
}
看到这个程序有点盲目吧,那就看下面这个图吧。
这个图说的是read_write_register()在调用其它5个子程序。这5个子程序把设备的描述符取出并发送到PC机。
7、设备配置
主机依照读取的USB设备描述符来进行配置,如果设备所需的USB资源得以满足,就发送配置命令给USB设备,表示配置完毕。
8、进入主题
前面的准备工作做好了,如果你不对设备进行任何操作,到了这一步,设备便挂起了。如果你在此刻,点击了PC机上的发送数据,那么现在设备就得“开工”了。
假设你PC机上给USB的端点2发送了数据,数据到达D12后,D12会向单片机发送一个中断信号,同样,通过fn_usb_isr()函数,调用ep2_rxdone()函数。ep2_rxdone()的原形如下:
void ep2_rxdone(void)
{
unsigned char len;
D12_ReadLastTransactionStatus(4);//复位中断寄存器
len = D12_ReadEndpoint(4, 64, EpBuf);//读取端点2接收数据
if (len != 0);
bEPPflags.bits.ep2_rxdone = 1;//标志端点2接收到数据
len=0;
}
从这个函数可以看出,经过D12_ReadEndpoint(),把接收到的数据最后被存入了 EpBuf这个数组里面了。D12_ReadEndpoint()里,按照D12的数据手册参数“4”的意思是端点2输出,参数“64”是输出的数据长度,参数“EpBuf”是接收数据所要存放的地址。D12_ReadEndpoint()和D12_WriteEndpoint()两个函数是最底层的函数,非常重要,所有端点的读写工作都是能过这两个函数。
看到这里,读者心里便踏实了。只要“东西”到手了,我想对它怎么操作,就可对他怎么操作了。
那现在我们就来对收到的数据“动手”。
我们到main()循环等待里加一个handle_data()函数,函数原形如下:
void handle_data(void)
{
if(bEPPflags.bits.ep1_rxdone) //端点1数据接收标志位,判断端点1是否收到数据
{
DISABLE;
bEPPflags.bits.ep1_rxdone = 0;//清数据接收标志位
ENABLE;
D12_WriteEndpoint(3, 1, &datalen);//回应PC机
if(datalen>0)
{
if(i>=datalen)
D12_WriteEndpoint(5, 1, &senddata[i-datalen]); // 发送数据到主机
else
D12_WriteEndpoint(5, 1, &senddata[i-datalen+64]);// 发送数据到主机
datalen--;
}
}
if(bEPPflags.bits.ep2_rxdone) //端点2数据接收标志位,判断端点2是否收到数据
{
DISABLE;
bEPPflags.bits.ep2_rxdone = 0;//清数据接收标志位
ENABLE;
senddata[i]=EpBuf[3];//把接收到的数据“采集”下来,入到senddata[]中。
i++;
datalen++;
if(i>64) i=0;
}
以上的细节读者不必追究,只须看我在上面的注释部分即可。从这处函数可以看出,数据能发送到PC机,主要是通过D12_WriteEndpoint()函数,拿D12_WriteEndpoint(5, 1, &senddata[i-datalen])来说,参数“5”的意思是选择端点2,参数“1”是写数据长度为1,参数“&senddata[i-datalen]”是要写的数据的地址。
最后,我们以USB源代码中各个“.c”文件的作用介绍来结束这次USB模块的制作总结。
① 中断服务程序ISR.C:这部分代码处理由PDIUSBD12 产生的中断它将数据从PDIUSBD12 的内部FIFO 取回到CPU 存储器并建立正确的事件标志以通知主循环程序进行处理。
② 协议层CHAP_9.C以及PROTODMA.C:协议层处理标准的USB 器件请求还有特殊的厂商请求例如DMA和TWAIN。
③ PDIUSBD12 命令接口D12CI.C:为了进一步简化PDIUSBD12的编程固件定义了一套压缩了所有访问PDIUSBD12 功能的命令接口
④ 主循环MAINLOOP.C:主循环检查事件标志并进入对应的子程序进行进一步的处理它还包含人机接口的代码例如LED和键盘扫描
附录: