前言8 P: L- m a ~4 z( v4 p* u
客户在 STM32F401RET6 中使用到了 USB 的虚拟串口功能。要求提供一个在 STM32Cube 中实现虚拟串口进行数据环回测试功能的范例程序。因为在我们目前所提供范例程序中,并没有适合客户需求的范例,所以我们在 STM32CubeMX 中创建一个范例程序给客户进行参考。' D; F% I9 \& u4 {( |! C
环境需求如下:, B0 ~' h1 e) r# a+ f$ M& r7 f
硬件环境:STM32F401C-Dicovery: V$ Z* |, F3 y7 I( C7 H
电脑操作系统:window 7.x
* ~8 l4 S+ Y' r5 k# q0 d) `7 s ?集成开发环境:MDK、IAR
w7 n/ @" U1 [5 h7 k( b" i; z3 ~驱动:ST VCP Driver
9 N9 e' B b* Y3 {! MSTM32CubeMX:4.12.08 R# O: t' f1 p; r( q
STM32CubeF4:1.10.0' r- L4 b$ n0 M0 D" A3 v h
6 U- S& _5 ? ] n8 N2 @
STM32CubeMX 中配置流程" ?) C# S4 D" [
实现步骤如下:" S z& h+ e& ~1 e. ~& ?. m0 s! n
首先,打开 STM32CubeMX 软件,选择 STM32F401C-Discovery 板子。
3 [5 T" E) P* T" p+ m8 H+ G
* T" I4 N% `8 T
& ~/ n! M: Q4 y( d, g0 x) @
( R Q9 o7 N8 N5 q在 PinOut 列中选择 USB OTG FS 的 Device Only 选项
! Y5 j# ~0 X* a% V J) q* K; P
' U" s6 ]' v+ l% r
. V6 s. S O, _6 @( b) y' B( P; F i5 j% D
使能 USB 中间件的虚拟串口功能( t6 q8 T* T* I/ ?. X- ^2 ~$ f
" g$ [* A" O' p$ ?
! [0 j& `( b0 B# T# f4 e
+ @( R9 X2 p; D! B- m$ H$ z3 z- f直接生成代码,这里我们选择 MDK 的 IDE,工程名字叫 VCP_Test
" J* v2 f* `& L- S( I5 k
7 j9 M- }1 t" d- A$ O& A6 i. S# l# ^
$ }9 H/ ?2 f" Y& w3 l5 }7 d/ _
* U9 d7 \9 g F0 X' {6 B
在生成的工程代码中的 usbd_cdc_if.c 文件中添加如下几行代码:
# P9 e' X" d; n8 @4 R3 G) W8 m- #define APP_RX_DATA_SIZE 1024
$ f! V2 ]9 N0 C8 i, q! L - #define APP_TX_DATA_SIZE 1024
复制代码
7 H, z& m! _& N* n这个定义本来就有的,只是建议将定义的值修改为 1024,这样效果更好。这个值的默认值是 4,但是在实际的操作中发现,如果你上位机传输的数据大于 4,且并不是 4 的整数倍的时候,会出现丢数据或者数据不返回的问题。其根本原因是因为虚拟串口的数据是以数据流的方式发送出来的,接收数据不知道每次接收到的数据大小是多少,所以使用了循环队列,但是一旦你的循环队列很小,很容易出现溢出的问题。比如你设置这个值为 5,你发一个 123456 的数据给 MCU,那么 MCU 就会返回 123451这六个数据。最后的一个数据就是溢出了,所以只能被第一个数据所代替。
2 C8 B9 Z1 Q/ s: e" j, u% Y- /* USER CODE BEGIN PRIVATE_MACRO */
+ |- X2 W0 I5 ?& r, U* H - USBD_CDC_LineCodingTypeDef LineCoding = {) `: w, n) m! F0 D
- 115200, /* baud rate */$ h0 u |+ E1 H Y2 {# A
- 0x00, /* stop bits-1 */
( k- B# t' n, N: b( u0 k9 d - 0x00, /* parity - none */
4 H2 s c1 W2 j% X3 P - 0x08 /* nb. of bits 8 */
0 i8 O3 s' ~, @5 @0 J4 w+ A - };
, @+ @# x2 S! T8 j- M" `% P$ t2 p - uint32_t UserTxBufPtrIn = 0;
3 r2 q) P ^' \& W# L1 ~" { - uint32_t UserTxBufPtrOut = 0;
0 a1 C8 _9 v- ^- E2 w& e; ] - /* USER CODE END PRIVATE_MACRO */
复制代码 # K0 F, }& [, j
上边这部分代码是用来做串口参数设置的。下边的代码中会用到。$ m( ], K' e2 H$ d
在 CDC_Control_FS 函数中的对应位置添加如下 code' `( l$ k, J$ ^! g5 ^2 C; v
- case CDC_SET_LINE_CODING:' G8 U1 V w: D3 s) t. J" \6 \
- pbuf[0] = (uint8_t)(LineCoding.bitrate);
: l' N1 X" {8 Z3 v - pbuf[1] = (uint8_t)(LineCoding.bitrate >> 8);
$ z9 N0 L& f! }" ]( a8 y - pbuf[2] = (uint8_t)(LineCoding.bitrate >> 16);( E, ]7 _5 G; @1 S" V0 g; v8 V* n' m
- pbuf[3] = (uint8_t)(LineCoding.bitrate >> 24);
9 H1 i. z& H Y! k" @+ K* X! M" @ - pbuf[4] = LineCoding.format;" ^- o* K; }& B2 `' q! Z3 D# Z
- pbuf[5] = LineCoding.paritytype;
* w+ ]* W& K# o: ?) K: P5 l+ Q, S - pbuf[6] = LineCoding.datatype;( O9 a6 {& T% ^/ R- b
- break;
复制代码 / z1 H, ^8 a# i* b0 g# M9 x! k
在 CDC_Receive_FS 函数中的对应位置添加如下代码
7 [$ b' M/ w. V* B- <blockquote>/* USER CODE BEGIN 6 */
复制代码
0 S5 z0 G7 s6 u7 Y; s2 I这是用来接收 USB 传输的数据,并缓存到 UserTxBufferFS 这个数组中。4 {3 ~+ s* n. T/ k& l$ R4 `
在最后添加如下函数
( e" c4 V. u/ L" ^4 U- /* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION *// g7 o- |' \+ T/ _& {* H
- //this function is check the application buffer whether has data3 ?3 I7 E# i- a) }; r
- void Main_loop(void)! v5 q; t* ~' s; @
- {
2 e1 b! u% n/ F+ A) B1 d - uint32_t buffsize;8 |5 I4 N3 j) m, E+ A
/ |" g1 y' L/ m- if(UserTxBufPtrOut != UserTxBufPtrIn)" u& n4 K6 ?' w( `" k
- {
9 ]7 O1 l7 T6 y+ U2 d+ h( V - if(UserTxBufPtrOut > UserTxBufPtrIn) /* Rollback */
6 [$ y0 P4 k/ g& @6 R - {
+ O$ U. d3 R6 G/ i - buffsize= UserTxBufPtrIn;; x" Z& N& [3 X/ l
- }" ^+ E1 W/ c7 X5 P' G8 q
- else
+ i p7 T) y0 G5 Q8 w! f+ N: j - {8 f( _1 Z5 x# ]( U/ s
- buffsize = UserTxBufPtrIn - UserTxBufPtrOut;
6 q" F; I, p, o4 e, R - }
0 @' I. K4 `& ]& Y& X - : m! y. R J; V8 b4 s- {+ ~9 q
- CDC_Transmit_FS((uint8_t*)(UserTxBufferFS+UserTxBufPtrOut),buffsize);
, O! f# i, _. } - UserTxBufPtrOut += buffsize;
6 N' h! K- c& k- e" T, Y, K - USBD_CDC_ReceivePacket(hUsbDevice_0);4 y* U1 Y0 D U0 n
- }
9 g/ B# ^7 J; ]' x# ~ - }+ t. i, ?6 _) Q% E
- /* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */
复制代码 4 A: p* f t$ R8 a+ Q C& L
这个函数实现了一个简单的数据队列操作,一旦有数据收到,就将该数据发送到 PC 端,实现数据的环回功能。9 m5 R, f, Z, V: h" c
在 main.c 的主循环中,调用 Main_loop 这个函数即可。
2 ]# W3 |: [) s0 i+ _- /* USER CODE BEGIN WHILE */0 b' x% w4 p9 K3 A& @
- while (1)- o3 F I5 o) M4 ~; V' i( ?6 {
- {& v6 ^/ d) g# `- D; \5 A5 E8 m/ P
- Main_loop();
% E K, w2 o7 I0 V5 ~' ] - /* USER CODE END WHILE */
复制代码
' Z; S. A! P, A) ?" u最后修改一下堆栈的大小:
" m5 W; u5 z( J( K在 MDK 中修改堆栈:, {2 s- S2 P! P' C) D
3 z. G Z8 u) R/ U K& z- y j7 [
6 l( o% J _5 I: E
IAR 中配置堆栈大小
, v$ l a5 O1 o6 \* A1 ]& K9 B- A, x2 m* c, Z9 R+ |
4 b4 [5 c2 I: L) Z/ f
: m3 U6 _- \ `' N7 v* b编译后直接下载到 MCU 中即可以运行查看结果。. J( z' J0 t9 R3 M2 |( B8 `; \" h8 ~
注意:硬件需要一根 Micro USB 线来连接板子的 USB 端口和 PC 机。/ v6 `7 ?& h, m
PC 端需要已经安装好 ST 提供 VCP 驱动程序。3 K. `- E# R/ }7 h
* j6 U2 I' I( _ K( |% p' ~; i+ X: h
: T- D. R1 E L( p
! g: j( S8 y$ g- b l5 v0 k4 M) @9 `" V
) Z3 D/ J( d5 ~* [
2 S& T8 T. T" h- E4 h' b5 A' T
问题总结:
' w/ b, e: M# P! Z# nSTM32CubeMX 会使 STM32 全系列中的软件移植工作变得更方便。在一些以前看起来比较复杂的程序,在 Cube 中可以很简的完成。
i( w: b9 Y6 i3 e R2 E6 h( ?3 \* L1 j* G8 C: L
: N' w4 [: u) u4 N7 X* \$ Q2 I
2 o0 o1 v6 j. @2 |+ t, _7 u3 l' [' F; \" [
|