linux设备树-LED字符设备驱动(pinctrl)
目录
----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
在前面我们已经介绍了pinctrl subsystem相关的基础知识,这一节我们尝试修改设备树,在pin controller node下添加两个子节点分别用来控制LED1~LED4的全亮/全灭。然后我们编写LED驱动程序,配置LED的两种状态:
- default:默认状态,LED1~LED4全亮;
- myled-off:LED1~LED4全灭;
一、修改设备树
1.1 修改s3c2440-pinctrl.dtsi
修改内核arch/arm/boot/dts/s3c2440-pinctrl.dtsi文件,在pinctrl节点下添加两个引脚配置节点:
myled_on: myled-on { samsung,pins = "gpb-5","gpb-6","gpb-7","gpb-8"; /* GPB5~8 */ samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; /* 设置为输出模式 */ samsung,pin-val = <0x0>; /* 初始值输出低电平 */ samsung,pin-pud = <EXYNOS_PIN_PULL_UP>; }; myled_off: myled-off { samsung,pins = "gpb-5","gpb-6","gpb-7","gpb-8"; /* 引用GPB5~8*/ samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; /* 设置为输出模式 */ samsung,pin-val = <0x1>; /* 初始值输出高电平 */ samsung,pin-pud = <EXYNOS_PIN_PULL_UP>; };
其中:
- myled_on节点配置GPB5~GPB8默认为输出模式,并且输出低电平,从而达到LED1~LED4点亮的效果;
- myled_off节点配置GPB5~GPB8默认为输出模式,并且输出高电平,从而达到LED1~LED4熄灭的效果。
1.2 修改s3c2440-smdk2440.dts
在内核arch/arm/boot/dts/s3c2440-smdk2440.dts文件中添加myled设备节点:
myled: myled { compatible = "myled"; status = "okay"; pinctrl-names = "default", "myled_off"; /* 定义两种状态 */ pinctrl-0 = <&myled_on>; /* 当使用default状态时,就会使用所引用节点的配置*/ pinctrl-1 = <&myled_off>; /* 当使用myled_off状态时,就会使用所引用节点的配置*/ };
这里定义了myled设备的两种状态:
- default:默认状态,引脚配置设置为&myled_on;
- myled_off:引脚配置设置为&myled_off;
二、LED驱动程序
这里我们仍然以led驱动程序为例,进行讲解。在/work/sambashare/drivers下创建24.led_dev_pinctr文件夹。用来保存LED驱动程序以及测试应用程序。
2.1 编写led_open、led_write函数
#define DTSLED_CNT 1 #define DTSLED_NAME "myled" /* 下面这个几个类型由于内核没有提供给client device driver使用,因此我们只能自己定义 */ struct pinctrl_setting_mux { unsigned group; unsigned func; }; struct pinctrl_setting_configs { unsigned group_or_pin; unsigned long *configs; unsigned num_configs; }; struct pinctrl_setting { struct list_head node; enum pinctrl_map_type type; struct pinctrl_dev *pctldev; const char *dev_name; union { struct pinctrl_setting_mux mux; // mux配置数据 struct pinctrl_setting_configs configs; // config配置数据 } data; }; struct pinctrl_state { struct list_head node; const char *name; struct list_head settings; }; /* 定义一个led设备 */ struct led_dev myled; /* 定义led结构体 */ struct led_dev { dev_t devid; /* 字符设备编号 */ struct cdev cdev; /* 保存操作结构体的字符设备 */ struct class *class; /* class */ struct device *device; /* 设备类 */ struct device_node *nd; /* 设备节点 */ struct pinctrl *pinctrl; /* pin control state holder */ struct pinctrl_state *state; /* 当前pin control state */ }; static int led_open(struct inode *inode, struct file *file) { return 0; } /* 点亮/熄灭 LED01 */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int val,ret; copy_from_user(&val, buf, count); // 用户空间到内核空间传递数据 printk("value %d",val); if(val == 1){ /* 点亮 */ myled.state = pinctrl_lookup_state(myled.pinctrl, "default"); //获取pinctrl-0的配置状态 if (IS_ERR(myled.state)) { dev_dbg(myled.device, "no default pinctrl state\n"); ret = PTR_ERR(myled.state); return ret; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态*/ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); return ret; }else{ printk("pinctrl select state success\n"); } } else{ /* 熄灭 */ myled.state = pinctrl_lookup_state(myled.pinctrl, "myled_off"); //获取pinctrl-1的配置状态 if (IS_ERR(myled.state)) { dev_dbg(myled.device, "no myled_off pinctrl state\n"); ret = PTR_ERR(myled.state); return ret; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态*/ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); return ret; }else{ printk("pinctrl select state success\n"); } } return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, };
2.2 platform driver定义
这里我们采用platform设备驱动模型,因此需要定义platform_driver:
/* * 用于设备树匹配 */ static const struct of_device_id led_dt_match[] = { { .compatible = DTSLED_NAME, }, {}, }; /* * platform驱动 */ static struct platform_driver led_driver = { .probe = led_probe, .remove = led_remove, .driver = { .name = DTSLED_NAME, .of_match_table = led_dt_match, // 匹配列表 } };
2.3 led_probe
/* * 当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中 */ static int led_probe(struct platform_device *pdev) { int ret = 0,i = 0; struct pinctrl_setting *setting; struct device_node *np; const char *statename; struct dev_pin_info *pins; printk("%s enter.\n", __func__); if(pdev->name != NULL){ printk("platform device name %s",pdev->name); // myled } /* 获取设备引脚状态信息 */ np = pdev->dev.of_node; ret = of_property_read_string_index(np, "pinctrl-names", 0, &statename); // 读取pinctrl-names属性第0个值 if (ret == 0) { printk("pinctrl-names index 0 value %s",statename); } /* 1.获取与设备相关联的pinctrl句柄 */ pins = pdev->dev.pins; myled.pinctrl = pins->p; if (IS_ERR(myled.pinctrl)){ printk("retrieves the pinctrl handle for a device failed\n"); ret = PTR_ERR(myled.pinctrl); goto faile_devid; }else{ printk("retrieves the pinctrl handle for a device success\n"); } /* 2. 获取指定的name的state */ myled.state = pinctrl_lookup_state(myled.pinctrl, "default"); //获取pinctrl-0的配置状态 if (IS_ERR(myled.state)){ printk("pinctrl lookup state failed\n"); ret = PTR_ERR(myled.state); goto faile_devid; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态 */ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); goto faile_devid; }else{ printk("pinctrl select state success\n"); } /* 输出状态state以及状态setting信息 */ if(myled.state->name != NULL){ printk("state name %s", myled.state->name); // 状态名称为default } /* 遍历当前状态下的所有setting */ list_for_each_entry(setting, &(myled.state->settings), node) { printk("setting type %d", setting->type); printk("setting dev_name %s", setting->dev_name); // 设备名称为myled if(setting->type == PIN_MAP_TYPE_MUX_GROUP ){ // 引脚复用 枚举值为2 printk("setting mux group %d", setting->data.mux.group); printk("setting mux func %d", setting->data.mux.func); }else{ // 配置引脚电气特性 printk("--------------configs start--------"); printk("setting configs group_or_pin %d", setting->data.configs.group_or_pin); for(i=0; i<setting->data.configs.num_configs; i++){ printk("setting configs configs %d", setting->data.configs.configs[i]); } printk("--------------configs end--------"); } } /* 4. 动态分配字符设备号 */ ret = alloc_chrdev_region(&myled.devid, 0, 1,DTSLED_NAME); // ls /proc/devices看到的名字 /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("alloc char device region failed\n"); goto faile_devid; }else{ printk("alloc char device region success\n"); } /* 5.初始化字符设备,添加字符设备 */ cdev_init(&myled.cdev, &led_fops); ret = cdev_add(&myled.cdev, myled.devid, DTSLED_CNT); /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("char device add failed\n"); goto fail_cdev; }else{ printk("char device add success\n"); } /* 6.创建类,它会在sys目录下创建/sys/class/dtsled这个类 */ myled.class = class_create(THIS_MODULE, DTSLED_NAME); if(IS_ERR(myled.class)){ printk("create class failed\n"); ret = PTR_ERR(myled.class); goto fail_class; }else{ printk("create class success\n"); } /* 7. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */ myled.device = device_create(myled.class, NULL, myled.devid, NULL, DTSLED_NAME); if(IS_ERR(myled.device)){ printk("create device failed\n"); ret = PTR_ERR(myled.device); goto fail_device; }else{ printk("create device success\n"); } return 0; fail_findnd: device_destroy(myled.class, myled.devid); fail_device: class_destroy(myled.class); fail_class: cdev_del(&myled.cdev); fail_cdev: unregister_chrdev_region(myled.devid, DTSLED_CNT); faile_devid: return ret; }
实际上该函数的前三个步骤是完全多余的,因此在platform设备和驱动匹配的时候,会调用pinctrl_bind_pins函数,该函数通过调用pinctrl_lookup_state函数获取状态名为default和init的状态,并分别保存到default_state和 init_state成员变量中。如果找不到init状态,则选择default状态作为设备引脚的状态。
这里我主要的目的是为了输出设备default状态以及该状态下的配置信息。
2.4 led_remove
/* * 硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */ static int led_remove(struct platform_device * pdev) { printk("led driver exit\n"); /* 注销类、以及类设备 */ device_destroy(myled.class, myled.devid); class_destroy(myled.class); /* 删除设备,卸载注册的设备编号 */ cdev_del(&myled.cdev); unregister_chrdev_region(myled.devid, DTSLED_CNT); return 0; }
2.5 led_drv.c完整代码

#include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/io.h> #include <linux/errno.h> #include <linux/uaccess.h> #include <linux/platform_device.h> #include <linux/device.h> #include <linux/of.h> #include <linux/pinctrl/consumer.h> #include <linux/pinctrl/machine.h> #include <linux/pinctrl/devinfo.h> #include <linux/pinctrl/pinctrl.h> #include <linux/list.h> #define DTSLED_CNT 1 #define DTSLED_NAME "myled" /* 下面这个几个类型由于内核没有提供给client device driver使用,因此我们只能自己定义 */ struct pinctrl_setting_mux { unsigned group; unsigned func; }; struct pinctrl_setting_configs { unsigned group_or_pin; unsigned long *configs; unsigned num_configs; }; struct pinctrl_setting { struct list_head node; enum pinctrl_map_type type; struct pinctrl_dev *pctldev; const char *dev_name; union { struct pinctrl_setting_mux mux; // mux配置数据 struct pinctrl_setting_configs configs; // config配置数据 } data; }; struct pinctrl_state { struct list_head node; const char *name; struct list_head settings; }; /* 定义一个led设备 */ struct led_dev myled; /* 定义led结构体 */ struct led_dev { dev_t devid; /* 字符设备编号 */ struct cdev cdev; /* 保存操作结构体的字符设备 */ struct class *class; /* class */ struct device *device; /* 设备类 */ struct device_node *nd; /* 设备节点 */ struct pinctrl *pinctrl; /* pin control state holder */ struct pinctrl_state *state; /* 当前pin control state */ }; static int led_open(struct inode *inode, struct file *file) { return 0; } /* 点亮/熄灭 LED01 */ static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int val,ret; copy_from_user(&val, buf, count); // 用户空间到内核空间传递数据 printk("value %d",val); if(val == 1){ /* 点亮 */ myled.state = pinctrl_lookup_state(myled.pinctrl, "default"); //获取pinctrl-0的配置状态 if (IS_ERR(myled.state)) { dev_dbg(myled.device, "no default pinctrl state\n"); ret = PTR_ERR(myled.state); return ret; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态*/ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); return ret; }else{ printk("pinctrl select state success\n"); } } else{ /* 熄灭 */ myled.state = pinctrl_lookup_state(myled.pinctrl, "myled_off"); //获取pinctrl-1的配置状态 if (IS_ERR(myled.state)) { dev_dbg(myled.device, "no myled_off pinctrl state\n"); ret = PTR_ERR(myled.state); return ret; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态*/ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); return ret; }else{ printk("pinctrl select state success\n"); } } return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .write = led_write, }; /* * 当驱动和硬件信息匹配成功之后,就会调用probe函数,驱动所有的资源的注册和初始化全部放在probe函数中 */ static int led_probe(struct platform_device *pdev) { int ret = 0,i = 0; struct pinctrl_setting *setting; struct device_node *np; const char *statename; struct dev_pin_info *pins; printk("%s enter.\n", __func__); if(pdev->name != NULL){ printk("platform device name %s",pdev->name); // myled } /* 获取设备引脚状态信息 */ np = pdev->dev.of_node; ret = of_property_read_string_index(np, "pinctrl-names", 0, &statename); // 读取pinctrl-names属性第0个值 if (ret == 0) { printk("pinctrl-names index 0 value %s",statename); } /* 1.获取与设备相关联的pinctrl句柄 */ pins = pdev->dev.pins; myled.pinctrl = pins->p; if (IS_ERR(myled.pinctrl)){ printk("retrieves the pinctrl handle for a device failed\n"); ret = PTR_ERR(myled.pinctrl); goto faile_devid; }else{ printk("retrieves the pinctrl handle for a device success\n"); } /* 2. 获取指定的name的state */ myled.state = pinctrl_lookup_state(myled.pinctrl, "default"); //获取pinctrl-0的配置状态 if (IS_ERR(myled.state)){ printk("pinctrl lookup state failed\n"); ret = PTR_ERR(myled.state); goto faile_devid; }else{ printk("pinctrl lookup state success\n"); } /*3、设置引脚状态 */ ret = pinctrl_select_state(myled.pinctrl, myled.state); if (ret < 0){ printk("pinctrl select state failed\n"); goto faile_devid; }else{ printk("pinctrl select state success\n"); } /* 输出状态state以及状态setting信息 */ if(myled.state->name != NULL){ printk("state name %s", myled.state->name); // 状态名称为default } /* 遍历当前状态下的所有setting */ list_for_each_entry(setting, &(myled.state->settings), node) { printk("setting type %d", setting->type); printk("setting dev_name %s", setting->dev_name); // 设备名称为myled if(setting->type == PIN_MAP_TYPE_MUX_GROUP ){ // 引脚复用 枚举值为2 printk("setting mux group %d", setting->data.mux.group); printk("setting mux func %d", setting->data.mux.func); }else{ // 配置引脚电气特性 printk("--------------configs start--------"); printk("setting configs group_or_pin %d", setting->data.configs.group_or_pin); for(i=0; i<setting->data.configs.num_configs; i++){ printk("setting configs configs %d", setting->data.configs.configs[i]); } printk("--------------configs end--------"); } } /* 4. 动态分配字符设备号 */ ret = alloc_chrdev_region(&myled.devid, 0, 1,DTSLED_NAME); // ls /proc/devices看到的名字 /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("alloc char device region failed\n"); goto faile_devid; }else{ printk("alloc char device region success\n"); } /* 5.初始化字符设备,添加字符设备 */ cdev_init(&myled.cdev, &led_fops); ret = cdev_add(&myled.cdev, myled.devid, DTSLED_CNT); /* 返回值为负数,表示操作失败 */ if (ret < 0) { printk("char device add failed\n"); goto fail_cdev; }else{ printk("char device add success\n"); } /* 6.创建类,它会在sys目录下创建/sys/class/dtsled这个类 */ myled.class = class_create(THIS_MODULE, DTSLED_NAME); if(IS_ERR(myled.class)){ printk("create class failed\n"); ret = PTR_ERR(myled.class); goto fail_class; }else{ printk("create class success\n"); } /* 7. 在/sys/class/led下创建dtsled设备,然后mdev通过这个自动创建/dev/dtsled这个设备节点 */ myled.device = device_create(myled.class, NULL, myled.devid, NULL, DTSLED_NAME); if(IS_ERR(myled.device)){ printk("create device failed\n"); ret = PTR_ERR(myled.device); goto fail_device; }else{ printk("create device success\n"); } return 0; fail_findnd: device_destroy(myled.class, myled.devid); fail_device: class_destroy(myled.class); fail_class: cdev_del(&myled.cdev); fail_cdev: unregister_chrdev_region(myled.devid, DTSLED_CNT); faile_devid: return ret; } /* * 硬件信息被移除了,或者驱动被卸载了,全部要释放,释放资源的操作就放在该函数中 */ static int led_remove(struct platform_device * pdev) { printk("led driver exit\n"); /* 注销类、以及类设备 */ device_destroy(myled.class, myled.devid); class_destroy(myled.class); /* 删除设备,卸载注册的设备编号 */ cdev_del(&myled.cdev); unregister_chrdev_region(myled.devid, DTSLED_CNT); return 0; } /* * 用于设备树匹配 */ static const struct of_device_id led_dt_match[] = { { .compatible = DTSLED_NAME, }, {}, }; /* * platform驱动 */ static struct platform_driver led_driver = { .probe = led_probe, .remove = led_remove, .driver = { .name = DTSLED_NAME, .of_match_table = led_dt_match, // 匹配列表 } }; /* * platform驱动模块入口 */ static int led_drv_init(void) { // platform驱动注册 int err = platform_driver_register(&led_driver); if (err) { printk("platform driver registered failed\n"); } else { printk("platform driver registered successfully\n"); } return err; } /* * platform驱动模块出口 */ static void __exit led_drv_exit(void) { printk("platform driver unregistered\n"); // platform驱动卸载 platform_driver_unregister(&led_driver); } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE("GPL");
2.6 Makefile
KERN_DIR :=/work/sambashare/linux-5.2.8-dt all: make -C $(KERN_DIR) M=`pwd` modules clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += led_drv.o
三、LED驱动测试应用程序
在24.led_dev_pinctr下创建test文件夹,保存测试应用程序。
3.1 main.c
#include <sys/stat.h> #include <fcntl.h> #include <stdio.h> void print_usage(char *file) { printf("Usage:\n"); printf("%s <dev> <on|off>\n",file); printf("eg. \n"); printf("%s /dev/myled on\n", file); printf("%s /dev/myled off\n", file); } int main(int argc,char **argv) { int fd; int val; char *filename; if (argc != 3){ print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename,O_RDWR); if(fd == -1){ printf("can't open %s!\n",filename); return 0; } if (!strcmp("on", argv[2])){ // 亮灯 val = 1; printf("%s on!\n",filename); write(fd, &val, 4); }else if (!strcmp("off", argv[2])){ // 灭灯 val = 0; printf("%s off!\n",filename); write(fd, &val, 4); }else{ print_usage(argv[0]); } return 0; }
3.2 Makefile
all: arm-linux-gcc -march=armv4t -o main main.c clean: rm -rf *.o main
四、烧录开发板测试
4.1 编译设备树
root@zhengyang:/work/sambashare/linux-5.2.8-dt# make dtbs DTC arch/arm/boot/dts/s3c2416-smdk2416.dtb DTC arch/arm/boot/dts/s3c2440-smdk2440.dtb
编译设备树文件,把前面配置过的arch/arm/boot/dts里的dts文件编译成dtb文件。
将s3c2440-smdk2440.dtb复制到tftp服务器路径下:
root@zhengyang:/work/sambashare/linux-5.2.8-dt# cp /work/sambashare/linux-5.2.8-dt/arch/arm/boot/dts/s3c2440-smdk2440.dtb /work/tftpboot/
4.2 编译驱动
执行make命令编译驱动,并将驱动程序拷贝到nfs文件系统:
root@zhengyang:/work/sambashare/drivers/24.led_dev_pinctr# cd /work/sambashare/drivers/24.led_dev_pinctr/ root@zhengyang:/work/sambashare/drivers/24.led_dev_pinctr# make root@zhengyang:/work/sambashare/drivers/24.led_dev_pinctr# cp /work/sambashare/drivers/24.led_dev_pinctr/led_drv.ko /work/nfs_root/rootfs/
4.3 启动内核
uboot启动后,将dtb下载到内存地址0x30001000中:
SMDK2440 # tftp 0x30001000 s3c2440-smdk2440.dtb
然后将内核镜像加载到内存0x30008000地址:
nand read 0x30008000 kernel;
然后可以使用如下命令启动内核:
SMDK2440 # bootm 0x30008000 - 0x30001000 // 无设备树时,直接bootm 0x30008000 //bootm uImage地址 ramdisk地址 设备树镜像地址
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了