1 前言* @7 Q% M! ?6 T. m/ I9 A
在进行 USB 开发的过程中,有多个客户反馈,USB 传输数据时出现卡顿现象。本文将针对这一问题进行分析。
; X3 W/ y$ w" V# q- A* D5 T4 x' x' R3 h, T) V! A
2 问题分析
6 s3 j( }' q+ b" a$ P' `! B这几个客户问题现象基本差不多,采用 STM32 作为 Device 设备,在与上位机或者 PC 端双向通讯一段时间后,从 Device 端到 Host 端的数据能够正常,而从 Host 端到 Device 端的数据异常,也就是说,STM32 在一段时间后不再能正常接收数据,但是,如果只是单向通信,就一直都是正常的。
3 l7 C, _/ ^ p这几个客户,有用 STM32F2 的,也有用 STM32F4 的,有用 CDC 类的,也有用作 HID 设备的,但都使用了 Cube 库。; Y! P! Q6 G) U3 j4 }
下面就具体问题以其中一个客户使用 STM32F411 的 USB CDC 类的案例来分析问题,现象如下 USB 通讯数据(CDC 类): c Z5 a3 L9 ~( U$ T7 o+ m D
: X% J7 N# N5 Y5 E4 @' H
8 @/ Q3 ?# W+ L7 _) ?! a# ^
' l2 G2 [$ ^$ b) |0 m展开 Data Out 数据:
4 A7 @ q4 m9 t8 n
8 K3 C) a$ p6 V1 [8 f8 I% Q
9 m5 E1 ~& d) G1 o* p8 h# l! V% Z
% m, s+ |5 n7 U- u% V2 F分析上图发现,并不是 Host 端没有向 Device 端发送 Data Out 数据,而是确实发送了,但被 Device端 NAK 了。那么为什么会被 NAK 呢?# s' ]6 x" k7 R' j. Q/ J
通过在调试下查看寄存器,我们发现当出现问题时,Data OUT 对应的端点 1 是处于关闭状态,那么为什么端点 1 会关闭?查看 STM32 端的接收代码:( i5 H7 @+ c% h% Z3 M& D" Y0 A
usbd_cdc_if.c:# \7 R. F4 r, v# Y4 v& A
- static int8_t CDC_Receive_FS (uint8_t* Buf, uint32_t *Len) z* |, E. e) r! ^; u
- {
2 m0 L4 F. T: ?6 p" M/ { - /* USER CODE BEGIN 6 */
! x6 M5 i ~1 ^ - //USBTask_ReceiveMsg(Buf, *Len); //UserRxBufferFS
/ {$ ^+ n% B9 w, @2 Y3 o - USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);, F/ R2 c* N. H( |
- USBD_CDC_ReceivePacket(&hUsbDeviceFS);
2 T7 v$ {" v& W5 O- T - return (USBD_OK);0 {, N9 `: D0 Y
- /* USER CODE END 6 */
, q$ c G! O8 B7 N N; C7 h9 Z; G - }
复制代码
' R4 `7 D \* K如上代码,在 MCU 端接收到赖在 Host 端的数据后不做任何处理就立即接收下一次数据传输,问题是,这里对接收到的数据啥也没有做,居然还会出现 Data Out 端点关闭的问题,那么 OUT 端点到底是怎么关闭的呢?我们接下来看子函数:
8 k" I* {: }$ a; y9 X! YCDC_Receive_FS() ->USBD_CDC_ReceivePacket() ->USBD_LL_PrepareReceive() -" v* V2 ^2 M, ~8 L" ~8 g; U6 H! ^6 Z
> HAL_PCD_EP_Receive() ->USB_EPStartXfer()
) x( E ]# B( u" r9 P> 最后在 USB_EPStartXfer 函数中有发现再次使能 OUT 端点的代码:
) R2 ?' p. t2 v$ C- //…
" W* p& R/ z! e0 ^ - else /* OUT endpoint */& Z: \; S8 ?$ A4 \& U6 P4 P% Y
- {
3 g: R3 g5 f3 b* ~' L; T - /* Program the transfer size and packet count as follows:6 z5 v- s% q& G* Q2 E4 [
- ' ]. N6 [" a7 z9 a2 D* C
- * pktcnt = N9 {9 \- r4 V0 ?' E" M3 ^
- * xfersize = N * maxpacket
5 v; a7 p: k6 N% N1 g6 K L$ F- I - */
( N& e# C9 D9 j) X% q! N: U - USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_XFRSIZ);
$ i8 e! g5 { d - USBx_OUTEP(ep->num)->DOEPTSIZ &= ~(USB_OTG_DOEPTSIZ_PKTCNT);( @) x* [: h; U
- if (ep->xfer_len == 0U)
& h" i4 R5 X7 o& F* ` - {/ W6 R( |" }$ ~! ?3 e
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & ep->maxpacket);
2 Q5 B- W1 H3 g X" L/ h( g - USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (1U xfer_len + ep->maxpacket -1U)/ ep->maxpacket;9 |9 L6 G" L7 E7 X: \2 O, q
- USBx_OUTEP(ep->num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_PKTCNT & (pktcnt num)->DOEPTSIZ |= (USB_OTG_DOEPTSIZ_XFRSIZ & (ep->maxpacket *
: L W- C$ m% ?# E' ^5 g1 V - pktcnt));
7 L9 N' V# n; O - }, ` @& H5 A9 M |$ X& m3 D
- if (dma == 1U) D2 Y v- z8 Z/ h" S& l
- {) j0 ^; P; B( E+ r/ D. T
- USBx_OUTEP(ep->num)->DOEPDMA = (uint32_t)ep->xfer_buff;4 g. M' H, m' R, Y3 k+ w' m
- }2 i: N5 B. U9 R: S
- ) U) E- ]" M) M2 X0 K5 c3 a
- if (ep->type == EP_TYPE_ISOC)
/ x2 U, k% c' n7 ?% W( I, M: { - {
; D' k8 Q& g* k( u; h- J - if ((USBx_DEVICE->DSTS & ( 1U num)->DOEPCTL |= USB_OTG_DOEPCTL_SODDFRM;
! y+ l+ D/ ?* x( M - }! y$ m; i1 d1 q4 \2 D- A
- else
* K) r- j& \1 ?( l - {
) @. S; K+ t% I+ S$ K - USBx_OUTEP(ep->num)->DOEPCTL |= USB_OTG_DOEPCTL_SD0PID_SEVNFRM;
q$ w; F- ?# [& z - }; s3 I1 N2 q" k6 F' {6 E, s- |8 [# W
- }
8 _: @( ^" Z. _% M; G/ Q/ p - /* EP enable */3 M2 i& [% Z1 k( r0 _/ g0 H
- USBx_OUTEP(ep->num)->DOEPCTL |= (USB_OTG_DOEPCTL_CNAK | USB_OTG_DOEPCTL_EPENA);$ I+ [: b" ?7 i! g
- }
复制代码
2 a) o! q! A& N/ E也就是说,在调用这个函数之前这个 OUT 端点原本就是关闭的?真的吗?这怎么跟我们理解的不一样?不应该是 OUT 端点一旦打开就一直开着的吗?带着这些疑问,我们查看 STM32F411 的参考手册,终于在 22.17.6 Operational model 一节中找到这么一幅图(由于 CDC 类数据传输采用的是 BULK 传输):
i \1 c, `9 [7 v7 _/ G" Q, S- K3 g$ C% N3 y: D
$ p f9 W. j# N" m) m8 _- a
* U8 J M, ~8 f5 v- w$ p
如上图,MCU 对 BULK 类型的 OUT 数据处理例程大体如下:& p2 c) S" D2 i4 E( K
1> Host 端试图向一个端点发送 OUT token;
/ b9 a6 {& G1 M& ?2 n9 @9 b2> 当 Device 端的 USB 外设接收到这么一个 OUT token 后,如果 RXFIFO 空间足够,它将数据包存储到 RXFIFO 中;4 Q7 p6 @0 M" U2 U! F$ q
3> 在将数据包内容存储到 RXFIFO 后,USB 外设将产生一个 RXFLVL 中断(OTG_FS_GINTSTS);
K$ [* g9 e `4 C% k0 H4> 在接收到 USB 数据包的个数后(PKTCNT),USB 核将内部自动将这个 OUT 端点的 NAK 为置 1,以阻止接收更多数据包;* b. c" K5 E+ V0 L
5> 应用程序处理 RXFLVL 中断和从 RXFIFO 读取数据;
0 ?1 \ P% ]! q" u1 p! w' D% }6> 当应用读取完所有数据(等于 XFRSIZ)后,USB 核将产生一个 XFRC 中断(OTG_FS_DOEPINTx);& T$ v: P \: t" ^0 O: M D
7> 应用处理这个 OTG_FS_DOEPINTx 中断并通过 OTG_FS_DOEPINTx 的中断为 XFRC 来判断传输完成;/ Y K' r5 `8 t% Y2 K3 M' m
# T2 o: t0 k4 G+ y& T R* {8 [从上面步骤中的第 4 步中可以看出,当 USB 核收到来自 Host 端的数据后会自动将 OUT 端点关闭,这也就是为什么在接收函数中在接收下一次数据时要再次使能这个 OUT 端点的原因。因此我们大体可以判断出在 OUT 数据传输的过程中,USB 核会禁止端点->打开端点->禁止端点…如此不断循环中;那么问题到底出现在哪里呢?会不会在 USB 核自动关闭端点后就没有再次成功打开?带着这样的怀疑心态逐句查看代码,最终在接收函数的子函数中发现这么一段代码:
7 a4 o' [& O1 Z8 P* c- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t' U2 C9 A$ O* |% a
- *pBuf, uint32_t len). z/ J; T0 ]1 @ n/ s
- {) G3 |3 b# i, h: b
- USB_OTG_EPTypeDef *ep;
]& @" X9 V$ {4 F8 [2 ?' l6 w( x, c2 o - / h( {. \! `7 i Q4 z. Y: l
- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
7 A4 f# B5 c2 t, x& j* K$ N2 j
- A! q1 t7 j/ g# x% M- d- /*setup and start the Xfer */3 S' m4 J5 \+ O- K5 U6 ^
- ep->xfer_buff = pBuf;
- I* ]2 C, K& h) [ - ep->xfer_len = len;
; ^8 j( B# F: Y" i. b- ~ - ep->xfer_count = 0U;
1 T$ X4 C6 ?2 c' {2 w; k: Z' l - ep->is_in = 0U;& _9 W: O4 k3 U2 n: n; w
- ep->num = ep_addr & 0x7FU;
8 Z! i8 `; v; t& h# W
2 m! Q* h+ ^0 S+ z2 G7 D) o- if (hpcd->Init.dma_enable == 1U)$ U! A1 M# p7 T, X4 o
- {1 J, x7 E# h; ^2 C' Z: a7 h& z
- ep->dma_addr = (uint32_t)pBuf;
) J0 A4 X% P. | - } ] O. \8 [( ^- F) ~# H" ]
- % E$ |4 J! m% Z
- __HAL_LOCK(hpcd);) i- Y' b" v; {
/ |% p' [* m5 P% { W: q- if ((ep_addr & 0x7FU) == 0U)" y( W4 D4 T: w. W0 W6 d
- {: F# s' S$ U- B2 M" @ T% P( C
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);0 k, q& f4 B& D/ x% J
- } E5 K5 x' x! {' {3 f4 j; ^3 O
- else
- q" U! t8 P( J9 l. a3 O" | - {
* _- t; B, i! K - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);5 F4 D; y3 n; Y8 t- {
- }' K) u* M. E0 m
- __HAL_UNLOCK(hpcd); ( U0 K3 A; k. @; s
- return HAL_OK;
1 T2 J& t3 o# i3 s) o - }
复制代码 ! u* e/ f) i0 a. r2 k
; [& r) g6 S( X) b) R7 i$ F
之所以会怀疑这里,这是客户提供了一个信息,单向通信的时候就不会有问题!这是因为在发送数据时,发送函数的底层函数内也使用到了这个互斥锁:
9 o1 w( W/ Y' m- CDC_Transmit_FS() -> USBD_CDC_TransmitPacket() -> USBD_LL_Transmit() ->
9 L6 O# A& r0 q* B- N! a: K4 } - HAL_PCD_EP_Transmit() :
6 R$ _( h& x/ D - HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
L( Y1 u! s! m7 c2 U - *pBuf, uint32_t len)2 i) o0 I) ?. [( |$ y" }7 L
- {* h- n9 z. z, ^- e: s& j+ v
- USB_OTG_EPTypeDef *ep;
) ^" B$ A1 [; F" B - : c' E$ O/ Z4 P/ O; R
- ep = &hpcd->IN_ep[ep_addr & 0x7FU];
; [7 ^4 f7 f" g ] - /*setup and start the Xfer */- M; {6 i/ x% b4 u
- ep->xfer_buff = pBuf;
, a+ k8 q3 R5 \- C3 y+ E - ep->xfer_len = len;
1 `7 Y8 }' g$ f3 ` - ep->xfer_count = 0U;
- N( m# p y7 \. o# b - ep->is_in = 1U;; O: i5 \, e- g1 j
- ep->num = ep_addr & 0x7FU;8 Y4 P8 I) I l: F4 c5 U* h! T0 q
- ; Q2 A, B* I2 Y# m9 e
- if (hpcd->Init.dma_enable == 1U)
+ S+ C4 L; G0 k - {
$ j! ] V4 Q' l, W' A - ep->dma_addr = (uint32_t)pBuf;4 E1 W/ V' b% k" q5 e( H/ x4 F
- }2 G9 q% p# c1 Y, |! G& G
2 T5 d0 S# H. N8 B0 f; l$ i5 h- __HAL_LOCK(hpcd);4 k1 |+ D3 i' { |8 Y6 v# j/ K
- ! r# v* p& y0 T
- if ((ep_addr & 0x7FU) == 0U)
6 e1 \ T5 v# v% x - {
4 n8 M- F/ ~% N! E - USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
7 m. Q3 t9 W1 D& w0 v. U ` - }4 _- h4 U. \# t1 \) Y
- else- T+ p) J# d0 L' v% M$ l
- {7 z# @" [9 Q" O/ l/ I
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
- J0 t/ M* M; W5 h" f# B$ d - }
6 C) X% X* a! c- v$ i2 @
3 U" s3 V1 {* Y9 N" q- __HAL_UNLOCK(hpcd);) t9 n/ ]; J9 @. t2 d$ [
- return HAL_OK;
! P1 x& w$ K% h- l9 L9 u# K - }3 i+ b) V/ U& H& b: r5 d
复制代码 ; ?! d, t. t% `3 M0 G' R& I0 F
接收处理数据时,底层是通过接收中断回调上来的,但发送时,我们往往将发送放到 main 等用户函数中。这两个是不一样的,一个在中断内,一个在中断外,优先级别是不一样的,优先级不一样就有可能导致资源冲突;5 p5 ~" s- V* z" v0 P
/ h- F- t6 c, r: A0 l3 t& B
我们进一步查看__HAL_LOCK()宏定义:
: z: n0 M; F& f1 Z0 O3 K+ B- #define __HAL_LOCK(__HANDLE__) & j6 s/ e4 R! b- m; m5 z3 M' V
- do{
+ Y) u* h' D+ y' E) p! V - if((__HANDLE__)->Lock == HAL_LOCKED) ; B+ K; O. U2 e$ W
- {
7 D+ N8 E1 u2 {' f* w - return HAL_BUSY;
& Z4 C8 a2 @: [ - }
( B4 C% I& ^, a; T9 k - else 8 \) M% D( w% V
- {
}, O1 R4 P& |# F! R7 D6 \ - (__HANDLE__)->Lock = HAL_LOCKED;
/ G" @/ ?; S4 T" ]3 w }$ G8 g - } ( c2 |1 Q1 D/ W, b
- }while (0U)
复制代码 + O: |1 i7 F& _* {7 H( B& e
若__HAL_LOCK(hpcd);失败则直接返回 return HAL_BUSY 的。为了验证在接收过程中是否__HAL_LOCK 失败,我们引进全局变量 Lock_Flag,在发送函数中若成功 LOCK 则设置Lock_Flag=1,UNLOCK 后则复位为 0:* @2 [3 s9 h% U: m) N& F
- uint8_t Lock_Flag =0;( W; s% d: C1 M7 \1 g
- HAL_StatusTypeDef HAL_PCD_EP_Transmit(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t/ b+ \% }$ ?3 Z4 }
- *pBuf, uint32_t len)8 c' Z8 i* S$ v% `
- {
/ }. Z2 i" v' V8 a) `3 |& a - USB_OTG_EPTypeDef *ep;! d: f7 i3 I1 m' A* J' Z, b4 i7 _' p$ \' U
- / k! e( k3 o% r( J* p- r
- ep = &hpcd->IN_ep[ep_addr & 0x7FU];, M$ \& K. k, t) h3 k
4 \0 i9 U" y, u4 R3 p0 Z7 X) l2 J- /*setup and start the Xfer */9 w$ @; o8 Q) W( R
- ep->xfer_buff = pBuf;% G! \6 }5 Y4 ]; k
- ep->xfer_len = len;
, n7 j, I$ d. I7 L% Q, f - ep->xfer_count = 0U;4 y8 j y7 w" K: B- }
- ep->is_in = 1U;8 M4 U* o: ~. o! @( E9 b+ ^
- ep->num = ep_addr & 0x7FU;/ ?0 N3 a5 |* _# E, F g2 s
- $ ~7 E7 I( i9 H$ L
- if (hpcd->Init.dma_enable == 1U)
w% F* n4 w' c# p7 _ - {0 z8 J; [* Y* f/ g3 l; M- z
- ep->dma_addr = (uint32_t)pBuf;
- i' o2 ?4 [% B3 s- u, T# R - }: {8 C2 i$ T; R9 H( N H( P
- ' U7 F$ y( b5 d3 M9 F* ]
- __HAL_LOCK(hpcd);
4 x8 }4 S( z' [# u1 C - Lock_Flag =1;
. n7 t' N2 B) G7 v6 t - 6 u# d2 G; |: B! _( }$ G
- if ((ep_addr & 0x7FU) == 0U)) m C Z/ S0 O% N
- {0 e+ G$ K( A& O. c/ ~* u; W
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
) f0 A6 o5 f3 Q - }
% a( G2 T- O: `1 d. }8 d - else
7 F: r! w- T: d- P& E( s - {- G7 E3 X6 W! C+ y; K0 j, E' Q( S- _
- USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
6 S% [4 @0 L: t. s8 \ - }* O2 [5 z9 M& F R3 b) ]$ W- T+ w
' r0 K2 E! W8 b, }* q8 f- __HAL_UNLOCK(hpcd);
. X: Z: t- V- z - Lock_Flag =0;
" B5 p- F; D% D3 I - return HAL_OK;
" D& g8 m( T7 J" C4 n - }
复制代码
4 c$ J* m' k. `接下来在接收函数中对全局变量 Lock_Flag 值进行判断,若为 1 则锁死程序,因为在 Lock_Flag=1 时,则表示发送函数中已经获取了锁没有释放,此时若再去获取则会导致失败从而返回 HAL_BUSY;这里通过锁死代码以便判断这种情况:1 U3 ?* @; V) t# }
- HAL_StatusTypeDef HAL_PCD_EP_Receive(PCD_HandleTypeDef *hpcd, uint8_t ep_addr, uint8_t
: {% K5 [0 Q4 D r - *pBuf, uint32_t len)
9 I, P" _/ ^, }$ i0 a+ Q7 Z6 p2 v - {! Y& X6 g; ]- h7 O! B1 l
- USB_OTG_EPTypeDef *ep;
* S8 l* w1 v& l - " r8 W2 {* v- s8 O
- ep = &hpcd->OUT_ep[ep_addr & 0x7FU];
6 b- J' }/ i) N) c; { - 6 F0 I5 z- |7 ?( u+ x9 O0 Z4 y: @
- /*setup and start the Xfer */1 `* H& B- P9 |3 F! v
- ep->xfer_buff = pBuf;
+ `, [; T$ J. B( S - ep->xfer_len = len;
1 j, t! L' [! j4 w2 E - ep->xfer_count = 0U;
0 k; G) g3 C8 k7 ?" W, T - ep->is_in = 0U;/ S7 ~6 u7 c% ]7 ?4 ]9 A/ ?
- ep->num = ep_addr & 0x7FU;
1 o# `( w- j- [ G2 D; G - 6 y1 s/ U6 k, _' B; U2 a/ G
- if (hpcd->Init.dma_enable == 1U)- y3 y- S* S5 u/ i) d& a2 O
- {: |5 L2 e6 [. a" G$ Z D* X% Q- g
- ep->dma_addr = (uint32_t)pBuf;5 V) E2 V1 p; |% f* ?/ d9 B
- }( V8 w% d0 B8 T0 S* L Q
4 I% H8 M# I: ~- if(Lock_Flag ==1)
0 s2 j0 E! ~3 b5 I* Q/ ? - {) A% z: w# r4 L( E9 m# N7 \
- while(1);1 h/ [8 l8 W1 f, n5 @1 m
- }/ W! e& ?; O( ]$ {8 i
- __HAL_LOCK(hpcd);
4 M4 v$ s3 r( d, }( n& C" M4 ^6 ^ - , g3 y/ p$ H( h6 p4 w* s
- if ((ep_addr & 0x7FU) == 0U)2 x/ M6 ^' d( ^% g/ M
- {% Y! w' T7 u+ E/ ]3 P3 I
- USB_EP0StartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);
5 Y8 M; T; _& {( ]% s1 v7 H9 c - }' h g# V" a0 ^6 @
- else- H9 C4 \: H" L; _& O
- {
- r$ H) {4 M8 H; n5 @: Z" g - USB_EPStartXfer(hpcd->Instance , ep, hpcd->Init.dma_enable);6 k9 l: A1 R u
- }
; `: `# E+ }# f- Y, V - __HAL_UNLOCK(hpcd);
6 ]0 S: F4 `5 u" B
- J5 ~! `% h. ]- return HAL_OK;
" ?; O0 y/ o* W - }
复制代码 # Y# e0 N' b5 l; P I( H. A
通过调试,当出现问题时,程序果然被锁死在这个 while(1)了,这也证明了正是这个互斥锁所致。因此,我们大体可以判断出现问题时流程大致如下:4 ]4 Q4 }$ x$ Y5 i* `& }" _
1> 在 mian 函数中发送数据 CDC_Transmit_FS()
2 q; B1 K: `# s( `$ K2> USBD_CDC_TransmitPacket()6 L6 l6 T, n7 Q: p2 E
3> USBD_LL_Transmit()$ _2 a, v+ V' H; r$ ^- H
4> HAL_PCD_EP_Transmit()+ ^! F \ ~1 O x h. G6 V
5> __HAL_LOCK(hpcd); 此时成功获取互斥锁- C- }. x3 Z( h: T0 W' U
6> 恰好此时有一个接收中断,由于 USB 中断具有优先级,跳转到接收中断内执行;同时,USB 核会自动关闭 OUT 端点;
; f# L) f' Q# M- e/ x$ X T7> HAL_PCD_DataOutStageCallback()
, n+ E3 q0 v7 z3 Y4 K$ C8> USBD_CDC_DataOut()6 t9 O$ a1 J# _; e# s7 q
9> CDC_Receive_FS(). R8 D) w( |" v# s" S* I
10> USBD_CDC_ReceivePacket()6 [8 |- b: X' [$ R3 I+ s/ O& s$ O
11> USBD_LL_PrepareReceive(): X0 e$ T! v" m1 Q* S
12> HAL_PCD_EP_Receive()
# a* `' k& M, y1 G& F13> __HAL_LOCK(hpcd); 此时获取互斥锁失败导致返回,接收函数在 OUT 端点没有再次打开就已, }" f& o8 W% j9 B
经提前结束,导致接收循环无以为继。+ C8 t& u y. L+ f5 L& W
7 Y* Y2 f2 l" a4 ?$ Q3 n3 解决方案. E1 R2 k; _1 d* e
知道了问题原因所在,接下来解决问题就相对来说比较容易的了。由于此问题是发送与接收处于不同优先等级导致资源冲突所致,那么我们可以将发送也放到与 USB 接收中断相同的中断等级中去,例如可以利用 USB 的 EOPF 中断,在开启 EOPF 中断后,在此中断内发送数据,这样发送与接收中断就处于相同等级了,EOPF 每 1ms 触发一次,速度完全可以。当然开启一个相同优先级的定时器来做发送数据也是可以,只不过定时器间隔得控制好。& N# l: n, c8 _, {1 s3 E
" B( t' D* G4 I7 Q, [ \此外,其实此问题是出现在 Cube 库的低版本中,例如 CubeF4 V1.5.0 和 CubeF2 V1.3.0 中都存在,但是在最新本的 CubeF4 V1.16.0,CubeF2 V1.6.0 版本中此问题得到了解决;此问题虽然后来发现是版本太旧所致,但从多个客户反馈此问题来看,此问题依然不失为一个很好的参考和教训。
' y5 o ~$ \& b0 r' v. M) `# t; f
. U" I5 \) ^& x: S% J o6 n3 | |