zynq实现udp网络的数据包的上传(zynq7z035)(ps端)

1、前面的文件的建立,参考另外的一篇blog

https://blog.csdn.net/weixin_42066185/article/details/106421611

2、直接进入udp建立的那个部分

对于udp的建立,在2018版本的vivado 上的,有相关对应的例程,然而,我是用的版本的是2017,当前并无相关udp的通信例程,因此,我主要参考的都是如下的参考的链接:

https://blog.csdn.net/FPGADesigner/article/details/88746532

https://blog.csdn.net/FPGADesigner/article/details/88748846(最主要参考)

https://blog.csdn.net/FPGADesigner/article/details/88751502

3、主函数建立过程

(1) 整体建立过程

	/*
	 * 设置开发板MAC地址
	 * 开启中断系统
	 * 设置本地IP地址
	 * 初始化lwIP
	 * 添加网络接口
	 * 设置默认网络接口
	 * 启动网络
	 * 初始化TCP或UDP连接(自定义函数)
	 * */

(2)设置开发板的mac地址

	struct netif *netif;
	struct ip_addr ipaddr, netmask, gw;

	/* the mac address of the board. this should be unique per board */ //设定开发板的MAC地址
	unsigned char mac_ethernet_address[] = {
		0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

	netif = &server_netif;

热555555555555555555555(3) 初始化平台,开启中断

init_platform();  //开启中断系统,这个里面设定的时定时中断

(4)初始化网卡的ip

/* initliaze IP addresses to be used */
	IP4_ADDR(&ipaddr,  192, 168,   127, 202);
	IP4_ADDR(&netmask, 255, 255, 255,  0);
	IP4_ADDR(&gw,      192, 168,   127,  254);
	print_app_header();
	lwip_init();

(5)  初始化lwip, 添加网卡的netlif , 并且将这个网卡设置成默认网卡


  	/* Add network interface to the netif_list, and set it as default */
	if (!xemac_add(echo_netif, &ipaddr, &netmask,
						&gw, mac_ethernet_address,
						PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\n\r");
		return -1;
	}
	netif_set_default(echo_netif);

	/* now enable interrupts */
	platform_enable_interrupts();

	/* specify that the network if is up */
	netif_set_up(echo_netif);

	print_ip_settings(&ipaddr, &netmask, &gw);

(6)初始化udp

	/* start the application (web server, rxtest, txtest, etc..) */
//	start_application();
	xil_printf("初始化udp网络/r/n");
	user_udp_init();            //初始化UDP

(7) 循环初始化


	while (1) {
		xil_printf("we are sending \r\n");
		/*  将MAC队列中的包传输的LwIP/IP栈中   */
		xemacif_input(netif);
//		if (udp_connected_flag) { //发送
		sleep(1);
//			xil_printf("we are in if nei\r\n");
		udp_printf();
//		}
	}

	/* never reached */
	cleanup_platform();

整个主函数文件内容:

/******************************************************************************
*
* Copyright (C) 2017 Xilinx, Inc.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* XILINX  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/

#include <stdio.h>
#include "xparameters.h"
#include "netif/xadapter.h"
#include "platform.h"
#include "platform_config.h"
#include "lwipopts.h"
#include "xil_printf.h"
#include "sleep.h"

#include "sleep.h"

#include "sys_intr.h"
#include "lwip/init.h"
#include "lwip/inet.h"
#include "xil_cache.h"
//#include "ip_addr.h"


#include "user_udp.h"


#if LWIP_DHCP==1
#include "lwip/dhcp.h"
extern volatile int dhcp_timoutcntr;

#endif
static  XScuGic Intc;   //GIC
extern unsigned udp_connected_flag;
extern volatile int TcpFastTmrFlag;
extern volatile int TcpSlowTmrFlag;
//
#define DEFAULT_IP_ADDRESS	"192.168.127.202"
#define DEFAULT_IP_MASK		"255.255.255.0"
#define DEFAULT_GW_ADDRESS	"192.168.127.254"

//#define DEFAULT_IP_ADDRESS	"115.156.163.175"
//#define DEFAULT_IP_MASK		"255.255.254.0"
//#define DEFAULT_GW_ADDRESS	"115.156.163.254"

void platform_enable_interrupts(void);
void start_application(void);
void print_app_header(void);

#if defined (__arm__) && !defined (ARMR5)
#if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || \
		 XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1
int ProgramSi5324(void);
int ProgramSfpPhy(void);
#endif
#endif

#ifdef XPS_BOARD_ZCU102
#ifdef XPAR_XIICPS_0_DEVICE_ID
int IicPhyReset(void);
#endif
#endif

struct netif server_netif;

static void print_ip(char *msg, ip_addr_t *ip)
{
	print(msg);
	xil_printf("%d.%d.%d.%d\r\n", ip4_addr1(ip), ip4_addr2(ip),
			ip4_addr3(ip), ip4_addr4(ip));
}

static void print_ip_settings(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
	print_ip("Board IP:       ", ip);
	print_ip("Netmask :       ", mask);
	print_ip("Gateway :       ", gw);
}

static void assign_default_ip(ip_addr_t *ip, ip_addr_t *mask, ip_addr_t *gw)
{
	int err;

	xil_printf("Configuring default IP %s \r\n", DEFAULT_IP_ADDRESS);

	err = inet_aton(DEFAULT_IP_ADDRESS, ip);
	if (!err)
		xil_printf("Invalid default IP address: %d\r\n", err);

	err = inet_aton(DEFAULT_IP_MASK, mask);
	if (!err)
		xil_printf("Invalid default IP MASK: %d\r\n", err);

	err = inet_aton(DEFAULT_GW_ADDRESS, gw);
	if (!err)
		xil_printf("Invalid default gateway address: %d\r\n", err);
}

int main(void)
{

	/*
	 * 设置开发板MAC地址
	 * 开启中断系统
	 * 设置本地IP地址
	 * 初始化lwIP
	 * 添加网络接口
	 * 设置默认网络接口
	 * 启动网络
	 * 初始化TCP或UDP连接(自定义函数)
	 * */


	struct netif *netif;
	struct ip_addr ipaddr, netmask, gw;

	/* the mac address of the board. this should be unique per board */ //设定开发板的MAC地址
	unsigned char mac_ethernet_address[] = {
		0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };

	netif = &server_netif;
#if defined (__arm__) && !defined (ARMR5)
#if XPAR_GIGE_PCS_PMA_SGMII_CORE_PRESENT == 1 || \
		XPAR_GIGE_PCS_PMA_1000BASEX_CORE_PRESENT == 1
	xil_printf("we are in here\r\n");
	ProgramSi5324();
	ProgramSfpPhy();
#endif
#endif

//	Xil_ICacheEnable();
//	Xil_DCacheEnable();
	init_platform();  //开启中断系统,这个里面设定的时定时中断
//	Init_Intr_System(&Intc);
//	Setup_Intr_Exception(&Intc);
	//定义一个网卡
	netif = &server_netif;
	/*两种方案实现对网卡ip、子网掩码、以及网关*/
	//方案1
//	IP_ADDR(&ipaddr,  192, 168,   127, 202);
//	IP_ADDR(&netmask, 255, 255, 255, 0);
//	IP_ADDR(&gw,      192, 168,   127, 254);
	assign_default_ip(&(netif->ip_addr), &(netif->netmask), &(netif->gw));


	xil_printf("\r\n\r\n");
	xil_printf("-----lwIP RAW Mode UDP Server Application-----\r\n");


	/* initialize lwIP */
	lwip_init();
	xil_printf("we have finnished lwip init");

	/* Add network interface to the netif_list, and set it as default */
	if (!xemac_add(netif, NULL, NULL, NULL, mac_ethernet_address,
				PLATFORM_EMAC_BASEADDR)) {
		xil_printf("Error adding N/W interface\r\n");
		return -1;
	}
	xil_printf("before  the  the default set");
	netif_set_default(netif);  //将当前的网卡设定成默认的网卡
	xil_printf("after the default set");


	/* now enable interrupts */
	platform_enable_interrupts();  //开启之前设定的中断

	/* specify that the network if is up */
	netif_set_up(netif);     //启动之前所设定的网络



	print_ip_settings(&(netif->ip_addr), &(netif->netmask), &(netif->gw));

	xil_printf("\r\n");

	/* print app header */
	//print_app_header();

	/* start the application*/
	//start_application();   //初始化udp,启动服务器
	user_udp_init();


	xil_printf("\r\n");

	while (1) {
		xil_printf("we are sending \r\n");
		/*  将MAC队列中的包传输的LwIP/IP栈中   */
		xemacif_input(netif);
//		if (udp_connected_flag) { //发送
		sleep(1);
//			xil_printf("we are in if nei\r\n");
		udp_printf();
//		}
	}

	/* never reached */
	cleanup_platform();

	return 0;
}

 

 

3.1 user_udp.c 文件

#include "user_udp.h"

//---------------------------------------------------------
//                    变量定义
//---------------------------------------------------------
struct udp_pcb *connected_pcb = NULL;
static struct pbuf *pbuf_to_be_sent = NULL;
char *send_buff = "udploWorld";  //待发送字符
int *send_buffint = 100;
struct ip_addr ipaddr;

static unsigned local_port = 5002;      //本地端口
static unsigned remote_port = 8080;  //远程端口

//---------------------------------------------------------
//                  UDP连接初始化函数
//---------------------------------------------------------
int user_udp_init(void)
{
	struct udp_pcb *pcb;
	err_t err;

	/*  创建UDP控制块   */
	pcb = udp_new();
	if (!pcb) {
		xil_printf("Error Creating PCB.\r\n");
		return -1;
	}
	/*  绑定本地端口   */
	err = udp_bind(pcb, IP_ADDR_ANY, local_port);
	if (err != ERR_OK) {
		xil_printf("Unable to bind to port %d\r\n", local_port);
		return -2;
	}
	/*  设置远程地址   */
	IP4_ADDR(&ipaddr, 192, 168, 127, 200);
//	IP4_ADDR(&ipaddr, 192, 168, 1, 101);
//	IP4_ADDR(&ipaddr, 115, 156, 162, 123);
//	IP4_ADDR(&ipaddr, 115, 156, 162, 76);
	connected_pcb = pcb;

	/*  申请pbuf资源  */
	pbuf_to_be_sent = pbuf_alloc(PBUF_TRANSPORT, 10, PBUF_ROM);
	memset(pbuf_to_be_sent->payload, 0, 10);
//	memcpy(pbuf_to_be_sent->payload, (u8 *)send_buff, 4);

	return 0;
}

//---------------------------------------------------------
//                   UDP发送数据函数
//---------------------------------------------------------
void udp_printf(void)
{
	err_t err;
	struct udp_pcb *tpcb = connected_pcb;
	if (!tpcb) {
		xil_printf("error connect.\r\n");
	}
//
//	send_buff[0] ='0';
//	send_buff[1] = '1';
//	send_buff[2] = '2';
//	send_buff[3] = '3';
//	send_buff[4] = '4';
//	send_buff[5] = '5';
//	send_buff[6] = '6';
//	send_buff[7] = '7';
//	send_buff[8] = '8';
//	send_buff[9] = '9';
	*send_buff = "0123456789";

//	memset(pbuf_to_be_sent->payload, 0, 10);
	memcpy(pbuf_to_be_sent->payload, (u8 *)send_buff, 10);

	/*  发送字符串  */
	err = udp_sendto(tpcb, pbuf_to_be_sent, &ipaddr, remote_port);
	if (err != ERR_OK) {
//		xil_printf("Error on udp send : %d\r\n", err);
		return;
	}
}
void udp_senddata(int data)
{
	err_t err;
	struct udp_pcb *tpcb = connected_pcb;
	if (!tpcb) {
		xil_printf("error connect.\r\n");
	}
//	data =100;

	*send_buffint = data;
	memcpy(pbuf_to_be_sent->payload, send_buffint, 2);

	xil_printf("we are sending here:%d\r\n",*send_buffint);

	/*  发送字符串  */
	err = udp_sendto(tpcb, pbuf_to_be_sent, &ipaddr, remote_port);
	if (err != ERR_OK) {
//		xil_printf("Error on udp send : %d\r\n", err);
		return;
	}
}

3.2 user_udp.h

/*
 * usr_udp.h
 *
 *  Created on: 2020年1月16日
 *      Author: Scottar
 */


#ifndef SRC_USER_UDP_H_
#define SRC_USER_UDP_H_

#include "lwip/err.h"
#include "lwip/udp.h"
#include "lwip/init.h"
#include "lwipopts.h"
#include "lwip/err.h"
#include "lwipopts.h"
#include "netif/xadapter.h"
#include "xil_printf.h"

int user_udp_init(void);
void udp_printf(void);
void udp_senddata(int data);

#endif /* SRC_USER_UDP_H_ */

4. 过程测试

4.1 上位机接收程序

  reveiver_url = "ipc://11_Router"

    socketzmq = context.socket(zmq.ROUTER)
    socketzmq.set_hwm(HWM_VAL)
    sendinglist=[]

    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client_socket.bind(("192.168.127.200", 8080))
    print('we have connected to the tcp data send server!---port is :', port)
    packagenum = 0
    start_time_perf = time.perf_counter()
    start_time_process = time.process_time()
    count = 0
    # 实际上应当启用的市多线程来做这些事情的
    # 每一个线程要做的事情就是接收对应的内容
    # 我想epics里面做的也是基本想同样的事情  ---最后写一个自动化的脚本多线程
    start_flag = True
    while True:
        b, addr = client_socket.recvfrom(10)
        print('I',count)
        if count==1000000:
            break
        count = count + 1
        # timestample = str(datetime.datetime.now()).encode()
        # b = b + timestample
        #

            # sendinglist.append(b)


    print(packagenum)
    end_time_perf = time.perf_counter()
    end_time_process = time.process_time()
    print('Receiving port is: ', port)
    print('Package num:', count)
    print('receing time cost:', end_time_perf - start_time_perf)  #

    socketzmq.close()

4.2 以100us 发送的速率

(1)下位机的测试程序

(2)测试结果如下:

 

4.3 以10us 的速度进行发送。

(1)下位机的测试的程序

(2) 测试结果

上位机的测试结果:

(3) 上位机去掉print 打印进行结果测试

第一次测试结果

第二次测试结果:

第三次测试结果:

 

 

4.4  设定下位机的上传延时为5us

(1)上位机数据接收程序以及下位机发程序

(2)测试结果:

第一次测试结果:

 

第二次测试结果:

第三次测试结果:

 

4.5  设定在7us下的的测试结果

 

测试结论:

对于udp来说,以10us的速度进行上传,上位机的接收速度没有办法完全达到10us 的接受速度, 这样的原因,可能是由与下位机在发送的时候设定的程序的延时就是10us,但是,由于其调用udp的发送程序,其也会出现相应的时间的消耗,所以整体出现了接收1M个数据包会出现较多的耗时,经过不断的测试,可以发现将下位机的发送程序设定的延时在7us的情况下,上位机接收到1M个数据包,所消耗的时间十分接近10s,也就是100k/s的上传的速度。

 

4.6   关于udp数据上传的丢包情况验证

4.6.1 下位机的上传的的速度设定为100us

(1)上位机的测试代码

(2)以下位机以100us的速度 ,通过udp进行上传。上位机接收到数据后,打上时间标签。

(3) 测试结果如下:

可见,对于这样的速度的udp来说,也基本不会出现丢包的问题。

 

4.6.2下位机上传的速度设定为10us

(1) 下位机数据上传代码:

下面的主要代码是提前设定好了,数据包发送的速度是10us,然后每个数字每10个一轮回

(2)上位机测试代码:

(3) 测试结果

通过上面的测试结果,我们可以得出,数据包的传输会出现相对较多的丢包情况。

4.6.3 设定下位机的上传速度是7us

测试打时间标签的耗时影响。

(1)下位机测试程序代码:

(2)上位机测试程序

(3) 测试结果

第一次测试结果

第二次测试结果

第三次测试结果

第四次测试结果:

 

(4) 判断丢包情况

通过上面的测试结果,可以看到会丢包情况。

5 整体接收与转发的测试:

 

5.1 在另一台linux 电脑上进行测试数据的接收与打时标(接收1M数据的测试)

(1) 测试接收代码:

(2) 测试接收耗时:

第一次测试:

第二次测试:

 

5.2 测试接收和转发1M个数据包的耗时

(1)上位机测试代码

 

(2) 测试结果:

          

 

5.3  中间的数据监测接收模块(累积10个数据后发送)(这一节的测试是没有考虑pub和sub之间的连接的耗时所导致的数据接收,其他地方都进行了考虑)

(1) 上位机接收代码:

 

(2)中间测试结果

   

(3) 测试结论

    中间累积10个数据包再进行发送,可以大量减少中间数据中转的情况,但是可以看到实际的数据丢包的情况,其接近1%的丢包率。这个丢包率应该是建立在pub与sub之间的数据包丢包,应当,出现这样的原因显然是由于pub-sub的定义的客户端和服务器不合适,我们通过现在将pub定义成客户端,连接好sub服务器之后,再进行数据的发送。

 

5.4 中间累积10个数据包后进行数据发送(并且修改成pub 是客户端)

(1)第一次测试结果:

(2)第二次测试结果

(3) 第三次测试结果:

(4) 第四次测试结果:

 

(5) 第五次测试结果:

 

ps:以上所有的测试结果中,第三层次的测试耗时,都与第二层次的耗时基本一样,如下:

(6) 第六次测试结果

5.5 测试结论

    通过上面的测试过程与结果,我们可以得到zmq 的发送函数zmq.send 函数频繁的调用,会出现比较消耗系统的资源,占用一定的时间。因此,当我们累积10个之后,再进行数据的发送,在这样的情况下,理论上,可以将由于数据发送的耗时降低10倍

 

6  验证数据包之间打上时间标签的时间间隔。

6.1 验证原理

应当首先默认下位机的上传的速度是能够大概在每隔10us 上传一次数据,我们需要验证上位机的打时标的前后两个数据包的时间,保证其前后两个数据包的时间个间隔在可接受的范围内,通过上述的测试的结果,我们可以计算每个数据包之间平均间隔。

对于10.68s,对应1M个数据包,即每个数据包的时间为如下:10.68us,即约10us。

 10.68/1000000
1.068e-05

但是实际上,两个数据包之间的数据的间隔是总是变化的,因此,我们将这样的数据包通过单上时间标签,然后通过通过对包的数据格式的解析之后,将其存储到数据库当中,然后对之后存储进去的数据进行实际的验证。


6.2 验证实际两个数据包之间的间隔

---------------updata----on 2020-0601-------------

6.2.1第三层的数据接收中加入实际的数据的存储之后,系统耗时加长

(1)测试结果

中间数据监测层耗时:

第三层的数据解析与存储层次的耗时:

(2) 第二次的测试结果

中间监测层的耗时:

 

第三层的数据的解析与存储层次的耗时:

(3) 第三次测试结果

中间数据接收和监测层的耗时:

第三层数据解析与存储层的耗时:

 

6.2.2 查看系统中的数据存储的数据

 

 

 

 

 

6.2.3 结论:

  通过上面存入到数据库当中,的时间标签间隔,可以发现很多数据之间的监测大概都在13us左右,由于我们接收的数据的时间为13s,因此,每个数据包的时间的间隔约为13s/1M=13us。

  根据上一小节展示的结果,我们可以看到结果是存在一定成都上的数据包的丢失,但是丢失很少。这也是udp通讯协议不可避免的地方。

 

 

 

6.3 系统资源的监测与分析

6.3.1 系统资源监测

(1) 在数据传输过程中的系统监测图:

(2)监测结论

>>> 100000*(64)*8/1024/1024
48.828125

当下位机设备通过100k/s 的速度进行上传数据包的时候,我们可以通过将udp的头部,ip头部,数据长度加在一起,可以大致计算出实际上传的速度的大小。

对于tcp,udp ip mac 的帧格式,请参考:https://www.cnblogs.com/OctoptusLian/p/8580052.html

udp+ip的头部整体的字节数为:8个字节(udp头部) +  20字节(ip)+ mac(14) =42字节,我们再额外加上10个字节的数据,我们可以得到52个字节。由于mac会帮助我们进行数据的填充,所以实际还是应当按照64个字节进行计算,结果如上所示,并且也可验证出我们的结果的正确性。

 

6.3.2  耗时解决方案

    占用了更多的系统的资源,此时对于udp的数据的接收来说,我们可以通过降低udp数据的发送延时,来提高其发送的速度,从而使得实际的这边的数据的接收的时间的间隔更加短一些。

 

 

 

 

 

 

 

 

 

 

 

 

 


 

6.0 debug 说明(中间偶尔出现的bug的)

中间的某些时刻有可能会出现,上位机接收不到的情况,很有可能是由于phy芯片自动协商的问题,这个时候,由于笔记本的电脑采用一般都是千兆的网卡, 但是有时候由于介质的原因可能协商到百兆,就会出现意向不到问题,总之,要确定自己的网卡协调后的网速应当是1Gb,如下图

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 5
    点赞
  • 99
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论
Zynq 7z035是一款由赛灵思公司推出的可编程逻辑器件,它采用了基于ARM处理器的FPGA系统级集成平台。该器件的启动流程主要包括初始化、配置和启动三个阶段。 在初始化阶段,首先由ROM中的启动程序加载FSBL(First Stage Boot Loader,第一阶段引导加载器)代码到内存中。FSBL负责初始化DDR控制器、设置UART串口和初始化中断控制器等。然后,FSBL将加密的Bitstream(比特流)加载到FPGA的可编程逻辑部分,使其完成设计的硬件配置。 接下来是配置阶段,FSBL将加载U-Boot(Linux系统的引导加载器)和设备树(Device Tree)等关键组件到RAM中。U-Boot负责加载Linux内核、设置文件系统和初始化设备等,设备树则定义了硬件资源的布局和配置信息。此外,启动启动程序还可能加载其他应用程序或固件到内存中,以便系统启动时使用。 最后是启动阶段,当U-Boot加载完成后,它将控制权交给Linux内核。Linux内核根据设备树中的配置信息初始化硬件设备,并加载文件系统。一旦文件系统加载完成,系统就进入用户空间,可以执行应用程序和其他任务。 总结来说,Zynq 7z035的启动流程包括初始化、配置和启动三个阶段。在初始化阶段,FSBL负责初始化硬件并加载Bitstream配置FPGA。在配置阶段,FSBL加载U-Boot和设备树等关键组件到RAM中。最后,在启动阶段,U-Boot加载Linux内核,Linux内核初始化硬件设备并启动系统。这样,Zynq 7z035就能够完成启动并运行用户应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

没有水杯和雨伞的工科男

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

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

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

打赏作者

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

抵扣说明:

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

余额充值