【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程
文章目录
- 注册杂项设备
- 驱动模块传参
- 注册字符设备
开发环境: windows + ubuntu18.04 + 迅为rk3568开发板
注册杂项设备
相较于字符设备,杂项设备有以下两个优点:
- 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时,只需使用子设备号进行区分即可。
- 使用简单:相比如普通的字符设备驱动, misc驱动只需要将基本信息通过结构体传递给相应处理函数即可。
在linxu系统中可使用 cat /proc/misc 命令查看系统中的杂项设备。注册杂项设备的步骤:
-
1.填充设备操作集结构体
struct file_operations; -
2.填充杂项设备结构体
struct miscdevice; -
3.使用函数
misc_register注册杂项设备; -
4.使用函数
misc_deregister卸载杂项设备;
上面三步可使用下面函数直观用表达,即:
static struct file_operations xxx_fops{.owner = THIS_MODULE, .read = xxx_read, ....
};
struct miscdevice xxx_dev{.minor = MISC_DYNAMIC_MINOR, .name = "xxx", .fops = &xxx_fops
};
static int __init xxx_init(void) //驱动入口函数
{int ret;printk(KERN_EMERG "xxx_init\r\n");ret = misc_register(&xxx_dev);//注册杂项设备if(ret<0){printk( "misc_register failed\r\n");return -1;}printk( "misc_register ok\r\n");return 0;
}
static void __exit xxx_exit(void) //驱动出口函数
{printk(KERN_EMERG "xxx_exit\r\n");misc_deregister(&xxx_dev); //卸载杂项设备
}
module_init(xxx_init); //注册入口函数
module_exit(xxx_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xxx");
具体实现注册一个杂项设备的示例代码如下:
#include <linux/kernel.h>
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>// 打开杂项设备
int _open(struct inode *inode,struct file*file)
{printk(KERN_EMERG"hello misc");return 0;
}// 关闭杂项设备
int close(struct inode * inode, struct file *file)
{printk(KERN_EMERG"close");return 0;
}// 读取杂项设备中的数据
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] = "kernel";if(copy_to_user(buff,kbuff,strlen(kbuff)) != 0) // 将内核中的数据给应用{printk("copy_to_user error\r\n");return -1;} printk(KERN_EMERG"copy_to_user is successful\r\n");return size;
}// 写入数据到杂项设备中
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{char kbuff[32] ;if(copy_from_user(kbuff,buff,size)!= 0) // 从应用那儿获取数据{printk("copy_from_user error\r\n");return -1;} printk(KERN_EMERG"copy_from_user data:%s\r\n",kbuff);return size;
}// 设备文件描述集
struct file_operations misc_fops ={.owner = THIS_MODULE,.open = misc_open,.release = close,.read = misc_read,.write = misc_write
};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc", // 杂项设备名 注册成功后会在 /dev目录下显示.fops = &misc_fops
};// 驱动的入口函数
static int __init misc_init(void)
{int ret = 0;ret = misc_register(&misc_dev);if(ret < 0)printk(KERN_EMERG"misc register is error\r\n.");elseprintk(KERN_EMERG"misc register is seccussful\r\n.");return 0;
}//驱动的出口函数
static void __exit misc_exit(void)
{misc_deregister(&misc_dev);printk(KERN_EMERG"baibai\r\n");
}module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
编译传送到开发板上后,先试用insmod +驱动名.ko挂载驱动,其结果为:

上述驱动代码的测试代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char *argv[])
{char filePath[] = "/dev/hello_misc";// 打开文件int fd = open(filePath,O_RDWR);if(fd < 0){printf("opening is failed.\n");return -1;}elseprintf("opening is successful.\n");// 读取char buff1[32],buff2[32] = "hello this is app";read(fd,buff1,sizeof(buff1));printf("buff1 is %s.\n",buff1);// 写入write(fd,buff2,sizeof(buff2)); close(fd);return 0;
}
使用./+程序名运行测试代码后,得结果如下:

驱动模块传参
总所周知,应用程序传参是通过shell终端传,只要将main函数按照下面格式书写即可完成传参操作
int main(int argc ,char *argv[])
{return 0;
}
相比之下,驱动模块传递参数需要借助其他函数完成传参操作:
1. 传递单个参数给内核
module_param(name, type, perm)
参数解释:
-
name:参数名,既是外部参数名,又是内部参数名。
-
type:参数的数据类型,可取int、charp等。
-
perm:访问权限。八进制,如:0777。0表示该参数在文件系统中不可见。
注意:传递字符作为参数时数据类为charp,而不是char.
2.传递数组给内核
module_param_array(name, type, nump, perm)
- module_param_array(name,type,nump,perm)
- name:数组参数名,既是外部参数,又是内部参数
- type:参数的数据类型
- nump:终端传给数组的实际元素个数(指针变量)
- perm:访问权限,0644。0表示该参数在文件系统中不可见
3.传递字符串给内核
module_param_string(name, string, len, perm)
- name:参数名,外部参数名
- string:内部参数名(内部字符数组名)
- len:数组长度
- perm:访问权限,0644。0表示该参数在文件系统中不可见
传递参给内核的作用:
- 1.设置驱动的相关参数,如:设备数量、设备缓冲区大小等等。
- 2.可进行安全校验,放置驱动被他人盗用。
说明:
参数传递的适用时机为 加载驱动到内核时,命令形式为:insmod +驱动名.ko +参数1名=参数值 参数2名=参数值 ……。例如下面示例中使用insmod file.ko date=12传递参数:
#include <linux/module.h>
#include <linux/init.h>// 保存参数
static int date;
static int date1;
static int data[5];
static int count;
static char str[32];
static char *strData;// 传递单个参数
module_param(date,int,S_IRUGO|S_IWUSR); // 可读可写
module_param(date1,int,S_IRUGO); // 可读可写
module_param(strData,charp,S_IRUGO); // 可读// 传递多个参数
module_param_array(data,int,&count,S_IRUGO); // 可读
module_param_string(str,str,sizeof(str),S_IRUGO); // 可读// 驱动的入口函数
static int __init dev_init(void)
{int i=0;if(strcmp(str,"myTest")!=0){printk("dev_init error\r\n");return -1;}printk("---------------------------------------\r\n");printk(KERN_EMERG"dev_init is successful!\r\n");for(i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("str:%s strData:%s\r\n",str,strData);printk("date:%d count:%d\r\n",date,count);data[1] = 111;date = 10;date1 = 111;return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{int i=0;for( i=0;i<count;++i)printk("data[%d] = %d \r\n",i,data[i]);printk("date:%d count:%d\r\n",date,count);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
注册字符设备
步骤一:驱动初始化,需要申请设备号,初始化并且注册cdev结构体,初始化硬件;
可使用动态申请或静态申请设备号,其中动态申请设备号一般在235-255,静态申请则一般由用户手动输入。
静态申请的函数原型为:
int register_chrdev_region(dev_t, unsigned, const char *);
参数含义:
- from: 自定义的 dev_t 类型设备号。
- count: 申请设备的数量。
- name: 申请的设备名称。
函数返回值:申请成功返回 0,申请失败返回负数。
动态申请的函数原型为:
int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
参数含义:
- dev : 会将申请完成的设备号保存在 dev 变量中。
- baseminor: 次设备号可申请的最小值。
- count: 申请设备的数量。
- name: 申请的设备名称。
函数返回值:申请成功返回 0,申请失败返回负
Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符设备对象,cdev 记录了字符设备号、内核对象、文件操作 file_operations 结构体(设备的打开、读写、关闭等操作接口)等信息:
struct cdev {struct kobject kobj; //内嵌的内核对象.struct module *owner; //该字符设备所在的内核模块的对象指针. const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.struct list_head list; //用来将已经向内核注册的所有字符设备形成链表. dev_t dev; //字符设备的设备号,由主设备号和次设备号构成. unsigned int count; //隶属于同一主设备号的次设备号的个数.
};
初始化设备描述集cdev结构体的函数原型为:
void cdev_init(struct cdev *, const struct file_operations *);
参数含义:
- 参数1:表示是抽象设备结构体;
- 参数2:表示文件操作集;
注册设备到内驱使用下面函数
int cdev_add(struct cdev *, dev_t, unsigned);
参数含义:
- 参数1:为要添加的 struct cdev 类型的结构体
- 参数2:为申请的字符设备号
- 参数3:为和该设备关联的设备编号的数量
若函数在内核中添加成功返回 0,添加失败返回负数。
步骤二:构建设备文件操作描述集file_operations
也就是read、write、open、close等函数。
步骤三:生成并且添加设备节点
- 手动添加设备节点:也就是在加载驱动到内核时添加,即
mknod + 路径/设备名 +设备类型 + 主设备号 + 次设备号 - 自动添加设备节点:初始化内核时,使用函数自动添加。即先试用函数
class_create创建一个类,在使用函数device_create创建并且添加设备节点。
class_create函数说明:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \ __class_create(owner, name, &__key); \
})
函数作用:
用于动态创建设备的逻辑类,并完成部分字段的初始化,然后将其添加进 Linux 内核系统。
参数含义:
- owner:指向函数即将创建的这个 struct class 的模块。一般为 THIS_MODULE。
- name:代表即将创建的 struct class 变量的名字。
返回值:struct class * 类型的类。
device_create函数说明:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt,...);
函数作用:
用来在 class 类中下创建一个设备属性文件,udev 会自动识别从而进行设备节点的创建。
参数含义:
- cls:指定所要创建的设备所从属的类。
- parent:指定该设备的父设备,如果没有就指定为 NULL。
- devt:指定创建设备的设备号。
- drvdata:被添加到该设备回调的数据,没有则指定为 NULL。
- fmt:添加到系统的设备节点名称。
返回值:struct device * 类型结构体的设备
步骤四:注销字符设备驱动,
- 步骤一:使用函数
unregister_chrdev_region释放设备号
void unregister_chrdev_region(dev_t, unsigned)
该函数只有一个参数,为要删除设备的设备号,并且函数无返回值。
- 步骤二:使用函数
cdev_del步骤二:删除设备操作集卸载cdev
void cdev_del(struct cdev *);
该函数只有一个参数,为要删除的 struct cdev 类型的结构体,并且函数无返回值。
- 步骤三:使用函数
device_destroy卸载设备
void device_destroy(struct class *cls, dev_t devt);
用来删除 cls 类中的devt设备属性文件,udev 会自动识别从而进行设备节点的删除。
- 步骤四:使用函数
class_destroy删除类class
void class_destroy(struct class *cls);
该函数只有一个参数,为要删除的类,并且函数无返回值。
示例代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>#define DEVICE_NUMBER 1 // 设备数量
#define DEVICE_SNAME "schrdev" // 静态申请时设备名
#define DEVICE_ANAME "achrdev" // 动态申请时设备名
#define DEVICE_MINOR_NUM 0 // 次设备号起始地址
#define DEVICE_CLASS_NAME "myTestClass" // 类名
#define DEVICE_MYNAME "mytest"// 打开设备
int chrdev_open(struct inode*inode,struct file*file)
{printk("chrdev_open is opened\r\n");return 0;
}// 保存设备号 其中前12位为主设备号 后20位为次设备号
static dev_t dev_num;// 定义主设备号 次设备号
static int major_num,minor_num; // 设备信息描述集
struct cdev cdev;// 类描述集
struct class *cls;// 设备描述集
struct device*device;// 传递单个参数
module_param(major_num,int,S_IRUGO); // 可读
module_param(minor_num,int,S_IRUGO); // 可读// 文件操作集
struct file_operations chrdev_opr = {.owner = THIS_MODULE,.open = chrdev_open
};// 驱动的入口函数
static int __init dev_init(void)
{int ret;/* 步骤一:申请设备号 */// 有传递主设备号就静态申请if(major_num){ dev_num = MKDEV(major_num, minor_num);//将主设备号 次设备号合并成设备号//参数分别表示 设备号 设备数量 设备名称ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME); if(ret < 0){printk("dev_init error\r\n");return -1;}} // 否则就动态申请else{// 参数:设备号 次设备号起始地址 设备数量 设备名称ret = alloc_chrdev_region(&dev_num , DEVICE_MINOR_NUM, DEVICE_NUMBER, DEVICE_ANAME);if(ret < 0){printk("dev_init error\r\n");return -1;}// 获取主设备号 次设备号major_num = MAJOR(dev_num);minor_num = MINOR(dev_num);}printk("---------------------------------------\r\n");printk("dev_num:%d major_num:%d minor_num:%d\r\n",dev_num,major_num,minor_num);/* 步骤二:初始化设备 */// 初始化cdevcdev.owner = THIS_MODULE;// 初始化设备 cdev_init(&cdev, &chrdev_opr);/* 步骤三:注册设备到内核 *///添加(注册)到内核 参数: 设备 设备号 设备数量cdev_add(&cdev, dev_num, DEVICE_NUMBER);/* 步骤四:先创建类再自动创建添加设备名称*/// 创建类 参数1: 类的归属 参数2: 类名cls = class_create(THIS_MODULE,DEVICE_CLASS_NAME);// 创建设备 参数1: 归属到类 参数2:设备的父设备 参数3:设备号 参数4:添加到设备的回调数据 参数5:设备名device = device_create(cls,NULL,dev_num,NULL,DEVICE_MYNAME);printk("auto add device name\r\n");return 0;
}//驱动的出口函数
static void __exit dev_exit(void)
{/* 步骤一:注销设备号 参数:设备号 设备数量 */ unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);/* 步骤二:删除设备操作集 */cdev_del(&cdev);/* 步骤三:删除设备*/device_destroy(cls,dev_num);/* 步骤四: 删除类*/class_destroy( cls);printk(KERN_EMERG"dev_exit is successful\r\n");
}module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhouxianjie0716@qq.com");
使用加载驱动到内核insmod file.ko命令,得结果

在测试驱动前需要使用mknod /dev/mytest c 236 0创建设备文件,命令格式为:mknod + /路径/设备名称 +设备类型+主设备号 + 次设备号。
然后就可写一个应用程序来测试我们的字符驱动是否成功注册,测试源码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main(int argc,char* argv[])
{char name[] = "/dev/mytest";int fd = open(name,O_RDONLY); // 仅读if(fd < 0){printf("open is failed\n");return -1;}close(fd);return 0;
}
执行测试程序后结果如下:

注意:在运行测试程序时,一定要先创建驱动文件,否则就会报段错误,具体如下:

杂项设备与字符设备的比较:
- 杂项设备的主设备号固定为10,而字符设备的主设备号需要创建。
- 杂项设备的创建相对简单,只需要填充设备操作集结构体、杂项设备结构体再注册即可。而字符设备需要经过 申请设备号、初始化并且注册cdev结构体、初始化硬件、构建设备文件操作描述集、生成并且添加设备节点。
- 杂项设备与字符设备都需要构建文件操作描述集。(应用层调用驱动的核心)
相关文章:
【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程
文章目录 注册杂项设备驱动模块传参注册字符设备 开发环境: windows ubuntu18.04 迅为rk3568开发板 注册杂项设备 相较于字符设备,杂项设备有以下两个优点: 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时&…...
Golang条件编译 | 获取系统的磁盘空间内存占用demo | gopsutil/disk库(跨平台方案)
文章目录 一、Golang条件编译1. 构建标签( Build tags)2. 文件后缀(File suffixes) 二、GO golang 获取磁盘空间 条件编译思路 三、【推荐】使用github.com/shirou/gopsutil/disk这个库,如何获取机器下不同磁盘分区的内容 一、Golang条件编译…...
22/76-池化
池化(最大池化层:选每个kernel中最大的数) 填充、步幅、多个通道: 池化层与卷积层类似,都具有填充和步幅。 没有可学习的参数。 在每个输入通道应用池化层以获得相应的输出通道。 输出通道数输入通道数。 平均池化层…...
江科大STM32 下
目录 ADC数模转换器DMA直接存储器存取USART串口9-2 串口发送接受9-3 串口收发HEX数据包 I2C(mpu6050陀螺仪和加速度计)SPI协议10.1 SPI简介W25Q64简介10.3 SPI软件读写W25Q6410.4 SPI硬件读写W25Q64 BKP、RTC11.0 Unix时间戳11.1 读写备份寄存器BKP11.2 RTC实时时钟 十二、PWR1…...
利用HTML和CSS实现的浮动布局
代码如下 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>*{m…...
2024年第十届控制、自动化与机器人国际会议(ICCAR 2024)即将召开!
2024年4月27~29日 新加披 会议官网:10th-ICCAR 2024https://iccar.org/index.html 第十届控制、自动化和机器人国际会议将于2024年4月27-29日在新加坡举办。本次会议由新加坡电子学会,IEEE机器人和自动控制协会和IEEE联合主办,并得到北京航空…...
基于python集成学习算法XGBoost农业数据可视化分析预测系统
文章目录 基于python集成学习算法XGBoost农业数据可视化分析预测系统一、项目简介二、开发环境三、项目技术四、功能结构五、功能实现模型构建封装类用于网格调参训练模型系统可视化数据请求接口模型评分 0.5*mse 六、系统实现七、总结 基于python集成学习算法XGBoost农业数据可…...
第29集《佛法修学概要》
丁三、声闻乘 分二:戊一、释义;戊二、四谛法;戊三、结示 请大家打开讲义第八十二页。我们看丁三,声闻乘。 在祖师大德的判教当中,把我们整个大乘的成佛之道分成了三个部分:第一个是安乐道,第…...
奥伦德光电耦合器5G通信领域及其相关领域推荐
光电耦合器是以光为媒介传输电信号的一种电-光-电转换器件。由于该器件使用寿命长、工作温度范围宽,所以在过程控制、工业通信、家用电器、医疗设备、通信设备、计算机以及精密仪器等方面有着广泛应用在当前工艺技术持续发展与提升的过程中,其工作速度、…...
机器学习算法 - 马尔可夫链
马尔可夫链(Markov Chain)可以说是机器学习和人工智能的基石,在强化学习、自然语言处理、金融领域、天气预测、语音识别方面都有着极其广泛的应用 > The future is independent of the past given the present 未来独立于过去ÿ…...
Linux下防火墙相关命令整理
目录 一.前言二.相关命令整理 一.前言 这篇文章简单整理一下Linux系统中防火墙相关命令。 二.相关命令整理 开启防火墙 systemctl start firewalld关闭防火墙 systemctl stop firewalld重启防火墙 systemctl restart firewalld开机启用防火墙 systemctl enable firewall…...
Python八股文总结
一. Python基本数据结构有哪四种?区别是什么? 列表(List)元组(Tuple)字典(Dictionary)集合(Set) 区别主要在于它们的可变性(是否可以修改&#x…...
计算机导论05-计算机网络
文章目录 计算机网络基础计算机网络概述计算机网络的概念计算机网络的功能计算机网络的组成 计算机网络的发展计算机网络的类型 网络体系结构网络互联模型OSI/RM结构与功能TCP/IP结构模型TCP/IP与OSI/RM的比较 网络地址与分配IP地址构成子网的划分IPv6 传输介质与网络设备网络传…...
sentinel熔断与限流
文章目录 一、sentinel简介Sentinel 是什么?Sentinel安装 二、sentinel整合工程新建cloudalibaba-sentinel-service8401微服务引入依赖yml配置主启动类添加EnableDiscoveryClient业务类测试 三、sentinel流控规则基本介绍流控模式直接(默认)关…...
vi/vim 编辑器 --基本命令
1 vi/vim编辑器介绍 vi 是visual interface 的简称,是Linux中最经典的文本编辑器 vim是vi的加强版。兼容了vi的所有指令,不仅能编辑文本,而且具有shell程序编辑的功能,可以通过不同颜色的字体辨别语法的正确性,极大…...
C++——STL标准模板库——容器详解——set
一、基本概念 set容器是一种具备自动排序功能的集合,默认递增排序;元素无法直接修改,且不能重复;另一个版本叫做multiset,允许存在重复元素,其他功能和性质一样。 set容器底层结构一般为自平衡二叉搜索树…...
Vim一键配置指南,打造高效率C++开发环境
文章目录 前言安装与卸载功能演示gcc/g升级问题 前言 Vim作为当下最受欢迎的文本编译器之一,不仅具有强大的文本编辑功能,还提供了高度的可定制性。用户可以根据自己的喜好自定义配置,并且通过自己编写插件或者使用现有的插件来扩展Vim的功能…...
新航向,新生态: Michael在出海业务圆桌会议分享HyperBDR全球业务拓展之道
1月15日-16日,以“领航新开局,共赢新生态”为主题的华为云生态大会2024在华为云贵安数据中心云上屯盛大举行。本次会议聚焦于华为云全国生态伙伴与开发者,旨在共同见证华为云生态战略的最新进展和伙伴政策的新升级。与会者将分享来自优秀生态…...
SpringBoot异步处理
Spring boot异步处理 业务场景: 如执行数据库备份任务,前端发起请求到后端,后端备份数据库的处理逻辑需要很长一段时间,此时前端会一直等待后端返回结果,给用户给等待时间过长,这是就要考虑异步处理了&…...
2024年甘肃省职业院校技能大赛信息安全管理与评估 样题一 模块二
竞赛需要完成三个阶段的任务,分别完成三个模块,总分共计 1000分。三个模块内容和分值分别是: 1.第一阶段:模块一 网络平台搭建与设备安全防护(180 分钟,300 分)。 2.第二阶段:模块二…...
OpenClaw多模型路由策略:百川2-13B与CodeLlama任务分配逻辑
OpenClaw多模型路由策略:百川2-13B与CodeLlama任务分配逻辑 1. 为什么需要多模型路由? 去年我在搭建个人AI助手时遇到一个典型问题:当我把所有任务都交给同一个大模型处理时,发现代码生成任务的质量总是不尽如人意。后来通过日志…...
VINS-Mono跑EUROC数据集后,如何用evo工具包进行轨迹精度评估与可视化(附完整命令)
VINS-Mono轨迹精度评估实战:从EUROC数据集到evo工具包全流程解析 在完成VINS-Mono算法在EUROC数据集上的运行后,如何科学评估其轨迹精度成为算法优化和论文撰写的关键环节。本文将深入讲解使用evo工具包进行定量分析的完整流程,涵盖指标计算、…...
保姆级教程:用300条数据微调SenseVoice语音模型(附数据格式详解)
300条数据高效微调SenseVoice语音模型的实战指南 去年在为一个医疗咨询项目定制语音识别系统时,我发现通用模型对专业医学术语的识别准确率不足60%。当时团队仅有400条标注数据,却通过SenseVoice的微调功能在3小时内将准确率提升至89%。本文将分享这种小…...
QMK Toolbox终极指南:轻松掌握机械键盘固件部署与定制
QMK Toolbox终极指南:轻松掌握机械键盘固件部署与定制 【免费下载链接】qmk_toolbox A Toolbox companion for QMK Firmware 项目地址: https://gitcode.com/gh_mirrors/qm/qmk_toolbox QMK Toolbox是一款功能强大的开源键盘固件部署工具,专为QMK…...
Swin Transformer生产部署与性能调优:从环境适配到架构优化的全周期解决方案
Swin Transformer生产部署与性能调优:从环境适配到架构优化的全周期解决方案 【免费下载链接】Swin-Transformer This is an official implementation for "Swin Transformer: Hierarchical Vision Transformer using Shifted Windows". 项目地址: http…...
gitru:一个由 Rust 打造的零依赖 Git 提交信息校验工具
gitru 基于 Git 的 commit-msg Hook 实现,用于在提交阶段自动校验提交信息格式。 在团队协作开发中,规范的 Git 提交信息是代码追溯、版本管理、自动生成变更日志的基础。 但现实往往是: 人工约束容易遗漏手动配置 Hook 繁琐提交信息格式随心…...
FlowState Lab创意作品展:从音乐旋律到光影变化的波动艺术
FlowState Lab创意作品展:从音乐旋律到光影变化的波动艺术 1. 波动艺术的新维度 当数据不再只是冰冷的数字,而是化作跳动的音符、流动的光影和变幻的图形,这就是FlowState Lab带来的创意革命。我们最近完成了一系列跨媒介艺术实验ÿ…...
M2LOrder模型Mathtype公式编辑器的趣味扩展:为数学证明添加情感注释
M2LOrder模型Mathtype公式编辑器的趣味扩展:为数学证明添加情感注释 你有没有过这样的经历?面对一篇复杂的数学论文或教材,读到某个证明步骤时,心里忍不住嘀咕:“这一步也太巧妙了,怎么想到的?…...
Whisper-large-v3开源大模型部署教程:无需Docker,纯Python一键启动方案
Whisper-large-v3开源大模型部署教程:无需Docker,纯Python一键启动方案 本文由113小贝基于Whisper-large-v3语音识别模型二次开发构建 1. 项目概述 今天要给大家介绍一个超级实用的语音识别工具——基于OpenAI Whisper Large v3的多语言语音识别Web服务…...
lychee-rerank-mm快速上手:3步完成图库重排序(输入描述→上传图片→点击排序)
lychee-rerank-mm快速上手:3步完成图库重排序(输入描述→上传图片→点击排序) 1. 项目简介 lychee-rerank-mm是一个专门为RTX 4090显卡优化的智能图片排序工具。它能帮你从一堆图片中快速找出与文字描述最匹配的那些图片,就像有…...
