01+ P1 M3 E$ d5 I( N
客户需求% o- L2 h9 u$ |; [3 j
客户使用STM32H563开发产品,需要USB CDC ACM虚拟串口的工程,并且要求不使用PD功能,而我们STM32CubeH5代码库中是包含PD功能的工程。于是协助客户解决这个问题,提供给客户不带PD功能的虚拟串口工程。! x7 t4 @" o$ Q6 A+ U" i2 Z
4 V4 D/ v; w; }1 T
在STM32CubeMX软件中,选择ThreadX USBX模块完成USB CDC ACM虚拟串口的工程,并且不使能PD功能。/ [; |6 \+ ~8 Q
7 G1 U. J% G* n02
- }% A2 ~( O; n. R, E/ Q$ ~0 j基本硬件和STM32CubeMX的配置
2 V/ O& x4 K( R+ i; e: S' o' I5 g i4 x8 l
硬件方面使用客户开发板和NUCLEO_H563ZI同时来进行软件开发。0 O8 _/ k, s1 e- H; |* B1 l2 A
8 K, ]0 \7 y% I% Q# H) T3 g1 _
下面是NUCLEO_H563ZI USB Type C接口原理图:
1 ?: y. r5 V* X
* B7 [* s' ^2 P% C- u
' m6 F: W. J0 X& U2 g! ~ 0 }/ y* j: O9 w, `% I5 v" f
由于不使用PD功能,需要对NUCLEO_H563ZI的电路进行改动,在开发板上 PA11、PA12连接到了使用PD功能的Type-C接口,因此需要做如下的硬件修改:需要把SB27、SB28上的两个小电阻换移到SB22、SB21的位置,这样做是把 PA11、PA12连接到开发板的CN12上,然后就可以接USB线了。
; ~# j' x" W! z1 ^: S' W% U- F
' p! H+ w/ @ b3 k# P1 h1 ~1 u% q5 i5 j$ Z) A: e! G& g
7 W! r' l* o$ P( J$ ^8 |0 p- S
1 B6 v: V, ]& I1 Q" \ & {/ P3 `/ e7 m3 T+ y6 m
USB连接线需要接GND、D+、D-;在对NUCLEO_H563ZI开发板的调试中,VBUS没有连接起来,但客户开发板的设计中是需要连接的。以此来告诉USB Device端(STM32H563ZI)的软件工程,USB线连接到PC上了。
' y7 Y; K" X! a: y) z2.1. 使用STM32CubeMX配置并生成工程(不带trustZone) 6 Z) F1 s9 z* ?
' A9 n$ J6 ~+ k0 I# d
3 k9 n1 ?( O `8 J; C) K! z# s: q$ I
第一步:开始一个新的不带trustZone工程: 9 G- n5 o- ~8 B) M* V
2 D3 J/ Q! |) t6 S2 D f
9 t& ]2 D! S/ ~; w, ?2 W
% E n) a& H3 p7 R
! D) K. q0 _' j# j& a& ]4 {第二步:配置USB外设,基本上选择默认配置,同时使能USB的全局中断。
. }/ a8 \' T; R: x: w ' r+ F1 A& @9 W; n4 ^4 @* c
; j/ Y( D8 S1 \0 K: w第三步:配置ThreadX外设,使能Core,其它先选择默认配置。
- k( c! @. x) }4 Q
2 i3 S$ w; ^6 i
, V e& \$ Q+ x4 w1 @# C6 J& ~. b
& v" K6 I; i, E2 E
第四步:配置USBX:
. B; E. P: O( N9 ~( S" u" E2 G: D1. 使能Core System。 : h* u" A5 S' |/ R7 Q7 L
2. 在UX Device FS中,选择Device CoreStack FS和Device Contronllers FS。
% C+ a% r# S l$ E" @3. 在Device Class FS中,选择CDC ACM。 . H6 |7 s6 l6 q9 G G+ M: V2 f
. ], a+ b ^- r u
# Q f7 j1 `8 B5 K- q e# \( `- ~7 ^% f# [$ |# @3 E7 Q
9 d% @" m+ J+ l1 R$ k2 [' e5 Q& ~
4. 配置USB基本参数。其配置除了对RAM需要设置外,其它选择基本配置就可以了。
" p/ s; C$ O0 y
' }, h* }% C3 V% C9 j0 q
2 T5 V2 e' c; ~) V9 O% L7 G
5. 其它的Platform,选择USB。 3 F u( S/ y) r! U: e' d
4 v9 n* k( |( D3 R3 j9 O' {& _& y8 ^
; |$ f4 A/ {9 c
第五步:配置SYS,选择TIM6作为系统滴答时钟的时钟源。
) {- g. ~* I6 K; h' _; V: Z
- M4 S7 U1 ?/ R7 T9 z) J3 A% i6 j$ q! O
$ a( U7 C) ` D9 Y5 } f2 K% X" c, ^
$ D; U2 ~, l. Y: U) y3 n第六步:为了模拟USB的断开也连接,使用一个GPIO来控制USB的断开和连接 (GPIO_EXTI13);并且使能外部中断。
/ e- ^& e( S% M1 A) v F1 { : x/ P4 u) I4 B; {
, g! x5 O& E& W& e% x, V2 D
6 I: B6 E1 D- j5 G% E9 s1 v
; t! S) c; m7 s* u& ^- J& i6 |1 j第七步:配置时钟,选择默认时钟源。配置系统时钟为250MHz。
: g9 H1 `6 L% \ 7 V0 B- S2 d* H( a
& Q& p7 i# H' I4 E/ |) [, S
+ P# j* u+ y; @1 X8 C4 @
8 G8 d( C$ t. N( o) x请注意:使用HSI48作为CRS时钟,同时注意选择HSI48作为USB时钟源。 ! B1 w2 k7 K9 Q; U, I
$ W7 G/ l. R l' s
% V, G* T$ I# C* Q5 w$ }* Q6 l( P3 u4 m9 _' S2 N7 j
+ ~" K; W5 y3 R) @( Z" Z
2.2. 使用STM32CubeMX配置并生成工程 % c) h) T& m1 L* F5 a
定义一个自己的工程名,由于用的RTOS和USB,所以需要适增加堆栈空间:
5 _* J/ T/ H$ W% a9 J
: d3 p) o( x$ F3 y7 x
9 H$ a1 R; E& i* h7 \0 f
最后生成的工程文件的结构如下:
1 I7 m# s ^% @6 R, ]% e. U7 M 8 {" l. @1 u2 l& }* @# |
, D" D+ Z4 j# M( L
- ^. I: v6 ]1 w* d) g
2.3. 参数配置的解释
. m3 I& S& k4 G+ `* o3 v" t0 D g% v需要调整USBX Device System Stack Size和USBX Device memory pool size的参数。 9 E$ H$ I5 Z4 u* n
! h8 L4 k- N/ C' s5 _" U' x1 S
9 h0 A X' r, G) D D调整依据请参考如下内容:
# ?% T5 E" l' G( ]* m4 I' P) Y
, v( c' v% r0 e8 S3 l, y
' C! i% y. _ M8 ^5 ~; |6 u: q1 }- _' Y
; b" [( L" B; S! ^. e , I2 F! [- k6 |
要定义USBX设备内存池(USBX Device memory pool size)大小的内存量,必须考虑以下因素: % |9 \" ]8 E1 A0 ?5 H, i
1. USBX设备系统堆栈大小(USBX Device System Stack Size)
1 @6 l" C/ Y$ `2. USBX设备应用程序线程堆栈大小(USBX Device Application Thread stack size) ) {" N2 e6 n5 b
3. USB应用程序创建的线程堆栈
8 {& x0 A9 B! W8 r4. 以及RX和TX缓冲区 ! W) b5 u9 C5 g& |- a
& _$ k- t0 b3 ?! o! A
, o. z& S5 \9 a5 |
6 H( e' I/ E6 ^% a$ f( |03 : ]2 a' J1 {- Y) S; x
对工程进行完善和修改 . [" f2 @/ s- @, K7 }' t9 [
由于CubeMX的生成的代码有限,生成软件工程后需要对工程进行完善,具体改动如下:
\) n" ]" S }& }4 l N4 t - k7 u9 J- h0 F8 F5 O* T
* R; h! J0 q) |. v9 B
0 y' W# e& P) ]9 z( C: o. [
' Y; I) u; O, X代码开发的第一步是将ST HAL USB驱动程序与USBX固件连接起来,然后初始化USB外设。还需要添加一下必要的代码,下面蓝色部分为需要添加的,绿色部分是生成的代码,你可以搜索绿色部分快捷收到需要添加的位置。 7 u/ N+ O% s# a& j3 o t
2 p5 l+ [% l. V. ?( c: _' U第一步:在“app_usbx_device.h”添加必须包含的头文件和需要用到的函数声明。 # T6 e( ?/ o8 }% I: a
- <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 G& u9 B0 W3 T* q1 |: q第二步:在“app_usbx_device.c”添加需要用到的变量和函数声明。( G& u7 x$ k7 e A6 s3 y7 @6 j4 i
- <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>
复制代码
- H( Y( z- ]) q# o第三步:在app_ux_device_thread_entry函数中,添加链接驱动程序和初始化USB外设的代码。
0 B4 ]4 s7 F9 W& c, g" B+ x- <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>
复制代码
3 A$ \( v7 l9 U. Z2 Z先调用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。 " z2 g+ C6 \ Y0 v I4 [: B8 f
' b% d' g. }$ P0 u. e0 a+ L9 K
: k- z8 J: b4 ~" J# z- T. h
使用内存的第一个地址来存储BTABLE,它是端点缓冲区的地址列表。BTABLE为每个端点存储8个字节。由于STM32H563有8个端点,因此它最多可以消耗64字节。
& z+ ^0 P/ b, L" p$ J
; E4 N7 ]" b* ]- |7 B0 p 7 [# o& ^3 ~, m5 O% q1 w
8 W% y: v8 D m* ^
在本例中,我们使用从0到3的端点,因此可以不使用剩余的其余部分。如果需要可以减少TX Ep 0缓冲区的偏移量,优化内存的使用。但是,按照这个表,您可以毫无问题地分配所有8个端点缓冲区。 4 ^, Z3 R% w/ e. Q" O! J E& u8 @' B
9 i, b/ b& T; s% K# s# ?) O
1 z3 P# X% {/ F( }3 O$ p! d! E
3 _5 A; M7 D/ Y( _6 X: sCDC类的配置描述符一般包含一个接口0(Interface 0),一个控制接口,另外一个是数据接口(Interface 1),除此之外,IAD(Interface Association Description),这个是可选的,根据实际情况来确定是否需要。 1 X% ^6 S1 t$ Q9 M
6 k5 O3 X3 i. [# z$ E
6 C# R7 B/ [, f) q# A
$ G# o1 S9 g" r3 R下一个调用的函数是ux_dcd_stm32_initialize(…) ,它负责将HAL USB驱动程序链接到USBX应用程序。最后,我们调用HAL_PCD_Start(..)来启动 USB PCD外设。 ; J) h5 P, o( P& l0 q' Z/ L0 I, j
; c. z- `3 c' w) G" R* N9 ]
1 x" n" k* H! ]- U0 Q" g: G* x8 e+ m& X' c
第四步:在app_usbx_device.c中MX_USBX_Device_Init(…)函数的最后用户代码部分,创建两个线程来处理数据的写和读,以及一个消息句柄,具体的代码如下,最后一个参数为TX_DONT_START,意思是任务先挂起,之后再启动。
! P" K. z" v. \ [$ b8 v# ]& F- <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>
复制代码
$ z4 e9 G6 G% z2 q6 r1 }; s6 y第五步:下面的usb_connect()函数是根据客户需要设计的。在PC端连接到设备端STM32H563,VBUS连接时触发中断后调用的函数(客户产品中把VBUS分压后连接到一个GPIO口,由于客户第一版本的PCB没有设计这部分电路,因此调试时使用按键中断替换了Device连接到PC的情况)。Usb_disconnect()函数,是处理STM32H563从PC断开的情况(调试时使用按键再次按下来替换VBUS断开的具体功能)。保留了信号量,程序处理中如果不使用这个信号量可以删除,具体请参考软件工程文件:
( p# v$ h7 |1 [5 Q+ N- <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>
复制代码
. W9 h- @9 A+ N/ Y# h8 V5 `第六步,在ux_device_cdc_acm.h文件并添加必要的头文件包含和函数声明:
6 a1 ?& e9 ?- E- <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>
复制代码
' Z! R" \0 F, K& C; v1 }( C第七步,在ux_device_cdc_acm.c添加私有变量并在“USBD_CDC_ACM_Activate”初始化。 ( J- U6 ]) K4 U; j
- <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>
复制代码
# W' J3 m$ o$ @7 S这样,我们已经有了USB应用程序的功能。类资源在ux_device_cdc_acm.c文件中可用。
5 h/ U" e' d7 K% y! w9 Y6 f" t6 J8 j6 Z7 ~1 S/ g9 h1 e
第八步:在“ux_device_cdc_acm.c”中定义线程的实现程序。 # h3 }( k! d' ?; h. D5 R
- <div style="text-align: left;">/* USER CODE BEGIN 1 */</div> <div style="text-align: left;">& C. s, z, E1 ]
- </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>
复制代码 - }! G7 e# Z m4 E% h
) l% r. K2 u' ^' V* V2 C04 / _- h0 R9 ~ A% y6 J# i$ M
对代码进行的测试 7 g! S, z9 ?. I* X; s! t
对软件工程添加代码后就可以进入编译和调试环节。 o2 d5 [ k$ k8 }: n2 l
. L: {$ b/ L; @' c) Y# T
客户第一版的硬件产品板上无PC13按键,要手动触发按键中断,需要在下面蓝色栏中写‘1’,然后按下键盘中的回车键来模拟按键按下的动作。(NUCLEO_H563ZI硬件有这个按键,可以直接使用这个按键来生成按键中断)。 6 r+ l# _& a6 V1 i9 d3 i0 E
/ w) M) ]. H0 X4 w2 z& m
0 P }4 Q! K: ?; ]) d或EXTI_SWIER1.SWI13中写1回车后:
& n) P9 G. H$ n4 ` $ |# c( m5 X2 i r! @$ {
! j! Y% P& K0 G# E8 h
然后产生外部按键中断: 5 Y# \4 J" b& m" ?+ P; R
3 I# L7 e2 H% a! X% [: H/ y
$ K, k. N8 @9 k4 V8 m# m7 ~使用USB线连接PC,并且程序全速运行后会看到识别的端口号: ' V! g5 N; _- h
6 |% d* K. r, F. v% G
: i* q: ^8 _* w6 d代码运行后在PC端打开UART调试助手,可以看到从虚拟串口接收到的内容,然后PC发送“1”,在usbx_cdc_acm_read_thread_entry函数中设置断点就可以看到接收到的数据: - b; h0 y, Q3 i! i M9 a" v$ r t Z
8 C- _9 u! m! B! N8 j6 g
" y# q T% \/ H: V( }- z3 s
按键(Nucleo_H563ZI 上的 USER蓝色按键)第一次按下(模拟VBUS上电的动作)后启动(resume)usbx_cdc_acm_write_thread_entry和 usbx_cdc_acm_read_thread_entry任务,在第二次按键按下时挂起(suspend)那两个任务。 7 J5 X4 c2 C) [! [& |1 Z+ s9 K
( r* N h4 p+ L; I
使用时需要根据情况修改HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)函数,以实现使用Vbus上电代替第一次按下Key的功能,Vbus掉电代替第二次按下Key的功能。
/ a8 M1 v4 c8 S- e+ Y1 X; G9 w( V0 z4 i- J9 n: u' h
9 {" U' p2 k8 |" v0 O3 J3 f) `* l; S2 X' _
代码调试时Vbus上电(第一次按键)和Vbus下电(第二次按键)之间需要暂停程序,然后在调试状态下,在观察窗中修改KeyNumber的值从0到1;Vbus下电(第二次按键)后需要在窗口中改KeyNumber为0: . G4 d& R5 G: C2 A* i9 F1 Y0 T
/ H& j1 ~* o0 U( h% e! l
1 P8 d" m( @% ^$ u5 \$ k: r, D4 Y4 O
下面的功能在程序中预留了,如果后续程序开发中不使用可以删除。
H/ Q& @" Z+ ~- <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>
复制代码 & `2 s# L* W% h
初次上电后,请全速运行代码,等待初始化完成,第一次按下按键后走usb_connect流程(vbus 上电,参考HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13)处理函数,代码运行后PC端会看到具体的串口端口号。用串口调试助手测试过数据收发功能后,在调试中暂停代码运行,然后修改KeyNumber从0到1(在观察窗中直接修改后回车)。然后全速运行然后再次按下按键就会走usb_disconnect流程。之后再修改KeyNumber从1到0,再次按下按键(vbus上电),进入的设备枚举阶段…
# F% F4 ^/ I$ M/ P' m
0 Q6 }+ M& W9 B- S& L& p7 T) \, D
% D$ P) M# l5 G, n2 o( {) c- s05
) U7 Z5 U* R2 l- J, m1 f1 E小结 , Y% \5 C9 G# F: L2 \: |3 ~; Q
具体的软件工程放在了附件中,使用NUCLEO_H563ZI,然后修改硬件就可以进行测试。
: a3 P" |& i3 s: M' a4 M9 m $ D/ n z5 B1 a. \! s
|