正点原子imx6ull-mini-Linux驱动LED(新字符设备驱动)(3)

news2024/11/13 14:42:52

经过前几节实验的实战操作,我们已经掌握了 Linux 字符设备驱动开发的基本步骤,字符 设备驱动开发重点是使用 register_chrdev 函数注册字符设备,当不再使用设备的时候就使用 unregister_chrdev 函数注销字符设备,驱动模块加载成功以后还需要手动使用 mknod 命令创建 设备节点。register_chrdev 和 unregister_chrdev 这两个函数是老版本驱动使用的函数,现在新的 字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数。 本节我们就来学习一下如何编写新字符设备驱动,并且在驱动模块加载的时候自动创建设备节 点文件。

1:新字符设备驱动原理

1.1:分配和释放设备号

使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会 带来两个问题:

①、需要我们事先确定好哪些主设备号没有使用。

②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为 200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。这样太浪 费次设备号了!一个 LED 设备肯定只能有一个主设备号,一个次设备号。有前面的学习知道主设备号占高12位,次设备号是低20位。

1.2:新的设备申请函数

如果没有指定设备号的话就使用如下函数来申请设备号:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数 from 是要申请的起始设备号,也就是给定的设备号;参数 count 是要申请的数量,一 般都是一个;参数 name 是设备名字。 注 销 字 符 设 备 之 后 要 释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函 数 还 是 register_chrdev_region 函数申请的设备号,统一使用如下释放函数:

void unregister_chrdev_region(dev_t from, unsigned count)

新字符设备驱动下,设备号分配示例代码如下:

int major; /* 主设备号 */
int minor; /* 次设备号 */
dev_t devid; /* 设备号 */

if (major) { /* 定义了主设备号 */
    devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */
    register_chrdev_region(devid, 1, "test");
} else { /* 没有定义设备号 */
    alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
    major = MAJOR(devid); /* 获取分配号的主设备号 */
    minor = MINOR(devid); /* 获取分配号的次设备号 */
}

先定义主次设备号及设备号变量devid,判断设备号是否有效,有效就进行注册,MKDEV 来构建设备号,次设备号选择 0。。无效就动态注册设备号,设备号申请成功以后使用 MAJOR 和 MINOR 来提取出主设备号和次设备号。

1.3:新的字符设备注册方法

1.3.1:字符设备结构

1.3.2:cdev_init 函数

1.3.3 :  cdev_add 函数

 

 1.3.4:cdev_del 函数

 1.4:自动创建设备节点

在前面的 Linux 驱动实验中,当我们使用 modprobe 加载驱动程序以后还需要使用命令 “mknod”手动创建设备节点。本节就来讲解一下如何实现自动创建设备节点,在驱动中实现 自动创建设备节点的功能以后,使用 modprobe 加载驱动模块成功的话就会自动在/dev 目录下 创建对应的设备文件。

1.4.1:mdev 机制

 udev 是一个用户程序,在 Linux 下通过 udev 来实现设备文件的创建与删除,udev 可以检 测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用 modprobe 命令成功加载驱动模块以后就自动在/dev 目录下创建对应的设备节点文件,使用 rmmod 命令卸载驱动模块以后就删除掉/dev 目录下的设备节点文件。使用 busybox 构建根文件 系统的时候,busybox 会创建一个 udev 的简化版本—mdev,所以在嵌入式 Linux 中我们使用mdev 来实现设备节点文件的自动创建与删除,Linux 系统中的热插拔事件也由 mdev 管理,在 /etc/init.d/rcS 文件中如下语句:

echo /sbin/mdev > /proc/sys/kernel/hotplug

上述命令设置热插拔事件由 mdev 来管理

1.4.2:创建和删除类

1.4.3:创建设备和删除设备

参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。

1.4.4:参考示例

1.5:设置文件私有数据

又是类又是结构体,有点CPP的味道了,一般来说c里面的结构体是成员不能有函数,这里有函数了,那结构体和类的区别就是成员默认结构体是私有的,类是共有的,类可以用private,public和protect来修饰。

 

2:LED灯驱动程序编写

这里我们就不能像上节那样设备操作四件套加注册注销的出入口两件套了。我们的注册和注销都被细分了,分成设备、设备号、类之类的。

 2.1:头文件添加

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

 跟上节比起来多了device.h/cdev.h

2.2:设备号、设备名及宏定义

宏 NEWCHRLED_CNT 表示设备数量,在申请设备号或者向 Linux 内核添加字 符设备的时候需要设置设备数量,一般我们一个驱动一个设备,所以这个宏为 1。

宏 NEWCHRLED_NAME 表示设备名字,本实验的设备名为“newchrdev”,为了 方便管理,所有使用到设备名字的地方统一使用此宏,当驱动加载成功以后就生成 /dev/newchrled 这个设备文件。

#define NEWCHRLED_CNT			1		  	/* 设备号个数 */
#define NEWCHRLED_NAME			"newchrled"	/* 名字 */
#define LEDOFF 					0			/* 关灯 */
#define LEDON 					1			/* 开灯 */

2.3:寄存器物理地址

/* 寄存器物理地址 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)

2.4:映射后的虚拟内存地址指针

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

2.5:新字符设备结构体

创建设备结构体 newchrled_dev。

定义一个设备结构体变量 newchrdev,此变量表示 led 设备。

/* newchrled设备结构体 */
struct newchrled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};

struct newchrled_dev newchrled;	/* led设备 */

2.6:开关灯函数

/*
 * @description		: LED打开/关闭
 * @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED
 * @return 			: 无
 */
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}

2.7:设备操作四件套

2.7.1:打开设备函数

在 led_open 函数中设置文件的私有数据 private_data 指向 newchrdev。

/*
 * @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 = &newchrled; /* 设置私有数据 */
	return 0;
}

2.7.2:读取设备函数

/*
 * @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;
}

2.7.3:写入设备函数

/*
 * @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;
}

2.7.4:关闭设备函数

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

四件套与上节基本一致,可能就是在打开设备上有区别,因为设备的属性都被设置成私有的了。

2.8:设备操作函数,结构体变量成员赋值

/* 设备操作函数 */
static struct file_operations newchrled_fops = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
	.release = 	led_release,
};

2.9:新的注册注销两件套:驱动入口函数及出口函数

2.9.1:入口函数

根据前面讲解的方法在驱动入口函数 led_init 中申请设备号、添加字符设 备、创建类和设备。本实验我们采用动态申请设备号的方法,使用 printk 在终端上显 示出申请到的主设备号和次设备号。

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init led_init(void)
{
	u32 val = 0;

	/* 初始化LED */
	/* 1、寄存器地址映射 */
  	IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
  	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
	GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

	/* 2、使能GPIO1时钟 */
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, IMX6U_CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	/*寄存器SW_PAD_GPIO1_IO03设置IO属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
	 */
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (newchrled.major) {		/*  定义了设备号 */
		newchrled.devid = MKDEV(newchrled.major, 0);
		register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);	/* 申请设备号 */
		newchrled.major = MAJOR(newchrled.devid);	/* 获取分配号的主设备号 */
		newchrled.minor = MINOR(newchrled.devid);	/* 获取分配号的次设备号 */
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	
	
	/* 2、初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);

	/* 4、创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		return PTR_ERR(newchrled.class);
	}

	/* 5、创建设备 */
	newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		return PTR_ERR(newchrled.device);
	}
	
	return 0;
}

9.2:驱动出口函数

根据前面讲解的方法,在驱动出口函数 led_exit 中注销字符新设备、删除 类和设备。就是对应把结构体里的成员全部清除

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit led_exit(void)
{
	/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);

	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除cdev */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */

	device_destroy(newchrled.class, newchrled.devid);
	class_destroy(newchrled.class);
}

 2.9.3:模块入口出口调用

module_init(led_init);
module_exit(led_exit);

2.10:许可证及作者

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 3:编写测试APP

3.1:头文件添加

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

3.2:宏定义部分

#define LEDOFF 	0
#define LEDON 	1

3.3:  main函数编写

/*
 * @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;
}

4:Make file文件编写

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
 
obj-m := newchrled.o
 
build: kernel_modules
 
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
 
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

5:编译测试 APP

 输入如下命令编译测试 newchrledApp.c 这个测试程序:

arm-linux-gnueabihf-gcc newchrledApp.c -o newchrledApp

编译成功以后就会生成 ledApp 这个应用程序。

将编译出来的 newchred.ko和 newchrledApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中, 重启开发板,进入到目录 lib/modules/4.1.15 中,输入如下命令拷贝newchrled.ko 驱动模块:

要加sudo

cd ~/linux/drivers/linux_drivers/led/
sudo cp newchrled.ok newchrledApp ~/linux/nfs/rootfs/lib/modules/4.1.15/ -f

 为了方便我们不用每一次都用gcc编译代码和复制,我们在本目录下加入一个shell脚本一起执行这些操作CompileTest.sh

#!/bin/bash
rm newchrledApp
arm-linux-gnueabihf-gcc newchrledApp.c -o newchrledApp
sudo cp newchrled.ko newchrledApp ~/linux/nfs/rootfs/lib/modules/4.1.15/ -f
chmod 777 CompileTest.sh

 给其可执行权限,然后我们去nfs挂载的文件系统里看看,没有问题,每次跟Makefile一样都改个名字就行

depmod //第一次加载驱动的时候需要运行此命令
modprobe led.ko //加载驱动

使用 cat /proc/devices查看挂载驱动或者使用 ls /dev/newchrled -l 查看该设备节点是否存在 

 

这里我们没有再手动挂载设备节点

驱动节点创建成功以后就可以使用 newchrledApp 软件来测试驱动是否工作正常,最后卸载驱动

./newchrledApp /dev/newchrled 1 //打开 LED 灯
./newchrledApp /dev/newchrled 0 //关闭 LED 灯
rmmod newchrled.ko

 第一次modprobe安装驱动都要depmod一下

 卸载驱动后,再次查看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1957032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Springboot原理相关

目录 配置优先级 bean的管理 获取bean bean的作用域 第三方bean SpringBoot原理 自动配置 配置优先级 在springboot中优先级application.properties>application.yml>application.yaml 虽然支持多种格式配置文件&#xff0c;但是在项目开发中&#xff0c;推荐统…

[MIT6.5840]MapReduce

MapReduce Lab 地址 https://pdos.csail.mit.edu/6.824/labs/lab-mr.html 论文地址 https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/mapreduce-osdi04.pdf 工作原理 简单来讲&#xff0c;MapReduce是一种分布式框架&#xff0c;可以用来处理…

英伟达最强劲敌Groq一招绝杀GPU,反超GPT-4o mini2倍,AI大佬Karpathy:直接飞升AGI!

Llama 3.1 405B被吐槽太笨重? 英伟达对手AI新星Groq一招绝杀:上LPU直接速度翻倍,直接让Llama 3.1飞升AGI! Meta 最新发布的 Llama 3.1 405B 的开源让AI圈不平静了! 追捧者感慨"GPT-4o的能力已握在手中”,而批评者反驳说,大体量消耗这么多算力,有些结果跑得还不如GP…

【内网】安装wget

先是去RPM Search 下载了wget-1.24.5-2.1.x86_64.rpm这个包&#xff0c;结果安装的时候报一堆错 [rootlocalhost ~]# rpm -ivh wget-1.24.5-2.1.x86_64.rpm warning: wget-1.24.5-2.1.x86_64.rpm: Header V3 RSA/SHA512 Signature, key ID 29b700a4: NOKEY error: Failed dep…

不同WEB下的的ApplicationContext的选择

依赖 ApplicationContext类型选择 默认情况下&#xff0c;spring通过选择的web端的框架来选择使用哪个ApplicationContext子类&#xff0c;默认情况下我们一般使用spring mvc框架&#xff0c;这个时候AC的实现类为 org.springframework.boot.web.servlet.context.AnnotationC…

docker安装mysql8自动备份脚本

引用&#xff1a;https://blog.csdn.net/leovnay/article/details/140585094 # 创建两个卷 docker volume ls docker volume create mysqlData docker volume create mysqlSQL# 运行容器 docker run -d --namemysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORDxxx -e TZAsia/Shangh…

Java小抄|Java中的List与Map转换

文章目录 1 List<User> 转Map<User.id,User>2 基础类型的转换&#xff1a;List < Long> 转 Map<Long,Long> 1 List 转Map<User.id,User> Map<Long, User> userMap userList.stream().collect(Collectors.toMap(User::getId, v -> v, …

自闭症儿童上小学教育方法:个性化关怀,引领全面发展

在教育的征程中&#xff0c;为自闭症儿童提供适合他们的小学教育方法至关重要。这些孩子如同独特的星星&#xff0c;需要我们用个性化的关怀去照亮他们的成长之路&#xff0c;引领他们实现全面发展。 个性化关怀是自闭症儿童小学教育的核心。每个孩子都是独一无二的&#xff0c…

钡铼技术PLC网关:实现PLC数据无缝对接MQTT协议

MQTT 协议概述 MQTT 是用于物联网的标准消息传递协议。它被设计为一种非常轻量级的发布/订阅消息传送&#xff0c;非常适合以较小的代码占用量和网络带宽连接远程设备。 PLC网关是一种专门设计用于连接可编程逻辑控制器&#xff08;PLC&#xff09;与其它网络设备或系统的中间…

元器件基础学习笔记——二极管基础

一、二极管基础 二极管是用半导体材料(硅、硒、锗等)制成的一种电子器件&#xff0c;具有单向导电性&#xff0c;是现代电子技术的基石。它在电子电路中扮演着至关重要的角色&#xff0c;通过与电阻、电容、电感等元器件的合理连接&#xff0c;能够实现整流、检波、限幅、稳压等…

python实现GUI版图片锐化小工具

目录 效果展示代码脚本代码 效果展示 锐化前&#xff1a; 锐化后代码 sharpen_img.py import tkinter as tk from tkinter import filedialog from PIL import Image, ImageTk,ImageFilter import os class ImageViewerApp:def __init__(self, root):self.root rootself.r…

空气净化器CE认证简介

空气净化器中有多种不同的技术和介质&#xff0c;使它能够向用户提供清洁和安全的空气。由于空气净化器本身就和我们的生活息息相关。因此对于产品本身的安全性是消费者首先需要考虑的&#xff0c;另一方面就是其在净化空气上的效率和效果。如今国内的空气净化器随着工艺上的不…

ts 下使用 interactjs 的时候,事件类型该如何定义 InteractEvent

ts 下使用 interactjs 的时候&#xff0c;事件类型该如何定义 InteractEvent 一、问题 interactjs 是一个很好用的给元素添加拖动事件的插件&#xff0c;它可以实现如下的效果。 其官网是 https://interactjs.io/ vitetsvue3 项目中用到了 interactjs 这个库&#xff0c;但在…

42度酒和52度酒哪个好?

我们平时在聚会的时候都会喝酒&#xff0c;而在买酒时通常会看到超市或者白酒专卖店里的白酒大多都是52度或者是42度的&#xff0c;而喝酒的人当中大多对白酒没有一定的了解&#xff0c;所以在接到买酒任务的时候&#xff0c;当看到一款酒有两种度数的时候&#xff0c;就有些拿…

元宇宙营销,能够持续下去吗?

Photo by Oberon Copeland veryinformed.com on Unsplash 一场完美风暴让一些行业观察人士怀疑&#xff0c;元宇宙这个曾经营销界最喜欢的闪亮对象&#xff0c;是否正在维持生命。像ChatGPT这样的生成式人工智能(AI)已经接管了技术炒作周期&#xff1b;关键平台的参与度微乎其…

为什么要加密源代码?六款好用的源代码加密软件推荐

在当今数字化时代&#xff0c;源代码是许多企业和开发人员最重要的资产之一。无论是保护知识产权、维护竞争优势&#xff0c;还是确保应用程序的安全性&#xff0c;加密源代码都是至关重要的措施。以下将详细探讨为什么需要加密源代码&#xff0c;并推荐六款好用的源代码加密软…

手把手教你暗通道先验去雾算法

0&#xff0c;流程 暗通道先验去雾算法&#xff08;Dark Channel Prior, DCP&#xff09;是一种基于图像的去雾技术&#xff0c;由Kaiming He等人在2009年提出。这种算法利用了大气散射模型&#xff0c;通过估计大气光和图像的传输图来去除雾的影响。以下是暗通道先验去雾算法…

PLC网关:开启工业4.0时代的智能工厂之路

PLC即可编程逻辑控制器&#xff0c;是工业自动化领域的核心设备&#xff0c;广泛应用于各个工业领域。从PLC问世至今&#xff0c;一直表现出强大的生命力和高速增长态势&#xff0c;2020年全球PLC市场的销售量已经达到了百亿RMB级别。 随着行业智能化、数字化推广&#xff0c;…

Docker从入门到实践教程(电子版)

前言 Docker 是个伟大的项目&#xff0c;它彻底释放了虚拟化的威力&#xff0c;极大降低了云计算资源供应的成本&#xff0c;同时让应用的 分发、测试、部署和分发都变得前所未有的高效和轻松&#xff01; 本电子书既适用于具备基础 Linux 知识的 Docker 初学者&#xff0c;也…

hot100-5-普通数组

53最大子数组和 56合并区间 238除自身以外数组的乘积 用前缀乘积和后缀乘积 41缺失的第一个正数 189轮转数组