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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
01
* j) N2 j: J) x4 f客户需求0 C+ r7 X* y0 r* l9 D( N
客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。
  B+ y+ ^4 g" U" g7 R1 C* Z4 S3 M& `4 p: {' t1 o" ]
在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。
% E) m* g5 ]7 f  L5 d, R1 S
/ \2 K  T9 W4 U/ s: C: ]# i5 R02
" y5 p+ y5 d/ O4 v' i+ j基本硬件和STM32CubeMX的配置5 h0 N0 f' o# ]

( i3 ~! g9 C8 q7 J* Y硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。
( ~2 C; T% ~0 s5 R
5 h2 K3 n" M% h下面是NUCLEO_H563ZI USB Type C接口原理图:9 U) s5 x# E1 ~: a
12.png
; X3 T3 L* p* p

7 ~$ u7 G1 h6 J2 E, C% N2 `: F! r
" t  d5 B7 J- G: i2 f
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。

: a  M7 S1 q1 ]4 B) ~; P, x

9 d0 e. i- P% |7 X3 Y6 H- U& d6 y: I4 s
13.png

  U4 U; N( Y. i$ p+ l; B5 t
14.png : w1 x6 X5 @3 k, i3 i1 D! s
5 o: X: h8 e  F" j+ X

% s( B: r4 n) Q. Z# Z8 N2 }/ ?
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
/ S8 `2 k, y4 u2 i6 i
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)
5 r+ W0 c7 ]* @. P

7 B3 I* n: Q7 v, [  C4 H
" Q# m- O2 L4 d/ u9 g4 N& L/ s

( ]) l% f+ v; k8 ?4 q
第一步:开始一个新的不带trustZone工程:
+ o  K$ L5 \$ y  y, R
  u4 ]. @, b8 Y5 ~5 q6 r8 b& w6 e: l
15.png - s5 S; q# ?: L) n5 |
  q( P. C$ Y2 f: U) {

" y& ~4 p0 ~2 @
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。

- W% }' h/ Y! u7 L4 O5 m& F
' ?3 P# n+ |4 {' k& c6 u: s
16.png
) A8 s1 H2 `% B( j5 M7 ?
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。

. V& v9 Y( F% S3 ^
: ]" A+ F. `% u2 a2 S
17.png 4 a- b+ ~& s; ]' j0 o

: _% G% E6 W, w/ _# L

; a, v" X9 {- b' y/ b) b; p6 {
第四步:配置USBX:
: Z' w) v2 Z+ h3 }% O- k, E
1. 使能Core System。
: Q  a3 |7 }; W1 X
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
0 d$ X0 I$ _! _6 P8 z" J- _
3. 在Device Class FS中,选择CDC ACM。

0 L  h, J$ u4 r% `

( I% h; M+ F$ W* c
18.png 5 [8 g! \; U! `9 s, l! s

9 j* X2 o$ h  z+ y! \# X) H+ \
1 L, M* x" ^! t6 L: T
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。
( M" k+ d! y6 g. s5 C; e

6 Q5 g, }( S' _+ g( Q2 W
19.png
8 {! C1 [: K( N$ `" C5. 其它的Platform,选择USB。

) C7 Q7 R% y4 }* l2 u0 b
20.png
0 h5 e7 v* s, t- `7 r9 M- H0 d
9 `2 C8 a6 Y( y/ j: k2 q

' \" r; Z# X4 L, o# x
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
! }. P6 [! M" v5 R4 Y& n

4 Q- z3 @; ?9 ^
21.png
0 F8 o+ e! x3 A0 @& n- c
: E4 L! A% l$ R/ x

0 [# o; W' u4 Y3 b( m& q
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。

' P8 @0 \0 i4 w( b& d2 Q$ p" u  P. ?

' ?) I9 W' X& j% b: a5 D  x  g- A# W
22.png : [* O0 h) D, f. Y9 K& D5 D

/ ]2 L& h0 V# l) C5 t

4 f8 }6 {! \4 H& L/ F# p
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

3 o1 Z2 M2 ]$ E; }7 g% ]
7 Z9 @; a1 q( \: V
23.png 5 J9 D6 b! |" j) h! J
2 u+ W/ f6 E# I1 v$ N3 A7 [
! n. {  p" k3 U
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。
3 W* q* z8 W3 H2 E0 h) @) p* n

1 Z& p% o% q( t- F3 g
24.png
  ?9 i5 }- V0 z! [# W; G; }: p, M
! w& W! \* ?2 k' f3 D# [2 }# u

; h6 J6 J; H* ]: L2 B' ~/ V$ e
2.2. 使用STM32CubeMX配置并生成工程

7 L. I* _5 }7 r' U1 B. N6 P' E
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

, R* f/ b2 N' e! v* C
/ }$ s1 i, _! x: }
25.png
* Z0 [* ~! d5 X. v9 O' M5 [
最后生成的工程文件的结构如下:

+ ~: R- s) x( v& _2 Z$ w/ a" R( ?
" c4 \: j* C' m4 n
26.png
: A; x1 G) ?: \+ c) l( N) Y8 `7 p; T; w# \4 c
2.3. 参数配置的解释
3 q! t- i& o* j5 n+ y6 T+ \
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。

: c3 f' i4 a. `$ ^3 l" n
; X9 g: _: N. x7 r4 f6 X5 L9 A; w
27.png 7 Y& }% P; S& m% W& m4 m
调整依据请参考如下内容:
' @2 T2 I: w3 H0 {* H  g
* i1 Q- Q) V& j/ c4 Z. {0 O0 @
28.png
% _& i6 r' o% b5 O! v+ ~& D

+ {% ]& R! t4 S$ k) @; W+ j

1 c" k8 i# T% q% Y# W: ^
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:

7 a- W* w# G3 w7 G6 T5 `0 t0 d
1. USBX设备系统堆栈大小(USBX Device System Stack Size)

0 m/ i" x0 \( c: T! [8 V$ m3 L5 r* C  n
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)

4 Q$ f8 H& v0 K* h' `8 h
3. USB应用程序创建的线程堆栈
# t( y; i4 a" k) T' B9 d
4. 以及RX和TX缓冲区
9 N+ g/ \3 \( ]4 @  H% x- c
3 ~3 \9 e% b. c
29.png
& _+ m0 w% S  F9 P2 Y' X5 e6 C8 g( m. P0 f6 }
03

; {5 Q) [! I; h5 L  E% U
对工程进行完善和修改

; p! ^2 l0 u# a1 r5 ~
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:
. @2 [! s$ W7 |  R+ \( `4 i
( U( o, L# s: d! s9 H
30.png ! J4 ?! _. v- L/ S) r4 y8 k1 }

1 t, Y( @+ _# y4 O- v/ q

0 v+ T  N" b. b- l" K! V
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。
3 r6 I% _; b6 T* m7 H3 N' P7 G7 R

2 \3 C; t% Y2 u* u  d
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。

3 V; o* D" `6 x; O0 Z0 y
  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>
复制代码
: Q! H; \/ ?3 T- A
第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
2 ~* a: R8 H8 w7 d# D  q
  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>
复制代码
3 @5 a8 W- T" V# j0 ^
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。
4 d4 z' u, {+ F# x6 U  ?# o' g
  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>
复制代码
5 E. C% {& C, g% C: K
先调用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。

! W) m- ?/ u' a' r$ f
' @! e7 I  n) P8 m& Y* L* D+ H
31.png : c9 f5 d( ?, x
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。
4 {0 Z% \1 h3 H& d" V" t7 A0 a1 D+ x

. F2 e# B& Q8 H( v, D" P/ K

2 P5 }) n8 S% M, }6 A( F

% C8 R/ k( b* p  A! J1 f5 b
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
6 F0 [) l2 h+ e4 `4 M
  x) v( M6 m/ D7 o# f1 |. |- t0 g

0 H. \. A+ h9 F' m  _6 t4 ?

" w: s3 s) @2 G/ t# [
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。

3 L# M5 r2 \9 d$ L8 S: Z5 s3 K
7 b- u% Z/ K; m+ T
6 c: x  r9 X7 c. ~% ~
& V9 ]: @$ m! b
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。
3 M+ {6 |8 \. u4 B* d

7 y2 E6 b' |' w9 C/ i; L
4 x. n. f( V- n. [( J

2 x3 H1 U) t: I( f: a
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
" t2 w! \- g/ k& j' N) ~" p. v. S! t
  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>
复制代码

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

- x7 J+ `4 {1 d4 ~& |
  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>
复制代码

; t9 X7 [3 |' H' [4 s5 A; X& l
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:
% i: k. J8 v& s" S
  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>
复制代码
' J! s* v5 ^6 g
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。
( T2 W7 l. b7 z4 U+ j+ S' G5 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>
复制代码
( O+ N1 C8 d' J. l9 c( E) X' ~
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
& C; `: O0 a3 y% H

& X7 U8 d# h; q( t) E/ G$ v
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。

4 F+ H3 a6 s# M' h2 {$ }
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">
    ' ^) r) K" ?+ R: ?
  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>
复制代码
7 l0 U  N- W& i( h, p' y5 z
9 _% v# A& N1 r7 G1 ?/ u7 T
04

: Q# F+ W, C3 J- f1 G! F* }
对代码进行的测试

$ i$ E$ w, c$ S) B
对软件工程添加代码后就可以进入编译和调试环节。

& y2 }2 P" I/ g( c
" n0 b$ A- a' ]6 Z5 V
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。
" P+ |. S, X. @

  I4 o0 Z# v2 `$ l1 Y
32.png
: t% d# N' O! y) p
或EXTI_SWIER1.SWI13中写1回车后:
; P, n( W2 V+ T6 p: r
3 `0 u* E& f; d' _
33.png
3 ~: r& W+ l; |3 {/ j8 x2 [" V
然后产生外部按键中断:
9 ?$ l  t# U* B, l  q  l

) E; m+ e; C1 w) m% b
34.png
) P% M7 Z  E( y8 S
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

: W+ z9 T, g0 u) G4 b$ B1 j
( g/ ?5 R2 e0 p. y+ }% p0 K# A
35.png
9 u- S& g' g# H  d
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:
( R2 s7 F4 l5 d2 t

) _3 W0 {; s! e
36.png
+ H8 ~$ o/ C4 _( ?; v- o+ V
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。
, q. A. z* L# P2 t- H( `
$ h- E& E' V0 d
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
- K+ x0 [- N  t1 ?

$ G# j8 `2 F" E! z/ c0 g; [
0 n. ?' F. l$ W
* X" b: F7 U( b& j3 ]4 ]
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:

' C1 D- b5 D. L3 m: ]

5 R. j! H" B3 u' O
9 z+ Q. q, n# [3 W

- R9 n( `3 S/ n5 U6 g5 Q: o3 |
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。

6 h7 Z; F  f$ g' S8 G
  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>
复制代码
6 U% x% B4 f( M/ Q# V/ e  M4 }
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…

9 e1 o4 }8 x/ }  J3 J2 b3 _' H
7 J# }, x, K- V. c/ F
37.png
+ [0 _! D7 U1 v
05

- i/ p/ \* J$ j" V$ B  n* m
小结
9 j' N0 C; E# T
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。
8 q1 R9 o; {" O" E+ w/ x$ t
: D% L% s& t) p3 h3 h
赞 收藏 评论1 发布时间:2025-2-24 09:28

举报

1个回答
郭若 回答时间:2025-11-4 14:56:50

请问下这个软件工程的附件在哪,怎么没找见😄

所属标签

相似技术帖

官网相关资源

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