54、QSPY应用构建、调用及数据处理全解析

QSPY应用构建、调用及数据处理全解析

1. 从源码构建QSPY应用

QSPY的源代码采用可移植的C++编写,并且已经提供了针对Windows和Linux的移植版本。QSPY主机应用程序通过头文件 <qp>\qpc\include\qs.h 与QS目标组件相连接,该头文件列举了预定义的QS记录。

1.1 使用Visual C++ 2005为Windows构建QSPY

QSPY应用程序的Win32可执行文件位于 <qp>\qpc\tools\qspy\win32\vc2005\Release\qspy.exe 。这个可执行文件可以在任何32位Windows系统上运行。
若要重新构建该应用程序, <qp>\qpc\tools\qspy\win32\vc2005\ 目录中包含了用于构建QSPY应用程序的Microsoft Visual C++ 2005解决方案文件 qspy.sln 。你只需将该解决方案文件加载到Visual C++ 2005 IDE中,然后按下F7即可开始构建。

1.2 使用MinGW为Windows构建QSPY

你也可以使用从 www.mingw.org 获得的开源MinGW(Minimalist GNU for Windows)工具集来构建QSPY可执行文件。 <qp>\qpc\tools\qspy\win32\mingw\ 目录中包含一个简单的批处理文件 make.bat 用于构建QSPY应用程序。你可能需要修改批处理文件顶部的 MINGW 符号定义,使其指向你安装MinGW工具集的位置。默认情况下, make.bat 会在 <qp>\qpc\tools\qspy\win32\mingw\dbg\ 目录中生成应用程序的调试版本。若要生成发布版本,可在 make.bat 脚本中添加 rel 参数( make rel ),发布版本将生成在 <qp>\qpc\tools\qspy\win32\mingw\rel\ 目录中。

1.3 为Linux构建QSPY

<qp>\qpc\tools\qspy\linux\gnu\ 目录中包含了用于为Linux构建QSPY的Makefile。默认情况下,Makefile会在 <qp>\qpc\tools\qspy\linux\gnu\dbg\ 目录中生成应用程序的调试版本。若要生成发布版本,可在 make 命令中添加 rel 目标( make rel ),发布版本将生成在 <qp>\qpc\tools\qspy\linux\gnu\rel\ 目录中。

2. 调用QSPY

QSPY主机应用程序旨在与所有可能的目标CPU和数据链路配合使用,这就需要具备广泛的可配置性。例如,对于任何给定的目标CPU,QSPY应用程序必须“知道”对象指针、函数指针、事件信号、时间戳大小等的大小。你可以通过命令行参数将这些信息提供给QSPY,这些参数总结在表1中,也可以在“QSPY参考手册”中找到。需要注意的是,这些选项是区分大小写的。

选项 示例 默认值 必须匹配的QP宏(QP端口头文件) 注释
-h -h 帮助;打印选项摘要
-q -q 安静模式(无标准输出)
-o -o qs.txt 生成输出到指定文件
-s -s qs.spy 将二进制输入保存到指定文件;与 -f 不兼容
-m -m qs.mat 生成MATLAB输出到指定文件
-c -c COM2 COM1 COM端口选择;与 –t, -p, -f 不兼容
-b -b 115200 38400 波特率选择;与 –t, -p, -f 不兼容
-t -t TCP/IP输入选择;与 –c, -b, -f 不兼容
-p -p 6602 6601 TCP/IP服务器端口号;与 –c, -b, -f 不兼容
-f -f qs.spy 文件输入选择;与 –c, -b, -t, -p 不兼容
-T -T 2 4 QS_TIME_SIZE (qs_port.h) 时间戳大小(字节);有效值:1, 2, 4
-O -O 2 4 QS_OBJ_PTR_SIZE (qs_port.h) 对象指针大小(字节);有效值:1, 2, 4
-F -F 2 4 QS_FUN_PTR_SIZE (qs_port.h) 函数指针大小(字节);有效值:1, 2, 4
-S -S 2 1 Q_SIGNAL_SIZE (qep_port.h) 信号大小(字节);有效值:1, 2, 4
-E -E 1 2 QF_EVENT_SIZ_SIZE (qf_port.h) 事件大小(字节);有效值:1, 2, 4
-Q -Q 1 2 QF_EQUEUE_CTR_SIZE (qf_port.h) 队列计数器大小(字节);有效值:1, 2, 4
-P -P 4 2 QF_MPOOL_CTR_SIZE (qf_port.h) 池计数器大小(字节);有效值:1, 2, 4
-B -B 1 2 QF_MPOOL_SIZ_SIZE (qf_port.h) 块大小(字节);有效值:1, 2, 4
-C -C 4 2 QF_TIMEEVT_CTR_SIZE (qf_port.h) 时间事件计数器大小;有效值:1, 2, 4

在调用QSPY时,你主要需要确保其与你所使用的目标系统完全匹配。表1的第四列列出了目标系统使用的配置宏以及定义这些宏的特定于平台的QP头文件。只有当QP宏与默认值不同时,你才需要使用相应的QSPY命令行选项。QSPY假定的默认值与QP中使用的默认值是一致的。

需要注意的是,当QSPY主机应用程序与QS目标组件不匹配时,QSPY应用程序将无法正确解析不匹配的跟踪记录,并会开始生成以下错误:

********** 028: Error xx bytes unparsed
********** 014: Error -yy bytes unparsed

错误前面的数字表示无法解析的跟踪记录的记录ID。

3. 将跟踪数据导出到MATLAB

QSPY主机应用程序还可以将跟踪数据导出到MATLAB,MATLAB是一种流行的数值计算环境和高级技术编程语言。由The MathWorks, Inc.创建的MATLAB允许轻松操作和绘制以矩阵形式表示的数据。

3.1 使用MATLAB分析跟踪数据

当你使用 -m <文件名> 选项调用QSPY时,QSPY应用程序除了生成前面讨论的人类可读格式外,还会生成一个指定名称的MATLAB可读文件。

MATLAB输出文件是一个ASCII文件,其中包含所有为MATLAB格式化的跟踪记录。然而,MATLAB文件中的各种跟踪记录仍然保持在目标中生成时的顺序,尚未形成合适的MATLAB矩阵,而矩阵是MATLAB中表示数据的最自然方式。

你可以在 <qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\dpp.mat 文件中找到一个QSPY MATLAB输出的示例。

<qp>\qpc\tools\qspy\matlab\ 目录中包含MATLAB脚本 qspy.m ,该脚本用于读取QSPY MATLAB文件并将数据转换为当前工作区中的多个MATLAB矩阵。假设 <qp>\qpc\tools\qspy\matlab\ 目录已包含在MATLAB路径中,你可以从MATLAB命令窗口按以下方式调用该脚本:

Q_FILE='<qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\dpp.mat'; qspy

变量 Q_FILE 被设置为QSPY MATLAB文件的文件名。需要注意的是, qspy.m 脚本故意不是一个MATLAB函数,因为其主要目的是在脚本完成后在工作区中保留新创建的数据,以便你可以用于进一步的计算(MATLAB函数在单独的工作区中运行,函数返回后该工作区将消失)。

填充矩阵后, qspy.m 脚本会执行 whos 命令以显示创建的对象。前缀为 Q_ 的矩阵包含按时间顺序排列的跟踪数据。所有MATLAB矩阵的文档都可以在“QSPY参考手册”中找到。

3.2 MATLAB输出文件

QSPY MATLAB文件采用ASCII格式,下面是一个从DPP应用程序生成的QSPY MATLAB文件的片段示例:

62 Philo_initial= 308543675;
62 Philo_thinking= 308544589;
62 Philo_hungry= 308544835;
62 Philo_eating= 308545073;
60 HUNGRY_SIG=[
8
382343546];
60 TIMEOUT_SIG=[
10
382343546];
12
0
4
382343546
3
382343546
337510406 308544589
...

QSPY MATLAB文件以可移植的ASCII格式存储,以实现跨平台的可移植性,但实际上并不适合人类阅读。这个示例只是为了说明数据主要是数值,唯一的例外是“字典”条目,它们实际上以MATLAB命令的形式存储。

3.3 MATLAB脚本qspy.m

MATLAB脚本 qspy.m 位于 <qp>\qpc\tools\qspy\matlab\ 目录中,其设计目的是读取QSPY MATLAB文件并将不同的记录分类到各种MATLAB矩阵中,以便进行后续分析。以下是 qspy.m 脚本的代码:

% the string Q_FILE must be defined
fid = fopen(Q_FILE, 'r');
if fid == -1
    error('file not found')
end
Q_STATE = []; % sate entry/exit, init, tran, internal tran, ignored
Q_EQUEUE = []; % QEQueue
Q_MPOOL = []; % QMPool
Q_NEW = []; % new/gc
Q_ACTIVE = []; % active add/remove, subscribe/unsubscribe
Q_PUB = []; % publish/publish attempt
Q_TIME = []; % time event arm/disarm/rearm, clock tick
Q_INT_LOCK = []; % interrupt locking/unlocking
Q_ISR_LOCK = []; % ISR entry/exit
Q_MUTEX = []; % QK mutex locking/unlocking
Q_SCHED = []; % QK scheduler events
Q_TOT = 0; % total number of records processed
while feof(fid) == 0
    line = fgetl(fid);
    Q_TOT = Q_TOT+1;
    rec = sscanf(line, '%d', 1); % extract the record type
    switch rec % discriminate based on the record type
        % QEP trace records
        case 1 % QS_QEP_STATE_ENTRY
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [NaN 1 sscanf(line, '%*u %u %u')' NaN 1];
        case 2 % QS_QEP_STATE_EXIT
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [NaN 2 sscanf(line, '%*u %u %u')' NaN 1];
        case 3 % QS_QEP_STATE_INIT
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [NaN 3 sscanf(line, '%*u %u %u %u')' 1];
        case 4 % QS_QEP_INIT_TRAN
            tmp = sscanf(line, '%*u %u %u %u')';
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [tmp(1) 3 tmp(2) NaN tmp(3) 1];
        case 5 % QS_QEP_INTERN_TRAN
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [sscanf(line, '%*u %u %u %u %u')' NaN 1];
        case 6 % QS_QEP_TRAN
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [sscanf(line, '%*u %u %u %u %u')' 1];
        case 7 % QS_QEP_IGNORED
            Q_STATE(size(Q_STATE,1)+1,:) = ...
                [sscanf(line, '%*u %u %u %u %u')' NaN 0];
        % QF trace records
        case 10 % QS_QF_ACTIVE_ADD
            tmp = sscanf(line,'%*u %u %u %u %u')';
            Q_ACTIVE(size(Q_ACTIVE,1)+1,:) = [tmp(1) NaN tmp(2) tmp(3) 1];
        % Miscallaneous QS records
        case 60 % QS_SIG_DICTIONARY
            eval(line(5:end));
        case 61 % QS_OBJ_DICTIONARY
            eval(line(5:end));
        case 62 % QS_FUN_DICTIONARY
            eval(line(5:end));
        % User records
        % ...
    end
end
% cleanup ...
fclose(fid);
clear fid;
clear line;
clear rec;
clear tmp;
% display status information...
Q_TOT
whos

该脚本的主要步骤如下:
1. 确保变量 Q_FILE 已定义,并打开该文件。
2. 初始化各种MATLAB矩阵,用于存储不同类型的跟踪记录。
3. 逐行读取MATLAB文件,提取每行的记录类型。
4. 根据记录类型,将数据存储到相应的MATLAB矩阵中。
5. 清理临时数据并显示处理的记录总数和当前工作区的内容。

3.4 MATLAB矩阵生成

qspy.m 脚本会生成11个MATLAB矩阵,每个矩阵包含不同组相关的QS跟踪记录。这里以 Q_STATE 矩阵为例,它存储了系统中与状态机活动相关的所有QS记录。以下是 Q_STATE 矩阵的存储方式总结:

MATLAB索引 1 2 3 4 5 6
QS记录 时间戳 信号 状态机对象 源状态 新状态 事件处理
QS_QEP_STATE_ENTRY NaN 1 2 NaN 1
QS_QEP_STATE_EXIT NaN 2 2 NaN 1
QS_QEP_STATE_INIT NaN 3 2 1
QS_QEP_STATE_INIT_TRAN 3 2 NaN
QS_QEP_STATE_INTERN_TRAN 1 2
QS_QEP_STATE_TRAN 1 2
QS_QEP_STATE_IGNORED 1 2

通过以下索引矩阵可以从 Q_STATE 矩阵中明确选择特定的QS跟踪记录:
| QS记录 | MATLAB索引矩阵 |
| — | — |
| QS_QEP_STATE_ENTRY | Q_STATE(:,2) == 1 |
| QS_QEP_STATE_EXIT | Q_STATE(:,2) == 2 |
| QS_QEP_STATE_INIT | Q_STATE(:,2) == 3 |
| QS_QEP_STATE_INIT_TRAN | isnan(Q_STATE(:,4)) |
| QS_QEP_STATE_INTERN_TRAN | Q_STATE(:,2) > 3 & isnan(Q_STATE(:,5)) |
| QS_QEP_STATE_TRAN | Q_STATE(:,2) > 3 & ~isnan(Q_STATE(:,5)) |
| QS_QEP_STATE_IGNORED | ~Q_STATE(:,6) |

作为使用 Q_STATE 矩阵中信息的一个示例,以下是生成哲学家活动对象时序图的 philo_timing.m 脚本:

t=Q_STATE(:,2)>3 & ~isnan(Q_STATE(:,5)); % QS_QEP_STATE_TRAN
o=Q_STATE(:,3) == l_philo_0_;
subplot(5,1,1); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_1_;
subplot(5,1,2); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_2_;
subplot(5,1,3); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_3_;
subplot(5,1,4); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
o=Q_STATE(:,3) == l_philo_4_;
subplot(5,1,5); stairs(Q_STATE(o & t,1),Q_STATE(o & t,5),'r')
xlabel('time stamp'); zoom on

该脚本的主要步骤如下:
1. 使用索引矩阵 t 选择 Q_STATE 矩阵中与状态转换对应的行。
2. 使用索引矩阵 o 选择特定哲学家的状态机对象。
3. 为每个哲学家绘制时序图。

显然,这个简短的演示只是触及了可能性的表面。有关其他MATLAB矩阵的描述,请参考“QSPY参考手册”。

4. 向QP应用程序添加QS软件跟踪

接下来将展示如何向DPP应用程序添加QS软件跟踪,此应用程序之前已作为软件跟踪会话的示例。

4.1 DPP示例概述

80x86搭配QK抢占式内核的DPP示例,展示了QS在所有QP组件(QEP、QF和QK)中的使用。该示例位于 <qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\ 目录。你可以通过将DPP - SPY.PRJ项目加载到Turbo C++ IDE中来重新构建“Spy”配置。此应用程序会链接到位于 <qp>\qpc\ports\80x86\qk\tcpp101\l\spy\ 目录的“Spy”版本的QP库。该示例演示了QS设置的各个方面,特别是如何通过串口(UART)发送QS跟踪数据,以及如何使用每个基于x86的PC中都有的8254定时器/计数器以亚微秒精度为QS记录添加时间戳。

4.2 初始化QS并设置过滤器

以下是初始化QS、设置过滤器以及生成字典条目的代码(文件 <qp>\qpc\examples\80x86\qk\tcpp101\l\dpp\main.c ):

#include "qp_port.h"
#include "dpp.h"
#include "bsp.h"
/* Local-scope objects --------------------------------------------------*/
static QEvent const *l_tableQueueSto[N_PHILO];
static QEvent const *l_philoQueueSto[N_PHILO][N_PHILO];
static QSubscrList l_subscrSto[MAX_PUB_SIG];
static union SmallEvent {
    void *min_size;
    TableEvt te;
    /* other event types to go into this pool */
} l_smlPoolSto[2*N_PHILO];
/* storage for the small event pool */
/*......................................................................*/
int main(int argc, char *argv[]) {
    uint8_t n;

    Philo_ctor();
    /* instantiate all Philosopher active objects */
    Table_ctor();
    /* instantiate the Table active object */
    BSP_init(argc, argv);
    /* initialize the BSP (including QS) */
    QF_init();
    /* initialize the framework and the underlying RT kernel */
    /* setup the QS filters ... */
    QS_FILTER_ON (QS_ALL_RECORDS);
    QS_FILTER_OFF(QS_QF_INT_LOCK);
    QS_FILTER_OFF(QS_QF_INT_UNLOCK);
    QS_FILTER_OFF(QS_QK_SCHEDULE);
    /* provide object dictionaries... */
    QS_OBJ_DICTIONARY(l_smlPoolSto);
    QS_OBJ_DICTIONARY(l_tableQueueSto);
    QS_OBJ_DICTIONARY(l_philoQueueSto[0]);
    QS_OBJ_DICTIONARY(l_philoQueueSto[1]);
    QS_OBJ_DICTIONARY(l_philoQueueSto[2]);
    QS_OBJ_DICTIONARY(l_philoQueueSto[3]);
    QS_OBJ_DICTIONARY(l_philoQueueSto[4]);
    QF_psInit(l_subscrSto, Q_DIM(l_subscrSto));
    /* init publish-subscribe */
    /* initialize event pools... */
    QF_poolInit(l_smlPoolSto, sizeof(l_smlPoolSto), sizeof(l_smlPoolSto[0]));
    for (n = 0; n < N_PHILO; ++n) {
        /* start the active objects... */
        QActive_start(AO_Philo[n], (uint8_t)(n + 1),
                      l_philoQueueSto[n], Q_DIM(l_philoQueueSto[n]),
                      (void *)0, 0, (QEvent *)0);
    }
    QActive_start(AO_Table, (uint8_t)(N_PHILO + 1),
                  l_tableQueueSto, Q_DIM(l_tableQueueSto),
                  (void *)0, 0, (QEvent *)0);
    QF_run();
    /* run the QF application */
    return 0;
}

代码的主要步骤如下:
1. 包含必要的头文件 :引入 qp_port.h dpp.h bsp.h 头文件。当启用QS跟踪(即定义了宏 Q_SPY )时, qp_port.h 会包含QS活动接口 qs.h
2. 初始化对象 :实例化所有哲学家活动对象和表活动对象。
3. 初始化BSP和QF :调用 BSP_init 初始化板级支持包(同时也会初始化QS),调用 QF_init 初始化框架和底层实时内核。
4. 设置QS过滤器
- 启用所有全局过滤器: QS_FILTER_ON (QS_ALL_RECORDS)
- 禁用高流量的跟踪记录: QS_FILTER_OFF(QS_QF_INT_LOCK) QS_FILTER_OFF(QS_QF_INT_UNLOCK) QS_FILTER_OFF(QS_QK_SCHEDULE) ,以避免QS跟踪缓冲区溢出。
5. 提供对象字典 :使用 QS_OBJ_DICTIONARY 为相关对象提供字典条目。
6. 初始化发布 - 订阅和事件池 :调用 QF_psInit 初始化发布 - 订阅机制,调用 QF_poolInit 初始化事件池。
7. 启动活动对象 :通过 QActive_start 启动所有哲学家活动对象和表活动对象。
8. 运行应用程序 :调用 QF_run 运行QF应用程序。

4.3 总结与展望

通过上述步骤,我们详细了解了QSPY应用程序的构建、调用、跟踪数据导出到MATLAB以及向QP应用程序添加QS软件跟踪的方法。

在构建QSPY应用程序时,针对不同的操作系统和开发环境,提供了多种构建方式,如使用Visual C++ 2005、MinGW为Windows构建,以及为Linux构建。调用QSPY时,需要注意命令行参数的使用,确保与目标系统匹配,避免出现解析错误。将跟踪数据导出到MATLAB后,可以利用MATLAB强大的数据分析和可视化功能,对系统的运行状态进行深入分析。向QP应用程序添加QS软件跟踪时,需要正确初始化QS并设置过滤器,以确保跟踪数据的有效性和准确性。

未来,我们可以进一步探索如何优化QS软件跟踪的配置,以减少对系统性能的影响。同时,可以深入研究MATLAB矩阵的使用,挖掘更多潜在的分析方法,为系统的调试和优化提供更有力的支持。此外,随着技术的不断发展,还可以考虑将QS软件跟踪与其他工具和技术相结合,以满足更复杂的应用场景需求。

总之,掌握QSPY和QS软件跟踪技术,能够帮助我们更好地理解和调试事件驱动系统,提高系统的可靠性和性能。希望本文的内容能够为相关开发者提供有价值的参考,促进事件驱动系统开发技术的不断进步。

基于51单片机,实现对直流电机的调速、测速以及正反转控制。项目包含完整的仿真文件、源程序、原理图和PCB设计文件,适合学习和实践51单片机在电机控制方面的应用。 功能特点 调速控制:通过按键调整PWM占空比,实现电机的速度调节。 测速功能:采用霍尔传感器非接触式测速,实时显示电机转速。 正反转控制:通过按键切换电机的正转和反转状态。 LCD显示:使用LCD1602液晶显示屏,显示当前的转速和PWM占空比。 硬件组成 主控制器:STC89C51/52单片机(与AT89S51/52、AT89C51/52通用)。 测速传感器:霍尔传感器,用于非接触式测速。 显示模块:LCD1602液晶显示屏,显示转速和占空比。 电机驱动:采用双H桥电路,控制电机的正反转和调速。 软件设计 编程语言:C语言。 开发环境:Keil uVision。 仿真工具:Proteus。 使用说明 液晶屏显示: 第一行显示电机转速(单位:转/分)。 第二行显示PWM占空比(0~100%)。 按键功能: 1键:加速键,短按占空比加1,长按连续加。 2键:减速键,短按占空比减1,长按连续减。 3键:反转切换键,按下后电机反转。 4键:正转切换键,按下后电机正转。 5键:开始暂停键,按一下开始,再按一下暂停。 注意事项 磁铁和霍尔元件的距离应保持在2mm左右,过近可能会在电机转动时碰到霍尔元件,过远则可能导致霍尔元件无法检测到磁铁。 资源文件 仿真文件:Proteus仿真文件,用于模拟电机控制系统的运行。 源程序:Keil uVision项目文件,包含完整的C语言源代码。 原理图:电路设计原理图,详细展示了各模块的连接方式。 PCB设计:PCB布局文件,可用于实际电路板的制作。
【四旋翼无人机】具备螺旋桨倾斜机构的驱动四旋翼无人机:建模与控制研究(Matlab代码、Simulink仿真实现)内容概要:本文围绕具备螺旋桨倾斜机构的驱动四旋翼无人机展开研究,重点进行了系统建模与控制策略的设计与仿真验证。通过引入螺旋桨倾斜机构,该无人机能够实现向力矢量控制,从而具备更强的姿态调节能力和六自由度驱动特性,克服传统四旋翼欠驱动限制。研究内容涵盖动力学建模、控制系统设计(如PID、MPC等)、Matlab/Simulink环境下的仿真验证,并可能涉及轨迹跟踪、抗干扰能力及稳定性分析,旨在提升无人机在复杂环境下的机动性与控制精度。; 适合人群:具备一定控制理论基础和Matlab/Simulink仿真能力的研究生、科研人员及从事无人机系统开发的工程师,尤其适合研究先进无人机控制算法的技术人员。; 使用场景及目标:①深入理解驱动四旋翼无人机的动力学建模方法;②掌握基于Matlab/Simulink的无人机控制系统设计与仿真流程;③复现硕士论文级别的研究成果,为科研项目或学术论文提供技术支持与参考。; 阅读建议:建议结合提供的Matlab代码与Simulink模型进行实践操作,重点关注建模推导过程与控制器参数调优,同时可扩展研究不同控制算法的性能对比,以深化对驱动系统控制机制的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值