1 前言
1 N( v& w! R( V# ]6 o) J* k, N在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。- I0 E4 n+ a9 c0 G) _/ l
6 o7 \0 ]/ O: ]* g8 M/ b1 b2 问题分析
. o u( l& a0 j$ _' p. _) |! I" s. y这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
& r; _( l8 w- C8 P" M& ?这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。. t, m" q0 B- U
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):- j& b" M& D- z& T: w# E3 R5 B) l
1 b0 c, O) Y; M9 w3 G1 M
: t6 E6 B! c1 c5 p! i2 o% \! e0 ]- F: z
展开 Data Out 数据:
6 h% ?3 l' m! w7 A, h. t9 N% ?& w+ I2 ?; i
( R1 G6 o5 F* c( S4 W1 ?
) s- N' r1 b8 _% t& p- @% d+ Z# e分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?+ o1 j% K3 L2 z M+ M
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:5 U% @1 p5 a7 M0 a
usbd_cdc_if.c:
( |/ y4 f9 b% ?- static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)6 U O7 G" E6 `+ x
- {& \* j' w$ e f& q8 }& o! [5 X
- /* USER CODE BEGIN 6 */9 W9 x& X5 S; L
- //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
: K5 I v* k3 c' X Q: D& T - USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);" ~. V# |* w6 [+ ~2 C$ `8 v# k
- USBD_CDC_ReceivePacket(&hUsbDeviceFS);- s& N; l0 g5 p3 w# z
- return (USBD_OK);
0 Y0 W2 X! j) h7 y, [ - /* USER CODE END 6 */& m$ X0 ^$ s! P
- }
复制代码 6 E! H" A- @; z9 q! u8 G" B) L8 B
如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:( [3 N. O1 o, z6 E7 ]' [7 [% i$ r
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -2 e9 O' a I5 |( R5 U. Z- _( X
> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
( l3 k/ P$ m5 h9 F- L: x> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:# d* e" i2 K/ n* x# p
- //…
1 x2 O: e" k: P4 M6 ?) o - else /* OUT endpoint */1 u; M7 O: ?2 q+ @
- {- X! u: j0 }. @( U' |
- /* Program the transfer size and packet count as follows:
5 D3 i$ q. ?7 w) g& c0 |
! X/ j! u/ G3 B$ ? H- c' ]- U6 p- * pktcnt = N6 ]6 _4 y0 k/ ^* \1 y0 i
- * xfersize = N * maxpacket
6 Q9 s* \: @$ D2 n( J- Z5 o0 L - */+ n" a7 c( [# B; M3 a p; a* |
- USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
" _: [: \0 O/ H" |' q& l0 ?9 K) Q - USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);: F7 z$ ~" O7 |# l* u% Q" b
- if (ep->xfer_len == 0U)$ i* R# ]/ M# O& \9 {! X8 e
- {
1 T# f* t2 e3 X% S, V: } - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
: I5 ~% b# X* a# g4 k - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;1 _- S" g% _ ~! w6 N1 {4 N
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket */ D4 [5 a+ w0 f& Z) ~
- pktcnt));* v" G/ r G" o6 v9 z- u/ ?4 l! Q+ k
- }
" n/ C" ^" \# u ]% s1 V! W - if (dma == 1U)1 N! i: i3 J: L& {9 B
- {% |& G E' g+ N0 N- p$ Y; C8 I
- USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;; k" a1 c- l5 O5 E' U& N3 S
- }+ C# l0 j8 G2 _4 r# Q, x% f7 J" _2 D3 b& O
- 8 K9 _' E! L9 k' A4 l
- if (ep->type == EP_TYPE_ISOC)) O4 {) b+ c, T! ~
- {/ @. x/ L. p: O- g6 O0 a+ P: e
- if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM; l# ?8 G- u2 O5 J
- }
$ w1 E2 l, T7 i9 O( ^ - else
# F* Z% w+ z& M! c& w% H2 | - {7 o' b# n( k* M0 S$ y7 T
- USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
5 F/ G5 N! n9 F+ y4 ~$ J2 q - }
$ A5 L6 }. g. M - }1 a# a: b: J+ F* V2 e1 {2 h+ ~
- /* EP enable */
7 t- t* E1 k8 S1 ] - USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
% P" U7 S, e/ G6 { - }
复制代码
( X5 @) _( G( i$ S也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
5 o" l3 Q' t; |) i0 D6 C: b7 Q: m+ |+ ~6 k% |/ K5 e) s. F4 [
: k' @7 p+ C! l' V' J: j d. ]7 x' _! c5 Z% q+ ?
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
2 i* ]" W6 }. J. B) ~* e' V: J1> Host 端试图向一个端点发送 OUT token;
; o5 P8 m6 H, J w _; v$ ^! c2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;9 W# ~/ Z4 H/ }/ l! w, W0 o/ P
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);
! J8 a; A, z- [8 D3 M5 n9 q- U) f4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;
7 e7 @8 w8 Y- G& `1 f5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
: O6 ~1 V" b0 c o: G6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);" |% j* E1 |2 L3 h) h
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
9 L: a0 {' [: I! V/ |
! z M9 }. v7 m; D- z& \1 w+ j从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:4 T, h5 j4 C/ g, S5 \
- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t* G0 n3 S- c' u. S
- *pBuf, uint32_t len)
3 p [( @: w& p( W - {$ ` w) } A* w! E1 h V
- USB_OTG_EPTypeDef *ep;+ m% S* m* z8 p% e6 Z' o7 D* Y
`4 v7 c1 r) Q5 |+ }- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];; ^- D" r4 U6 n6 \
- 2 r% W7 J# `, f# a/ g( Q! F, [
- /*setup and start the Xfer */
& B, K. Y0 w8 J* | s( U - ep->xfer_buff = pBuf;. N7 c3 K" y1 h/ m! _" L1 Y
- ep->xfer_len = len;
/ p+ F3 j8 T$ d+ l+ e7 q - ep->xfer_count = 0U;
3 e: Y" j) W J' q/ i' B - ep->is_in = 0U;! O% b# F7 e6 ]/ T: K6 ~
- ep->num = ep_addr & 0x7FU;2 r6 Z ^1 l& n' i3 F. H4 y
- : f a. { k+ I: A% u5 m
- if (hpcd->Init.dma_enable == 1U)/ D6 n+ p) `- U, V
- {
; Z; d! I7 ^) t: p - ep->dma_addr = (uint32_t)pBuf;4 o: I) f# Y1 s2 d2 w9 D2 k! s
- }' |; V7 N8 M( Z5 q/ e# c
- 0 A' q) E" R# N' P- L7 g0 W
- __HAL_LOCK(hpcd);
% T& W% ^# L* L7 i; `4 M - ( }; |* T( a% C" i2 N* L6 r
- if ((ep_addr & 0x7FU) == 0U)
/ g3 h3 @; }( r& T% X1 f& \, } - {
1 `. }1 T4 d6 R. e* q - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
6 b3 y H7 y B5 \ - }
* b0 r. j& G! T. H$ d. w - else
; v. I4 x& I7 f7 E. y* E - {
/ x* f) G0 F' R9 J2 d2 w$ x5 ] - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
" N0 l2 G- S" Y+ H' @* Z6 @# D - }2 `2 S, y) Q& y, O/ p+ n' e- B
- __HAL_UNLOCK(hpcd); % x2 V2 `! s/ n3 J; U A, o
- return HAL_OK;
6 b$ E0 J+ d) }/ D! U2 }. e+ K - }
复制代码
8 Y6 S7 z) j& K7 v
9 }8 Q: n- T5 D9 J之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:7 i4 ^8 ~% S' A) ]5 }/ |
- CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->+ e- |& ]* _) k# G
- HAL_PCD_EP_Transmit() :7 X! E: a$ G6 }
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
4 \' Z0 k6 `$ p' D! L% d2 W, [ - *pBuf, uint32_t len)
# }1 a* L5 E. g3 q3 p' s - {
7 S& C9 N( v) n% c - USB_OTG_EPTypeDef *ep;
_$ K. R- {! o* w
3 y6 g; r' i9 ]- ep = &hpcd->IN_ep[ep_addr & 0x7FU];/ \ o' S: I9 u
- /*setup and start the Xfer */
/ Y2 [) b5 P1 E - ep->xfer_buff = pBuf;, ^, d; K! r9 H+ ?' U! o/ m% L
- ep->xfer_len = len;
8 z* f0 B g( }0 o: e" y - ep->xfer_count = 0U;
* A) n, `( P" E& f - ep->is_in = 1U;8 X& x9 V5 ^9 G& o: {
- ep->num = ep_addr & 0x7FU;
* V, r) m. W$ }# U! B - 3 Y, T a. f9 Z6 S0 e5 E0 g
- if (hpcd->Init.dma_enable == 1U)* E% Z1 e; |! h& p r* ~
- {* }* j3 Y( o3 ^
- ep->dma_addr = (uint32_t)pBuf;6 {; Y2 \# W; S3 _: i. K" J: E g
- }
- k/ M0 }5 {0 S! t3 l5 r5 N
, l( w5 G1 X' q: a" `1 ?+ g" l- __HAL_LOCK(hpcd);- P e& S1 ~ P( V
- / ?( _) J$ e, J t( m
- if ((ep_addr & 0x7FU) == 0U)- U% m- q8 `2 S/ V
- {% G3 V- o0 [8 k) l0 f) V" }' S4 k+ R
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
3 n/ S$ l$ R+ q: l - }
( D6 d) i+ L* p! `1 Z - else0 G' ~& Y0 |# J& z1 @) p8 A" J
- { a/ d0 d1 V& B; ~+ e a
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
' h x9 V; |# m/ ~& _: F( c - }
& N" Y: E5 A" Z9 ? - 7 T5 _$ o( Q2 b
- __HAL_UNLOCK(hpcd);/ H3 s' e5 u4 _; x$ \( o+ u
- return HAL_OK;) @* r( g$ J! D. v" j; v- o' a
- }
0 h" F, W( q$ w* [
复制代码 8 F7 R. @. i& E! V0 [) N
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;
3 @7 U3 W; C! w0 z4 H8 o5 G8 S
5 O% f: v7 [ m. A8 k我们进一步查看__HAL_LOCK()宏定义:6 U! A( {& D6 p8 J# h; Y" @
- #define __HAL_LOCK(__HANDLE__) ( |4 z7 d+ q( H' H4 \+ ~8 H
- do{ ( t& D# ?( [* e0 G
- if((__HANDLE__)->Lock == HAL_LOCKED) 2 S( V5 {4 t0 H5 x$ j
- { 5 }5 q+ P2 C/ p8 v
- return HAL_BUSY;
' [2 d1 {, i6 X/ ?0 @" _+ \ - }
+ A1 o4 i$ ?: T - else ) c) r4 x% P) g4 G2 H% z3 j6 M
- {
1 c6 G: x. { p. k* p$ G - (__HANDLE__)->Lock = HAL_LOCKED; " C; p+ }, v( L5 c$ Y/ T8 F
- }
! }4 L$ I9 V+ C7 o( O* Y2 q% z, q - }while (0U)
复制代码 3 ?% O1 G+ U. N
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:
" Q: T8 K) [7 z4 _5 ^- uint8_t Lock_Flag =0;! ^9 g( e! S. F5 o
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t* Y& e8 D# [. G# }$ }
- *pBuf, uint32_t len)
7 B, @& o: ]* g S - {
( i+ ^2 G5 M* q/ X; f7 m3 ` - USB_OTG_EPTypeDef *ep;6 E, g* k3 g6 G/ n. o7 N( {* \
0 o: ~: a# M" p( ?0 {2 h* T- ep = &hpcd->IN_ep[ep_addr & 0x7FU];: `5 D4 I# ^: b# i6 Q; c$ f; \' n
# ?% L0 ~, x4 e1 B- /*setup and start the Xfer */7 Q* a' C0 C9 [
- ep->xfer_buff = pBuf;
2 d6 t( \' C9 I/ Z; V - ep->xfer_len = len;
2 y' W. Q% l, u( Q- | - ep->xfer_count = 0U;
9 o8 J. E+ }2 j4 Z - ep->is_in = 1U;: l' d; S7 k5 T) E. I7 q1 I; I/ [ I
- ep->num = ep_addr & 0x7FU;' s, P0 }4 F& K; A+ {) L! S
- $ g/ O8 x( u# {
- if (hpcd->Init.dma_enable == 1U)
0 P! k. P' k1 v' f3 _ - {+ U2 E% [ [2 |/ i
- ep->dma_addr = (uint32_t)pBuf;
1 E2 o- D) Z- V - }
4 t; P% Y, E# d% _& y
0 s6 ~4 x" j, s0 ]- __HAL_LOCK(hpcd);
9 v" G4 B; _% A% ]( H s) \4 ] - Lock_Flag =1;
8 b. [& t6 V: | K8 E* T
8 a2 H0 A2 z M, F- if ((ep_addr & 0x7FU) == 0U)9 C3 Z% {* O: J% W* g
- {
7 q. i' E- q9 j - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
5 B, ]* s% L2 e/ Y. l7 g! a - }
8 m" ]7 }6 A% b. Z4 ]8 i5 ^ - else
& P# z9 T# ~) y& R- V% W7 n - {
! ^' c; H8 V, R - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);/ ^3 E6 `8 W1 a* q' D u
- }
- m5 o+ N3 r7 s9 e; [
1 y) W6 R8 q6 X, b+ c5 s- __HAL_UNLOCK(hpcd);5 @. K* z$ ]' a" w. w o$ H- m
- Lock_Flag =0;
2 q5 O1 R. K. |. ~' t% X ~$ ^ - return HAL_OK;& p. M) K/ m/ \3 ]2 h
- }
复制代码
3 `3 d2 v# V) M/ o: Z) S- o0 w接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:
# ?3 T: s* d( X) i; c- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
* k7 R0 K: G! h6 G/ E - *pBuf, uint32_t len)* p3 i5 N3 I/ p9 W& v9 h
- {. g; D: u- L( ?8 l1 i
- USB_OTG_EPTypeDef *ep;
; y6 k( \1 w2 B+ e
" ?. _% v/ b; y% i( Q; e- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
6 R1 r2 P! c4 L2 T6 l K - / l% @, H1 K$ M/ H
- /*setup and start the Xfer */- { s& J3 O0 I& ~/ P( h
- ep->xfer_buff = pBuf;
& m$ E% f7 \5 d. t, i1 w2 ? - ep->xfer_len = len;
' O4 \" I* P, h, E1 W$ `1 e - ep->xfer_count = 0U;
/ I4 O0 V* t' |6 I6 a - ep->is_in = 0U;8 }! o8 _( ^) \6 [9 [
- ep->num = ep_addr & 0x7FU;1 K! ^" z* V* R4 p$ n9 H
- , f; H4 O% e5 T) E! f' w, Q, |/ ]
- if (hpcd->Init.dma_enable == 1U)
4 d# b. } H* a x4 }+ ?) F. m2 g - {
6 w6 }: p. N6 f n3 K6 _0 _ - ep->dma_addr = (uint32_t)pBuf;
# t I- e1 T. {: n - }7 d% A! ?2 a5 r) k
- 6 o6 `& _3 r( W# ]2 p" I2 C
- if(Lock_Flag ==1)
" g i: c7 M/ W$ j - {- ^1 O0 \( }* Q/ m0 C+ K9 Q# _) t
- while(1);
. r' ~4 {2 r! u' R1 u - }
: }0 @" X, P0 m" c. e Q - __HAL_LOCK(hpcd);- z1 {" m* `! H
- & ^" A7 Z, U: h" ~% l6 m3 K) \
- if ((ep_addr & 0x7FU) == 0U) v1 g+ x- d# G1 W
- {
3 J3 O4 M8 `$ R - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);5 W4 d' T v5 a. Q J+ C
- }
2 t9 I4 c. s& d+ b- x9 ]; t - else1 I* |" Q' Q: O. R _) I7 g+ [3 x4 b
- {
/ v( @" E. G l$ i: A9 e - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);. t8 i% L4 m7 L9 ?% e+ O: q7 k
- }
" O4 e! `( j4 n6 x! N" I - __HAL_UNLOCK(hpcd);
/ r5 Z Z8 @4 D4 H. P - 1 v+ I2 V* \) T) o o. j
- return HAL_OK;
( g3 w- ?3 y8 s" k - }
复制代码 ' x' f! d% S: X2 p0 ?; {
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
8 E" |( c! E2 N+ I- ` j1> 在 mian 函数中发送数据 CDC_Transmit_FS()' R7 n' }- |6 k% l" t% g$ s
2> USBD_CDC_TransmitPacket()$ K! Q/ u; E/ F8 { N
3> USBD_LL_Transmit()
. w; g/ Z& f- h" S1 W4 E A5 ]4> HAL_PCD_EP_Transmit(), O! A0 K& @: J- a- c- ?& `
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁6 P% d+ D& S4 a3 c
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
' U7 E" Y) H7 g% c7> HAL_PCD_DataOutStageCallback()
7 \9 ]! w: J- ~7 W. |% @1 \8> USBD_CDC_DataOut()0 t; @# g. E8 X9 N# s# O0 P
9> CDC_Receive_FS()6 u* S8 l) { E8 Y- F- }0 ~
10> USBD_CDC_ReceivePacket()
8 m* {4 H( S) N/ \5 t, e11> USBD_LL_PrepareReceive(), I H; x7 j1 @) _
12> HAL_PCD_EP_Receive()' p/ i/ X( L+ {
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已# c1 q, D" J/ F, _, j& |5 O
经提前结束,导致接收循环无以为继。2 M' O1 n4 h8 S# R* m' d1 C6 y
+ i* g' w* A# o4 r ?" c2 e) K& u" A3 解决方案
6 B7 ~- I8 d" ]; I! M+ [知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。9 k0 m x, d R
6 g! H* g) J' t4 y$ H4 v
此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
# g0 g0 J1 F& |5 U2 k" A( K: z$ a
. F4 O/ |% b, K8 m) W( }1 [1 e |