湿疹病因
VCP 例程用于数据传输时丢失数据的处理
问题: 问题:
ST 的 USB 固件库 某客户工程师在其产品的设计中,使用了 STM32F205VET6。据其工程师讲述:他使用 STM32F205VET6 中的 VCP 例程来实现虚拟串口的功能,但是他发现虚拟串口一次输出的数据(从串口到上位机)如果 超过 2Kbytes 就会造成数据丢失,只输出尾部的 2Kbytes。客户工程师检查代码发现 USB 的 FIFO 大小 由宏定义 APP_RX_DATA_SIZE 决定,而 APP_RX_DATA_SIZE 的大小刚好为 2Kbytes。所以他认为此 FIFO 设计太小而造成的,于是他将 FIFO 的大小改成 5Kbyte,不过修改后并不能解决问题。
调研: 调研:
1. 打开“STM32F105/7, STM32F2 and STM32F4 USB on-the-go Host and device library (UM1021)” 库里边的 VCP 例程,位于“...\STM32_USB-HostDevice_Lib_V2.1.0\Project\USB_Device_Examples\VCP”中。对其进行测试,并没有出现所说的问 题,APP_RX_DATA_SIZE 的大小仍然为 2Kbytes,不管传输的数据是 2Kbytes 还是 5Kbytes 甚至是 25Kbytes,完全没有问题。 2. 了解客户程序 UART 所设置的波特率,为 115200,与原 VCP 例程一致。USB 采用的是 Full Speed, 全速 USB 总线的帧周期为 1ms。 3. 在 usbd_conf.h 中可以看
到 APP_RX_DATA_SIZE 的定义在这里,为 2048,它定义了 APP_Rx_Buffer 的大小。APP_Rx_Buffer 其实是一个循环缓冲区,APP_Rx_ptr_in 指明了其数据进来的位置,当 USART 接收到数据时,将数据存储于 APP_Rx_ptr_in 指定的位置;APP_Rx_ptr_out 指明其数据取出 的位置,当 USB 到 FIFO 中取出数据时,起始地址由 APP_Rx_ptr_out 决定。
4. 打开 VCP 项目,观察其程序通信部分。当 USART 每接收到字节时,进入 EVAL_COM_IRQHandler 函数, 调用 VCP_Tx(0,0)函数,将收到的字节存储于 APP_Rx_Buffer[APP_Rx_ptr_in]中,在 APP_Rx_Buffer 中的位置由 APP_Rx_ptr_in 指定。在 VCP_Tx 函数中, if(APP_Rx_ptr_in == APP_RX_DATA_SIZE) { APP_Rx_ptr_in = 0; }
辞职信英文可以看到当 APP_Rx_ptr_in 达到 APP_RX_DATA_SIZE 时,将其置 0,也就是在循环缓冲区中绕了一圈 回到缓冲区起始地址。 5. 再来看 APP_Rx_Buffer 是如何被 USB 取走,并送到上位机的。在 usbd_cdc_core.c 中,我们在 usbd_cdc_SOF 函数中看到: if (FrameCount++ == CDC_IN_FRAME_INTERVAL) { FrameCount = 0; Handle_USBAsynchXfer(pdev); } 可以看到,USB 每 CDC_IN_FRAME_INTERVAL 个帧调用一次 Handle_USBAsynchXfer 到 APP_Rx_Buffer 中去取数据。CDC_IN_FRAME_INTERVAL 同样定义在 usbd_conf.h 中,全速的时候,其值为 5。在定义 的这边,我们可以看到: APP_RX_DATA_SIZE*8/MAX_BAUDARATE*1000 should be > CDC_IN_FRAME_INTERVAL 其目的是在于告诉我们 APP_RX_DATA_SIZE、MAX_BAUDARATE 和
名人摄影CDC_IN_FRAME_INTERVAL 的关系,以 保证 APP_Rx_Buffer 是够用的。 6. 接着看 Handle_USBAsynchXfer 函数,同样,我们可以看到: if (APP_Rx_ptr_out == APP_RX_DATA_SIZE) { APP_Rx_ptr_out = 0; } 也就是当 APP_Rx_ptr_out 达到 APP_RX_DATA_SIZE 时,将其置 0,也就是在循环缓冲区中绕了一圈 回到缓冲区起始地址。 if (APP_Rx_prt_out == APP_Rx_ptr_in) { USB_Tx_State = 0; return; } 当 APP_Rx_prt_out 赶上 APP_Rx_ptr_in 时,证明 Buffer 里边的数据已经发送完毕,返回。再往下 看: if (APP_Rx_ptr_out > APP_Rx_ptr_in) /* rollback */ { APP_Rx_length = APP_RX_DATA_SIZE - APP_Rx_ptr_out; //① } el { APP_Rx_length = APP_Rx_ptr_in - APP_Rx_ptr_out; //② } 第① 种情况为 APP_Rx_ptr_out 比 APP_Rx_ptr_in 大,也就是说 APP_Rx_ptr_in 已经绕了一圈,而 APP_Rx_ptr_out 还没有绕一圈,比如下面情况:
这种情况下:APP_Rx_length 设置为 APP_Rx_ptr_out 当前圈里还剩下数据长度; 第② 种情况为 APP_Rx_ptr_in 比 APP_Rx_ptr_out 大,也就是 APP_Rx_ptr_in 和 APP_Rx_ptr_out 处 于同一圈,于是数据情况比如下面情况:
这种情况下:APP_Rx_length 设置为 APP_Rx_ptr_in 减去 APP_Rx_ptr_out,也就是当前所有数 据长度。 程序的后面就是对 USB 包的设置,然后发送数据。 7. 结合 EVAL_COM_IRQHandler 和 Handle_USBAsynchXfer 函数对 APP_Rx_Buffer 的处理,我们可以发现, 有一种情况在程序中是没
草莓蛋糕图片大全
有做处理的:当 APP_Rx_ptr_in 绕了一圈回来,并追上 APP_Rx_ptr_out 时, APP_Rx_Buffer 的数据已满,这个时候,若 USART 继续接收到数据,APP_Rx_ptr_in 指针继续增长, 就会造成数据溢出,新来的数据冲掉还没被 USB 取走的旧数据。但是,需要注意的一点是,此 USB 库的 VCP 例程是实时的,也就是 USART 收进来,USB 会取走送到上位机。而 USB 将数据送往上位机 的速率是肯定大于 USART 接收数据的速率的。也就是说,这个例程可以保证“当 APP_Rx_ptr_in 绕 了一圈回来,并追上 APP_Rx_ptr_out 时,APP_Rx_Buffer 的数据已满,这个时候,若 USART 继续接 收到数据,APP_Rx_ptr_in 指针继续增长,就会造成数据溢出,新来的数据冲掉还没被 USB 取走的 旧数据。”这种情况不会发生! 之前所分析的: APP_RX_DATA_SIZE*8/MAX_BAUDARATE*1000 should be > CDC_IN_FRAME_INTERVAL 这个要求,正是保证 APP_Rx_Buffer 安全的重要条件。 8. 至此,我们怀疑客户并不是照搬 VCP 例程,而是对其做了修改。拜访客户,了解到确实如此。客户 由于其应用需要,将 VCP 程序拆成两部分,先是用 USART 把所有的数据接收进来,放到 RAM 中,然 后再将数据送到 APP_Rx_Buffer,由 USB 将数据送往上位机。这样,问题就来了,由于客户 USART 先从外部接收到并保存于 RAM 的数据大于 5Kbytes,而 CPU 将 RAM 中的数据搬往 APP_Rx_Buffer 的 速率远大于 USB 将 APP_Rx_Buffer 送往上位机的速率。这样就造成了 APP_Rx_ptr_in 绕了一圈回来 追上 APP_Rx_ptr_out 并造成溢出的情况,甚至可能是 APP_Rx_ptr_in 绕了几圈,而 APP_Rx_ptr_out 却还未开始动,因为 USB 每 CDC_IN_FRAME_INTERVAL*1ms 才送一次数据。 9. 建议客户修改其程序,在将数据从 RAM 中搬往 APP_Rx_Buffer 的时候,不能采用原 VCP 例程中 U相关服务
方便面的做法>cad圆角SART 一样的操作方式,也就是存一个字节到 APP_Rx_Buffer,APP_Rx_ptr_in 指针加 1,并绕圈。 新的程序需要在此基础上判断,当 APP_Rx_ptr_in 指针加 1 后,若等于 APP_Rx_ptr_out,置 “APP_Rx_Buffer 满”标志位,并停止将 RAM 中的数据搬往 APP_Rx_Buffer。等 USB 从 APP_Rx_Buffer 中取走数据,再清“APP_Rx_Buffer 满”标志位,允许将 RAM 中的数据继续搬往 APP_Rx_Buffer。
10.
修改图片客户修改程序,问题解决。
结论: 结论:
修改 VCP 例程时,没有对 APP_Rx_Buffer 的操作有足够的了解,造成在其特定应用中产生了数据溢出 问题。
处理: 处理:
在将数据存入 APP_Rx_Buffer 时,对 APP_Rx_ptr_in 指针在循环缓冲区中绕一圈回来后,是否会追上 APP_Rx_ptr_out 指针进行监控,以避免数据溢出。
建议: 建议:
在对例程进行修改程序以供自己的应用来使用的时候,我们不仅需要把接口看清楚,更需要把程序的 流程看明白。