libusb开发流程

本文详细介绍使用STM32单片机实现USB Bulk传输的方法,包括STM32端USB程序编写、libusb驱动生成及上位机驱动程序编写等关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
USB的用途就不多说了,下面的内容主要就是讲解如何利用ST提供的USB驱动库和libusb上位机驱动库实现一个USB数据传输功能,为了降低开发难度,我们仅仅讲解Bulk传输模式,当然这也是用得比较多的传输模式。

开发流程
1,完成STM32单片机端的USB程序;
2,利用linusb自带的inf-wizard工具生成USB驱动;
3,基于libusb编写USB通信程序;
4,测试PC和单片机的数据通信;

STM32程序编写
1,完成描述符的修改,修改后的描述符如下(在usb_desc.c文件中)
设备描述符:

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

const

uint8_t CustomHID_DeviceDescriptor[CUSTOMHID_SIZ_DEVICE_DESC] =

{

     0x12,                      
/*bLength
*/

     USB_DEVICE_DESCRIPTOR_TYPE,
/*bDescriptorType*/

     0x00,                      
/*bcdUSB
*/

     0x02,

     0x00,                      
/*bDeviceClass*/

     0x00,                      
/*bDeviceSubClass*/

     0x00,                      
/*bDeviceProtocol*/

     0x40,                      
/*bMaxPacketSize40*/

     LOBYTE(USBD_VID),          
/*idVendor*/

     HIBYTE(USBD_VID),          
/*idVendor*/

     LOBYTE(USBD_PID),          
/*idVendor*/

     HIBYTE(USBD_PID),          
/*idVendor*/

     0x00,                      
/*bcdDevice
rel. 2.00*/

     0x02,

     1,                         
/*Index
of string descriptor describing manufacturer */

     2,                         
/*Index
of string descriptor describing product*/

     3,                         
/*Index
of string descriptor describing the device serial number */

     0x01                       
/*bNumConfigurations*/

};
/*
CustomHID_DeviceDescriptor */



配置描述符:

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

const

uint8_t CustomHID_ConfigDescriptor[CUSTOMHID_SIZ_CONFIG_DESC] =

{

     0x09,
/*
bLength: Configuation Descriptor size */

     USB_CONFIGURATION_DESCRIPTOR_TYPE,
/*
bDescriptorType: Configuration */

     CUSTOMHID_SIZ_CONFIG_DESC,

     /*
wTotalLength: Bytes returned */

     0x00,

     0x01,        
/*
bNumInterfaces: 1 interface */

     0x01,        
/*
bConfigurationValue: Configuration value */

     0x00,        
/*
iConfiguration: Index of string descriptor describing

                                  the
configuration*/

     0xE0,        
/*
bmAttributes: Bus powered */

                   /*Bus
powered: 7th bit, Self Powered: 6th bit, Remote wakeup: 5th bit, reserved: 4..0 bits */

     0xFA,        
/*
MaxPower 500 mA: this current is used for detecting Vbus */

     /**************
Descriptor of Custom HID interface ****************/

     /*
09 */

     0x09,        
/*
bLength: Interface Descriptor size */

     USB_INTERFACE_DESCRIPTOR_TYPE, /*
bDescriptorType: Interface descriptor type */

     0x00,        
/*
bInterfaceNumber: Number of Interface */

     0x00,        
/*
bAlternateSetting: Alternate setting */

     0x04,        
/*
bNumEndpoints */

     0xDC,        
/*
bInterfaceClass: Class code = 0DCH */

     0xA0,        
/*
bInterfaceSubClass : Subclass code = 0A0H */

     0xB0,        
/*
nInterfaceProtocol : Protocol code = 0B0H */

     0,           
/*
iInterface: Index of string descriptor */

     /********************
endpoint descriptor ********************/

     /*
18 */

     0x07,        
/*
endpoint descriptor length = 07H */

     USB_ENDPOINT_DESCRIPTOR_TYPE,
/*
endpoint descriptor type = 05H */

     0x81,        
/*
endpoint 1 IN */

     0x02,                                       
/*
bulk transfer = 02H */

     0x40,0x00,   
/*
endpoint max packet size = 0040H */

     0x00,        
/*
the value is invalid when bulk transfer */

 

     0x07,        
/*
endpoint descriptor length = 07H */

     USB_ENDPOINT_DESCRIPTOR_TYPE,
/*
endpoint descriptor type = 05H */

     0x01,        
/*
endpoint 1 OUT */

     0x02,                                       
/*
bulk transfer = 02H */

     0x40,0x00,   
/*
endpoint max packet size = 0040H */

     0x00,        
/*
the value is invalid when bulk transfer */

                 

     0x07,        
/*
endpoint descriptor length = 07H */

     USB_ENDPOINT_DESCRIPTOR_TYPE,
/*
endpoint descriptor type = 05H */

     0x82,        
/*
endpoint 2 IN */

     0x02,                                       
/*
bulk transfer = 02H */

     0x40,0x00,   
/*
endpoint max packet size = 0040H */

     0x00,        
/*
the value is invalid when bulk transfer */

                 

     0x07,        
/*
endpoint descriptor length = 07H */

     USB_ENDPOINT_DESCRIPTOR_TYPE,
/*
endpoint descriptor type = 05H */

     0x02,        
/*
endpoint 2 OUT */

     0x02,                                       
/*
bulk transfer = 02H */

     0x40,0x00,   
/*
endpoint max packet size = 0040H */

     0x00,        
/*
the value is invalid when bulk transfer */

};
/*
CustomHID_ConfigDescriptor */


配置描述符就包含了端点描述符,我们用了4个端点,两个BULK-OUT端点,两个BULK-IN端点。

其他的描述符:

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

/*
USB String Descriptors (optional) */

const

uint8_t CustomHID_StringLangID[CUSTOMHID_SIZ_STRING_LANGID] =

{

     CUSTOMHID_SIZ_STRING_LANGID,

     USB_STRING_DESCRIPTOR_TYPE,

     0x09,

     0x04

};
/*
LangID = 0x0409: U.S. English */

 

const

uint8_t CustomHID_StringVendor[CUSTOMHID_SIZ_STRING_VENDOR] =

{

     CUSTOMHID_SIZ_STRING_VENDOR,
/*
Size of Vendor string */

     USB_STRING_DESCRIPTOR_TYPE, 
/*
bDescriptorType*/

     //
Manufacturer: "STMicroelectronics"

     'M' ,
0,
'y' ,
0,
'U' ,
0,
'S' ,
0,
'B' ,
0,
'_' ,
0,
'H' ,
0,
'I' ,0, 'D' ,0

};

 

const

uint8_t CustomHID_StringProduct[CUSTOMHID_SIZ_STRING_PRODUCT] =

{

     CUSTOMHID_SIZ_STRING_PRODUCT,         
/*
bLength */

     USB_STRING_DESCRIPTOR_TYPE,       
/*
bDescriptorType */

     'B' ,
0,
'y' ,
0,
'
'
,
0,
'e' ,
0,
'm' ,
0,
'b' ,
0,
'e' ,0, 'd' ,0, '-' ,0, 'n' ,0, 'e' ,0, 't' ,0

};

uint8_t
CustomHID_StringSerial[CUSTOMHID_SIZ_STRING_SERIAL] =

{

     CUSTOMHID_SIZ_STRING_SERIAL,          
/*
bLength */

     USB_STRING_DESCRIPTOR_TYPE,       
/*
bDescriptorType */

     'x' ,
0,
'x' ,
0,
'x' ,
0,
'x' ,
0,
'x' ,
0,
'x' ,
0,
'x' ,
0

};



2,根据端点缓冲区大小配置端点缓冲区地址,配置信息如下(在usb_conf.h文件中):

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

/*
buffer table base address */

#define
BTABLE_ADDRESS      (0x00)

 

/*
EP0  */

/*
rx/tx buffer base address */

#define
ENDP0_RXADDR        (0x18)

#define
ENDP0_TXADDR        (0x58)

 

/*
EP1  */

/*
tx buffer base address */

//地址为32位对其,位4的倍数,不能超过
bMaxPacketSize

//EP1

#define
ENDP1_RXADDR        (0x98)

#define
ENDP1_TXADDR        (0x98+64)

////EP2

#define
ENDP2_RXADDR        (0xA0+64+64)

#define
ENDP2_TXADDR        (0xA0+64+64+64)



3,初始化每个端点(在usb_prop.c文件中的CustomHID_Reset函数中)

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

/*
Initialize Endpoint 0 */

SetEPType(ENDP0,
EP_CONTROL);

SetEPTxStatus(ENDP0,
EP_TX_STALL);

SetEPRxAddr(ENDP0,
ENDP0_RXADDR);

SetEPTxAddr(ENDP0,
ENDP0_TXADDR);

Clear_Status_Out(ENDP0);

SetEPRxCount(ENDP0,
Device_Property.MaxPacketSize);

SetEPRxValid(ENDP0);

 

/*
Initialize Endpoint 1 */

        SetEPType(ENDP1,
EP_BULK);

        SetEPRxAddr(ENDP1,
ENDP1_RXADDR);

        SetEPTxAddr(ENDP1,
ENDP1_TXADDR);

        SetEPRxCount(ENDP1,
EP_SIZE);

        SetEPRxStatus(ENDP1,
EP_RX_VALID);

  SetEPTxStatus(ENDP1,
EP_TX_NAK);

 

/*
Initialize Endpoint 2 */

        SetEPType(ENDP2,
EP_BULK);

        SetEPRxAddr(ENDP2,
ENDP2_RXADDR);

        SetEPTxAddr(ENDP2,
ENDP2_TXADDR);

        SetEPRxCount(ENDP2,
EP_SIZE);

        SetEPRxStatus(ENDP2,
EP_RX_VALID);

        SetEPTxStatus(ENDP2,
EP_TX_NAK);



4,实现端点的回调函数(需要在usb_conf.h中注释掉对应的回调函数宏定义)

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

/*******************************************************************************

*
Function Name  : EP1_OUT_Callback.

*
Description    : EP1 OUT Callback Routine.

*
Input          : None.

*
Output         : None.

*
Return         : None.

*******************************************************************************/

void

EP1_OUT_Callback(
void )

{

         EP1_ReceivedCount
= GetEPRxCount(ENDP1);

         PMAToUserBufferCopy(USB_Receive_Buffer,
ENDP1_RXADDR, EP1_ReceivedCount);

         SetEPRxStatus(ENDP1,
EP_RX_VALID);

}

/*******************************************************************************

*
Function Name  : EP2_OUT_Callback.

*
Description    : EP2 OUT Callback Routine.

*
Input          : None.

*
Output         : None.

*
Return         : None.

*******************************************************************************/

void

EP2_OUT_Callback(
void )

{

         EP2_ReceivedCount
= GetEPRxCount(ENDP2);

         PMAToUserBufferCopy(USB_Receive_Buffer,
ENDP2_RXADDR, EP2_ReceivedCount);

         SetEPRxStatus(ENDP2,
EP_RX_VALID);

}



5,完成主函数的测试程序

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

int

main(
void )

{

         uint8_t
data[256];

         uint32_t
i=0;

         Set_System(); //系统时钟初始化

         USART_Configuration(); //串口1初始化

         printf ( "\x0c\0" ); printf ( "\x0c\0" ); //超级终端清屏

         printf ( "\033[1;40;32m" ); //设置超级终端背景为黑色,字符为绿色

         printf ( "\r\n*******************************************************************************" );

         printf ( "\r\n************************
Copyright 2009-2012, EmbedNet ************************"
);

         printf ( "\r\n***************************
[url=http://www.embed-net.com]http://www.embed-net.com[/url] **************************"
);

         printf ( "\r\n*****************************
All Rights Reserved *****************************"
);

         printf ( "\r\n*******************************************************************************" );

         printf ( "\r\n" );

 

         USB_Interrupts_Config();

         Set_USBClock();

         USB_Init();

 

         while (1)

         {

                 if (EP1_ReceivedCount
> 0){

                         USB_GetData(ENDP1,data,EP1_ReceivedCount);

                         USB_SendData(ENDP1,data,EP1_ReceivedCount);

                         printf ( "usb
EP1 get data %d byte data\n\r"
,EP1_ReceivedCount);

                         for (i=0;i<EP1_ReceivedCount;i++){

                                 printf ( "0x%02X
"
,data[i]);

                         }

                         printf ( "\n\r" );

                         EP1_ReceivedCount=0;

                 }

                 if (EP2_ReceivedCount
> 0){

                         USB_GetData(ENDP2,data,EP2_ReceivedCount);

                         USB_SendData(ENDP2,data,EP2_ReceivedCount);

                         printf ( "usb
EP2 get data %d byte data\n\r"
,EP2_ReceivedCount);

                         for (i=0;i<EP2_ReceivedCount;i++){

                                 printf ( "0x%02X
"
,data[i]);

                         }

                         printf ( "\n\r" );

                         EP2_ReceivedCount=0;       

                 }

         }

}



到此,STM32的程序基本上编写完成,然后编译下载程序,如果一切顺利,系统会检测到一个新的设备并试图加载对应的驱动,由于我们还没做驱动程序,所以肯定会加载驱动失败,如下图所示:
 

驱动程序生成
下面我们就利用libusb自带的inf-wizard工具生成USB驱动程序,该工具可以到本文章的附件下载,其具体过程如下:
 

运行该程序,出现下图对话框,点击“Next”;
 

出现下图对话框后选择我们需要生成驱动程序的设备;
 

这里可以写该Device Name,我们保持默认值,其他的都不需要修改;
 

点击Next后出现下图对话框,我们选择一个目录保存这个inf文件;
 

保存后的文件
 

若要立即安装驱动,可以点击下面对话框的红色框按钮;
 

Win7下可能会出现如下对话框,点击始终安装;
 

到此,USB驱动程序自动生成完毕,若安装了驱动,则在设备管理器里面会看到如下信息
 

基于libusb的上位机驱动程序编写
首先建立一个驱动程序工程,然后将libusb的库(附件有下载)添加到工程里面,编写以下几个函数
设备扫描函数,该函数用来找到插入电脑上的USB设备

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

/**

   *
@brief  扫描设备连接数

   *
@param  NeedInit 是否需要初始化,第一次调用该函数需要初始化

   *
@retval 识别到的指定设备个数

   */

int

__stdcall USBScanDev(
int

NeedInit)

{

         if (NeedInit){

                 usb_init();
/*
initialize the library */

                 usb_find_busses();
/*
find all busses */

                 usb_find_devices();
/*
find all connected devices */

         }

         return

scan_dev(pBoard);

}



打开设备

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

/**

   *
@brief  打开指定的USB设备

   *
@param  devNum        需要打开的设备号

   *
@retval 打开状态

   */

int

__stdcall USBOpenDev(
int

DevIndex)

{

         pBoardHandle[DevIndex]
= open_dev(DevIndex,pBoard);

         if (pBoardHandle[DevIndex]==NULL){

                 return

SEVERITY_ERROR;

         } else {

                 return

SEVERITY_SUCCESS;

         }

}



关闭设备

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

/**

   *
@brief  关闭指定的USB设备

   *
@param  devNum        需要关闭的设备号

   *
@retval 打开状态

   */

int

__stdcall USBCloseDev(
int

DevIndex)

{

         return

close_dev(DevIndex,pBoardHandle);

}



BULK端点写数据

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

/**

   *
@brief  USB Bulk端点写数据

   *
@param  nBoardID 设备号

   *
@param  pipenum 端点号

   *
@param  sendbuffer 发送数据缓冲区

   *
@param  len 发送数据字节数

   *
@param  waittime 超时时间

   *
@retval 成功发送的数据字节数

   */

 

int

__stdcall USBBulkWriteData(unsigned
int

nBoardID,
int

pipenum,
char

*sendbuffer,
int

len,
int

waittime)

{

         int

ret=0;

         if (pBoardHandle[nBoardID]
== NULL){

                 return

SEVERITY_ERROR;

         }

#ifdef
TEST_SET_CONFIGURATION

     if

(usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)

     {

         usb_close(pBoardHandle[nBoardID]);

         return

SEVERITY_ERROR;

     }

#endif

 

#ifdef
TEST_CLAIM_INTERFACE

     if

(usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)

     {

         usb_close(pBoardHandle[nBoardID]);

         return

SEVERITY_ERROR;

     }

#endif

 

#if
TEST_ASYNC

     //
Running an async write test

     ret
= transfer_bulk_async(dev, pipenum, sendbuffer, len, waittime);

#else

         ret
= usb_bulk_write(pBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);

         /*if((len%64)
== 0){

                 usb_bulk_write(pBoardHandle[nBoardID],
pipenum, sendbuffer, 0, waittime);

         }*/

#endif

#ifdef
TEST_CLAIM_INTERFACE

     usb_release_interface(pBoardHandle[nBoardID],
0);

#endif

     return

ret;

}



BULK端点读数据

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

/**

   *
@brief  USB Bulk读数据

   *
@param  nBoardID 设备号

   *
@param  pipenum 端点号

   *
@param  readbuffer 读取数据缓冲区

   *
@param  len 读取数据字节数

   *
@param  waittime 超时时间

   *
@retval 读到的数据字节数

   */

int

__stdcall USBBulkReadData(unsigned
int

nBoardID,
int

pipenum,
char

*readbuffer,
int

len,
int

waittime)

{

         int

ret=0;

         if (pBoardHandle[nBoardID]
== NULL){

                 return

SEVERITY_ERROR;

         }

#ifdef
TEST_SET_CONFIGURATION

     if

(usb_set_configuration(pBoardHandle[nBoardID], MY_CONFIG) < 0)

     {

         usb_close(pBoardHandle[nBoardID]);

         return

SEVERITY_ERROR;

     }

#endif

 

#ifdef
TEST_CLAIM_INTERFACE

     if

(usb_claim_interface(pBoardHandle[nBoardID], 0) < 0)

     {

         usb_close(pBoardHandle[nBoardID]);

         return

SEVERITY_ERROR;

     }

#endif

 

#if
TEST_ASYNC

     //
Running an async read test

     ret
= transfer_bulk_async(pGinkgoBoardHandle[nBoardID], pipenum, sendbuffer, len, waittime);

#else

         ret
= usb_bulk_read(pBoardHandle[nBoardID], pipenum, readbuffer, len, waittime);

#endif

#ifdef
TEST_CLAIM_INTERFACE

     usb_release_interface(pBoardHandle[nBoardID],
0);

#endif

     return

ret;

}



到此,PC端的驱动程序编写基本完成,下面就是驱动程序的测试,我们可以把之前这个程序生成为一个dll文件,然后单独建立一个测试工程来测试这个dll文件中的函数,测试程序如下:

[C]  纯文本查看 复制代码
?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

//
USB_DriverTest.cpp : 定义控制台应用程序的入口点。

//

 

#include
"stdafx.h"

 

#define       
EP1_OUT_SIZE        64

#define       
EP1_IN_SIZE        64

 

int

_tmain(
int

argc, _TCHAR* argv[])

{

         int

DevNum;

         int

ret;

         char

WriteTestData[256]={1,2,3,4,5,6,7,8,9};

         char

ReadTestData[256]={0};

         for ( int

i=0;i<256;i++){

                 WriteTestData[i]
= i;

         }

         //扫描设备连接数,需要初始化

         DevNum
= USBScanDev(1);

         printf ( "设备连接数为:%d\n" ,DevNum);

         //打开设备0

         ret
= USBOpenDev(0);

         if (ret
== SEVERITY_ERROR){

                 printf ( "打开设备失败!\n" );

                 return

SEVERITY_ERROR;

         } else {

                 printf ( "打开设备成功!\n" );

         }

 

         //端点1写数据

         ret
= USBBulkWriteData(0,EP1_OUT,WriteTestData,EP1_OUT_SIZE,500);

         if (ret
!= EP1_OUT_SIZE){

                 printf ( "端点1写数据失败!%d\n" ,ret);

                 return

SEVERITY_ERROR;

         } else {

                 printf ( "端点1写数据成功!\n" );

         }

         //端点1读数据

         ret
= USBBulkReadData(0,EP1_IN,ReadTestData,EP1_IN_SIZE,500);

         if (ret
!= EP1_IN_SIZE){

                 printf ( "端点1读数据失败!%d\n" ,ret);

                 return

SEVERITY_ERROR;

         } else {

                 printf ( "端点1读数据成功!\n" );

                 for ( int

i=0;i<EP1_IN_SIZE;i++){

                         printf ( "%02X
"
,ReadTestData[i]);

                         if (((i+1)%16)==0){

                                 printf ( "\n" );

                         }

                 }

                 printf ( "\n" );

         }

         Sleep(100);

         //端点2写数据

         ret
= USBBulkWriteData(0,EP2_OUT,WriteTestData+64,64,500);

         if (ret
!= 64){

                 printf ( "端点2写数据失败!%d\n" ,ret);

                 return

SEVERITY_ERROR;

         } else {

                 printf ( "端点2写数据成功!\n" );

         }

         //端点2读数据

         ret
= USBBulkReadData(0,EP2_IN,ReadTestData,64,500);

         if (ret
!= 64){

                 printf ( "端点2读数据失败!%d\n" ,ret);

                 return

SEVERITY_ERROR;

         } else {

                 printf ( "端点2读数据成功!\n" );

                 for ( int

i=0;i<64;i++){

                         printf ( "%02X
"
,ReadTestData[i]);

                         if (((i+1)%16)==0){

                                 printf ( "\n" );

                         }

                 }

                 printf ( "\n" );

         }

         getchar ();

         return

0;

}



到此,整个开发流程基本完成,下面是本套程序的测试图片

串口打印输出
 

PC端测试程序输出
 

Bus Hound抓取到的USB数据
 

转载于:https://www.cnblogs.com/AijunHe/p/7596123.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值