找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 8452|回复: 4
收起左侧

VCP例程用于数据传输时丢失数据的处理

[复制链接]
ID:98618 发表于 2015-12-19 14:53 | 显示全部楼层 |阅读模式
本文作者:Miler Shao
几天前有个工程师在使用STM32F4芯片的VCP功能时,发现数据接收异常,反映每次上位机发送过来的数据只要超过了64个字节就只收到后面的64字节数据,小于64字节正常。因为只是通过网络随便的一个留言,其它信息不详,但我怀疑其原因可能跟下面的案例类似。

下面的案例是之前ST MCU FAE跟客户一起经历过的,并将相关过程和解决方案形成一份文档并发布出来了。我觉得该案例可能具有一定代表性,并把它转载过来分享给大家。该方面的文档在ST MCU中文官网[www.stmcu.com.cn]的设计资源本地化资料部分可以找到。

************************************************************

VCP 例程用于数据传输时丢失数据的处理


问题回顾:

某客户工程师在其产品的设计中,使用了STM32F205VET6。据其工程师讲述:他使用 ST 的 USB 固件库 中的 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 devicelibrary (UM1021)” 库里边的 VCP 例程,位于“...\STM32_USB-Host- Device_Lib_V2.1.0\Project\USB_Device_Examples\VCP”中。对其进行测试,并没有出现所说的问 题,APP_RX_DATA_SIZE 的大小仍然为 2Kbytes,不管传输的数据是2Kbytes 还是 5Kbytes 甚至是 25Kbytes,完全没有问题。


2.了解客户程序UART 所设置的波特率,为 115200,与原 VCP 例程一致。USB 采用的是 FullSpeed, 全速 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*1000should 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; //①

}

else

{

APP_Rx_length= APP_Rx_ptr_in - APP_Rx_ptr_out; //②

}

第1种情况为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 时,这个时候,若 USART 继续接收到数据,APP_Rx_ptr_in 指针继续增长, 就会造成新来的数据冲掉还没被 USB 取走的旧数据。但是,目前ST官方提供的USB 库的 VCP 例程是实时的。USART 收进来,USB 就会取走送到上位机。而 且USB 将数据送往上位机 的速率是大于 USART 接收数据的速率的。换言之,它是不会发生这种情况的.

之前所分析的:

APP_RX_DATA_SIZE*8/MAX_BAUDARATE*1000should 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 例程中 USART 一样的操作方式,也就是存一个字节到 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 指针进行监控,以避免数据溢出。

建议:

在对例程进行修改用来供自己的应用使用的时候,我们不仅需要把接口看清楚,更需要把参考例程的原理流程应用背景看明白。

=============================================================

后记:上面文档分析得详细,看懂了原理就好。整个过程可以抽象为一个生活实例。有个池子,一边给池子里进水,一边取水,做到不溢出就好。如果进水快,取水慢,中途不做任何监控处理的话,池子再大也有溢出的时候。结合上面案例,存放数据指针折回并追上取出数据指针时就是溢出临界点,此时缓冲区里全是待取走的数据,如果此时又来新数据自然出现覆盖问题

回复

使用道具 举报

ID:185829 发表于 2017-4-3 20:12 | 显示全部楼层
注意的要点问题很重要
回复

使用道具 举报

ID:185829 发表于 2017-4-3 20:13 | 显示全部楼层
临界点的判断是非0?
回复

使用道具 举报

ID:224453 发表于 2019-8-23 09:25 | 显示全部楼层
最终是如何解决的
回复

使用道具 举报

ID:224453 发表于 2019-8-23 14:02 | 显示全部楼层
该问题已经解决 保证out始终不与in相等,假如套圈那就while等待一下,确保out 发送成功 再去填充buff
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表