logo资料库

cy7c68013 完全教程.pdf

第1页 / 共29页
第2页 / 共29页
第3页 / 共29页
第4页 / 共29页
第5页 / 共29页
第6页 / 共29页
第7页 / 共29页
第8页 / 共29页
资料共29页,剩余部分请下载后查看
算是给所有正在学习 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 中方便的看到结果,板子不在身边, 就懒的贴图了。 EZ-USB FX2(68013)固件研究(3)——FW.C 文件【1】 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?)。 // 重定向描述符 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。这里就用到了前面定义的变量,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 令牌数据的到来 {
分享到:
收藏