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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
019 Z% v5 x4 C4 m! u: \) O* l
客户需求
: \$ y9 F& S6 T& ?9 q/ j7 Y4 _客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。; T2 u2 J9 _1 f! `4 ?6 q

: d2 t& [! A% F0 s在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。
: q/ n! S: s$ W* h  K8 G1 P" C% W
02
% y3 d, I/ G7 @2 Y$ m基本硬件和STM32CubeMX的配置( E" n0 g5 ?( s: b

- P' z; }: p# ^7 a$ u4 R1 `硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。
7 K( N7 u* R0 z" u; E# L
3 K1 `( Q& @/ j# S下面是NUCLEO_H563ZI USB Type C接口原理图:
$ I' A4 ~* J7 j1 R1 ]9 o
12.png ' _& }" Z0 t9 f( t: S0 g+ e

- R- h7 X3 D! E( `

. D0 N9 q. {* J: B9 d! D
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。

3 A8 K3 h1 u- A# N: @6 x! o8 K/ g
0 c  ~3 V5 K  ]7 x  _! a$ l; i  R
13.png
5 V' ~! t0 O: p9 g
14.png 3 K1 N& S3 h& b+ ?3 x" h
: r  j+ F- ^% x, G
, ~" n$ {+ S$ ]) O8 q8 B
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
8 U. i0 C* U- L2 ~$ b& C
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)
; d3 S% e0 g1 o& d
% f8 D1 A. S7 ~$ ?( [! u+ M; Z2 I

! W' u/ H" n/ h

& _) f; s6 A5 K9 C$ e
第一步:开始一个新的不带trustZone工程:
& `+ p, c/ n: t. ]6 @
. s. [. ^% J* V
15.png
9 c& `: H  p9 o4 R
% T1 F' @% \/ l6 j! f* y
+ H! Z" C  s- i
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。
3 D7 F% Y. n# [6 u: F/ t

$ ]. f1 J% Y( t) m6 c4 l
16.png
4 c6 E9 \  W9 [, O5 u
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。

6 O# _) [& r" U, ]+ s9 s

, T/ B/ |. t5 s& g+ |1 c0 d
17.png ) e. l& ?# [' T& a, {, O% K1 ~, b' m
9 H2 J" G) [' x! d: M% H

0 ?( l4 h& F! d- e4 J' t1 F4 g5 t8 t
第四步:配置USBX:

4 l+ s+ H1 a1 p; B, l" J
1. 使能Core System。

* K& D0 i$ U7 y' Y( ?
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
5 k9 ^' y/ g. G! A& o  `" I
3. 在Device Class FS中,选择CDC ACM。

8 B  u# a. J# R4 Q5 h0 b
5 H; }! L5 ]& F
18.png " n  G) Z" F: j3 Q) F

& k1 v( ^" a. h; j) j7 \2 n
& _4 d2 I9 Y9 z$ E/ ^$ `. n: z: p
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。

; j5 y' Y( W) e. e$ [
% e& m/ D9 n* o" K
19.png
( v$ K; k1 {4 D2 H- A, d5. 其它的Platform,选择USB。
0 K6 g- G! h, ]; C
20.png
( c, c, n) t+ k- I! R

0 p5 K, {+ r( @) P& C% z& D3 b! j+ b7 Q, w
3 ~5 r3 L5 h. A  s: N. o" r
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
) E! n1 v& u  I5 a$ U, I% z
# r: B5 x( P0 o
21.png
4 Z8 R* _% y( W
3 a$ M' U& i3 `/ D0 S* Y, r5 P  q- I

( o. y! n2 b' G. ?- F( b$ T6 ^5 j( Z
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。
7 g$ N4 a7 a' @' \3 R$ S% ?
: X# l+ K: R% e8 p7 x: G
22.png / Q1 ~5 [& z/ Q+ v
( J4 W* U! @# i% A8 l

1 ]+ t. F7 c9 G: `( H( \) X6 |
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

3 }2 @; l- B# e# D9 }
( W. e, s* j% N; O5 Y& B' F
23.png
& K3 p4 b7 j( v$ ?8 K1 b

7 g3 Y! O, t; p4 J7 M! `

7 B% H) Q' U% ~: ]8 a
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。
) Z% L3 ^  V4 ]
: n: ^1 `+ P  N/ _* m
24.png
! e& u2 @# A) G6 {( c( S
( z! q9 V8 a* Q0 {
! f5 H& L, Y$ k: y  [  m' \! V
2.2. 使用STM32CubeMX配置并生成工程
  z$ Y& V# m7 }9 G: G  R
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:
+ J/ x! f2 ~: R# J/ p) v

7 Z8 \( k2 a, @& l) \( S# C
25.png
  N9 P. t, A! J( j; O
最后生成的工程文件的结构如下:
, f' `% t' Y) z' M, N
) m9 i' A6 `( R' `, {: Y; d
26.png * V" |' \9 G( K1 R

6 i% f, ~' ]9 w. \' Y6 b5 I
2.3. 参数配置的解释
6 M5 H0 q; a& z( R5 B( ^
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。

& w; @. q* w: Z- H# g+ s( ~! G
+ {5 W$ q  o6 G8 @! b
27.png * `0 C! V- Y( Q7 c8 m. C5 t/ m
调整依据请参考如下内容:
1 B1 u5 A1 M5 }( V

3 e: U8 b& n9 X, _6 y% E+ L* G2 o
28.png
1 g9 [3 {5 b& p" B7 ^' K/ N2 u
0 M4 O/ @: @. c* J& q6 _

2 K: e- R* L" i
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:
/ c. ?( |3 F4 C% i; H2 u+ I
1. USBX设备系统堆栈大小(USBX Device System Stack Size)
' b0 |( Z7 L1 n5 W) z1 F: M
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)
" O. ]# J) p& t. f1 w8 V
3. USB应用程序创建的线程堆栈

0 H5 p1 g) L( S( P& F
4. 以及RX和TX缓冲区

8 k# ~( ~. b( {  ]
. ~3 `4 P* ]* T
29.png
: d2 k* ^% U( ?" t1 J. @; v) f5 W
03

& Z7 D/ j% z; k1 U" K5 Q# M
对工程进行完善和修改
8 F7 q6 ~- b) j0 z8 W% B; {* t
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:

; Q3 f' L1 S) c- \2 R- T& I+ k% S7 m/ d

' q8 u  k, G) B+ v  y  W
30.png 2 L5 h7 F8 j6 a2 R; \2 Y

, ?" ?. k( V' r, l9 \* E' g+ p" n
; c# K% u  n: ?+ ]% ]& R/ n0 O' s
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。

- y+ q* T# W/ d) I! S0 D3 L, q6 o2 W+ W
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。
0 Y& w( M/ c6 u! _) R' [& Z. {; V8 j
  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>
复制代码

" j* T2 @# E8 `) ]7 t) C7 Q第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
- b& O, f1 W  f
  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>
复制代码

/ }- o! U3 m+ E
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。
$ j- j& {" H2 z4 i" M, v  b. Y0 p
  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>
复制代码

/ j% Z9 `3 \! M! K* }4 p
先调用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。

2 ?+ h+ q$ a8 V- @4 P- G

8 y0 W/ E: u0 V  c8 o9 X( W
31.png
  F- f0 J  q9 K" u' A* U$ P
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。
4 j% Q" _" u5 i- l$ J

- H+ {& X2 J9 j8 C$ @7 ^0 N
6 ~8 e/ d* b2 y5 z
' E7 R; r2 J$ \9 q+ l' @
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
! j. J- }9 X, D  j5 W( `5 P( ^
. X. I: ]6 |  N
( N" j# U& i; T9 B

- p2 h$ I! w2 S; _
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。

: E6 [5 G* `$ c- i4 Y

5 w# D3 _- U# P  G7 e& y

+ T% W$ t: m& V7 T. L! ?& v0 ~7 Z

% m; o- e9 e) y
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。

9 {4 R" G. f6 |1 y% {$ W
" F" O2 c! T* ]! p3 J+ G$ ]; j
7 a0 c. Y8 m5 z: X' F

1 B8 G* u; t  w) M3 y
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。

7 B- x2 A( a" b2 x. v' s! a( L
  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>
复制代码
) b, ?7 J) B- u2 y
第五步:下面的usb_connect()函数是根据客户需要设计的。在PC端连接到设备端STM32H563,VBUS连接时触发中断后调用的函数(客户产品中把VBUS分压后连接到一个GPIO口,由于客户第一版本的PCB没有设计这部分电路,因此调试时使用按键中断替换了Device连接到PC的情况)。Usb_disconnect()函数,是处理STM32H563从PC断开的情况(调试时使用按键再次按下来替换VBUS断开的具体功能)。保留了信号量,程序处理中如果不使用这个信号量可以删除,具体请参考软件工程文件:

3 }; u# z; R: q5 D7 |
  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 s# n; G2 |9 U5 H
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:

7 l7 x0 Y, d0 y9 R
  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>
复制代码
; R, W0 N- q% R5 u& y0 N
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。
' {$ w% a0 t8 a9 e- l6 M5 I9 b
  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>
复制代码
: A9 S% c) ?% ~& y) K
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。

0 }  Q- Q$ s  D1 P7 w1 r2 v! _; R
) E" a' w1 N) U: X6 V/ G
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。
' d. H& J; T1 h! g  Q
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">) {8 h/ ?( \( ?6 b, x$ a( H. Q
  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>
复制代码

$ ^+ o9 [% `7 M; I" W
, y3 W' |7 P1 `
04

  ^3 F1 v0 Z# w! m
对代码进行的测试

' U) v2 h  k% e/ Y
对软件工程添加代码后就可以进入编译和调试环节。
% _  W1 t: r. f; q$ l
7 Y* D- s* Y/ C5 ^- f4 H' r, b
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。

+ `1 Q. q5 I  L' g- N; C

: O& a2 D  L$ u: p. f; Z" X7 h
32.png
" L( H+ E1 p+ |6 ^" n
或EXTI_SWIER1.SWI13中写1回车后:
- L8 B$ O" }3 @5 G! M

2 Z- b& i9 L! W) @
33.png 0 j2 _: M/ |$ S( d2 W, e6 F
然后产生外部按键中断:
1 V- d4 V8 n/ Y* \
6 O! e% A$ d8 m5 e+ c. n" M* v
34.png
, [% e) K8 u0 p* E1 X  T' p5 m7 w  \
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

$ X9 L$ {3 u0 X! C
# a/ K( A. {  Y6 O! E; [' D
35.png : J$ e1 C. B3 Q4 e3 ^) K9 l& D
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:

# w( E5 y6 A0 D0 x$ [* p7 k

+ u- ?. d& {5 \1 j3 v1 @
36.png
! U& |+ r! C# t8 V. ]
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。

( H9 I1 p7 f: q: h' @
+ n% b+ _0 b- C: d
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
/ B+ M' I$ t- W. }
1 K+ G4 f6 k% |. Q/ z0 u7 ]

' u$ h7 h, T5 z' M& d; z( ?! k9 P
7 `3 @/ Z  O5 A: v8 ]' l# D% Q, a
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:

8 z# R8 i# }3 P
9 X- q2 D2 ], g& D
4 O: V2 U/ M: G' |4 F
5 c3 R- L4 M9 E$ S
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。

0 K5 m9 O$ ^; r  I
  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>
复制代码
0 B3 J$ E2 ~( O" e! ^
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…
+ a( a! W9 a) n

$ c& U5 Y2 J" Y3 w) M0 w: m
37.png
3 R. p# K: a, S9 N+ x$ o$ F
05
4 I/ X# g' p0 r
小结

; r9 N( x: W. t% C+ p! F# A; N- T
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。
+ _6 t) u* s+ {6 z( w4 ^$ B

3 H% s' H9 c1 n- h$ z* B% X1 O
赞 收藏 评论1 发布时间:2025-2-24 09:28

举报

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

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

所属标签

相似技术帖

官网相关资源

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