A40i使用笔记:GPIO操作方式(用于测试设备io驱动性能)(传统方式、设备树方式、寄存器方式操作GPIO)

一、前言

 

前文介绍了文件io的驱动方式,可以用于led点灯灭灯或者一些简单的不需求时序控制的ic操作,但是用于功能类ic,如AD采集芯片等时序控制需求高的,就不行了,这时候就需求高速io去实现指定的芯片手册时序逻辑,来完成ic的驱动。前文也介绍了一些高速io的驱动方式,如hx711芯片驱动,如未发布文章的AD7606驱动源码资源,都是使用高速io驱动的方式,但是经测试,io的速率还未开发到极致,是因为未直接操作底层寄存器的原因,还是经过包装,但是io速率已经满足大部分ic驱动开发,本编文章的目的也是介绍和详细写出各种方式io的速度测试方法,针对于不同的需求可以设计不同的驱动。

目前总结有io有几种驱动方式

1.应用层文件io驱动方式。优点:利用底层自带的gpio驱动,免去独立开发驱动。缺点:io驱动慢,无法适用于大部分ic,只能简单用于led操作,按键检测,和简单的ic。

2.驱动层gpio操作方式。优点:简单一些。缺点:需要搭配复杂的开发环境,io速度也不是极限,仍然感觉有限制,如无延时的操作一个io高低电平,会有500ns的保持时间,怀疑包装函数中有保护延时。

3.驱动层寄存器操作方式。优点:速度快。缺点:需要搭配复杂的开发环境,操作麻烦一些。而且在写驱动程序时需要查询芯片手册挨个引脚去对应配置地址等信息。

二、环境

宿主机:window10,Ubuntu16.04

目标及:a40i,linux3.10

三、正文

主要说明3种方式种io操作的步骤和效果对比

1.应用层文件io驱动方式

程序如下,文件方式驱动一个io无限正反转

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
    while(1)
    {
        system("echo 1 > /sys/class/gpio_sw/PG9/data");
        //usleep(10000);
        system("echo 0 > /sys/class/gpio_sw/PG9/data");
        //usleep(10000);
    }
    return 0;
}

适用逻辑分析仪测试io反转速率效果如下图所示

这里就不放图了,速度很慢的说,周期在200us以上,没得看

结论:慢

2.驱动层gpio操作方式(常规操作,非设备树,传统方式)

程序如下,底层gpio封装函数驱动gpio方式

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>
//#include <mach/platform.h> //PAD_GPIO_C

#define SUNXI_PA_BASE	0
#define SUNXI_PB_BASE	32
#define SUNXI_PC_BASE	64
#define SUNXI_PD_BASE	96
#define SUNXI_PE_BASE	128
#define SUNXI_PF_BASE	160
#define SUNXI_PG_BASE	192
#define SUNXI_PH_BASE	224
#define SUNXI_PI_BASE	256
#define SUNXI_PJ_BASE	288
#define SUNXI_PK_BASE	320
#define SUNXI_PL_BASE	352
#define SUNXI_PM_BASE	384
#define SUNXI_PN_BASE	416
#define SUNXI_PO_BASE	448
#define AXP_PIN_BASE	1024
/* sunxi gpio name space */
#define GPIOA(n)	(SUNXI_PA_BASE + (n))
#define GPIOB(n)	(SUNXI_PB_BASE + (n))
#define GPIOC(n)	(SUNXI_PC_BASE + (n))
#define GPIOD(n)	(SUNXI_PD_BASE + (n))
#define GPIOE(n)	(SUNXI_PE_BASE + (n))
#define GPIOF(n)	(SUNXI_PF_BASE + (n))
#define GPIOG(n)	(SUNXI_PG_BASE + (n))
#define GPIOH(n)	(SUNXI_PH_BASE + (n))
#define GPIOI(n)	(SUNXI_PI_BASE + (n))
#define GPIOJ(n)	(SUNXI_PJ_BASE + (n))
#define GPIOK(n)	(SUNXI_PK_BASE + (n))
#define GPIOL(n)	(SUNXI_PL_BASE + (n))
#define GPIOM(n)	(SUNXI_PM_BASE + (n))
#define GPION(n)	(SUNXI_PN_BASE + (n))
#define GPIOO(n)	(SUNXI_PO_BASE + (n))
#define GPIO_AXP(n)	(AXP_PIN_BASE  + (n))

//声明描述LED硬件相关的数据结构
struct led_resource {
    int gpio; //GPIO软件编号
    char *name; //LED的名称
};

//定义初始化LED灯的硬件信息对象
static struct led_resource led_info[] = {
    {
        .name = "SCL1",
        .gpio = GPIOG(2)
    },
    {
        .name = "SDA1",
        .gpio = GPIOG(0)
    },
};

//入口:insmod
static int led_init(void)
{
    int i;
    int num=1000;
    for(i = 0; i < ARRAY_SIZE(led_info); i++) {
        int result = gpio_request(led_info[i].gpio,led_info[i].name);//先向内核申请GPIO硬件资源;
		if(result!=0){//判断申请的GPIO资源是否成功,失败返回提示信息
			printk(KERN_ERR "GPIO%d has used...led init err!\n",led_info[i].gpio);//打印错误信息
			return -1;
		}
        gpio_direction_output(led_info[i].gpio, 0);//设置输入、输出GPIO方向,设置为输出功能,输出0
    }
    //2.然后配置GPIO为输出功能,输出0,开灯
	while(num--){
		gpio_set_value(194, 1);
		gpio_set_value(192, 1);
		gpio_set_value(194, 0);
		gpio_set_value(192, 0);
	}
    printk(KERN_INFO "led init ok...\n");//打印提示信息
    return 0;
}

//出口:rmmod
static void led_exit(void)
{
    int i;
    int num=1000;
	while(num--){
		gpio_set_value(led_info[0].gpio, 1);
		gpio_set_value(led_info[1].gpio, 1);
		gpio_set_value(led_info[0].gpio, 0);
		gpio_set_value(led_info[1].gpio, 0);
	}
    for(i = 0; i < ARRAY_SIZE(led_info); i++) {
        gpio_free(led_info[i].gpio);//释放GPIO硬件资源
    } 

    printk(KERN_INFO "led exit...\n");//打印提示信息
}
module_init(led_init);//三要素 必须有
module_exit(led_exit);//三要素 必须有
MODULE_LICENSE("GPL");//三要素 必须有
MODULE_AUTHOR("kbq-1950361006@qq.com");//模块作者的声明
MODULE_DESCRIPTION("HX711 Driver");//当前模块的功能描述

使用逻辑分析仪查看测试效果如下图所示

两个io

 一个io

示波器测试结果: 

结论:可以发现高低电平切换延时大概有500ns左右,但是这个500ns是哪里来的暂时还未发现,应该是gpio开放接口的一种保护吧,但是有些时候更高级的ic是不能容忍500ns的延时的,所以还没到极致,还需要更快的速度,快快快

2.驱动层gpio操作方式(设备树方式,传统方式)

程序如下,调用设备树驱动方式

我的引脚为PB10

设备树添加如下

	jkbuzzer {
	   compatible = "jk,buzzer";
	   gpios = <&pio 1 8 1 1 1 1>;
	   clocks = <&clk_pio>;
	};

具体io配置如下

 gpio = <&pio   1   1   1   1   1  0>;
    |      |    |   |   |   |   |  |-------------------表示有效电平
    |      |    |   |   |   |   |----------------------上下拉, 0关闭功能, 1上拉, 2下拉, 3保留
    |      |    |   |   |   |-------------------------驱动力,电流等级(0 - 3),级别越高,输出电流越大
    |      |    |   |   |----------------------------gpio功能类型,0输入, 1输出, 6和外部中断,7关闭功能(具体查手册)
    |      |    |   |------------------------------pin bank 内偏移(即组内第几个io口).
    |      |    |---------------------------------哪组gpio, PA(0),PB(1),PC(2),PD(3),PE(4),PF(5),PG(6),PH(7),PI(8),PJ(9),PK(10),PL(11)
    |      |--------------------------------------指向哪个gpio控制器,  pio / r_pio(PL组)
    |-----------------------------------------------------属性名字(随便命名)

 程序如下

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <linux/sys_config.h>
#include <linux/delay.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>

/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
static int led_gpio;

/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	return 0;
}

/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	err = copy_from_user(&status, buf, 1);
	/* 根据次设备号和status控制LED */
	gpio_set_value(led_gpio, status);	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	/* 根据次设备号初始化LED */
	gpio_direction_output(led_gpio, 0);
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	return 0;
}

/* 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 从platform_device获得GPIO
 *    把file_operations结构体告诉内核:注册驱动程序
 */
int myprobe(struct platform_device *pdev)
{
	//int err;
	/* 4.1 设备树中定义有: gpios=<...>;	*/
	struct device_node *nd = pdev->dev.of_node;
	struct gpio_config config;
	printk("gpio count:%d\n", of_gpio_named_count(nd, "gpios"));
	led_gpio = of_get_named_gpio_flags(nd, "gpios", 0, (enum of_gpio_flags *)&config);
	if (!gpio_is_valid(led_gpio))
        printk("gpio isn't valid\n");
    if (gpio_request(led_gpio, pdev->name) < 0)
        printk("gpio request failed %d\n", led_gpio);
	
	/* 4.2 注册file_operations 	*/
	major = register_chrdev(0, "kbq_led", &led_drv);  /* /dev/led */

	led_class = class_create(THIS_MODULE, "led_class");
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		gpio_free(led_gpio);
		return PTR_ERR(led_class);
	}
	device_create(led_class, NULL, MKDEV(major, 0), NULL, "kbq_led%d", 0); /* /dev/100ask_led0 */
    printk("gpio is ok\n");
    return 0;
    
}

int myremove(struct platform_device *pdev)
{
	device_destroy(led_class, MKDEV(major, 0));
	class_destroy(led_class);
	unregister_chrdev(major, "kbq_led");
	gpio_free(led_gpio);
	printk("gpio remove ...\n");
    return 0;
}


struct of_device_id ids[] = {
    {.compatible = "jk,buzzer"},
    {},
};
/* 1. 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
    .probe      = myprobe,
    .remove     = myremove,
    .driver     = {
        .name   = "mydrv",
        .of_match_table = ids,
    },
};
/* 2. 在入口函数注册platform_driver */
static int __init led_init(void)
{
	int err;
	err = platform_driver_register(&chip_demo_gpio_driver); 
	return err;
}
/* 3. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数
 *	   卸载platform_driver
 */
static void __exit led_exit(void)
{
	platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

测试程序如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <poll.h>
#include <unistd.h>
#include <time.h> 
#include <sys/time.h> 
char *keyscan_PATH = "/dev/kbq_led0";
int main(int argc, char **argv)
{
	int fd;
	char status;
	/* 打开文件 */
	fd = open(keyscan_PATH, O_RDWR);
	if (fd == -1){
		printf("can not open file %s\n", keyscan_PATH);
		return -1;
	}
	if(!strcmp(argv[1], "on")){//判断第二个参数是否为on
	    status=1;
            write(fd, &status, sizeof(status));
        }
        else if(!strcmp(argv[1], "off")){//判断第二个参数是否为off
            status=0;
	    write(fd, &status, sizeof(status));
        }	
	printf("led=%d,,,,",status);
	close(fd);
	return 0;
}

交叉编译命令:

/root/workspace/allwinner/A40i/bsp/lichee/out/sun8iw11p1/linux/common/buildroot/host/opt/ext-toolchain/bin/arm-linux-gnueabihf-g++ led_test.c -o ledtest

结论:这个设备树的测试方式之前使用whiile循环方式也是同上结果,有500ns的电平延时,具体不知道是为什么,不知道是不是gpio_set_value的问题,用gpiod_set_value的方式还缺少头文件

但是收获是已经成功调用到设备树去驱动程序了

3.驱动层gpio操作方式(寄存器方式)

全志A40i的芯片手册中GPIO引脚说明部分如下图所示,以GPIOA为例

图1:GPIO起始地址

 图2:GPIO各配配置和各组起始地址计算方式

 图3:GPIO模式配置地址+默认配置值+配置引脚对应位bit

 图4:GPIO输入输出配置地址

 图5:GPIO驱动能力配置地址

 图6:GPIO上拉下拉模式配置地址

 A40i的GPIO引脚配置很少,而且整体GPIO速率不是很高,没有单独配置GPIO的速率能力,很愁人,看来想用A40i驱动高速率ic得用专用外设接口spi或者采用FPGA芯片了,啊啊啊啊

使用寄存器的测试代码如下:(配置GPIOB10)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

/* 寄存器物理地址 */	
#define GPIOB_Congure_Register	(0x01C20800+0x0028) //GPIOB8地址,input、output、disable设置
#define GPIOB_Data_Register		(0x01C20800+0x0034) //GPIOB8地址,data读取、高低电平设置
#define GPIOB_Driving_Register	(0X01C20800+0x0038) //GPIOB8地址,引脚驱动能力设置
#define GPIOB_PULL_Register		(0X01C20800+0x0040) //GPIOB8地址,上拉、下拉设置


//static void __iomem *GPIOB_Congure;//用writel形式
//static void __iomem *GPIOB_Data;//用writel形式
static volatile unsigned int *GPIOB_Congure;//直接用指针形式,用此定义
static volatile unsigned int *GPIOB_Data;//直接用指针形式,用此定义
/*unsigned int Io_buff;
void led_Io_High(void __iomem *ADGPIO_DR, unsigned char GPIO_id)
{
	Io_buff = readl(ADGPIO_DR);
	Io_buff|= (1<<GPIO_id);	
	writel(Io_buff, ADGPIO_DR);
}
void led_Io_Low(void __iomem *ADGPIO_DR, unsigned char GPIO_id)
{
	Io_buff = readl(ADGPIO_DR);
	Io_buff &= ~(1<<GPIO_id);		
	writel(Io_buff,ADGPIO_DR);
}*/
//入口:insmod
static int led_init(void)
{
	unsigned int val = 0;
	unsigned long number=1000;
	/* 初始化GPIO */
	/* 1、寄存器地址映射 */
	GPIOB_Congure = ioremap(GPIOB_Congure_Register, 4);
	GPIOB_Data = ioremap(GPIOB_Data_Register, 4);
	/* 4、设置GPIO为输出功能 */
	val = readl(GPIOB_Congure);
	val &= 0xfffffff9;//将PB8设置为0x001为output模式,其余数据与1想与,保持不变
	writel(val, GPIOB_Congure);

	while(number--){
		//led_Io_Low(GPIOB_Data,8);
		//led_Io_High(GPIOB_Data,8);
		*GPIOB_Data &= ~(1<<8);
		*GPIOB_Data |= (1<<8);
	}

    printk(KERN_INFO "led init ok...\n");//打印提示信息
    return 0;
}

//出口:rmmod
static void led_exit(void)
{
    printk(KERN_INFO "led exit...\n");//打印提示信息
}

module_init(led_init);//三要素 必须有
module_exit(led_exit);//三要素 必须有
MODULE_LICENSE("GPL");//三要素 必须有
MODULE_AUTHOR("kbq-1950361006@qq.com");//模块作者的声明
MODULE_DESCRIPTION("led Driver");//当前模块的功能描述


新更新代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

/* 寄存器物理地址 */	
#define GPIOB_Congure_Register	(0x01C20800+0x0028) //GPIOB8地址,input、output、disable设置
#define GPIOB_Data_Register		(0x01C20800+0x0034) //GPIOB8地址,data读取、高低电平设置
#define GPIOB_Driving_Register	(0X01C20800+0x0038) //GPIOB8地址,引脚驱动能力设置
#define GPIOB_PULL_Register		(0X01C20800+0x0040) //GPIOB8地址,上拉、下拉设置


static void __iomem *GPIOB_Congure;//用writel形式
static void __iomem *GPIOB_Data;//用writel形式
static void __iomem *GPIOB_Driving;//用writel形式
static void __iomem *GPIOB_PULL;//用writel形式
//static volatile unsigned int *GPIOB_Congure;//直接用指针形式,用此定义//volatile禁止优化
//static volatile unsigned int *GPIOB_Data;//直接用指针形式,用此定义//volatile禁止优化
unsigned int Io_buff;
void led_Io_High(void __iomem *ADGPIO_DR, unsigned char GPIO_id)
{
	Io_buff = readl(ADGPIO_DR);
	Io_buff|= (1<<GPIO_id);	
	writel(Io_buff, ADGPIO_DR);
}
void led_Io_Low(void __iomem *ADGPIO_DR, unsigned char GPIO_id)
{
	Io_buff = readl(ADGPIO_DR);
	Io_buff &= ~(1<<GPIO_id);		
	writel(Io_buff,ADGPIO_DR);
}
//入口:insmod
static int led_init(void)
{
	unsigned int config_val = 0;
	unsigned int drive_val = 0;
	unsigned long number=10000;
	/* 初始化GPIO */
	/* 1、寄存器地址映射 */
	GPIOB_Congure = ioremap(GPIOB_Congure_Register, 4);
	GPIOB_Data = ioremap(GPIOB_Data_Register, 4);
	GPIOB_Driving = ioremap(GPIOB_Driving_Register, 4);
	/* 2、设置GPIO为输出功能 */
	config_val = readl(GPIOB_Congure);//读取配置
	printk(KERN_INFO "before config_val=%x\n",config_val);
	//将PB8、PB9设置为0x001为output模式,其余数据与1想与,保持不变,对应手册,一个字符就是一个io,最高位空,可以位0,默认为0x77777777
	//设置引脚具体数值见手册,常规000:input 001:output 010~110:见手册 111:IO disable
	config_val |= 0x000000ff;//先或上需要配置位的最高值
	config_val &= 0xffffff11;//在与上需要配置位的配置值,以防此驱动加载时无法向上配置
	printk(KERN_INFO "after config_val=%x\n",config_val);
	writel(config_val, GPIOB_Congure);//写入配置
	/* 3、设置GPIO驱动能力 */
	//暂不设置,保持默认level1
	/*drive_val = readl(GPIOB_Driving);//读取配置
	printk(KERN_INFO "before drive_val=%x\n",drive_val);
	//将PB8、PB9设置为0x11为level3驱动能力,其余数据与1想与,保持不变,对应手册,一个字符就是两个io,默认为0x555555
	//设置引脚具体数值见手册,常规00:level0 01:level1 02:level2 03:level3
	drive_val |= 0x000f0000;//先或上需要配置位的最高值
	drive_val &= 0xffffffff;//在与上需要配置位的配置值,以防此驱动加载时无法向上配置
	printk(KERN_INFO "after drive_val=%x\n",drive_val);
	writel(drive_val, GPIOB_Driving);//写入配置*/
	/* 4、设置GPIO pull上下拉 */
	//暂不设置,保持默认无上拉下拉


	while(number--){
		led_Io_Low(GPIOB_Data,9);
		led_Io_High(GPIOB_Data,9);
	}

    printk(KERN_INFO "led init ok...\n");//打印提示信息
    return 0;
}

//出口:rmmod
static void led_exit(void)
{
	unsigned long number=10000;
	while(number--){
		led_Io_Low(GPIOB_Data,8);
		led_Io_Low(GPIOB_Data,9);
		led_Io_High(GPIOB_Data,8);
		led_Io_High(GPIOB_Data,9);
	}

    printk(KERN_INFO "led exit...\n");//打印提示信息
}

module_init(led_init);//三要素 必须有
module_exit(led_exit);//三要素 必须有
MODULE_LICENSE("GPL");//三要素 必须有
MODULE_AUTHOR("kbq-1950361006@qq.com");//模块作者的声明
MODULE_DESCRIPTION("led Driver");//当前模块的功能描述

读取gpio

	unsigned int Io_read=0;
	Io_read = readl(GPIOB_Data);	
	Io_read = (Io_read & (1<<9)) ? 1 : 0;
	printk(KERN_INFO "Io_read=%d\n",Io_read);

  用逻辑分析仪得出的测试结果图如下图所示:

示波器测试结果:

 可以看见,高低电平转换时间大概在200ns左右,比起使用gpio函数速度快了2.5倍,目前使用寄存器最快的方式了,再也没有更快的方式可以测试了,难道A40i普通gpio的操作速度就这样了吗,有些失望,但是又无可奈何,毕竟国产的芯片本身不多,能有好的平台去使用和学习更不多,以后有机会尝试一下RK3399。

优点:电平反转时间最快200ns,比起使用GPIO操作io速度快了2.5倍。

缺点:配置io时需要一个一个引脚的去对应寄存器,很麻烦,一不留神就会搞错。容易被其他应用申请走io。

4.驱动层gpio操作方式(传统方式+寄存器方式)

此方式结合传统方式与寄存器方式,将初始化函数使用传统方式,便于对gpio的管理,申请,比较标准,操作gpio时如果需要速率,使用寄存器方式,如果时一般芯片,使用传统库函数方式操作gpio,最推荐的是使用此方法。经测试,在使用寄存器初始化gpio后,在使用传统方式申请可将gpio申请走,所以使用寄存器申请gpio一个是特别麻烦,一个是比较危险,io容易被其他应用使用导致自身不好使。

优点:电平反转时间最快200ns,比起使用GPIO操作io速度快了2.5倍。

缺点:配置简单,安全,使用方法标准。

在不想使用设备树的情况下强烈推荐此方法(适用于不是专业搞linux驱动开发,只是偶然用到linux驱动,写一个使用的开发工程师,如果有用,请帮我留言回复get到了,你的留言是对我最大的支持和动力哦,奥里给!)

四、结语

学习、积累、开拓

  • 5
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大桶矿泉水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值