CANoe:第5个仿真工程:仿真+测试

目录

工程背景

工程目的

报文发送情况

工程实现

工程步骤概述

1 测试方法分析

1-1 检测报文周期

1-2 检测报文长度DLC

1-3 功能测试

1-4 检测未定义报文

2 添加Test Module

2-1创建测试环境

 2-2 插入CAPL Test Module

3 CAPL编写测试用例

 3-1 测试模块入口函数MainTest

3-2 CAPL测试用例——检测报文周期

3-3 CAPL测试用例——检测报文长度DLC

3-4 CAPL测试用例——检测未定义报文undefined msg

3-5 CAPL测试用例——功能测试

4 工程运行测试

 5 故障注入

5-1 系统变量控制报文发送

5-2  IG节点发送自定义报文

 6 测试报告


工程背景

本工程主要目的是: 基于第3个仿真工程, 熟悉CANoe的报文测试功能。

工程目的

本工程将围绕CAN总线中的报文,在Test Module中实现测试功能。主要包括:

  1. 检测周期性报文的周期
  2. 检测报文的长度
  3. 检查网络中是否有未定义的报文
  4. 简单的功能测试:通过修改相关系统变量的数值,模拟真实测试环境的操作,最后检验总线上的信号数值的改变。
  5. 生成测试报告

报文发送情况

第3个仿真工程报文发送与接收情况如下:

报文的相关属性整理如下表:

报文中的信号属性如下表:

 

工程实现

工程步骤概述

本章实例基于第12章的仿真工程,为了便于区别,需要将原工程的文件夹复制并 名为Vehicle_System_Simulation_Test, 工程名称也另存为Vehicle_System_CAN_Test.cfg, 在该工程文件夹下创建一个名为Testmodul的文件夹,用于存放相关的测试代码。

接下来,将在此基础上添加测试模块及故障注入面板等。

工程包括以下几个关键步骤:

  1. 测试方法分析
  2. 添加Test Module,
  3. CAPL测试用例编写,
  4. 运行工程,运行测试用例。
  5. 故障注入,运行测试用例。
  6. 查看测试报告。

1 测试方法分析

本工程的目的是分别测试不同报文的发送周期、报文长度DLC;功能测试(检测信号值是否在期望的数值范围内 );未定义报文。

1-1 检测报文周期

检测报文周期的方法是划定某一段特定的时间,选取该时间段中第一条待测报文作为起始时间戳,观察此待测报文后续重复的时间间隔。

TSL函数——检测函数

检测函数1

ChkStart_MsgAbsCycleTimeViolation ( Message aObservedMessage,duration aMinCycleTime, duration aMaxCycleTime)

函数功能:观察总线周期性报文aObservedMessage的每次出现,如果该报文的间隔不符合规范要求,则会触发一个代表异常出现的特殊事件。

返回值:>0,返回一个IDaCheckedId,即观察待测报文的事件;=0报错。

TSL函数——状态报告函数

状态报告函数1long ChKQuery_NumEvents(dword aCheckId) 

函数功能:查询该时间段内异常出现的特殊事件的个数

状态报告函数2double ChkQuery_StatProbeIntervalAvg(dword aCheckId)

返回该时间段内,该报文的平均周期间隔时间

状态报告函数3double ChkQuery_StatProbeIntervalMin(dword aCheckId)

返回该时间段内,该报文的最小周期间隔时间

状态报告函数4 double ChkQuery_StatProbeIntervalMax(dword aCheckId)

返回该时间段内,该报文的最大周期间隔时间

TSL函数——检测控制函数

检测控制函数1 long ChkControl_Destroy(Check aCheckId)

用于测试结束时,销毁该事件对象aCheckId,释放资源。返回0操作成功,<0报错。

1-2 检测报文长度DLC

函数:

  • 状态报告函数 ChkQuery_NumEvents

  • 检测控制函数 ChkControl_Destroy

  • 检测函数:dword ChkStart_InconsistentDLC(Message aMessage,char [] aCallback)

    • 检测发送到总线上指定报文长度是否与DBC数据库中的定义一致

    • aMessage待测报文;char [] aCallback回调函数名,可选参数

    • 返回值:>0:返回一个事件对象aCheckId;=0报错

1-3 功能测试

功能测试用过CAPL程序逻辑来设置某信号的数值,然后使用ChkStart_MsgSignalValueInvalid函数来检测信号值是否在期望的数值范围内。

dword ChkStart_MsgSignalValueInvalid (Signal aObservedSignal,double aMinValue, double aMaxValue, Callback aCallback)

函数参数:待测信号,必须是定在DBC中的信号,最小信号值,最大信号值,回调CAPL函数名,可选。

返回值:返回一个事件对象aCheckId,即检测未定义报文的事件

1-4 检测未定义报文

检测函数:dword ChkStart_UndefinedMessageReceived (char [] CaplCallback)

作用:观察当前总线上是否有未定义的报文

返回值:>0:返回一个事件对象aCheckId,即待观测报文的事件;=0报错。

状态报告函数:long ChkQuery_EventMessageId (dword aCheckId)

作用:返回触发该事件的报文的MessageId

返回值:>0返回触发该事件的报文ID;<0报错。

2 添加Test Module

2-1创建测试环境

创建测试环境,命名为NetworkTester。

 2-2 插入CAPL Test Module

插入CAPL Test Module,并配置此模块Configuration对话框

 配置Module的Name为:Network Tester,在TestModule文件夹下创建CAPL文件NetworkTester.can

3 CAPL编写测试用例

 选中TestModule,右键选中Edit,可以编辑NetworkTester.can 。

 3-1 测试模块入口函数MainTest

CAPL测试模块中can文件要求:-必须包含MainTest函数,所有的测试用例均从此接口进入

  1. 添加TestModule描述:Title,DisCription。
  2. 仿真工程初始化,确保报文的正常发送;本工程中为车门开锁,设置CarDriver,并且钥匙位置设置为2.
  3. 将测试用例分组testGroupBegin,测试用例函数名调用,testGroupEnd。
void MainTest()
{
  testModuleTitle("NetworkTester");
  testModuleDescription("Message Specification Test and Function Test Demo.");
  testGroupBegin("Check msg cycle time","Check the differ mesage cycle time");
    Init_Test_Condition();
    CheckMsgEngineData();
    CheckMsgVehicleData();
    CheckMsgGear_Info();
    CheckMsgIgnition_Info();
    CheckMsgLight_Info();
    testGroupEnd();
  
  testGroupBegin("Check msg DLC","Check DLC of a message");
  CheckDLCLock_Info();
  testGroupEnd();
  
  testGroupBegin("Check undefined msg","Check the undefined message");
  CheckUndefinedMessage();
  testGroupEnd();
  
  testGroupBegin("Fucntion Test","Check the engine speed after setup");
  CheckEngine_Speed();
  testGroupEnd();
  
}

//初始化仿真工程状态,确保各个模块处于Online
Init_Test_Condition()
{
  @Vehicle_Key::Unlock_Car = 1;
  @Vehicle_Key::Car_Driver = 0;
  @Vehicle_Key::Key_State = 2;
  testWaitForTimeout(500);
}

3-2 CAPL测试用例——检测报文周期

分别测试EngineData(50),VehicleData(50),Gear_Info(50),Ignition_Info(50) ,Light_Info(500)的报文周期。

以EngineData为例,CAPL程序逻辑如下:

  1. 先声明检测事件gCycCheckId
  2.  定义常量:
    1. 周期最大最小范围值  :[lCycMinCycleTime,lCycMaxCycleTime] 为 [40,60]
    2. Light_Info的周期范围区间:[Light_MIN_CYCLE_TIME,Light_MAX_CYCLE_TIME] 为 [490,510]。
  3. 写测试用例:  
    1. 定义测试报告提示信息:用testCaseTitle定义测试报告提示信息,以EngineData为例testCaseTitle("TC-1","TC-1:Check cycle time of msg EngineData");
    2.  观测待测报文,周期是否在范围要求内:使用ChkStart_MsgAbsCycleTimeViolation观察待测报文是否在设置的区间范围内。正常返回报文检测事件ID——aCheckedId 。如果aCheckedId大于0代表测试正常运行,如果=0代表测过程有错误。
  4. 检测测试用例结果,输出测试报告后,销毁测试事件
    1.  设定用例测试的时间 KTIMEOUT,在此观测时间段内观测待测报文的周期。
    2. 将测试用例运行结果aCheckedId 作为test条件——testAddCondition,这意味着将在报告中展示测试用例运行结果
    3. 等待测试时间结束,
    4. 观测结束后,统计测试用例中报文的周期平均值、最大值、最小值。
    5. 根据用例运行结果aCheckedId打印报告,如果aCheckedId的数量大于0,即检测报文周期存在不在范围内的情况,则用snprintf+TestStepFail 描述导致错误的测试步骤,测试用例的判决在此自动设置为失败。否则用snprintf+TestStepPass描述测试结果,报告中将显示按预期执行的测试步骤,顺利通过。
  5. 最后销毁检测事件gCycCheckId。

其他报文只需在3测试用例部分,改为相应的报文名称即可。1声明检测事件,4用例检测结果,5销毁检测事件都是通用功能。

具体代码示例如下:

variables
{
  //TC1
  dword gCycCheckId;//声明检测事件的ID
  int gUndefinedMsgCheckResult;//声明未定义报文的检测结果
  const long kMIN_CYCLE_TIME = 40;//一般最小周期时间常量
  const long kMAX_CYCLE_TIME = 60;//一般最大周期时间常量
  const long Light_MIN_CYCLE_TIME = 490;//定义报文Light_Info最小周期时间常量
  const long Light_MAX_CYCLE_TIME = 510;//定义报文Light_Info最大周期时间常量
  const long kTIMEOUT = 4000;//定义测试等待时间常量
  
  //自定义报文——使用IG模块
  
}

//周期时间检测结果函数
CheckMsgCyc(float aCycMinCycleTime, float aCycMaxCycleTime)
{
  long lQueryResultProbeAvg;//声明平均时间
  long lQueryResultProbeMin;//声明最小测量时间
  long lQueryResultProbeMax;//声明最大测量时间
  char lbuffer[100];
  
  testAddCondition(gCycCheckId);//在该函数中添加事件
  testWaitForTimeout(kTIMEOUT);//等待测试时间结束
  //统计平均时间
  lQueryResultProbeAvg = ChkQuery_StatProbeIntervalAvg(gCycCheckId);
  //统计min时间
  lQueryResultProbeMin = ChkQuery_StatProbeIntervalMin(gCycCheckId);
  //统计max时间
  lQueryResultProbeMax = ChkQuery_StatProbeIntervalMax(gCycCheckId);  
  
  if(ChkQuery_NumEvents(gCycCheckId)>0)
  {
    //统计异常次数//打印报告
    snprintf(lbuffer,elCount(lbuffer),"Valid values %.0fms - %.0fms",aCycMinCycleTime,aCycMaxCycleTime);
    testStepFail("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Average cycle time: %dms",lQueryResultProbeAvg);
    testStepFail("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Min cycle time: %dms",lQueryResultProbeMin);
    testStepFail("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Average cycle time: %dms",lQueryResultProbeMax);
    testStepFail("",lbuffer);
  }
  else
  {
    snprintf(lbuffer,elCount(lbuffer),"Valid values %.0fms - %.0fms",aCycMinCycleTime,aCycMaxCycleTime);
    testStepPass("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Average cycle time: %dms",lQueryResultProbeAvg);
    testStepPass("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Min cycle time: %dms",lQueryResultProbeMin);
    testStepPass("",lbuffer);
    snprintf(lbuffer,elCount(lbuffer),"Average cycle time: %dms",lQueryResultProbeMax);
    testStepPass("",lbuffer);
  }
  ChkControl_Destroy(gCycCheckId);//销毁事件
  
}

//TC1:Check Cycle time of msg EngineData
testcase CheckMsgEngineData()
{
  float lCycMinCycleTime;//声明最小周期时间
  float lCycMaxCycleTime;//声明最大周期时间
  lCycMinCycleTime = kMIN_CYCLE_TIME;//赋值
  lCycMaxCycleTime = kMAX_CYCLE_TIME;
  //测试报告提示信息
  testCaseTitle("TC-1","TC-1:Check cycle time of msg EngineData");
  //开始观察待测报文
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(EngineData,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);//周期时间检测结果函数
  testRemoveCondition(gCycCheckId);//移除测试条件
}
//TC-2:Check Cycle time of msg VehicleData
testcase CheckMsgVehicleData()
{
  float lCycMinCycleTime;
  float lCycMaxCycleTime;
  lCycMinCycleTime = kMIN_CYCLE_TIME;
  lCycMaxCycleTime = kMAX_CYCLE_TIME;
  
  
  testCaseTitle("TC-2","TC-2:Check cycle time of msg VehicleData");
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(VehicleData,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);
  testRemoveCondition(gCycCheckId);
}
//TC-3:Check Cycle time of msg  Gear_Info 
testcase CheckMsgGear_Info()
{
  float lCycMinCycleTime;
  float lCycMaxCycleTime;
  lCycMinCycleTime = kMIN_CYCLE_TIME;
  lCycMaxCycleTime = kMAX_CYCLE_TIME;
  
  
  testCaseTitle("TC-3","TC-3:Check cycle time of msg Gear_Info");
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(Gear_Info,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);
  testRemoveCondition(gCycCheckId);
}
//TC-4:Check Cycle time of msg  Ignition_Info 
testcase CheckMsgIgnition_Info()
{
  float lCycMinCycleTime;
  float lCycMaxCycleTime;
  lCycMinCycleTime = kMIN_CYCLE_TIME;
  lCycMaxCycleTime = kMAX_CYCLE_TIME;
  
  testCaseTitle("TC-4","TC-4:Check cycle time of msg Ignition_Info");
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(Ignition_Info,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);
  testRemoveCondition(gCycCheckId);
}
//TC-5:Check Cycle time of msg  Light_Inf
testcase CheckMsgLight_Info()
{
  float lCycMinCycleTime;
  float lCycMaxCycleTime;
  lCycMinCycleTime = kMIN_CYCLE_TIME;
  lCycMaxCycleTime = kMAX_CYCLE_TIME;
  
  testCaseTitle("TC-5","TC-5:Check cycle time of msg Light_Info");
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(Light_Info,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);
  testRemoveCondition(gCycCheckId);
}


3-3 CAPL测试用例——检测报文长度DLC

//TC6:DLC 报文长度测试
testcase CheckDLCLock_Info()
{
  dword checkId;
  //测试报告提示信息
  testCaseTitle("TC-6","TC-6:Check msg DLC of Lock_Info");
 //管事观测报文Lock_Info的DLC
  checkId = ChkStart_InconsistentDlc(Lock_Info);
  testAddCondition(checkId);
  //等待测试时间结束
  testWaitForTimeout(kTIMEOUT);
  testRemoveCondition(checkId);
}

3-4 CAPL测试用例——检测未定义报文undefined msg

//TC-7:检测未定义信号
testcase CheckUndefinedMessage()
{
  long lEventUndefineMessageId;//声明未定义报文Id
  char lbuffer[100];
  
  gUndefinedMsgCheckResult = 0;//?初始化未定义报文数量为0
  testCaseTitle("TC-7","TC-7:Check CAN channel for undefined message");
  //开始观测当前总线
  gCycCheckId = ChkStart_UndefinedMessageReceived("UndefinedMsgCallback");
  //延时,即测量该时间段
  testWaitForTimeout(kTIMEOUT);
  
  switch(gUndefinedMsgCheckResult)
  {
    case 1:
      write("undefined message detected!");
      //获取未定义报文ID
      lEventUndefineMessageId = ChkQuery_EventMessageId(gCycCheckId);
      snprintf(lbuffer,elCount(lbuffer),"Undefined message detected: Id 0x%x",lEventUndefineMessageId);
      testStepFail("",lbuffer);
      break;
    default:
      write("Iamdefault");
      testStepPass("","No undefined message detected!");
      break;   
  }
  ChkControl_Destroy(gCycCheckId);//销毁事件
}

UndefinedMsgCallback(dword aCheckId)
{
  //回调函数,检测到未定义报文时调用
  write("Test: undefined message finded");
  ChkQuery_EventStatusToWrite(aCheckId);
  gUndefinedMsgCheckResult=1;//将未定义报文个数置为1
}

3-5 CAPL测试用例——功能测试

testcase CheckEngine_Speed()
{
  dword checkId;
  testCaseTitle("TC-8","TC-8:Check Engine Speed Value");
  @Vehicle_Key::Unlock_Car = 1;
  @Vehicle_Key::Car_Driver = 0;
  @Vehicle_Key::Key_State = 2;
  @Vehicle_Control::Eng_Speed = 2000;
  
  //开始观测,信号值是否在范围内
  checkId = ChkStart_MsgSignalValueInvalid(EngineData::EngSpeed,1900,2100);
  testWaitForTimeout(kTIMEOUT);
  if(ChkQuery_EventSignalValue(checkId))
  {
    testStepPass("","Correct Engine Speed Value");
  }
  else
  {
    testStepFail("","Incorrect Engine Speed Value");
  }
}

4 工程运行测试

运行工程后,运行测试。

测试用例运行结果如图所示,Light_Info的报文周期检测报错。

——查看CANdb,发现只有报文Light_Info的报文周期为500,代码中使用的检测范围为40-60,将周期范围更改为Light_MIN_CYCLE_TIME和Light_MAX_CYCLE_TIME,如下示例:

//TC-5:Check Cycle time of msg  Light_Info
testcase CheckMsgLight_Info()
{
  float lCycMinCycleTime;
  float lCycMaxCycleTime;
  //lCycMinCycleTime = kMIN_CYCLE_TIME;//kMIN_CYCLE_TIME=40==>Light_MIN_CYCLE_TIME=490
  //lCycMaxCycleTime = kMAX_CYCLE_TIME;//kMAX_CYCLE_TIME=60==>Light_MAX_CYCLE_TIME=510
  lCycMinCycleTime = Light_MIN_CYCLE_TIME;
  lCycMaxCycleTime = Light_MAX_CYCLE_TIME;
  
  testCaseTitle("TC-5","TC-5:Check cycle time of msg Light_Info");
  gCycCheckId = ChkStart_MsgAbsCycleTimeViolation(Light_Info,lCycMinCycleTime,lCycMaxCycleTime);
  CheckMsgCyc(lCycMinCycleTime,lCycMaxCycleTime);
  testRemoveCondition(gCycCheckId);
}

再次运行测试用例,全部通过。

 5 故障注入

为了验证测试用例的正确性,可以使用多种故障注入方法实现故障注入,常见的有:使用故障注入函数,采用网络节点CAPL编程,以及使用IG节点。

书中制作了一个面板: Msg_Switch和Custom_Msg,分别控制报文的发送和关闭,以及发送自定义报文。

 我自己尝试使用Panel模块实现这个面板:创建系统变量,将面板复选框关联系统变量,再在CAPL编程中读取系统变量,根据变量值控制对应的报文函数。

但最后,使用Panel只实现了Msg_Switch部分。Custom_Msg没有找到对应的未定义报文创建方法,有实现的小伙伴欢迎分享。最后使用IG节点实现了未定义报文的发送。

5-1 系统变量控制报文发送

创建系统变量,将面板复选框关联系统变量,再在CAPL编程中读取系统变量,根据变量值控制对应的报文函数。

测试用例涉及7个报文,因而创建7个系统变量创建如下:

 以Gateway_EngineData_off为例,系统变量创建过程如下。

先建立一个Value Table共同使用。因为7个系统变量具有相同的数值解释:勾选为1代表停止报文发送,不勾选为0代表报文正常发送。使用相同的ValueTable可以统一管理。

 Panel创建过程如下:

先添加一个Panel面板,命名为NetworkTest.

添加复选框组件,修改名称并,关联对应的系统变量,下图示例为EngineData,其余报文控制根据名称一一对应即可。

 Panel创建完毕后,在NetworkTest.can中添加变量监听事件,控制相应的报文发送。

以EngineData为例,CAPL编程如下:

on sysvar_update TestSysVar::Gateway_EngineData_off
{
  if (@this==1)
  {
    testDisableMsg(EngineData);
    write("Test: disable EngineData");
    //testDisableMsg(Cluster_Info);
   // ILDisableMsg("Cluster_Info");
  }
  else{
    testEnableMsg(EngineData);
    write("Test: enable EngineData");
  }
}

控制报文发送和终止的函数不止有testDisableMsg和testEnableMsg,其它函数可以参考此文(完善后放链接)。

5-2  IG节点发送自定义报文

IG节点可分为 CAN IG和IG,区别是CANIG只支持CAN报文,而IG可支持CAN、LIN、MOST等其他报文。此外还有IG和PDU IG的区别,PDU IG可以支持任意网络协议,包括CAN以及Ethernet 、FlexRay。

 5-2-1 创建IG节点

在Simulation Setup总线上创建CAN IG 模块,CAN IG模块允许用户发送自定义的CAN报文。

5-2-2 添加自定义报文,并配置

 如上图添加了3个自定义报文Msg_01,Msg_02,Msg_03,并按下图定义相关属性

 5-2-3 运行

保存后,启动工程,启动测试用例,发送自定义报文。

测试结果显示检测到了未定义报文。

 6 测试报告

测试报告有2种格式:①CANoe TestReport Viewer(推荐)②XML/HTML格式(以前的)

可如下图所示,修改为自己所需的格式:

 测试报告的打开位置如下

①CANoe TestReport Viewer(推荐)

 ②XML/HTML格式(以前的)

 

END 

  • 20
    点赞
  • 198
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 18
    评论
Canoe是一种用于仿真测试的开源工具,它可以用来测试SomeIP(Scalable Service-Oriented Middlewar)协议。SomeIP是一种用于汽车电子领域的通信协议,它允许不同的设备和应用程序在车辆内部进行通信。 使用Canoe进行SomeIP协议仿真测试,可以模拟车辆内不同设备之间的通信行为,以确保其在实际车辆运行中的可靠性和稳定性。通过模拟各种情况和场景,如不同设备之间的消息传递、服务调用和响应等,可以检验SomeIP协议在实际应用中是否能够正确地处理这些通信行为。 Canoe提供了一组图形化的工具和界面,可以方便地创建、编辑和管理SomeIP协议的模型和配置。用户可以定义不同的设备、服务和消息,并指定它们之间的关联关系和交互行为。通过这些配置,Canoe可以模拟出具体的场景和事件,以评估SomeIP协议在不同条件下的性能和稳定性。 Canoe还提供了一系列的分析和监控工具,用于跟踪和记录模拟测试中的通信数据和消息。这些工具可以帮助用户分析和评估SomeIP协议的性能,发现潜在的问题和瓶颈,并提供相应的优化和改进建议。 总之,Canoe是一种用于仿真测试SomeIP协议的有效工具。通过使用它,可以对SomeIP协议进行全面的测试和评估,以确保它在实际车辆应用中的可靠性和稳定性。同时,Canoe还提供了丰富的分析工具,帮助用户了解和优化SomeIP协议的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

picoasis

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

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

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

打赏作者

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

抵扣说明:

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

余额充值