你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】VCP 例程用于数据传输时丢失数据的处理

[复制链接]
STMCU小助手 发布时间:2022-2-19 18:29
问题:8 u8 h# o5 s# A: E$ h
某客户工程师在其产品的设计中,使用了 STM32F205VET6。据其工程师讲述:他使用 ST 的 USB 固件库中的 VCP 例程来实现虚拟串口的功能,但是他发现虚拟串口一次输出的数据(从串口到上位机)如果超过 2Kbytes 就会造成数据丢失,只输出尾部的 2Kbytes。客户工程师检查代码发现 USB 的 FIFO 大小由宏定义 APP_RX_DATA_SIZE 决定,而 APP_RX_DATA_SIZE 的大小刚好为 2Kbytes。所以他认为此 FIFO设计太小而造成的,于是他将 FIFO 的大小改成 5Kbyte,不过修改后并不能解决问题。2 K" x# T+ _- Y1 F* Q
调研:7 {' o$ q) Q- ]- }/ X6 Y% F
3 _% w9 U1 `0 }7 I
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,完全没有问题。# b3 ^0 X9 T6 J; f
2. 了解客户程序 UART 所设置的波特率,为 115200,与原 VCP 例程一致。USB 采用的是 Full Speed,全速 USB 总线的帧周期为 1ms。$ a9 B" N" U3 _1 \/ n& S
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 决定。! C. `: b- E) O0 |/ }$ @2 c$ y

3 Y0 X7 K5 t& R1 g' Q) U# x EPLE1`V4%T{C6%5]54VKW]A.png
* {9 j8 D7 `! E7 u
; N% [( k+ b& d  x$ K7 r: g* \6 K4. 打开 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 函数中,& u; x+ d# A" {# j
  1.    if(APP_Rx_ptr_in == APP_RX_DATA_SIZE)6 y9 U/ ]4 ~* _
  2.    {
    ; t9 |( y4 B) ^& G* L6 X6 o; {3 i
  3.    APP_Rx_ptr_in = 0;
    " \8 D& `: S/ K$ L4 `( I" _* Y
  4.    }
复制代码
# p+ L5 U2 r+ |! @
   可以看到当 APP_Rx_ptr_in 达到 APP_RX_DATA_SIZE 时,将其置 0,也就是在循环缓冲区中绕了一圈回到缓冲区起始地址。$ ~8 Y$ v7 z6 T& }7 p1 m
5. 再来看 APP_Rx_Buffer 是如何被 USB 取走,并送到上位机的。在 usbd_cdc_core.c 中,我们在usbd_cdc_SOF 函数中看到:# z+ r7 A. c3 o) c* u" K
  1.    if (FrameCount++ == CDC_IN_FRAME_INTERVAL)
    + m1 w5 v* r$ a* ^8 X
  2.    {2 q1 @, a) M; R$ V; T6 D$ L
  3.    FrameCount = 0;' k: A$ b8 i6 |  `  V' e# B
  4.    Handle_USBAsynchXfer(pdev);
    / B* b4 ]$ _8 M3 \" E$ H0 Y
  5.    }
复制代码
/ M1 s, }  b  ^* H: d+ n
   可以看到,USB 每 CDC_IN_FRAME_INTERVAL 个帧调用一次 Handle_USBAsynchXfer 到 APP_Rx_Buffer中去取数据。CDC_IN_FRAME_INTERVAL 同样定义在 usbd_conf.h 中,全速的时候,其值为 5。在定义的这边,我们可以看到:% `5 [) M" o* C8 M) ^% r) j# R
   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 是够用的。7 s1 R( D$ O6 u! Z7 w
6. 接着看 Handle_USBAsynchXfer 函数,同样,我们可以看到:5 _# M) [9 ]# u% J- t. B
  1.    if (APP_Rx_ptr_out == APP_RX_DATA_SIZE)" ^! ~( `9 E" C& }5 g* {
  2.    {
    : g& l( }, \, m
  3.    APP_Rx_ptr_out = 0;
    ' D1 t1 h% _5 y- Z9 s, B
  4.    }
复制代码

& n9 K+ o* Z0 o; V  N7 z7 L2 n   也就是当 APP_Rx_ptr_out 达到 APP_RX_DATA_SIZE 时,将其置 0,也就是在循环缓冲区中绕了一圈回到缓冲区起始地址。! F/ h8 h0 a7 d1 m
  1.    if (APP_Rx_prt_out == APP_Rx_ptr_in)7 B" h" |$ ~7 W# M5 Y  F- _1 a, i7 @
  2.    {5 @" Q5 W+ a3 k  u1 e
  3.    USB_Tx_State = 0;
    ( p( y: k% x* {7 S2 j$ r
  4.    return;
    6 u# t& Z7 @; |: Q' m: h, D% G
  5.    }
复制代码

1 o8 B$ A0 Y. l3 x  x& Z   当 APP_Rx_prt_out 赶上 APP_Rx_ptr_in 时,证明 Buffer 里边的数据已经发送完毕,返回。再往下看:
" ^1 `; w( h5 Q0 }* J9 F
  1.    if (APP_Rx_ptr_out > APP_Rx_ptr_in) /* rollback */9 s. c; A; n- a
  2.    {# [' f% n# H+ j9 {% M
  3.    APP_Rx_length = APP_RX_DATA_SIZE - APP_Rx_ptr_out; //①5 J0 [- s' u; x+ H. E/ b7 _4 O
  4.    }9 I/ ~$ g: A' z6 v: T4 c" k" p  s
  5.    else
    ! |  G) q$ y2 \. w1 c0 E- d
  6.    {; a/ Y( m5 }1 A7 v& Y3 f
  7.    APP_Rx_length = APP_Rx_ptr_in - APP_Rx_ptr_out; //②3 n7 p) e- A7 E& H
  8.    }
复制代码
, @/ s+ s4 H. Q  W% F( Q- K
   第①种情况为 APP_Rx_ptr_out 比 APP_Rx_ptr_in 大,也就是说 APP_Rx_ptr_in 已经绕了一圈,而APP_Rx_ptr_out 还没有绕一圈,比如下面情况:
0 x! |0 `) _) w- o3 x+ @2 b
# p9 c8 e; ]* i1 C5 k5 S/ ~; P AHP6JV6OXV)AL(FLR$HQRH1.png
3 h! \1 A9 {  ]* a/ ^5 |8 `7 U7 G# ]1 m) f
   这种情况下:APP_Rx_length 设置为 APP_Rx_ptr_out 当前圈里还剩下数据长度;8 b4 g% C) P8 ]- z
   第②种情况为 APP_Rx_ptr_in 比 APP_Rx_ptr_out 大,也就是 APP_Rx_ptr_in 和 APP_Rx_ptr_out 处于同一圈,于是数据情况比如下面情况:, o% a2 T7 S) ]: Y8 f: A
  x8 Q0 d' {4 ?: ]2 O  a& d4 X
QR7X55OJKX0LVE(~KGLB.png
+ L+ W6 N' R+ b3 [
! M% O0 ~+ E6 N/ W   这种情况下:APP_Rx_length 设置为 APP_Rx_ptr_in 减去 APP_Rx_ptr_out,也就是当前所有数据长度。  r$ \: O5 e6 v* A
   程序的后面就是对 USB 包的设置,然后发送数据。. [4 l+ a- S7 D/ M) F/ b
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 取走的旧数据。”这种情况不会发生!5 [, X, J6 s: J5 c9 O: {* f
   之前所分析的:
3 j/ l! L2 L* D( L   APP_RX_DATA_SIZE*8/MAX_BAUDARATE*1000 should be > CDC_IN_FRAME_INTERVAL这个要求,正是保证 APP_Rx_Buffer 安全的重要条件。) d2 |3 Q+ w3 x! T* L, I/ E/ ^5 u
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 才送一次数据。& q! V$ v5 G* K" f; O' w& b/ X
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。( Y& j9 \7 L6 t* i6 |, b1 X
10. 客户修改程序,问题解决。" n# a8 q' {  c7 |
   6 E8 J$ Q9 E3 U) r8 z; X
结论:( `& A5 j/ J) G9 h9 ]
    修改 VCP 例程时,没有对 APP_Rx_Buffer 的操作有足够的了解,造成在其特定应用中产生了数据溢出问题。
+ I+ c  L1 ~3 U! M% `
  r6 C0 x3 W+ q$ f! ~% m处理:0 x* Y2 J2 h, V% W
    在将数据存入 APP_Rx_Buffer 时,对 APP_Rx_ptr_in 指针在循环缓冲区中绕一圈回来后,是否会追上APP_Rx_ptr_out 指针进行监控,以避免数据溢出。9 y$ h8 o" h4 w, }3 Q( q* a5 a) V
   
; g8 q- j3 @4 ^. i1 Q建议:
  s* s' Y% \9 }: {% h在对例程进行修改程序以供自己的应用来使用的时候,我们不仅需要把接口看清楚,更需要把程序的流程看明白。1 h' P% E' J5 w! B' [+ a! r# r
, Y# K4 d; S* j, w" N) \3 p8 ?6 E' O' X4 p
赞 收藏 评论0 发布时间:2022-2-19 18:29

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版