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

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

[复制链接]
STMCU-管管 发布时间:2025-2-24 09:28
01; g# A; ]# B; H: C
客户需求9 Q7 p: q; S3 t9 a6 ?5 z$ r. v: {9 g
客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。" i2 C+ f# b) h: ~

; |1 Y8 t0 m4 |  s: ^% |& p* L- k在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。
8 G! ?" |1 _1 R: V) i- D; H5 `0 X6 j; ~
02
. T5 g5 Q) s6 u5 Z( ]基本硬件和STM32CubeMX的配置
5 I, n* V; h  z# k

4 Y& o7 q( s+ d4 j: A硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。0 m- S* G4 s+ J9 v" W. |" V
& u1 f5 h2 a3 ?2 n
下面是NUCLEO_H563ZI USB Type C接口原理图:5 R0 _( X( g" j- F! V
12.png
# h2 s& g' p; P! r9 |* I  F
( f' g+ p- k. ^! q% n$ V& Z

, x* A8 R# S$ b( ]9 w1 ^
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把  PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。
' k! [# c5 v' c7 z' v2 P0 j, S
* w( T/ G: ?4 J/ n  w  p
13.png
$ {9 o5 x) D9 t7 r5 d
14.png
6 l0 h) k6 g) O7 ^, w! |  A
3 J7 k& M" n/ H) u# |$ R' T" V, @2 m
5 v. t7 f! R& \. _
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
9 {5 ]. n9 R# B* _) Y5 i
2.1. 使用STM32CubeMX配置并生成工程(不带trustZone)

/ i+ P" ~( y$ c, S. Z/ r

2 P( o; w8 o6 ~3 b3 }! {7 {
% \- R6 l, m3 _: p$ a

) Q3 h8 h, y9 n0 ]/ b+ ^
第一步:开始一个新的不带trustZone工程:
9 v' L- M$ D5 J2 y( ]

6 z* z7 ]  m* D* E7 ]9 p3 ]1 ~
15.png 5 C0 D5 ]. o  {! [5 {( m
4 n, }0 w) H. L1 m2 s
  S* d9 }6 D: {8 e2 u
第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。

9 i- r! f# c/ Q" S: s1 R8 ~5 p
9 ^3 q7 n' b$ b  ?& T3 P7 m7 q
16.png
+ B, s2 i# ?* r8 w5 L, F4 r
第三步:配置ThreadX外设,使能Core,其它先选择默认配置。

" \* z2 n* u9 P1 u- D

4 O8 X9 v* ^' n( \( ?+ C  C) Z* }% b
17.png 6 c- c. q8 ]. R$ g
# @" ^. t0 X# n: U4 m( M1 |8 W

2 N/ W  i% y) M7 q. j
第四步:配置USBX:

* E; @  Q6 q: X$ R" T4 S8 O, ~
1. 使能Core System。
5 A5 [( N  F3 h) X1 W8 J2 V
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。

# U/ W$ j) @; Z
3. 在Device Class FS中,选择CDC ACM。
0 O8 H; _* V) R7 g+ g

9 H' T* v6 F( s5 q, Q" a3 n
18.png + G2 G" ^1 R% @' h% p
! U$ U" U' J+ K/ Z8 d3 \

1 J% j7 u3 y; [% k; j9 \: d
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。

+ |. ^+ C0 D/ s- r
2 a, {! [6 j+ |7 G0 A# ]
19.png 6 U2 {% ]4 K5 y" h* u  H) b
5. 其它的Platform,选择USB。
$ e* y& V: [3 \3 P& ]* r7 P
20.png
+ Y4 i; D& {% y% a! p
! E8 [8 Y- N3 F  e

/ Y: V9 F$ |# S& s- W
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。

, s  j. o' g$ U7 l

2 e8 a' E$ E$ F% z- l  Z
21.png , R" h% N$ i, Q8 W; w
5 Q! w  G1 Z3 n3 i/ u. }( u

7 V- M" I  q& ~: ^( g3 @
第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。

# c2 A6 C# ^6 k$ ^, a! v

7 a$ ~4 L1 I; B
22.png
# f7 N% K  F) h4 {+ [6 D

" a+ h9 n8 Q" u3 ~' r

) u9 A4 @3 w  B7 L- R2 f2 f
第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。

, V) ?' e8 ~2 C# {. h
, \( i8 \. B; K3 V
23.png ! R% r- _0 H  k# I
7 B% K9 b7 j7 {6 W8 n, t1 {& M# M
* m+ {: }6 H% B% P) r, V
请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。
- l: ?( n5 U- x. Z3 Q

2 T0 f# ?2 U5 C6 K* T
24.png 4 H4 s3 a5 I' l2 T4 X
: L7 V) E. n# B5 X4 h+ T. L2 x1 W
, ]; Z1 W; H6 |. c) C8 v4 u; H4 a
2.2. 使用STM32CubeMX配置并生成工程
  }! G3 c+ m2 O" @5 q
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:

( J/ `2 Q9 Z/ i4 u1 D! R9 K

: g) |$ x% y6 K% y& y
25.png
1 j6 s2 m6 R" G
最后生成的工程文件的结构如下:
. D, ^% L4 f9 G! T# U

: x6 }$ J. f; |  N/ N7 s  q* S
26.png , r. ]# J+ A4 \5 ~1 E; Q
; \: F8 ^. t; v( W9 n6 }9 E
2.3. 参数配置的解释
$ i  J3 [4 @9 L
需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。

8 X+ x$ o! ]! ^7 c5 a8 v

$ ]9 R5 S& z1 A; S( Y
27.png
7 K3 c$ Q5 J) G; m9 ?* d, G
调整依据请参考如下内容:
. ]% i) q, E' w7 y: U- ]! x2 N

* F: s) J" Q# X. T
28.png
, M! a0 q; y. ^4 K$ k* Q% _
( m8 p4 [& N( s( r0 G0 U

7 c) L1 @' I- h# u5 x0 F
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素:
5 j4 w6 v3 |% b  c4 \/ J6 y* a) }
1. USBX设备系统堆栈大小(USBX Device System Stack Size)

  M# x' j8 b5 R- V
2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size)

( W$ s3 @, V  U- v4 ?$ q# b$ Z
3. USB应用程序创建的线程堆栈

! q  h) N) I3 e3 v& J, ?  S
4. 以及RX和TX缓冲区
2 R0 V2 c: K6 V* z

: |3 c/ b% M  B9 t0 A! F
29.png
7 q- k4 I. j% [# l8 e/ O, M- f0 \! a3 y& z. m5 u% V6 S
03

% s5 N( d* i2 d! }' |! F
对工程进行完善和修改
$ [6 F9 _3 V9 L: E5 ~7 n9 K  l
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:

4 m6 k& u4 a; J& U, ^4 K. G6 ?+ G

; A' Y# p0 K2 |$ o
30.png
6 T* k" G9 O% j/ V0 }0 M$ i1 ?, z. V
8 F, M$ h. @/ u
5 B4 w: @  q. `4 u- i
代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。

$ y1 u' F, n4 `0 ~  p0 {  a, U) }, N4 E; H. Q
第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。

) l! X  [8 C  a3 p8 a2 O+ F) i) \
  1. <div style="text-align: left;">/* USER CODE BEGIN Includes *///</div><div style="text-align: left;">#include "ux_dcd_stm32.h"   </div><div style="text-align: left;">#include "main.h"</div><div style="text-align: left;">/* USER CODE END Includes */  </div><div style="text-align: left;">/* USER CODE BEGIN 1 */</div><div style="text-align: left;">#define APP_QUEUE_SIZE  5</div><div style="text-align: left;">typedef enum     </div><div style="text-align: left;">{</div><div style="text-align: left;">STOP_USB_DEVICE = 1,</div><div style="text-align: left;">START_USB_DEVICE,</div><div style="text-align: left;">} USB_MODE_STATE;</div><div style="text-align: left;">/* USER CODE END 1 */</div>
复制代码
1 V* F0 S: S6 n4 R6 b/ j3 g. O
第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。
  u/ Y( R- z: q" l
  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 W6 E! O$ r4 v" }8 E) n4 z9 Z
第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。

' J  i! q" D" H1 V1 y5 G3 s- 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>
复制代码

$ u8 g: e! k/ Y( 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。
" y1 j: Z# ~0 I4 O. i

4 y$ a: n; }5 {
31.png 8 O7 v0 V. C* |6 Q! x; A
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。

/ O0 X: \1 e& Z0 c2 g

& Z& m& Z0 n4 ~
& }+ I  D9 w( \: z$ P& e
* v, V6 Y9 w7 d; O% r& u
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。

" K/ Y9 K# }* h/ e8 P" I) \
6 b0 z+ }$ z% b8 `4 [

5 Y' ?3 p1 h& A+ O9 k
+ Y) ]) \! U& ^8 w0 d
CDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。

$ b' F; F! Y# O2 h
6 y6 m, o* ?4 \- v6 z0 B: s
0 K  Z# ?. A8 s1 v9 j

6 C% I7 }. ]9 {
下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。

' E( J7 a5 k3 V) h, J
7 |& ]! i6 G5 a4 h; i  ?  ]
8 ^& S1 h( h* S

3 Z5 m( b& m$ m6 I7 o
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
: [, G# o/ J$ `  b  g" E" c
  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>
复制代码

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

/ w) y) s# X7 z  e5 I. y) H
  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>
复制代码
3 P) u2 @+ s! [% ]' j" \
第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:
" y1 g( C- s4 a+ T5 T2 q) Z
  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>
复制代码
" _) P* i. E( s0 k9 V/ o
第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。
6 u$ f0 n( }6 i/ d4 p. V7 r! K; 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>
复制代码
" h& d3 I" y1 f7 D! Y
这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
* u" F( `! V2 t! e8 H
) m7 H) O1 b1 d* }
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。

/ b. S' b% b/ {( s  G
  1. <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">: t: O0 z+ t1 t9 m1 {6 B: T" N" \
  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>
复制代码
5 ~- t; K  j  B" n2 k

5 Y$ \) {* d  Q& p8 b6 M" _, }
04

$ A$ a4 `7 `1 |! [6 u" D; s
对代码进行的测试
- e) V; w$ j0 q( H+ v* S1 q( G7 _
对软件工程添加代码后就可以进入编译和调试环节。
% L  W5 J  w  ^9 w
4 Z) ~( W* }2 i# T3 I
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。
  Z- b* f! o' J7 o# Q1 ^: q4 y
( k7 g0 v9 `0 H4 U
32.png
! B/ @' J( H: M4 W, m- e
或EXTI_SWIER1.SWI13中写1回车后:

5 A( u. X, |0 a3 a+ F% J' N6 q+ U
- ?! ?# I1 t! i. i  C, G
33.png " V7 t0 }: O+ h$ X- x+ \  }8 W
然后产生外部按键中断:

5 I4 ]: @4 ~0 ]4 g3 J' R
: C1 N! i3 x$ x
34.png , `% q- T8 m, F- ?
使用USB线连接PC,并且程序全速运行后会看到识别的端口号:

( A, z: n9 F) E

8 B7 o/ ?% w% {8 P6 s+ Z
35.png
1 H3 |( n( t! D* r- [
代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据:
! O/ h2 ]6 K; Z1 e+ j2 `# P

4 a+ }! f) D% ]! u
36.png
) D8 d4 ?  F$ m& O# N  k
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。

1 t# I1 |$ h; A" G: D# M, ]- Y- U  @9 d% v1 L0 q% y4 k
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
! [' T5 i9 L6 e& i& M) e2 ^
2 `9 R2 ~1 o3 O
6 @7 d8 E. n, v" d8 C- Q
: h4 ^$ w) U% I
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0:
8 \) S# N" W) G. A
1 A- L* A& P$ ^8 S) v! c
9 E4 I6 J) c; [/ x

( N" ?0 E2 p; T: E  A' d! T
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。
7 v+ L- ]) m' O7 v2 l* V3 e  t& W7 ]
  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>
复制代码

" }$ c) z, m. {3 d( g( F: G1 p
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…

( w( t6 A& k) P9 W) `1 o
9 G% E' }; J0 @- z* B+ R
37.png
' \' d  x9 [" O3 k3 n! ^
05

6 D; k5 i6 j, |$ v1 ?
小结

+ D: j+ n& L' \6 [
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。

9 e$ q/ l! V, @0 \) @* V7 B
4 c, G& W9 Z4 V  F, |
收藏 评论0 发布时间:2025-2-24 09:28

举报

0个回答

所属标签

相似分享

官网相关资源

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