CANoe入门——8、CAPL基础(4)_使用CAPL发送报文

前文已经讲解了CAPL的一些基础语法,但是CAPL毕竟是Vector为了总线分析和测试推出的编程语
言,我们主要还是运用在CAN、LIN及其他通信总线上,本篇文章就开始讲解如何使用CAPL控制
CANoe发送CAN、LIN报文。

目录

一、没有DBC文件,使用CAPL发送CAN报文

1、定义CAN报文变量

2、使用output函数发送一帧CAN报文

3、修改CAN报文的DLC

4、修改CAN报文的Data数据

5、多次发送CAN报文

6、利用定时器自动发送CAN报文

7、关闭定时器停止CAN报文的自动发送

二、有DBC文件,发送CAN报文

1、关闭CANoeIL层自动发送

2、定义CAN报文变量

3、发送报文,修改报文中信号的值。

4、报文定时自动发送、修改报文DLC,关闭定时发送等。

三、通过CAPL发送LDF中已定义的报文

1、定义LIN报文帧变量

2、发送LIN报文帧

3、修改LIN报文的Data值,并发送到总线上

4、切换LIN调度表

四、没有LDF情况下发送LIN报文,或者发送不存在于LDF中的LIN报文

1、定义LIN报文帧

2、发送LIN报文

3、修改自定义的LIN报文的信号(字节)值

4、定时发送自定义的LIN报文


一、没有DBC文件,使用CAPL发送CAN报文
1、定义CAN报文变量

首先,我们想要发送CAN报文,需要先在CAPL中定义一个CAN报文帧变量

可以在全局变量定义区Variables{ }里定义,也可以在需要用到的事件、函数内部定义成局部变量,这里我以在全局变量定义区内定义为例

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg;//由于没有DBC,只能使用以ID形式声明CAN报文
}
2、使用output函数发送一帧CAN报文

现在我们就有了这个CAN报文帧变量了,随后我们在CANoe工程启动事件中把这帧报文通过CAPL发送出去

on start
{
  output(Test0x123Msg);
}

启动之后,打开Trace窗口,可以看到窗口中出现了一帧ID为123的CAN报文。

但是此时这帧CAN报文的Data中没有数据,这是由于DLC(Data Length Code)是0导致的,我们需要修改这个CAN报文的DLC,这个报文才会有数据。

3、修改CAN报文的DLC

修改定义的DLC有两种办法,一种是在定义时就直接给予一个DLC的长度,像这样在定义时的名称后面使用{ }并在其中写上dlc = x,即可定义dlc。

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg = {dlc=8};//由于没有DBC,只能使用以ID形式声明CAN报文
}

随后我们再次启动工程,查看一下Trace窗口,可以看到,设置了DLC之后,这帧报文的Data部分就有了8字节的数据。

第二种设置dlc的方法,是在使用时动态修改dlc

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg;//由于没有DBC,只能使用以ID形式声明CAN报文
}

on start
{
  Test0x123Msg.dlc = 7;//使用报文变量名称+.dlc = x;的方式来动态设置dlc
  output(Test0x123Msg);
}

随后我们再次启动工程,可以看到报文的dlc已经变为了7,且数据只有7字节。

4、修改CAN报文的Data数据

现在,我们已经知道了如何修改CAN报文的dlc,但是这个CAN报文的Data还是全0的数据,我们的CAN报文在大多数情况下都不会是全0的值,我们需要掌握如何修改CAN报文数据的方法

对于没有DBC的CAN报文,或者DBC中未定义的CAN报文,修改Data需要采取对字节赋值的方法。

像这样

on start
{
  Test0x123Msg.dlc = 7;
  Test0x123Msg.byte(0) = 3;//为Test0x123Msg这帧CAN报文的第0个字节(即我们口语中的第一个,计算机语言是从0开始)赋值为3
  Test0x123Msg.byte(1) = 6;//第1个字节赋值为6
  Test0x123Msg.byte(2) = 9;//第2个字节赋值为9
  output(Test0x123Msg);
}

 

随后我们再次启动工程,打开Trace窗口查看。

5、多次发送CAN报文

上面的演示,是将发送CAN报文的代码放在了启动事件on start中,然而这个事件是只能执行一次的,有时候我们需要多次发送这个CAN报文,所以我们需要使用其他的事件来进行报文的发送

这里使用键盘事件来进行发送

像这样

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg;//由于没有DBC,只能使用以ID形式声明CAN报文
}

on start
{
  Test0x123Msg.dlc = 7;
  Test0x123Msg.byte(0) = 3;//为Test0x123Msg这帧CAN报文的第0个字节(即我们口语中的第一个,计算机语言是从0开始)赋值为3
  Test0x123Msg.byte(1) = 6;//第1个字节赋值为6
  Test0x123Msg.byte(2) = 9;//第2个字节赋值为9
  output(Test0x123Msg);
}


on key'A'//当大写字母A被按下时
{
  output(Test0x123Msg);
}

 启动工程,我们发现只有一帧CAN报文,那是由于on start事件中的那段代码实现的,接下来我们只需要按一下大写字母A,即可再发送一帧,且每按一下就会发送一帧。

这样子,利用按键事件,我们就实现了再需要的时候发送一帧CAN报文。

6、利用定时器自动发送CAN报文

使用on key事件执行代码,每按一次键盘只能执行一次,有时候我们需要报文能够自己定时自动发送,而不需再自己去操作,那么就需要用到定时器了。

首先,我们需要先定义一个定时器变量,用于定时发送CAN报文。虽然不管是在全局变量定义区还是在事件、函数内都能定义定时器,语法上不报错,但关于定时器,我的说法是只能定义在全局变量定义区内,这是因为定义的局部定时器变量是无法使用的,局部变量的定时器变量无法被识别,无法创建定时器事件。

接下来,我们定义一个定时器变量,像这样

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg;//由于没有DBC,只能使用以ID形式声明CAN报文
  
  msTimer TaskTimer1;//定义一个毫秒级别的定时器
}

随后,我们在这个定时器事件内,编写发送CAN报文的代码。

on timer TaskTimer1
{
  output(Test0x123Msg);
}

 现在我们已经有了这个定时器事件,且这个定时器事件内的功能已经被我们编写成了发送0x100这个CAN报文,我们启动工程看一下。

诶,发现了吧,即便是有了定时器,还是没能够自动发送报文,怎么回事呢?

这是由于我们虽然创建了这个定时器任务,但是我们却没有开始它,定时器没有被开始,自然也就不会执行定时器事件里的代码了。

我们需要开启这个定时器,开启定时器的方法有两种。

第一种:使用settimercyclic函数周期开启定时器,使用这个命令开启的定时器,会每间隔一定事件后就执行一次,循环执行,直到收到关闭的命令为止才会停止定时器的工作。

我们在上面的代码中,在on key事件中加上一行开启定时器的代码

像这样

on key'A'
{
  output(Test0x123Msg);
  setTimerCyclic(TaskTimer1,500);//循环开启定时器TaskTimer1,循环时间为500ms。
}

settimercyclic函数的用法为:

settimercyclic(被开启的定时器的名字,每次开启的时间间隔);

时间间隔,当定时器类型为mstimer(毫秒级别定时器)时,时间的单位是ms

当定时器类型为Timer(秒级别定时器)时,事件单位是s

启动工程,看看现象

 当我们按下大写字母A之后,可以看到,报文进行了自动发送,且根据左侧的时间间隔可以看出,每次发送的时间差为500ms,这样子就实现了CAN报文的自动发送。

第二种使用定时器的方法为:settimer函数,这个函数的功能也是开启定时器,但使用它开启的定时器,只会执行一次。

像这样

variables
{
  //定义一个ID为0x123 名称为Test0x123Msg的CAN报文帧变量
  message 0x123 Test0x123Msg;//由于没有DBC,只能使用以ID形式声明CAN报文
  
  msTimer TaskTimer1;//定义一个毫秒级别的定时器
}

on start
{
  Test0x123Msg.dlc = 7;
  Test0x123Msg.byte(0) = 3;//为Test0x123Msg这帧CAN报文的第0个字节(即我们口语中的第一个,计算机语言是从0开始)赋值为3
  Test0x123Msg.byte(1) = 6;//第1个字节赋值为6
  Test0x123Msg.byte(2) = 9;//第2个字节赋值为9
  output(Test0x123Msg);
}


on key'A'
{
  output(Test0x123Msg);
  setTimer(TaskTimer1,500);//开启定时器TaskTimer1,时间为500ms。
}

on timer TaskTimer1
{
  output(Test0x123Msg);
}

看看现象

总共只发送了三帧报文,第一帧是on start中发送的,第二帧是on key'A'中当大写字母A被按下时发送的,第三帧则是定时器Tasktimer1中发送的,随后就没有了。

即使用settimer开启的定时器只能执行一次,那么我们怎么利用这个函数实现报文的自动发送呢?

很简单,只需要在定时器TaskTimer1中也执行一次settimer即可。

即,当执行到定时器TaskTimer1时,就让它再开启一次定时器TaskTimer1,那么每次执行到定时器后,都会在过一段时间后再次开启。这就达到了循环开启定时器的效果。

像这样,只需要在on timer TaskTimer1中再加一句开启自己的话即可。

开启工程,随后按下A按键,看看现象。可以看到,报文进行了自动发送,周期为500ms。

7、关闭定时器停止CAN报文的自动发送

当我们不想要定时器再执行,不想让它发送报文出来的时候(比如需要停发报文让ECU休眠),我们可以使用canceltimer函数关闭被开启的定时器,停止报文的自动发送。

这里我再使用一个按键事件B,当B被按下时,关闭定时器的自动发送

像这样,其余代码不变,仅添加用于关闭定时器的事件。

on key'B'
{
  cancelTimer(TaskTimer1);//关闭定时器TaskTimer1
}

启动工程,我们先按A开启定时发送,随后再按下B,就可以看到,报文已经停止发送了。

二、有DBC文件,发送CAN报文

当有DBC文件时,如果使能了CANoe的IL交互层,CANoe会根据DBC中各个报文定义的周期自动发送报文,就像本系列文章第一篇里那样。我们使用CAPL发送的话,尽量关闭IL层自动发送,以免CAPL的发送代码和IL交互层起到冲突。

1、关闭CANoeIL层自动发送

需要在prestart事件中添加如下两行代码

on preStart
{
  ILControlInit();//初始化CANoe的IL交互层
  ILControlStop();//禁止CANoe的IL交互层作用
}
2、定义CAN报文变量

随后,我们定义一个CAN报文变量。

由于有了DBC,我们可以任意使用一种CAN报文的定义方法。

这里我采用两种方式,各自定义了一条报文。一个0x123,一个0x321。

variables
{
  message ECU01_Frame CAN_ECU01_Msg;//定义一个名称为CAN_ECU01_Msg的CAN报文变量。
  //其中 ECU01_Frame是DBC中对应的报文的名称,这个报文DBC中的ID为0x123,这里采取使用名称定义的方式。 CAN_ECU01_Msg是我们定义的变量名。

  message 0x321 CAN_ECU02_Msg;//定义一个名称为CAN_ECU02_Msg的CAN报文变量
  //其中 0x321是DBC中定义了的报文的ID,CAN_ECU02_Msg使我们定义的变量名
}
3、发送报文,修改报文中信号的值。

由于使用了DBC,我们就可以采取变量名+小数点的方式,来索引到报文中具体的信号,从而修改信号值,像这样

variables
{
  message ECU01_Frame CAN_ECU01_Msg;//定义一个名称为CAN_ECU01_Msg的CAN报文帧
  message 0x321 CAN_ECU02_Msg;//定义一个名称为CAN_ECU02_Msg的CAN报文帧
}

on preStart
{
  ILControlInit();
  ILControlStop();
}

on start
{
  CAN_ECU01_Msg.ECU01_Sig1 = 0x6;//将CAN_ECU01_Msg报文帧中的ECU01_Sig1信号值设置为0x6
  CAN_ECU01_Msg.ECU01_Sig3 = 0x55;//将CAN_ECU01_Msg报文帧中的ECU01_Sig3信号值设置为055
  output(CAN_ECU01_Msg);
}

启动工程看看。可以看到,该报文中的信号值确实按照我们定义的进行了发送。

4、报文定时自动发送、修改报文DLC,关闭定时发送等。

此处与没有DBC中发送和修改的方法相同,可以在定义时修改,也可以在发送之前动态修改。

自动发送方法也一致,采取settimercyclic或者settimer即可,关闭定时发送同样使用canceltimer,就不再演示了。

可以看出来,有没有DBC,对于报文发送的方法,仅在于没有DBC的时候修改数据需要按照字节修改,即.byte(x)方法,有DBC的时候,可以使用小数点符号直接索引到报文中的信号,修改信号值(同时也依旧可以通过修改字节的方法进行修改)。

三、通过CAPL发送LDF中已定义的报文

1、定义LIN报文帧变量

LIN报文帧变量定义的方式也雷同于CAN报文帧定义,区别仅在于所使用的关键字不同。

LIN报文使用关键字linframe进行定义

像这样

variables
{
  linframe 0x5 Lin0x5Frame;//定义一个id为0x5,名称为Lin0x5Frame的LIN报文帧变量。
}

2、发送LIN报文帧

只要加载了LDF文件,CANoe就会根据LDF中定义的调度表自己进行LIN报文的发送,我们可以不用再人为进行定时发送。

即便CAPL代码中没有书写任何逻辑,CANoe也会自动发送LIN报文和帧头,像这样

可以看到,CANoe中一直在自己发送主机报文以及从节点帧头。(红色报错是因为在仿真,没有接LIN从机,没有响应造成的)

如果不希望CANoe在一启动就自己根据LIN调度表进行报文发送,可以使用linStopScheduler( )方法

像这样,只需要在prestart或者start中写上这句话,即可不再自动发送报文了。

on start
{
  linStopScheduler();//停止LIN调度表
}

停止调度表后,我们发报文和帧头就不方便了,我们一般需要再次发送建议先开启调度表。

开启调度表使用linStartScheduler()方法,或者只要不关闭调度表,那么调度表就是默认开启的

像这样,我在on key中使用这个方法开启调度表,实际上你可以在需要的任何地方使用这个语句开启调度表

on key' '
{
  linStartScheduler();
}

由于在on start中关闭了调度表,所以总线上没有任何消息。

接着,我按下空格按键,开启调度表,总线上就出现了LIN报文

3、修改LIN报文的Data值,并发送到总线上

改变LIN报文的Data值,和上述的CAN的方法一致,可以使用.byte(x)方法设置每个字节的数据,也可以使用小数点索引到具体的信号进行设置。

像这样

on key'A'
{
  Lin0x5Frame.MasterFrame_Sig1 = 0x16;//将LIN帧Lin0x5Frame中的信号MasterFrame_Sig1赋值为0x16
  Lin0x5Frame.byte(5) = 0x61;//将LIN帧Lin0x5Frame中的第5个字节赋值为0x61
  output(Lin0x5Frame);
}

运行工程,看看结果,启动时LIN报文的Data为LDF中设置的默认值

随后我们按下按键A  再次观察,可以看到,当我按下A之后,LIN报文的信号和字节的值发生了改变,改变为我想要设置的值。

4、切换LIN调度表

当我们使用LIN调度表进行报文收发时,如果我们想要发送的报文不在当前运行的调度表内的话,我们是无法使用output函数将报文发送出去的。

例如我们正常下执行的是应用帧的调度表,此时我想发送诊断帧的LIN报文,是无法发送出去的。

此时我们就需要将调度表切换到有LIN诊断帧的表内。

CANoe启动时,如没有使用CAPL代码改变调度表,那么CANoe会默认执行第0张调度表,即LDF内定义在最上面的一个调度表。

如下图的TestRun和DiagTable两个表,CANoe启动默认是执行上面的,也就是索引为0表,即TestRun这个表。往下的表索引依此为1,2,3....

当我们想要发送诊断帧时,就需要将调度表切换到含有诊断帧的表中,比如我这里的索引为1的表。

切换调度表使用linChangeSchedTable( )方法,括号内填想要切换的表的索引值。

像这样

on key'B'
{
  linChangeSchedTable(1);//切换到索引为1的调度表运行
}

当调度表切换过后,我们就可以使用output函数发送对应的表内的LIN报文了。

四、没有LDF情况下发送LIN报文,或者发送不存在于LDF中的LIN报文

当没有LDF或者想要发送LDF中未定义的LIN报文时,我们也需要先定义一个LIN报文帧变量

1、定义LIN报文帧
variables
{
  linframe 0x36 LinTestFrame;//定义一个不存在于ldf中的lin帧
}
2、发送LIN报文

无ldf或者发送不存在于ldf中的报文时,同样使用output函数进行发送,但是不能直接使用output函数发送

我们需要将LIN报文变量的rtr属性设置为1才可以使用output进行发送

且经过本人实际使用的经验,必须在prestart事件中,先使用output发送一次帧头才可以在后续其他事件中修改并发送这个报文的其他信息。

像这样

on preStart
{ 
  LinTestFrame.RTR = 0;//可以写也可以省略,定义的LIN帧变量这个值默认是0。
  output(LinTestFrame);//在预启动事件中,先output一次帧头
}

随后再在需要发送报文的地方,设置该变量的rtr为1,再次调用output函数即可将这帧LIN报文发送出去。

像这样

on key 'a'
{
  LinTestFrame.RTR=1;
  output(LinTestFrame);
}

以上就是全部代码了。

启动工程看一下现象,可以看到。Trace窗口中并没有0x36的LIN报文。

随后按下小写字母a看下现象,当按下之后我们发出了id为0x36的LIN报文。

通过我的ldf文件,可以看到我的ldf中是没有这个0x36的LIN报文的

也就是说我们已经实现了无ldf发送LIN报文,或者说是发送了ldf中未定义的LIN报文。

3、修改自定义的LIN报文的信号(字节)值

由于没有ldf定义,当然也就没有直接修改信号这个说法了,我们要修改这个LIN报文,只能通过修改长度和字节值的方式去修改。

此类不在ldf中的自定义的LIN报文变量,每次在修改之前,需要将rtr置为0才可以修改字节的值,且修改完成之后需要将rtr置为1才能通过output发送出去。

像这样

variables
{
 linFrame 0x36  LinTestFrame;
}
on preStart
{ 
  LinTestFrame.RTR = 0;//可以写也可以省略,定义的LIN帧变量这个值默认是0。
  output(LinTestFrame);
}

on key 'A'
{
  LinTestFrame.RTR=1;//将rtr置1才可以把数据发出去
  output(LinTestFrame);//发送报文
}

on key'B'
{
  LinTestFrame.RTR = 0;//将rtr置0才能修改报文的值
  LinTestFrame.byte(0) = 0x12;
  output(LinTestFrame);//修改完成之后先output把rtr = 0和修改的值生效
  LinTestFrame.rtr = 1;//随后再次修改rtr为1,rtr为1才能将报文发送出去
  output(LinTestFrame);
}

启动工程看看现象

启动时,Trace无任何报文,按下A之后,发出0x36的报文。由于我们还没有修改字节的值,所以此处发出的是默认值全ff

随后我们再次按下B按键,可以看到,0x36的第0个字节被修改为了代码中填写的0x12。

4、定时发送自定义的LIN报文

上述的代码也只能实现每按一次按键就发送一次我们定义的LIN帧,有时候我们想让它也自动发送,此时我们可以借助定时器来实现。

先定义一个定时器,用于发送这个报文

像这样

variables
{
 linFrame 0x36  LinTestFrame;
  
  msTimer TaskTimer1;
}
on preStart
{ 
  LinTestFrame.RTR = 0;//可以写也可以省略,定义的LIN帧变量这个值默认是0。
  output(LinTestFrame);
}

on key 'A'
{
  LinTestFrame.RTR=1;
  output(LinTestFrame);
}

on key'B'
{
  LinTestFrame.RTR = 0;
  LinTestFrame.byte(0) = 0x12;
  output(LinTestFrame);
  LinTestFrame.rtr = 1;
  output(LinTestFrame);
}

on timer TaskTimer1
{
  output(LinTestFrame);
}

on key'C'
{
  setTimerCyclic(TaskTimer1,500);
}

启动工程,我们先按下A,B,最后按下C开启这个定时器,看看现象

可以看到,0x36这个LIN报文进行了定时发送,且周期为500ms。

但是这种自定义LIN报文的发送和修改还是有些麻烦了,大多数情况下还是建议有LDF使用,或者封装一个函数用于发送和修改这个LIN自定义报文会更好。

<think>好的,用户想要用CAPL语言在CANoe发送LIN诊断报文的示例代码或方法。我需要先回忆一下CAPL的相关知识,并结合提供的引用内容来组织回答。 首先,用户提到的引用[1]和[2]提到了在Test Module节点中使用diagGenerateKeyFromSeed函数和创建CAPL测试模块。这可能涉及到诊断请求的生成和处理。引用[3]提到了发送自定义CAN报文的方法,比如添加CAN Frame并设置ID和信号内容。虽然这是针对CAN的,但LIN的诊断发送可能有类似的结构,但需要调整到LIN协议。 接下来,我需要确定CAPL发送LIN报文的方法。通常,LIN在CAPL中可能需要使用特定的函数,比如linSendHeader或通过LIN诊断对象来发送。对于诊断报文,可能需要使用诊断层函数,比如diagSendRequest,但需要配置好LIN的诊断描述文件(如LDF)和诊断层设置。 然后,考虑用户可能需要先设置LIN总线配置,比如在CANoe中配置LIN网络,添加LIN节点,并导入相关的数据库或LDF文件。发送LIN诊断报文可能分为两种方式:直接发送LIN帧,或者使用诊断层的API。如果是直接发送,可以使用linSendFrame或类似函数;如果是诊断请求,可能需要使用诊断服务,比如通过diagSendRequest函数,并指定目标地址等参数。 示例代码部分,可能需要创建一个Test Module,在其中使用diagSendRequest发送UDS请求,或者在定时器中周期发送LIN帧。需要确保LIN的调度表配置正确,或者使用主节点发送报头,然后发送响应数据。同时,要引用提供的参考内容,比如引用[3]中的添加报文发送的方法,但调整为LIN的语法。 还需要注意用户可能需要的步骤,比如如何添加CAPL Test Module(引用[2]),如何配置LIN节点,如何设置报文内容。可能需要分步骤说明:配置LIN环境、编写CAPL代码、发送诊断请求或直接发送报文、验证发送结果(通过Trace窗口查看)。 最后,生成相关问题时要围绕LIN诊断、CAPL的其他功能、错误处理等,确保问题与主题相关,帮助用户进一步探索。</think>在CANoe使用CAPL发送LIN诊断报文需要结合LIN协议栈及诊断功能配置。以下是分步说明及示例代码: ### 1. LIN诊断环境配置 - **导入LDF文件**:在CANoe的LIN Network配置中导入LIN描述文件(.ldf),定义报文和信号[^2]。 - **创建LIN诊断层**:通过`Diagnostics`窗口配置LIN诊断描述文件(CDD/ODX),绑定到对应LIN节点。 ### 2. CAPL发送LIN诊断报文 #### 方法1:直接发送LIN帧 ```c variables { linFrame msg; } on key 'a' { msg.id = 0x3C; // 诊断帧ID通常为0x3C或0x3D msg.dlc = 8; msg.byte(0) = 0x02; // 示例数据:02 10 03 00 00 00 00 00 msg.byte(1) = 0x10; linSendFrame(msg); } ``` > 注:需在LIN调度表中启用该帧或使用主节点主动发送[^3] #### 方法2:通过诊断API发送 ```c on start { DiagSetTarget("LIN::MyNode"); // 绑定LIN节点 } on key 'b' { byte request[] = {0x10, 0x03}; // UDS诊断请求示例:10 03(进入扩展会话) diagSendRequest(request); } ``` > 需提前在Diagnostic/ISO TP配置中设置LIN传输层参数[^1] ### 3. 验证发送结果 在`Trace`窗口中过滤LIN总线数据,观察报文是否成功发送(检查ID、数据方向、内容)[^3]
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值