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

实战经验 | 选择USBX模块生成USB CDC ACM无PD的项目

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
01
  M; B2 ?( \6 W: ~: Q客户需求
7 ~' C0 w) O& h7 f客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。7 m3 G3 U: @3 q: P

: W- }7 Z( \  j& V在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。
2 N- c% o' L4 k' [+ V$ J: m$ P! {
' T4 I5 w7 Q- e1 z2 K: N02* x1 J% O* h9 H/ c
基本硬件和STM32CubeMX的配置
: k" O  r! w! _/ h& Z3 N  r
! X! z7 T+ C/ p9 s, C: |
硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。
! d' z9 Q/ N  t# U, g2 b4 o- V" }: ]+ V3 r
下面是NUCLEO_H563ZI USB Type C接口原理图:# m" x. p- l. f+ V! ^' O
12.png
. q- Y: [+ b, p; V5 |: }4 H6 e
3 i. A% I2 i' a: \' z% g' s3 _

" `2 K! }5 q6 r" W6 Q( ^) c
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。

1 P1 T, a$ {+ z) R* B* ^
; n$ e. V+ D4 i! s" d/ ?
13.png

" ~- ^# q' Y6 I, Y1 \
14.png
. N9 n: L& Z6 p5 O; h

) W/ E. v! h% Z0 C9 z8 i
+ A: }2 h( ]; @7 B
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
* u) o% \  _/ A
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)

' ~$ \' q6 {* |; W+ K  D: T

: Z7 `! h4 z9 d2 y9 M, H6 D7 R
; G0 R5 o% I0 v& a! L

' S! O" [, {6 c) v2 J! W6 g
第一步:开始一个新的不带trustZone工程:
' a; |" G  i& e9 q# m% R! |+ j
! i4 m  {7 L  F; d" D
15.png
" N' A! Y6 P/ v0 z% Z
$ d0 Z% [) z& q
$ }8 A$ T1 s+ I3 A' o
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。
3 F% \# d4 v' Z9 L% _
& }/ ]9 F) ^% I' G; U5 C
16.png 3 N+ X6 X" S9 n" V7 R3 ~
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。

3 Z+ [% _) _" D7 J9 m& n; o2 G# _+ w

" T, I8 N- F' r1 A0 f& N7 W5 l
17.png
$ N% L+ X) U/ ]4 |5 k3 u
: e4 U& M" v- ^1 B- r  f1 K

0 j: w; E3 m1 L
第四步:配置USBX:

& G$ ?1 Y6 `$ A6 _1 y4 c
1. 使能Core System。
9 y8 B* U$ t% {1 g* |/ ?
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
+ t* h/ M: i5 k: S* M
3. 在Device Class FS中,选择CDC ACM。

  |, y3 R+ n4 A( Q

8 ~0 N  R/ V, |8 K2 q
18.png
0 y& Y" z$ V2 K+ z

  J2 i' g, X( D8 o/ r$ E: ^6 G
1 |1 Q, H, V4 B7 y" Q6 G- R
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。

, p& x8 w5 n7 S! ]

( B( n% i6 O9 X: E/ x8 t0 `
19.png 9 h7 {* o: m" {1 D& e7 m
5. 其它的Platform,选择USB。
) V& F6 h7 G, \& F  a$ D5 H6 N' O
20.png
% m, h- D$ y$ j( X, R* X. Z

3 n1 M& E" s7 `$ Q

3 |& Q/ Y3 b  D+ Y
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
$ _0 c4 B( x  n& N, h

- y) f& Z+ d' \5 b
21.png
5 T5 g  r; a/ x- e4 q6 Y/ D

4 ~; B3 r8 z# p
, H6 o1 N. n" o0 u  _9 E! ]5 m1 _
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。

! X9 B4 M. _9 J. D
# E& P7 y, d9 \$ p  g; u9 E4 n
22.png ; K4 {% G+ F8 l; p

6 L& D% k6 {% K# J' H( h

' B; x  r, |) s- F, t: J+ p0 Y
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。
/ G! k3 W: m+ y8 {! J

+ P1 L6 F. B8 R) U$ _# H) a
23.png 3 G- @3 R) ^, K4 U
0 `( H/ i3 T3 I8 j+ F0 o' B

7 P0 E' y5 L4 J6 w
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。

7 r+ `1 z$ {& w+ ]/ Y; D; I  J

+ ?/ W1 L5 d4 |) T: |6 y& l
24.png ; R+ j4 @* r1 b5 j3 P
# t! `  B' W  l

1 `0 ^/ R* q6 K% z
2.2. 使用STM32CubeMX配置并生成工程
2 p! n  j* m) I# e
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

2 w  o+ m  \- ^& J/ J) j
- v5 z( N5 e8 T$ k+ p
25.png / ]( j  O# _, Q6 K6 K9 P2 m
最后生成的工程文件的结构如下:

. k5 m2 ~; \) ?4 k& S
4 }  a0 E: t2 K% J& i
26.png & C5 E' I. k! D
& U. N6 A5 @: R: k5 J
2.3. 参数配置的解释

7 N0 X  }5 a1 B7 u
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。

: F" Y/ ]. r3 U
- E7 R8 _& W0 Z4 w
27.png
" ~2 y! B6 p* L8 b* K; b3 {
调整依据请参考如下内容:

! B" y) N( \+ }% O
6 [4 ~! r; X2 ~3 @
28.png + h0 c" {# ^. z& a8 `

! f9 M4 }& [7 o) @& ?. x$ a8 b4 _

( R, k0 F- W* L, h; A1 Y
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:
1 R3 j4 O1 C8 V/ @
1. USBX设备系统堆栈大小(USBX Device System Stack Size)
* U7 F2 C' X3 P0 ^0 l
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)

! X0 ?; I9 W9 z; I
3. USB应用程序创建的线程堆栈
4 l. Z1 X- R% b9 H! T
4. 以及RX和TX缓冲区

0 [  U( |3 O5 n! e$ g& H" ]

" N) |) k$ R2 {+ ], k  M/ G) D
29.png
2 {; F4 H0 u+ a" j7 Y4 r- Z- A' E
03

4 ^* e' x' B" I1 H+ R8 C3 h
对工程进行完善和修改

7 B8 ?; M% v6 D$ {! [4 q0 P3 ]
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:

1 V7 ?4 l+ ~5 J6 }0 P: }. i' Y0 y7 @; [
/ {7 t/ E7 t, f. e
30.png 3 S* e6 x9 [" `9 b7 J  m
5 v- W" I1 J/ }. i. r' [) q
( G$ z/ i% B! I! d+ `& d
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。
, a1 W2 V* C3 J9 v3 e
* `( b8 A. ?7 b/ o5 f
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。

" R4 r' U. T% J6 t: B+ [7 {' M- P
  1. <div style="text-align: left;">/* USER CODE BEGIN Includes *///</div><div style="text-align: left;">#include "ux_dcd_stm32.h"   </div><div style="text-align: left;">#include "main.h"</div><div style="text-align: left;">/* USER CODE END Includes */  </div><div style="text-align: left;">/* USER CODE BEGIN 1 */</div><div style="text-align: left;">#define APP_QUEUE_SIZE  5</div><div style="text-align: left;">typedef enum     </div><div style="text-align: left;">{</div><div style="text-align: left;">STOP_USB_DEVICE = 1,</div><div style="text-align: left;">START_USB_DEVICE,</div><div style="text-align: left;">} USB_MODE_STATE;</div><div style="text-align: left;">/* USER CODE END 1 */</div>
复制代码
  y/ m! H7 F9 p0 U3 j. y% K
第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
4 a) }# n! w8 n  U
  1. <div style="text-align: left;">/* USER CODE BEGIN PTD */</div><div style="text-align: left;">extern PCD_HandleTypeDef hpcd_USB_DRD_FS;</div><div style="text-align: left;">#if defined ( __ICCARM__ ) /* IAR Compiler */</div><div style="text-align: left;">  #pragma data_alignment=4</div><div style="text-align: left;">#endif /* defined ( __ICCARM__ ) */</div><div style="text-align: left;">__ALIGN_BEGIN USB_MODE_STATE                  USB_Device_State_Msg   __ALIGN_END;   </div><div style="text-align: left;">TX_QUEUE                         ux_app_MsgQueue;   </div><div style="text-align: left;">extern TX_QUEUE     ux_app_MsgQueue;</div><div style="text-align: left;">extern TX_BYTE_POOL ux_device_app_byte_pool;</div><div style="text-align: left;">extern unsigned int USB_connect_State;</div><div style="text-align: left;">/* USER CODE END PTD */</div><div style="text-align: left;">/* USER CODE BEGIN PV */</div><div style="text-align: left;">extern PCD_HandleTypeDef hpcd_USB_DRD_FS;</div><div style="text-align: left;">static TX_THREAD ux_cdc_read_thread;   </div><div style="text-align: left;">static TX_THREAD ux_cdc_write_thread;</div><div style="text-align: left;">unsigned int KeyNumber=0;  </div><div style="text-align: left;">void usb_connect(PCD_HandleTypeDef* hpcd);</div><div style="text-align: left;">void usb_disconnect(PCD_HandleTypeDef* hpcd);</div><div style="text-align: left;">void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin);</div><div style="text-align: left;">void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin);</div><div style="text-align: left;">/* USER CODE END PV */</div>
复制代码
8 O1 b/ R7 r7 a/ B
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。

  ~, J& m% a# z! z& n9 Y
  1. <div style="text-align: left;">static VOID app_ux_device_thread_entry(ULONG thread_input)      </div><div style="text-align: left;">  /* USER CODE BEGIN app_ux_device_thread_entry */</div><div style="text-align: left;">{</div><div style="text-align: left;">      MX_USB_PCD_Init();   </div><div style="text-align: left;">      HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x00 , PCD_SNG_BUF, 0x40);</div><div style="text-align: left;">      HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x80 , PCD_SNG_BUF, 0x80);</div><div style="text-align: left;">      HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x03, PCD_SNG_BUF, 0xC0);</div><div style="text-align: left;">      HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x81, PCD_SNG_BUF, 0x100);</div><div style="text-align: left;">      HAL_PCDEx_PMAConfig(&hpcd_USB_DRD_FS, 0x82, PCD_SNG_BUF, 0x140);</div><div style="text-align: left;">      ux_dcd_stm32_initialize((ULONG)USB_DRD_FS, (ULONG)&hpcd_USB_DRD_FS);</div><div style="text-align: left;">  /* USER CODE END app_ux_device_thread_entry */</div><div style="text-align: left;">}</div>
复制代码

* k! d* |6 j. ]2 `% n; U, t. m: }
先调用MX_USB_PCD_Init()来初始化USB外设;然后使用HAL_PCDEx_PMAConfig(…)进行配置,该函数为STM32H563配置专用USB RAM内存中的PMA(packet memory area),在本例中,端点为0 IN/OUT ( USB标准控制端点)、端点3 OUT  (CDC数据输出点)、端点1 IN  (CDC数据输 入端点)和控制端点2 IN ( CDC 命令端点)。PCD_SNG_BUF参数意味着我们为端点使用单个缓冲区,这是必要的,因为我们在双向模式下使用端点。最后,该函数的最后一个参数是PMA地址,缓冲区大小为64Bytes。
) ^0 Z( h0 _  G6 Y8 c0 h2 |* D

4 e) Y" f- E1 t1 u# [( Q4 w* C& ~
31.png
+ D' L8 k* ^$ m- b% U
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。

) }5 L! I% F  R" Z+ {9 F6 h% R9 S3 n
. m1 K0 y7 X# B' N! Z- o

6 o, Z/ R0 G6 p" U

/ v6 A7 C+ \6 y  R
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
/ x% N. _9 R) g! v
: b7 [' d3 e; I2 Z" P8 i

1 s- @9 P' V; p$ V, }  W% r
6 {2 q: d6 M+ _5 k$ \2 N# C) ?- p8 K
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。

( c2 {. x5 W1 F- w1 J( p4 t. A+ a
: D" k! h& [* |0 ?  u  Q
& b9 M* H. I6 ^4 B

7 b  m' B4 h* o6 T% \
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。

, C! }0 }4 M  ?6 c* t& d9 d
' O- D5 e- E% w2 L5 C3 J

' x8 @* [$ p$ h- j4 `2 G; Q9 o  q( f
7 ^, @1 x( s& }( x8 t& ~& b
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
! m! |+ K/ u6 H; g5 h& B$ O# n
  1. <div style="text-align: left;">/* USER CODE BEGIN MX_USBX_Device_Init1 */</div><div style="text-align: left;">    /* Allocate Memory for the Queue */</div><div style="text-align: left;">    if (tx_byte_allocate(byte_pool, (VOID **) &pointer, APP_QUEUE_SIZE*sizeof(ULONG),</div><div style="text-align: left;">                       TX_NO_WAIT) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    /* USER CODE BEGIN MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">    return TX_POOL_ERROR;</div><div style="text-align: left;">    /* USER CODE END MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">  }</div><div style="text-align: left;">  /* Create the MsgQueue */</div><div style="text-align: left;">  if (tx_queue_create(&ux_app_MsgQueue, "Message Queue app", TX_1_ULONG,</div><div style="text-align: left;">                      pointer, APP_QUEUE_SIZE * sizeof(ULONG)) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    return TX_QUEUE_ERROR;</div><div style="text-align: left;">  }</div><div style="text-align: left;">/* Allocate memory for the UX RX thread */</div><div style="text-align: left;">if (tx_byte_allocate(byte_pool, (VOID **) &pointer, UX_DEVICE_APP_THREAD_STACK_SIZE,</div><div style="text-align: left;">                       TX_NO_WAIT) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    /* USER CODE BEGIN MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">    return TX_POOL_ERROR;</div><div style="text-align: left;">    /* USER CODE END MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">  }</div><div style="text-align: left;">/* Create the UX RX thread */</div><div style="text-align: left;">    if (tx_thread_create(&ux_cdc_read_thread, "cdc_acm_read_usbx_app_thread_entry", usbx_cdc_acm_read_thread_entry,</div><div style="text-align: left;">                       1, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO,</div><div style="text-align: left;">                       UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE,</div><div style="text-align: left;">                       TX_DONT_START) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    /* USER CODE BEGIN MAIN_THREAD_CREATE_ERROR */</div><div style="text-align: left;">    return TX_THREAD_ERROR;</div><div style="text-align: left;">    /* USER CODE END MAIN_THREAD_CREATE_ERROR */</div><div style="text-align: left;">  }</div><div style="text-align: left;">   </div><div style="text-align: left;">/* Allocate memory for the UX TX thread */</div><div style="text-align: left;">if (tx_byte_allocate(byte_pool, (VOID **) &pointer, UX_DEVICE_APP_THREAD_STACK_SIZE,</div><div style="text-align: left;">                       TX_NO_WAIT) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    /* USER CODE BEGIN MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">    return TX_POOL_ERROR;</div><div style="text-align: left;">    /* USER CODE END MAIN_THREAD_ALLOCATE_STACK_ERROR */</div><div style="text-align: left;">  }</div><div style="text-align: left;">/* Create the UX TX thread */</div><div style="text-align: left;">    if (tx_thread_create(&ux_cdc_write_thread, "cdc_acm_write_usbx_app_thread_entry",</div><div style="text-align: left;">usbx_cdc_acm_write_thread_entry,</div><div style="text-align: left;">                       1, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO,</div><div style="text-align: left;">                       UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE,</div><div style="text-align: left;">                       TX_DONT_START) != TX_SUCCESS)  //UX_DEVICE_APP_THREAD_START_OPTION</div><div style="text-align: left;">  {</div><div style="text-align: left;">    /* USER CODE BEGIN MAIN_THREAD_CREATE_ERROR */</div><div style="text-align: left;">    return TX_THREAD_ERROR;</div><div style="text-align: left;">    /* USER CODE END MAIN_THREAD_CREATE_ERROR */</div><div style="text-align: left;">  }</div>
复制代码

  f: u2 ^3 C+ D) z
第五步:下面的usb_connect()函数是根据客户需要设计的。在PC端连接到设备端STM32H563,VBUS连接时触发中断后调用的函数(客户产品中把VBUS分压后连接到一个GPIO口,由于客户第一版本的PCB没有设计这部分电路,因此调试时使用按键中断替换了Device连接到PC的情况)。Usb_disconnect()函数,是处理STM32H563从PC断开的情况(调试时使用按键再次按下来替换VBUS断开的具体功能)。保留了信号量,程序处理中如果不使用这个信号量可以删除,具体请参考软件工程文件:

3 Z4 _5 @0 {6 h  C6 d
  1. <div style="text-align: left;">/* USER CODE BEGIN PFP */</div><div style="text-align: left;">void usb_connect(PCD_HandleTypeDef* hpcd) {</div><div style="text-align: left;">  HAL_PCD_Start(hpcd);   </div><div style="text-align: left;">  USB_Device_State_Msg = START_USB_DEVICE;</div><div style="text-align: left;">  if (tx_queue_send(&ux_app_MsgQueue, &USB_Device_State_Msg, TX_NO_WAIT) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    Error_Handler();  }</div><div style="text-align: left;">   if(tx_thread_resume(&ux_cdc_read_thread)==TX_THREAD_ERROR)</div><div style="text-align: left;">   {</div><div style="text-align: left;">     Error_Handler();   }</div><div style="text-align: left;">  if(tx_thread_resume(&ux_cdc_write_thread)==TX_THREAD_ERROR)</div><div style="text-align: left;">   {</div><div style="text-align: left;">     Error_Handler();  }</div><div style="text-align: left;">}</div><div style="text-align: left;">void usb_disconnect(PCD_HandleTypeDef* hpcd)  {</div><div style="text-align: left;">  HAL_PCD_Stop(hpcd);  </div><div style="text-align: left;">   USB_Device_State_Msg = STOP_USB_DEVICE;</div><div style="text-align: left;">  if (tx_queue_send(&ux_app_MsgQueue, &USB_Device_State_Msg, TX_NO_WAIT) != TX_SUCCESS)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    Error_Handler();  }</div><div style="text-align: left;">  if(tx_thread_suspend(&ux_cdc_read_thread)==TX_THREAD_ERROR)</div><div style="text-align: left;">   {</div><div style="text-align: left;">     Error_Handler();   }</div><div style="text-align: left;">  if(tx_thread_suspend(&ux_cdc_write_thread)==TX_THREAD_ERROR)   {</div><div style="text-align: left;">     Error_Handler();</div><div style="text-align: left;">   }</div><div style="text-align: left;">}</div><div style="text-align: left;">void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin){</div><div style="text-align: left;">   if(KeyNumber==0)  //please use the status of "VBUS connect and disconnect" to control program direction.</div><div style="text-align: left;">  {</div><div style="text-align: left;">    usb_connect(&hpcd_USB_DRD_FS);   }</div><div style="text-align: left;">   if(KeyNumber==1)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    usb_disconnect(&hpcd_USB_DRD_FS);  </div><div style="text-align: left;">  }</div><div style="text-align: left;">}</div><div style="text-align: left;">void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin){</div><div style="text-align: left;">  if(KeyNumber==0)  //please use the status of "VBUS connect and disconnect" to control program direction.</div><div style="text-align: left;">  {   </div><div style="text-align: left;">    usb_connect(&hpcd_USB_DRD_FS);   }</div><div style="text-align: left;">   if(KeyNumber==1)</div><div style="text-align: left;">  {</div><div style="text-align: left;">    usb_disconnect(&hpcd_USB_DRD_FS);  </div><div style="text-align: left;">  }</div><div style="text-align: left;">}</div><div style="text-align: left;">/* USER CODE END PFP */</div>
复制代码

, h8 d" Q# A! Z. I
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:
! p! c; y8 n, F6 y, N9 I
  1. <div style="text-align: left;">/* Private includes ----------------------------------------------------------*/</div><div style="text-align: left;">/* USER CODE BEGIN Includes */</div><div style="text-align: left;">#include "app_usbx_device.h"</div><div style="text-align: left;">/* USER CODE END Includes */</div><div style="text-align: left;">/* USER CODE BEGIN EFP */</div><div style="text-align: left;">#define APP_RX_DATA_SIZE   2048   </div><div style="text-align: left;">#define APP_TX_DATA_SIZE   2048</div><div style="text-align: left;">VOID usbx_cdc_acm_write_thread_entry(ULONG thread_input);</div><div style="text-align: left;">VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input);</div><div style="text-align: left;">/* USER CODE END EFP */</div>
复制代码
$ e8 t0 q& n6 T% e
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。
2 K0 y# t/ ]6 X* O0 p2 X
  1. <div style="text-align: left;">/* Private define ------------------------------------------------------------*/</div><div style="text-align: left;">/* USER CODE BEGIN PD */</div><div style="text-align: left;">UX_SLAVE_CLASS_CDC_ACM *cdc_acm;   </div><div style="text-align: left;">/* USER CODE END PD */</div><div style="text-align: left;">VOID USBD_CDC_ACM_Activate(VOID *cdc_acm_instance)</div><div style="text-align: left;">{</div><div style="text-align: left;">/* USER CODE BEGIN USBD_CDC_ACM_Activate */</div><div style="text-align: left;">cdc_acm = (UX_SLAVE_CLASS_CDC_ACM *)cdc_acm_instance;     </div><div style="text-align: left;">//UX_PARAMETER_NOT_USED(cdc_acm_instance);</div><div style="text-align: left;">/* USER CODE END USBD_CDC_ACM_Activate */</div><div style="text-align: left;">return;</div><div style="text-align: left;">}</div>
复制代码
6 Y' v6 W- x5 b+ S# Q
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
! @4 ]6 g5 E; l+ b' i" y! U
+ P) c. Y! O* H: q
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。
3 E/ @4 @  P3 e, y+ Y. d% O
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">2 n" O7 R7 C" M7 D1 ]' W
  2. </div><div style="text-align: left;">VOID usbx_cdc_acm_write_thread_entry(ULONG thread_input)</div><div style="text-align: left;">{</div><div style="text-align: left;">      ULONG tx_actual_length;</div><div style="text-align: left;">      const uint8_t message[] = "USBX Application Running!\r\n";</div><div style="text-align: left;">   while(1)</div><div style="text-align: left;">      {</div><div style="text-align: left;">        ux_device_class_cdc_acm_write(cdc_acm, (UCHAR *)(message), sizeof(message), &tx_actual_length);</div><div style="text-align: left;">                     tx_thread_sleep(100);</div><div style="text-align: left;">      }</div><div style="text-align: left;">}</div><div style="text-align: left;">VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input)</div><div style="text-align: left;">{</div><div style="text-align: left;">      /* Private Variables */</div><div style="text-align: left;">      ULONG rx_actual_length;</div><div style="text-align: left;">     uint8_t UserRxBuffer[64];</div><div style="text-align: left;">      while(1)</div><div style="text-align: left;">      {</div><div style="text-align: left;">          if(cdc_acm != UX_NULL)</div><div style="text-align: left;">             {</div><div style="text-align: left;">                    ux_device_class_cdc_acm_read(cdc_acm, (UCHAR *)UserRxBuffer, 64, &rx_actual_length);</div><div style="text-align: left;">                   switch(UserRxBuffer[rx_actual_length-1])</div><div style="text-align: left;">                   {</div><div style="text-align: left;">                   case '1':</div><div style="text-align: left;">                          break;</div><div style="text-align: left;">                   case '0':</div><div style="text-align: left;">                          break;</div><div style="text-align: left;">                   }</div><div style="text-align: left;">             }   </div><div style="text-align: left;">      }</div><div style="text-align: left;">}</div>
复制代码
  \: r% r8 W1 M; K. N, O

+ c2 s7 n( \* a3 V& n0 ~- f
04

" U: z2 c  f; D8 f
对代码进行的测试

; L  g+ i0 X% D# H& U4 i
对软件工程添加代码后就可以进入编译和调试环节。

4 c% @; H( q% i; \. ?

( y8 p6 j" p( b1 [
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。

2 S$ [  ?1 N) k% {, ?. ^

$ _' w1 D# H# A- I
32.png 1 l" y, r# x7 [: Z+ k! s% W
或EXTI_SWIER1.SWI13中写1回车后:
& I+ d+ i( J$ B( K  |) K7 C
& c2 S& `. G+ u/ S4 i! y: T  V
33.png % v' i; c# v, Y( E1 C
然后产生外部按键中断:

. E/ K. e8 J/ M/ i& x; d
: y0 W5 h- x, ?, D0 C# L
34.png
) J0 X+ C  V, l
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:
4 ^3 B0 S, b: Y# X1 _5 s
6 r4 i# S/ |" ~" P  X
35.png # q, H! t9 m& B  B2 W
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:
; F9 |( V7 q2 Z6 r# p9 M  ~) P
- i. |* j: N' s# Z# T
36.png
, I/ b  E. i" n( m8 y- m; e; ]
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。

; P( Z/ q$ P( @4 a/ P7 L5 h, ?6 X& k! x* O: d# r, |
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
+ U6 b7 K/ u: Y) r: O
0 e' S$ g4 y6 @& O( x9 s

6 W% I* u! T2 \9 A* U, ?& N
& k( Y8 v1 I' \0 H
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:

" Z1 w  T% p) D' w- b

9 Z# c/ U0 ]. h+ l

0 W; S" f  r) A: g+ A9 ^& g; @

/ r1 R+ k& [4 q' q! d7 @
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。
1 i0 `, t  N2 t
  1. <div style="text-align: left;">tx_queue_create(&ux_app_MsgQueue, "Message Queue app", TX_1_ULONG,</div><div style="text-align: left;">pointer, APP_QUEUE_SIZE * sizeof(ULONG))</div><div style="text-align: left;">tx_queue_send(&ux_app_MsgQueue, &USB_Device_State_Msg, TX_NO_WAIT)</div><div style="text-align: left;">tx_queue_receive(…)</div>
复制代码

  ~* c+ {- w' \& w. X/ ^
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…

$ }/ }! l- D1 m* W5 S" F! p

0 B+ k* y0 }/ ^6 s
37.png
. S# z  _( Z; |: j2 {. O
05

# h) r- I+ g* v" {+ y# X% G
小结

4 u3 }' G. @  A6 L7 F
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。
- j6 T) P# M, Q# @: l7 r2 [) G

) y% S6 t# F, B3 v* [) N# E
收藏 评论0 发布时间:2025-2-24 09:28

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版