1 前言
$ U, ^' ?+ ^: t v; k( U+ L本文将针对 STM32F767 的 USB_OTG_FS 模块工作在 Host 模式下的挂起与恢复以及远程唤醒方面的问题进行讨论。
$ Q6 e1 G6 T" ] S8 d8 I
9 C3 `: _6 z4 R5 R6 S9 d# W' [2 问题描述
+ b6 y) l- @0 v" [# U# I) W b客户使用 STM32F767 做项目,使用 USB 作为 Host,客户发现 USB 在进入到挂起状态后通过远程唤醒信号并不能将其唤醒。
# y# D0 Y* J8 M2 Z5 C9 m# P N$ s7 A
' ?/ D/ K1 c- y2 t! M# ]; o& o3 问题分析与解决
* U4 z ?6 ]' O7 ^$ I- w5 i经过与客户邮件电话沟通发现用户是通过操作寄存器 OTG_HPRT 的 PSUSP,与 PRE 位来实 USB 总线的挂起与恢复,发现 USB 总线的挂起与恢复会有问题。3 q9 L! ], m! F2 ~# W
$ k2 F4 ?; o- ? K9 G+ a* i1 y
8 ?/ J- m0 s/ z i+ t& p6 f6 J' N" S1 r# D
为了重现客户的现象,通过 STM32CubMX 工具基于 NUCLEO-F767 板子上新建一个工程,使 USB 工作在HID HOST 模式下。我们希望 HOST 端主动进入挂起状态后使用另外一块实现了鼠标功能的 STM32F407-DISCOVERY 板子可以发送一个远程唤醒信号来使 USB 总线恢复,从而唤醒 USB 主机。由于当前最新 F7 的 HAL 库(V1.9.0)针对HOST 并没有提供挂起与恢复的接口,于是,我们需要自己来实现以下两个功能,并添加到路径下面的中。& S* \+ b ?7 F. _
/ A0 ?+ a6 W* g# ^: _
$ Q. }3 \: q( w! i8 x于是 HOST 端主动进入挂起的代码实现为:代码操作 HPRT 寄存器的 PSUSP 位,使 USB 进入到挂起状态
6 `2 [- N1 I. i- C3 x- /**
) i) @; L1 X2 ?$ y4 Z( Z4 N
# e4 A# y4 z/ \4 i* Q- * @brief Suspend Host Core
* x/ E7 B1 B: h8 ?1 C( A - * @param USBx : Selected device- p+ n# }3 v7 L8 C I8 P
- * @retval HAL state5 s8 f% Z& I; P" p3 E
- */
' F- G6 t, o8 L5 e! h - HAL_StatusTypeDef USB_SuspendPort(USB_OTG_GlobalTypeDef *USBx)
6 m( `6 a: v# Z/ e2 K" P5 j8 p - {
: w& M/ f$ `/ w - __IO uint32_t hprt0;
( o* V2 ~ M% b0 q/ F - hprt0 = USBx_HPRT0;
4 E- R6 b, Q! q2 _" g" I- R7 _8 b - hprt0 |= USB_OTG_HPRT_PSUSP;1 v4 m* R& r. ]7 d7 f L8 i
- hprt0 &= ~(USB_OTG_HPRT_PRES | USB_OTG_HPRT_PCDET | USB_OTG_HPRT_PENA |0 i( f0 v* ?' \3 Q5 ~6 g' \8 H
- USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);
6 e, \3 h; @% v: q" @ - USBx_HPRT0 = hprt0;
( |2 e9 h, l6 b( ~& s& x9 b4 c - return HAL_OK;
* k* ?7 \0 n V* G0 ~ - }
复制代码 . u- c8 ^' f$ o" S
下面是 Host 从挂起模式恢复:代码两次操作了 PRES 位,为的是确保操作成功.& T# u+ h$ N5 V! F
- /**
( Z7 J5 h5 L5 V - * @brief Resume Host Core( v- r9 I* f" s) l3 F3 j+ O
- * @param USBx : Selected device% ^, W) a2 b# i, X
- * @retval HAL state
0 e, U9 K+ A" _& k - */
\ S7 s. X- p( }! h/ n3 }* Q j" G - HAL_StatusTypeDef USB_ResumePort(USB_OTG_GlobalTypeDef *USBx)
X. W# H9 w) d6 D - {+ T" B8 G. E! \) U B
- __IO uint32_t hprt0;, p, z. N9 p0 j9 d$ r+ o
- hprt0 = USBx_HPRT0;
3 C3 Z- y5 l N - hprt0 |= USB_OTG_HPRT_PRES;8 |0 {$ u. Q3 Q; D$ }5 ^" p% e2 ?
- hprt0 &= ~ (USB_OTG_HPRT_PSUSP | USB_OTG_HPRT_PCDET | USB_OTG_HPRT_PENA |
$ X: x* d& Y0 w - USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);
6 x9 W% V, q( n* I" C" L0 r- F - USBx_HPRT0 = hprt0;
6 s6 m" X/ R) ? - HAL_Delay(30);4 u6 Y$ Y+ A9 s: } a, E) |4 x( P
- hprt0 = USBx_HPRT0;
8 b6 [! H: ~- f) {1 z0 } - hprt0 &= ~(USB_OTG_HPRT_PRES | USB_OTG_HPRT_PCDET | USB_OTG_HPRT_PENA |; ?9 `9 q' N7 a2 Q% c7 t0 ~9 ^) d
- USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);# u/ g2 @9 U2 K5 s- A" B/ k1 X$ Z
- USBx_HPRT0 = hprt0;
8 Y8 |: ^' P& c4 }% @- o6 V - return HAL_OK;
# c. l# _. _" I; a3 c% Z7 M, z9 { - }/ Z% N( Q8 f' h d& e, ^
复制代码
6 d. D, T9 h# I1 m4 O1 ~' e/ T6 Q& }5 X5 E4 N) z
那么这样的代码能成功挂起和恢复 USB 总线吗?
5 ?5 |/ [0 W; D: B% z2 A经过实际测试,如果 HOST 主动调用 USB_SuspendPort 进入挂起状态是可以正常进入的,如果 HOST 自己主动再次调用 USB_ResumePort 恢复的话也可以正常恢复。但是,如果通过接收到远程唤醒信号后,HOST 端被动地在中断内调用 USB_ResumePort 函数来恢复的话则会失败! 同样的操作,放在 OTG_HS 的话又能够正常,但是客户当前使用的是 OTG_FS 来做产品的,看来两个 USB 外设之间还是存在一定的差异性,针对 OTG_FS 的远程唤醒对寄存器操作还是有一定的要求。
{$ E' Z- k* s9 U4 J% S" E
5 c7 Q$ F; ]8 \8 r; ~& [& J, K# ^经过一番研究与测试,我们最终通过修改后的代码来实现客户的需求:- i3 X8 S6 U; x) j
9 \2 p/ ` ?/ J6 \2 T. \# B- /**/ o9 U5 }0 [; `* t- P! H
- * @brief USB_SuspendPort suspend Host Port. j) y% F7 [/ ?( d, s
- * @param USBx Selected device( a' e- i9 ^9 f
- * @retval HAL status
' M3 {4 o& Q: {: r - */
/ t3 ^7 d: Z: ?8 t0 v# i# C - HAL_StatusTypeDef USB_SuspendPort(USB_OTG_GlobalTypeDef *USBx)
1 x% T. f/ @* } - {1 D7 B2 Y; V& u- w& V; B! A6 |
- uint32_t UsbBase = (uint32_t)USBx;
+ t' ?' \) z6 S: b" K7 A" c - uint32_t *pcgcctl;; Y# M9 p% S+ Q7 f+ e, H6 |' o% {
- __IO uint32_t hprt0;! R( q) ^3 h: D$ p: d* T
- hprt0 = USBx_HPRT0;) C% a% i2 b$ }9 C* ^; u1 m6 p
- hprt0 |= USB_OTG_HPRT_PSUSP;) P; o }1 J M7 U5 ]
- hprt0 &= ~(USB_OTG_HPRT_PRES | USB_OTG_HPRT_PCDET |2 Y! [( l; f& ^; Y" I
- USB_OTG_HPRT_PENA |" w7 L2 @- u8 S- E7 Z
- USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);
Q4 Z2 X9 i: O% |& D8 h8 `" { - USBx_HPRT0 = hprt0;( w- }% Y( m6 {! u9 R. R9 r" |# z: R
- pcgcctl = (uint32_t *)(UsbBase + USB_OTG_PCGCCTL_BASE);
/ i) @! E. [5 j# @, r' l% ^# { - *pcgcctl |= USB_OTG_PCGCCTL_STOPCLK;
# l" W( j' h; X - }
9 g& }. M; i: O - /**
3 e; X+ T- {2 L/ [6 T8 v - * @brief USB_ResumePort resume Host Port& h v8 ^( v `& a+ d, N
- * @param USBx Selected device; U2 z( }$ |. U2 e0 c
- * @retval HAL status( h: G9 ]: Q' R' G* c% C1 }4 T* D! d3 ^
- */! a( R: Y1 ~$ Y. t# R
- HAL_StatusTypeDef USB_ResumePort(USB_OTG_GlobalTypeDef *USBx)% e4 }# H* {5 n% Q+ _) l# \; K
- {
: D$ t+ r% {+ Q+ y D/ ~' \ - uint32_t UsbBase = (uint32_t)USBx;8 r+ s5 a+ ^6 ]% r
- uint32_t *pcgcctl;
" ?8 m7 ^9 M5 b5 ^' E; ?. J" C - __IO uint32_t hprt0;
+ }5 z* ^, o b+ w$ m - pcgcctl = (uint32_t *)(UsbBase + USB_OTG_PCGCCTL_BASE);
+ s8 ?$ G& g/ ~# @" P7 c - *pcgcctl &= ~USB_OTG_PCGCCTL_STOPCLK;
6 A! K x2 e. n$ y: f3 e - hprt0 = USBx_HPRT0;
5 i' p- [4 O) T+ q4 A# T. z - hprt0 |= USB_OTG_HPRT_PRES;
2 A9 Z* B9 q. R8 y) R - hprt0 &= ~(USB_OTG_HPRT_PSUSP | USB_OTG_HPRT_PCDET |USB_OTG_HPRT_PENA |\7 U [- ?* r. ?0 s# q- f0 k: d
- USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);
' z- L) n* I+ K - USBx_HPRT0 = hprt0;
& v/ c/ z. ^2 {' a; q8 y4 i - HAL_Delay(30);1 g+ q D" F, a# y
- hprt0 = USBx_HPRT0;2 j- H7 ?7 H# e
- hprt0 &= ~(USB_OTG_HPRT_PRES | USB_OTG_HPRT_PCDET |USB_OTG_HPRT_PENA |\
' M& C1 F7 y& k$ a( H7 r2 R - USB_OTG_HPRT_PENCHNG | USB_OTG_HPRT_POCCHNG);
4 n4 y' U% s9 f4 z - USBx_HPRT0 = hprt0;8 M- b3 V! Y/ W9 }3 F' x
- return HAL_OK;1 z* `; g1 W# X( @
- }
6 e) v* i2 _* n% h1 B; F+ o
复制代码
$ V( X ^5 K0 j. _8 g7 r6 B7 {) o) C, D4 r! `1 g8 t
此代码最终测试是可以正常挂起和恢复 USB 总线的,不管是 HOST 自己主动挂起与恢复还是由远程唤醒触发被动恢复都是能正常工作的。这里的关键所在是在挂起后,必须将 PHY 的时钟关掉,再操作 HPRT寄存器的 PSUSP 位;然后在唤醒时,先将 PHY 时钟打开后再操作 HPRT 的 PRES 位,按此顺序才能正常。
4 f3 G; {7 C; v7 i: H& G" A在结束本篇全速 OTG 作为 USB 主机挂起、唤醒的特殊操作之前,我们看一下这个额外操作的寄存器的描述:. \3 l N; i" }. Q
0 c" f# b% r7 i/ j1 C8 o
! t, O7 v4 k# p. K
# V; ? q" J" P3 Y! m8 x
" |3 g& l+ X: \- `5 F Q4 后记) Q4 \( I6 ?. v& t9 x
HAL 库并不是一个完整的库,它也在不断的自我更新与完善中,客户需要的有些功能接口可能在库中并没有提供,这个是正常的,这个时候,客户则需要根据情况自己来实现这种接口。
2 w- T8 x9 V7 I9 C 本文所描述的两个接口在当前版本并不存在,但将来 HAL 版本更新时会将它更新。, ~: @# G, C4 N4 Y0 C( a
+ ?! k& Y% p: E$ E& J
|