USB设备枚举全纪录
在编写这部分程序之前,首先需要了解有关USB协议,重点是USB数据通信结构、11条标准请求命令和标准USB描述符。
因为嵌入式设备的软硬件是密切相关的,所以还需做的准备工作是了解选用的USB芯片及主控MCU的性能。
一.硬件篇
USB芯片
作用:
1. 管理和实现USB物理层的差模信号
2. 以寄存器的形式提供各种端点(如控制端点,中断端点,大批量传输端点,同步传输端点)
3. 提供状态寄存器,配置寄存器,存储寄存器,中断寄存器,控制寄存器
4. 电源管理(提供3.3V的电源)
5. 实现某些协议层功能(如CRC校验/产生,PID校验/产生,同步模式的识别,并行串行转换等)
固件就是以这些硬件资源作为基础来实现USB设备功能的,其中,端点是需要重点学习的对象,需要熟练掌握与之相关的状态寄存器,配置寄存器,存储寄存器,中断寄存器
MCU主控芯片
作用:
1. 实现USB设备的功能
2. 处理USB芯片产生的中断,解析SETUP包,处理标准请求和厂商请求
二.软件篇
USB设备端固件程序,枚举部分是全部程序的基础和重心,只有主机对设备枚举成功后,主机才能和设备进行正常的通信
USB的枚举过程分为4个状态.
1.接入态
v主机检测到USB设备插上,击活端口,并发送复位命令(保持10ms)
2.默认态
v主机使用默认地址读取设备描述符 (GET_DESCRIPTOR)
v主机分配给设备一个总在线的唯一地址 (SET_ADDRESS)
3.地址态
v主机从新的地址获取设备描述符(GET_DESCRIPTOR)
v主机获取所有设备的配置描述符(GET_DESCRIPTOR)
4.配置态
v主机设置描述符(设备,配置) (SET_CONFIGURATION)
v主机读取配置状态(可选) (GET_CONFIGURATION)
v主机读取接口状态(可选) (GET_INTERFACE)
三.实践篇
在枚举阶段的固件编写中,我主要参考PHILIPS公司提供的D12型 USB芯片代码(有参考比自己从零开始要容易很多).由于该代码是基于51单片机编写的,与我所用的16位单片机在性能和结构上有较大差异,因此在固件移植过程中,所遇到的问题大多来源于此.
下面是我在移植过程中碰到的四个主要问题。
第一个问题,出现在默认态阶段,主机用默认地址读取设备描述符。当MCU收到USB芯片产生的中断时,无**确读出USB芯片中断寄存器中的值。
这个问题发生在整个枚举过程的一开始,准确的说是第一步,所以我开始怀疑MCU与USB芯片的通信是否真正建立;而在此前,我对它们之间的通信能力一直深信不疑;因为我曾经用MCU发出测试专用指令来读取USB芯片的ID值,并且正确地读到了USB芯片的ID值,从理论上讲MCU与USB的通信已经建立.解决这个问题大约用了半天时间,后来我在MCU读中断寄存器命令后加了一段延时程序,问题得到解决,即中断产生后MCU正确读出了USB芯片中断寄存器的值
我分析原因是MCU发送命令的速度太快,当发送了读USB中断寄存器值命令后,就迅速发送取数据命令,而USB芯片的速度要慢的多,在MCU读数据时还未来得及把数据准备好。之前我读取USB芯片ID值的测试指令是循环发出的,刚开始虽然数据没准备好,但随着指令的循环发出,后面指令自然可以把前面指令准备的数据发出。
第二个问题出现在默认态阶段,主机用默认地址读取设备描述符。MCU收到USB芯片产生的中断,却无法进入到正确的中断服务程序。
在第一个问题解决后,立刻又出现了这一问题。通过串口监控,我很快发现,解析中断信息的程序中,有一段程序的功能是互换一个BYTE数据的高低字节位,这一功能主要是针对51单片机数据存放的结构与USB接口芯片的数据结构高低位颠倒而设置的。通过实验我发现:这款16位单片机也不具有51单片机的这一特点,所以只要去掉这段高低字节互换的程序,问题就解决了。
第三个问题,出现在地址态阶段,主机获取设备描述符。USB设备响应主机要求,发送16字节的设备描述符给主机,主机不能收到。
因为16字节的数据是通过MCU发送到USB芯片,再由USB芯片发送到
主机的。所以需要判断数据是在哪一个环节没有被正确传输的。我利用串口监测到: MCU收到主机的读中断(读取设备描述符命令)后,会送出了16字节的设备描述符到USB芯片;但是用BUS HOUND(一种基于主机端的USB总线监视软件)监视主机端发现:主机并没有收到这16字节的数据。因此我判断问题出在数据从MCU到USB芯片这一环节——即由MCU发出的设备描述符没有被USB芯片接收到。通过实验,我发现USB芯片没有接收到MCU发来的数据是因为USB芯片的速度较慢,对它来说,每一笔由MCU发出的数据在数据线上保持的时间太短,以致USB芯片无法将数据存放到其寄存器中。当改变MCU的相关设置,延长数据在数据线上保持的时间后,问题得以解决。
第四个问题,出现在配置态阶段,主机设置描述符。通过BUS HOUND监视,主机在SET_CONFIGURATION后停止枚举。
这个问题是我碰到的最难解决的困难,难就难在一直找不出主机停止枚举的原因,不知道问题产生在哪个环节。通过BUS HOUND可以清楚地看到:主机发出了正确的SET_CONFIGURATION命令,接下来USB设备只需回送一个空包(数据为0)通知主机已收到该命令;可是主机的枚举过程进行到这里却戛然而止。为什么?难道是设备没有发出应答空包,或者是主机没有收到应答空包,还是设备回送的空包有错误?我大约用了5天时间来找支持这些假设的理由。不过,除了证明假设不成立外,一无所获。
第六天,在一次检查BUS HOUND的数据时终于有了新发现。我发现:在SET_CONFIGURATION命令之前,主机发出了GET_DESCRIPTOR来获取全部的描述符合集共46字节数据,设备回复的描述符中在第20多字节时多了一个0字节;不过我并不清楚是否是这个问题对下一步产生了影响,在我看来只要主机在枚举过程中发出了下一步枚举命令,那就表明在此之前的枚举都是正确的(后来的事实证明情况并非如此),后一步的枚举不成功应该与前一步无关。但是因为我再也找不到任何可疑之处,所以只好去寻找那一个字节的0是从哪里来的?又经过了一天的摸索,终于找到了问题的症结。我的描述符数据是以结构体的形势存放的,当MCU收到主机发出的获取描述复命令时,就通过指针的指示将结构体中的数据发出。这些数据大多数是CHAR型,少数几个是INT型(症结),对16位单片机来说INT型数据存放是从偶地址开始的,因此,一旦在INT型的数据前有奇数个CHAR型数据,那就天下大乱了,INT型数据会空出一个奇数的地址位,从其后的偶数地址位开始存放数据。这样中间就多出1位的0字节数据。因此当主机那边收到时这笔数据时,从这一位起其后的数据就全部错位了。当我把INT型数据改用2个CHAR型表述时,一切就正常了; 同时,在此之后的SET_CONFIGURATION也能顺利进行下去,直到枚举结束。
看来枚举的 GET_DESCRIPTOR阶段如果主机得不到正确得描述符信息,并不会影响该阶段的枚举,而是对SET_CONFIGURATION阶段产生影响,使枚举无法继续。我想这应该是由主机端驱动程序来决定的。
非常幸运,利用BUS HOUND观察到的有限数据中,在最后的几字就出现了异常,否则,这个问题不知道要困扰我多久(BUS HOUND可以显示主机端接收到设备发来的数据,但数据的长度仅为32个字节)
写在最后
对设备端固件开发而言,如果有专用的USB总线分析仪是最好不过了,不但能监视到主机端收发的数据,而且可以监视到设备端收发的数据,这样,开发的效率会大大提高,开发时间可以大大降低。但如果开发条件像我一样有限,至少应该保证有串口和BUS HOUND这种USB总线监视工具(从网上可以下载),其中,串口可以在枚举的开始阶段使用,监控MCU的相关数据;BUS HOUND则可以在枚举的后期发挥更大的作用,因为此时传输的数据量较大,串口已不太适合作为监视工具了。
此外,在USB设备的枚举程序编写中,我遇到的最大挑战来自于8位单片机与16位单片机的结构上的差别,比如数据存取速度,数据高低字节的存放次序,数据类型与存放地址相关的特点等。从我前面列出的问题来看,大部分问题均源自于此;因此在学习某种单片机时,就要准确地掌握不同类型的MCU特性,这样就一定可以降低开发难度,缩短开发时间。 |