EZ-USB FX2(68013)固件研究

原始资料来自网络 整理日: 2015年2月12日


1. Welcome

算是给所有正在学习USB,还徘徊着不得其门而入的朋友一个入门的契机吧,我也深知入门的痛苦,有些人入门就是抱着那什么USB协议,包定义,帧格式......啃来啃去的,结果啃不出个所以然来。

依我的经验来看,协议方面的东东,随便找本书,过一遍就行了;然后,你的终点应该放在你如何来写第一个成功的USB固件;而要写USB固件,那么了解Cypress固件架构是必要的,也是重中之重;再然后,等你积累了一些端点,控制,bulk,中断传输,SlaveFIFO,GPIF等等的经验后,再回过头去看协议方面的内容,就会有更加深刻的体会了;然后,你就可以试着更改FW。c文件了——这个时候你就是高手了。

2. 工具

  1. 68013的USB开发板
    淘宝随便买块好了,还送不少资料。

  2. 要准备开发工具
    去Cypress官网下一个Cy3684的开发包;
    全称: cy3684_ez_usb_fx2lp_development_kit_15.exe
    网址: www.cypress.com/?rID=14321

  3. 安装开发包
    工具就是Cypress USB Console了。怎么用不用我说了吧.

3. 固件架构

以一个3684开发包自带的例子讲解。

  1. 进入目录(个人找自己的)
    D:\Program Files\Cypress\USB\Examples\FX2LP\Bulkloop,

  2. 3个头文件拷到bulkloop文件夹
    文件夹:D:\Program Files\Cypress\USB\Target
    Cypress头文件: Fx2.h, fx2regs.h, syncdly.h

  3. keil设置
    output里关掉Run User Program #1(前面的勾去掉)

  4. 检查keil C51文件路径是否正确
    如果你的keil是直接装载C:\Keil....下,那不会有错误,否则,自行设置正确的路径。

4. 编译bulkloop工程

在工程下,有以下几个文件:其中,USBJmp.OBJ, EZUSB.LIB基本上是每个工程都要添加的,是一些中断向量表,EZUSB的函数库等等,不用管它们。 现在重点看前面三个文件:

  1. fw.c
    这个文件是整个USB的固件根本(FirmWare的缩写),USB协议方面的通信都是在这里完成的,包括上电枚举,重枚举,唤醒以及调用用户自己的程序和控制命令等等。基本上,如非必要,尽量不要动这个文件的内容,也不要在里面书写你自己的任何代码。

  2. bulkllop.c
    这个就是用户自己的代码书写文件(原始名称:periph.c)。
    我们所有的代码都在这个文件里书写。Cypress已经给我们搭好了架构。
    void TD_Init(void)
    这个函数只会在USB启动后调用一次。在这个函数里添加你自己的初始化代码,也就是传输数据前要处理的,例如IO口配置,时钟,端点,FIFO的选择等等。 我们看bulkloop的初始化,它在USB的in,out传输启动前进行了哪些初始化: CPU时钟频率,USB工作模式选择,端点选择,端点传输方向,FIFO大小的配置等等。
    void TD_Poll(void)
    Poll中文意思调度,这个函数就是用户调度程序,USB会在空闲的时候反复调用该函数,所以我们把自己需要反复执行的代码放在这里。例如在bulkloop里,它就实现了反复从端点2接收上位机数据然后传给端点6,再从端点6传给上位机(4,8端点一样)。 BOOL DR_VendorCmnd(void):这个函数就是自定义命令代码的书写处。我们的Vendor命令都会写在这里,fw.c固件会自动调用我们的代码。
    void ISR_Ep0in(void)
    interrupt 0~void ISR_Ep8inout(void) interrupt 0:这几个函数是当使用端点中断传输时,中断代码的书写处,很少用。 以上,是经常会用到的几个函数;其他,基本不常用。

  3. dscr.51
    这个文件是USB描述符文件,包括了设备描述符,接口描述符,端点描述符,字符串等等。里面的英文都注释得很详细了,我就不多做介绍了,刚开始入门的时候,这个文件也不必改动。

5. 几个包含文件

  1. fx2.h
    预定义,宏及函数声明

  2. fx2regs.h
    68013的寄存器地址定义。

  3. syncdly.h
    同步延时。在其他文件里经常调用的一个函数SYNCDELAY就是这里定义的。

  4. intrins.h
    C51一些数据类型及函数定义。

好了,就写到这里,搞懂每个文件的作用非常非常重要,这样,你就可以知道自己的代码书写在什么地方,遇到不明的函数,定义可以到指定的位置查询,或者想修改某个设置(例如想把端点2设成IN,端点6设成out),知道到哪个文件里去修改。

6. 入门第一个例子例子(bulkloop)

USB入门的第一个例子,肯定是bulkloop了,装好驱动,开发包后,在开发包下....
Cypress\USB\Examples\FX2LP\Bulkloop就是bulkloop例子。

我之所以从bulkllop开始说,也是深有体会的。想当初刚开始学USB的时候,抱着协议闷头看楞是看不出个所以然,后来从例子开始学,才慢慢搞懂了USB,说实话,我到现在对USB协议类的DD还是一知半解,不过并不妨碍我进行USB的开发不是,所以说,Cypress的固件架构是个好东东,我们可以偷懒了。

开发包里例子是基于Cypress的一块开发板的,我想很少有人能弄到吧,而bulkloop就简单了,什么外围都不需要,一块68013,加个cyconsole interface就可以看到效果了.可以说,看懂了bulkloop,USB也就算正式入门了,剩下的要做的就是触类旁通,然后再不停的回过头去看USB协议,就会恍然大悟“然来是这样”。

6.1 readme.txt

首先,看bulkloop文件夹下的readme.txt文件,告诉我们这个固件主要实现的功能。
数据从EP2OUT->EP6IN ,从端点2的out缓冲区到端点6的in缓冲区。数据流向为:

  1. PC端(console软件)设定要传输的数据
  2. PC端发起端点2out传输,数据到达68013端点2的out缓冲区
  3. bulkloop固件查询到端点2的out缓冲区有数据,于是将数据发往端点6的IN缓冲区
  4. PC端发起端点6 IN传输,于是68013的端点6 IN缓冲区中的数据被读到PC机显示
    这就是整个bulkloop过程,EP4->EP8同理。

6.2 TD_Init()

然后看固件,看一个固件总是从TD_Init()开始的:

CPUCS = ((CPUCS & ~bmCLKSPD) | bmCLKSPD1) ;
IFCONFIG |= 0x40;

这两句设定CPU工作状态以及端口的工作模式。
bmCLKSPD,bmCLKSPD1是一个预定义,在FX2.H文件中有定义,keil应该都会用的,直接go to definition...跳到定义处查看;至于语法,使用了与或操作,用来进行位操作,置1或清0,编程语言中常用的技巧,C语言不要太差哦
然后寄存器每位的意义,我们在TRM中可以查到,这两句告诉我们:
设定68013 CPU时钟为48M,端口工作模式为普通的IO口。

EP1OUTCFG = 0xA0;
EP1INCFG  = 0xA0;
SYNCDELAY; // see TRM section 15.14
EP2CFG    = 0xA2;
SYNCDELAY;
EP4CFG    = 0xA0;
SYNCDELAY;
EP6CFG    = 0xE2;
SYNCDELAY;
EP8CFG    = 0xE0;

这几行代码进行端点的配置EP2,EP4为out端点,512×2缓冲;EP6,EP8为in端点,512×2缓冲。
例如
EP2CFG=0XA2=1010 0010,看TRM对该寄存器的解释,

key comment
b7 = 1 该端点有效;
b6 = 0 该端点为out传输;
b5b4 = 10 该端点进行bulk传输;
b3 = 0 端点为512缓冲;
b2 = 0 只读;
b1b0 = 10 该端点缓冲区倍率为双重,即512×2.其他同理。
SYNCDELAY;
EP2BCL = 0x80; // arm EP2OUT by writing byte count w/skip.
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
EP4BCL = 0x80; // arm EP4OUT by writing byte count w/skip.
SYNCDELAY;
EP4BCL = 0x80;

这段代码对端点计数器进行初始化
注意这里两个端点都写了两次,那是因为我们设置的端点缓冲为512×2,假如缓冲倍率为4,即512×4的话,那么这里初始化要写4次。

// enable dual autopointer feature
AUTOPTRSETUP |= 0x01;

这端代码告诉我们可以使用自动指针,也就是AUTOPTRHx两个自动指针,这两个自动指针使用方便,可以自动指向端点缓冲区。

6.3 TD_POLL()

然后是TD_POLL();在这里处理相关数据传送,USB在空闲的时候会自动调用这里面的代码。


WORD i;
WORD count;

if(!(EP2468STAT & bmEP2EMPTY)) {

    // check EP2 EMPTY(busy) bit in EP2468STAT (SFR), 
    // core set's this bit when FIFO is empty
    if(!(EP2468STAT & bmEP6FULL)) {

        // check EP6 FULL(busy) bit in EP2468STAT (SFR), 
        // core set's this bit when FIFO is full
        // 首先,查询端点2的EMPTY标志,如果不为1,说明有数据,
        // 然后查询端点6的FULL标志,如果不为1,说明端点6 FIFO为空,
        // 可以接收数据。至于bmEP2EMPTY,bmEP6FULL,自行go to definition... */
        // 当检查到端点2 out fifo有数据且端点6 in fifo为空时,就可以将ep2的数据"copy"到ep6.
        // 使用自动指针直接更换2者的指针,实现数据传送: 
        APTR1H    = MSB( &EP2FIFOBUF ); // 取端点2 FIFO指针
        APTR1L    = LSB( &EP2FIFOBUF );
        
        AUTOPTRH2 = MSB( &EP6FIFOBUF ); // 取端点6 fifo指针
        AUTOPTRL2 = LSB( &EP6FIFOBUF );
        
        count     = (EP2BCH << 8) + EP2BCL; // 计数器
        
        // loop EP2OUT buffer data to EP6IN
        // 传送count字节
        for( i = 0x0000; i < count; i++ ) {
            // setup to transfer EP2OUT buffer to EP6IN buffer using AUTOPOINTER(s)
            EXTAUTODAT2 = EXTAUTODAT1; // APTR1指针赋给APTR2,实现数据传送
        }
    
        // 完毕后,重置计数器,以进行下次传输:
        EP6BCH = EP2BCH;
        SYNCDELAY;
        
        EP6BCL = EP2BCL;    // arm EP6IN SYNCDELAY;
        EP2BCL = 0x80;      // re(arm) EP2OUT
    }
}

以上即时bulkloop的整个工作过程,可以在interface中方便的看到结果,板子不在身边,就懒的贴图了。

7. FW.C文件

FW.C文件,是比较难看懂的了,这个要逐字逐句研读,
我当初整整看了一个星期,边理解,边一行一行的注释,可以说,看懂了,USB协议部分也就差不多了。

7.1 main()函数

从main()函数开始看:

DWORD i;
WORD offset;
DWORD DevDescrLen;
WORD IntDescrAddr;
WORD ExtDescrAddr;
Sleep   = FALSE;        // 初始化用户变量 休眠使能--禁止
Rwuen   = FALSE;        // 远程唤醒--禁止
Selfpwr = FALSE;        // 
GotSUD  = FALSE;        // SetUp令牌包到来标志

定义了一些变量,具体用途在后面;第二段同时对变量进行初始化,从名字可以看出其用途。

7.2 TD_Init();

紧接着调用TD_Init()函数,是一些我们自己的初始化配置。

// 定向USB描述符
pDeviceDscr          = (WORD)&DeviceDscr;
pDeviceQualDscr      = (WORD)&DeviceQualDscr;
pHighSpeedConfigDscr = (WORD)&HighSpeedConfigDscr;
pFullSpeedConfigDscr = (WORD)&FullSpeedConfigDscr;
pStringDscr          = (WORD)&StringDscr;

这段代码用来获取USB的各个描述符在68013内存中的地址,准确说是在RAM中的地址,在dscrpt.a51文件中有定义,所有的描述符组成了整个的描述符表,后面会用到。

if ((WORD)&DeviceDscr & 0xC000)

这段代码及以后的,在固件中解释是:

Is the descriptor table in external RAM (> 16Kbytes)? If yes, then relocate.
Note that this code only checks if the descriptors START in external RAM. It will not work if the descriptor table spans internal and external RAM.

意思是说,这段代码用来判断描述符表首址,也就是前面的DeviceDscr、DeviceQualDscr等是否位于68013的外部RAM区,如果是,则移除,然后将描述符表移到内部RAM区,为什么要移到内部RAM区,因为当描述符表位于外部RAM时,USB是不工作的。那么如何判断描述符地址是否超出内部RAM的地址呢?首先,&DeviceDscr取得整个描述符表的首地址(它也是DeviceDscr设备描述的首址),然后和0XC000相与,为什么要和0XC000相与?这就牵涉到68013 FX2LP(注意是LP)的内部结构图:

图片暂咯

上图针对的是128pin的FX2LP,如果是56或100pin的,那么没有外部RAM,只有内部RAM。
可以看到,FX2LP内部RAM从0000-FFFF,其他为外部RAM。而内部RAM中,只有从0000-3FFF和从E000-FFFF的区域可用,其他为系统保留。从0000-3FFF这16K bytes的内部RAM空间,叫做主RAM,对56,100,128pin来说,都可以同时作为程序或数据存储器(对128pin来说,EA=0)。
再看&DeviceDscr & 0xC000的结果,要为“真”的话,显然,必须&DeviceDscr>=0X4000,也就是说判断的是描述表首址&DeviceDsc是否大于3FFF,刚好是主RAM区的大小,这就是为什么要用&DeviceDscr 和 0xC000相与就来判断实现了描述符表首地址的原因了(外部 or 内部 ram?)。

// 重定向描述符S
IntDescrAddr = INTERNAL_DSCR_ADDR;
ExtDescrAddr = (WORD)&DeviceDscr;
DevDescrLen  = (WORD)&UserDscr - (WORD)&DeviceDscr + 2;
for (i = 0; i < DevDescrLen; i++)
     *((BYTE xdata *)IntDescrAddr+i) = *((BYTE xdata *)ExtDescrAddr+i);

判断发现描述符表首址位于外部RAM的后,紧接着就将外部RAM的描述符移到内部RAM。这里就用到了前面定义的变量,

  1. IntDescrAddr
    保存内部RAM首址0X80,

  2. ExtDescrAddr
    保存我们获得的当前描述符表外部RAM的首址 ,

  3. DevDescrLen
    是整个描述表的长度,从DeviceDscr段到UserDscr段,在dscrptr中有定义。然后for循环,将从ExtDescrAddr地址开始的外部RAM中的数据逐个copy到从IntDescrAddr地址开始的内部RAM区。

// 更新描述符指针
pDeviceDscr = IntDescrAddr;
offset      = (WORD)&DeviceDscr - INTERNAL_DSCR_ADDR;
pDeviceQualDscr         -= offset;
pConfigDscr             -= offset;
pOtherConfigDscr        -= offset;
pHighSpeedConfigDscr    -= offset;
pFullSpeedConfigDscr    -= offset;
pStringDscr             -= offset;

完毕后更新描述符指针,指向内部RAM区,通过原指针减去一个偏移量得到。

然后是USB的一些初始状态设置:

EZUSB_IRQ_ENABLE();                 // EZUSB中断使能
EZUSB_ENABLE_RSMIRQ();              // 使能远程唤醒中断
INTSETUP |= (bmAV2EN | bmAV4EN);    // 使能INT2,4自动向量跳转
USBIE |= bmSUDAV | bmSUTOK | bmSUSP | bmURES | bmHSGRANT; // 使能所选择中断
EA = 1;                             // 开8051中断
  1. EZUSB_IRQ_ENABLE();
    预定义是EZUSB=1,查TRM得知,ezusb是EIE寄存器的第0位,EIE.0=1,使能USB中断;

  2. EZUSB_ENABLE_RSMIRQ();
    EICON |= 0x20,EICON.5=1,使能远程唤醒中断;

  3. INTSETUP |= (bmAV2EN | bmAV4EN);
    使能INT2,4自动向量跳转; USBIE |= bmSUDAV | bmSUTOK | bmSUSP | bmURES | bmHSGRANT;使能所选择中断,相关的中断意义,我也一知半解,后面慢慢学习补充;

  4. EA = 1;
    开8051中断。从下面的代码开始,才真正开始我们自己的USB事务处理:

7.3 Main Loop主循环

// Main Loop主循环
while(TRUE) {

    // Poll User Device 用户调度程序
    TD_Poll(); // Check for pending SETUP

    // 等待SETUP令牌数据的到来
    if(GotSUD) {
        SetupCommand(); // Implement setup command // 处理SETUP事务
        GotSUD = FALSE; // Clear SETUP flag 清Setup标志
    }
}

TD_Poll,也就是用户调度程序,USB空闲时调用,不过为什么要放在开始呢?照理说应该放在令牌包后面的,这里不是很明白。。。。。。

if(GotSUD),GotSUD是令牌包标志,准确的说是“令牌阶段数据到来”

7.4 什么是令牌包?

首先,USB一连串的数据传输、处理、响应等就叫做USB事务。
例如,上位机要读取一个描述符,那么就会触发一次USB事务。
一个完整的USB事务处理有三个阶段:

  1. 令牌阶段
  2. 数据阶段
  3. 握手阶段。
    每个阶段数据传输是有各种包组成的,例如令牌阶段:同步字段+令牌包+EOP构成。

USB主机启动事务处理,开始发送令牌包,这个时候假如说我们当前的USB设备地址号为2(重枚举时分配的),而主机发送的地址号也为2,那么这个USB设备硬件会产生中断,进入中断处理。也就是periph.c文件中的void ISR_Sudav(void)函数,在这个中断处理中,设置 GotSUD标志为TRUE,表示收到令牌数据,要启动USB传输了。然后我们固件中判断if(GotSUD),GotSUD为真,则执行SetupCommand()函数,在这里处理控制传输,读取描述符,设置特性,处理Vendor命令等。完毕后置GotSUD = FALSE;然后检查USB各种状态并处理:

7.5 Sleep

if (Sleep){
    // 如果USB进入了休眠状态,这里Sleep是USB休眠标志,通GotSUD一样,USB休眠后产生中断,
    // 进入void ISR_Susp(void)函数处理,置sleep标志为TRUE。

    // 检测USB是否挂起
    if(TD_Suspend()) {

    Sleep = FALSE; // 清Sleep标志
    do {
        EZUSB_Susp(); // 置8051为空闲状态.
    } while(!Rwuen && EZUSB_EXTWAKEUP());

    // 如果唤醒
    EZUSB_Resume();

    // 从空闲状态中恢复
    TD_Resume();
    
}

到这里,一个事务处理完毕,等待下次事务处理中断的到来.

7.6 To Be Continue...

在继续看fw.c文件前,我觉得有必要先把USB的整个工作过程搞懂!
这本书上讲的不错搞懂了USB整个工作过程,对FW中的列举/重列举就比较好理解了。这也是我自己曾经学习的一个过程吧,我并没有一开始就去仔细推敲USB协议方面的东西,而是先看一个大概,然后学到哪里不懂的时候,再回过头去进一步研究,这样慢慢就理解深入了。
个人觉得好多书里一上来就给你细细剖析协议的,完全是浪费脑细胞,越看越糊。哎~USB协议真的非常非常难搞通,我也深深的痛苦中。。。。。。

以下参照《USB原理与工程实践》这本书上讲的,我简化了一下,算是自己的理解吧:

  1. USB连上电脑(实际是集线器HUB),但是还没有上电。
    也就是说VCC还没有电压,到VCC上有5V电压有一个很短的延时过程。该过程主机PC和HUB通信。

  2. USB上电,但是还没有被复位。
    此时不能响应USB任何事务,也没有被分配到任何设备地址,包括默认地址。集线器通过检测D+,D-上的电压来判断是否有新的USB设备连接。 当检测到有新的USB设备连接时,报告给PC。该过程主机和HUB通信。
  3. 复位USB总线。
    PC通知HUB复位USB总线,获得传输模式低速or全速or高速。复位后,USB设备获得一个默认的设备地址号0。此过程PC和HUB通信。

  4. USB设备获得地址号0后,可使用该地址号进行某些事务处理。
    使用地址号0,控制传输,端点0,主机开始和USB设备功能层开始通信。 USB功能层是USB总线结构中的一个,USB总线结构由USB功能层,USB设备层,USB接口层构成。USB功能层,主要负责数据传输操作,就是控制传输,中断传输,块传输,同步传输。

  5. 主机开始获得USB设备的信息
    例如刚开始要获得USB控制传输所支持的最大数据包长度,那么就要向USB设备发命令(发送GetDescription获得设备描述符信息),于是启动一个USB事务处理,而USB事务处理分为3个阶段:
    令牌阶段
    数据阶段
    握手阶段
    也就是说,这一步中,主机发送GetDescription请求读取设备描述符,获得USB控制传输所支持的最大数据包长度(只需读取前8个字节即可),这是一个USB事务,既然是 事务,那么所有的USB事务必然从令牌包开始,于是USB固件首先等待令牌包到来,然后处理相应的命令。这样,主机通过发送GetDescription请求(USB 11中标准请求之一 ),读取设备描述符DeviceDscr,目的是获取控制传输支持的最大字节数(第8个字节),一旦检测到这个数,主机复位USB总线并开始进入枚举过程.

  6. 至此,开始进入枚举过程。
    主机向USB设备发送SetAddress请求,为其分配一个新的,唯一的设备地址(1~127,总共128个)。以后,USB将使用这个新的地址号与主机通 信。

  7. 主机循环向USB设备发出GetDescription请求,读取所有的描述符,获得该USB设备的全部配置信息。
    首先读取设备描述符DeviceDscr全部字段,
    然后读取配置描述符 Configuration,接口描述符,端点描述符,其他各种设备类描述符以及自定义描述符等。

  8. 然后主机根据读取的PID,VID选择一个合适的驱动加载,如果第一次使用,则提示发现新硬件。
  9. 加载了USB驱动后,主机发送SetConfigration()请求为该USB设备选择合适的配置,主机为该USB设备选择一个配置值,一个接口,一个可替换设置值。
  10. 至此,USB枚举结束。

7.7 重枚举

那么,什么是重枚举呢?
首先,上面的USB枚举是对通用USB来说的,一般USB设备只有枚举过程,没有重枚举过程。也就是说其实,
对EZ-USB系列来说,上面的枚举举实际包含了EZ-USB枚举和重枚举两个过程:

  1. 对EZ-USB来说,枚举过程就是USB上电复位到加载固件前这段过程,此时USB设备地址号为默认的0号,枚举完成后,驱动为cypress...eeprom..missing(FX2/FX2LP来说)。

  2. 然后加载固件,进行重枚举,重枚举完成后,显示驱动为cypress...ez-usb...example或其他自定义设备。

为什么EZ-USB有重枚举过程呢?这就是为了可以让主机在前期固件未加载前自动枚举,识别USB,从而可以从上位机加载固件到68013的RAM中,而无需使用ROM,EEPROM,FLASH等内存。实现“软件”架构加载程序代码,随时修改固件。

7.8 EZ-USB如何控制重枚举?

首先,EZUSB有一个寄存器USBCS,其中第1位USBCS.1为Renum控制枚举重枚举。
在USB上电未加载固件代码前,Renum=0,表示使用“EZUSB核心”对芯片的初始配置,处理主机设备请求,并负责把固件下载到RAM中,这个过程就是“枚举”,完成该枚举过程的设备叫做“缺省USB设备(地址号0)”,即驱动显示为“...missing”的设备。

当固件被下载到8051 RAM后,此时Renum=1,表示使用“增强8051核心”处理主机设备请求,并按照固件的代码(读取所有描述符)重新配置USB设备,这个过程就叫做“重枚举”,完成重枚举后,显示自定义的USB设备“...example”,实际是模拟断开与连接的过程。 用以下表表示:

处理设备请求 Renum 8051动作
枚举 EZUSB核心 Renum=0 8051置Renum=1
重枚举 8051 Renum=1 8051重置Renum=0

在重枚举完成后,对控制端点0的设备请求可以由“EZUSB核心”处理或由“增强8051核心”处理,有Renum的值决定。芯片上电时Renum=0,由“EZUSB核心”处理;一旦8051开始运行,就可以设置Renum=1,由“增强8051核心”处理,表示按照8051下载的固件代码处理。

当然,这时也可以设置Renum=0,让“EZUSB核心”处理端点0的设备请求,而让8051完成具体的USB数据传输,这样做会大大简化8051固件代码。 然后再看接下来的代码:

注意,在这段代码之前,EZUSB已经枚举完成,当然,整个过程是自动的,我们看不见的。

#ifndef NO_RENUM if(!(USBCS & bmRENUM)) // 如果RENUM位为0,则重列举
{
    EZUSB_Discon(TRUE); // renumerate 重列举
}
#endif

这段代码即告诉USB进行重枚举,用软件设置,模拟USB断开与连接,。EZUSB_Discon(TRUE)完成断开连接,具体实现在EZUSB.lib库中,大概代码是这样的:

void UsbDisconnect(BOOL renum) {
    if(renum)
        USBCS |= (bmDISCON | bmRENUM);
    else
        USBCS |= bmDISCON;

    EZUSB_Delay(1500);
    USBIRQ  = 0xff;
    EPIRQ   = 0xff;
    EZUSB_IRQ_CLEAR();
    USBCS &=~bmDISCON;
}

将USBCS寄存器的DICON位置1,断开USB,同时如果RENUM位为0,则置1;然后重新连接USB。

// unconditionally re-connect. If we loaded from eeprom we are
// disconnected and need to connect. If we just renumerated this
// is not necessary but doesn't hurt anything
USBCS &=~bmDISCON; // 重新连接
CKCON = (CKCON&(~bmSTRETCH)) | FW_STRETCH_VALUE;
// Set stretch
// clear the Sleep flag.
Sleep = FALSE; // 清sleep标志

这段代码英文意思说的很清楚了,CKCON还不清楚干什么用的。。。 紧接着主机开始读描述符,并进行配置(发送SetConfiguration),加载驱动等,完成重枚举。

8. dscr.a51文件

8.1 设备描述符DeviceDsc

重新看了描述符文件,对照了好多参考书,发现以前很多不明白的地方现在清晰了很多,不过有些地方可能从来没有用过的缘故,我也是不甚明了(打上了问号),只能以后用到的时候,有新的发现再慢慢理解了...

dscr51里放的是USB描述符表,EZ-USB在重枚举阶段会读取或设置相应的描述符

db DSCR_DEVICE_LEN  ;; Descriptor length
db DSCR_DEVICE      ;; Decriptor type
dw 0002H            ;; Specification Version (BCD)
db 00H              ;; Device class
db 00H              ;; Device sub-class
db 00H              ;; Device sub-sub-class
db 64               ;; Maximum packet size
dw 0B404H           ;; Vendor ID
dw 0410H            ;; Product ID (Sample Device)
dw 0000H            ;; Product version ID
db 1                ;; Manufacturer string index
db 2                ;; Product string index
db 0                ;; Serial number string index
db 1                ;; Number of configurations
  1. db DSCR_DEVICE_LEN
    bLength段, 指明整个设备描述符的长度,单位字节。

  2. db DSCR_DEVICE
    bDescriporType段, 描述符类型值。DSCR_DEVICE=04H--设备描述符。

  3. dw 0002H
    bcdUSB, 表明该USB设备所遵循的USB协议版本,用bcd码表示,2字节。例如2.0版本,值为0200H,用bcd码表示,低字节在前,高字节在后,表示为0002H;同理,1.1版本,则表示为1001H。

  4. db 00H
    bDeviceClass段, 指明USB设备所属的设备类。

Value comment
0 表示USB各接口相互独立工作,分属不通的设备类,具体信息在接口描述符中说明
1~FEH 表明该USB设备属于某个明确的设备类,例如04H代表显示设备
FFH 厂商自定义的设备类
  1. db 00H
    bDeviceSubClass段, 指明USB设备所述的设备子类。其值依赖bDeviceClass。 =0,此时bDeviceClass必须首先为0 =1~FEH,详细的设备子类。例如如果bDeviceClass=04H,是显示设备,则bDeviceSubClass=01H,表示CRT显示器; =FFH,厂家自定义

  2. db 00H
    bDevicePortcol段, 指明USB所使用的设备类协议。其值依赖bDeviceClass和bDeviceSubClass。

Value comment
0 表示该设备不使用任何设备类协议
1~FEH 该USB必须属于某个明确的设备类和子设备类。如视频类协议(UVC),音频类协议(UAC)等
FFH 厂家自定义
  1. db 64
    bMaxPacketSize0段, 指明该USB设备端点0控制传输所支持的最大数据包长度,单位字节。

  2. dw 0B404H
    VID

  3. dw 0410H
    PID

  4. dw 0000H
    bcdDevice段, 指明USB设备版本号。产品ID???

  5. db 1
    iManuFacture段, 厂商信息字符串索引值,没有时为0.这里为1,,即下面的“Cypress”字符串。

  6. db 2
    iProduct段, 产品信息字符串索引值,没有时为0.后面的“EZ-USB”字符串。

  7. db 0
    iSerial段, USB设备序列号信息字符串索引值,没有时为0.

  8. db 1
    bNumConfigurations段 指明USB设备所支持的配置数。???如果USB设备支持两种传输速率,则该字段指出的是该速率下的配置数,而不是两种速率下的配置数和。

8.2 设备限定描述符DeviceQualDscr

设备限定描述符:DeviceQualDscr

DeviceQualDscr:
db DSCR_DEVQUAL_LEN     ;; Descriptor length
db DSCR_DEVQUAL         ;; Decriptor type
dw 0002H                ;; Specification Version (BCD)
db 00H                  ;; Device class
db 00H                  ;; Device sub-class
db 00H                  ;; Device sub-sub-class
db 64                   ;; Maximum packet size
db 1                    ;; Number of configurations
db 0                    ;; Reserved

设备限定描述符,9个字段,共10字节。
仅当该USB为高速USB设备,且设备既需支持高速(High Speed)又需支持全速(Full)时,就需要用到设备限定描述符。

例如该高速USB设备目前工作于全速模式,则该描述符中包含高速模式的总体信息。

在设备请求处理函数SetupCommand(void)中,当收到读设备限定描述符请求时,会首先判断是否为高速USB设备。if (HighSpeedCapable())。

  1. db DSCR_DEVQUAL_LEN
    bLength段, 整个设备限定描述符的长度,单位字节,共10个字节。

  2. db DSCR_DEVQUAL
    bDescriptorType段, 指出该描述符类型。06H->设备限定描述符。

  3. dw 0002H
    bcdUSB段, USB协议版本号。

  4. db 00H
    bDeviceClass段, 该USB设备所属的USB设备类。

  5. db 00H
    bDeviceSubClass段, 所属子类。对bDeviceClass的进一步细化分类说明。

  6. db 00H
    bDeviceProtocol段, 该设备所使用的设备类协议。

  7. db 64
    bMaxPacketSize0段, 端点0控制传输所支持的最大数据包长度,单位字节。

  8. db 1
    bNumConfigurations段 另一速率所支持的配置数。

  9. db 0
    bReserved段 保留,必须为0.

8.3 配置描述符HighSpeedConfigDscr/FullSpeedConfigDscr

配置描述符:

HighSpeedConfigDscr/FullSpeedConfigDscr
db DSCR_CONFIG_LEN      ;; Descriptor length
db DSCR_CONFIG          ;; Descriptor type
db (HighSpeedConfigDscrEnd-HighSpeedConfigDscr) mod 256 ;; Total Length (LSB)
db (HighSpeedConfigDscrEnd-HighSpeedConfigDscr) / 256   ;; Total Length (MSB)
db 1                    ;; Number of interfaces
db 1                    ;; Configuration number
db 0                    ;; Configuration string
db 10000000b            ;; Attributes (b7 - buspwr, b6 - selfpwr, b5 - rwu)
db 100                  ;; Power requirement (div 2 ma)

配置描述符包含8个字段,共9字节。所有的USB设备至少包含一个配置描述符,例如这里包含两个配置描述符高速HighSpeedConfigDscr和全速FullSpeedConfigDscr。

  1. db DSCR_CONFIG_LEN
    bLength段, 描述符长度,9字节。

  2. db DSCR_CONFIG
    bDescriptorType段, 描述符类型。

  3. db (HighSpeedConfigDscrEnd-HighSpeedConfigDscr) mod 256

  4. db (HighSpeedConfigDscrEnd-HighSpeedConfigDscr) / 256
    wTotalLength段,指明配置信息总长度,2字节表示。为配置描述符,接口描述符,端点描述符,设备类定义描述符,供应商自定义描述符长度的和。在这里只有配置、接口和端点描述符。

  5. db 1
    bNumInterface段, 指明该配置所支持的接口数(??),最小为1.

  6. db 1
    bConfigurationValue段, 指明该配置的配置值。例如这里值为1,在重枚举时,主机发送Setconfiguration(x),当x=1时,就调用该配置。

  7. db 00
    iConfiguration段, 该配置的字符串索引值,没有时为0.

  8. db 10000000b
    bmAttributes段, 指明该配置的特性,8位。
    b0~b4,保留,必须为0.
    b5:远程唤醒选择。=1,支持远程唤醒;=0,不支持远程唤醒。
    b6:是否总线电源选择。如果该USB设备外加了电源,=1,使用总线电源,=0,使用自供的电源。
    b7:必须为1. 在主机设备请求case SC_GET_STATUS,case GS_DEVICE中获得该信息。

  9. db 100
    bMaxPower段, 总线供电时,该USB设备可获得的最大电流。单位2mA,所以最大值为250.如果该电流得不到满足,USB将不能使用这个配置。

8.4 接口描述符Interface Descriptor

接口描述符

;; Interface Descriptor
db DSCR_INTRFC_LEN ;; Descriptor length
db DSCR_INTRFC     ;; Descriptor type
db 0               ;; Zero-based index of this interface
db 0               ;; Alternate setting
db 6               ;; Number of end points
db 0ffH            ;; Interface class
db 00H             ;; Interface sub class
db 00H             ;; Interface sub sub class
db 0               ;; Interface descriptor string index

接口描述符有9个字段,共9字节。
注意,主机不能用SetDescription和GetDescription来设置和读取接口描述符,它只能作为配置描述符的一部分信息返回,在主机发送case SC_GET_DESCRIPTOR,且case GD_CONFIGURATION时一并读取。所以我们看到,在fw.c文件中并没有对接口描述符的判断。

  1. db DSCR_INTRFC_LEN
    bLength段, 描述符长度。

  2. db DSCR_INTRFC
    bDescriptorType段, 描述符类型。

  3. db 0
    bInterfaceNumber段, 指明该接口的接口号。

  4. db 0
    bAlternateSetting段, 指明接口的可替换设置值。

  5. db 6
    bNumberEndpoints段, 指明接口所使用的断点数,不包括端点0.

  6. db 0ffH
    bInterfaceClass段, 指明接口所使用的设备类。

Value comment
0 保留
1~FEH 表明该接口属于某个明确的USB设备类
FFH 厂家自定义的设备类
  1. db 00H
    bInterfaceSubClass段, 该接口所属的USB设备子类。

  2. db 00H
    bInterfaceProtocol段, 该接口所使用的设备类协议。

  3. db 0
    iInterface段, 接口字符串描述符的索引值,没有时为0.

8.5 端点描述符Endpoint Descriptor

;; Endpoint Descriptor
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT     ;; Descriptor type
db 02H             ;; Endpoint number, and direction
db ET_BULK         ;; Endpoint type
db 00H             ;; Maximun packet size (LSB)
db 02H             ;; Max packect size (MSB)
db 00H             ;; Polling interval

端点描述符有6个字段,共7字节。和接口描述符一样,也不能由主机通过发送GetDedcription()请求读取,只能作为配置信息case GD_CONFIGURATION的一部分返回给主机。

  1. db DSCR_ENDPNT_LEN
    bLength段, 该描述符长度,单位字节。

  2. db DSCR_ENDPNT
    bDescriptorType段, 该描述符类型。

  3. db 02H
    bEndpointAddress段, 指明端点的端点号及传输方向。
    b0~b3:该端点的端点号。如0001端点1,0010端点2;
    b4~b6::保留,必须为0
    b7:端点传输方向。1-IN传输;0-OUT传输

  4. db ET_BULK
    bmAttributes段, 指明端点的一些特性。
    b0~b1:端点的传输类型。

Value comment
00 控制传输
01 同步传输
10 块传输
11 中断传输

b2~b3:当该端点为同步端点时,这两位指出同步类型。

Value comment
00 非同步
01 异步
10 自适应
11 同步

b4~b5:端点用法类型。

Value comment
00 数据端点
01 显示反馈端点
10 隐式反馈端点
11 保留

b6~b7:保留,必须为0

  1. db 00H
    wMaxpacketSize段(LSB), 指明端点所支持的最大数据包长度,共16位。
    b0~b10:端点所支持的最大数据包长度。
    b11~b12:当该端点为高速中断端点或同步端点时,这两位指出每小帧中最多传输的事务数。
key comment
00 每小帧1次(默认),
01 每小帧2次(附加一次)
10 每小帧3次(附加2次)
11 保留。

b13~b15保留,必须为0

  1. db 02H
    wMaxpacketSize段(MSB)
    高8位。
    数据包大小为:0000 0010 0000 0000,取0~10位,还是0200,512字节。

  2. db 00H
    bInterval段, 指明端点数据传输的访问间隔。
    低速中断端点:=10~255ms.
    全速中断端点:=1~255ms
    高速中断端点:=1~16,
    访问间隔为2(bInterval-1)(幂)×1us 全速/高速同步端点:=1~16,
    访问间隔为2(bInterval-1)(幂)×1ms 和2(bInterval-1)(幂)×1us 高速块/控制out端点:指明其最大NAK握手包发送速率。 =0,表示该端点永远不会发出NAK握手包 =其他值,表示每个bInterval时间内,该端点最多只能发送一次NAK握手包。 其他类型端点:该字段无效。

8.6 字符串描述符StringDscr1

字符串描述符:StringDscr1:

StringDscr1:
db StringDscr1End-StringDscr1   ;; String descriptor length
db DSCR_STRING
db 'C',00
db 'y',00
db 'p',00
db 'r',00
db 'e',00
db 's',00
db 's',00
StringDscr1End:

3个自独,长度是变化的(字节)。

偏移量 大小 描述
0 bLength 1 N+2 此描述表的字节数
1 bDescriptorType 1 常量 字串描述表类型
2 wLANGID[0] 2 数字 语言标识 (LANGID)码0
N wLANGID[x] 2 数字 语言标识(LANGID)
  1. db StringDscr1End-StringDscr1
    bLength段, 描述符长度

  2. db DSCR_STRING
    bDescriptorType段, 描述符类型。

  3. Unicode编码的字符串。

9. 学习资料

【关于USB】这家伙总结得不孬啊,我刚看一会儿就学了不少东西。
摘自: http://www.daxiamcu.com/bibis/moredata30_1384202.shtml

  1. 元器件
    CYPRESS 68013A:支持USB 2.0协议,带增强型8051单片机,时钟频率48Mhz。支持串口通讯。

  2. 文档

文档名 说明
cy7c68013.pdf 68013外设手册
cy7c68013_5.pdf 68013外设手册
CY3684_A_SCH.PDF 68013A外围电路图
FX2 TechRefManual.pdf EZUSB-FX2技术手册
fx2_to_fx2lp.pdf FX2和FX2LP的区别
CYAPI.PDF CYAPI手册 高级类库
CYUSB.PDF CYUSB手册 底层API

10. 开发环境

  1. Keil C 7.0编译器
  2. C++ Builder 6.0
  3. VC++ 6.0
  4. EEPROM烧写器
  5. 68013A的开发包(含CYPRESS CONSOLE、CYUSB.SYS、例程等)
  6. BUS HOUND 5.0

11. 开发流程

11.1 硬件程序编写

  1. 根据CYPRESS的示例程序建立工程框架,一般由FW.C PERIPH.C和定义寄存器的几个头文件组成。如下图:
  2. FW.C负责了设备连接、重枚举、设备初始化等过程
  3. PERIPH.C负责响应各种中断事件。
  4. dscr.a51文件定义了USB设备握手时需要的各种描述符
  5. FX2REGS.H定义了USB中所有的寄存器
  6. FX2.H主要定义了各种二级中断向量和描述符的数据结构
  7. 编译后的二进制代码和工程同名,扩展名为HEX。
  8. 相应的头文件和类库在KEIL C的lib和inc文件夹内,需在项目设置中设置路径。

11.2 硬件程序烧录

  1. EFROM
    因为本产品要求将二进制代码和硬件PID/VID烧录在EEPROM,而不是使用CYPRESS推荐的在线下载方式,所以外部采用了8K 的EEPROM。上电后68013A会将EEPROM中的数据和程序加载到RAM中运行。

  2. HEX2BIN.EXE
    HEX文件只是68013A上8051的程序代码,还要加上PID/VID等信息才能正确运行,CYPRESS在开发包中提供了HEX2BIN.EXE这个工具,可以根据HEX生成完备的IIC文件,将此文件烧录到EEPROM上即可。

  3. HEX2BIN.EXE的使用方法如下:
    将XXX.HEX文件拷贝到HEX2BIN.EXE所在目录,打开CMD,按如下格式输入:
    hex2bix -i -o xxx.iic xxx.hex -f 0xC2 -v 0x1234 -p 0x1234

key comment
-i 表示输出文件,也就是IIC文件
-o 表示输入文件,也就是HEX文件
-f 表示68013A发送PID/VID的方式,这里为C0,即从EEPROM上读取。
-v 表示VID的BCD码,开发阶段使用1234
-p 表示PID的BCD码,开发阶段使用1234
  1. IIC文件烧录到EEPROM上
    将生成的IIC文件用烧写器烧录到EEPROM上,本项目使用的是深圳思泰佳电子公司的NSP通用烧写器,此烧写器不支持IIC类型,选择BIN类型可替代。

11.3 驱动的识别

  1. 将EEPROM连到68013A上后,接上USB线,上电。计算机提示找到新硬件,要求安装驱动。
  2. CYPRESS针对68013A提供了全新的驱动程序CYUSB.SYS。这个驱动使用了新的API,所以上位机的编写上和旧的方式完全不同。底层的IOCTL控制字的定义也完全不同,详见CYAPI.PDF和CYUSB.PDF。
  3. 安装驱动之前,必须先根据VID/PID正确编辑CYUSB.INF文件,在文件中添加自己的PID/VID代码和设备描述,连接设备时, 将根据硬件上的PID/VID查找INF文件中对应的驱动,如果找不到,在设备管理器中将显示“68013 EEPROM MISSING”的字样。
  4. 详细的INF配置方法参考CYUSB.PDF PART1/PART2/PART3。这里不在赘述。
  5. 安装驱动时候找到修改好的CYUSB.INF文件,驱动将被正确安装,此时设备可以正常使用。

11.4 测试过程

  1. 被正确识别的设备可以在CYPRESS CONSOLE上看到设备信息。如图:
  2. CYPRESS CONSOLE的具体使用方法请参考CyConsole.chm。
  3. 要注意的是,除EP0/EP1外,当其他端点Max Pkt Size大小为64字节时,表示工作在USB 1.1模式,有可能是软件的原因, 也有可能是外围上拉电阻的问题。开发中要特别注意。

11.5 推荐开发流程

  1. 看本介绍USB 2.0协议的书,对USB 2.0协议有所了解。推荐《USB 2.0原理与工程开发》
  2. 看CYUSB.PDF文档。了解驱动安装方法。
  3. 看KEIL C51的书籍,熟悉C51的编程方法,熟悉KEIL C编程环境。
  4. 看CYPRESS提供的例程,了解68013A编程框架。推荐《EZ-USB 2100系列单片机原理、编程及应用》(基本框架类似,部 分寄存器定义不同)。 5.5 对照USB 2.0协议,编写dscr.a51文件,配置各种描述符。
  5. 结合FX2 TechRefManual.pdf,研读FW.C、PERIPH.C、FX2REGS.H、FX2.H,了解寄存器的定义。
  6. 根据系统需求编写相应代码,有开发板时,根据开发版上的LED来测试程序正确与否。
  7. 根据CYAPI.PDF CYUSB.PDF编写上位机通讯程序。 同步读取数据方法 XferData(); 异步读取数据方法 BeginDataXfer()/WaitForXfer()/FinishDataXfer();
  8. 调试程序。
  9. 编写其他8051上的程序,并继续调试。

11.6 发布时应提供的文件

  1. CYUSB.SYS
  2. CYUSB.INF
  3. XXX.IIC

12. 重点讲解

12.1 如何理解CYPRESS 68013A程序框架

CYPRESS提供了非常好的程序框架,免去了用户自己编写一些通用性比较强、模式化的程序(如果不提供,很少有人能写出如 此高效,结构紧凑的程序,实际上此框架和68013A内部结构关系密切,一般人也没有足够的内部资料也不可能写出来)。在框 架的基础上,用户只需在相应的地方写相应的代码即可完成USB工作。

一般来说框架可以分成3个部分。

  1. 描述符文件
    例如dscr.a51文件,里面定义了枚举设备的时候要用的各种描述符信息,这部分用户需要根据实际的情况自己编写。我写的时候发现一个最大的问题就是各种书籍协议版本不同,翻译质量不同,同一字段的意义表述不同,容易让人产生困惑。例如USB 1.1/2.0/2.13对设备类型的子类定义都不完全相同,所以写的时候最好几种文档对比起来写。由于USB官方网站的文档中字段解释过于专业化,所以对USB不是很熟悉的人比较难以理解其真正含义。所以要多参考不同的书籍,某种程度上降低了开发速度,但对第一次做USB开发的人来说,这也是值得的。

  2. 固件文件
    例如FW.C文件,这是硬件程序的函数入口。主要有以下这些方法:

void SetupCommand(void); // 握手命令处理
void TD_Init(void);      // 初始化,完成配置,启动时调用一次
void TD_Poll(void);      // 用户处理程序,循环调用
void IO_Init(void);      // 8051 IO初始化
void REG_Init(void);     // 8051寄存器初始化
BOOL TD_Suspend(void);   // 挂起处理
BOOL TD_Resume(void);    // 唤醒处理

// 以下为各种描述符的获取和设置函数,重枚举时自动调用
BOOL DR_GetDescriptor(void);
BOOL DR_SetConfiguration(void);
BOOL DR_GetConfiguration(void);
BOOL DR_SetInterface(void);
BOOL DR_GetInterface(void);
BOOL DR_GetStatus(void);
BOOL DR_ClearFeature(void);
BOOL DR_SetFeature(void);
BOOL DR_VendorCmnd(void);
  1. 功能文件
    处理各种中断。例如PERIPH.C文件。8051一般默认只有四个中断,这显然不够USB使用,所以 CYPRESS引入了自动向量的概念,相当于软中断,大大扩展了现有的中断数量。主要的中断有:
void ISR_Sudav(void) interrupt 0     // 收到setup包
void ISR_Sutok(void) interrupt 0     // 收到SETUP令牌
void ISR_Sof(void) interrupt 0       // 收到起始帧
void ISR_Ures(void) interrupt 0      // 收到RESET
void ISR_Susp(void) interrupt 0      // 收到挂起信息
void ISR_Highspeed(void) interrupt 0 // 高速模式
void ISR_Ep0ack(void) interrupt 0    // 正常响应ACK
void ISR_Stub(void) interrupt 0
void ISR_Ep0in(void) interrupt 0
void ISR_Ep0out(void) interrupt 0
void ISR_Ep1in(void) interrupt 0
void ISR_Ep1out(void) interrupt 0    // EP1输入中断
void ISR_Ep2inout(void) interrupt 0  // EP2中断
void ISR_Ep4inout(void) interrupt 0
void ISR_Ep6inout(void) interrupt 0
void ISR_Ep8inout(void) interrupt 0
void ISR_Ibn(void) interrupt 0
void ISR_Ep0pingnak(void) interrupt 0
void ISR_Ep1pingnak(void) interrupt 0
void ISR_Ep2pingnak(void) interrupt 0
void ISR_Ep4pingnak(void) interrupt 0
void ISR_Ep6pingnak(void) interrupt 0
void ISR_Ep8pingnak(void) interrupt 0
void ISR_Errorlimit(void) interrupt 0
void ISR_Ep2piderror(void) interrupt 0
void ISR_Ep4piderror(void) interrupt 0
void ISR_Ep6piderror(void) interrupt 0
void ISR_Ep8piderror(void) interrupt 0
void ISR_Ep2pflag(void) interrupt 0
void ISR_Ep4pflag(void) interrupt 0
void ISR_Ep6pflag(void) interrupt 0
void ISR_Ep8pflag(void) interrupt 0
void ISR_Ep2eflag(void) interrupt 0
void ISR_Ep4eflag(void) interrupt 0
void ISR_Ep6eflag(void) interrupt 0
void ISR_Ep8eflag(void) interrupt 0
void ISR_Ep2fflag(void) interrupt 0
void ISR_Ep4fflag(void) interrupt 0
void ISR_Ep6fflag(void) interrupt 0
void ISR_Ep8fflag(void) interrupt 0
void ISR_GpifComplete(void) interrupt 0
void ISR_GpifWaveform(void) interrupt 0

特别是对于接受数据,一般都在中断中完成相应处理,“中断中适合进行少量简短的操作,不适合进行复杂操作”,这句话在 此依然有效。如果要进行复杂的操作可以在TD_POLL()中进行(多数操作都是在这个函数中完成的)。
另外非常重要的一点是,中断程序的结尾应该让中断复位,允许下一次中断,有些端点的计数器也要清零并允许接受新的中断 请求。例如:

EP1OUTBC = 0;      // 清空计数
EZUSB_IRQ_CLEAR(); // USB中断复位
EPIRQ = 0x08;      // 允许EP1中断请求

12.2 68013A端点寄存器介绍

68013A内部的寄存器约有300个上下,一次都记住是不可能的,而且每个寄存器都有8个位,也就是说一共有2000多个可以配置的位,一次都理解掌握这些位的含义也是不可能的,所幸地是开发中并不会用到所有的寄存器,但是依然强烈建议把 FX2REGS.H和FX2.H走读一边,这就像读书一样,没有学会识字,再看都是天书。结合FX2 TechRefManual.pdf走读这些寄存器 大约需要一到两天时间,这点时间投入还是值得的。
在通讯过程中,打交道最多的是各种端点寄存器,掌握好这些寄存器地使用对提升开发效率是很有帮助。值得特别关注的寄存 器和配置位如下:

  1. Rwuen
  2. REVCTL
  3. EP1OUTCFG
  4. EP1INCFG
  5. EP2CFG
  6. EP4CFG
  7. EP6CFG
  8. EP8CFG
  9. EP2FIFOCFG
  10. EP4FIFOCFG
  11. EP6FIFOCFG
  12. EP8FIFOCFG
  13. FIFORESET
  14. EPIRQ
  15. EPIE
  16. EP1OUTBC
  17. APTR1H
  18. APTR1L
  19. EXTAUTODAT1
  20. AUTOPTRH2
  21. AUTOPTRL2
  22. EXTAUTODAT2
  23. EP2BCH
  24. EP2BCL

其中有些寄存器的设置需要连续设置多次,看似重复了,其实不然,这和设置的缓冲区数量有关。
有些寄存器中间必须用SYNCDELAY来延时。这类寄存器FX2 TechRefManual.pdf上有说明。

对于EP0,用于系统握手,相关的寄存器操作基本上都由68013A的内核(SIE)来完成了。
对于EP1,分为OUT/IN两组配置和寄存器。
对于EP2~EP8,不分OUT/IN输入输出,主要有EP2CFG/ EP2FIFOCFG/ EP2BCH/EP2BCL寄存器。

12.3 什么是自动指针

自动指针是CYPRESS提供的一个非常有用的特性。
在数据交互的过程中,很多时候都涉及到数据的搬迁,比如从EP2OUT收到的 数据需要转发到EP6IN上(一些转换类设备);再比如从RAM中拷贝数据到EP4IN上,传统的做法是申明两个指针,指向源和目 的地址,然后用循环一个个字节拷贝,同时还要考虑增加指针地址,对于连续的空间这到不是问题,关键是如果数据需要拷贝 到多个缓冲时,指针地址是循环的。这时候如果手工完成操作很容易出错。 因此CYPRESS提供了两组自动指针,用的时候一组指向源,一组指向目的地址。然后循环拷贝数据就行了,自动指针会自动指 向下一个源或目的空间,不论是否是循环地址方式。这样减少了程序出错的几率。

下面的程序将EP2OUT接受到的数据拷贝到EP6IN发送出去:

if(!(EP2468STAT & bmEP6FULL)) { 

    // check EP6 FULL(busy) bit in EP2468STAT (SFR), 
    // core set's this bit when FIFO is full APTR1H = MSB( &EP2FIFOBUF ); 
    
    APTR1L = LSB( &EP2FIFOBUF ); 
    AUTOPTRH2 = MSB( &EP6FIFOBUF ); 

    AUTOPTRL2 = LSB( &EP6FIFOBUF ); 
    count = (EP2BCH << 8) + EP2BCL; 
    // loop EP2OUT buffer data to EP6IN 
    for( i = 0x0000; i < count; i++ ) { 
        // setup to transfer EP2OUT buffer to EP6IN buffer using AUTOPOINTER(s) 
        EXTAUTODAT2 = EXTAUTODAT1; 
    } 
    EP6BCH = EP2BCH; 
    SYNCDELAY; 

    EP6BCL = EP2BCL; // arm EP6IN 
    SYNCDELAY; 

    EP2BCL = 0x80; // re(arm) EP2OUT 
}
  1. APTR1H/APTR1H
    通过MSB和LSB获取EP2FIFOBUF的高位地址和地位地址。

  2. EXTAUTODAT1
    表示APTR1H/APTR1H指向的数据。

  3. AUTOPTRH2/AUTOPTRL2
    通过MSB和LSB获取EP6FIFOBUF的高位地址和地位地址。

  4. EXTAUTODAT2
    表示AUTOPTRH2/AUTOPTRL2指向的数据。

12.4 CYUSB和CYAPI的关系

以前68013上位机程序的编写过程中,应用程序端通过调用DeviceIoControl() API或CREATEPIPE() API与驱动进行交互,继而读写控制硬件设备,在新的68013A的驱动中采用了两种新的调用方法:

第一种
继续使用DeviceIoControl()函数读写,不同的是,IOCTL控制字和老驱动完全不同,具体定义参考CYUSB.PDF。用户可以通过这些底层API完成操作。

第二种
使用CYPRESS提供的面对对象的类,一共有9个类,调用这些类的方法就可以和硬件打交道。这些类是对

第一种方法的封装,使用起来非常简便。 用户可以根据需要选择这两种方法或混合使用,使用时需要加上头文件CyAPI.h和cyioctl.h,另外在项目中还要引用 CyAPI.lib。

12.5 同步和异步读写的比较

CYAPI提供了同步和异步读写方式。
同步方式的时候调用线程阻塞在哪里,直到读写到数据或超时;
异步方式的时候调用线程 立即返回。具体实例参考CYAPI.PDF。

12.6 如何用C++ BUILDER写上位机程序

  1. 首先确定使用7.4中的第几种方法,添加相应的头文件和库文件。
  2. 连接USB设备,确保驱动已经被正确加载。
  3. 编写收发数据线程。通过开发板上的LED或CYPRESS CONSOLE或BUS HOUND分析收发正确与否。

12.7 U盘如何正确加载驱动

在WINDOWS 2000/XP上U盘使用的PID/VID应该直接能加载操作系统默认的海量存储器的驱动程序。为了使用正确的PID/VID,可以通过以下途径:

  1. 找一个现有品牌的U盘,看看他的PID/VID是何值。
  2. 在注册表中查找海量存储器信息。

12.8 其他问题

  1. 编写上位机的时候要注意添加异常处理。
  2. 调试上位机的时候,USB外设应正确连接。
  3. 8051其他模块的编写请参考相应书籍

你可能感兴趣的:(B)