首页 USB3.0设计资源cypress芯片程序解读2

USB3.0设计资源cypress芯片程序解读2

举报
开通vip

USB3.0设计资源cypress芯片程序解读2USB3.0设计资源cypress芯片程序解读2 CYPEESS USB3.0程序解读 解读同步FIFO的一个例子。 生产者,消费者 首先看DMA的回调函数: typedef void (*CyU3PDmaCallback_t) ( CyU3PDmaChannel *handle, /* Handle to the DMA channel. */ CyU3PDmaCbType_t type, /* The type of callback notification being generated. *...

USB3.0设计资源cypress芯片程序解读2
USB3.0设计资源cypress芯片程序解读2 CYPEESS USB3.0程序解读 解读同步FIFO的一个例子。 生产者,消费者 首先看DMA的回调函数: typedef void (*CyU3PDmaCallback_t) ( CyU3PDmaChannel *handle, /* Handle to the DMA channel. */ CyU3PDmaCbType_t type, /* The type of callback notification being generated. */ CyU3PDmaCBInput_t *input /* Union that contains data related to the notification. The input parameter will be a pointer to a CyU3PDmaBuffer_t variable in the cases where the callback type is CY_U3P_DMA_CB_RECV_CPLT or CY_U3P_DMA_CB_PROD_EVENT. */ ); 根据其说明,解读如下: 1( 对每一个DMA通道,回调函数必须被注册。如果没有注册或者相应的 通知 关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知 事件没有被注册,则回调函 数不会被执行。 2( 回调函数不能被阻塞。即不能用SLEEP()之类的函数。如果数据需要处理,必须在回调函数之外。 3( 在生产者事件中,应用希望尽可能快地处理输入的数据。如果缓冲的处理不能在规定的时间内完成, 则输入的可能是陈旧的数据。在自动信号通道中,输入参量指向最新的数据。如果处理延时,生产者 socket可能复盖部分数据。 ,输入参量指向第一个缓冲(用于去消费者socket).如果在第二次调用4( 在手动或手动IN通道模式时 时,这个缓冲仍没有被处理,输入参量中将是被陈旧的数据。如果数据处理必须在通道中做, CyU3PDmaChannelGetBuffer函数必须被应用,而回调函数必须作为一个通知。 而输入指针input的定义如下: typedef struct CyU3PDmaBuffer_t { uint8_t *buffer; /* Pointer to the buffer */ uint16_t count; /* Byte count of valid data in buffer */ uint16_t size; /* Buffer size */ uint16_t status; /* Buffer status. This is a four bitdatafield… } CyU3PDmaBuffer_t; CyU3PDmaChannel这个结构中包含20个左右的参数,其中含回调函数。 定义了一个全局变量:CyBool_t glIsApplnActive=CyFalse; 这个变量是一个BOOL型先设为FALSE. 程序然后定义了一个错误处理,我们不处理错误,故是一个死循环语句。 然后,定义一个debug_init用串口来显示一些信息。初始化串口,设波特率—只允许发不允许收,另外,采用DMA模式来处理UART。 注意到这个函数:CyU3PDebugInit(CY_U3P_LPP_SOCKET_UART_CONS,8) 关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf 示只处理8以下的显示,大于8将不显示。 接下来就是一个回调处理函数 CyFxSlFifoUtoPDmaCallback( 1 CyU3PDmaChannel *chHandle, CyU3PDmaCbType_t type, CyU3PDmaCBInput_t *input) { CyU3PReturnStatus_t status =CYU3P_SUCESS; If(type == CY_U3P_DMA_CB_PROD_EVENT){ Status = CyU3PDmaChannelCommitBuffer(chHandler,input->buff_p.count,0); glDMARxCount++;} } 其中,CommitBuffer这个函数通常在手动DMA方式下被调用,它3个参数的含义分别为:DMA的句柄号,处理的字节数及当前的状态。其中地址由通道描述符隐含着。这个函数发送一个buffer向消费者socket. 接下来,是一个比较复杂的程序 Void CyFxSlFifoApplnStart(void) 这个函数启动一个slave fifo应用。当从USB接口收到一个SET_CONF事件时,即设置配置事件时,它被调用。在这个函数中,端点被配置,DMA管道被建立。我们稍后将看到它就是在USB配置时被调用的。 首先,根据USB的接口速度,决定这个DMA缓冲区的大小为多少字节。对于3。0是1024。 然后,端点配置。而得到速度是一个库函数,如何得到速度不得而知。 不过,由于配置是在设备描述符得到后,并且是设置地址后调用的。故此时估计PC机已经与下位机协商好速度了。例如PC为2。0则速度只能设为2。0 端口设为BULK方式,且被允许。突发长度为1。尺寸也被设为1024。 先配置生产者: 允许端点1的收,尺寸按速度设置好,其它没什么。 不过IN端点1定义成0x81 OUT定义成0x01 接下来,要产生一个DMA_MANUAL通道 为U TO P 看dmaCfg的一些参数填充: 尺寸,即1024。 缓冲区个数,2个。 生产者ID号 从0X401开始的。 消费者socket端口,从0X103开始的。 DMA模式:为0表示按字节计数。 DMA事件:CY_U3P_DMA_CB_PROD_EVENT表示收到一个‎‎生产者发来的 缓冲DMA的回调函数 UtoP 头 0 尾 0 消费socket的头的编移 0 最少要多少个空的缓冲才会在生产者激活前。0 表示任何时候都要激活它。 在接收PtoU的DMA通道中,修改了这些: 产生者socketID被定义为0x100 消费者socketID被定义为0x301 回调函数改变了。 2 然后是生成DMA通道。 再就是刷新生产者端点 EP。 再就是刷新消费者端点 EP 设置DMA传输尺寸。设为0表示无限。 最后将glIsApplnActive = CyTrue; 将这个全局变量设为TRUE。 下面是一个停止FIFO循环的程序。断开时或复位时会被调用。此处暂不管它。 再下面是一个当USB在SETUP时的回调处理 由于SETUP时交由DRIVER缺省处理,故直接返回一个FALSE. USB事件处理回调函数 当设置配置时,调用AppStart() 但是如果已激活又来这么一下,则直接调用ApplnStop() 复位和断开时,调用ApplnStop() 下面又定义一个比较重要的函数:用于初始化GPIF和USB接口。 CyFxSlFifoApplnInit(void) ///下面将这个函数写于此 { CyU3PPibClock_t pitClock; CyU3PReturnStatus_t apiRetStatus =CY_U3P_SUCCESS; //以下初始化p-port块 pibClock.clkDiv = 2; pibClock.clkSrc = CY_U3P_SYS_CLK; pibClock.isHanfDiv = CyFalse; pibClock.isDllEnable = CyFalse; apiRetStatus = CyU3PPibInit(CyTrue,&pibClock); //这里是设置好时钟 ///以下装载GPIF Slave_Fifo----未明白 它是如何配置的。 apiRetStatus = CyU3PGpifLoad(&Sync_Slave_Fifo_2Bit_CyFxGpifConfig); ///接下来是启动状态机(略),启动USB函数: apiRetStatus = CyU3PUsbStart(0;---开始USB功能 接下来 注册回调函数用于USB的SETUP过程。但它是一个返回FALSE的函数。 CyU3PUsbRegisterSetupCallback(CyFxSlFifoApplnUSBSetupCB,CyTrue); CyU3PUsbRegisterEventCallback(CyFxSlFifoApplnUSBEventCB); 事务处理不是缺省的,而是我们上面定义 过的。例如ApplnStart()就是在配置过程中启动的。 接下来,要开始配置设备描述符了,因为描述符中含有PID和VID的值。所以必须配置。 apiRetStatus= CyU3PUsbSetDesc(CY_U3P_USB_SET_SS_DEVICE_DESCR,NULL,(uint8*)CyFxUSB30Dev iceDscr); 接下来是二进对象存储描述符的设置。 接下来是设备量化描述符。 接下来是超速配置描述符,高速设备配置描述符, 重点看一下超速配置描述符,它含配置描述符 主要指明了接口数,配置数,配置字符串(无) 特性-自供电,远端唤醒功能 电流消耗400mA 3 接口描述符有: 端口数量,2个。 接口类 FF,子类00 接口协议0 接口协议字符串0 生产者端点描述符如下: 端点地址 0x01 生产者。最大包的长度1024,数据间隔传输类型 0表示BULK。 超速端点公司描述符,基本上全是0。 消费者端点,与生产者基本类似。只是端口地址不一样,其它一样的。 接下来是高速,全速描述符。 接下来是语言描述符。 接下来是制造厂名描述符。 为CYPRESS 接下来是产品描述符 FX3 最后连接USB物理层。至此枚举将开始。 } 然后定义了一个线程进入点,如下: SlFifoA ppt 关于艾滋病ppt课件精益管理ppt下载地图下载ppt可编辑假如ppt教学课件下载triz基础知识ppt hread_Entry(uint32 input) { CyFxSlFifoApplnDegugInit(); ///这里是UART串口初始化 CyFxSlFifoApplnInit(); ///初始化FIFO 应用 在这中间是GPIF和USB的初始化程序。 for(;;) { CyU3PThreadSleep(1000); //sleep If(glIsApplnActive) ///如果还是激活状态 CyU3PDebugPring(6,”Data tracker:…%d,buffer send:%d\n”,glDMARxCount,glDMATxCount); } } } ///// 下面是应用定义函数,估计这个函数名是不能改的。在这个函数中,我们先分配一个堆栈空间 Ptr=CyU3PMemAlloc(CY_FX_SLFIFO_THREAD_STACK); //然后产生一个线程 retThrdCreate = CyU3PThreadCreate(*slFifoAppThread, “ 21:Slave_FIFO_syne”, slFifoAppThread_Entry, 0,ptr, CY_FX_SLFIFO_THREAD_STACK, CY_FX_SLFIFO_THREAD_PRIOITY, CY_FX_SLFIFO_THREAD_PRIORITY, CYU3P_NO_THIME_SLICE, CYU3P_AUTO_START ); 最后是主程序main() 首先初始化设备,设备指的是CPU,主要是时钟和堆栈等。 然后cachecontrol 不用DATA CACHE。 4 在开发板上,由于53:56脚被连接到UART,这意味着我们要么选择DQ32模式,要么选择LppMode. 不然UART就没办法用了。Lpp模式好象是GPIO+UART模式,见datasheet 33页。 几个参数 用UART,不用IIC 不用IIS,不用SPI。没有GPIO使用到(简单复杂都没有)。 然后就是设置配置了。 最后进入到内核。不返回。结束主程序。 内核于是调用某一个函数,也就是CyFxApplicationDefine(void)。程序就从此开始了。 问: 1程序是如何下载到USB中去的。要怎么做。例如将这个编译好的代码放到什么目录里还是怎么办,还是启动时,安装driver时,自动下载,这个下载的东西是个什么文件格式,该放在哪里,不明白 2(难道GPIF口上没接任何东西, 如何将PC机发下来的数据发回到PC机上去, 生产者:USB的OUT接口1,向GPIF发送一批数据 消费者:GPIF向IN接口81,由它消费掉一批数据。然后通过IN发回PC机。 再看一个简单一点的GPIO的例子 1先是一个错误处理的函数,我们不需要它,故这是一个死循环。 CyFxDebugIni这个函数,将串口作为调t 试口用 115200bps 2 3 void CyFxGpioIntrCb ( uint8_t gpioId /* Indicates the pin that triggered the interrupt */ ) 这个函数是一个中断回调函数。必须在某个地方注册一下。 它有下列过程:apiRetStatus =CyU3PGpioGetValue(gpioID,&gpioValue); //这个函数得到某个端口中断的值 这个gpioValue是一个BOOL值。而ID则是某一个端口的端口号。这个函数只能返回一个引脚。 等会看这个ID是什么指定的。 CyU3PEventSet(&glFxGpioAppEvent, CY_FX_GPIOAPP_GPIO_HIGH_EVENT,CYU3P_EVENT_OR); 如果为高,则设置一个事件。是一个高事件发生。注意到事件是一个全局变量,而这个事件中有许多参数,其中比较重要的是一个回调函数。应该在某个地方将这个事件与一个回调函数联系起来。一会要补充这里 5( Void CyFxGpioInit (void) apiRetStatus = CyU3PGpioInit(&gpioClock,CyFxGpioIntrCB); 这个函数是设定gpio的时钟,以及中断的回调函数。这与4中部分形成对照。 然后将gpio45定义为输入且允许中断 gpioConfig.intrMode = CY_U3P_GPIO_INTR_BOTH_EDGE; apiRetStatus = CyU3PGpioSetSimpleConfig(45, &gpioCo nfig);GPIO的21脚本来作为GPIF的控制信号的。不能用CyU3PDeviceConfigureIOMatrix来将它作为GPIF IOs. 这个过载API调用必须进行必须小心当改变这个引脚的功能时。如果IO脚作为GPIF的一部分连到外部设备上。则它不能再作为GPIF IO使用。在这里CTL4是不使用的,所以用它用IO脚是安全的。 apiRetStatus = CyU3PDeviceGpioOverride(21,CyTrue); 接下来apiRetStatus = CyU3PGpioSetSimpleConfig(21,&gpioConfig); 5 6 接下来有两个线程,一个是输出线程,一个是输入线程,先看输出线程: apiRetStatus = CyFxDebugInit(); ////初始化调试模式。这个在2中定义的。 CyFxGpioInit();这个也在前面5定义过。后面是一个闪灯程序。 apiRetStatus = CyU3PGpioSetValue(21,true); 将输出置为高。 延时2秒,将输出变为低。 延进2秒。 7 下面再来看输入线程:是一个循环,等事件发生。 txApiRetStatus = CyU3PEventGet (&glFxGpioAppEvent, (CY_FX_GPIOAPP_GPIO_HIGH_EVENT | CY_FX_GPIOAPP_GPIO_LOW_EVENT), CYU3P_EVENT_OR_CLEAR, &eventFlag, CYU3P_WAIT_FOREVER); 这里表示永远等下去。等到后要清除事件,另返回事件的标志,这个标志我们没有用。如果等到高的标志,就打印一个引脚为高,如果为低,就打印一个引脚为低的标志。估计这个等事件标志将被block. 这样整个过程清楚了,IO脚触发引起一个中断。这个中断回调函数中将触发一个事件。在这个线程中将等事件发生,如果发生了,就打印出引脚的状态。 事件在什么地方初始化呢,还是不需要初始化, 8 果然,事件是要初始化的。在应用程序中初始化了,下面就看这个应用程序 先创建一个输出线程。 再创建一个输入线程 然后 retThrdCreate = CyU3EventCreate(&glFxGpioAppEvent); 9 最后看一下main() Main()中主要是将GPIO引脚初始化一下。 io_cfg.gpioSimpleEn[0] = 0; io_cfg.gpioSimpleEn[1] = 0x00002000; /* GPIO 45 */ io_cfg.gpioComplexEn[0] = 0; io_cfg.gpioComplexEn[1] = 0; 45引脚为什么对应的是0x2000. 这是因为它是32位的,45引脚=32+13 这个D13位正好是0X2000 从main开始看起: 再看一下几个定义:输出线程,输入线程及事件在文件一开始就定义了。 CyU3PThread gpioOutputThread; /* GPIO thread structure */ CyU3PThread gpioInputThread; /* GPIO thread structure */ CyU3PEvent glFxGpioAppEvent; /* GPIO input event group. */ 它主要是调用了一个串口设置函数,然后就进入到cache控制设置,再后来就是设置一个IO脚,45脚使之使能。并且选用配置模式(即LPP模式)。允许了UART,不允许IIC,IIS,SPI,另外isDQ32bit也不允许。这个表示它不支持GPIF的32位模式。 然后我们再看应用程序启动,这是由系统自动调用的。我们可能修改它的内容,但是它是必须的。 这个函数中,它创建了两个线程。一个是输入线程,一个是输出线程。 另外,容易遗忘的一件事是它创建了一个事件。事件的创建只要这样就可以了: retThrdCreate = Cy3U3PEventCreate(&glFxGpioAppEvent); 再往上,就是输入线程了。这个线程看输入引脚的变化,而这个变化由中断回调函数引起,中断回调函数 6 中,它会产生一个事件,而我们的线程就监视这个事件。如果有事件高发生,就串口打印一个引脚高,如果低,就打印一个引脚低。看它是如何实现的: txApiRetStatus = CyU3PEventGet(&glFxGpioAppEvent, (CY_FX_GPIOAPPP_GPIO_HIGH_EVENT|CY_FX_GPIOAPP_GPIO_LOW_EVENT), CYU3P_EVENT_OR_CLEAR,&eventFlag,CYU3P_WAIT_FOREVER); 这是个等事件的函数,这个函数无法找到它的定义,它是一个API函数。我们找API,发现它的参数含义。 这里有一个CYU3P_EVENT_OR_CLEAR表示只要上面有一个位被设置就返回且清除标志。----OR。 而真正的事件就放在标志中返回了。 既然有读事件,就必有设置事件,事件的设置应该在中断回调中实现。而中断回调的注册,应该在初始化时实现。下面应该可以很快看到这点。---事实上,在下面的输出线程中就实现了注册 输出线程实现,输出线程比较有意思的是其DebugInit()居然是在它中间实现的。这有点不合常理。 而接下来,它又调用了初始化GpioInit()这个函数。在这个函数中,先初始化GPIO,这个GPIO居然还要将时钟也设置一下,有点不合常理。在这个初始化中,它还指明了GPIO中断回调函数的注册。尽管这个中断函数应该是在输入线程中注册似更合理一些。接下来,45脚要用之为输入,所以要将配置设一下: gpioConfig.outValue = CyTrue; //输出为高 因为是输入,要将它设为高 gpioConfig.inputEn = CyTrue; //输入使能 gpioConfig.driveLowEn = CyFalse; //不要驱动低也不要驱动高 gpioConfig.driveHighEn = CyFalse; gpioConfig.intrMode = CY_U3P_GPIO_INTR_BOTH_EDGE; //允许中断 apiRetStatus = CyU3PGpioSetSimpleConfig(45, &gpioCo nfig);如此这般配置了45脚。 接下来,要配置21脚,因为21脚比较特殊本来是用于GPIF的CTRL4的。现在要使用它就要重载一下: 这样的IO脚是不可以象在主程序中哪样,将它直接设为输出的,而是要先重载。 同样,看输出脚是如何定义的 gpioConfig.outValue = CyFalse; ///低电平 gpioConfig.driveLowEn = CyTrue; //允许低输出 gpioConfig.driveHighEn = CyTrue; ///允许高输出 gpioConfig.inputEn = CyFalse; //方向设为输出,(假的输入就是输出) gpioConfig.intrMode = CY_U3P_GPIO_NO_INTR; //不用中断 再看一下回调函数,如何实现它的: 当引脚有跳变时,这个函数被调用。首先,它得到引脚的值。这个回调函数是带参数的。当它发生时,会带过来一个参数。表明是哪一个引脚触发了这个事件。这在库函数中可能已经处理了,提供给用户程序就不用麻烦再去看原因了。我想可能有一个机制,即有一个中断状态寄存器,表示是哪一个引脚变化了。 在这里调用了一个函数:CyU3PGpioGetValue(gpioId,&gpioValue); 注意到这个值是一个BOOL型的。 然后根据情况来设置事件: CyU3PEventSet(&glFxGpioAppEvent,CY_FX_GPIOAPP_GPIO_HIGH_EVENT,CYU3P_EVENT_OR); 我们看,其中有要设置的事件指针,有什么事件,以什么方式设置,它是以OR的方式设置的。这个OR表示的是将这个第2个参数与当前的事件标志进行或。显然,如果相或的话,则事件标志将被置1,而如果与则完全不同,它没效果。 (在得到事件中,有一个AND表示全部标志都符合才生成事件,所以也是用OR的,不然,不可能全部符合的,永远不会发生事件了,因为不可能既变高又变低的) 至此整个程序解读完了。在这个例子中,似乎没用到USB有关的部分。 7 有了这个基础后,再看一下原来syncFifo的文档。方法同样从后向前看。 先看main() 1 main基本上没有什么变化,除了不用配置45脚之外。 2再看应用程序启动部分,只创建了一个线程。 3再看在这个线程中做什么事,事实上,它就是每秒钟打一些串口信息,接收了多少包,发送了多少包。不过在开始时,它增加了2个初始化设置,第一个初始化设置设置了串口,第2个初始化,是一个非常关键的初始化,它设置了GPIF口和USB接口。 pibClock.clkDiv = 2; ///除2 pibClock.clkSrc = CY_U3P_SYS_CLK; //用系统时钟 pibClock.isHalfDiv = CyFalse; ///半倍数除不用 /* Disable DLL for sync GPIF */ pibClock.isDllEnable = CyFalse; ///DLL不用,为什么不知道, apiRetStatus = CyU3PPibInit(CyTrue, &pibClock); ///PP初始化 //下面这个函数将GPIF的配置初始化一下,这个配置字是预先定制好的,我们不管它内容 apiRetStatus = CyU3PGpifLoad(&Sync_Slave_Fifo_2Bit_CyFxGpifConfig); 再下面就是启动状态机,至此,GPIF设置完成。 后面要启动USB配置了。这个函数的目的是为USB配置一个DMA通道,也就是启动driver. USB的SETUP过程我们不干预,故注册这个SETUP回调函数尽管在程序中也注册了,但是却直接返回一个FALSE,故其实质是交给主程序去处理去。什么事也不做。 USB的事件处理我们就要干预了。所以我们先注册它,然后,在这个注册的回调函数中,进行干预。看这个回调函数,发现在配置事件中进行了处理,另一个是在断开连接及复位时进行了处理。 配置事件中做了如下处理: 先取得USB的速度,由于USB已经启动到配置这一步,故其与PC沟通后速度信息已经知道了,所以它是可以得到的。得到速度的目的是为了想得到DMA的尺寸,即1024字节。也就是buffer的尺寸。 我们在这里,要得到生产者和消费者的端点配置。 但是这个配置如何与PC打交道,我们是不知道的。是在这个库函数中就进行或者是另有一个机制不清楚。 然后要为U TO P 产生一个DMA通道。 U作为生产者,P作为消费者。 dmaCfg.size = size; ///大小 dmaCfg.count = CY_FX_SLFIFO_DMA_BUF_COUNT; ///2个DMA缓冲就可以了 dmaCfg.prodSckId = CY_FX_PRODUCER_USB_SOCKET; ///生产者的ID号 dmaCfg.consSckId = CY_FX_CONSUMER_PPORT_SOCKET; ///消费者的ID号 dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE; ///按字节计算的 /* Enabling the callback for produce event. */ dmaCfg.notification = CY_U3P_DMA_CB_PROD_EVENT; // 产生通知 dmaCfg.cb = CyFxSlFifoUtoPDmaCallback; //回调函数的定义 dmaCfg.prodHeader = 0; //头没用 dmaCfg.prodFooter = 0; //尾没用 dmaCfg.consHeader = 0; // dmaCfg.prodAvailCount = 0; //等到多少个缓冲区空才。。 oP, apiRetStatus = CyU3PDmaChannelCreate (&glChHandleSlFifoUt CY_U3P_DMA_TYPE_MANUAL, &dmaCfg); 8 然后再产生另一个DMA通道。这个通道与上一个基本相同。除了ID号和回调函数不一样之外。 再下去就是设置DMA的传输尺寸,这里都设为0表示无限大。 最后将一个全局变量,即标志置为1。表示现在这个传输已经激活,其实这个标志只在打印中用一下,表示可以开始打印了。还有就是在激活应用时,如果已激活,则直接停止而不是去再激活它。见程序 断开及复位时做了如下处理: 如果上文中的全局变量已设为标志,则停止应用,如果没设,则什么事也不做。停止应用中做的事情是删除DMA通道,重新配置EP1和EP80 注册了配置事件后,下面要将描述符设置一下。因为等一会USB连接好了后就会与PC交互,如果事先不设置好,则没办法做,所以要做在先。此时配置描述符并不表示已经和PC进行交互了,而是等到以后由芯片自动与之交谈。程序就不干预了。 设置超速设备描述符。 VID和PID在此,还有EP0的长度设为2^9=512与下文中有点矛盾啊, 设置高速设备描述符。 同上,EP0的长度设为64字节。 设置全速设备描述符吗,不设了,估计不支持全速设备吧。 设置BOS描述符。这叫二进制设备存储描述符,这个是超速设备才有的(好象1.1中没看到它)。 设置设备量化描述符,例如EP0支持64字节在这里定义的。设备类与子类也在这里有。指明了USB2.0。 设置超速设备配置描述符,这个描述符有点长,因为它要配置端口的情况。它含配置,接口,端点,而端点有两个。一个发送端点,一个接收端点。在接口描述符中就会指出有2个端点。而在端点描述符中会指出它是BULK型的传输,另外还有端点的地址,最大包长即0124超速。这个超速端点的公司描述符没有什么用。而2。0就没有这个公司描述符部分。 设置高速设备配置描述符(略—与超速差不多的) 设置全速设备配置描述符(略) 设置字符串0的描述符----这里主要是语言描述符。 设置字符串1的描述符---这里主要指制造厂名称,例如可以改为renywell 设置字符串2的描述符----这里指的是产品号,例如F3X。我们可以改为camara或任意 最后连接USB物理设备。此时枚举将正式开始进行。PC机将与设备进行交互了。 小结一下,主程序启动一个线程后,在这个线程的开始的第2个初始化非常之重要,在这个初始化中 做一些非常重要的事情,它先是将GPIF初始化了一下,并启动了状态机。然后将USB的回调函数设置了一下,SETUP注册了,但是没有什么用,配置设置注册了,起了一个很大的作用就是调用了CyFxSlFifoApplnStart。在这个函数中配置了端点,创建了DMA通道(回调也就注册了)。设置通道为无究大尺寸。并将一个全局变量的标志置为1。配置注册后,它开始对USB进行配置。具体将各个描述符设置好。其中第一个设置好的就是设备描述符,在这里PID和VID都在这个描述符中,正因为它PC才能找到驱动程序。在将所有的描述符都设置好了后,就连接USB等着PC机其交互了。 与 交互好了后,PC机就会发数据下来,UtoP这个DMA通道就会被启动。当一个缓冲区满了后就会有一个回调函数被调用。而这个回调函数就是在创建DMA通道时被注册的。于是回调函数中就要处理来的数据。它要将来的数据送到P端口去。就是UtoP 在这个回调函数中做的主要事情就是: Status=CyU3PDmaChannelCommitBuffer(chHandle,input->buffer_p.count,0); 这个函数有点困难。它要把count个字节送到目的地去,按照注释,这是一个生产者事件通知回调函数,这个通知将在每一个缓冲区收到数据后被发出。这个缓冲区将不会被送到目的地除非这个缓冲区被明显地被committed. 这个调用将失败如果总线复位,USB断开或者有一个应用错误时。 我们再看定义时的注释:这个函数通常用于手动DMA通道中。这个函数发送当前DMA描述符中的缓冲 9 区至消费者。它在CY_U3P_DMA_TYPE_MANUAL_IN通道中是无效的。这个数据的数量必须是严格的需要发送给消费者socket的数量。另外这个API也用于AUTO通道为某一特殊的目的。它仅仅当消费者socket处于挂起状态时被调用。在其它所有的情况下,它将返回一个错误标志。这个API也用于承诺当前的消费者缓冲区在部分BUF挂起状态下-----SUSP_CONS_PARTIAL_BUF.. 注意到的是这个回调函数传过来一个 *input变量。而这个结构中含有buffer的地址,实际数据个数,buffer的大小,例如可以很大,我们这里是1024。和状态。这4个参量。我们当前仅用了一个参量。下次调试时也许可以看一下这个buff中到底是些什么,是纯数据呢还是有什么长度之类的参量。好象记得buffer中含一个长度似的。 于是整个代码基本上看清了。 下面我们再看一个代码试试看,想找一个代码,让PC从一个端口发数据过来,然后由下位机将发下来的包全部求反后再发回去。在PC上,将编一个小程序,向某一端口发送1024个字节,然后从另一个端口读回来。看下位机是否有这个例程呢, 还真有这些例程。我们先看一个手动的。即回到PC的数据后,由手动将这些数据再送回去。再看一个自动的,即完全不用程序干预,将收到的自动送到PC上去。 如果我想将数据都求一下反该怎么办呢,好象DMA中也有这方面的不过看完这两个例程再说。好象讲到可以修改里面的数据再送回去的。这个可能是DMA的什么的。故在看例子之前,我们再看详细一下DMA方面的章节。 DMA定义了下列概念:socket,buffers and descriptors. 缓冲,描述符,和socket. 一个抽象的软件提供使得编程者无需太多关注下面的细节。抽象来看,DMA能在两个块中提供一个数据通道。 一个数据通道首先要定义。 一半是生产者块,另一半是消费者块。 生产者和消费者可以是:USB端点,GPIF II,SPI,UART等串口,MEMERY。 需要的缓冲数量必须指定。 DMA的类型必须定义以便于寻址公共数据传输的情形。 自动通道。 自动通道是数据流在生产者和消费者之间可以不间断地传输当这个通道建立起来并开始后。 在运行时,没有软件需要干预。软件仅仅负责为通道建立需要的资源,建立连接。当做好这些后,数据可以继续在这个通道上流动直到它停止或释放。 两种自动通道方式是支持 一种是自动通道一种是自动通道带提示的。 先看自动通道。DMA_TYPE_AUTO 这是完全自动的通道,它由一个生产者socket,一个消费者socket和一个预先定义的缓冲区数量来定义,每一个都是用户可编程的。 缓冲区是等尺寸的。缓冲区的数量由通道产生时指定好了。在内部缓冲区是循环链接由一个描述符链。 带提示的有一点点区别,仅仅的改变是在一个buffer被传送好后,一个事件被传给应用程序。缓冲区指针和数据大小传给了应用程序。当数据需要检查时这是有用的。例如想知道有多少数据被收集这类统计信息。 但要注意的是: A真正的数据流是不会被阻碍的。DMA继续传输而不会中断。 B这个通知并不能用于去修改DMA缓冲区的内容。 一对多自动通道 多对一自动通道 10 手动通道 DMA_TYPE_MANUAL 这是需要CPU干预的。内部,这个通道有两个分开的描述列表。一个为生产者socket.另一个为消费者socket。在通道产生时,用户程序必须指明缓冲区的数量并注册一个回调函数。 当通道进行操作时,注册的回调函数被激活在一个数据缓冲被填充被生产者。在这个回调函数中,用户程序可以: 修改数据包的内容(数据包的大小不能变) 承担这个数据包,触发一个发送传输。 插入一个新的客制化的数据包进入数据流中。 丢弃当前的包而不发给消费者。 加一个固定大小的头或尾给接收包。这个头或尾的大小在创建时就指定好的。 从接收的包中移动一个固定大小的头或尾。 手动在通道 Manual IN Channel DMA_TYPE_MANUAL_IN这个选择是一个特殊的通道,此时CPU的固件是一个消费者。一个回调函数必须注册在通道创建时。当一个指定的缓冲区数量被传输出就会调用这个回调函数。 Manual Out Channel DMA_TYPE_MANUAL_OUTP 这个通道是一个特殊的通道,此时CPU固件是数据的生产者。用户应用需要得到激活的数据缓冲,填充的缓冲然后承担它。 此外还有手动的一对多,和手动的多对一通道。 DMA需要的缓冲由通道函数处理。缓冲的数量(缓冲的个数及每个缓冲的大小)必须在通道建立时指定。如果通道产生成功,请求的缓冲将被成功地在内存中分配。它是从block pool中分配的。用户程序并不需要分配任何缓冲区。 有以下DMA的API函数: 创建和清除DMA通道 在DMA通道上建立数据传输。 挂起或恢复DMA通道。 放弃和复位DMA通道 接收数据指指定的缓冲(重载模式) 传送数据从指定的缓冲区(重载模式) 等当前的传输完成 取得数据缓冲区从DMA通道 承担数据缓冲区去传输 丢弃缓冲区从DMA通道。 有了以上基础上,我们再来看程序就方便多了: 这是一个手动传输的例子 Main()函数 唯一有关的是在初始化IO口时,用到串口。 应用定义的函数:生成一个进程。没有多少要讲的。 线程函数中: 首先,初始化串口,这里是UART。没有新的不同。 再就是CyFxBulkLpApplnInit(); 可能会在这里有很多事要做。 看来来是起动USB功能。 先是建立USB的DMA通道,这个是API函数过程不知道的。 然后是注册两个回调函数,一个是USB建立过程中的回调,一个是配置设置的回调 11 在配置回调中做了一些事主要是启动传输过程CyFxBulkLpApplnStart() 在这个过程中先得到USB的速率,然后将生产者端点使能(配置好) 再将消费者配置好。再创建一个DMA通道,在创建这个通道时,有下列参数要设置的 DMA的尺寸。 DMA的缓冲区个数,用了8个缓冲区 DMA生产者ID DMA的消费者ID DMA传输模式 以字节为计算 DMA通知的事件:这是DMA的缓冲生产出来时发通知 通知的回调函数。 其它都为0 然后再将一个全局变量glIsApplnActive置为1。---注意到兰色部分都是在设置配置时回调中做的事。 然后,这个由线程函数的再继续做下去。应该配置USB描述符了吧。确实如此, 最后做的就是连接USB了。结束。 然后我们要看的恐怕只有回调函数要关注一下子了。很简单如下 Status=CyU3PDmaChannelCommitBuffer(chHandle,input->buffer_p.count,0); glDMARxCount++; 有一个问题是生产者是从PC机向USB的端口发一批数据过来。而DMA通道却只涉及到 CY_FX_EP_PRODUCER_SOCKET 哪么,这个DMA通道怎样将USB的OUT端口1与这个生产者联系起来的呢, 看下面的注释看是否明白一些: Socket ID有两个部分组成,一个是IP号,一个是 socket号。每一个周边器件(peripheral)有一个固定的ID。例如LPP的ID号是0而PIB是1。USB输出是3,而USB输入是4。所以,输出应该是301而输入应该是401。但是看了后发现生产者是401 而消费者是301。比较奇怪。按手册上的说法,生产者是ingress.而消费者是engress.所以USB的OUT端点是生产者,而IN端点是消费者。Ingress它是以4开头的,因此,这个USB的输出端点就是401。而输入IN端点则是301。我们的回调函数是在生产者生产出一个buffer时,就启用了这个回调函数。 再看自动方式是如何运行的, 数据从PC机经OUT端点到生产者中。OUT端点实际上是设备端的接收端点。它是生产者。 然后自动的返回到IN端点,它是消费者。Ingress代表生产者。而Egress代表消费者。 OUT IN Ingress Egress 生产者 消费者 端点01 端点81 Socket 401 Socket 301 看它开启的一个线程中做的工作就是将USB配置好。然后连接USB。当然在这个函数中早些时候,它将USB的事件设置回调函数加进去。 事件回调函数中将创建DMA通道之类的事情。主要区别在这里。估计回调函数也没有了因为是自动的。 这个回调函数中调用的函数是CyFxBulkLpAppInStart(). 12 首先仍是通过USB的速度取得缓冲区的长度。即1024。 然后要配置生产者的端点,它就是一个OUT端点1。再配置消费者端点,它的端点就是IN端点0X81 最重要的时刻到来了,它就是创建DMA通道,这点与以前是不同的。 dmaCfg.size = size; dmaCfg.count = CY_FX_BULKLP_DMA_BUF_COUNT; //=8 dmaCfg.prodSckId = CY_FX_EP_PRODUCER_SOCKET; //401 dmaCfg.consSckId = CY_FX_EP_CONSUMER_SOCKET; //301 dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE; dmaCfg.notification = 0; ///最大的区别 没有事件通知 dmaCfg.cb = NULL; ///最大的区别 没有回调 dmaCfg.prodHeader = 0; dmaCfg.prodFooter = 0; dmaCfg.consHeader = 0; dmaCfg.prodAvailCount = 0; 于是整个程序就读完了。 再读一个例子关于用DMA_MANUAL_IN DMA_MANUAL_OUT的例子。 这个例子说明USB端点数据源和数据沉机制用bulk stream。 这个例子用了两个USB的端点。 输出端点作为生产者,作为主机数据的沉---接收者。 一个输入端点作为数据的消费者,作为主机的源。 ---发送者。 每一个端点包含CY_FX_BULK_MAX_STREAMS这么多个的bulk streams的数目。对每一个streams,一个DMA_MANUAL_IN通道被创建在生产者USB sockets和CPU之间。一个DMA_MANUAL_OUT通道被创建在CPU和消费者sockets之间。 在IN的DMA缓冲区能道中,数据从主机接收通过生者端点。CPU被通知数据收到了(回调函数)。然后CPU丢失这个缓冲,这个引向一个沉的机制。 一个常数类型的数据被连续地装进每一个输出通道的DMA缓冲区,只要当缓冲区是空。CPU然后发一个允许DMA数据传输给消费者端点,通过它向主机发送数据。这引起了一个源机制。 Bulk streams这个方法仅仅在超速情况下是有效的。对别的速度,应用程序可以用单个的DMA_MANUAL_IN and DMA_MANUAL_OUT分别用于数据的沉和数据的源。 Stream ID Stream id 1 被映射到socket 1. stream id 2 被映射到socket 2….. 此程序与前面程序的区别在于两个函数中,一个是创建DMA通道时不同,另一个是DMA的回调函数不同。先看创建通道时的情况,如果是超速,我们就用一个 glIsStreams=1 而这个在端口设置时,就看它是否是超速,如果是,则将这个流标志置为CY_FX_MAX_STREAMS 在下面的过程中,每一个流,都对应着每一个生产者的ID。即生产者似有好几个。例如流有8个,则生产者就有8个。就要产生8个DMA通道。而这8个DMA通道,其消费者ID却是一样的。其回调函数也是一样的,哪在回调函数中怎么知道是哪一个流引起的回调呢,暂不知。 接下来,要将流与socket对应起来。 If(stream!=0) apiRetStatus = CyU3PUshMapStream(CY_FX_EP_PRODUCER,CyU3PDmaGetSckNum(dmaCfg.prodSckId),(stream+1)); 这样,每一个生产者的ID与socket的ID号就一一对应起来的。 13 可以,为什么一定要从stream!=0开始。等于0时该怎么办呀,还有这个函数有三个参数,第一个是0x01,第2个就是生产者的socket ID号,第3个就是流号。 最后要做的工作是将要发给PC机的数据用到的缓冲区全部填满,例如填成0x55aa之类。看它是如何取得缓冲区的地址的。 For (index=0;index<8;index++) ///每一个DMA通道,用到8个缓冲区,如果共有4个通道,则将用到32个缓冲区。在创造每个通道时,都会对应一个流号。即每一个流有32个缓冲区。所以每一个通道时都要将8个缓冲区置一下。 CyU3PDmaChannelGetBuffer(&glChHandleBulkSrc[stream],&buf_p,CYU3P_NO_WAIT); 第一个参数指的是DMA的通道号。 CyU3PMemSet(buf_p.buffer,CY_FX_BULKSTREAMS_PATTERN,buf_p.size); 再调用这个 CyU3PDmaChannelCommitBuffer(&glChHandleBulkSrc[stream],buf_p.size,0); 这个函数就是先将这个数据发到消费者上去再说。共发了8个缓冲区。 如果空了怎么办,它是如何管理空和满的, 看回调函数: 回调函数根据是生产者发出的通知还是消费者发出通知来进行处理。 如果是生产者通知,一个包已经来了,则调用抛弃这个包。 CyU3PDmaChannelDiscardBuffer(chHandle); 不过我们还有别的方法去处理吗,比如收下来,查API有一个函数GetBuffer如下面的合适但是未深入去看。 如果是消费者通知,则 CyU3PDmaChannelGetBuffer(chHandle,&buf_p,CYU3P_NO_WAIT); 先得到缓冲区的数据 对于消费者,实际上是无需得到缓冲区的数据的,这个函数的意义在于返回一个指针,可以让CPU可以填充它。不过在开始时我们已经填充完毕,故就不做什么事了。但是这个函数还是必须调用的。也许这个函数还附带了一些其它功能吧。 CyU3PDmaChannelcommitBuffer(chHandle,buf_p.size,0); 这是表示发送就可以了, 下面再看一下这个FLASH编程的例子: 上位机通过某种方式,将数据下发到下位机,下位机然后编程IIC或SPI接口。以后就可以选择这些作为BOOT而不是从主机下载方式。主机下载可以作为调试应用。 先看main()函数。 将配置使能IIC SPI UART。其它没有什么不同。 再看线程函数:线程函数中未作任何事务。也没有初始化。仅仅打开一个线程。 再看线程函数中的应用:首先将UART初始化一下。然后将FlashProgInit() 然后就每隔一秒打印一些版本号。这个FlashProgInit()看它做些什么, 先初始化IIC,带有一个参数0x40. 这个参数表示页长度 40表示的是64。它是DMA的长度。 它先设IIC的时钟频率为100KHz。这是一个库函数无法看细节。 它的数据传输是通过DMA来的,故先它对IIC进行配置。 配置中设好100KHz频率,再就是总线时间无限长,再是DMA的超时无限长,再就是将其设为DMA方式。 这样调用CyU3PI2cSetConfig(&CyU3PI2cConfig,NULL) 它也是一个库函数。第二个参数为NULL表示这是一个block挂起模式,它一直等完成而不用回调函数 接下来产生一个DMA调用。这里没有缓冲区需要,只是在重载模式。所以: dmaConfig.size = pageLen; 14 damConfig.count = 0; dma.dmaMode = CY_U3P_DMA_MODE_BYTE; dmaConfig.prodHeader = 0; dmaConfig.prodFooter = 0; dmaConfig.consHeader = 0; dmaConfig.notification = 0; dmaConfig.cb = NULL; dmaConfig.prodSckId = CY_U3P_CPU_SOCKET_PROD; dmaConfig.consSckId = CY_U3P_LPP_SOCKET_I2C_CONS; status = CyU3PDmaChannelCreate(&glI2cTxHandle,CY_U3PDMA_TYPE_MANUAL_OUT,&DMAcONFIG); 同时将一个全局变量glI2cPageSize = pageLen; 这个变量有用的。 接下来就是初始化SPI了。与IIC基本上是完全一样的。不过SPI的页长度设定为256。然而SPI的参量有些不同,主要是增加了时钟的POL极性,SSNPOL片选极性,高位优先,相位PHA=1,时钟频率为8M。以字节长度读写(8)。 接下来是启动USB功能。这个API函数目的是使能USP并为其设置DMA。 接下来与上面解读的一样,是设置设置过程的回调函数和事件过程的回调函数。这个设置阶段的回调函数 比较复杂。事件回调函数比较简单,就是在复位或断线时将DMA通道复位。 所以重点看设置时的回调函数: 设置函数带有两个参数,setupdat0,setupdat1. 先将这个包解开: Attr = uint8 setupdat0&0xff; //属性 Rqt = uint8 (setupdat0&0xff00)>>8 //请求 Value =uint16 setupdat0>>16; //值 Index =uint16 setupdat1&0xffff; //索引 Length = uint16 setupdat1>>16; //长度 if( sttr&CY_U3P_USB_TYPE_MASK!=CY_U3P_USB_VENDOR_RAT) return FALSE; ///如果不是VENDOR请求,则返回假—意连云港着由驱动缺省处理 ////所以证明这是一个vendor设置请求 Switch(rqt) { case CY_FX_RQT_ID_CHECK: ///如果是看软件版本号,则直接写回去。 CyU3PUsbSendEP0Data(8,(uint8*)glFirmareID); break; case CY_FX_RQT_I2C_EEPROM_WRITE: i2cAddr = 0xA0|(value&0x0007)<<1; ///芯片地址放在value中 status = CyU3UsbGetEP0Data(length,glEp0Buffer,NULL); ///从EP0得到很长的数据 case CY_FX_RQT_I2C_EEPROM_READ: i2cAddr = 0xA0 |(value&0x07)<<1; CyU3PMemSet(glEp0Buffer,0,sizeof(glEp0Buffer));//将接收缓冲全部清零 Status = CyFxFlashProgI2cTransfer(index,i2cAddr,length,glEp0Buffer,CyTrue); 索引,地址,长度缓冲区 这是一个小复杂的程序,其中最未一个参数指的是读还是写如果读则为真。 到底有多少页,如果不能整除,则页数要加1。因为只能一页一页的读。 打印一个信息,我们将开始访问地址为,,的设备,字节地址为,字节数为,总共,,页 15 尽管用不到DMA的缓冲,但是我们还是用到了这个结构: CyU3PDmaBuffer_t 并将它内部的*buffer指向我们用到的4096字节的缓冲区。 Buffer_p.buffer = buffer; While(pageCount!=0) If(isRead) ////如果是读操作 { preamble.length = 4; preamble.buffer[0] = devAddr; preamble.buffer[1] = byteAddress>>8; preamble.buffer[2] = byteAddress&0xff; preamble.buffer[3] = devAddr|0x01; preamble.ctrlMask = 0x0004; buf_p.size =glI2cPageSize; buf_p.count = glI2cPageSize; status = CyU3PI2cSendCommand(&preamble,glI2cPageSize,isRead); status = CyU3PDmaChannelSetupRecvBuffer(&glI2cRxHandle,&buf_p); status = CyU3PDmaChannelWaitForCompletion(&glI2cRxHandle,CY_FX_FLASH_PROG_TIMEOUT); else{ ///这是写的情形,就不写了 。。。。。。。。 } byteAddress +=glI2cPageSize; buf_p.buffer +=glI2cPageSize; gageCount --; Sleep(10); } 粗略地看,这个例子是在SETUP阶段与下位机进行一些对话,从而得到I2C和SPI的读写,擦除动作。 现在我们要做的是接收上位机的指定,进行设置。然后上位机就通过一个端口读图象数据,而下位机则从 GPIF-II端口往某一端口通过DMA读数据。作一片FPGA,使之以某种方式往GPIF中写数据。通过设一个DMA通道,往IN端口发送数据。 这与同步GPIF 的例子有点象,但是发下来的数据,下位机CPU要读取的。读总是通过DMA进行的。只 是对于CPU的读,采用的方式可能就不是自动而是manual_in。 再看一个SPI读写的例子,它是主程序命令从SPI中读写一些数据。 SPI传输子程序看一下: 页地址,字节计数,缓冲区,读写标志 故读写总是从页地址开始的 因为只能一页一页的读或写, CyFxSpiTransfer(uint16_t,pageAddress,uint16_t bytecount,uint8_t *buffer,Cyboot_t isRead) { CyU3PDmaBuffer_t buf_p; Uint8_t location[4]; Uint32_t byteAddress=0; Uint16_t pageCount=byteCount/glSpiPageSize; CyU3PReturnStatus_t status=CY_U3P_SUCESS; If(byteCount ==0 Return CY_U3P_SUCESS; 16 If(byteCount%glSpiPageSize!=0) pageCount++; ///如果除不尽,则按多一页来读写 buf_p.buffer = buffer; byptAdddress = pageAddress*glSpiPageSize; //页地址*尺寸=实际地址 while(pageCount!=0) { location[1]=byteAddress>>16; Location[2]=byteAddress>>8; Location[3]=byteaddress; If(isRead) { location[0]=0x03; ///这是读命令 Buf_p.size=glSpiPageSize; Buf_p.count=glSpiPageSize; CyFxSpiWatiForStatus(); CyU3PSpiSetSsnLine(False); CyU3SpiSetBlockXfer(0,glSpiPageSize);///设置块传输尺寸 /// 这个子程序是允许DMA。两个参数一个是TX,一个是RX的个数。 CyU3PDmaChannelSetupRecvBuffer(&glSpiRxHandle,&buf_p); CyU3PDmaChannelWaitForComplete()&glSpiRxHandle,CY_FX_USB_SPI_TIMEOUT); CyU3PSpiSetSsnLine(TRUE); CyU3PSpiDisableBlockXer(FALSE,TRUE); } //以上只是一部分,写未进去。 } 在USB的SETUP中回调中,有两个参数,一个是setupdat0,一个是setupdat1. 这两个参数是类,VENDOR调用时的回调函数。 如果是请求ID号,则直接往EP0中写入一个字符串。 如果是请求FLASH写,则从EP0中得到相应的数组。后写到SPI中 如果是请求读,则从SPI中读出指定长度的数,再送到EP0中去。 如果是请求擦除或点名。则如果点名,就送状态过去,如果是擦除,就关一个ACK包给USB即可。 哪么就要了解一下类/VENDOR请求的格式是什么即可。 我们现在要将EP1 OUT作为命令口。EP1的IN作为数据流的口。 上位机发一个命令,下位机就将一些数据发上来。怎么做,用DMA方式。 下面将两个都由CPU来进行响应,也就是DMA用一个重载模式来做。 上位机通过EP1的OUT发来一个hell.下位机打出来,然后通过EP1的IN回复一个OK。 测试一下。 我们要选什么例程上改为好呢, 看一下这个DMABULKMANUALINOUT可能比较合适,不过它不是用回调函数的,如果要参考用回调歪 数的,可以参考其它例程。 先不管这些,我们看CPU如何得到上位机发下来的命令: 先看在USB设置配置时中启动的一个初始化: CyFxBulkLpApplnStart(void) { uint16_t size CyU3PEpConfig_t epCfg; 17 CyU3PDmaChannelConfig_t dmaCfg; CyU3PReturnStatus_t apiRetStatus = CY_U3P_SECCESS; CyU3PUSBSpeed_t usbSpeed =CyU3PUsbGetSpeed(); Switch(usbSpeed) { 以下是根据全速,超速,高速分别得到缓冲区的大小 } 然后进入最重要的,难以理解的环节---下面实际上先将端点1配置一下 CyU3PMemSet((uint8*)&epCfg,0,sizeof(epCfg)); // epCfg.enable =CyTrue; epCfg.epType = CY_U3p_USB_EP_BULK; epCfg.burstLen = 1; epCfg.streams= 0; epCfg.pcktSize=size; ////生产者端点配置 01—表示OUT端点 apiRetStatus = CyU3PSetEpConfig(CY_FX_EP_PRODUCER,&epCfg); ///此时并未指明它是生产者还是消费者。 81—表示它是IN端点 apiRetStatus = CyU3PSetEpConfig(CY_FX_EP_CONSUMER,&epCfg); 以上两个端点其参数都是一样的。 然后清空这两个端点的缓冲区。。。。略去 下面最重要的时刻到来了,就是产生两个DMA通道。仔细看每一个参数 dmaCfg.size = size; dmaCfg.count=CY_FX_BULKLP_DMA_BUF_COUNT; //2 dmaCfg.prodSckId = CY_FX_EP_PRODUCER_SOCKET; //系统规定的是401 //需注意的是这个socket是第一个生产者socket,而系统规定ingress的socket其开头为4,而所谓的ingress指的是数据流入者,这是相对于buffer来说的。生产者就是ingress. 而从USB的out端口,往缓冲区写,这就是生产者,而它的socket就是ingress. dmaCfg.consSckId =CY_U3P_CPU_SOCKET_CONS; ///这个是CPU作为消费者,从缓冲中得到数据,它定义成0x3F00。 dmaCfg.dmaMode = CY_U3P_DMA_MODE_BYTE; dmaCfg.notification=0; dmaCfg.cb=NULL; //其余的都为0 最后创建一个DMA通道: apiRetStatus=CyU3PDmaChannelCreate(&glChHanleBulkLpIn,CY_U3P_DMA_TYPE_MANUAL_IN,&dmaCfg); ///这相通道中有三个参数,一个是表示其句柄,一个是表示类型,一个是表示配置。 注意到这个句柄是一个全局变量,这是一个读入的句柄,也就是从USB的输出端点向CPU的名柄,因此它是生产者,也因此它是ingress。 再来看DMA_MANUAL_OUT通道。稍微修改一下dmaCfg的两个参数,就可以了 dmaCfg.prdSckId = CY_U3P_CPU_Socket_Prod; //0x3f01 dmaCfg.consSckId=CY_FX_EP_CONSUMER_SOCKET; 从以上看出生产者是CPU 而消费者是第一个消费者socket,它对应的是0x301. 哪么这个301与81是如何对应起来的呢,估计我们有81~8F共15个口。而对应的有301~30F。 因此可以对应上。 接下来就要将DMA的尺寸设置一下了。这个尺寸指的是流过这个DMA的总的流量是否有限制,一般我们 18 都用无限制来表示。即0表示无限地传输。 最后将一个全局性的标志置为1结束这个在USB事件时初始化时创建的子程序。 glIsApplnActive = True; ///这个标志往往用于打印用途。 既然设好了DMA,接下来应该在线程中查询数据到了否,如果到了,就通过IN通道发送回PC机上。 也可以用回调函数,回调函数记得好象有一个例子。还有点迷湖状态中。 另外一个最重要的是CPU如何来处理这些事务的一个例子了: CPU在一个线程中,将不断地询问数据是否到了,如果到了就发送看它是否是这样做的呢, CyU3PdmaBuffer_t inBuf_p,outbuf_p; ///输入缓冲和输出缓冲,哪个是输入,生产者吧看看是, 在这个线程中,先将串口初始化了一下,然后将USB部分设置了一下并在这个函数的最后打开了USB。使上位机会对下位机进行配置,在配置的过程中,将会设置好DMA的生产者和消费者,最后将一个全局变量设为真。而这个线程将进入一个循环,在这个循环中,将检查这个全局变量,如果为真就往下走,如果不为真就空转去SLEEP(1000); 如果已为真做如下处理: Status =CyU2PDmaChannelGetBuffer(&glChHandleBulkLpIn,&inbuf_p,CYU3p_WAIT_FOREVER);) 这个函数有下列说明:对于DMA_MANUAL 或者DMA_manual_in通道,这个函数返回的是一个从生者者socket返回的数据缓冲区的地址。在MANUAL_OUT类型的通道中,这个函数返回的是指向CPU要填充数据的缓冲区的指针。所以这个指针和计数值并不包含偏移。 例如,如果有一个生产者的头偏移是12个字节,这个从生产者头开始的有效的数据可能是从buffer_p->buffer[-12]。 因为没有更多的分配空间去访问,所以要当心。 估计这个函数是一个阻塞函数,因为有一个等FOREVER。 然后再等接收区的缓冲是否有空 CyU3PDmaChannelGetBuffer(&glChHandleBulkLpOut,&outBuf_p,CYU3P_WAIT_FOREVER); 这个函数其实是等有没有空的消费者socket缓冲空出来了。 如果缓冲区足够多,且传输速度足够快,则什么事也不用等。 真正的处理在这里了。 CyU3PmemCopy(outbuf_p.buffer,inBuf_p.buffer,inBuf_p.count); 这里我们可以将发过来的包修改为OK发出去了。不过由于DMA的长度好象是规定好了的,是1024字节。如果字节少了怎么办,例如我只发几个字节下去,返回两个字节,是不是可以另外处理。上位机是否可以任意发短的字节。这个留待PC机的人员去管。 CyU3PDmaChannelDiscardBuffer(&glChHandleBulkLpIn); 以上这个的含义是将缓冲区空出来,以便于下一个包过来。共有8个缓冲区 CyU3PDmaChannelCommitBuffer(&glChHandleBulkLpOut,inBuf_p.count,0); //以上指的是将某一个缓冲区的字节发送回去。发回去一个短包不行吗,可试一试。 至此,整个程序解读完成。还要搞清一个问题如何用回调函数而不是查询方式来做呢,有些函数是不能在回调函数中调用的。 在回调函数中,还需要将接收到的数据丢失吗,再回过头去看一下一个例子。这个例子是LOOP MANUAL DMA。这个例子以前看过再重温一遍。 在这个生产者的回调函数中,有下列调用: if(type==CY_U3P_DMA_CB_PROD_EVENT) CyU3PDmaChanelCommitBuffer(chHandle,input->buffer_p.count,0); 上面有一个注释:这是一个生产者事件通知。告诉CPU每接到一个buffer.这个缓冲如果你不处理它,它将不会发送到消费者手中去。所以要调用这个函数处理。在这个例子中,只有一个DMA通道而不是2个。不 19 象上面的例子,它有两个DMA通道。因为不需要CPU的处理,所以这些数据是自动的。 下面再看一个例子,与我们的需求比较贴合的。就是UsbBulkSourceSink这个例子用的是回调函数。 我们直接来看回调函数: CyFxBulkSrcSinkDmaCallback(CyU3PDmaChannel *chHandle, CyU3PDmaCbType_t type, CyU3PDmaCBInput_t *input) CyU3PDmaBuffer_t buf_p; CyU3PReturnStatus_t status=CY_U3P_SUCCESS; If(type == CY_U3P_DMA_CB_PROD_EVENT){ CyU3PDmaChannelDiscardBuffer(chHandle); glDMARxCount++;} if(type==CY_U3P_DMA_CB_CONS_EVENT){ CyU3PDmaChannelGetBuffer(chHandle,&buf_p,CY3P_NO_WAIT); CyU3PDmaChannelCommitBuffer(chandler,buf_p.size,0); glDMATxCount++; } 当收到数据时,就将它丢弃,而当数据缓冲区空时,就将预先定义好的数据发出去。注意到这里有两个事件,一个是如果收到数据事件,一个是消费事件。什么时候 报告 软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载 消费事件呢, 如果这两个事件没有任何关系,则什么时候会启动一个消费发送请求呢,请求后是否会不停地狂发一气呢, (结论找到了,因为它只是生产者调用。只有生产者来一个才调用一次,所以不会狂发一气。另外带来一个个问题是生产者的回调函数,当消费者有这个事件发生时,也会引发生产者的回调函数吗,以上是错误的事实上消费者也是用同样的回调函数。) 而在初始化时,就发了一个。不过上位机可以控制这个过程,因为上位机只在发一个下去后,才读一个回来。这样下位机就不会乱发一气了。因为上位机不读,则它的发送将被挂起。 接收了是否还要调用丢弃这个函数呢,上面一个例子中是肯定的。如果用手动方式,看来都要调用这个函数。 到目前为此,在回调函数中可以看到可以调用得到DMA缓冲区函数,丢弃这个收到的缓冲区,还有就是允许发送,这三个函数可以在回调中应用。 下面看一个例子,就是用同步方式来读写而不是BULK。然后有一个SOURCE和一个SINK。 与BULK方式没有多少不同,除了模式设置不一样之外。所以很快看完了。 再看一下寄存器模式是个什么东东。先看最简单的I2C寄存器模式。估计它不是用DMA方式来进行的。 首先在I2C初始化时,其i2cConfig.isDma=FALSE; 这就表示不用DMA了。 再看I2C的传输函数,与DMA方式应有所不同好象简单了许多。 两个程序,基它地方都是一样的,就是读数据时不一样,一个很简单用寄存 方式,一个比较复杂一点,用DMA方式来读。从中间我们可以看出一些不同比较有意思。 不同之处在于先在初始化时,DMA要初始化两个DMA通道,一个是CPU写向IIC。 一个是从IIC中读。在这两个通道中,写通道,CPU是生产者,而IIC是消费者。 读通道中,CPU是消费者,而IIC是生产者。注意到IIC的DMA缓冲区的长度是64字节。 即每次都是按64字节来作DMA的。这与1024,512似无关系。 20 在DMA模式中,三个函数将数据读出来:先发一个命令下去: CyU3PI2cSendCommand(&preamble,glI2cPageSize,isRead); CyU3PDmaChannelSetupRecvBuffer(&glI2cRxHandle,&buf_p); 后面是一个等状态完成。 而这个过程与USB下发的这个数据长度是没有关系的,因为下发是不是通过端点1下发的。而是通过端点0下发的。至于如何通过端点0下发。这个还要回去将书的的USB的类/VENCOR看一遍,了解一下其在设置过程中的下发的机制。 21 22
本文档为【USB3&#46;0设计资源cypress芯片程序解读2】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_998870
暂无简介~
格式:doc
大小:86KB
软件:Word
页数:0
分类:企业经营
上传时间:2017-09-20
浏览量:28