串口调试在项目中被使用越来越多,串口资源的紧缺也变的尤为突出。很多本本人群,更是深有体会,不准备一个USB转串口工具就没办法进行开发。本章节来简单概述STM32低端芯片上的USB虚拟串口的移植。在官方DEMO中已经提供了现成的程序,这里对修改方法做简单说明。 3 ^, v: Z0 r8 Q" q+ ?0 e( v
首先打开官方demo我们开始进行移植,第一步复制我们可用的文件,操作如下: Projects\Virtual_COM_Port文件夹下,复制红线部分 图1 " \* v6 w. \7 a* l6 Q0 \
- b5 p W4 z, u3 A图2  ! b1 L, j. t. P3 }* m1 k8 v
8 {/ X) z4 v- Q/ X2 M我为了方便演示统放在usb/src文件夹下: 图3 
) L" V% K; d) n* A3 J: \7 r$ j9 N/ e1 k1 a) p. j/ W
现在复制USB的库文件,这些文件不需要我们修改: 图4 
% ^- z" a: Q9 o+ ]1 p$ X/ n% }3 o$ T" i7 `: `- m3 |) K) @8 z: V, G
上图中的文件统一放在usb/lib文件夹下: 图5 
6 }1 r+ V; ]8 i3 G( k: \% s; X6 x! K; ]
好了现在所需要的文件我们以复制完了。这里先讲一下DEMO程序的主要工作流程: 图6  . V. P. e/ j! t
由上图可知,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 /* Includes ------------------------------------------------------------------*/
. W; _- p8 P' u6 x9 F' `5 s - 2 #include "hw_config.h"$ U1 L9 T4 T! H9 A! m* J r Z
- 3 #include "usb_lib.h"( T* D3 W. ~- x
- 4 #include "usb_istr.h"
3 k2 X, p) c# H - 5
( T! r* G! J3 p+ | - 6
: Z5 Y1 ?, z- ^: g; r3 C: k - 7 /*******************************************************************************
( S s8 c/ Q5 }$ Z - 8 * Function Name : USB_IRQHandler
$ z9 E _$ D$ `0 D3 F - 9 * Description : This function handles USB Low Priority interrupts' @8 m( b" i9 K/ i1 z$ l+ i/ A. m i
- 10 * requests.$ ~, `3 I, ?) b% c1 Y3 t8 m* e
- 11 * Input : None
5 S- _7 U4 U0 n - 12 * Output : None/ ^+ M6 z" e( c$ ^8 Z
- 13 * Return : None
5 q' ]) h: v" }& Q; e - 14 *******************************************************************************/
0 a5 R9 N" z+ I3 z8 a - 15 #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)|| defined (STM32F37X). A+ f, g% x A# X2 y
- 16 void USB_LP_IRQHandler(void)' v7 p5 i2 y( Q- I
- 17 #else
E5 w# }+ ^ ^# {: ] - 18 void USB_LP_CAN1_RX0_IRQHandler(void)
' l4 ?& ]& o, b( \: q, s - 19 #endif7 t0 M- l2 {8 R3 S
- 20 {
# l6 q* g% H2 F/ v+ X - 21 USB_Istr();9 K( m; V& r1 X- ^2 s9 _
- 22 }% f4 c1 B3 v3 `6 {& J! x4 n7 u. o
- 23 ! v0 `# l' P' P6 d; [, {
- 24 /*******************************************************************************6 X; i3 W( N: [" J, Q4 O1 i
- 25 * Function Name : USB_FS_WKUP_IRQHandler7 {9 H% f& j+ l' R8 }
- 26 * Description : This function handles USB WakeUp interrupt request.. a& `8 e, z. m# q% x
- 27 * Input : None* R2 \# V9 u. r/ W$ x u
- 28 * Output : None
0 H* j3 R6 E2 e- d1 h1 [7 h - 29 * Return : None
) z8 r( e2 H8 B8 C- i - 30 *******************************************************************************/7 @5 v# s+ V* d' I. U5 ~' W8 Q
- 31 5 j' `8 i! b9 ?0 R. e& M' M' i
- 32 #if defined(STM32L1XX_MD) || defined(STM32L1XX_HD)|| defined(STM32L1XX_MD_PLUS)
" W) u, n i, m/ [* j9 Z, y - 33 void USB_FS_WKUP_IRQHandler(void)
9 O) ?* c$ A! s; g6 T; k/ ` - 34 #else0 |1 o+ K G6 C8 s0 d$ e6 `) d4 y2 ]
- 35 void USBWakeUp_IRQHandler(void)% T' Y4 J: I: y2 [
- 36 #endif
: P7 t: C$ C5 T3 }$ j5 v5 e - 37 {
6 t( d; k( `8 p- f$ e - 38 EXTI_ClearITPendingBit(EXTI_Line18);8 _2 A; F! z3 U
- 39 }
复制代码 3 t; t' J* v3 l& S0 z& U5 v! @ I
2,修改代码hw_config.c删除无用代码,新建立2组,读FIFO和写FIFO的函数。后面会用到。 代码如下: 代码2 - <img src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" border="0" alt=""> View Code
复制代码这里要讲一下为什么要屏蔽SystemInit(),因为demo只运行虚拟串口功能,在USB未插入的情况下,是进入低功耗状态,插入时从低功耗状态退出后会调用此函数。当然我们在项目中一般不会这样,系统是否运行和插USB接口没有联系。所以我在下文中把进入低功耗代码屏蔽了,自然也就不用唤醒代码了。 图7  ' e7 J- Z# m2 p4 z% N
) @. H; S) @5 i6 ^: D
关于USB口使能控制引脚,需要根据开发板的引脚定义来修改宏定义platform_config.h文件中,笔者使用的是神舟3号开发板,控制信号刚好和demo相反,所以修改hw_config.c代码如下: 代码3 - 1 /*******************************************************************************" g! A# G- V! R5 Y- H1 {, W) l
- 2 * Function Name : USB_Cable_Config
& A! r6 Y& I( J - 3 * Description : Software Connection/Disconnection of USB Cable
- s( b. S" t( t3 ^& m - 4 * Input : None.
8 K. i2 q' q! T5 [+ q0 `1 ^ - 5 * Return : Status
& N, c& ^% K2 k6 k5 l* w" J$ V - 6 *******************************************************************************/
& m7 s7 i% I* j9 B! f - 7 void USB_Cable_Config (FunctionalState NewState)
4 q. L6 i1 m. f* z% L/ L9 \ - 8 {# Q+ H$ m Z% w" x9 J4 Y
- 9 if (NewState == DISABLE)
. p2 z$ L4 X K* q! E - 10 {' ?( _' e9 g9 ^7 S8 ?5 z
- 11 GPIO_ResetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);' x& S9 m3 X- _& f& o
- 12 }
, W2 D; F8 ^7 t: s% ] - 13 else5 R9 _. S$ `/ ~% i" V. t
- 14 {# ^2 B7 V0 a) W+ N" w! H2 Q
- 15 GPIO_SetBits(USB_DISCONNECT, USB_DISCONNECT_PIN);! o9 C7 ` ^+ a5 I3 k9 W& f7 X4 P
- 16 }+ a# q' \$ J' i1 Z
- 17 }
复制代码 2 O6 D* i4 J' u
3,现在修改USB 回调函数中的代码usb_endp.c文件。使用下文代码替换: 代码4 - 1 /* Includes ------------------------------------------------------------------*/
$ L! G2 D; [1 [' f& n9 u" Z0 e) ] - 2 #include "usb_lib.h"% I N A$ [* D
- 3 #include "usb_desc.h", e7 a9 W7 Q2 ~+ O8 g+ \' `
- 4 #include "usb_mem.h"% x5 w! x9 T$ ^' g3 Y
- 5 #include "hw_config.h"
V3 j, w7 {6 i+ U0 Q5 ?" d - 6 #include "usb_istr.h"9 d3 Z7 r$ [! H/ [& ?+ a
- 7 #include "usb_pwr.h"
1 f! {; [7 J1 l# c - 8 8 q9 n0 }% J+ v- m, H) R/ C# C+ j
- 9 /* Private typedef -----------------------------------------------------------*/
9 |) O9 e' C2 }* `+ {: z }& S, A - 10 /* Private define ------------------------------------------------------------*/) V: W k7 b7 D. c. |2 U2 T
- 11
/ `: S; n( N3 n8 v - 12 /* Interval between sending IN packets in frame number (1 frame = 1ms) */
G9 p( ~: R9 A - 13 #define VCOMPORT_IN_FRAME_INTERVAL 5
5 _0 H8 {% s$ t q) K! s- J& T& D - 14
5 h& f. c3 {( O - 15 /* Private macro -------------------------------------------------------------*/
) L# v3 h0 g8 V- R1 L* Y" g* K* G v - 16 /* Private variables ---------------------------------------------------------*/
& k9 R" q1 J2 n7 _ - 17 static uint8_t txBuffter[VIRTUAL_COM_PORT_DATA_SIZE] = {0};
! Y' f# X1 m- y3 M5 V - 18 static volatile uint8_t txFlg = 0;
1 @& Y1 V9 t+ q - 19 static volatile uint32_t FrameCount = 0;! ?6 F9 x/ L5 e1 [. Q" l
- 20
+ F% U) W/ Q+ q2 {4 Y - 21
1 \, R' G- F+ g5 m, J* L - 22 /* Private function prototypes -----------------------------------------------*/# R) p+ T4 q+ }
- 23 /* Private functions ---------------------------------------------------------*/: j: ?9 G9 H2 _+ U
- 24 2 u6 ^4 I% L! j! K) Y3 C5 z
- 25 /*******************************************************************************/ b; \6 d) C. a
- 26 * Function Name : EP1_IN_Callback
/ v) F3 V6 G2 }8 K/ o+ L - 27 * Description :
, y0 L1 G6 Z; @1 S% ~' K - 28 * Input : None.( n6 ^ k) p! _! a& }6 k D& a9 \
- 29 * Output : None.
) b8 y; p- T' X - 30 * Return : None./ }0 _/ ]! E. u- h: @
- 31 *******************************************************************************/
4 F8 v% f8 }% N" p/ C9 b* Q3 V - 32 void EP1_IN_Callback (void)& Z& e$ b K% o! O, `
- 33 {
8 I' |5 a. h% }" D% I - 34 uint16_t len = 0;, ?$ u& c, W' }# E- F/ Y
- 35 O+ j* b0 s! f
- 36 if (1 == txFlg)# V$ [( w E* B
- 37 {+ I( [8 q& T1 _
- 38 len = USB_TxRead(txBuffter, sizeof(txBuffter));
( }+ t9 x# N' B- F) G/ ? - 39
" O1 K5 G5 O1 U - 40 if (len > 0)
- F, ^+ l# H5 g/ H, M0 ] - 41 {9 x+ O( H! ~" j. I7 G
- 42 UserToPMABufferCopy(txBuffter, ENDP1_TXADDR, len);
a7 l7 E5 o# \% a0 f/ z - 43 SetEPTxCount(ENDP1, len);
1 e& | R I+ H' ^0 b$ j+ I5 r - 44 SetEPTxValid(ENDP1); : ^- g+ ]+ I4 U) ]! K
- 45 FrameCount = 0;3 n: J! k* u0 [# Q
- 46 }$ o9 \- m9 D. f7 t* Y# b+ t
- 47 else- x1 f0 r6 h9 @. P0 D. E$ }, ]+ @
- 48 {
5 F3 H. \. _! U! g" e - 49 txFlg = 0;$ Z! v! Z' q$ U0 i7 T8 ?# n
- 50 }
2 d& G- s; b C/ U& f - 51 }
. e) ?3 ~: Z+ Z8 D. j - 52 }
, L+ f- H) ?# z+ B - 53 ) R" T- W! Y* i5 x$ y
- 54 /*******************************************************************************" {% F. T4 @: z4 B
- 55 * Function Name : EP3_OUT_Callback: \ E( a4 h# R a
- 56 * Description :
; R) [5 _* {9 W b( {/ @& p! x - 57 * Input : None.+ R3 ^$ _6 K9 d$ N( y, m, J# q6 P
- 58 * Output : None.3 D4 d" Z4 |6 L! g
- 59 * Return : None.
8 ]3 q4 ]& k/ {5 I/ b - 60 *******************************************************************************/+ K. V) X5 F3 r( a
- 61 void EP3_OUT_Callback(void)
9 s- y n/ X2 P - 62 {( h' W' y/ H. H1 `; i
- 63 static uint8_t buffter[VIRTUAL_COM_PORT_DATA_SIZE] = {0};
% }- ~/ h- J9 M - 64
( Q7 ^* _% @/ D# s - 65 uint16_t USB_Rx_Cnt;3 f3 }0 u: ]8 F% s8 [4 H$ I
- 66 : u1 \: z8 t% A/ E! I
- 67 /* Get the received data buffer and update the counter */7 T* \# {0 F; y; @- `
- 68 USB_Rx_Cnt = USB_SIL_Read(EP3_OUT, buffter);) G& \: s: _# W& R* x5 L2 |8 ]
- 69
% |% a Q! q! [ - 70 /* USB data will be immediately processed, this allow next USB traffic being 3 B1 q1 W0 p: W- Z1 v
- 71 NAKed till the end of the USART Xfer */5 F* H+ b( N1 q
- 72 USB_RxWrite(buffter, USB_Rx_Cnt);7 a% [; G. _* l/ }
- 73 & T/ x, A5 j( W3 K# m, j5 h
- 74 /* Enable the receive of data on EP3 */$ V# |7 C& a1 E j- D+ S& d
- 75 SetEPRxValid(ENDP3);. ?5 b% Z7 m: O M4 O. P
- 76
* C6 O) ~+ N6 H+ C) C, Q - 77 }
5 H2 t- I1 I7 Y% B5 Q$ B9 v% a - 78
/ _# [1 V' _1 j9 j1 K" G% j( A - 79 9 l+ l- D' U: H
- 80 /******************************************************************************* y3 T! H `4 [
- 81 * Function Name : SOF_Callback / INTR_SOFINTR_Callback
! r6 ~6 E+ L; x& E4 K - 82 * Description :
6 s! S$ h) a t m' e( [; J' t( {' v - 83 * Input : None.
3 i2 d* V: e: w: v) f0 Q, v - 84 * Output : None.
% ~7 w6 [4 S! Z3 S# E6 K - 85 * Return : None.
E2 O( A: y2 O3 r. a; o3 P - 86 *******************************************************************************/
" G2 ~+ d! Z5 \$ b+ Q* X B. u8 Z d - 87 void SOF_Callback(void)+ E2 Y, e( j/ X
- 88 {4 o7 n6 n$ X& }, ~7 z
- 89 uint16_t len = 0;- [3 d8 d: M# f1 e
- 90 - \& o' I, c5 }' m& h- a
- 91 if(bDeviceState == CONFIGURED)
+ X0 W! Y) m1 J# ^+ m [# h - 92 {
4 T7 L! k; |; `# o6 K - 93 if (0 == txFlg): _* \( ?7 E% V. x! i2 S. V
- 94 {
$ z; F& Q$ z; N - 95 if (FrameCount++ == VCOMPORT_IN_FRAME_INTERVAL)
5 M) [5 [( z) F - 96 {& O/ R3 j" Q% g7 N7 {8 F- U8 T
- 97 /* Reset the frame counter */
( H9 |9 z/ z4 n9 Y) U) ]- n) F. ~ - 98 FrameCount = 0;
9 F1 X/ @0 f! j# h( u- S - 99 6 d* I" N' k2 V; t5 W
- 100 /* Check the data to be sent through IN pipe */" }' [: Q, u6 g& ^
- 101 len = USB_TxRead(txBuffter, sizeof(txBuffter));
. V5 Z; m, f8 n; U: a - 102 3 U+ r9 X! v( G9 d- @6 o
- 103 if (len > 0)% K$ D# P7 {, x" x7 s
- 104 {7 D9 h# t8 @& Z% j) z
- 105 UserToPMABufferCopy(txBuffter, ENDP1_TXADDR, len);: `( |6 u0 \) x
- 106 SetEPTxCount(ENDP1, len);" T; ~* P% A, V( M- @' h
- 107 SetEPTxValid(ENDP1);
K, M8 A% z5 b a0 T - 108 , H0 r6 X7 A8 y8 q. }
- 109 txFlg = 1;; n8 T$ A6 G3 [- F; e b
- 110 }
' ]2 `- s$ m! T+ d6 H# E" w - 111 }$ X B+ c8 V k7 Q" y; d
- 112 }: S' Y. H, W3 o* W/ X8 ~1 a, E a+ l
- 113 }
8 g- N2 E& I& Y4 ^! v5 a- t5 ] - 114 }
' t% T6 C! L- M& u - 115 /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
复制代码 , t R8 ^$ H ?" t9 `# h
2 ]% X2 w5 R9 y8 b
这里讲下大概意思,函数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也一起存放在上文章开始的链接中。
6 X6 `4 h" T4 ^) Q |