CYUSB 开发包

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

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

     【1】:首先,还是说下工具吧!

              1:你要有一块68013的USB板子,淘宝随便买块好了,还送不少资料。

              2:要准备开发工具,去Cypress官网下一个Cy3684的开发包,全称:

             cy3684_ez_usb_fx2lp_development_kit_15.exe

             网址:http://www.cypress.com/?rID=14321

            3.安装开发包。工具就是Cypress USB Console了。怎么用不用我说了吧,这偏文章主题是

               Cypress固件架构.

   【2】好了,进入主题,固件架构(汗--!好多废话)。以一个3684开发包自带的例子讲解。

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

          (2)到D:\Program Files\Cypress\USB\Target文件价下把Cypress头文件Fx2.h,fx2regs.h,syncdly.h这三个头文件拷到bulkloop文件夹里。

           (3)keil设置output里关掉Run User Program #1(前面的勾去掉)(别说不会用keil。。。)

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

   【3】现在可以打开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:这几个函数是当使用端点中断传输时,中断代码的书写处,很少用。

      以上,是经常会用到的几个函数;其他,基本不常用。

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

        【4】然后说下几个包含文件:

               有这几个:

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

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

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

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

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

################################################

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

      我之所以从bulkllop开始说,也是深有体会的。想当初刚开始学USB的时候,抱着协议闷头看楞是看不出个所以然,后来从例子开始学,才慢慢搞懂了USB,说实话,我到现在对USB协议类的DD还是一知半解,不过并不妨碍我进行USB的开发不是,所以说,Cypress的固件架构是个好东东,我们可以偷懒了。开发包里例子是基于Cypress的一块开发板的,我想很少有人能弄到吧,而bulkloop就简单了,什么外围都不需要,一块68013,加个cyconsole interface就可以看到效果了.可以说,看懂了bulkloop,USB也就算正式入门了,剩下的要做的就是触类旁通,然后再不停的回过头去看USB协议,就会恍然大悟“然来是这样”。

      首先,看bulkloop文件夹下的readme.txt文件,告诉我们这个固件主要实现的功能。

      数据从EP2OUT->EP6IN ,从端点2 out缓冲区到端点6的in缓冲区。数据流向为:

            ◆PC端(console软件)设定要传输的数据

            ◆PC端发起端点2out传输,数据到达68013端点2的out缓冲区

          ◆bulkloop固件查询到端点2的out缓冲区有数据,于是将数据发往端点6的IN缓冲区

            ◆PC端发起端点6 IN传输,于是68013的端点6 IN缓冲区中的数据被读到PC机显示

这就是整个bulkloop过程,EP4->EP8同理。

          然后看固件,看一个固件总是从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对该寄存器的解释,b7(第8位)=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两个自动指针,这两个自动指针使用方便,可以自动指向端点缓冲区。

       然后是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
        for( i = 0x0000; i < count; i++ )      //传送count字节
        {
           // 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中方便的看到结果,板子不在身边,就懒的贴图了。

#########################################

FW.C文件,是比较难看懂的了,这个要逐字逐句研读,我当初整整看了一个星期,边理解,边一行一行的注释,可以说,看懂了,USB协议部分也就差不多了。
    从main()函数开始看:
DWORD i;
WORD   offset;
DWORD DevDescrLen;
WORD   IntDescrAddr;
WORD   ExtDescrAddr;

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

   定义了一些变量,具体用途在后面;第二段同时对变量进行初始化,从名字可以看出其用途。
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?)。
// 重定向描述符
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。这里就用到了前面定义的变量,IntDescrAddr保存内部RAM首址0X80,ExtDescrAddr保存我们获得的当前描述符表外部RAM的首址 ,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中断

   EZUSB_IRQ_ENABLE();
预定义是EZUSB=1,查TRM得知,ezusb是EIE寄存器的第0位,EIE.0=1,使能USB中断;EZUSB_ENABLE_RSMIRQ();EICON |= 0x20,EICON.5=1,使能远程唤醒中断;INTSETUP |= (bmAV2EN | bmAV4EN);使能INT2,4自动向量跳转; USBIE |= bmSUDAV | bmSUTOK | bmSUSP | bmURES | bmHSGRANT;使能所选择中断,相关的中断意义,我也一知半解,后面慢慢学习补充;   EA = 1;    开8051中断。

 

 

##################################

从下面的代码开始,才真正开始我们自己的USB事务处理:
while(TRUE)               // Main Loop主循环
   {
      // Poll User Device 用户调度程序
      TD_Poll();

      // Check for pending SETUP
      if(GotSUD)     //等待SETUP令牌数据的到来
      {
         SetupCommand();          // Implement setup command //处理SETUP事务
         GotSUD = FALSE;          // Clear SETUP flag   清Setup标志
      }
   TD_Poll,也就是用户调度程序,USB空闲时调用,不过为什么要放在开始呢?照理说应该放在令牌包后面的,这里不是很明白。。。。。。
   if(GotSUD),GotSUD是令牌包标志,准确的说是“令牌阶段数据到来”,什么是令牌包?
   首先,USB一连串的数据传输、处理、响应等就叫做USB事务。例如,上位机要读取一个描述符,那么就会触发一次USB事务。一个完整的USB事务处理有三个阶段:令牌阶段,数据阶段,握手阶段。每个阶段数据传输是有各种包组成的,例如令牌阶段:同步字段+令牌包+EOP构成。
   USB主机启动事务处理,开始发送令牌包,这个时候假如说我们当前的USB设备地址号为2(重枚举时分配的),而主机发送的地址号也为2,那么这个USB设备硬件会产生中断,进入中断处理。也就是periph.c文件中的void ISR_Sudav(void)函数,在这个中断处理中,设置 GotSUD标志为TRUE,表示收到令牌数据,要启动USB传输了。然后我们固件中判断if(GotSUD),GotSUD为真,则执行SetupCommand()函数,在这里处理控制传输,读取描述符,设置特性,处理Vendor命令等。完毕后置GotSUD = FALSE;然后检查USB各种状态并处理:
if (Sleep)
   如果USB进入了休眠状态,这里Sleep是USB休眠标志,通GotSUD一样,USB休眠后产生中断,进入void ISR_Susp(void)函数处理,置sleep标志为TRUE。
if(TD_Suspend())
    检测USB是否挂起
         { 
            Sleep = FALSE; //清Sleep标志
            do
            {
               EZUSB_Susp();         // 置8051为空闲状态.
            }
            while(!Rwuen && EZUSB_EXTWAKEUP());//如果唤醒
           
            EZUSB_Resume();   //从空闲状态中恢复    
            TD_Resume();
    到这里,一个事务处理完毕,等待下次事务处理中断的到来.
   To Be Continue...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值