当前位置: 首页 > news >正文

RK3568笔记三十六:LED驱动开发(设备树)

若该文为原创文章,转载请注明原文出处。

录使用设备树编写一个简单的 LED 灯驱动程序

一、编程思路

程序编写的主要内容为添加 LED 灯的设备树节点、在驱动程序中使用 of 函数获取设备节点中的 属性,编写测试应用程序。

• 首先向设备树添加 LED 设备节点。

• 其次编写平台设备驱动框架,主要包驱动入口函数、驱动注销函数、平台设备结构体定义 三部分内容。

• 实现.probe 函数,对 LED 进行设备注册和初始化。

• 实现字符设备操作函数集,这里主要实现.write 操作。

• 编写测试应用程序,对于输入不同的值控制 LED 亮灭

二、编写设备树

RK3568支持设备树,设备树相关知识执行了解。

设备树目录在arch/arm64/boot/dts/rockchip下。

修改 rk3568-atk-evb1-ddr4-v10.dtsi文件,在根目录 /下建立一个节点:

led_test {compatible="yifeng,led_test";status="okay";reg = <0x0 0xFDC20010 0x0 0x08 /* PMU_GRF_GPIO0C_IOMUX_L */ 0x0 0xFDC20090 0x0 0x08    /* PMU_GRF_GPIO0C_DS_0 */ 0x0 0xFDD60004 0x0 0x08    /* GPIO0_SWPORT_DR_H */ 0x0 0xFDD6000C 0x0 0x08 >; /* GPIO0_SWPORT_DDR_H */};

reg里的值参考LED驱动开发,比如“0x0 0xFDC20010 0x0 0x08”表示 RK3568 的 PMU_GRF_GPIO0C_IOMUX_L 寄存器,其中寄存器地址为 0xFDC20010,长度为 8 个字节。

修改如下:

设备树修改完成以后在 SDK 顶层目录输入如下命令重新编译一下内核:

# 指定 SDK 的板级配置文件
./build.sh lunch
# 编译内核
./build.sh kernel
编译完成以后得到 boot.img, boot.img 就是编译出来的内核+设备树打包在一起的文件

只需要重新烧写boot.img。

烧写完成以后启动开发板。Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有
“led_test”这个节点.

三、编写驱动

led_test.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
//#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define DTSLED_CNT			1		  	/* 设备号个数 */
#define DTSLED_NAME			"led_test"	/* 名字 	*/
#define LEDOFF 				0			/* 关灯 	*/
#define LEDON 				1			/* 开灯 	*//* 映射后的寄存器虚拟地址指针 */
static void __iomem *PMU_GRF_GPIO0C_IOMUX_L_PI;
static void __iomem *PMU_GRF_GPIO0C_DS_0_PI;
static void __iomem *GPIO0_SWPORT_DR_H_PI;
static void __iomem *GPIO0_SWPORT_DDR_H_PI;/* dtsled设备结构体 */
struct dtsled_dev{dev_t devid;			/* 设备号 	 */struct cdev cdev;		/* cdev 	*/struct class *class;		/* 类 		*/struct device *device;	/* 设备 	 */int major;				/* 主设备号	  */int minor;				/* 次设备号   */struct device_node	*nd; /* 设备节点 */
};struct dtsled_dev dtsled;	/* led设备 *//** @description		: LED打开/关闭* @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return 			: 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO0_SWPORT_DR_H_PI);val &= ~(0X1 << 0); /* bit0 清零*/val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1,允许写bit0,bit0,高电平*/writel(val, GPIO0_SWPORT_DR_H_PI);}else if(sta == LEDOFF) { val = readl(GPIO0_SWPORT_DR_H_PI);val &= ~(0X1 << 0); /* bit0 清零*/val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1,允许写bit0,bit0,低电平	*/writel(val, GPIO0_SWPORT_DR_H_PI);} 
}/** @description		: 取消映射* @return 			: 无*/
void led_unmap(void)
{/* 取消映射 */iounmap(PMU_GRF_GPIO0C_IOMUX_L_PI);iounmap(PMU_GRF_GPIO0C_DS_0_PI);iounmap(GPIO0_SWPORT_DR_H_PI);iounmap(GPIO0_SWPORT_DDR_H_PI);
}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{filp->private_data = &dtsled; /* 设置私有数据 */return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {	led_switch(LEDON);		/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);		/* 关闭LED灯 */}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations dtsled_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = 	led_release,
};/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static int __init led_init(void)
{u32 val = 0;int ret;u32 regdata[16];const char *str;struct property *proper;/* 获取设备树中的属性数据 *//* 1、获取设备节点:led_test */dtsled.nd = of_find_node_by_path("/led_test");if(dtsled.nd == NULL) {printk("led_test node not find!\r\n");goto fail_find_node;} else {printk("led_test node find!\r\n");}/* 2、获取compatible属性内容 */proper = of_find_property(dtsled.nd, "compatible", NULL);if(proper == NULL) {printk("compatible property find failed\r\n");} else {printk("compatible = %s\r\n", (char*)proper->value);}/* 3、获取status属性内容 */ret = of_property_read_string(dtsled.nd, "status", &str);if(ret < 0){printk("status read failed!\r\n");} else {printk("status = %s\r\n",str);}/* 4、获取reg属性内容 */ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 16);if(ret < 0) {printk("reg property read failed!\r\n");} else {u8 i = 0;printk("reg data:\r\n");for(i = 0; i < 16; i++)printk("%#X ", regdata[i]);printk("\r\n");}/* 初始化LED *//* 1、寄存器地址映射 */PMU_GRF_GPIO0C_IOMUX_L_PI = of_iomap(dtsled.nd, 0);PMU_GRF_GPIO0C_DS_0_PI = of_iomap(dtsled.nd, 1);GPIO0_SWPORT_DR_H_PI = of_iomap(dtsled.nd, 2);GPIO0_SWPORT_DDR_H_PI = of_iomap(dtsled.nd, 3);/* 2、设置GPIO0_C0为GPIO功能。*/val = readl(PMU_GRF_GPIO0C_IOMUX_L_PI);val &= ~(0X7 << 0);	/* bit2:0,清零 */val |= ((0X7 << 16) | (0X0 << 0));	/* bit18:16 置1,允许写bit2:0,bit2:0:0,用作GPIO0_C0	*/writel(val, PMU_GRF_GPIO0C_IOMUX_L_PI);/* 3、设置GPIO0_C0驱动能力为level5 */val = readl(PMU_GRF_GPIO0C_DS_0_PI);val &= ~(0X3F << 0);	/* bit5:0清零*/val |= ((0X3F << 16) | (0X3F << 0));	/* bit21:16 置1,允许写bit5:0,bit5:0:0,用作GPIO0_C0	*/writel(val, PMU_GRF_GPIO0C_DS_0_PI);/* 4、设置GPIO0_C0为输出 */val = readl(GPIO0_SWPORT_DDR_H_PI);val &= ~(0X1 << 0); /* bit0 清零*/val |= ((0X1 << 16) | (0X1 << 0));	/* bit16 置1,允许写bit0,bit0,高电平	*/writel(val, GPIO0_SWPORT_DDR_H_PI);/* 5、设置GPIO0_C0为低电平,关闭LED灯。*/val = readl(GPIO0_SWPORT_DR_H_PI);val &= ~(0X1 << 0); /* bit0 清零*/val |= ((0X1 << 16) | (0X0 << 0));	/* bit16 置1,允许写bit0,bit0,低电平	*/writel(val, GPIO0_SWPORT_DR_H_PI);/* 注册字符设备驱动 *//* 1、创建设备号 */if (dtsled.major) {		/*  定义了设备号 */dtsled.devid = MKDEV(dtsled.major, 0);ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);if(ret < 0) {pr_err("cannot register %s char driver [ret=%d]\n",DTSLED_NAME, DTSLED_CNT);goto fail_devid;}} else {						/* 没有定义设备号 */ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);	/* 申请设备号 */if(ret < 0) {pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DTSLED_NAME, ret);goto fail_devid;}dtsled.major = MAJOR(dtsled.devid);	/* 获取分配号的主设备号 */dtsled.minor = MINOR(dtsled.devid);	/* 获取分配号的次设备号 */}printk("dtsled major=%d,minor=%d\r\n",dtsled.major, dtsled.minor);	/* 2、初始化cdev */dtsled.cdev.owner = THIS_MODULE;cdev_init(&dtsled.cdev, &dtsled_fops);/* 3、添加一个cdev */ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);if(ret < 0)goto del_unregister;/* 4、创建类 */dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);if (IS_ERR(dtsled.class)) {goto del_cdev;}/* 5、创建设备 */dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);if (IS_ERR(dtsled.device)) {goto destroy_class;}return 0;destroy_class:class_destroy(dtsled.class);
del_cdev:cdev_del(&dtsled.cdev);
del_unregister:unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:led_unmap();
fail_find_node:return -EIO;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit led_exit(void)
{/* 取消映射 */led_unmap();/* 注销字符设备驱动 */cdev_del(&dtsled.cdev);/*  删除cdev */unregister_chrdev_region(dtsled.devid, DTSLED_CNT); /* 注销设备号 */device_destroy(dtsled.class, dtsled.devid);class_destroy(dtsled.class);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel
ARCH=arm64
CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-export  ARCH  CROSS_COMPILECURRENT_PATH := $(shell pwd)
obj-m := led_test.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

四、编写应用

test_app.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF 	0
#define LEDON 	1/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开led驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 *//* 向/dev/led文件写入数据 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 关闭文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;
}

编译

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc ledApp.c -o ledApp

五、测试

1、关闭 LED 的心跳灯

echo none > /sys/class/leds/work/trigger

2、加载和卸载驱动模块

depmod //第一次加载驱动的时候需要运行此命令
modprobe dtsled //加载驱动或
insmod led_test.ko卸载
rmmod led_test

3、测试

./ledApp /dev/led_test 1 //打开 LED 灯
./ledApp /dev/led_test 0 // 关闭 LED 灯

如有侵权,或需要完整代码,请及时联系博主。

相关文章:

RK3568笔记三十六:LED驱动开发(设备树)

若该文为原创文章&#xff0c;转载请注明原文出处。 记录使用设备树编写一个简单的 LED 灯驱动程序 一、编程思路 程序编写的主要内容为添加 LED 灯的设备树节点、在驱动程序中使用 of 函数获取设备节点中的 属性&#xff0c;编写测试应用程序。 • 首先向设备树添加 LED 设备…...

AC修炼计划(AtCoder Regular Contest 180) A~C

A - ABA and BAB A - ABA and BAB (atcoder.jp) 这道题我一开始想复杂了&#xff0c;一直在想怎么dp&#xff0c;没注意到其实是个很简单的规律题。 我们可以发现我们住需要统计一下类似ABABA这样不同字母相互交替的所有子段的长度&#xff0c;而每个字段的的情况有&#xff…...

云计算练习题

第一题&#xff1a;每周日晚上11点59分需要将/data目录打包压缩到/mnt目录下并以时间命名 #crontab -e 59 23 * * 7 /bin/tar czvf /mnt/date %F-data.tar.gz /data 59 23 * * 7 /bin/tar czvf /mnt/date %T.tar.gz /data 第二题&#xff1a;查找出系统中/application目录下所有…...

《战甲神兵》开发者报告:游戏崩溃问题80%发生在Intel可超频酷睿i9处理器上——酷睿i7 K系列CPU也表现出高崩溃率

在Intel持续面临第13代和第14代CPU崩溃问题的背景下&#xff0c;近日&#xff0c;《战甲神兵》(Warframe)的开发者们于7月9日披露了游戏崩溃的统计数据&#xff0c;并描述了诊断该问题的过程。根据开发团队的说法&#xff0c;一名未进行超频且使用全新PC的员工&#xff0c;即便…...

Postman下载及使用说明

Postman使用说明 Postman是什么&#xff1f; ​ Postman是一款接口对接工具【接口测试工具】 接口&#xff08;前端接口&#xff09;是什么&#xff1f; ​ 前端发送的请求普遍被称为接口 ​ 通常有网页的uri参数格式json/key-value请求方式post/get响应请求的格式json 接…...

什么是im即时通讯?WorkPlus im即时通讯私有化部署安全可控

IM即时通讯是Instant Messaging的缩写&#xff0c;指的是一种实时的、即时的电子信息交流方式&#xff0c;也被称为即时通讯。它通过互联网和移动通信网络&#xff0c;使用户能够及时交换文本消息、语音通话、视频通话、文件共享等信息。而WorkPlus im即时通讯私有化部署则提供…...

hnust 1794: 机器翻译

hnust 1794: 机器翻译 题目描述 小晨的电脑上安装了一个机器翻译软件&#xff0c;他经常用这个软件来翻译英语文章。 这个翻译软件的原理很简单&#xff0c;它只是从头到尾&#xff0c;依次将每个英文单词用对应的中文含义来替换。对于每个英文单词&#xff0c;软件会先在内存…...

AI人工智能开源大模型生态体系分析

人工智能开源大模型生态体系研究 "人工智能开源大模型生态体系研究报告v1.0"揭示&#xff0c;AI(A)的飞速发展依赖于三大核心&#xff1a;数据、算法和算力。这一理念已得到业界广泛认同&#xff0c;三者兼备才能推动AI的壮大发展。随着AI大模型的扩大与普及&#xf…...

ArkTS学习笔记_封装复用之@Styles装饰器

ArkTS学习笔记_封装复用之Styles装饰器 背景&#xff1a; 在开发中&#xff0c;如果每个组件的样式都需要单独设置&#xff0c;就会出现大量代码在进行重复样式设置&#xff0c;虽然可以复制粘贴&#xff0c;但为了代码简洁性和后续方便维护&#xff0c;给出的思路是&#xff…...

根据vue学习react

react的函数式组件与vue2是很像的 一、基础类似点 1、组件下拥有一个根节点&#xff0c;vue2是template&#xff0c;react是幽灵标签<> 2、vue2是{{}}以及v-model&#xff0c;react的绑定是{} 3、vue2编译html是v-html&#xff0c;react是{}&#xff0c;并且react的jsx中…...

Hi3861 OpenHarmony嵌入式应用入门--HTTPD

httpd 是 Apache HTTP Server 的守护进程名称&#xff0c;Apache HTTP Server 是一种广泛使用的开源网页服务器软件。 本项目是从LwIP中抽取的HTTP服务器代码&#xff1b; Hi3861 SDK中已经包含了一份预编译的lwip&#xff0c;但没有开启HTTP服务器功能&#xff08;静态库无法…...

MICS2024|少样本学习、多模态技术以及大语言模型在医学图像处理领域的研究进展|24-07-14

小罗碎碎念 本期推文主题 今天的会议很多主题都集中在大模型、多模态这两个方面&#xff0c;很明显&#xff0c;这两个方向都是目前的研究热点。 所以&#xff0c;我这一期推文会先简单的分析一下秦文健&#xff08;中科院&#xff09;和史淼晶&#xff08;同济大学&#xff09…...

ConfigMap-secrets-静态pod

一.ConfigMap 1.概述 ConfigMap资源&#xff0c;简称CM资源&#xff0c;它生成的键值对数据&#xff0c;存储在ETCD数据库中 应用场景&#xff1a;主要是对应用程序的配置 pod通过env变量引入ConfigMap&#xff0c;或者通过数据卷挂载volume的方式引入ConfigMap资源 官方解释…...

SQL Error: 1406, SQLState: 22001

SQL错误代码1406和SQLState 22001通常表示“列数据过长”错误。这意味着尝试插入或更新列中的值&#xff0c;但该值的长度超过了该列允许的最大长度。 解决此问题的几个步骤&#xff1a; 检查列长度&#xff1a; 确定引起错误的列。检查数据库架构中该列允许的最大长度。 验证…...

【密码学基础】基于LWE(Learning with Errors)的全同态加密方案

学习资源&#xff1a; 全同态加密I&#xff1a;理论与基础&#xff08;上海交通大学 郁昱老师&#xff09; 全同态加密II&#xff1a;全同态加密的理论与构造&#xff08;Xiang Xie老师&#xff09; 现在第二代&#xff08;如BGV和BFV&#xff09;和第三代全同态加密方案都是基…...

Linux - 基础开发工具(yum、vim、gcc、g++、make/Makefile、git)

目录 Linux软件包管理器 - yum Linux下安装软件的方式 认识yum 查找软件包 安装软件 如何实现本地机器和云服务器之间的文件互传 卸载软件 Linux编辑器 - vim vim的基本概念 vim下各模式的切换 vim命令模式各命令汇总 vim底行模式各命令汇总 vim的简单配置 Linux编译器 - gc…...

网络安全法律框架更新:最新合规要求与企业应对策略

网络安全法律框架的最新更新 近期&#xff0c;中国的网络安全法律框架经历了重要的更新。2022年&#xff0c;《网络安全法》迎来了首次修改&#xff0c;这一修订主要是为了与《数据安全法》和《个人信息保护法》等新实施的法律进行衔接协调&#xff0c;完善法律责任制度&#x…...

数仓工具—Hive语法之正则表达式函数

正则表达式函数 之前我们介绍过like rlike regexp 这些关键字,都是和匹配有关的,今天我们介绍一下hive 的REGEXP_REPLACE 和REGEXP_EXTRACT 函数,背景是使用Hive正则表达式函数提取数字 在我的其他文章中,我们已经看到了如何使用Hive正则表达式从字符串中提取日期值。正则…...

WKCTF 2024 easy_heap

很经典的house of orange unsortedbin attack FSOP 变量覆盖 不能 free&#xff0c;那首先想到就是 house of orange泄露Libc基址&#xff0c;然后unsortedbin attack。 但是只能show(8)&#xff0c;就不能用largebin的套路来泄露堆地址了&#xff0c;那怎么办呢&#xff1f; …...

SQL 多变关联使用子查询去重

不去重状态 select a.*,b.recon_amt from free_settlement_first aleft join free_settlement_second b on a.settlement_first_id b.settlement_first_id 有2条数据出现了重复 使用子查询去重 select a.*,b.recon_amt from free_settlement_first aleft join free_settlem…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

python执行测试用例,allure报乱码且未成功生成报告

allure执行测试用例时显示乱码&#xff1a;‘allure’ &#xfffd;&#xfffd;&#xfffd;&#xfffd;&#xfffd;ڲ&#xfffd;&#xfffd;&#xfffd;&#xfffd;ⲿ&#xfffd;&#xfffd;&#xfffd;Ҳ&#xfffd;&#xfffd;&#xfffd;ǿ&#xfffd;&am…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...