1 前言5 W' O; f- |8 F: @$ w
在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。) q& A+ ]7 T5 d1 [9 v5 ` I8 B
9 U" ]/ G7 ^8 p3 S+ P; C- e, V
2 问题分析
& g. r1 k* C+ \6 q3 ]0 S3 i这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
2 s9 d. _2 Z' X) d' u+ T* t: v这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。5 m. q$ e7 K' W6 ^) e) t; c
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类):
$ K$ W8 v" A. E6 w; M& i
# R( C! L; C i) q9 o! `
5 ~3 p# N+ D2 V( q) s; I* m: D) P, x+ Q) c; B
展开 Data Out 数据:
! \; S* ]2 y2 W
e9 D# h; l s) M
7 W* v3 O1 z( S* @) a7 @& S3 A1 i
3 |$ q, \4 H, v5 d/ U' Y分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?& W+ g, }! i/ W4 F
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:& x5 Z8 x8 u: ], w( X
usbd_cdc_if.c:& u$ x5 |5 \; l- j9 U
- static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len)
" B- e1 r) G7 R- ~2 M - {
. x! K% r. n# S: z4 M" Z - /* USER CODE BEGIN 6 */5 T/ @" r( u, d2 _
- //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
- n, e. {+ k% F# N - USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
3 `! E: l" ~* K - USBD_CDC_ReceivePacket(&hUsbDeviceFS);
5 C4 c I2 Q4 N% n* W* B9 B1 X) A - return (USBD_OK);
% f- D/ B g; y/ L: P - /* USER CODE END 6 */
- y, O/ }8 o% V: `9 `' k - }
复制代码
7 d' g1 p4 G: `9 |如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:1 \3 E# x, L( X
CDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -
: |* m5 r$ z1 |> HAL_PCD_EP_Receive() ->USB_EPStartXfer()- L4 [2 k9 x' T: d" C: Q
> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
+ q/ V# q4 O8 Z ^ m* I7 n! Y- //…, ?7 x. u3 X& a1 s
- else /* OUT endpoint */7 ?( p2 w I5 c8 O
- {
8 p1 }/ V7 P% F5 H) A; E' V2 O - /* Program the transfer size and packet count as follows:( O( v0 D% h) Q4 z
; J6 g: D* q/ A- m* }. {+ }- * pktcnt = N
; a/ `% J, ]% d2 p* Q - * xfersize = N * maxpacket
3 H/ @( U* q( b: }6 @ - */! I" ~; S, o' }8 z& G1 Q
- USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
p+ H1 E: o' Q; p4 X) F/ u4 E! K - USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);
! T! x1 Y7 t, s a* B9 k3 R8 |. ~ - if (ep->xfer_len == 0U)
6 P. E0 h( }, U" i - {
- N& G6 q3 H/ v. z0 o5 f - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
/ s3 W" L5 S% _0 U) h' n! t" g - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;2 n. H& ^* j) b$ d; j- ]1 h
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *0 i/ @/ M/ ]+ m& {; [% _
- pktcnt));
) f/ e$ F% A* B( r - }
W" T: R% e$ G/ B1 n7 @ - if (dma == 1U)
, U, L, N" s! p4 Z4 X1 l5 }& p9 t - {- R( u9 [% S# i" H b9 m- K
- USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;
$ v c" b8 C" I1 ] - }0 _0 a8 \8 e& X7 P
8 \7 H8 @- G4 \/ Y; H- if (ep->type == EP_TYPE_ISOC)4 U* G7 R; v/ D. a
- {
0 O( g a$ y1 ] - if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
3 Z# p! w$ G* k9 j9 p - }% b# ^; q$ A+ h' o4 v3 f* w
- else4 ^% [5 D- C+ I6 p8 }% M7 z
- {
! c' s- Q& q' S. | - USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
0 T6 y3 R$ n2 g u - }
2 N. c3 l- p% }1 q$ [ - }) s6 K- J' w# E3 ]
- /* EP enable */; g* K9 B( T9 V- \/ G2 p+ s% P* B
- USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);
6 x& V5 }2 j _, y - }
复制代码
$ {) U7 f2 D3 T: v也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
+ H0 N3 J$ ?7 o9 f
. A) m- \& F7 Z* D3 E. j
" [2 z4 L+ P. T! u4 k( x$ S
" i7 }. k0 W6 K" ?0 n5 ^, D' h2 K% Y如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:
1 L( C* a3 y Y1> Host 端试图向一个端点发送 OUT token;& Y, E# r2 L% V% b9 s
2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;, d- o1 p7 X/ P- H. J9 j4 t( m
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);! S2 s# c* B' U* {, H
4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;8 Q6 Q, b/ R1 m+ n0 n( d/ s
5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
6 H4 o. y( b. [; u6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);8 ?. T9 t- p6 ]2 F. T
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;
+ @( _2 G: j# E+ y9 h
" a& F& G' V- R, L从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:, g& B% b1 G6 C! v! D
- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
6 J# N: R2 n& w7 x8 H. [ - *pBuf, uint32_t len)3 ?# S' } ]: N- o4 `- @; c+ @# a, o8 z
- {; D% H% J/ [" Y( O- W i
- USB_OTG_EPTypeDef *ep;3 ^0 w% u" _/ M2 g$ l9 I
, B1 n' k% c2 z9 s! L9 w3 l4 M- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
5 b5 ~" O: o2 C& Q) V - 3 {) O9 x* X p& G7 d* e
- /*setup and start the Xfer */
/ V) P7 A' w9 L8 P& h/ ` - ep->xfer_buff = pBuf;
/ H% V9 \" M2 }% L/ r' O. l - ep->xfer_len = len;
8 U6 ?* F( V+ h T% |+ [- H, w7 D - ep->xfer_count = 0U;
3 q6 v* h; O! P) U7 g: p - ep->is_in = 0U;) ^5 u; w) `* ]% l
- ep->num = ep_addr & 0x7FU;% m- s, O8 w% [( ?: c
- 9 W6 K% o# O% W% b9 v
- if (hpcd->Init.dma_enable == 1U)* j2 ` @, T3 B6 c" A
- {$ N1 D; R& y$ g- A: A5 O0 A
- ep->dma_addr = (uint32_t)pBuf;
4 Z" l O8 W, w, L* v# l, e7 V - }& \( t' _/ o3 R; ]# Y
+ P" [7 I# s& S$ B0 |- __HAL_LOCK(hpcd);8 |1 W) ?: E. \' `$ s" m
* I) f# o0 j: K$ c; r G3 H- if ((ep_addr & 0x7FU) == 0U)9 U6 X) x$ s7 J2 k
- {
- r/ a6 z1 r( d3 D5 N: L& w2 k - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);, s$ f0 S" P4 _& o* X$ n7 _1 B' B" V
- }2 V; ?8 s3 o) o( o9 H1 Z
- else% e% i7 Q# s" K, e0 G3 U) l
- {
1 |: a' i: p+ f - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
+ R7 P- z' |* u( h L9 |6 j6 i - }. y3 M9 I; K/ a* ?7 h7 b4 R @
- __HAL_UNLOCK(hpcd);
# O' i ~# h; w - return HAL_OK;
% E/ j% u5 n' e0 Z4 I3 s+ Z4 d - }
复制代码 ( ^7 I+ J# ^$ @# H6 a
6 ? h+ h* {0 B0 L0 n7 m5 O之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:
$ _9 Q4 U) w+ y- CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->' v- }, f J5 s5 w2 Y
- HAL_PCD_EP_Transmit() :
. A9 a9 @; h, g0 k0 A6 e - HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t- y1 J2 ~" @0 D6 Y6 T
- *pBuf, uint32_t len)+ c) F5 _) h' _$ X+ [' q
- {" ?" Y# @# a, b3 L; t" P
- USB_OTG_EPTypeDef *ep;$ M% i3 x( K7 w& ?+ h) c: c
- ( ?, [% |" w; K
- ep = &hpcd->IN_ep[ep_addr & 0x7FU];
" A; b5 L. j2 o1 ]4 w7 N! C+ O4 w - /*setup and start the Xfer */
5 ~( J1 Y/ I; @8 {& M. R, U - ep->xfer_buff = pBuf;
6 }; S. Q$ e. @% x x$ J - ep->xfer_len = len;( X& P7 d3 e) J# y' h( T8 I
- ep->xfer_count = 0U;
6 C- i* R4 f' y - ep->is_in = 1U;
0 Z3 b* @7 a r - ep->num = ep_addr & 0x7FU;- n. M6 k0 Y; K9 e7 W
1 }% P4 j$ a' c2 H( F4 q- if (hpcd->Init.dma_enable == 1U)
6 q" M; C2 M/ u# ~0 Z - {
6 S3 @: B& t( v0 ]$ Q - ep->dma_addr = (uint32_t)pBuf;. R$ { q m, V+ U$ R& H% _
- }9 K! J* ~$ L/ I0 r3 y3 U
' k; w" ~1 L' U$ Q* ~- __HAL_LOCK(hpcd);! [+ x: y! }8 g
" R2 {# X8 ?% N) }1 E7 G0 q& ?- if ((ep_addr & 0x7FU) == 0U)+ \ ~* n7 c- u9 @2 t, i
- {
- Y1 q% X. W; m8 p6 z& V" r1 s - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
# M" M2 N7 P( q - }! ]) P3 s, T' g7 J- J/ Z$ v
- else6 R5 f% U7 m4 r! _$ P
- {
" f( [2 `2 f4 D, A - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
. Q$ ?3 l9 }( P# q5 g - }7 |5 }0 R; S$ d6 Q( n5 M
( L+ p! ]% Z w" P4 c* ~$ u8 l- __HAL_UNLOCK(hpcd);
8 d1 j7 T* \$ o6 `, _; W4 l( U - return HAL_OK;1 ~- i% f' K% B% d
- }" x. @5 x$ V+ M( H x+ {" a% Z
复制代码 ' c- m( ~0 `; m) c3 e% u4 h& s
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;) p+ c$ m6 }! ?% \# ]
( ]; U D; f# |9 Z5 J
我们进一步查看__HAL_LOCK()宏定义:5 n4 X' {2 R2 q4 P# g' j
- #define __HAL_LOCK(__HANDLE__)
: `+ }" }+ k D T0 ^ - do{
, Z( r, |3 ] c' A$ O2 b& Z - if((__HANDLE__)->Lock == HAL_LOCKED) U! w% r& }/ t5 B' c
- { 7 X) f- W2 {! H+ k o
- return HAL_BUSY; 3 l) e7 {- P8 Z- i9 @
- }
# n2 z9 Y* I( R. h0 h+ y - else 6 ^( \' j$ {+ U
- {
, T7 m. W' {3 i4 q9 K! { - (__HANDLE__)->Lock = HAL_LOCKED;
+ q4 p: e: ~: o* s6 X, s - } , ~ Y! C$ I! ^) C
- }while (0U)
复制代码 " o0 x9 ] X. ]- j3 T; `; b" Y! X
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:( U$ ^ y1 k [0 W
- uint8_t Lock_Flag =0;3 R- Z$ m! x! j6 A0 f9 R. _* t3 ]
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
R. s1 o% V x) }8 u - *pBuf, uint32_t len)2 {- G- J/ i s9 @
- {6 S `0 C6 }* ~/ A0 g4 R
- USB_OTG_EPTypeDef *ep;+ e* L, {7 t0 C A
- - Y) }5 c( ?9 x8 c @. N
- ep = &hpcd->IN_ep[ep_addr & 0x7FU];$ m H; @9 M5 k+ ~( i
" q. n+ i6 A3 t" b/ X' i8 ?% R- /*setup and start the Xfer */
6 m6 Z2 |6 ^: R1 d: t# A& w, f F1 u - ep->xfer_buff = pBuf;& q* a3 z; j2 [& E
- ep->xfer_len = len;
* m; U% ^/ n2 Y9 p8 L( J8 j. o' B - ep->xfer_count = 0U;) Z5 r# n+ D- \. h1 _
- ep->is_in = 1U;$ X' P4 i" M1 T9 j ]3 a: J$ o4 ^$ `
- ep->num = ep_addr & 0x7FU;
* ^& [: [5 k0 f* Z! q* o6 T" n
4 ^. T a5 M6 \& s& [- if (hpcd->Init.dma_enable == 1U)5 A. E4 J3 {0 E+ v% q8 A
- {
+ u h5 ^: j6 A- L - ep->dma_addr = (uint32_t)pBuf;
" A& g/ f" [. O" W* b) t - }
: f: {+ \6 e s - $ y+ f: Q5 z! Q0 j# m, g
- __HAL_LOCK(hpcd);0 `2 q- ^( Q+ z% q) ?" X" J
- Lock_Flag =1;( ?- r' L' `: ?- b% n
- 6 q% H" N1 P4 H0 X
- if ((ep_addr & 0x7FU) == 0U)5 v4 H0 G8 u6 r( g% @% Y4 d5 Y- Y
- {
9 E% a( j, A. w$ R - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
/ a6 ^0 }/ @0 N, V. X/ n - }
! s$ h, L% J3 U& }. A8 }- x# H - else
$ r( Y7 n5 \0 ^& c, J' g, m R5 j - {
8 d* b P- L! ]5 n" x( w/ G9 F9 M - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);- W0 Z: k/ U6 h) l, s, l- G" K
- }( `. h' H1 C _0 W; W8 h7 e
# X; j0 T' f% E% r- __HAL_UNLOCK(hpcd);
# M4 i6 O) q* i; h; I - Lock_Flag =0;
, ^% `/ E# U, O - return HAL_OK;6 ?- E2 J& i1 D! H
- }
复制代码
1 w, b3 ^9 t* |/ E$ {7 Z接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:
4 w9 M' {; e9 T' o- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
% i% X, D5 a4 |7 v* _* X - *pBuf, uint32_t len)
, {" [: E- P' i+ J1 K+ U& q1 T( Y - {
7 G$ Z, E8 Y$ a ?4 v - USB_OTG_EPTypeDef *ep;
& L& v1 T! u4 S, ]+ @$ O+ X - ! h" y9 q8 i6 ?( l
- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];$ F$ O% l& M' S3 h, |/ J
- ! a X2 X3 `# w; U+ ^8 ?
- /*setup and start the Xfer */
( I8 p, U" ^6 Y; X - ep->xfer_buff = pBuf;
- {0 t: j7 j. m$ W5 \# e( B8 ^2 y - ep->xfer_len = len;
$ J% h8 B3 l0 z" P1 m- E! a - ep->xfer_count = 0U;
* m# j# \2 ^5 o' j: d: T - ep->is_in = 0U;
1 |( L* S4 Q% S9 d3 s5 S1 v( V - ep->num = ep_addr & 0x7FU;6 i: n5 l! u) |% @- _
- * ^& g+ W. a6 s3 F9 j5 A1 }! @
- if (hpcd->Init.dma_enable == 1U)' Z: h& b0 v* a5 f
- {
) g6 k- T9 w1 s$ Y7 G - ep->dma_addr = (uint32_t)pBuf;
( j# v1 B: ^$ Q3 o - }4 s6 ]) u* l/ u, c j
- 3 ]$ H: O$ e6 {9 D% P. V
- if(Lock_Flag ==1)
9 i3 Q/ W% R) j; n% g' ?& w+ B - {, l+ q" i' x0 [! S0 y
- while(1); `7 A1 A! }4 P& _/ A5 f4 s! {
- }1 y0 K' B3 v* }8 m
- __HAL_LOCK(hpcd);
( V8 m0 I0 v1 n4 P1 J4 n
" x [$ U8 a9 x- if ((ep_addr & 0x7FU) == 0U)' B3 c5 ^- n8 \1 R- H B
- { k5 l- ~4 k- N* ]3 v
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
* B! ^, T* U& d" B8 F' x' L - }* g5 S, m; h( \* S. `8 o! O7 U1 M
- else2 C9 K4 Y d( K" s) R5 o
- {, d; A6 m7 c, L6 @- ]
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);) `# M8 r$ w3 p+ H; o
- }
) w+ f Z( I( T' K, P - __HAL_UNLOCK(hpcd);
, j& j% m$ s; J# H. j
$ C- u& y, d9 O, F5 e- return HAL_OK; i8 F0 o/ _$ z4 `2 l4 i4 e
- }
复制代码
4 f- X! E! X# w5 z. n通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:
& P9 K1 E3 o1 D% o# |" c9 l1> 在 mian 函数中发送数据 CDC_Transmit_FS()3 t4 i' ~3 F6 `2 N+ r
2> USBD_CDC_TransmitPacket()1 v" P) V6 G4 f6 u
3> USBD_LL_Transmit(); p) h( Q: I2 w. |; F
4> HAL_PCD_EP_Transmit()) ^' V3 ~7 r! G w+ ~
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁+ ?$ V# `4 g+ E5 G0 i5 i
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
& p" ] ^5 f/ K' A+ @7> HAL_PCD_DataOutStageCallback()
; Z* }/ B8 M( s$ Q8> USBD_CDC_DataOut()
- Q( D c) x* z: m% U; x x9> CDC_Receive_FS()
k/ x1 I( o3 Z10> USBD_CDC_ReceivePacket()& I& m6 ?; v- U9 g
11> USBD_LL_PrepareReceive(): K& a3 G3 t4 n3 O7 l8 i
12> HAL_PCD_EP_Receive()8 w% D' c% W" J- c: ~
13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已% _1 M* Q# t9 p! m8 k# }! c# M. t; @# C
经提前结束,导致接收循环无以为继。4 ~( @2 r3 q- E+ `
0 T0 c0 y6 [5 X3 解决方案
. C/ z8 }! I+ g. m% z知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。
( T& ^- ^$ H+ H$ N8 z2 d* z# i C, G7 {0 S+ @: R$ I$ k
此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
; K" N6 R [" G9 a' u& s5 e+ O
5 b4 H* D# T1 x0 K) q4 c |