duBand源码分析-底层通讯部分

md3骨骼动画模型 md3骨骼动画模型,其中包含两个md3例子参考! 立即下载

一、编写说明

duBand源码中的通讯部分源码较多,但细细阅读发现其分层结构清晰,很值得借鉴。为了深入对duBand通讯源码的学习,并理解通讯分层设计的思想,决定对duBand源码的通讯部分进行分析,本文档用于记录分析过程。

二、源码分析

1. 层次说明

通讯部分主要分为三个层,这三个层主要的功能划分如下:

L0层:负责从硬件电路上读、写数据。移植时主要重写该层函数。

L1层:负责对协议包的接收,保证包的完整性、正确性,并返回相应的应答信号。

L2层:负责对协议包的解析,进行相应处理并返回数据。

 

2. 接收数据

1) 数据/资源说明

a) 包、帧

协议包为一组完成的协议数据,里面包含起始码、命令码、数据码和校验码等。由于蓝牙传输一次最多传输20个字节,因此对于一些大于20字节的协议包会分成多帧发送。如一个45字节的协议包,会为成两个20的帧和一个5字节的帧,分3次发送。

 

b) 接收状态-receive_state

接收状态有三个,分别为WAIT_START、WAIT_MESSAGE与MESSAGE_RESOLVE。WAIT_START为等待接收协议包的起始帧,几前20个字节(或小于20个字节的协议包的全部字节)。接收到起始包后,接收状态变成WAIT_MESSAGE,表示等接收当前协议包剩下的帧。MESSAGE_RESOLVE,表示正在 等待对方回答ACK/NACK信号。

 

c) 接收超时定时器-receive_time_out_timer

超时定时器的作用是检测同一包内的多帧是否接收完成。在WAIT_MESSAGE和ACK/NACK的状态下,需要启动该定时器,等待目录数据。超时定时器超时时,会将receive_state设置为WAIT_START,重新等待接收新的协议包。

 

 

2) L0层

duBand为蓝牙通讯的设备,因为L0层实现上用是蓝牙数据的传输。见源码ble_nus_service_init()中,通过nus_init.data_handler = L1_receive_data将L1的接收函数设置为蓝牙接收的回调函数。因此nordic的蓝牙串口服务已经将L0层的接收函数做好了。但是其有一个重要的特性,就是nordic的BLE每次传输时,最大只能传输20个字节,即20个字节为一个协议包中的小帧。如果需要将协议移植到非蓝牙传输的系统中,这个地方需要做一个中间层函数转换。

 

3) L1层

L1层的接收函数为L1_receive_data(),输入参数为接收到的数据缓存和接收的数据长度。在蓝牙串口接收到数据时,会调用此函数。

i. 先判断是否接收等待定时器是否有启动,如果定时器启动,则停止其,表示在定时到达时间内接收到了数据。

 

ii. 接着根据receive_state进行相应的处理。

iii. 状态为WAIT_START时,先判断首字节是否为L1_HEADER_MAGIC(协议包起始码),如果不是,则丢弃该数据帧。如果是,则获取其协议包总长度字段(注意,因为蓝牙数据是20字节一个包的,所以第一个数据帧肯定有包含长度字段。但对于掉数据的情况下,可能有起始码,但没剩下的数据,这样可能会导致有BUG出现),计算是否有接收剩余的数据帧。如有剩余帧,则设置状态为WAIT_MESSAGE,等待接收剩余帧。如无剩余帧,则判断是否为应答包,是的话则进行等待应答信号的回调处理,并将receive_state设置为WAIT_START。如果为非应答包,则将receive_state设置为MESSAGE_RESOLVE(这样做感觉没什么意义,因为在后面的L2层数据解析的return前,会将receive_state重新设置为WAIT_START,CRC校验错误的处理也会重新设置为WAIT_START),接着进行CRC检验,并且根据校验结果返回ACK或NACK,并进行L2层数据解析。

iv. 状态为WAIT_MESSAGE时,先判断实际接收的字节数是否等于协议包总字节数,如果未接收完成,则继续接收,并启动超时接收定时器。如果接收完成,则再次判断当前接收的数据包(注意,是数据包,而不是数据帧)是否为应答包,是的话则进行等待应答信号的回调处理,并将receive_state设置为WAIT_START。如果为非应答包,则将receive_state设置为MESSAGE_RESOLVE,接着进行CRC检验,并且根据校验结果返回ACK或NACK,并进行L2层数据解析。此部分操作与在WAIT_START一样。

v. 当状态为MESSAGE_RESOLVE时,则表示目标接收的协议包应该为应答包,接收到数据后进行相应的回调处理。这个地方好像永远都不会进入的,因为在后面的L2层数据解析的最后,会将receive_state重新设置为WAIT_START,CRC校验错误的处理也会重新设置为WAIT_START。

 

 



3. 发送数据

1) 数据/资源说明

a) 包、帧

协议包为一组完成的协议数据,里面包含起始码、命令码、数据码和校验码等。由于蓝牙传输一次最多传输20个字节,因此对于一些大于20字节的协议包会分成多帧发送。如一个45字节的协议包,会为成两个20的帧和一个5字节的帧,分3次发送。

 

b) 首帧说明

Nordic蓝牙芯片的蓝牙数据发送也是一样,一次最多只能发送20个字节。duBand的发送协议传输再在此基础进行进一步的拆分,即先发送L1 Header(协议包的前8个字节),根据协议文档,这8个字节包括起始码、标志码、长度字段(2字节)、CRC(2字节)和协议序列号(2字节)。

 

c) 首帧缓冲-global_L1_header_buffer[]

此数据组中存储着协议包的首帧8个字节的数据,每一个数据包的传输先传输这个帧。也可以理解为该缓冲用于存放协议L1层的数据。

 

d) 数据缓冲-global_reponse_buffer[]

首帧之后的协议指令、数据存储的缓冲。这里存储着命令码、KEY码、KEY数据长度及数据,即首包后的剩下的数据。也可以理解为该缓冲用于存放协议L2层的数据。

 

e) g_ack_package_buffer

用于存储当前发送的ACK协议包数据。

 

f) g_next_L1_send_content_buffer

指向下一个待发送的缓冲数组。

 

g) static L1_Send_Content sendContent[MAX_SEND_TASK]

该结构为发送数据缓冲,目前MAX_SEND_TASK的大小定义为1。该结构里面包括isUsed元素,用于指示该缓冲是否被使用。

 

h) 发送等待定时器

每一个数据包发送完成后,都需要等待一个ACK响应。通过register_wait_response()函数设置响应的ACK数据(主要是序列号),如果在定时器时间超时时都未收到正确的响应,则定时器delay_send_wait_timer调用相关函数重发。

 

i) 发送成功回调

通过set_complete_callback()设置蓝牙L0层数据发送成功的回调(有点类似于发送完成中断),主要是处理有数据等待发送时的队列。

 

j) current_task_type

记录当前发送程序的状态,分别为TASK_NONE-空闲状态,TASK_DATA-发送数据包状态和TASK_ACK-发送应答包状态。

 

k) L1层发送数据内容-L1_Send_Content

 

l) L2层发送数据内容- L2_Send_Content

 

2) L0层

L0层的发送调用ble_nus_send_string()进行发送,此函数主要的两个参数为,发送缓存指令与发送的数据长度。进行移植时,主要实现这个函数的即可。

 

3) L1层

a) L1_send()应用

以下以return_sys_time()操作为例,分析L1_send()的应用,并通过实例去理解发送数据的流程。如下图所示为return_sys_time()函数的实体。

 

 

程序先设置数据缓冲-global_reponse_buffer[]的值,将返回系统时间的指令、KEY、长度和当前时间值放入该缓冲。也可以当作为L2层的协议数据。

接着设置L2_Send_Content,并以其作地参数,调用L1_send()。

 

b) uint32_t L1_send(L2_Send_Content * content)

发送数据主要由于L1_send()开始。如下图所示,先判断是否有可用的缓冲,如没有,则直接返回。有可用缓冲,则设置err_code为0,表示无错误,然后继续运行。

 

如有缓冲可用,则先设置global_L1_header_buffer[]数组,此数组发协议包的首帧数组,根据协议文档,这8个字节包括起始码、标志码、长度字段(2字节)、CRC(2字节)和协议序列号(2字节)。

 

设置接下来的帧的数据指针,将发送的数据内容传到下级函数调用。

 

 

最后调用schedule_async_send()函数,进行发送同步。

 

 

c) void schedule_async_send(void * contenxt,SEND_TASK_TYPE_T task)

函数内部先判断发送的类型是普通数据或ACK,通过输入参数2判断。所以,该函数也主要是用来判断是发送普通协议包还是发送ACK/NACK回应包。

 

d) async_send()

schedule_async_send()再调用此函数进行进一步的数据处理,并在这里调用L0层的发送函数ble_nus_send_string()。

如下图所示,程序先判断当前发送程序的状态,如为空闲状态,则直接赋予新的状态(输入参数2的值);如当前程序状态为发送ACK,新的状态为发送DATA,则将要发送的内容赋值给g_next_L1_send_content_buffer指针,并将next_task_type设置为发送数据,下次进行调度发送;如上当前发送状态为DATA,新的状态为ACK,则,直接设置next_task_type为ACK,下次进行调度发送。这样就会出现一种情况,则如果当前为发送DATA,新的发送状态也为DATA时,程序是不会返回。

 

 

如当前发送状态为空闲状态,则马上进行发送。程序先根据current_task_type的类型进行DATA、ACK的发送流程。

先分析发送ACK的流程,如下图所示,(1)先调用construct_response_package()函数组装ACK协议包数据。接着调用L0层发送函数ble_nus_send_string()进行数据发送。(2)发送成功后,则清空占用的ACK数据,并判断下一类型发送数据是否为DATA,如果是,则接着发送DATA(在不退出函数的情况下,通过goto直接跳转),否则直接退出。(3)调用L0发送后,如果返回值为没有空闲的BUFFER(BLE_ERROR_NO_TX_BUFFERS),则设置发送完成中断的回调函数(set_complete_callback()),使蓝牙发送完成后再进入schedule_async_send()函数处理。(4)如果调用L0发送函数后,返回其它类型的错误值,则直接丢弃该次传输的数据,复位所有占用的资源后退出。这里有一个问题,就是if()的判断是多余的,因为在前面已经设置了next_task_type为TASK_NONE,因此永远不会进入if里面运行。

 

 

接下来,分别发送数据的流程,如下图所示。(1)先判断是否要发送首帧,如需要,则设置发送内容为首帧,并发送。(2)判断是否有数据未发送完成(因为BLE一次只能发20个字节,因此可能出现一条指令的L2层被分成多个帧发送,(通过content ->contentLeft不为0进行判断),如有,则设置发送的内容为剩下的数据。(3)如果content ->contentLeft为0,则说明没有数据需要发送了,设置sendContentType为CONTENT_NONE。(4)退出循环,并退出函数。如果有数据发送,则调用ble_nus_send_string()发送数据。

 

 

函数接着运行下去(L0发送调用后),判断ble_nus_send_string()的返回值error_code,并进行相应的处理。如返回值错误,则与上面发送ACK时的处理一样,(1)如果返回值为没有空闲的BUFFER(BLE_ERROR_NO_TX_BUFFERS),则设置发送完成中断的回调函数(set_complete_callback()),使蓝牙发送完成后再进入schedule_async_send()函数处理。如果调用L0发送函数后,(2)返回其它类型的错误值,则直接丢弃该次传输的数据,复位所有占用的资源后退出。唯一的不同,发送数据时还会判断next_task_type是否为ACK,如果为ACK,则还要goto到ACK发送的LABEL上。如下图所示。

 

ble_nus_send_string()返回值正确的话则根据sendContentType的值进行相应处理,(1)如果该值为CONTENT_NONE,则不做任何处理;(2)如果该值为CONTENT_HEADER时,则清空L1_header_need_schedule的数据(根据代码的结构,此处也是永远不会进入的)。(3)如果该值为CONTENT_DATA,则判断是否还有数据需要发送(通过content ->contentLeft不为0进行判断),(4)没有数据需要进行发送,则清空所占用的资源(清空发送完成回调函数,设置current_task_type为TASK_NONE),并通过register_wait_response()设置等待的应答包信息(主要是协议包序列号),在接收处理中,调用response_package_handle()会处理返回的ACK。(5)接着启动重发定时器(在没收到ACK回应时,定时重发)。(6)最后判断是否有next_task_type是否为TASK_ACK,如果是并且g_ack_package_buffer中有数据,则goto到ACK发送的LABEL上。(7)如果sendContentType的值为CONTENT_ACK并且g_ack_package_buffer有数据,则清空ACK发送占用的资源。

 

 

e) 重发定时器服务函数-delay_send_func()

(1)程序先判断发送的内容是否存在,如不存在,则直接return。(2)如有数据存在 ,判断协议包是否完成发送(通过判断content->contentLeft是否为0),(3)先判断重试的次数是否超出范围,(4)没有超出,则调用L1_resend_package()重发。(5)如重试次数超出范围,则清空占用资源,并设置NULL的发送完成回调。(6)如果设置了发送回调函数,则还需要调用回调函数,告诉发送者发送失败。

 

 

f) 协议包重发-L1_resend_package()

由于协议包重新发送时,其协议序列号会改变,因此这里重新填充一次首帧,并调用schedule_async_send()发送数据。

 



数字IC后端新手必看:Innovus从安装到跑通第一个设计的完整流程(附常用命令清单) 本文为数字IC后端设计新手提供了Cadence Innovus工具的完整实战指南。详细介绍了从环境搭建、软件安装与许可证配置,到加载网表、库文件和约束,并逐步完成布局规划、单元布局、时钟树综合、布线及物理验证的核心使用流程。文章最后附有常用Tcl命令清单,帮助初学者快速上手并跑通第一个设计。 阅读详情

相关推荐

Weblogic安装与部署

27.免密登录,在目录Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain\servers\AdminServer和Oracle\Middleware\Oracle_Home\user_projects\domains\base_domain\servers\Server-TA目录下,新建security\boot.properties文件,13.点击新建按钮,填写服务器名称,服务器监听地址,再点击下一步,最后点击完成。

m0_46259466的博客 6635

Seelen-UI与DigitalOcean合作:云端开发环境的完美整合实践

在当今数字化时代,云端开发环境已成为开发者的必备工具。Seelen-UI作为一款功能强大的Windows桌面环境定制工具,通过与DigitalOcean云服务的深度整合,为开发者提供了前所未有的云端开发体验。这款完全可定制的桌面环境不仅优化了本地工作流程,更将开发效率提升到新的高度。 ## 🌟 云端开发环境的核心优势 Seelen-UI与DigitalOcean的整合为开发者带来了多重便利。

gitblog_00022的博客 529

HC32F460的GPIO重映射实战:如何用3步搞定SPI引脚冲突问题

本文详细介绍了HC32F460的GPIO重映射功能,通过三步操作解决SPI引脚冲突问题。文章解析了HC32F460的FuncGroup功能分组机制,提供了具体的寄存器配置步骤和验证技巧,并对比了与传统方案的性能优势,帮助工程师灵活配置引脚,优化硬件设计。

t4y5u6i7o的博客 918

【亲测免费】 百度可穿戴设备项目:Duband 智能手环开发框架

**Duband** 是百度可穿戴团队推出的一款开源智能手环开发框架,其主要目标是简化智能手环应用的开发流程,加速产品迭代。此项目在GitHub上托管,链接为 [https://github.com/baiduwearable/duband.git](https://github.com/baiduwearable/duband.git)。项目采用了多种编程语言来构建其核心部分,其中主要包含 **...

gitblog_00835的博客 699

基于集成运放心电放大器设计(原创)

原创作者:einyboy or alert 最近在看美剧《危机边缘》关于边缘科学的案件,科技控可以看一下,但建议不在吃东西的时候看。想像力是创造的思想源头,动手能力则是创造的车床。 自认为是没有想像力的那类,只能发挥动手能力了。 心电测量简介 图1 心电极接线图 如图1心电测试一般使用3个心电极进行测量,各个电极的说明如下: RA: Right Arm缩写,右手电极 LA: L...

huandaohack的专栏 6922

基于ST32 DAC7311数模转换器驱动代码及电路设计完整版

基于ST32的DAC7311模数转换器的电路设计及驱动代码完整版

qq_39626643的博客 3716

百度可穿戴设备项目杜邦(Duband)快速入门指南及问题解决方案

**百度杜邦(Duband)** 是一个开源项目,专注于提供智能手环的开发解决方案。此项目基于 **Apache-2.0** 许可证发布,旨在支持开发者快速构建自己的智能手环应用。官方网站位于 [dulife.baidu.com](http://dulife.baidu.com/),并提供了官方文档和教程以辅助开发者。项目主要使用的编程语言包括 **C**(占比74.0%)、**Objective...

gitblog_00070的博客 442

VMware Workstation Pro 17 最新破解版安装教程(附永久激活密钥)

本文详细介绍了VMware Workstation Pro 17专业版的安装与配置指南,包括系统要求检查、官方版本安装流程、高级配置优化及常见问题排查。通过合规使用官方版本,开发者可以充分利用其强大的虚拟化能力,提升多平台开发与测试效率。

weixin_29327977的博客 3530

duBand源码分析

Duband源码下载地址:http://pan.baidu.com/s/1uBFFC 1.Main函数(main.c) a) gpio_init(); //初始化全部IO口为初始状态(输入、关上拉、关复用) b) pre_init(); //初始化时钟与RTC0并进行待机,待RTC0中断唤醒。用此方式待电源稳定 c) init1(); //初始化LED、MOTOR、TIMER、ADC、

lshiceman的专栏 4567

UCIE Specification详解(五)

Logical Physical Layer(逻辑物理层)

Crazzy_M的博客 1219

Allegro基本规则设置指导书之Spacing Region

当网络需要在Region中需要按照自身的规则匹配时,可以创建Region Class来匹配。Region也支持Class to Class规则,点击ok即可。Allegro基本规则设置指导书之Spacing Region。下面介绍基本规则设置指导书之Spacing Region。给创建好的Region 匹配一个规则。空白的地方创建一个Region。

weixin_54787054的博客 563

duBand源码分析-存储部分

一、编写说明 对duBand源码的存储部分进行分析,本文档用于记录分析过程。 二、源码分析 1. 存储区域划分 在使用nRF Studio进入程序下载时,可以看出,存储区域分为三个Region,如下图所示,分别存在蓝牙协议栈、应用程序和Bootloader。     在APP中,程序存储区域再细分为APP、DATA和SWAP区,通过阅读源码,得知区域图详细划分如下图所示(注意:d

lshiceman的专栏 1665

杰理SDK入门教程(一):编译环境的安装和程序下载

本文档提供了杰理SDK在Windows系统下的开发环境配置与代码编译指南。主要内容包括:1) 开发环境安装步骤,需下载Code::Blocks和杰理工具链;2) 编译过程中常见问题的解决方案,如安全警告提示和链接文件缺失问题;3) 代码编译流程说明,包括打开工程、连接开发板、执行编译等步骤。文档特别强调了强制升级模式的操作方法,并指出需避免点击运行/调试按钮。最后提示了杀毒软件可能误报工具链的注意事项。

qq_42014563的博客 2736

【NRF51822】百度手环开源源码分析--底层通讯部分

一、编写说明 duBand源码中的通讯部分源码较多,但细细阅读发现其分层结构清晰,很值得借鉴。为了深入对duBand通讯源码的学习,并理解通讯分层设计的思想,决定对duBand源码的通讯部分进行分析,本文档用于记录分析过程。 二、源码分析 1. 层次说明 通讯部分主要分为三个层,这三个层主要的功能划分如下: L0层:负责从硬件电路上读、写数据。移植时主要重写该层函数。 L1层:负责对协...

丨晋丨 2871

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【NRF51822】百度手环开源源码分析--存储部分

一、编写说明 对duBand源码的存储部分进行分析,本文档用于记录分析过程。 二、源码分析 1. 存储区域划分 在使用nRF Studio进入程序下载时,可以看出,存储区域分为三个Region,如下图所示,分别存在蓝牙协议栈、应用程序和Bootloader。     在APP中,程序存储区域再细分为APP、DATA和SWAP区,通过阅读源码,得知区域图详细划分如下图所示(注意:du...

丨晋丨 1791

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

duband源码总结

一、BLE相关参数 1、广播间隔参数:快速广播模式:                                                            #define APP_ADV_INTERVAL_FAST       0x0050     /**

错过以后 终究是路人 963

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

duBand源码分析-数据自动同步部分

1.        void send_all_data(boolis_from_cb) 发送数据都是通过send_all_data()去上传的。   1)       static boolneed_send_sync_process(void ) 判断是否需要进行同步,在send_all_data()一开始时调用。当sync_process_start_send等于false并且有数据

lshiceman的专栏 1275

百度手环 资料

https://github.com/baiduwearable/duband

时刻学习 1212

NRF51822蓝牙服务(4)——串口透传

前言 这一次我们尝试创建一个串口服务来实现手机与开发板的数据透传,其实无非就跟学习创建LED读写服务的时候一样创建一个私有服务而已。废话少说,我们直接开始实验。 实验分析 首先,我们要知道NRF51822的串口通信引脚:RX:P0.11,TX:P0.9,RTS:P0.10,CTS:P0.8。 接着,新建串口驱动文件:ble_nus.c和ble_nus.h ...

SammySum的博客 4087

BLE-NRF51822教程4-串口BLE解析

本讲逐行代码解析官方串口BLE例程demo PS: 基于SDK5.1   主要分一下几个部分: 1 :Main函数的整体注释 2 :函数单独解析。 3 :接收串口数据并发送给对端设备 4 :接收手机数据并通过串口打印   Ps :第一和第二部分我在教程工程初始化流程中已经详细说明这里直接复制过来,做了一些修改以及添加了关于添加服务和添加特征值的讲解,如果之前看过可以直接看下 2函

技术无止境 7863

dubbo教程

https://blog.youkuaiyun.com/u014401141/article/details/71248550   windows安装注册中心和监测中心 https://www.cnblogs.com/lionsblog/p/7767379.html

qq_32814555的博客 369

99And源码自制教程Tinder最新技术教程原理讲解直接秒赞

只要能利用活号技术什么社交软件都能拿下交友功能99And源码-99源码网只要能利用活号技术什么社交软件都能拿下交友功能 就如我发现的一个良心管理.什么活号技术.,实现批量+好友 99And源码-99源码网http://www.andxuan.com/ “浏览器指纹”是一种通过浏览器对网站可见的配置和设置信息来跟踪Web浏览器的方法。 每个浏览器指纹信息拥有唯一的ID识别码,将由您的系统、安全浏览器、插件和其他参数所有可用信息组成。 AdsPower指纹浏览器已涵盖左侧所有指纹信息设置 ...

Cc991的博客 2215

dubbo应用配置

方式一:jpa+web 一、项目结构: (1)api: (2)server (3)web 二、项目代码 1 api 1.1 Speaking接口代码:package com.ming.api;/** * * @author ming * */ public interface Speaking { /** * 个人speaking接口

小猪奋斗 2346

VS2010+Opencv249 图像叠加 添加水印

C++: void Mat::copyTo(OutputArray m) const C++: void Mat::copyTo(OutputArray m, InputArray mask) const 这个函数可以复制图像到另一个图像或矩阵上,可选参数是掩码 由于叠加的图像大小不一定相等,比如我们这里把一张小照片加到一张大照片上 我们可以在大照片上设置一个和小照片一样大的感兴趣区域

raywang5288的博客 34万+
上一篇: 在TabActivity中无法使用bindService的解决方法
下一篇: duBand源码分析-存储部分
lshiceman
博客等级 码龄18年 31粉丝 44原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值