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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
013 B3 b, Y' d4 I
客户需求: w* i& o3 s# c  j. \- t+ X
客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。; l* D! F2 L: _8 g9 g% @! |9 d/ ?/ J
2 m' [2 ~" r" |
在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。
) S! r8 L: ]2 l" Q) i$ L( v
# \5 \; g3 w# [: T) ^4 Y& K02) I! ]* }5 E, k. Y
基本硬件和STM32CubeMX的配置
9 S4 m1 r& k& f) \% J1 u

) j) L, S; y4 H4 A; ]硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。3 ?% G% e# I( n9 ^3 |- @8 @
2 U4 o4 c) }- _
下面是NUCLEO_H563ZI USB Type C接口原理图:
6 N" \1 {: G1 t% {8 i. Q6 `" k
12.png
8 L, F* G$ n, p5 O, u/ F  {

6 \7 r, t8 G* H) o

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

6 |! t* b- R& ^! E( ]& U

! K1 p% R+ A& ^* H% u- r
13.png

( [/ K4 T) ^, [
14.png
6 l; B0 q5 K8 x8 ?& B

$ }+ M8 b* b. G& Y( Y8 M0 q

8 r$ i  D$ c9 h* m1 s+ G. n. `
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
6 K% O  K) s2 l* C9 y
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)

# `/ i. l! q" k& R8 V

# O; J( d6 W5 P5 @6 X7 _% P

& _' c- H  q. ~& \+ [

  P- Y+ L4 x% Y: Q- t" l! v
第一步:开始一个新的不带trustZone工程:

  r3 V2 M* @0 V" p& X
! m) b1 \' s3 r6 D; x' W
15.png # `8 ~6 w$ f' y+ j. D! \( D
& d. z( }5 \9 f. {5 |7 r4 V2 k1 P

* C+ K( v/ W2 R0 r# X6 B2 ^
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。

0 A0 w; X  k% F9 k5 `. m
# `+ R; j9 }- C
16.png ) t' F: n4 R$ O' l* L# Z. v9 f4 i1 z
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。
3 c* d- r1 v* {  i
4 o5 M" j8 X, E+ P% Y
17.png
. M* }. m* v8 J5 j
4 f7 @9 o: l9 O8 E* K
% c# N  v$ {. j8 Q1 s
第四步:配置USBX:

; ~, u: l+ V2 r, x) d
1. 使能Core System。
3 a  `) t. |1 X! V2 t
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
) s3 b0 n' }& X- ]
3. 在Device Class FS中,选择CDC ACM。
; i) J9 ^; R) o
- C! T" o  W! M  |; z5 ?! [6 E" I
18.png # ]* z3 k5 c7 z0 T! `2 p1 e: Q* [

1 p% G: w* F5 i: O& [6 N% A! x

+ w( X+ _& n& P7 b* @4 x
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。

2 ~& Q3 Y  z' y0 {! H2 C

! b$ m, F+ `" `( b: v
19.png
( G7 E. C" _9 @! H5. 其它的Platform,选择USB。

7 T% p$ B) H$ k3 }9 @# R
20.png 8 c. d/ t6 q& X" n

$ @' H9 G( U' i+ Y

) s# d, N% g) u" c7 w( z
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
- q% Y5 I* l5 c+ T

7 _* P* I- }0 w% A7 \: I) D" F
21.png
8 W- |) y' \; J7 |* z5 e% Q. T# T

& X( g% J! y7 ]1 }
7 Y  |  Z3 p* Q: L+ J( K! X
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。
4 [: f' d: U. Y+ z  a

( \9 V: ?2 w6 |- V- M
22.png & E" e0 t; x8 _! f+ z9 u
6 C5 B; n% o2 E
1 q5 ]) J3 H; W9 ?7 x& D# Z& ?9 p
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

- l2 Y6 U8 P$ e3 M: K. [1 E
% l0 s! g+ r) F9 W* o
23.png ; C: v6 Q5 [+ A6 |

4 w2 S, c/ I- W* `& x  c: |( Y
3 D: E: ^' y. n  L& k. R
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。

) t0 P9 |8 x6 F  a1 @
' P6 @+ ?; d" h
24.png
' f1 y6 }* I2 ^. _6 m3 R

& W* z( W: H  d& i1 C4 e

: H; J/ T1 l9 U1 a
2.2. 使用STM32CubeMX配置并生成工程
# w0 b' X& G. Y" b3 f" U
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

- o9 J; w5 X% t. v1 |8 m8 A% b
: b7 p6 Q8 p% M  ^  @1 p- Z, r
25.png
  l6 H6 J% t* H- h2 U; @) @
最后生成的工程文件的结构如下:
8 a/ \8 K- `. u1 v) A2 M
3 ?$ ?/ I* `, b3 L& ~
26.png ! `5 Y8 X6 v% Q2 o" Y" V" y9 V; c

( ^! y7 S" R% }2 D2 u
2.3. 参数配置的解释

4 A. u9 Q% T+ a
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。
, A, s4 C# u& x6 k- p! Y) s+ d: b+ O

) z. i" \& q( ]/ N
27.png & Z% X7 F% A) l8 W/ e6 q
调整依据请参考如下内容:
# ~1 W, r# C7 D" z; H+ A( L# D. D* l

+ G! _8 h9 E  @5 a: B
28.png
- f1 T4 K9 s# a) ~/ X
' b6 X  d: @$ F& ~

" O1 Y, D$ N8 g/ P! u( N2 R
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:

5 b3 G" e: Z$ a; E% e0 Y$ y
1. USBX设备系统堆栈大小(USBX Device System Stack Size)

, Q8 h" f. q' }/ }9 M# i
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)
0 h: l* [( I' f; r2 m, V# N
3. USB应用程序创建的线程堆栈

! J, F# u, l7 u( j& v  J# ^# {
4. 以及RX和TX缓冲区

3 _6 k' p3 l( T6 z0 O& B# j
$ t0 r* y, ?) ~8 w3 I) p
29.png
. X% N& F, S1 p! I6 c- G2 T
+ t* t) }" B1 Y/ @3 ^0 x
03

$ Y; g$ L8 s1 ?8 S4 |2 j8 y
对工程进行完善和修改

; S1 x1 M7 }" `8 x- [. X. v# D
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:
- ~* ]3 j. S, ~# e; o2 _) t

* q1 ^0 c2 B* G5 g
30.png
' A+ N+ R+ Z' e/ I$ `  ~
7 I3 k1 R' s) o- N9 R
. q: e4 ^* m0 M1 d% t1 z
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。

# \. a  _% @, Y! L/ C
/ l% l% A# ^4 d; E1 ?$ |0 f3 g
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。

* {# T& D6 Y$ k+ K# O& A1 C( K
  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>
复制代码

! e0 {; A. {* y* s& m- s第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
8 @  G3 ]2 o0 E0 f: M; U( Y
  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>
复制代码

0 \# }$ H" _0 ~) i+ C7 J1 Q1 `
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。
0 h7 u) @$ ]! A: s) D; j
  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>
复制代码
2 w. V6 O; q' c+ e2 O
先调用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。

8 ?, b7 x4 _- c
( j8 a, L5 a3 Q( D
31.png 2 e% a6 Q$ E) |/ c% Y0 X/ D
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。
5 j2 A/ X) P; R. R; n$ [

/ W3 a: a" x. u9 C# S+ a  a) ^! I
+ J+ w) p6 L, [. O5 W; t6 }3 Q8 o

( E, t: P; g/ ^8 G) S+ n
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。
! d* H" f- D. a: h8 y& @* C2 m
; E# k/ E, C2 e0 Z. P5 _

3 T" T! m9 p5 M1 ]
) x2 n  k& j* |  T
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。

4 a7 J" R+ I# `$ O/ m

' \5 ~, A0 z. t( f; L! _
  D6 o4 V/ W: C9 X& @

0 w; Y7 W# O+ e4 @. y
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。

" t8 b/ C& Z( s8 I0 Q% O! @
6 L9 R) K5 f5 ?. \8 R' w

- t) v8 Z5 n5 v0 a/ I8 S' N/ ~
( ?) A- I1 J2 Y+ [2 b
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
/ R/ a, O& S: f% k" g5 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>
复制代码

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

0 l( z+ \5 M$ `( H( Z. `9 H! x
  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>
复制代码
/ n7 ~0 B' ~' D" F: v
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:

' t0 v/ L) T7 D/ q
  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>
复制代码

9 w% P4 d) r: ]
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。

* `4 `6 q8 ?+ H  U7 X( J
  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>
复制代码

# H9 T  c. X0 L4 K8 S: M( W3 l1 {8 m
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
1 F. {$ |2 }; F

4 V# Z$ ~# Q/ ^* u" d3 l
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。
7 w5 J$ V  h" c( A& V) W' |
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">$ E- l3 N9 ]" t$ z% U! j
  2. </div><div style="text-align: left;">VOID usbx_cdc_acm_write_thread_entry(ULONG thread_input)</div><div style="text-align: left;">{</div><div style="text-align: left;">      ULONG tx_actual_length;</div><div style="text-align: left;">      const uint8_t message[] = "USBX Application Running!\r\n";</div><div style="text-align: left;">   while(1)</div><div style="text-align: left;">      {</div><div style="text-align: left;">        ux_device_class_cdc_acm_write(cdc_acm, (UCHAR *)(message), sizeof(message), &tx_actual_length);</div><div style="text-align: left;">                     tx_thread_sleep(100);</div><div style="text-align: left;">      }</div><div style="text-align: left;">}</div><div style="text-align: left;">VOID usbx_cdc_acm_read_thread_entry(ULONG thread_input)</div><div style="text-align: left;">{</div><div style="text-align: left;">      /* Private Variables */</div><div style="text-align: left;">      ULONG rx_actual_length;</div><div style="text-align: left;">     uint8_t UserRxBuffer[64];</div><div style="text-align: left;">      while(1)</div><div style="text-align: left;">      {</div><div style="text-align: left;">          if(cdc_acm != UX_NULL)</div><div style="text-align: left;">             {</div><div style="text-align: left;">                    ux_device_class_cdc_acm_read(cdc_acm, (UCHAR *)UserRxBuffer, 64, &rx_actual_length);</div><div style="text-align: left;">                   switch(UserRxBuffer[rx_actual_length-1])</div><div style="text-align: left;">                   {</div><div style="text-align: left;">                   case '1':</div><div style="text-align: left;">                          break;</div><div style="text-align: left;">                   case '0':</div><div style="text-align: left;">                          break;</div><div style="text-align: left;">                   }</div><div style="text-align: left;">             }   </div><div style="text-align: left;">      }</div><div style="text-align: left;">}</div>
复制代码

, R. e( w( E& s: R4 [' _" M
% J, O, u& b% X7 P0 B8 q
04

- v% }+ W" ]7 k- f2 A
对代码进行的测试

' C% G( S+ `( b! F/ ]: ?
对软件工程添加代码后就可以进入编译和调试环节。

! K6 A1 G" U3 `% g

' |+ B. D* g5 \+ K8 b( L* O
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。
4 ~7 t, f! o; w& G. X7 m
: j! c- T2 Y+ _5 d* |
32.png
1 X/ U% o3 h+ U6 ^) G& k
或EXTI_SWIER1.SWI13中写1回车后:
7 Q( V. B5 u( A$ B5 X9 k' U) p  e
0 x, I: f  X1 v  F6 D
33.png
! J2 X9 [/ B6 v* A
然后产生外部按键中断:

4 ]  ~) y6 ?; {
  ~# s6 y. P# |- g
34.png 0 z' d: F. p% V2 e% d# c& O
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

$ H- X8 _9 L4 h5 J# M
8 t+ k% y* w5 D6 a0 e6 G& J
35.png
" g: S' o$ U. }
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:

3 W3 h3 Z0 v+ B4 t, p" R$ G( P, D

9 \! h7 Q6 K1 |1 V" Z* d4 r; a7 j
36.png   X& g2 ?8 p# P6 U" t; W3 |
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。

  ]! y! e6 e  U5 a3 {3 Y9 c1 Z; z4 F+ b2 H  B% f, j+ f
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
9 I8 ?) y8 c( D$ ?1 l4 s

4 S& P9 D" M+ S- x4 _; G; f0 i
2 \# M" B+ ^" y/ F0 D8 H
# \3 L: V/ E7 T% N, g5 S
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:
0 H; W5 H8 }% g6 e9 Q, a

  ?7 U: C! `0 c. \% G9 k) u: X& l- w! m

* A2 p: L4 `9 `; w7 k8 n

4 Q! Y. B4 X7 O4 v7 l1 K$ c
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。

! s* Z% X; b, ^; ]# ?, t
  1. <div style="text-align: left;">tx_queue_create(&ux_app_MsgQueue, "Message Queue app", TX_1_ULONG,</div><div style="text-align: left;">pointer, APP_QUEUE_SIZE * sizeof(ULONG))</div><div style="text-align: left;">tx_queue_send(&ux_app_MsgQueue, &USB_Device_State_Msg, TX_NO_WAIT)</div><div style="text-align: left;">tx_queue_receive(…)</div>
复制代码
# n" N# p$ k) W; v( z, l8 n
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…

& W3 e& {  H. o) [* C. L1 F  m: P. S

* w. ?8 w, p5 d& p8 q8 k% U
37.png ; x1 e! {& U/ Y- E3 L
05

" f" R% R) V0 U0 _
小结
) i+ O; @- \5 C* h  I( B/ ~* N
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。

3 k" D: y- t: R3 L1 t8 N1 f
- Z  U  W& g. T+ _' u7 r  u8 Y* c" G, J
收藏 评论0 发布时间:2025-2-24 09:28

举报

0个回答

所属标签

相似分享

官网相关资源

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