你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32 USB虚拟串口

[复制链接]
STMCU小助手 发布时间:2022-1-13 21:00
串口调试在项目中被使用越来越多,串口资源的紧缺也变的尤为突出。很多本本人群,更是深有体会,不准备一个USB转串口工具就没办法进行开发。本章节来简单概述STM32低端芯片上的USB虚拟串口的移植。在官方DEMO中已经提供了现成的程序,这里对修改方法做简单说明。

2 T; o/ a- R! b; p3 c( h3 |7 m2 U' n) ~! S$ w' K1 Q5 D
首先打开官方demo我们开始进行移植,第一步复制我们可用的文件,操作如下:
Projects\Virtual_COM_Port文件夹下,复制红线部分
图1

3 I/ j( B; U6 }; r( m
300035032772082.jpg

# ^+ }& |7 s' B# S, q
图2

& O: S1 Y7 A/ j# f+ B1 M5 S6 P6 ]
300035202932682.jpg
5 g8 A, ^) o# k8 z2 s, i
我为了方便演示统放在usb/src文件夹下:
图3
% B) R4 [% ^; Q. V% ?5 H
300035313246246.jpg
- e7 o0 n( s( }/ q( R
现在复制USB的库文件,这些文件不需要我们修改:
图4

2 Z; A# A8 {, t
300035404189967.jpg
. u: e" K0 W* }3 o$ M* M" _
上图中的文件统一放在usb/lib文件夹下:
图5

  O9 h6 Y7 W9 X: c  c/ z' j
300036032935279.jpg

- |8 b, b" }: S
& d% \2 M+ L& M. h
         好了现在所需要的文件我们以复制完了。这里先讲一下DEMO程序的主要工作流程:

9 s$ ?+ Z/ @" ]$ Q% U# _
300036178402768.png
# t: X9 T5 s4 \+ s0 U! U
图6
         由上图可知,PC通过虚拟串口发送数据到STM32 usb口,STM32再通过usart1发送数据到PC串口。我们做项目时,只用USB虚拟串口即可。所以我们现在需要把串口发送部分删除。把USB做为一个COM口来使用。我们要如何使用这个USB口呢?demo中是把USB发送数据做了一个缓存,先把要发送的数据存入缓存中,然后由USB自动发送出去。而接收部分是直接通过串口透传。我们在应用时就需要用到两个FIFO,1是发送,这个和demo方式是样;2是接收,接收也做一个缓存,我们通过查询来判断是否收到新数据。这下大家应该明白为什么使用两个FIFO了。 我这里有写好的FIFO库函数可直接使用Queue.c文件。
         现在开始修改:
1,stm32_it.c 更名为usb_it.c删除无用代码,只保留usb中断函数,和唤醒函数。代码如下:
代码1
  1. 1 /* Includes ------------------------------------------------------------------*/
    , \: C) j" f% b  G3 n" h4 K/ ^- x* B
  2. 2 #include "hw_config.h"
    7 m+ \+ k# m, f
  3. 3 #include "usb_lib.h"
    / v2 F5 k; k! d2 M+ r( H# U1 M
  4. 4 #include "usb_istr.h"2 w+ q' e6 `: f. m
  5. 5 ( S7 {8 j9 r8 m# V
  6. 6 - }$ R! `: _1 n* e
  7. 7 /*******************************************************************************, H& G$ f1 Q& l, E1 Y
  8. 8 * Function Name  : USB_IRQHandler) D" k6 ?; D* H1 y
  9. 9 * Description    : This function handles USB Low Priority interrupts
    & v' n: J, {5 x! X. o; d) L
  10. 10 *                  requests.
    5 V- Z9 D) L+ n) Y1 C9 ?
  11. 11 * Input          : None4 z$ }8 ]% ~" k% V
  12. 12 * Output         : None: ?3 H- X& L- v4 {7 s( x
  13. 13 * Return         : None
    8 G) Z  ^5 }3 D3 _9 h
  14. 14 *******************************************************************************/
    . m0 s) {# j8 V6 P4 K, G$ _, Y% s9 z
  15. 15 #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)|| defined (STM32F37X)( k$ {" d  K3 p% w% ~$ |
  16. 16 void USB_LP_IRQHandler(void)
    . k% f( E, z+ Q# {1 f1 f
  17. 17 #else
    7 E: A) W# t6 X, f" S4 m7 p( ]
  18. 18 void USB_LP_CAN1_RX0_IRQHandler(void), N. y7 k% x3 T3 b
  19. 19 #endif& F' I- _5 a, K4 Z+ g
  20. 20 {7 p* o- y3 w4 }) \. E
  21. 21   USB_Istr();
    7 Q' ~6 |4 Y; O4 I4 n
  22. 22 }* {, ^, t4 l4 M2 j
  23. 23
    7 ?! s& M& [4 D3 h
  24. 24 /*******************************************************************************
    ( n( g. A5 P) _; C  X2 z
  25. 25 * Function Name  : USB_FS_WKUP_IRQHandler: d$ a  `9 R, u' D/ [6 q4 _6 A
  26. 26 * Description    : This function handles USB WakeUp interrupt request." L% ?3 ~* G9 E
  27. 27 * Input          : None
    , j+ H7 g/ j4 r5 ^
  28. 28 * Output         : None! z. t6 b, E! K' w6 g/ K' y
  29. 29 * Return         : None7 T' F  |3 J6 c3 T8 q
  30. 30 *******************************************************************************/3 u$ J! n% v, f. o
  31. 31   \. c+ O% N4 T* l
  32. 32 #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)
    2 t* i$ P3 P$ `, e; a7 i# Q
  33. 33 void USB_FS_WKUP_IRQHandler(void)
    4 ?" T- A6 m( Q, N5 j3 T: ]5 k) }
  34. 34 #else# ?, \/ J& z% z: b2 p& Q  J
  35. 35 void USBWakeUp_IRQHandler(void). i& k- W" L" l" g1 e+ d. B; m
  36. 36 #endif
      `3 r3 j# T& N4 m. u8 a" V
  37. 37 {  ?$ n9 j/ [: y* u
  38. 38   EXTI_ClearITPendingBit(EXTI_Line18);
    . @- n( U% \" S6 R) A1 Z
  39. 39 }
复制代码

0 z; [8 \4 ~# ?' s& @; L
2,修改代码hw_config.c删除无用代码,新建立2组,读FIFO和写FIFO的函数。后面会用到。
代码如下:
代码2
  1.   1 /* Includes ------------------------------------------------------------------*/5 R, i, J+ p& q! T. E
  2.   2 1 I4 x2 H  b( j5 z6 ?9 x; l* c' m1 r' ^
  3.   3 #include "usb_lib.h". c& s5 Y8 [2 {/ B1 f
  4.   4 #include "usb_prop.h"1 S" U6 E- ?2 n8 s/ V4 v
  5.   5 #include "usb_desc.h"
    ( J- I9 B2 Q. r0 p# j( N6 y. G% x
  6.   6 #include "hw_config.h"3 j, e, v' v( i5 C0 u. T
  7.   7 #include "usb_pwr.h"9 j0 J4 I4 P) K  j
  8.   8 #include "Queue.h"2 e/ }7 F  ^& T% s) E
  9.   9
    ; l; L1 m! _& D6 o1 g9 n
  10. 10 8 j' l9 m) d2 U7 J+ n
  11. 11 /* Private typedef -----------------------------------------------------------*/
    " A# h% A% r' Y7 _+ s7 q. e9 P
  12. 12 /* Private define ------------------------------------------------------------*/0 b8 `9 E$ C4 Y# H: L
  13. 13 /* Private macro -------------------------------------------------------------*/% F" C$ H1 f3 M3 g& V2 p. L2 h
  14. 14 /* Private variables ---------------------------------------------------------*/
    # P! e) i1 U  |! K
  15. 15 ErrorStatus HSEStartUpStatus;
    6 |% T6 z# [" M) a# B
  16. 16 USART_InitTypeDef USART_InitStructure;" |9 L& Y" D3 u$ n
  17. 17 EXTI_InitTypeDef EXTI_InitStructure;
    ' V4 P: d& c5 c* z
  18. 18 3 w4 {) @" ]% i- d# h9 O; @
  19. 19 8 ^; g: H, y! G/ E; ~
  20. 20 #define USB_COM_RX_BUF_SIZE         (1024 + 256)% Y2 u9 ~5 W- U; f) n
  21. 21 #define USB_COM_TX_BUF_SIZE         (1024 + 256)" N+ M+ \, H( g( Q3 \  t
  22. 22 0 q2 k" z+ h5 z0 B2 g2 G9 u
  23. 23 static QUEUE8_t m_QueueUsbComRx         = {0};. U! N  J$ ^( w# [  M. t
  24. 24 static QUEUE8_t m_QueueUsbComTx         = {0};$ R0 b6 h! q2 I9 A" ?* q
  25. 25 static uint8_t  m_UsbComRxBuf[USB_COM_RX_BUF_SIZE]      = {0};     
    7 u# i% Z( q1 c7 e
  26. 26 static uint8_t  m_UsbComTxBuf[USB_COM_TX_BUF_SIZE]      = {0};   % E6 j& ~( F) Z, [$ S! m
  27. 27 2 V8 m. c8 D* Y( A6 q: g' d
  28. 28 static void IntToUnicode (uint32_t value , uint8_t *pbuf , uint8_t len);+ e# D' X& q9 B8 ~% P) Z5 H! [
  29. 29 /* Extern variables ----------------------------------------------------------*/3 n; y$ {& Q( h* [! L
  30. 30 & w2 O5 @1 v, ?5 q, s- E
  31. 31 extern LINE_CODING linecoding;. n* l' @, c2 E
  32. 32 & D; w; _$ ^6 Y6 j) [1 g
  33. 33 /* Private function prototypes -----------------------------------------------*/
    7 {, z3 R3 F8 w1 x4 Z5 w" b
  34. 34 /* Private functions ---------------------------------------------------------*/
    9 v" z& o$ F+ ~% a$ @
  35. 35 /*******************************************************************************
    9 @% }4 s, O- Y0 z0 W/ {5 D  t
  36. 36 * Function Name  : Set_System4 h2 w9 P) n6 E; C. G. P' T
  37. 37 * Description    : Configures Main system clocks & power3 p; A# y- F: @9 J4 G( S6 P$ l7 n
  38. 38 * Input          : None.
    : t' |' T4 m, n
  39. 39 * Return         : None.  S2 N/ r- w0 V) q) ^2 @8 L- N" ~
  40. 40 *******************************************************************************/2 z/ f! u0 P9 A" E$ ^( `
  41. 41 void Set_System(void)
    " g) m: y$ O- J& x$ a9 z4 q* [
  42. 42 {
    / _  Y; Z3 |1 J3 I0 P3 D' q
  43. 43   GPIO_InitTypeDef GPIO_InitStructure;! {8 O# J, C* U8 ?
  44. 44
      S6 t- w" [* ^/ K# g9 b' J4 h
  45. 45   QUEUE_PacketCreate(&m_QueueUsbComRx, m_UsbComRxBuf, sizeof(m_UsbComRxBuf));$ Q$ Y0 h- E9 I; O
  46. 46   QUEUE_PacketCreate(&m_QueueUsbComTx, m_UsbComTxBuf, sizeof(m_UsbComTxBuf));
    ' [3 e+ z& ?: ^! e' M5 c5 p* D, Q
  47. 47   
    9 J7 i2 Z6 w/ C+ ?8 K5 H
  48. 48   /* Enable USB_DISCONNECT GPIO clock */) {! C+ }& H, ^* r7 a
  49. 49   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIO_DISCONNECT, ENABLE);3 s, B0 l+ d. D2 W" }; z
  50. 50 - \4 Q7 k8 S3 r! q2 D" R( }
  51. 51   /* Configure USB pull-up pin */
    2 [, P( t5 `* a. G' J! C2 Y! y+ u
  52. 52   GPIO_InitStructure.GPIO_Pin = USB_DISCONNECT_PIN;2 A: e$ u2 g; D0 J! p
  53. 53   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    / I0 v4 U4 v) }
  54. 54   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    , ^5 ?9 l7 P0 K: l+ ]6 U  U7 q' v
  55. 55   GPIO_Init(USB_DISCONNECT, &GPIO_InitStructure);
    ; p; T* L* k: G* s; |. K& y# t! r# y7 y
  56. 56   - W$ v* O( {/ j( Z. Q
  57. 57   /* Configure the EXTI line 18 connected internally to the USB IP */0 d$ ]7 v5 m& Q" I( p9 L, ^2 z
  58. 58   EXTI_ClearITPendingBit(EXTI_Line18);9 w2 @8 Z2 Y8 K7 n
  59. 59   EXTI_InitStructure.EXTI_Line = EXTI_Line18; 3 h& o- x$ o) z9 A
  60. 60   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;9 q, H9 f% x3 S
  61. 61   EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    1 c5 N* c" b1 P! J9 q- W. Z6 q- v
  62. 62   EXTI_Init(&EXTI_InitStructure);
    . M3 V  ]; [, R; Z
  63. 63 ! o7 ~! q8 \) L
  64. 64   
    ) u9 s: g$ k3 ~" K! A
  65. 65 }
    2 f# _( b  d9 e0 F6 ~( ?: ~
  66. 66 + S0 {) r5 [4 E. Y  ]( U
  67. 67 /*******************************************************************************+ R* ^" s6 \4 |0 p7 ~  j) ]
  68. 68 * Function Name  : Set_USBClock& C5 ^7 u5 m# {( b
  69. 69 * Description    : Configures USB Clock input (48MHz)  F; ?: b) e; G8 T3 R5 m
  70. 70 * Input          : None.$ E2 s/ W  G3 v
  71. 71 * Return         : None.
    : H: O8 t: Q( _4 g3 |" C
  72. 72 *******************************************************************************/
    # x0 u- \( C+ u1 G
  73. 73 void Set_USBClock(void)3 W) ^; S) ^: m/ ~, p8 x
  74. 74 {; S, Z4 M+ G0 w+ k; a* r
  75. 75   /* Select USBCLK source */
    . U4 B; O# U, g- s6 q2 ^1 }' u
  76. 76   RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);9 p" n( T0 N0 x9 x9 m
  77. 77   9 r% l2 [$ |! S3 ?$ K; x; q
  78. 78   /* Enable the USB clock */) h& ], J& \0 Q, _) m
  79. 79   RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
    9 Q7 ]- \, K; w
  80. 80 }
    " H! q' I5 D0 p# O; s+ L8 ?
  81. 81 8 d0 @9 y, m3 J0 ~
  82. 82 /*******************************************************************************
    ' T' t& S, I  k" z9 L- C
  83. 83 * Function Name  : Enter_LowPowerMode
    4 r0 m) g1 `8 Y0 B# L
  84. 84 * Description    : Power-off system clocks and power while entering suspend mode
    + v. c  x& l: k$ c( b8 ^, Q# W! S: o
  85. 85 * Input          : None.1 u0 P; g" |( p4 @, `5 ]
  86. 86 * Return         : None.
    % V: X% d; h& A, j/ y2 n+ y
  87. 87 *******************************************************************************/
    - }( G4 q2 M2 _0 Z6 A
  88. 88 void Enter_LowPowerMode(void)
    $ N( [$ K% V/ j. f3 N
  89. 89 {* \5 K# i& R- U- h7 x
  90. 90   /* Set the device state to suspend */
    8 S( V: V# C( v. z7 u: _$ J) F$ }" p
  91. 91   bDeviceState = SUSPENDED;
    , v0 V' n+ ?5 V
  92. 92 }
    0 [$ r& B4 m( U9 M3 [8 s
  93. 93
    9 R9 j) o3 c) B+ w, r3 M' w
  94. 94 /*******************************************************************************3 |2 P' r% b- Y/ g
  95. 95 * Function Name  : Leave_LowPowerMode
    3 o0 g' O# a& s) G0 L$ ^- h6 l
  96. 96 * Description    : Restores system clocks and power while exiting suspend mode) [6 l4 b: c  [
  97. 97 * Input          : None.
    2 B, e" |; u2 r1 `
  98. 98 * Return         : None.
      M; I2 ]* s/ a% ]/ Q
  99. 99 *******************************************************************************/" e$ b  U; u$ l
  100. 100 void Leave_LowPowerMode(void)
    0 c  ^# T, f0 F/ p9 ~+ X* z3 a7 x, V
  101. 101 {% _5 D4 |* z0 S2 e- m& O, U
  102. 102   DEVICE_INFO *pInfo = &Device_Info;( q5 |8 t3 z: Z3 R
  103. 103
    2 R% h& z# e0 d& E
  104. 104   /* Set the device state to the correct state */
    7 X8 P. t0 @; ]; M3 Z' e' p
  105. 105   if (pInfo->Current_Configuration != 0)
    ! d* Z1 M( m% X6 r
  106. 106   {( {1 Z5 U* L) P- c1 L) |6 a
  107. 107     /* Device configured */4 {1 \6 E- z2 A6 M0 K( R( U( x2 M
  108. 108     bDeviceState = CONFIGURED;( L# _6 @0 B) `' I# e3 }
  109. 109   }
      r8 Q! f  W0 G7 K" F
  110. 110   else0 G* n/ b% P0 R, B$ b
  111. 111   {
    3 I: l: M. \! [, G. m2 L
  112. 112     bDeviceState = ATTACHED;" c4 m* ~# M; g/ {/ E( j7 M! O5 m
  113. 113   }0 b* o* w1 ~1 F3 e+ N9 x
  114. 114   /*Enable SystemCoreClock*/9 m& ^9 l' K* t3 q, h
  115. 115 //  SystemInit();
    / o# w0 k: q  C$ `9 m# C
  116. 116 }
    , K& ^) a4 e4 P# I2 l* F
  117. 117
    / e7 C* L6 C# u  W
  118. 118 /*******************************************************************************
    4 u! H6 L" H0 U9 W5 G" a' J& |8 J: M
  119. 119 * Function Name  : USB_Interrupts_Config
    # f/ F! Q( \- n" c
  120. 120 * Description    : Configures the USB interrupts1 l  @2 J6 a) x( h8 x7 t
  121. 121 * Input          : None.
    + p! ~* i& {0 e: H" b8 \- [
  122. 122 * Return         : None.
    5 Z0 ^9 W. L2 N9 ?. n6 o, m
  123. 123 *******************************************************************************/
    ) Y) P9 @7 m. u( D
  124. 124 void USB_Interrupts_Config(void)  f& J% U& ^0 f, a) {% L# L
  125. 125 {/ l. c3 }+ Q) b
  126. 126   NVIC_InitTypeDef NVIC_InitStructure; * j7 o  h1 n5 e; n* d
  127. 127
    # V4 E# B8 L7 B! ~" d) n/ m
  128. 128   NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
    1 ~. n% ^1 i& ^$ G
  129. 129   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;5 F# T4 e4 D8 O$ y8 k# i" h
  130. 130   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;1 t# R; T. ~+ z9 Z% N4 E. F
  131. 131   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;6 C; v, ]5 y, d! c* v  j5 s& A
  132. 132   NVIC_Init(&NVIC_InitStructure);
    : P1 u# ]8 r+ B1 r+ A% f
  133. 133   
    1 {' J& V6 _9 I6 E: k9 e4 f  J
  134. 134     /* Enable the USB Wake-up interrupt */
    * _* I1 A( x: S5 V0 z7 P
  135. 135   NVIC_InitStructure.NVIC_IRQChannel = USBWakeUp_IRQn;9 B/ G+ t7 E9 Z" M5 [. v: j
  136. 136   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    7 B8 J* Y' k6 _4 V' A) }5 F  K0 b
  137. 137   NVIC_Init(&NVIC_InitStructure);
    9 v0 ?/ [. v2 r5 K/ F. d
  138. 138 }2 k) _5 g4 x6 t: l9 w+ m1 X& t" N/ g" @
  139. 139 ! n  L' f. x# A& z
  140. 140 /*******************************************************************************
    7 z! {6 z% v" e, g! ]
  141. 141 * Function Name  : USB_Cable_Config
    : ?# E& U1 F& M9 f- V
  142. 142 * Description    : Software Connection/Disconnection of USB Cable3 J% E% j! t4 F0 c/ e# U
  143. 143 * Input          : None.7 f# Q8 e5 w, y/ M! a  W
  144. 144 * Return         : Status
    3 i  D- g0 q  p$ u$ L! o! P$ q8 S1 ]
  145. 145 *******************************************************************************/
    + V/ r  V: T% q! U5 v8 M
  146. 146 void USB_Cable_Config (FunctionalState NewState)
    * w7 ^9 g& y: [( r3 ~1 D8 h4 M" j
  147. 147 {5 t- Y: g7 ?( I
  148. 148   if (NewState == DISABLE)1 M% W  F& {6 o! G
  149. 149   {
    " K  R, h) M9 X4 U; q
  150. 150     GPIO_ResetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);: e2 K5 e: q+ D5 {; W: f/ C
  151. 151   }
    ) \* j! h4 }3 L  n
  152. 152   else
    1 F& Z4 s) g4 v) p) U- ]$ @
  153. 153   {
    3 o* _- H( h0 A( i! h" Z* N
  154. 154     GPIO_SetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);: @; O1 [4 C/ B& B
  155. 155   }, y, E6 r9 ~+ w( i
  156. 156 }9 R6 N2 y  `& ~: h. k+ j9 V7 d
  157. 157 " `* U% q- z9 m4 P. ~* V
  158. 158 /*******************************************************************************
    9 Z# ?: o  l2 H  P: b. E" M0 |
  159. 159 * Function Name : void USB_Config(void)
    $ @) L( ^2 W# G/ E  n
  160. 160 * Description   : USB系统初始化7 j% f+ _2 w% a6 ^
  161. 161 * Input         :
    . J7 \" w- H+ \* v+ _# [) M
  162. 162 * Output        : . n; H2 h! |( g( D3 w8 G
  163. 163 * Other         : * i: }( n" e* ?4 Z4 m
  164. 164 * Date          : 2014.11.28
    5 S- v9 j" P1 {% Y, v/ `. L* a
  165. 165 *******************************************************************************/+ ^3 Y: ~4 J# ~4 w! _
  166. 166 void USB_Config(void), i% V7 }9 c+ G: q: ^+ ~0 S7 x* r
  167. 167 {
    4 w5 V; j8 W! s# d: X. E; x* G: N
  168. 168     Set_System();
    5 {% p& U6 B  D8 ?, q6 T. T, P
  169. 169
    ; h" |; h9 Q" ^0 z+ p, a  S$ K
  170. 170     Set_USBClock();6 ~. l- |7 B8 x
  171. 171
    7 ~  C6 b8 Q+ \6 }7 _* s* w/ g. U
  172. 172     USB_Interrupts_Config();
    + h! d- b6 m7 r/ v  K. Q
  173. 173 1 I$ b0 F) }3 F  F( s  T. e/ U
  174. 174     USB_Init();
    ' h9 i+ i, b3 X! p! W. v
  175. 175 }
    / C* S% T5 u( \1 |
  176. 176   J3 ~! H2 c9 I$ Z# a+ j
  177. 177 /*******************************************************************************
    ) Z* }* ?4 B. C7 _  D
  178. 178 * Function Name : uint32_t USB_RxRead(uint8_t *buffter, uint32_t buffterSize)3 |9 w' `9 Q. J8 C. S; g# w
  179. 179 * Description   : 从USB接收缓存中读数据, n9 I& x0 u3 U
  180. 180 * Input         :
    ' V, c3 ?% Q( Y
  181. 181 * Output        : - P# b$ `7 j5 c# T* I
  182. 182 * Other         :
    3 I+ I( t9 L' w' p0 ~
  183. 183 * Date          : 2014.11.28
    / R6 \! H" ]* Z5 k! V( z4 v9 _
  184. 184 *******************************************************************************/
    . ?' K: ?. i/ O" L0 ?3 p
  185. 185 uint32_t USB_RxRead(uint8_t *buffter, uint32_t buffterSize)4 G1 \, t% s/ f8 V. ~: S2 t, o
  186. 186 {/ S2 g9 }2 h5 h9 T$ d+ Z+ F1 d5 j
  187. 187     return QUEUE_PacketOut(&m_QueueUsbComRx, buffter, buffterSize);
    # d6 U' d& @0 y6 d1 h
  188. 188 }. z, H9 d% d- q+ D3 e+ D: T" F! L2 Q
  189. 189 /*******************************************************************************6 f' l9 \% u% e/ B
  190. 190 * Function Name : uint32_t USB_RxWrite(uint8_t *buffter, uint32_t writeLen)
    % j. a/ n  S/ g; g8 V" w9 V
  191. 191 * Description   : 写数据到USB接收缓存中" k$ `) w; V% P
  192. 192 * Input         : ( @* T) K9 I2 Q. T; V6 P+ m
  193. 193 * Output        :
    * X6 Z% Y% ]9 Z+ O% I3 }
  194. 194 * Other         :
    % c& X2 X* R, L4 k( O7 N
  195. 195 * Date          : 2014.11.28+ r. w& Y( Q$ u" b2 H) _( }
  196. 196 *******************************************************************************/
    0 R9 S8 e& G: z9 h2 S, u# x
  197. 197 uint32_t USB_RxWrite(uint8_t *buffter, uint32_t writeLen)
    $ t) S0 n, F& i# Y1 ]9 @5 `
  198. 198 {9 l- P( G6 y( y" \
  199. 199     return QUEUE_PacketIn(&m_QueueUsbComRx, buffter, writeLen);
    ; X# W+ X( H4 F5 ^' {
  200. 200 }  i3 D; T2 [9 C$ `
  201. 201 /*******************************************************************************" a2 i3 _3 H  h* ]: ^- ~, v! e
  202. 202 * Function Name : uint32_t USB_TxRead(uint8_t *buffter, uint32_t buffterSize)
    * Y: n7 D5 y8 @2 M
  203. 203 * Description   : 从USB发送缓存中读数据
    3 C7 M% G1 C2 U5 C
  204. 204 * Input         :
    " }0 b; z' N/ l6 v1 j
  205. 205 * Output        :
    / p: Z9 n: y* ^4 G
  206. 206 * Other         : , I+ y# _5 b# P7 A) v- |: r: T
  207. 207 * Date          : 2014.11.28
    , e3 K) J$ L# r+ @1 ?
  208. 208 *******************************************************************************/, q4 ?4 t: `) G6 {7 B6 E6 B" o
  209. 209 uint32_t USB_TxRead(uint8_t *buffter, uint32_t buffterSize): E% m5 r, o8 t
  210. 210 {7 f4 `' D; w/ T& e
  211. 211     return QUEUE_PacketOut(&m_QueueUsbComTx, buffter, buffterSize);;% ]  N& h% j. U7 z4 g) v
  212. 212 }4 k& Z& W8 P; b! I& h& B1 P
  213. 213 /*******************************************************************************2 J6 S# r1 p1 ]9 o! m) B- l
  214. 214 * Function Name : uint32_t USB_TxWrite(uint8_t *buffter, uint32_t writeLen)$ I/ m3 G# r! J, z8 [
  215. 215 * Description   : 写数据到USB发送缓存中* O9 R  J$ w1 A; W/ b
  216. 216 * Input         : 3 [; m% j1 M  e4 j1 `6 {/ ^- T
  217. 217 * Output        : 2 ?. n- [* I. P; Y- v, @% A) E9 ], D
  218. 218 * Other         :
    ( a* j4 y$ A- Y( ~1 w
  219. 219 * Date          : 2014.11.28- a. j$ ^' ?# `7 G! I- J
  220. 220 *******************************************************************************/
    $ a7 g' v6 p+ V6 Z: o! |( a/ l
  221. 221 uint32_t USB_TxWrite(uint8_t *buffter, uint32_t writeLen)
    ( @& x2 e  l  ~$ K3 o
  222. 222 {- C! b9 ]; A  K7 x! p" a4 J
  223. 223     return QUEUE_PacketIn(&m_QueueUsbComTx, buffter, writeLen);
    $ W1 `/ F- Q2 L, b
  224. 224 }: W; A; E* B+ q
  225. 225 8 v+ b  W' l4 P) C7 g- P2 j/ q, ?5 J$ `
  226. 226 $ }8 Q7 U% S" Z+ r5 M
  227. 227
    ( ~) P7 V$ Q9 N2 D: ~
  228. 228 /*******************************************************************************& P! f% t" r$ i* ]6 o8 J
  229. 229 * Function Name  : Get_SerialNum.$ L) o9 d+ [% W3 s
  230. 230 * Description    : Create the serial number string descriptor.
    ( ?! [7 h3 y% i7 E) {
  231. 231 * Input          : None.% T- O2 Q, c7 v2 Z6 x. f& j
  232. 232 * Output         : None.0 ]+ s) _3 c) g% j% r- \$ J
  233. 233 * Return         : None.: L' R, C- ^, b: @
  234. 234 *******************************************************************************/9 i, j- Q7 V! b% @: W
  235. 235 void Get_SerialNum(void)
    5 Y" |" l" ~3 H
  236. 236 {9 a% C) ]- ^9 s, D
  237. 237   uint32_t Device_Serial0, Device_Serial1, Device_Serial2;# m0 ^0 V$ y% X" V  \: G
  238. 238 + a3 f7 S; v+ L
  239. 239   Device_Serial0 = *(uint32_t*)ID1;0 I7 P' _5 {- p5 q$ V3 s5 {$ ^- y
  240. 240   Device_Serial1 = *(uint32_t*)ID2;& o& |5 X/ e( c: }# I+ ~
  241. 241   Device_Serial2 = *(uint32_t*)ID3;  
    1 l9 `' Q* }4 m& R/ |
  242. 242 , i1 w8 _7 S' A+ ]7 U
  243. 243   Device_Serial0 += Device_Serial2;" O9 r. C0 w  T: @
  244. 244
    2 X6 h: Z$ J; _% I3 V7 G
  245. 245   if (Device_Serial0 != 0). B$ }  K0 N3 r
  246. 246   {
    - j4 Q7 `& ^8 l4 x
  247. 247     IntToUnicode (Device_Serial0, &Virtual_Com_Port_StringSerial[2] , 8);' k4 L8 {1 {; \' O. k. L8 i
  248. 248     IntToUnicode (Device_Serial1, &Virtual_Com_Port_StringSerial[18], 4);. x; Q, _0 J, h' C9 D
  249. 249   }
    1 ~" F0 I+ x' m0 x, f
  250. 250 }
    ) Q* i! y3 E2 |3 Z
  251. 251
    / t) j. x7 [: T) o# l! ^( N
  252. 252 /*******************************************************************************; s8 v* v+ q3 @
  253. 253 * Function Name  : HexToChar.) l) T5 R' b; w3 a
  254. 254 * Description    : Convert Hex 32Bits value into char.% i& Z+ i) l5 u. W: ~) R
  255. 255 * Input          : None.5 ]* l5 _* S4 ]/ N6 C; p% c
  256. 256 * Output         : None.
    5 e" F# _. U# K. Y
  257. 257 * Return         : None.
      @5 N1 Y9 `! }9 X7 p
  258. 258 *******************************************************************************/$ |  T5 I: r. t5 j0 m8 y
  259. 259 static void IntToUnicode (uint32_t value , uint8_t *pbuf , uint8_t len)! @' e4 A6 f9 y* U3 J7 p: @
  260. 260 {
    1 {) T% k: b! _  s
  261. 261   uint8_t idx = 0;
    7 m( I0 W1 |: e% \/ d
  262. 262   
    : l, k5 B  d! c0 i0 L# Y& d+ a
  263. 263   for( idx = 0 ; idx < len ; idx ++)
    ( p+ |* \- c. h' m( q2 ~
  264. 264   {& n0 ]3 t+ ^+ _/ q+ H! E
  265. 265     if( ((value >> 28)) < 0xA )3 n1 k, \( w5 T
  266. 266     {2 q% Y. ^# [. _, H7 _
  267. 267       pbuf[ 2* idx] = (value >> 28) + '0';9 W7 S8 X, G6 z2 F6 a
  268. 268     }
    ; m/ x; d8 w# t) J% D, |
  269. 269     else5 p7 r; B+ ^( J+ w& I2 q) L$ e: c$ P
  270. 270     {
    ( L( U7 K5 i; [9 k
  271. 271       pbuf[2* idx] = (value >> 28) + 'A' - 10; ( O* t5 T+ A; v9 ]9 r. C' r
  272. 272     }
    $ I) {. O. X! H- [, i8 Y6 o
  273. 273     9 l* @3 V3 x7 v, o
  274. 274     value = value << 4;6 R" k. }, f# ]+ V  a
  275. 275     
    4 ~% s6 d' [8 T0 T7 r% d
  276. 276     pbuf[ 2* idx + 1] = 0;
    : U0 F9 l/ q7 t
  277. 277   }
    7 b% f. B% P0 f2 ^. ?
  278. 278 }5 Z; |; N% n' V2 O! Z
  279. 279 - h, h. i: ]9 h  M# @0 J
  280. 280 /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
复制代码
+ s0 g! J6 y) S

6 \- K7 ^' k' X+ \$ ^1 C) v
这里要讲一下为什么要屏蔽SystemInit(),因为demo只运行虚拟串口功能,在USB未插入的情况下,是进入低功耗状态,插入时从低功耗状态退出后会调用此函数。当然我们在项目中一般不会这样,系统是否运行和插USB接口没有联系。所以我在下文中把进入低功耗代码屏蔽了,自然也就不用唤醒代码了。
图7

& |: E1 y' {" g" i- I3 {6 S
300038465597900.jpg

* C7 x7 g/ ?2 H/ l" [) j
关于USB口使能控制引脚,需要根据开发板的引脚定义来修改宏定义platform_config.h文件中,笔者使用的是神舟3号开发板,控制信号刚好和demo相反,所以修改hw_config.c代码如下:
代码3
  1. 1 /*******************************************************************************
    6 [9 l8 h7 c' U. U1 c6 ~
  2. 2 * Function Name  : USB_Cable_Config
    " B! r6 }" N# q, @
  3. 3 * Description    : Software Connection/Disconnection of USB Cable: i" g, y0 u5 u3 B- G( R
  4. 4 * Input          : None." J2 c8 U& n* W1 |
  5. 5 * Return         : Status8 r. U* I# Q! S$ @  k: y6 W
  6. 6 *******************************************************************************/8 X$ I, `# N7 Z$ S+ {
  7. 7 void USB_Cable_Config (FunctionalState NewState)
    3 Z* ?7 H2 {  x8 U4 L. l- L8 v
  8. 8 {, }* `$ p- A2 r" _6 }
  9. 9   if (NewState == DISABLE)' }3 F2 `! P4 r8 [" s& j) B) P
  10. 10   {4 O3 W: F0 s# S1 X, Z' j
  11. 11     GPIO_ResetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
    ' t, g. W( O- U+ k
  12. 12   }& j: [1 A2 m1 k1 Z7 R: E
  13. 13   else
    * z6 D% }) }4 }; i* U2 P4 n( g
  14. 14   {8 T) V% I9 H9 f* U1 ?
  15. 15     GPIO_SetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);
    & i  l: K* [7 B* |$ \" S" z' e9 E
  16. 16   }
    & C* @8 ]5 ?* M7 h2 l7 q5 V* \6 J$ A
  17. 17 }
复制代码
8 R, E" L/ s* H/ c3 P
3,现在修改USB 回调函数中的代码usb_endp.c文件。使用下文代码替换:
代码4
  1.   1 /* Includes ------------------------------------------------------------------*/
    4 l4 m( |* R$ P; B
  2.   2 #include "usb_lib.h"
    - S7 {5 e; ~! f3 }
  3.   3 #include "usb_desc.h"
    * Z' C; h4 C# `8 q) O) K4 x; o
  4.   4 #include "usb_mem.h"
    9 c/ P" T0 E- b+ F& z
  5.   5 #include "hw_config.h", i# Q& j& B  e, {% Q5 A
  6.   6 #include "usb_istr.h"
    & S' J/ }6 Z5 S
  7.   7 #include "usb_pwr.h"0 I  @6 t/ v# `
  8.   8
    1 n: y5 L' [* W+ @
  9.   9 /* Private typedef -----------------------------------------------------------*/
    $ J; [% Y- N7 u
  10. 10 /* Private define ------------------------------------------------------------*/+ _2 ~# ^, X1 {- M. j9 l. c
  11. 11
    , t8 Z) D! j; b: F) D
  12. 12 /* Interval between sending IN packets in frame number (1 frame = 1ms) */
    + ^$ X% S; J, X6 o& a7 V! i
  13. 13 #define VCOMPORT_IN_FRAME_INTERVAL             5' v( r& C0 s  ?
  14. 14
      F1 U+ |/ d9 G( \( z3 a
  15. 15 /* Private macro -------------------------------------------------------------*/9 o7 m! P; f% f! j0 G
  16. 16 /* Private variables ---------------------------------------------------------*/
    % D& y7 M! C/ E! g8 z* z9 w6 [
  17. 17 static uint8_t txBuffter[VIRTUAL_COM_PORT_DATA_SIZE] = {0};
    & N# Y6 ?$ t6 Z6 g8 ]" S
  18. 18 static volatile uint8_t txFlg = 0;
    $ K( r4 @/ L( ?* ~( P
  19. 19 static volatile uint32_t FrameCount = 0;5 s/ e/ o! I2 o% h
  20. 20 ) \! Z+ ^9 E0 T7 ]* [
  21. 21
    & W' F, T& |; j5 s
  22. 22 /* Private function prototypes -----------------------------------------------*/
    4 v( c5 N5 R  L" j$ f
  23. 23 /* Private functions ---------------------------------------------------------*/: [& k1 F+ [4 Q$ l
  24. 24
    % ^( H( b# \7 {
  25. 25 /*******************************************************************************
    * b% d' D; K1 y6 c5 w
  26. 26 * Function Name  : EP1_IN_Callback& n0 l9 z& w% @7 q7 |' c4 ~
  27. 27 * Description    :) f4 C; h/ I8 {* t9 w4 b
  28. 28 * Input          : None.
    4 \8 [; z0 X3 k* Z9 D  Z  ^
  29. 29 * Output         : None.+ x+ u) A& C' o3 i
  30. 30 * Return         : None.
    , x% [7 A0 y% W; c! L4 P7 E
  31. 31 *******************************************************************************/( q' X2 M# \* p$ o: o$ L- A: p/ @
  32. 32 void EP1_IN_Callback (void). W! k4 `' R- M' N7 s" [
  33. 33 {
    ' W+ [/ m/ C* ?  j& ], z
  34. 34     uint16_t len = 0;$ P) {3 X6 I* X  T
  35. 35     " g3 n5 B- s) k+ o6 ]
  36. 36     if (1 == txFlg)# X; V" G, m% E
  37. 37     {$ P* Q& j; G- Y
  38. 38         len = USB_TxRead(txBuffter, sizeof(txBuffter));$ z( ~; {( j, j9 r" n- V
  39. 39
    2 {  `  d. @. r1 M$ P
  40. 40         if (len > 0)9 x; I4 J1 ]: M; ~; x: X
  41. 41         {
    3 A4 j5 p  |$ C5 o( F% f& ^; ~: D% Q
  42. 42             UserToPMABufferCopy(txBuffter, ENDP1_TXADDR, len);  P2 A9 E" W4 g5 w6 ~2 K% U
  43. 43             SetEPTxCount(ENDP1, len);
    6 y+ }1 X: N  a; t  \' E& Q& P
  44. 44             SetEPTxValid(ENDP1); 1 i- N9 L$ I  {
  45. 45             FrameCount = 0;9 J3 X* t$ ]) J2 N" \1 `+ U1 ~# T
  46. 46         }: H* W7 ]0 ?* \( h2 l! U$ w0 v
  47. 47         else9 D. i5 C& e8 S2 L  Z! m
  48. 48         {
    6 w4 ]1 S) K" M5 `$ ]7 w
  49. 49             txFlg = 0;
    , G3 `& t* P6 o# O
  50. 50         }4 l6 e# m* @/ r& Y
  51. 51     }
      V$ h  K) R/ q0 T; n% i( R
  52. 52 }
    $ U7 k- e8 J' I6 ^) E: `  \3 T4 P
  53. 53
    . M6 B1 H' |, d% |- m% J4 |  u( P/ R
  54. 54 /*******************************************************************************. ~+ o7 `. t. m5 Y" S4 o
  55. 55 * Function Name  : EP3_OUT_Callback
    ) p2 L6 D+ Q7 y
  56. 56 * Description    :
      B6 m+ ?; P) x. \' }* ]
  57. 57 * Input          : None.
    7 Q. g1 n2 P$ O/ w$ b
  58. 58 * Output         : None.
    ! P7 \; ~; Y1 n
  59. 59 * Return         : None.
    6 W+ ~3 O2 x( }
  60. 60 *******************************************************************************/
    & b9 I* O; \/ J- A
  61. 61 void EP3_OUT_Callback(void)
    6 ]: x& b( [. P/ r
  62. 62 {
    1 C' }# ~/ P) }1 E" H# G. f! A# V
  63. 63   static uint8_t buffter[VIRTUAL_COM_PORT_DATA_SIZE] = {0};
    1 [# }& h2 w$ P3 v# r: j+ d
  64. 64 7 j0 y) @' ]0 w& h* i
  65. 65   uint16_t USB_Rx_Cnt;% o) w8 l9 {2 B; e
  66. 66   , I$ H6 o) k0 `3 p4 g5 M  V  b; f
  67. 67   /* Get the received data buffer and update the counter */, X* k$ ?: V3 \0 {# }* u
  68. 68   USB_Rx_Cnt = USB_SIL_Read(EP3_OUT, buffter);7 v: |; B, f2 U8 X
  69. 69   
    / p+ E/ m3 T! B& u( g( G9 k: ^
  70. 70   /* USB data will be immediately processed, this allow next USB traffic being % W; O3 F6 ^, V1 T
  71. 71   NAKed till the end of the USART Xfer */
    ' Z4 c' b: K2 t$ t( a/ [, M$ b0 K
  72. 72   USB_RxWrite(buffter, USB_Rx_Cnt);! Q  e. U3 ]" J; U
  73. 73   V2 n& L) T" G# v5 L2 F
  74. 74   /* Enable the receive of data on EP3 */
    5 a' f& Z9 J+ v. j, y& t! @
  75. 75   SetEPRxValid(ENDP3);* N) ]' [2 M5 ?) S
  76. 76
    1 }# R( X( M8 z  [9 z: W
  77. 77 }* r3 i9 O- Q" ?
  78. 78
    4 ~9 d$ N8 W4 e6 Z. i! F4 n% j
  79. 79
    5 q2 Q( H* ^- p6 K0 A1 M4 F
  80. 80 /*******************************************************************************7 `2 w6 b/ k% B: K; k- i( \! R
  81. 81 * Function Name  : SOF_Callback / INTR_SOFINTR_Callback
    5 W! r5 V) u( Y+ `5 z
  82. 82 * Description    :1 Y$ N% N- N( E0 G2 v, Y, z
  83. 83 * Input          : None.* P& d% ^/ R. s/ O: }- ]
  84. 84 * Output         : None.
    * O( ]& q$ B) v8 m  ]  W
  85. 85 * Return         : None.
    - @7 N4 B- j, M1 I4 E
  86. 86 *******************************************************************************/
    6 {* ?& d" g: B" a
  87. 87 void SOF_Callback(void)2 p3 X  x5 `( U) D$ F
  88. 88 {
    , k) ?0 J3 u  t" w, V: w' V! O+ T1 K- ~
  89. 89     uint16_t len = 0;& O' {3 ~' O, E/ v2 B
  90. 90 7 k0 l3 H+ v$ [: n: Z* d
  91. 91     if(bDeviceState == CONFIGURED)
    , G% T  B. m( F# P' u
  92. 92     {7 B$ A: v3 |; Z" \# Z% J
  93. 93         if (0 == txFlg)9 h: O: [! V& i4 o) l6 N+ P# B
  94. 94         {" N5 z3 ?/ z* d# l& z+ ^' A* v8 t4 f, G
  95. 95             if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL)
      b/ d7 t: |$ b7 v( x# G
  96. 96             {& @9 y- _" \* Y, t' S9 J! X
  97. 97                 /* Reset the frame counter */
    5 n* A7 W7 w1 m7 T4 {
  98. 98                 FrameCount = 0;0 k! S  C3 @$ c) |
  99. 99 ) ?0 N, K9 y* o9 Z5 ~4 M
  100. 100                 /* Check the data to be sent through IN pipe */
    3 C( q! d( ]2 u6 v
  101. 101                 len = USB_TxRead(txBuffter, sizeof(txBuffter));
    ) U9 l1 X; E+ a5 F
  102. 102
    , i2 x+ @% C# O/ v, T
  103. 103                 if (len > 0)
    # ~; n) t& e2 c5 b, r% }5 O: c
  104. 104                 {
    ; T6 D0 C9 K( O$ |" t7 E
  105. 105                     UserToPMABufferCopy(txBuffter, ENDP1_TXADDR, len);
    / s2 w  F! \; k/ O5 G1 ]
  106. 106                     SetEPTxCount(ENDP1, len);
    4 [* ?  ]6 ^+ U9 {7 w/ s* w9 b
  107. 107                     SetEPTxValid(ENDP1);
    4 E; o- w; L6 ~, y3 _% F
  108. 108
    1 ~# h5 ]1 g: j9 y1 t
  109. 109                     txFlg = 1;
    8 l2 N4 Z5 K8 \( o; ?9 ?  e
  110. 110                 }- A7 D; B8 Q6 m* n6 r$ a
  111. 111             }8 T4 @$ }# }; _8 M* m
  112. 112         }
      G( H" T' K1 c0 ~! H: G, n
  113. 113     }  
    # x, M; I& w5 [- B  Y$ }4 Z4 ^! z
  114. 114 }0 B: g$ K% L" h
  115. 115 /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
复制代码

/ V4 W; `4 J4 u' H! f9 T) a: ?; R' j5 X" R
这里讲下大概意思,函数EP3_OUT_Callback是在USB口收到数据后,将数据存入FIFO中。
函数SOF_Callback定时查询用户是否有要发送的数据,如果有则进行发送,在发送完成后会触发发送中断EP1_IN_Callback函数,如果发送完毕就不调用SetEPTxValid(ENDP1)函数,发送完成后就不会再触发EP1_IN_Callback函数。
4,修改usb_pwr.c在前文中说到:不让系统进入休眠状态,这里屏蔽185行 __WFI();
5,修改usb_prop.c屏蔽COM初始化代码。137行USART_Config_Default(); 237行USART_Config();
6,修改usb_desc.c 这里修改需要参考一些USB专业的书籍,推荐全圈圈的书,讲的通俗易懂。关于本程序的驱动,笔者在win7下测试可以自动安装,如果无法自动安装可使用文章开始的链接中的驱动程序。本文件如果修改需谨慎,其中pid,vid是制造商ID和产品编号,如果修改了那驱动也要对应修改,官方驱动就无法自动进行安装了。
到这里移植就差不多完成了,下面进行测试。由于USB虚拟串口不受波特率限制,所以笔者进行过50k/s的压力测试,运行半小时未丢1个字节。
移植好的工程STM32_UsbVirtualCom.rar也一起存放在上文章开始的链接中。

  z% K! N8 V6 [- I( a" Q
赞 收藏 评论0 发布时间:2022-1-13 21:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版