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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
01
0 V; B9 ]( u% {, [5 v客户需求
, G6 w; x, k/ c) F客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。
8 m4 O  N- ?8 y9 r. |! T
. A* I. o- c1 @; ], b在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。( `- g$ \2 o+ }7 ~
; n, {# |; [3 _" @: v( G3 w
02
; U. v7 x& p. T/ ~基本硬件和STM32CubeMX的配置
1 s7 u4 G- `2 V3 `! b
1 y) [$ ^, R7 |1 \5 a* l
硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。
6 @6 y' K* G2 s/ n9 p2 n4 ~, Q
3 S. N% }. m: g3 u1 o+ O) N下面是NUCLEO_H563ZI USB Type C接口原理图:
. ]* h' n9 X1 t
12.png
" [+ F0 P6 \5 W0 G3 f
) C( e! X- j! N7 y6 M

6 {0 K0 Q3 ~. M  f& r
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。
- ^$ q) }9 B' F$ }2 E. H
, Z7 c8 A2 z# G& G: h
13.png
6 U' Q! @: ]6 {# ]# X* k8 }0 v
14.png * M3 C# T1 N+ M- O

. o1 B7 A! D! K! r7 [' @: [( c

! ]& l8 }: h7 r2 S
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。

% n+ ~* V6 E: i8 v
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)
7 W0 X, z& d8 {" t2 x$ {
! Y5 c6 }8 ^# q" F7 M6 Z6 H

+ Q2 C' d4 z" A

+ O# G* D9 K9 U! H% S: d+ \& z5 e  `% V
第一步:开始一个新的不带trustZone工程:
4 E8 K: M: @5 p+ \6 z) Y

. i8 ]+ h" C& F" Z' i1 ^+ r
15.png
2 D* I; ?, }3 h- A
: Z+ b( v0 |! Z7 f$ ?- H( ~! x% h
; v0 G% ]" d0 T
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。
. Q1 k) [+ K+ r% j/ d- X

# _4 t) P& l- h, M
16.png - m; g$ s( v7 [/ l2 l
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。
- i5 V* b9 P" O7 M+ V7 F9 R+ K

$ v! Z# |; a! P6 E" i
17.png
) L# P7 z1 A# s5 V8 O4 j8 z

2 K- D* L3 s) e! X6 z! a3 C

3 E- K9 s5 |! J9 h9 Z- g, {
第四步:配置USBX:
* y: P. C) |7 D+ T- n$ v3 Q
1. 使能Core System。
5 I* w# O  Z4 _3 z+ {- e, N
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
; ^: Z& O8 d* q' X
3. 在Device Class FS中,选择CDC ACM。

9 V3 d& [% Y3 ~

' s1 W( D- }' A. Y# F( W
18.png
( ~% n9 R, X$ b" ^
; F, S* C0 T; N
; e' ^& E3 ]# e2 R0 B7 F
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。

/ b  p4 H4 \1 A1 B
) M% v+ O. w! U, R- ~3 |# F" Y
19.png
0 M, T; Y: b7 ^6 }( K5. 其它的Platform,选择USB。
" V8 z! Y1 L- R* P* F4 C& u5 Q
20.png ! C* h" H6 X1 ?& j" E" n

/ W) W) K( I1 t" ]+ Z' r
, w% _' `+ M& t$ t( t
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
/ D0 i/ g8 P" J; J& G  v5 n) c$ G
+ P5 U4 p- h1 n; a3 }2 R* h4 [
21.png ' L0 Z) ]1 f+ J* U% N7 A
4 z1 F* h* n7 G! \+ {0 O

; E  w' {$ ?) ]! y1 _6 ?' Y
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。

& ^, K" o5 T& [% n; ?
6 w- {: d; I8 Q6 y
22.png " `2 r* S7 p7 ]& }! R/ T

$ |8 o5 W& D; N9 F7 Y1 `
7 [0 G2 L! C* u5 l: _3 b6 }
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

% l/ Y7 t! E& j/ Z; u
$ {, N8 k8 \: c5 O$ c
23.png 7 N4 [# i9 B9 N2 G
+ n2 ?: F1 D2 V/ g- e
, K5 i$ C' [0 ]& y( `( K8 S1 ^4 B
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。
! B6 l, E) U1 h8 I

" [( C4 C2 m! v( o/ W* c5 D# e1 f
24.png ( e" Q0 T# \/ V2 B. ~

: E0 f* S; N: S: |6 X# P: Y: G' P' g
* n5 B3 D4 z- M
2.2. 使用STM32CubeMX配置并生成工程
; y. i% ?! m3 U
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

! {' ^  C, e" E- y" G

: R; L$ A3 w8 M! n1 _7 [+ g
25.png 4 F4 k; \* i7 E# U, Y
最后生成的工程文件的结构如下:

0 _& V* T6 F% M$ Y

4 ]. n8 O8 h& |& x3 T0 m
26.png
. S! }7 p, n% Q
8 a& F* R1 r: \
2.3. 参数配置的解释

) `2 x  w1 N3 {, P; A+ x5 S
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。
. `' h& D$ p7 H1 ?% b" s
! f% `1 B0 d# Q. i
27.png . @6 ^7 C& d/ P
调整依据请参考如下内容:

! e* c, B* d, k' x0 f
9 |6 m/ a; t& {) V" o7 O
28.png ; v4 k$ y* I4 m' u: A

  Y# F# L# a5 [) t: [
  h- I; c( G( ~( z8 a! \5 w  z9 L
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:

% E  |( l4 f( w$ i8 x! Z# b  _: V
1. USBX设备系统堆栈大小(USBX Device System Stack Size)
0 U8 u/ D9 g/ c. B" p( E! G
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)
; f) h0 D+ d% J- |5 K  \- C
3. USB应用程序创建的线程堆栈
/ h# b3 Z" G) W
4. 以及RX和TX缓冲区

( s1 v% |5 v* _1 P8 Q, h' |
. V  m7 F" f$ q: L% `# k  Y
29.png 6 ?* a' Y- I( p8 D
9 @' q3 l' ~" e) J2 u' [" z
03

- c. }. I- s% o2 g
对工程进行完善和修改

; \! q3 M4 l# g0 d
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:

" m8 p) m/ a0 `
: }& e: y4 O- Q6 W
30.png , L. c9 R9 P/ G# y, d
; o8 h8 S$ k7 m

' T2 R: F% U  J0 Q/ \' |2 U( I7 I$ F
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。
! R! z& J  Z& k6 S- v; m
$ J; k) v3 x. b0 s$ I
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。
* J4 c) Y2 h5 B4 X6 g, ^4 T( e
  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>
复制代码
9 y* Y( J+ |0 t$ O" O6 ?+ X# h, X- G
第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
. `' v/ s# N7 I/ W" ^. I6 Z
  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>
复制代码

. \4 A" X, g% n7 S5 j0 [
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。

: o8 C9 z0 O% R) ?5 \4 m# V  J* Q
  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 H4 R/ B! Z# X2 {
先调用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。
( G7 N! c9 M7 p2 {/ o& ]
( ~6 C6 X, _5 c" b: x6 @. w
31.png / l6 \8 ]+ O6 R: b5 R
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。
* |# a  o& t) ?; ~

1 v/ c0 o" J6 h1 ]; I; ]

, I' \4 p- V6 [
7 W1 j' Z& g6 d4 F
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
. P( k$ a+ ]0 D; \

% A2 e& \3 s3 V8 c) U6 R0 |
: T: e3 ?& U1 i1 f' ]" l
% k% ]% X" D* Q; ]9 J  S
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。
/ ?' M7 h) M# u1 ]+ [- @% l5 P) V% ^
, }* U5 S- _$ z: J

5 n1 Z8 b% q5 j% Z

6 x7 ^4 ]5 S  b* P- y
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。
" \: z5 T# a0 |$ r; x
! X7 A5 K) _4 s! L( m; Z2 S

- ^2 R! F7 {; H3 f& d; \
4 @$ c9 u/ a3 N9 R- Y$ x0 R; H- _
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
# H1 }" o. a6 `, f. V6 W
  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>
复制代码
/ N( B% M" {' f* z8 y$ A/ i
第五步:下面的usb_connect()函数是根据客户需要设计的。在PC端连接到设备端STM32H563,VBUS连接时触发中断后调用的函数(客户产品中把VBUS分压后连接到一个GPIO口,由于客户第一版本的PCB没有设计这部分电路,因此调试时使用按键中断替换了Device连接到PC的情况)。Usb_disconnect()函数,是处理STM32H563从PC断开的情况(调试时使用按键再次按下来替换VBUS断开的具体功能)。保留了信号量,程序处理中如果不使用这个信号量可以删除,具体请参考软件工程文件:

( g  y. v- b$ O
  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>
复制代码
* G9 w0 v6 B! m+ x4 `
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:

3 G" L! y* W, D: W
  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>
复制代码

1 x3 d" Q* p* K/ j/ v
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。

+ V8 \3 i% q9 x9 _/ @
  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>
复制代码

4 _5 G  M2 Y% p9 E
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
$ N3 n+ K& |4 m4 @1 [* x& p- K. ?
+ C1 ~  ?+ c* C: `" t2 {
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。

1 X/ [3 Q; g  h/ f+ F
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">% _8 i& D8 l( g- H8 P, S/ G  [
  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>
复制代码

; O; L8 g% l' `( d% M& T/ V( K; E% s  \7 b/ Q
04

; B: u# h) u+ f+ v- V/ Y2 L
对代码进行的测试
, K0 t! g; @1 z$ b, u" a  c" D
对软件工程添加代码后就可以进入编译和调试环节。
) |& G) L8 M/ T" @
$ k' X# F3 |- D4 i+ k2 r
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。
% `; I( o0 `, u6 \5 U! P
1 f% a" w% L& U( P  b( w/ Z. i+ R
32.png 9 J2 |1 c; o( N+ x
或EXTI_SWIER1.SWI13中写1回车后:
: e6 z9 Y* H! }8 y. d7 v

; _: j; p5 H7 ~
33.png ; D% o' K4 c3 Y5 W+ B( \
然后产生外部按键中断:
2 n) D4 N% k& g6 E
6 u4 L' ?5 N* ~5 c# b6 F
34.png 8 ^& `5 O( B3 U& ?
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

% q; i" k( i% H: w& d
. A! v' I) e4 b2 Q
35.png
% Y3 a$ M0 c9 o' P. E  w  Z. [
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:
4 l+ D) `! Z+ Q# q
' ~! {. ^2 @5 i2 q# C, w
36.png , r/ ]( E9 [7 @" a) L& I
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。
, _: L5 j: W- J% X$ n

6 _  p7 G3 D2 y8 A- m! x& X
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。

* c* e- P: m1 Y# H

0 m" D& q$ e. b

' c( U2 S0 h5 T/ [: z
4 d4 I" d$ s+ K( ~7 D- j8 L
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:

8 D% V$ _! u$ B: j( O' d+ f& M
/ J2 \: Y# [3 t1 R; b! s

: L0 N! g# D1 x% z

8 h  J- q* V! O/ d8 u
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。

. N# v; x! i( i; w$ n3 ^) p- u' H# F8 S
  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 a) Z8 A7 h. G6 @6 @; @1 W
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…
: a9 V1 p' B5 _& g

2 q( d/ \: W+ q9 ]( r4 e
37.png 8 p, R7 i# i/ a# d/ ^2 F- y' j
05
( J* K* e6 A; V+ x- y
小结

: }& N+ `3 U8 B- c! W$ s
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。

; f0 ~$ ^. h- ~& j, i& s
3 d1 d! O3 Y$ a: q3 {/ t
赞 收藏 评论1 发布时间:2025-2-24 09:28

举报

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

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

所属标签

相似技术帖

官网相关资源

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