串口助手是怎么做出来的 :第一节,串口助手界面的实现及串口通信原理的介绍

文/樊晓鑫

python3.7和wxPython版。

要想做出串口助手软件,首先得深入理解串口的原理。

不知道的,可以百度一下,有条件的最好可以用单片机具体实现一下,这样一圈下来保证,你可以理解什么是串口以及它的协议。

来,首先,来了解一下串口。

串口通讯结构图

1、UART通信协议

 UART作为异步串口通信协议的一种,工作原理是将传输数据的每一个字符一位一位地传输。其中每一位(bit)的意义如下:

      起始位:先发出一个逻辑“0”的信号,表示传输字符开始。

      数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。

      奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以次来校验数据传送的正确性。

      停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率也就越慢。

      空闲位:处于逻辑“1”状态,表示当前线路上没有数据传输
如下图所示:

2、UART工作原理

      发送数据过程:空闲状态,线路处于高电平;当收到发送指令后,拉低线路的一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位,一帧数据发送完成。

      数据接收过程:空闲状态,线路处于高电平;当检测到线路的下降沿(高电平变为低电平)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备接收数据或存入缓冲。

      由于UART是异步传输,没有传输同步时钟,为了保证数据的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误吗。一般UART一帧的数据位数为8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。

      UART的接收数据时序为:当检测到数据的下降沿时,表明线路上有数据进行传输,这是计数器CNT开始计数,当计数器为24=16+8时,采样的值为第0位数据;当计数器的值为40时,采样的值为第一位数据,依次类推,进行后面6个数据的采样。如果需要进行奇偶校验,则当计数器的值为152时,采样的值即为奇偶位;当计数器的值为168时,采样的值为“1”表示停止位,数据接收完成。

      一个标准的10位异步串行通信协议(1个起始位、1个停止位和8个数据位)收发时序,如下图所示:

3、UART应用场景。

串口是一种非常通用的设备通信的协议(不要与通用串行总线Universal Serial Bus(USB)混淆)。大多数计算机包含两个基于RS232的串口。串口同时也是仪器仪表设备通用的通信协议;很多GPIB兼容的设备也带有RS-232口。同时,串口通信协议也可以用于获取远程采集设备的数据。

串口通信的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。
典型地,串口用于ASCII码字符的传输。通信使用3根线完成:(1)地线,(2)发送,(3)接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通行的端口,这些参数必须匹配。

当你理解了,串口之后,也有助于理解其他的I2C,SPI等协议。

要想做出串口助手软件,我就从基本的概念介绍一下,这样子,我们心中可以有个数。

而后,可以首先完成界面的安排。

第一步,概念的介绍。

1,串口选择。首先我们用的电脑作为上位机,要想和下位机进行信号互动,得先选择一个固定的串口号,然后才可以发一些想发信号给下位机。

2,波特率。不言而喻,波特率可以具象理解为就是一个所谓的clock信号,这是上位机和下位机之间信号交流的基准。只有上位机和下位机的比特率一致,这样子才不会出现信号失真或者乱码的现象。这是一个衡量符号传输速率的参数。它表示每秒钟传送的符号的个数。例如300波特表示每秒钟发送300个符号。当我们提到时钟周期时,我们就是指波特率,例如如果协议需要4800波特率,那么时钟是4800Hz。这意味着串口通信在数据线上的采样率为4800Hz。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比。高波特率常常用于放置的很近的仪器间的通信,典型的例子就是GPIB设备的通信。

3,数据位。这是衡量通信中实际数据位的参数。当计算机发送一个信息包,实际的数据不会是8位的,标准的值是5、6、7和8位。如何设置取决于你想传送的信息。比如,标准的ASCII码是0~127(7位)。扩展的ASCII码是0~255(8位)。如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。由于实际数据位取决于通信协议的选取,术语“包”指任何通信的情况。

4,校验位。在串口通信中一种简单的检错方式。有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,串口会设置校验位(数据位后面的一位),用一个值确保传输的数据有偶个或者奇个逻辑高位。例如,如果数据是011,那么对于偶校验,校验位为0,保证逻辑高的位数是偶数个。如果是奇校验,校验位为1,这样就有3个逻辑高位。高位和低位不是真正的检查数据,简单置位逻辑高或者逻辑低校验。这样使得接收设备能够知道一个位的状态,有机会判断是否有噪声干扰了通信或者是否传输和接收数据是否不同步。

发送数据做如下图示,data byte MSB 与 Parity 重合。

先刨去 Parity bit,分析 LSB ~ MSB 的纯数据:

    > 假定数据中的 ‘1’ 个数为奇数,偶校验:Parity = ‘1’,奇校验:Parity = ‘0’

    > 假定数据中的 ‘1’ 个数为偶数,偶校验:Parity = ‘0’,奇校验:Parity = ‘1’

 将Parity bit 写入 MSB 位置做替换,这可以理解为编码的过程

接收端收到数据后作解码操作:

根据 Parity bit 的值 + 奇偶校验方式 + data( LSB ~ bit6 ) => 算出 MSB 的值,这其实就是发送过程组合数据的逆过程

5,停止位。用于表示单个包的最后一位。典型的值为1,1.5和2位。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

6,打开/关闭串口。让选择的串口号使能,这样子,上位机和下位机就可以进行信号传输。

7,接收区/发送区。毫无疑问,接收区代表着下位机将数据传输给上位机,下位机是发送方,上位机是接收方;发送区代表着上位机将数据传输给下位机,上位机是发送方,下位机是接收方。

8,发送按钮。这按钮的功能,就是上位机将发送区的内容发送到下位机。

9,16进制。勾选上这复选框,发送区和接收区中的内容会以16进制模式显示。

10,清空按钮。就是清除发送区或者接收区中的内容。

第二步,具体的界面实现。

第三步,具体的代码如下所示。

# -*- coding: utf-8 -*-
#########################################################################################################
# pyserial   3.4
# Python 3.7.6rc1 (tags/v3.7.6rc1:bd18254b91, Dec 11 2019, 19:31:14) [MSC v.1916 32 bit (Intel)] on win32
#########################################################################################################

import wx


class serialFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u"串口助手", pos=wx.DefaultPosition,
                        size=wx.Size(559, 568), style=wx.DEFAULT_FRAME_STYLE)

        self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)

        self.panel = wx.Panel(self, -1)

        # 在屏幕中央显示界面
        self.CenterOnScreen()

        ################################################################################################################
        ################################################################################################################
        sergrid = wx.GridBagSizer(0, 0)
        sergrid.SetFlexibleDirection(wx.BOTH)
        sergrid.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

        self.m_staticText1 = wx.StaticText(self.panel, wx.ID_ANY, u"串口选择", wx.Point(1, 1), wx.DefaultSize, 0)
        self.m_staticText1.Wrap(-1)
        sergrid.Add(self.m_staticText1, wx.GBPosition(0, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_rcvtext = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(300, 200),
                                     wx.TE_AUTO_URL | wx.TE_LEFT | wx.TE_MULTILINE)
        sergrid.Add(self.m_rcvtext, wx.GBPosition(0,2), wx.GBSpan(8,2), wx.ALL, 6)

        self.m_send2but = wx.Button(self.panel, wx.ID_ANY, u"发送2", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add( self.m_send2but, wx.GBPosition(9, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr2but = wx.Button(self.panel, wx.ID_ANY, u"清空2", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_clr2but, wx.GBPosition(9, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_send3but = wx.Button(self.panel, wx.ID_ANY, u"发送3", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_send3but, wx.GBPosition(10, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr3but = wx.Button(self.panel, wx.ID_ANY, u"清空3", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add( self.m_clr3but, wx.GBPosition( 10, 1 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 )

        self.m_send3but = wx.Button(self.panel, wx.ID_ANY, u"发送4", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_send3but, wx.GBPosition(11, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr4but = wx.Button(self.panel, wx.ID_ANY, u"清空4", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_clr4but, wx.GBPosition(11, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_send5but = wx.Button(self.panel, wx.ID_ANY, u"发送5", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_send5but, wx.GBPosition(12, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr5but = wx.Button(self.panel, wx.ID_ANY, u"清空5", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_clr5but, wx.GBPosition(12, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_send6but = wx.Button(self.panel, wx.ID_ANY, u"发送6", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_send6but, wx.GBPosition(13, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr6but = wx.Button(self.panel, wx.ID_ANY, u"清空6", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_clr6but, wx.GBPosition(13, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        m_comsetChoices = [u"COM1", u"COM2", u"COM3", u"COM4", u"COM5", u"COM6", u"COM7", u"COM8", u"COM9", u"COM10"]
        self.m_comset = wx.ComboBox(self.panel, wx.ID_ANY, u"COM1", wx.DefaultPosition, wx.DefaultSize, m_comsetChoices,
                                    0)
        self.m_comset.SetSelection(0)
        sergrid.Add(self.m_comset, wx.GBPosition(0, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_openser = wx.Button(self.panel, wx.ID_ANY, u"打开串口", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add( self.m_openser, wx.GBPosition(5, 0), wx.GBSpan(1, 1), wx.ALL, 8)

        self.m_clrRcvText = wx.Button(self.panel, wx.ID_ANY, u"清空接收", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_clrRcvText, wx.GBPosition(6, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_rcvBox = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_rcvBox, wx.GBPosition(6, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_send1but = wx.Button(self.panel, wx.ID_ANY, u"发送1", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_send1but, wx.GBPosition(8, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_clr1but = wx.Button(self.panel, wx.ID_ANY, u"清空1", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add( self.m_clr1but, wx.GBPosition( 8, 1 ), wx.GBSpan( 1, 1 ), wx.ALL, 5 )

        self.m_textCtrl5 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1),
                                    wx.HSCROLL | wx.TE_AUTO_URL)
        sergrid.Add(self.m_textCtrl5, wx.GBPosition(8, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox2 = wx.CheckBox( self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox2.SetValue(True)
        sergrid.Add( self.m_checkBox2, wx.GBPosition(8, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox3 = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox3.SetValue(True)
        sergrid.Add(self.m_checkBox3, wx.GBPosition(9, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox4 = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox4.SetValue(True)
        sergrid.Add(self.m_checkBox4, wx.GBPosition(10, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox5 = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox5.SetValue(True)
        sergrid.Add(self.m_checkBox5, wx.GBPosition(11, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox6 = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox6.SetValue(True)
        sergrid.Add(self.m_checkBox6, wx.GBPosition(12, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_checkBox7 = wx.CheckBox(self.panel, wx.ID_ANY, u"16进制", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_checkBox7.SetValue(True)
        sergrid.Add(self.m_checkBox7, wx.GBPosition(13, 3), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_textCtrl6 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), 0)
        sergrid.Add(self.m_textCtrl6, wx.GBPosition(9, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_textCtrl7 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), 0)
        sergrid.Add(self.m_textCtrl7, wx.GBPosition(10, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_textCtrl8 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), 0)
        sergrid.Add(self.m_textCtrl8, wx.GBPosition(11, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_textCtrl9 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), 0)
        sergrid.Add(self.m_textCtrl9, wx.GBPosition(12, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_textCtrl10 = wx.TextCtrl(self.panel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size(200, -1), 0)
        sergrid.Add(self.m_textCtrl10, wx.GBPosition(13, 2), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_closeser = wx.Button(self.panel, wx.ID_ANY, u"关闭串口", wx.DefaultPosition, wx.DefaultSize, 0)
        sergrid.Add(self.m_closeser, wx.GBPosition(5, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_staticText6 = wx.StaticText(self.panel, wx.ID_ANY, u"校验位:", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticText6.Wrap(-1)
        sergrid.Add(self.m_staticText6, wx.GBPosition(3, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_staticText7 = wx.StaticText(self.panel, wx.ID_ANY, u"停止位:", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticText7.Wrap(-1)
        sergrid.Add(self.m_staticText7, wx.GBPosition(4, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        m_comboBox4Choices = [u"None", u"Odd", u"Even", u"Mark", u"Space"]
        self.m_comboBox4 = wx.ComboBox(self.panel, wx.ID_ANY, u"None", wx.DefaultPosition, wx.DefaultSize,
                                    m_comboBox4Choices, 0)
        sergrid.Add(self.m_comboBox4, wx.GBPosition(3, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        m_comboBox5Choices = [u"1", u"1.5", u"2"]
        self.m_comboBox5 = wx.ComboBox(self.panel, wx.ID_ANY, u"1", wx.DefaultPosition, wx.DefaultSize,
                                    m_comboBox5Choices, 0)
        sergrid.Add(self.m_comboBox5, wx.GBPosition(4, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_staticText5 = wx.StaticText(self.panel, wx.ID_ANY, u"数据位:", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticText5.Wrap(-1)
        sergrid.Add( self.m_staticText5, wx.GBPosition(2, 0), wx.GBSpan(1, 1), wx.ALL, 5)

        m_comboBox2Choices = [u"9600", u"19200", u"38400", u"57600", u"115200", u"230400", u"460800", u"921600",
                            wx.EmptyString, wx.EmptyString]
        self.m_comboBox2 = wx.ComboBox(self.panel, wx.ID_ANY, u"9600", wx.DefaultPosition, wx.DefaultSize,
                                    m_comboBox2Choices, 0)
        sergrid.Add(self.m_comboBox2, wx.GBPosition(1, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        m_comboBox3Choices = [ u"8", u"7", u"6", u"5" ]
        self.m_comboBox3 = wx.ComboBox(self.panel, wx.ID_ANY, u"8", wx.DefaultPosition, wx.DefaultSize,
                                    m_comboBox3Choices, 0)
        sergrid.Add(self.m_comboBox3, wx.GBPosition(2, 1), wx.GBSpan(1, 1), wx.ALL, 5)

        self.m_staticText4 = wx.StaticText(self.panel, wx.ID_ANY, u"波特率:", wx.DefaultPosition, wx.DefaultSize, 0)
        self.m_staticText4.Wrap(-1)
        sergrid.Add(self.m_staticText4, wx.GBPosition(1, 0), wx.GBSpan(1, 1), wx.ALL, 5)
        ################################################################################################################
        # layout
        self.panel.SetSizerAndFit(sergrid)
        self.panel.Layout()

        self.Centre(wx.BOTH)
        ################################################################################################################
        ################################################################################################################


class SerialAPP(wx.App):
    def __init__(self):
        # 如果要重写 __init__, 必须调用wx.App的__init__,否则OnInit方法不会被调用
        wx.App.__init__(self)

    def OnInit(self):
        self.frame = serialFrame(None)
        self.frame.Show(True)

        return True

# 主函数


if __name__ == "__main__":
    app = SerialAPP()
    # 循环监听事件
    app.MainLoop()

 

  • 6
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
与硬件通信的程序基本上要用到串口,虽然qt5以后集成了串口通信类,但是个人觉得那个串口通信类有点问题,在linux上表现很好,windows上大数据会有怪怪的问题出现,而且只能在qt5以上的版本才能用,无奈大部分的嵌入式linux上还停留在4.7.1到4.8.5左右的版本,所以本人一直喜欢用第三方的串口通信处理。 程序调试中经常需要串口调试,甚至还需要模拟设备数据回复,甚至串口转网络出去,特意将这些常用功能都到一个串口调试助手中去。 基本功能: 1:支持16进制数据发送与接收。 2:支持windows下COM9以上的串口通信。 3:实时显示收发数据字节大小以及串口状态。 4:支持任意qt版本,亲测4.7.0 4.8.5 4.8.7 5.4.1 5.7.0 5.8.0。 5:支持串口转网络数据收发。 高级功能: 1:可自由管理需要发送的数据,每次只要从下拉框中选择数据即可,无需重新输入数据。 2:可模拟设备回复数据,需要在主界面开启模拟设备回复数据。当接收到设置好的指令时,立即回复设置的回复指令。例如指定收到0x16 0x00 0xFF 0x01需要回复0x16 0x00 0xFE 0x01,则只需要在SendData.txt中添加一条数据16 00 FF 01:16 00 FE 01即可。 3:可定时发送数据和保存数据到文本文件:,默认间隔5秒钟,可更改间隔时间。 4:在不断接收到大量数据时,可以暂停显示数据来查看具体数据,后台依然接收数据但不处理,无需关闭串口来查看已接收到的数据。 5:每次收到的数据都是完整的一条数据,而不是脱节的,了延时处理。 6:一套源码随处编译,无需更改串口通信类,已在XP/WIN7/UBUNTU/ARMLINUX系统下成功编译并运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值