1 前言
, G5 V$ u* Z3 ?5 c# Q! m在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。
8 s. y1 r1 y( ]1 ~
* {- a$ N8 |: q$ W6 t; Y+ x3 Q" ]2 问题分析
8 ~6 m! F; I& n( l: ^2 S这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。3 d" X: h6 R( }7 d! @6 p
这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。
5 b# b/ H* r# A" g, I o下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):
! ?" P- Z. H* ?- q( s; X( l3 O4 h- w& V* r0 C' Y; V
7 m, y) M8 J7 A# F4 G
8 d& }6 ] z0 _1 X$ i展开 Data Out 数据:, z! |2 D: @ B( ^
5 L/ `- K) U6 I" g- l5 a' V% r: y
y; q1 O% H6 o: s, g/ t+ o
6 E p# h8 q, c
分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?
3 [, S$ G. r6 E% q% m通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:7 p, u2 _: q. N
usbd_cdc_if.c:; [7 s+ j7 q8 V0 {1 A
- static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
: p8 _0 D9 C& O, y - {" ~9 L7 m7 ]! d
- /* USER CODE BEGIN 6 */# N# ^! I- N! f2 h8 o- [$ `
- //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
7 q. ~' C: c$ T0 X- s1 G) ~, } - USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);0 @( F9 t) j5 x
- USBD_CDC_ReceivePacket(&hUsbDeviceFS);
. A6 ~% n, l( `1 T - return (USBD_OK);$ J7 p7 A9 O f2 m+ m* c, `
- /* USER CODE END 6 */
$ ?1 h: w7 U+ c& T+ t6 m - }
复制代码
' ~0 Q S5 \6 V5 v. P如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:
2 |0 A; K/ F1 g% oCDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -
6 J+ M( W5 i1 {* S. ]+ B4 \$ p M3 Q> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
( o, G a- @. e _& B> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
6 x: x( V2 R/ @4 l! P- //…
, D! g) E9 A6 A, C - else /* OUT endpoint */
/ ~8 S+ h m* t' L - {* g7 B/ D, A% T8 j( [( P* K3 A. X/ \
- /* Program the transfer size and packet count as follows:
1 P- r# ^6 l$ D! a, K8 [6 J" F
1 a" _6 o& G2 E- * pktcnt = N
& G! Y: z, J$ U& R+ v - * xfersize = N * maxpacket
* _( N! p8 X, o9 c0 ?! x* Q, S - */
! x; y, @: q% g) b$ c' a% a - USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);8 O! r% B. s* L0 l' c
- USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);% }: P1 s$ [. Z# S% m# y
- if (ep->xfer_len == 0U)
6 R$ W5 a& k/ H# U" b P) M( L - {: g, ]1 m+ B& j, ?
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);( |2 [! q$ o, V/ l3 b) r+ z
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;
5 ~4 @$ F8 ~% F( X- A - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *& S# M+ G2 f+ r0 x! @) z+ o% w% i
- pktcnt));
2 T) ?7 u4 o/ e) ?/ } - }9 h& {& s& X( ^/ G) [. E
- if (dma == 1U): `( S$ }1 k& C
- {
7 A! u5 H; J% L I8 U+ }% ?- S - USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
& {2 Y' T. k# ]+ b$ i - } k3 d \# p0 O/ S
- 9 d+ S5 l* o; b6 ^( i6 v" S
- if (ep->type == EP_TYPE_ISOC)8 y- N" a* Z* u
- {
# A j% Y' k, T& { - if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
' F( Y8 N0 c. @9 X6 y( ~- k - }# |1 z! N; \" z: q# ^/ {5 h" K( s
- else" u% |2 x' e, q" `: h; W9 j
- {
4 G: J9 L: |! z* E- i9 ^ - USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;) E# {, f7 ~1 d# W
- }9 ?& z' g. f( F g K- n# _
- }1 i! H( P1 _& J+ e/ n' v
- /* EP enable */ `6 F; `! d; n+ K2 H; o
- USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
3 B I: t! r7 { H! w& k - }
复制代码
3 b0 q% `: }3 ?% Z, I& A* f' t6 \也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
$ H7 f- r/ }( ?+ S3 r" K: v: a0 {- @; Q4 Y5 q+ w
1 l2 l* O/ \& s6 ^8 x4 A( N
' `) b/ `4 B* Z# r9 [4 v
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
) G. Q: Q- h. p8 j& C7 f1> Host 端试图向一个端点发送 OUT token;% i- ^# P7 A: O, j% }( W# u
2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;
7 C7 Z( h) ?, G* N4 S$ B3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);+ n6 N! X6 L& h# T
4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;
6 d$ m( `1 |7 _0 H7 K1 x5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;9 Z3 r7 ]6 H4 N B
6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);3 W0 x( ?1 I2 g+ B) O
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
- ~; V. J- {- d1 G/ v4 U0 d7 D6 n7 D; n3 E
从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
* `9 P: \) ~3 _& ~3 m* Q1 }) P- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
. ?- F( I% n! B2 c - *pBuf, uint32_t len)+ ]2 O4 c) c, p" U4 r7 u1 d0 V
- { ?+ U l% j; G) h' z# o7 R+ {
- USB_OTG_EPTypeDef *ep;
+ T T4 v1 M% O G* J7 A( K
4 j9 L$ }4 Y7 t# D# r- ep = &hpcd->OUT_ep[ep_addr & 0x7FU]; m4 s" n0 @1 n1 V- @" W/ Z
) M1 Z8 x# n$ q& c- /*setup and start the Xfer */
0 ]7 o2 h/ A, A5 `8 t - ep->xfer_buff = pBuf;
4 t* }" B. m0 b! I! {' Z6 n/ _) _ - ep->xfer_len = len;
0 y: Q) J8 i0 v* w: | - ep->xfer_count = 0U;) D7 d, A% D6 c) n1 O1 U
- ep->is_in = 0U;, F8 @+ y# K! X& V4 Q
- ep->num = ep_addr & 0x7FU;* [4 e* x/ A8 |
- * o' `( H( E" X5 j
- if (hpcd->Init.dma_enable == 1U)
, q* W# W, N7 d/ P9 j! m' O6 `1 r - {
" {1 ~' G. |5 ^7 [$ Z$ {3 ~1 m - ep->dma_addr = (uint32_t)pBuf;
1 |) `# n6 y7 d% } - }/ P6 {! j- y5 U$ v" P* c9 I
- : q9 z9 H- q. B' e9 B
- __HAL_LOCK(hpcd);( |& S9 t6 q ~7 w( Y/ I0 K. I
- M8 w2 L! P. \( [6 w: Z! x6 s- if ((ep_addr & 0x7FU) == 0U)
# X* Z6 a9 o% L! B - {
% b' L" j9 h& C. Y' C - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
5 x- e2 c# d/ j - }! |3 ?& h0 Y E% X* X2 ]
- else' J$ K( T _. l% M8 ~
- {# n6 r# Q4 k$ P9 P$ j. E6 h1 O1 e0 E
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);6 ~0 i6 f+ p8 {
- }
+ y5 E" f& C B& p) s- |" k+ R - __HAL_UNLOCK(hpcd);
* B$ l6 b) [. _3 \( p0 {5 ^ - return HAL_OK;
& g1 `0 m; \$ z. }( z, s3 x - }
复制代码 , y' D/ z1 m; j
3 Z( X: [1 x9 K% {8 R之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:8 s" w, S. C! N9 j: U
- CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->5 D R6 K' `$ {6 P8 t' F) x
- HAL_PCD_EP_Transmit() :+ U9 {- {2 K# C" I9 R& K
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
: ?6 ?* {7 z- d _9 d$ x- j: O# Z - *pBuf, uint32_t len)( O% N7 y9 m4 l+ l
- {: C% ?6 Z& s, o: |% X4 i
- USB_OTG_EPTypeDef *ep;) t; m c6 ?. c: ~. r+ r4 ? t9 v
u- Y. I( {+ j5 @0 o, b9 d- ep = &hpcd->IN_ep[ep_addr & 0x7FU];
( d" ^0 ^2 v/ L - /*setup and start the Xfer */
8 p2 B$ d$ u' E n# h) D& U/ y - ep->xfer_buff = pBuf;! z m q. [) ?9 Y8 u
- ep->xfer_len = len;. n _& Q; R3 H+ s' P+ D+ E
- ep->xfer_count = 0U;. _1 D5 w5 x) v6 }) ?" r( q- W) k% U
- ep->is_in = 1U;7 c4 K3 H/ C) ^- I, K
- ep->num = ep_addr & 0x7FU;
3 r x8 d# H( j% y- t
; Q4 p: u" D" s. v! L8 n7 T; c- if (hpcd->Init.dma_enable == 1U)
/ p5 ~) i6 B- C# p: j - {
2 m% P( K. Q4 z8 K - ep->dma_addr = (uint32_t)pBuf;
' I% N5 k" L/ ^! w. _- B - }! t! r3 a5 r9 N! _7 Q
0 t2 ]* X8 ]! I1 K1 t6 y& ?- __HAL_LOCK(hpcd);% v8 F$ g+ S G% G+ z
- , r2 S6 C, G4 l. R6 C& D& e( L
- if ((ep_addr & 0x7FU) == 0U)
2 y. o3 C6 w- ^% D - {2 c, V* | f- s& A0 v" C5 {
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
' @; f/ U/ G o' B3 L - }3 ]" v/ J' E( ~; z% i9 z! M
- else
3 ~% y( r' |: H% v; S - {
) |& x4 ~. Q) w2 y - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
$ L; @4 { g# a/ ~1 Y - }& M0 O/ l4 r0 U9 f
: a0 F* o5 A0 q% q+ ^- __HAL_UNLOCK(hpcd);
' H9 H3 q Q& v0 ? - return HAL_OK;
" O. G% M( i5 C - }
0 I0 e7 V* c8 G$ W. O
复制代码
1 o0 y8 `, G8 L$ f6 z% i接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;/ G9 B: R2 V5 K9 j$ I- t: C" n
% }! Y# K( ]/ B1 C$ {
我们进一步查看__HAL_LOCK()宏定义:
) X; U% ^ G, Z1 C- #define __HAL_LOCK(__HANDLE__) 9 M: Z. A# U6 P& u
- do{ 1 d; T0 C# E6 k+ @: K: S: e
- if((__HANDLE__)->Lock == HAL_LOCKED)
6 c$ C2 r, H/ z - { $ ]' E- o" p, X; ]2 h, i4 w
- return HAL_BUSY; ' w1 a) l( X( X: e$ M
- } 7 X% C7 i# E- T
- else
. m% {7 v1 T4 z7 n c - {
6 T' R, A# U$ x. v" E8 v- \. D - (__HANDLE__)->Lock = HAL_LOCKED; : b- a5 ]2 Z. W! `# c! X5 \
- } B5 E' n y( V" g
- }while (0U)
复制代码 ; d6 H5 w( z& S7 P+ _
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:* B$ V1 S: F+ { y4 b( {
- uint8_t Lock_Flag =0;3 A d. {8 D4 H; X. [; K
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t9 l) ?. Q, b6 X
- *pBuf, uint32_t len)' r& W c8 s; w
- {3 M7 B( e2 z8 b! M) {
- USB_OTG_EPTypeDef *ep;
. J/ u6 l/ u! d! \
( X2 {/ E- T; ^5 t2 J2 c3 n" s3 o- ep = &hpcd->IN_ep[ep_addr & 0x7FU];2 b( f+ f0 @8 q" T/ U: G l) R
- 1 q" G0 F7 Q! M P# y( ?
- /*setup and start the Xfer */
" f. X) {- ]1 N3 p6 F4 x - ep->xfer_buff = pBuf;
9 [8 N( C" G N+ ?. B - ep->xfer_len = len;% Y$ ]. l8 W- ]* a7 H/ H; G/ @
- ep->xfer_count = 0U; K# p; A# Q" ?( T3 w+ \
- ep->is_in = 1U;
# H& C! ^3 z" o3 K$ e4 x0 U) @ - ep->num = ep_addr & 0x7FU;
& S% l0 p W6 B$ y% b - 9 d. ^2 b, w8 D" ~
- if (hpcd->Init.dma_enable == 1U)' ?; q! B& x* E/ ~/ e4 s
- {+ A$ N! ?2 n7 u2 @! I
- ep->dma_addr = (uint32_t)pBuf;7 P, _) E; q$ i) z: h% ~
- }
# g5 L% w* a3 s6 @% N* r
: K5 U$ `3 E) R# ?2 W4 w- __HAL_LOCK(hpcd);! _8 F" `8 z1 F* ~
- Lock_Flag =1;
$ E+ f* q/ J% i8 H& p2 C' `) o6 ?& G
+ h2 i! \/ Q0 O/ }4 }! |" u- if ((ep_addr & 0x7FU) == 0U)
7 ^; i9 D0 P0 b2 s - {) Q! g6 g. _; l* l9 x4 h% C* W
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
. }% N5 Z8 l% N4 Y. y& Z - }/ t4 n8 y6 w3 B" e
- else) W( L8 D- o5 a5 u6 w# G
- {' O8 e9 p+ ?2 w' g' F5 }
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);, F. p$ m% ]9 Y! V) J# u4 E
- }+ J) x4 p/ I) m7 ]. F$ ]
- : ^/ e) N1 O$ `! A
- __HAL_UNLOCK(hpcd);4 P5 o- d' \9 ?/ u' j( ^# o
- Lock_Flag =0;
5 ~) Q7 @: _4 r d5 j2 \) m# L1 B9 M; B - return HAL_OK;
$ e* k2 L$ D5 @: G( C - }
复制代码
2 n& k) G9 t6 W/ o' l9 s( l4 c接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:' j9 z5 [. @5 R, X& Y5 D% \
- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t- u0 [$ i& c! V% R9 T
- *pBuf, uint32_t len)
3 D9 s6 ?" }. ^' f! O8 S - {
! h6 a+ L$ U- p$ ` - USB_OTG_EPTypeDef *ep;
+ O8 R6 b$ t! `( L, t% ~( I0 H- m
$ Q" p% K" S( s7 |# s! G- ep = &hpcd->OUT_ep[ep_addr & 0x7FU]; a) D# p p5 X4 Z
- + P1 X$ B( Y1 g3 L2 B0 \
- /*setup and start the Xfer */" P* a, \- O9 p P6 T7 h
- ep->xfer_buff = pBuf;4 Q: c5 z" j8 i: v" ^3 x j& F
- ep->xfer_len = len;
7 ]9 m/ q: k/ C3 F - ep->xfer_count = 0U;
4 k( R- W: S! O2 u4 _# e - ep->is_in = 0U;
& e; b6 O/ [: Z* [: }! Q1 y r - ep->num = ep_addr & 0x7FU;
' v4 i# u* j5 R# t
5 J; T( f$ [/ b; B- if (hpcd->Init.dma_enable == 1U)
Y# U0 I2 _. b+ K; m& c - {' I6 M* \) l3 |) d+ I C$ k" p+ T6 p
- ep->dma_addr = (uint32_t)pBuf;
3 J+ E4 c) W2 L7 k# y% q - }5 x9 F" j& H0 B/ |) I- ^& B x8 k; Z
9 b4 P: w$ x# |' O0 i+ A- if(Lock_Flag ==1)9 q( \/ r8 O8 V2 l6 Z. [% r
- {
( P/ F1 A1 y5 Z( i! U - while(1);3 ?# @0 j" p$ f
- }$ O' h7 K; `, Z7 s9 e. p5 V
- __HAL_LOCK(hpcd);
) R! v! ^. M+ E7 r
7 C8 S8 J; p6 T" W- if ((ep_addr & 0x7FU) == 0U)
+ b9 \$ c) M) ^. u, r) f( J0 W: b" a" A, z - {5 J4 U1 ]% b, @9 c4 x, e
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);( J0 A' N+ W+ Y1 M2 \1 b
- }6 B# ]# n, ]) ^% G3 n/ w p
- else5 ]9 }; F7 j6 g b0 z4 K6 e3 t
- {# _. ]2 H# z' v& W3 z; l' V
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
; \+ x7 ?# M& `( A5 U - }& }) G7 R! }" H! X2 a
- __HAL_UNLOCK(hpcd);
9 ~: [$ K6 f$ H: [( Q6 {/ D - ) h/ y5 Z& J: h) f2 o1 z
- return HAL_OK;
8 u3 `- C) w) _3 o0 E, }; P. ] - }
复制代码 + \, y8 D( X- _8 v
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:" s- f7 c! K2 l9 g' U
1> 在 mian 函数中发送数据 CDC_Transmit_FS()
: V2 V2 f# P+ n. l. e2 V; w* Z! r2> USBD_CDC_TransmitPacket()
& ]! p7 @4 D4 v4 [% a* \1 {8 u& N, @3> USBD_LL_Transmit()
7 Q. R* i2 F" l4> HAL_PCD_EP_Transmit()
/ j( R$ l, X5 F8 y3 r" M) L5> __HAL_LOCK(hpcd); 此时成功获取互斥锁7 m w: k9 J/ l! ? a4 t8 a- R
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
; [+ W% m: k5 u- K: A7> HAL_PCD_DataOutStageCallback()! c [1 u2 L; G3 e2 O' e' i
8> USBD_CDC_DataOut()$ v w, z- H3 ^2 u0 w- g: z$ {8 _
9> CDC_Receive_FS()
& j1 R. k) P! t: q0 q10> USBD_CDC_ReceivePacket()
2 ^5 E0 E1 s+ v1 }$ B. t11> USBD_LL_PrepareReceive()
0 ]) L: G( f1 G5 C) ^+ ?$ @% ~( n$ ?12> HAL_PCD_EP_Receive()1 e0 }) D+ b6 f: P
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已
1 Q1 g# T9 n8 B+ }- ^$ N4 L9 T经提前结束,导致接收循环无以为继。# ^/ ~& y: t* j# e3 Y( s# B
- t7 y; n& _' L$ e3 解决方案& ]3 i7 @# g. c9 p6 s$ @' [3 q
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
; m8 f$ `& h7 g2 n! B' G2 a% l
* Q! z% K+ Z8 D/ f m此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
8 T- l: X p/ T
. C9 x1 u( N2 d s, Y |