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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
01
0 l- r+ v* H9 S/ `客户需求1 }. q/ a# e4 r1 Q4 t
客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。% S. E: n! V$ y; n2 k
/ Q. F1 k& ?# o% x  j0 q
在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。. E3 d1 ]0 I9 t$ ~

0 x; A- f3 m' ^7 T! N% f' M0 u02
8 V$ }3 l3 ]. R  G9 f基本硬件和STM32CubeMX的配置
/ I5 o9 W& ~" I, X

  \- B  P7 k( b! c硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。& t4 g1 Z9 g7 Y# y$ ~/ }

, g& V; y3 G: Q+ T" M下面是NUCLEO_H563ZI USB Type C接口原理图:5 H. V  ]# V: P" B5 x0 g9 B$ z
12.png 0 t$ n5 U  x$ g' o3 O
- ~0 b( {6 @; s2 h! h; W9 z
  m+ e! t- b, a) {
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。

7 Q& V) |4 w" w& C' y
7 N) w+ g) b/ R# ?7 Q  i. W
13.png

3 o: R  V* |  S7 d, m' o
14.png
9 k) ?: Z% r4 |# v8 U3 e

- x& Z( _" q  m, I4 Z

4 O) W4 g6 x5 w  O0 \* p
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。

7 Q: C% s2 `6 o* ^5 N+ _
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)
5 X" t3 Y9 F- X

, K. y7 x7 E4 Z7 ]: D
1 r5 q# c$ H3 V- S

. ^! I/ O/ R* ^1 H* i
第一步:开始一个新的不带trustZone工程:
% ^+ G3 J9 }! z1 r9 Y
5 c' q* ]4 l) E5 n0 U& E" j* X
15.png
$ [% Z) y# |6 g
4 p5 N+ o% f$ ?
7 C: v; L% A( ?5 V, I# z1 r
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。

1 ^& W$ N+ I/ k, C
: Z' X) [& E: |
16.png 7 P0 g. s1 I9 h, S, K0 @+ |( j
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。
8 U8 ]7 N) c, y9 U  Y( ^8 K& {

5 d. z  v2 {* b5 l" X
17.png 9 @# Y/ K+ s# c3 H9 s
2 l5 [. j6 O  F& A9 h4 v

- ^6 t6 x1 k3 A
第四步:配置USBX:
% n5 I- y* S0 n1 i
1. 使能Core System。
3 O" y' e3 n7 s. x. h4 `
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。

7 ^/ c: K/ g" i4 E; s+ u  \; U
3. 在Device Class FS中,选择CDC ACM。

$ r2 f9 l1 m4 \5 z6 U5 A
0 ^- j3 ?' Z9 x- M
18.png : ^' J2 ]5 n/ W. h% W. N5 ~

. j0 D. `9 E; Q7 x

! G. m) y/ {! u/ s: Z! b! n
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。
! ]% v& ^% X7 L7 k; j
6 h5 c  O, }) ?, {
19.png
* f' y4 w% m( h  A5. 其它的Platform,选择USB。

5 p1 k7 ~1 b3 L4 g) {2 R: y
20.png   Z0 Y% \9 g3 n0 }7 _7 j; A  I
. C! `. l" s- F

+ E* }9 ?, x$ R
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
6 u$ U  z" z- H, y* x6 i
9 B& x) b  r9 g# {4 h
21.png ! n: r2 ^) f" g' i
8 T+ Z; u. [+ ^+ o/ v; }! S- c

+ d( V& |8 J7 K8 N  `$ I. f
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。
" H( T5 \9 b+ h$ B2 l
) ^/ V/ e2 F0 F, _
22.png 1 |* |$ ~2 D# e* O8 s

1 @7 n: q" r, n. c( G0 Q9 j
, s8 [1 y! O2 Y5 d5 ?* o  `# u
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

0 F7 I$ h  O* `9 E  J1 H3 Z

# T* W, @* o6 S
23.png 6 N1 E% Z( j8 o4 H9 C. D

+ b+ m7 U2 N& U' C4 e3 J9 _
3 m/ e9 r( i" m4 n
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。

' v5 B( q. R4 u, O8 X; R

2 j, u5 Z% O2 X, `% y
24.png 7 `: ?* n) W* [. Z
, y1 B* r, `3 c

8 L5 M( O9 l6 m" B  h% K
2.2. 使用STM32CubeMX配置并生成工程

) m, ?$ s# F/ o/ E4 K
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

) W$ _! J- i* e3 A0 a
# a9 u: y8 o/ O6 Y
25.png 4 v8 }1 i% {  y
最后生成的工程文件的结构如下:
* y, ^3 E5 Q7 K8 A
! W) v& t  [- g! `% T6 q
26.png , \! ^6 A/ L5 y2 J  W4 J. U! T
9 H' ^1 n4 N8 ]( N
2.3. 参数配置的解释
& Z5 y" X" c' R- B/ J
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。

8 o( q$ `1 y# s5 e

; M/ W5 T0 F) ?9 [5 `5 [7 o& Z' T
27.png ) b1 f6 i/ g# o* l0 d8 _' O
调整依据请参考如下内容:
2 [- f. d4 F* ?$ Q: [4 P0 Y
& a! S8 O, b3 z4 X& c% l
28.png
: N# U" _' o- f0 x

% h4 I  M! b; L$ t9 h- v" |* f# n

$ H- F. ^) R" u0 ^2 u2 ?+ X
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:
/ R3 p+ `# N4 Y9 G7 n- d
1. USBX设备系统堆栈大小(USBX Device System Stack Size)
7 [8 N; g, m# T; q. m) Q$ Y
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)

3 [* J8 }% V+ O! e" q8 |3 e% c
3. USB应用程序创建的线程堆栈
+ Z  [. m& {4 {" O" [3 d
4. 以及RX和TX缓冲区
9 l2 Q8 [- W! f  l" l- ?$ t0 l, T
7 d6 X  p, k6 f& a3 V% s
29.png 9 Q6 F; Y$ j( t. ~

- z2 f; G% T' g5 o' J8 a' }" B8 [
03

% W% S& n# S: A) g. v4 v  f3 G
对工程进行完善和修改

7 C& j0 R! l6 f
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:
# f2 z1 f( W  {! z* I

0 D" f: g5 F5 `5 C
30.png
+ t7 Y( `; t" \! s& _" E3 O4 Q

4 q9 t3 c) f; U- S) g4 v
* l3 p, M# _( y" [+ P  Q8 b
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。

# o' u! Y% `- g  c% R, z3 W. Z2 F' N: N$ c$ F5 A
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。

4 C" }9 b8 y' X
  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>
复制代码
1 m; P3 T. U9 @. p" @: s
第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
; v2 z4 M4 I( B1 J( l( c8 T7 t: I* H
  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>
复制代码
$ H0 U/ k7 F  M6 o! i% J) C
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。

0 f7 R5 K* F& [8 d
  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>
复制代码

. p0 J9 O* m. e# p1 X) [
先调用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。

5 W% e- i) h& O4 h% Y7 o! v
' b0 {, k+ C  m; v4 x
31.png ; ~. M7 `/ b$ T- b1 z, ~
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。

+ {; i; u. X% b+ x9 \; {
! {% O- j5 M6 r

% l6 u" k3 n( m

1 [4 E5 D% A+ ]5 F6 O) n5 P
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
9 N3 R8 B; j% Z; t: W3 b

8 h- v5 j; j5 V: K" y# A
% G( F. a. E2 c

! I. b, K1 v8 k: v; s" e
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。
( s0 a7 {7 j: f; d) v% g6 b4 v
; a8 S8 c( M; |" D; O0 M
. K8 ^/ h6 S! @
! k# T, M- A/ k3 T1 T  x
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。
9 B$ @% E1 L5 t; ^! i- `  x

7 z' {2 X5 o( c/ f! N8 @8 o: s, z) m

! O- O/ j! |$ Q$ z

. x8 R/ V7 a. j. P/ x
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。

: h" P( h% @+ N& Q! [
  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>
复制代码
# ?; ?* U8 A# T9 E8 i6 v+ t; q, m
第五步:下面的usb_connect()函数是根据客户需要设计的。在PC端连接到设备端STM32H563,VBUS连接时触发中断后调用的函数(客户产品中把VBUS分压后连接到一个GPIO口,由于客户第一版本的PCB没有设计这部分电路,因此调试时使用按键中断替换了Device连接到PC的情况)。Usb_disconnect()函数,是处理STM32H563从PC断开的情况(调试时使用按键再次按下来替换VBUS断开的具体功能)。保留了信号量,程序处理中如果不使用这个信号量可以删除,具体请参考软件工程文件:

$ W8 D5 I# |/ G1 E" u4 `
  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>
复制代码
. e/ `# w/ s8 y, @/ @9 B
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:
3 O6 s9 P, }( u, H
  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>
复制代码
# @% w5 f- U' s( g. l* t7 F
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。
: \7 Z! j/ Z: w
  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>
复制代码

) d) v' p4 L2 O$ @& H
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
/ x+ ~+ f$ O, r" Z$ Q0 I# e
( Y  Z; H( D9 m
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。
4 f( D9 D" [3 s/ B" l& h0 \% K
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">
    : C, H4 o7 P" z; G1 I
  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>
复制代码

. ~, Y- f; r; U3 a: J
$ D7 B3 i4 h( c+ H% m1 P( P; E
04
( A$ v+ b: d8 ?' f1 l2 }9 t
对代码进行的测试
$ T. i1 m. }. d" A; S
对软件工程添加代码后就可以进入编译和调试环节。
& V: x. f- X$ i3 w4 {2 R
' Y4 H& ]- ?1 M7 g
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。

" P- a* ]! M! J! P

3 b' B2 T! H, w4 e, Q+ a! ]
32.png
& A3 p- G- a& E/ ^2 F" t
或EXTI_SWIER1.SWI13中写1回车后:
/ [; i' S4 h$ ^  J

/ k3 h1 P" E) L6 h5 ^
33.png
, `3 J% P# ]. `' h( U& k0 r
然后产生外部按键中断:

2 T: Y2 n0 {4 K, p+ }
4 o( a" q! l6 N# I( h+ }/ J/ s- E
34.png
5 Y) n  _: ]6 _  v1 g
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

& s4 [' a& i: {; ?8 w
& i1 E# `" o' Z: Y: u9 f; A
35.png ' ~+ V5 I+ y# z# ]% k+ X( f
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:

' o* v2 p* r2 y; f2 b' y

3 _3 O. w' z# {% q) K2 r
36.png 6 G4 ~# h" T! S% T
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。

  d' _* G4 M1 q7 }& j
0 L$ Q( L3 O& T* z
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
; l5 j* q, e+ Q+ ~8 B$ K" M

( p3 A, B1 Z  Y' @1 @( T$ h0 v' t: N3 t

/ t, b8 F2 H$ [1 B

' s, D/ n+ K* @
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:
4 W9 D+ N' M+ o- D# }5 ?+ A" ^& A
" L3 [7 g% k6 u) l  D6 F
8 r- G$ V, ?7 T
0 u% b  X! I3 O' O1 {+ B
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。
5 [  ^* {, P0 R
  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>
复制代码

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

% U( G$ v7 u9 m4 k

! B: d, z, L8 L1 L- w
37.png , Y1 s& R: m+ n1 f" |/ D9 @$ u7 l
05

5 j' ^# Z1 B7 p* @7 T
小结

& K0 P5 W( V4 A1 V4 u0 k  p' @
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。

& L% \& |. |2 t% H9 R
" R- r$ I# L3 y/ h; T2 \
收藏 评论0 发布时间:2025-2-24 09:28

举报

0个回答

所属标签

相似分享

官网相关资源

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