UDS诊断服务开发

本文深入探讨了UDS(统一诊断服务)的网络层和诊断服务层,包括ISO标准、帧类型、数据传输、定时参数、错误处理以及常见服务的实现。网络层涉及CAN总线传输规范,如单帧、多帧交互;诊断服务层涵盖各种服务,如会话控制、ECU复位、读取数据等,并解析响应和错误码。此外,还讨论了不同寻址方式对服务的影响。

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

解封后的环京地区还要办理通行出入证才能自由,据说在中国领土内需要出入证的有:香港、台湾、澳门以及河北三河~


一、标准文件

ISO 14229 定义的是诊断服务。

ISO 15765 定义的是诊断服务在总线上的传输方式。

ISO 11898 定义的CAN总线在物理层面传输的规范。

下图摘抄至ISO 14229 -1:

我们只考虑基于CAN总线的UDS,所以只需学习标准文件:ISO 15765-2,ISO14229-1、2、3即可,ISO 11898是硬件物理层传输面的,可不必理会。另外ISO 15765-3被ISO14229-3取代了。

二、UDS网络层

        主要参考ISO 15765-2,定义了CAN总线传输的数据结构,以及多帧传输的方法,并对帧传输规约了时间参数。

1、协议数据单元

     共有四种帧类型:

    单帧:

就是传统的8字节CAN报文,此基础上增加了数据长度SF_DL,占一个字节的前4位。

    第一帧:

第一帧用于数据长度大于7字节的诊断数据,它和一个或多个连续帧并用,其中FF_DL表示这套多帧的数据长度,12 位 FF_DL 理论上支持发送多达 4096 个数据字节。

   连续帧:

如果是多帧数据,先发第一帧,然后是连续帧,其中SN是帧计数,初始为1(因为有第一帧),累加达到15后,置0.

     流控:

第一帧或数据块的最后一个连续帧之后,需要更多的连续帧(CF)来完成数据流传输时,接收节点发送一个流控。一个流控帧包含三个参数:流控状态(FS),持续发送次数(BS),最小间隔时间(STmin).

 多帧的交互机制:

2、网络层定时计数参数

ISO 15765-2 对网络层数据的传输时间进行约束,实际交互时间可以在超时范围内自定义,一些企业的企标会对此有详细要求。

 

 3、代码实现

首先是UDS网络层的相关定义,帧类型、时间参数等。

typedef struct
{
	 uint16 N_As;
	 uint16 N_Ar;
	 uint16 N_Bs;
	 uint16 N_Br;
	 uint16 N_Cs;
 	 uint16 N_Cr;   
	 uint16 STmin;
	 uint16 BS;
}TimePeriodParam;


/*--流控帧第一个字节的低4位为FS,有3种状态*/
typedef enum{
	CTS,	   //continue to send
	WT,		   //wait
	OVFLW,	   //overflow
}FS_Type;

typedef enum{
	SF ,	//single frame
	FF ,	//first frame
	CF ,	//consecutive frame
	FC ,	//flow control
}N_PCIType;


typedef enum{
	N_OK,           //sender and receiver
	N_TIMEOUT_A,    //sender and receiver
	N_TIMEOUT_Bs,   //sender only
	N_TIMEOUT_Cr,   //receiver only
	N_WRONG_SN,     //receiver only
	N_INVALID_FS,   //sneder only
	N_UNEXP_PDU,    //receiver only
	N_WFT_OVRN,     
	N_BUFFER_OVFLW,  //sender only
	N_ERROR,         //sender and receiver
}N_Result;

typedef enum
{
	NWL_IDLE,
	NWL_TRANSMITTING,
	NWL_RECIVING,
	NWL_WAIT,
}NWL_Status;

/**--------------------------------------------*/
typedef enum{
	PHYSICAL,
	FUNCTIONAL,
}N_TAtype;

typedef enum{
	CONFIRM,      
	FF_INDICATION,
	INDICATION,    
}NotificationType;

typedef enum{
	RX_IDLE,
	RX_WAIT_FC_REQ,
	RX_WAIT_FC_CONF,
	RX_WAIT_CF,
}RecivingStep;


typedef enum{
	TX_IDLE,
	TX_WAIT_FF_CONF,
	TX_WAIT_FC,
	TX_WAIT_CF_REQ,
	TX_WAIT_CF_CONF,
}TransmissionStep;

 时间参数要有初始化,因为这个时间有可能根据企业要求自定义,可根据实际要求配置

static TimePeriodParam m_TimePeriod;
static NWL_Status m_NetworkStatus = NWL_IDLE;
static TransmissionStep m_TxStep;
static RecivingStep m_RxStep;

void UDS_NetWork_Init(void)
{    
    m_TimePeriod.N_As = 70;                        
    m_TimePeriod.N_Ar = 70;                        
	m_TimePeriod.N_Br = 50;                       
	m_TimePeriod.N_Bs = 150;                       
	m_TimePeriod.N_Cs = 50;                        
	m_TimePeriod.N_Cr = 150;                       
	 
	RxParam.STmin = 0;                             
    RxParam.BlockSize = 0;                        

   m_NetworkStatus = NWL_IDLE;
   m_TxStep = TX_IDLE;   
}

网络层提供诊断层的发送接口,通过数据长度判断是单帧还是多帧,如果是单帧的话,发送成功后要恢复初始状态,如果是多帧,发送完首帧FF之后,在NetworkLayer_TxStart()里启动定时参数N_As和N_Bs,在规定时间内如果收到流控帧,则继续发送多帧CF。

void N_USData_request(uint8* MessageData, uint16 Length)         
{

      if((Length == 0)||(Length > UDS_FF_DL_MAX)) 
      {
         //长度错误
      }
   
      if(Length <= 7)   //SF length must <= 7
		  {
			   NetworkLayer_SendSF((uint8)Length,MessageData);
			   NetworkLayer_TxEnd();
		  }
		  else
		  {
			   NetworkLayer_SendFF(Length,MessageData);
			   NetworkLayer_TxStart();
		  }
}

网络层的数据接收接口,首先判断通信模式是功能还是物理,所谓物理是1对1通信,支持所有类型,功能地址是1对多通讯的,相当于广播,仅支持单帧,并且报文ID可能是特定的。

void NetworkLayer_RxProcess(void) 
{
   N_PCIType PciType;
 
   if(UDS_NetWork_CanRxBuf[RxOutIndex].id ==FunctionID)
   {
      m_N_TAtype = FUNCTIONAL;
   } 
   else if(UDS_NetWork_CanRxBuf[RxOutIndex].id ==UDS_DownLoadID)
   {
      m_N_TAtype = PHYSICAL;
   }
   
   if(!IsRxBuffEmpty())    
   {
      PciType = (UDS_NetWork_CanRxBuf[RxOutIndex].data[0] >> 4) & 0x0F;
      switch (PciType)
	    {
		      case SF:
            	NetworkLayer_RxSF(UDS_NetWork_CanRxBuf[RxOutIndex]);
            	break;
            		
          case FF:
              if(m_N_TAtype == PHYSICAL)
              { 
              	 NetworkLayer_RxFF(UDS_NetWork_CanRxBuf[RxOutIndex]);
              } 
              else
              {
                  //ignore functional
              }
            	break;
            		
          case CF:
            	NetworkLayer_RxCF(UDS_NetWork_CanRxBuf[RxOutIndex]);
            	break;	
          case FC:
              if(m_N_TAtype == PHYSICAL)
              { 
            	   NetworkLayer_RxFC(UDS_NetWork_CanRxBuf[RxOutIndex]);
              }
              else
              {
                  //ignore functional
              }
            	break;
            	
          default:
          	  m_N_Result = N_UNEXP_PDU;
		        	break;  	      
      }
        
      (RxOutIndex == MAX_BUFF_NUMBER - 1) ? (RxOutIndex = 0) : (RxOutIndex++);
	     
   }
}

接收完之后还要通知上层诊断层当前帧的服务项,通过NotificationType定义的,见上文的相关定义,因为没有涉及到发送,所以缺少了ISO15765里的request项,另外每次接收数据都要重启定时参数,做超时判断。

三、UDS诊断服务层

1、诊断时间参数

诊断服务层同样也有一些超时判断用的参数,这些在IOS14229-2中有定义:

当时我写的时候这部分定时并没有启动~~!

2、诊断报文格式

报文格式有请求和响应两种:

请求通常是从诊断仪到ECU,对于ECU来说,就是接收解析请求报文,即诊断服务。

有带子服务的请求和不带子服务请求两种,没有子服务项的报文第二字节就是数据。

  响应则从ECU到诊断仪,对于ECU来说,就是发送,有正响应和负响应。

正响应Positive Response帧格式:
(SID+0x40) + Data Parameter
(SID+0x40) + Sub-Function + Data  Parameter

负响应Negative Response帧格式:
   0x7F + SID + NRC

其他空数据一般添0x00。

负响应的NRC码在ISO14229-1最后的附表里,不同的诊断服务对应若干不同的负响应NRC,即一个诊断服务对应的几种异常情况,实际应用不一定全部会用到。常用的NRC有:

typedef enum{
	PR = 0x00,//positive response   
	GR = 0x10,//general reject
	SNS = 0x11,//service not supported    企标
	SFNS = 0x12,//sub-function not supported  企标
	IMLOIF = 0x13,//incorrect message length or invalid format  企标
	RTL = 0x14,//response too long
	BRR = 0x21,//busy repeat request   企标
	CNC = 0x22,//condifitons not correct  企标
	RSE = 0x24,//request sequence error    企标
	NRFSC = 0x25,
	FPEORA = 0x26,
	ROOR = 0x31,//reqeust out of range    企标
	SAD = 0x33,//security access denied   企标
	IK = 0x35,//invalid key       企标
	ENOA = 0x36,//exceed number of attempts    企标
	RTDNE = 0x37,//required time delay not expired   企标
	UDNA = 0x70,//upload download not accepted  企标
	TDS = 0x71,//transfer data suspended      企标
	GPF = 0x72,//general programming failure   企标
	WBSC = 0x73,//wrong block sequence coutner  企标
	RCRRP = 0x78,//request correctly received-respone pending   企标
	SFNSIAS = 0x7e,//sub-function not supported in active session   企标
	SNSIAS  = 0x7F,//service not supported in active session   企标
	VTH = 0x92,//voltage too high    企标
	VTL = 0x93,//voltage too low    企标
}NegativeResposeCode;

 带子服务的请求和不带子服务请求均支持功能寻址物理寻址,其中带子服务的请求中包含“抑制正响应报文指示位”,此位为真时,不回复正响应,负响应正常回复,但一些NRC下,不论抑制正响应报文指示位是真是假,负响应也不回复,例如:SNS、SFNS等。

3、诊断服务内容

 除了这些SID外,UDS还通过Sub-function来补充SID的意图,而sub-function严格来说是7个bit,因为它的最高位bit被“抑制正响应报文指示位”占用。

常用的服务项有:

typedef enum{
	SESSION_CONTROL = 0x10,
	RESET_ECU = 0x11,
	SECURITY_ACCESS = 0x27,
	COMMUNICATION_CONTROL = 0x28,
	TESTER_PRESENT = 0x3E,
	GET_TIME_PARAM = 0x83,
	SECURITY_DATA_TRANSMISSION = 0x84,
	CONTROL_DTC_SETTING = 0x85,
	RESPONSE_ON_EVENT = 0x86,
	LINK_CONTROL = 0x87,
	READ_DATA_BY_ID = 0x22,
	READ_MEMORY_BY_ADDRESS = 0x23,
	READ_SCALING_DATA_BY_ID = 0x24,
	READ_DATA_PERIOD_ID = 0x2A,
	DYNAMICALLY_DEFINE_DATA_ID = 0x2C,
	WRITE_DATA_BY_ID = 0x2E,
	WRITE_MEMORY_BY_ADDRESS = 0x3D,
	CLEAR_DTC_INFO = 0x14,
	READ_DTC_INFO = 0X19,
	IO_CONTROL_BY_ID = 0x2F,
	ROUTINE_CONTROL = 0x31,
	REQUEST_DOWNLOAD = 0x34,
	REQUEST_UPLOAD = 0x35,
	TRANSMIT_DATA = 0x36,
	REQUEST_TRANSFER_EXIT = 0x37,
}ServiceName;

4、代码实现

网络层接收的数据经解析后,通知服务层,服务层解析出相应的SID,执行对应操作及回复正负响应。

void Diagnostic_RxProcess(void)
{
    NetworkNotification temp; 
    NetworkLayer_RxProcess();
    temp = PullIndication();
       if(temp.NoticeType == INDICATION)   
	     {
	          
	         if((temp.n_Resut == N_OK)||(temp.n_Resut == N_UNEXP_PDU))
			     {
			     
			         Diagnostic_ServiceHandle(temp.length,temp.MessageData);
			     }			 
			 
	     }
       else if(temp.NoticeType == CONFIRM) 
        {
            // temporary NO
        }
       else if(temp.NoticeType == FF_INDICATION)
	     {
			//NO
	     }

}
void Diagnostic_ServiceHandle(uint16 length,uint8 *tMsg)
{
   switch(tMsg[0])
   {
       case SESSION_CONTROL:    //10服务
            Service10Handle(length, tMsg);   
            break;
                    
       case RESET_ECU:         //11服务
            Service11Handle(length, tMsg);
            break;
            
       case CLEAR_DTC_INFO:     //14服务
            if(m_CurrSessionType ==ECU_PAOGRAM_SESSION)
            {
                 UDS_req_negative_responses(CLEAR_DTC_INFO, SNSIAS);
            }  //编程会话下不支持
            else
            {
              //Service14Handle(length, tMsg);
            }  
            break;
            
       case READ_DATA_BY_ID:     //22
       
            break;
            
       case SECURITY_ACCESS:       //27
            if(AccessErrCount >= 3)
            {
                UDS_req_negative_responses(SECURITY_ACCESS, ENOA);
              //  AccessErrCount = 0;
                AccessTimerEn=1;
            }
            if((m_CurrSessionType ==ECU_DEFAULT_SESSION)||(m_N_TAtype == FUNCTIONAL))
            {
                 UDS_req_negative_responses(SECURITY_ACCESS, SFNS);
                 
            }  //默认会话下不支持  功能寻址不支持
            else
            {
               Service27Handle(length, tMsg);
            } 
            
            break;
            
       case COMMUNICATION_CONTROL:     //28
            break;
            
       case WRITE_DATA_BY_ID:     //2E
               Service2EHandle(length, tMsg); 
            break;
            
       case ROUTINE_CONTROL:     //31            
               Service31Handle(length, tMsg);
            break;
            
       case REQUEST_DOWNLOAD:                      //34
               Service34Handle(length, tMsg);
            break;
                 
       case REQUEST_UPLOAD:     //35
            break;
             
       case TRANSMIT_DATA:     //36
            Service36Handle(length, tMsg);
            break; 
            
       case REQUEST_TRANSFER_EXIT:     //37
       
            Service37Handle(length, tMsg);
            break;
             
       case TESTER_PRESENT:     //3e
            break; 
       case CONTROL_DTC_SETTING:     //85
            break;      
       default:          	  
		        break;       
   }

}

5、常用的诊断服务说明

不是所有的服务都支持两种寻址方式。

诊断会话控制-10服务

其他诊断服务实现时必定要经过10服务进行会话模式的切换,可能会用到的有4种:

typedef enum{
    ECU_DEFAULT_SESSION = 1,    
    ECU_PAOGRAM_SESSION = 2,
    ECU_EXTENED_SESSION = 3,
    USER_DEFINED_SESSION = 0xXX,
}SessionType;

除默认会话外,其他会话都要有时间限制,计时到点后,非默认会话要退出。

 从默认会话到默认会话(状态1):重新进入默认会话时,ECU执行完全初始化。

从默认会话到非默认会话(状态2):基本不动。
从非默认会话到非默认会话(状态3):安全状态切换到锁定状态,通讯控制不受影响。
从非默认会话到默认会话(状态4):ECU执行完全初始化,复位所有在非默认模式下做的事。

ECU复位-11服务

就是对ECU进行复位操作,不过需要注意的是复位之前一定要先发送正响应。

清除诊断信息-14服务

正响应应在诊断信息清除请求完全处理发出,即使ECU没有存储的DTC,同样需要发出正响应
UDS规定FFFFFF清除所有DTC。

读取DTC故障码-19服务

 通过SubFunction定义19服务不同的请求类型,服务请求第三个字节是DTC状态位,有8个状态:

 ECU响应的每个DTC都会有一个对应的DTC状态字节。

关于DTC码在另一个标准文件ISO 15031-6里有定义,一般企业会给出定义好的DTC码。

读取数据-22服务

通过标识符读取数据,22服务与2E成对使用 前者是读,后者是写 。                     
UDS规定,诊断数据使用两个byte的标识符来标记,比如,0xF187用来标记ECU的零件号。

通过地址读取内存-23服务

通过地址读取内存,23服务与3D成对使用                       
如果这条命令的格式是 23 22 xx yy aa bb,则它的含义就是,读取xx yy地址的长度为aa bb的数据。
22的前4位表示地址长度,后4位表示数据长度。

安全访问-27服务

1、诊断仪向ECU请求“Seed”(通常是一个与时间相关的伪随机数),
2、ECU向诊断仪发送“Seed”,
3、诊断仪向ECU发送“Key” (根据请求得到的Seed和一个本地的密码进行计算得来)
4、ECU判断诊断仪发来的“Key”是否有效

一般企业会提供校验Key的算法,同时访问错误累积3次后,规定时间内不允许继续解锁访问。

访问有不同的安全级别,不同的安全级别下,访问范围也是有区别的。

通信控制-28服务

用于打开/关闭某些类别的报文的发送/接收,例如某些时刻不需要多余的通信则通过此服务关闭。

切换到默认会话时,通信控制恢复正常。

程序控制 -31服务

31服务中的SubFunction表示ECU要执行的动作,如某外设的启动,停止、FLASH的擦除等。boot loader用到此服务。

请求下载 -34服务

告知ECU准备接受数据,ECU应答自己的接收能力等信息,此服务用于BOOT下载。

请求下载报文带有帧计数,每次+1。

传输数据  36服务

boot loader用到此服务,接收到的数据写入FLASH。

请求传输退出-37服务

说明数据传输完毕,在本服务里,我置位了一个BOOTLOADER_SUCCEED的标志,表示APP程序成功下载。

诊断仪在线 -3E服务 

用于使ECU保持在当前session,永远只有2个字节,3E 80(00) ,80时不需要应答。

控制DTC设置  85服务

用于开启或关闭ECU内部的诊断故障码更新功能。0x01/0x81:开  0x02/0x82:关


学而时习之,不易忘。

<think>嗯,用户问的是UDS诊断脚本开发,我需要先回忆一下UDS的基本概念。UDS是Unified Diagnostic Services,统一诊断服务,属于ISO 14229标准,用于汽车电子系统的诊断诊断脚本开发通常用于自动化测试ECU,比如刷写、读取故障码等。 用户可能想知道如何开始开发这样的脚本,可能需要了解工具、协议细节、常用命令等。首先,应该介绍UDS的基本框架,比如服务类型,像0x10会话控制,0x22读数据,0x2E写数据这些。然后需要提到开发所需的工具,比如CANoe、CANalyzer或者开源的Python库如python-can、udsoncan。 接下来,脚本开发的基本步骤可能包括连接ECU、发送请求、处理响应。要详细说明每个步骤需要做什么,比如建立通信可能需要配置CAN总线参数,发送请求需要构造符合UDS格式的数据帧,包括服务ID、子功能、数据参数等。 还需要考虑不同服务的格式,比如会话控制的例子,0x10后面跟子功能,比如01是默认会话,02是编程会话。要给出具体的例子,比如发送的请求数据,预期的响应,以及如何处理可能的否定响应,比如0x7F表示错误,后面跟着服务ID和错误码。 另外,可能需要注意的事项,比如超时处理、重试机制、安全访问(0x27服务)的流程,需要种子和密钥的计算。这部分可能需要更详细的步骤,比如如何请求种子,如何用算法生成密钥,再发送密钥解锁。 用户可能还需要了解如何验证脚本的正确性,比如使用仿真工具或者真实ECU测试,日志记录和分析。此外,标准化方面,比如ODX文件的使用,或者CDD文件的解析,可能也是开发中的一部分。 需要确保回答结构清晰,分步骤说明,同时给出代码示例,比如Python中使用udsoncan库的片段,这样用户更容易理解。还要提醒常见的陷阱,比如字节顺序、数据对齐、错误处理不够完善等。 最后,总结学习资源,比如推荐标准文档、书籍或在线教程,帮助用户进一步学习。可能还要提到实际项目中的协作,比如与汽车工程师的沟通,理解具体需求等。</think>UDS(Unified Diagnostic Services)诊断脚本开发是汽车电子领域实现ECU(电子控制单元)自动化测试与诊断的核心技术。以下从原理到实践逐步解析开发要点: --- ### 一、UDS协议基础 1. **核心标准** - 基于ISO 14229-1定义的服务层协议 - 物理层依赖CAN(ISO 11898)、DoIP(ISO 13400)等 2. **关键服务类型** | 服务ID | 名称 | 功能示例 | |--------|---------------------|-----------------------------| | 0x10 | 会话控制 | 切换默认/编程会话 | | 0x22 | 读数据 | 读取故障码、软件版本 | | 0x2E | 写数据 | 配置参数、写入校准数据 | | 0x27 | 安全访问 | 解锁ECU写权限(种子-密钥) | | 0x31 | 例程控制 | 触发自检、复位操作 | --- ### 二、开发工具链 1. **硬件依赖** - CAN卡(如Vector VN1630、PCAN-USB) - 车载网络仿真工具(可选) 2. **软件库** - **Python**:`udsoncan`、`python-can` - **C++**:Vector CAPL脚本 - **商业工具**:CANoe(.NET/CAPL)、ETAS INCA --- ### 三、脚本开发步骤 #### 阶段1:通信初始化 ```python from udsoncan.connections import PythonIsoTpConnection from udsoncan.client import Client conn = PythonIsoTpConnection(interface='can0', rxid=0x7E8, txid=0x7E0) client = Client(conn, request_timeout=2) client.tester_present = True # 维持会话 ``` #### 阶段2:服务请求构造 **示例:读取故障码(DTC)** ```python response = client.read_data_by_identifier(0xF190) # 0xF190为DTC标识符 if not response.positive: print(f"错误码: {hex(response.code)}") else: dtc_list = response.data # 解析为形如['P0100', 'U0123']的列表 ``` #### 阶段3:安全访问流程 ```python # 步骤1:请求种子 seed_resp = client.request_seed(level=1) seed = seed_resp.data # 步骤2:生成密钥(需实现厂商算法) key = custom_algorithm(seed) # 步骤3:发送密钥 client.send_key(level=1, key=key) ``` --- ### 四、关键难点与对策 1. **时序控制** - 添加重试机制(如3次重试) - 会话超时检测(默认约2-5秒) 2. **数据解析** ```python # 解析多字节数据(如里程值) import struct odometer = struct.unpack('>I', bytes.fromhex('12 34 56 78'))[0] # 大端序解析 ``` 3. **错误处理** ```python try: client.download_file(file_path, memory_address=0x08000000) except NegativeResponseError as e: print(f"下载失败: {e.response.service_name} - {hex(e.response.code)}") ``` --- ### 五、验证与调试 1. **仿真测试** 使用CANoe等工具模拟ECU响应 2. **日志分析** - 记录原始报文(ASCII或HEX格式) - 使用Wireshark过滤UDS帧(筛选`iso15765`协议) --- ### 六、进阶方向 1. **ODX/CDD集成** 解析标准化诊断数据库(ODX 2.2/ASAM MCD-2D) 2. **自动化测试框架** - 结合Robot Framework实现用例管理 - 生成A2L文件兼容性报告 --- ### 七、学习资源 - **标准文档**:ISO 14229-1(2020版) - **书籍推荐**:《汽车诊断协议UDS详解》(李想 著) - **实战工具**:GitHub开源项目`udsoncan`示例库 通过以上步骤,可逐步构建支持刷写、故障注入、参数标定等场景的完整诊断脚本系统。实际开发需紧密结合目标ECU的特定需求与厂商规范。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记性不好的JJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值