极简字符设备驱动开发:控制继电器、DI/DO 数字输入输出

发布时间:2026/7/25 2:52:44

极简字符设备驱动开发:控制继电器、DI/DO 数字输入输出
极简字符设备驱动开发控制继电器、DI/DO 数字输入输出驱动开发不是玄学今天我们就把点亮一个继电器这件事从内核层到用户层扒个底朝天。一、Linux 驱动模型简介Linux 设备驱动分三大类搞不清楚这个分类后面学起来就容易串台设备类型特点典型例子字符设备按字节流访问顺序读写串口、GPIO、继电器块设备按块扇区访问可随机读写硬盘、U盘、SD卡网络设备面向数据包收发无设备节点网卡、WiFi工控场景中继电器控制、DI/DO 信号采集本质上都是操作 GPIO 电平属于典型的字符设备。二、字符设备驱动的核心结构写字符设备驱动绕不开三个核心2.1 file_operations —— 操作集合这是驱动和用户空间之间的接口合同用户层的open/read/write/close最终都映射到这个结构体里的函数指针structfile_operations{int(*open)(structinode*,structfile*);ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);int(*release)(structinode*,structfile*);};2.2 cdev —— 字符设备描述符struct cdev是内核内部表示字符设备的结构体需要初始化后添加到内核才能被使用。2.3 设备号分配每个设备有一个主设备号标识驱动和次设备号标识同类设备中的具体实例。分配方式有两种静态分配register_chrdev_region()你需要提前知道哪个号没被占用动态分配alloc_chrdev_region()内核自动给你分推荐用这个三、极简驱动开发流程整个开发流程可以浓缩为一句话模块初始化 → 注册设备 → 实现 fops → 模块卸载。insmod加载 → module_init() → 申请设备号 → 初始化cdev → 添加cdev → 创建设备节点 ↓ 用户程序 → open() → read()/write() → close() ↓ rmmod卸载 → module_exit() → 删除cdev → 释放设备号 → 删除设备节点四、实战GPIO 控制继电器驱动下面给一个完整的、可编译运行的极简继电器控制驱动。继电器接在 GPIO4 上高电平吸合。#includelinux/module.h#includelinux/fs.h#includelinux/cdev.h#includelinux/device.h#includelinux/gpio.h#includelinux/uaccess.h#defineRELAY_GPIO4#defineDEVICE_NAMErelay_ctrl#defineBUF_SIZE8staticdev_tdev_num;staticstructcdevrelay_cdev;staticstructclass*relay_class;staticstructdevice*relay_device;staticcharrelay_state0;// 0断开 1吸合staticintrelay_open(structinode*inode,structfile*filp){pr_info([relay] device opened\n);return0;}staticssize_trelay_read(structfile*filp,char__user*buf,size_tcount,loff_t*off){if(countBUF_SIZE)countBUF_SIZE;if(copy_to_user(buf,relay_state,1))return-EFAULT;return1;}staticssize_trelay_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*off){charkbuf[BUF_SIZE]{0};if(countBUF_SIZE)countBUF_SIZE;if(copy_from_user(kbuf,buf,count))return-EFAULT;if(kbuf[0]1){gpio_set_value(RELAY_GPIO,1);relay_state1;pr_info([relay] switched ON\n);}elseif(kbuf[0]0){gpio_set_value(RELAY_GPIO,0);relay_state0;pr_info([relay] switched OFF\n);}returncount;}staticintrelay_release(structinode*inode,structfile*filp){pr_info([relay] device closed\n);return0;}staticconststructfile_operationsrelay_fops{.ownerTHIS_MODULE,.openrelay_open,.readrelay_read,.writerelay_write,.releaserelay_release,};staticint__initrelay_init(void){intret;/* 1. 申请 GPIO */if(!gpio_is_valid(RELAY_GPIO)){pr_err([relay] invalid GPIO %d\n,RELAY_GPIO);return-ENODEV;}retgpio_request(RELAY_GPIO,relay_gpio);if(ret){pr_err([relay] failed to request GPIO\n);returnret;}gpio_direction_output(RELAY_GPIO,0);/* 默认断开 *//* 2. 动态分配设备号 */retalloc_chrdev_region(dev_num,0,1,DEVICE_NAME);if(ret){pr_err([relay] alloc_chrdev_region failed\n);gotoerr_gpio;}/* 3. 初始化并添加 cdev */cdev_init(relay_cdev,relay_fops);relay_cdev.ownerTHIS_MODULE;retcdev_add(relay_cdev,dev_num,1);if(ret){pr_err([relay] cdev_add failed\n);gotoerr_region;}/* 4. 自动创建设备节点 */relay_classclass_create(THIS_MODULE,DEVICE_NAME);relay_devicedevice_create(relay_class,NULL,dev_num,NULL,DEVICE_NAME);pr_info([relay] driver loaded, major%d minor%d\n,MAJOR(dev_num),MINOR(dev_num));return0;err_region:unregister_chrdev_region(dev_num,1);err_gpio:gpio_free(RELAY_GPIO);returnret;}staticvoid__exitrelay_exit(void){gpio_set_value(RELAY_GPIO,0);/* 退出时断开继电器 */device_destroy(relay_class,dev_num);class_destroy(relay_class);cdev_del(relay_cdev);unregister_chrdev_region(dev_num,1);gpio_free(RELAY_GPIO);pr_info([relay] driver unloaded\n);}module_init(relay_init);module_exit(relay_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(黒漂技术佬);MODULE_DESCRIPTION(Simple Relay Control Driver);4.1 关键点解读copy_to_user/copy_from_user内核空间和用户空间不能直接互访必须用这对函数搬运数据这是安全红线class_createdevice_create自动在/dev/下生成设备节点省去手动mknod的麻烦退出时gpio_set_value(RELAY_GPIO, 0)卸载驱动时确保继电器断开防止危险五、实战DI 数字输入与 DO 数字输出DIDigital Input和 DODigital Output本质上也是操作 GPIO只是方向不同/* DI 驱动 - 读取输入电平 */staticssize_tdi_read(structfile*filp,char__user*buf,size_tcount,loff_t*off){charvalgpio_get_value(DI_GPIO)?1:0;if(copy_to_user(buf,val,1))return-EFAULT;return1;}/* DO 驱动 - 设置输出电平 */staticssize_tdo_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*off){charkbuf[4]{0};if(copy_from_user(kbuf,buf,min(count,(size_t)4)))return-EFAULT;gpio_set_value(DO_GPIO,kbuf[0]1?1:0);returncount;}DI 驱动初始化时用gpio_direction_input()DO 驱动用gpio_direction_output()其余框架和继电器驱动一模一样。六、设备节点创建两种方式方式命令适用场景手动 mknodmknod /dev/relay_ctrl c 240 0调试阶段快速验证自动创建class_create()device_create()正式发布推荐手动mknod需要知道主设备号可通过cat /proc/devices查看。七、驱动编译与加载7.1 Makefileobj-m : relay_ctrl.o KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean7.2 编译加载流程# 编译make# 加载驱动insmod relay_ctrl.ko# 查看设备号cat/proc/devices|greprelay# 查看设备节点ls-l/dev/relay_ctrl# 卸载驱动rmmod relay_ctrl如果驱动有依赖关系用modprobe代替insmod它会自动解决依赖。八、用户层测试程序#includestdio.h#includefcntl.h#includeunistd.h#includestring.hintmain(intargc,char*argv[]){intfd;charbuf[8]{0};fdopen(/dev/relay_ctrl,O_RDWR);if(fd0){perror(open failed);return-1;}if(argc1strcmp(argv[1],on)0){write(fd,1,1);printf(Relay ON\n);}elseif(argc1strcmp(argv[1],off)0){write(fd,0,1);printf(Relay OFF\n);}else{read(fd,buf,1);printf(Relay state: %c\n,buf[0]);}close(fd);return0;}编译运行arm-linux-gnueabihf-gcc test_relay.c-otest_relay# 传到板子上执行./test_relay on# 吸合继电器./test_relay off# 断开继电器./test_relay# 读取当前状态九、驱动调试技巧9.1 dmesg —— 查看内核日志驱动里的pr_info/pr_err输出都走内核日志用dmesg查看dmesg|tail-20# 看最后20条dmesg|greprelay# 过滤关键字dmesg-w# 实时跟踪类似 tail -f9.2 /proc/devices —— 查看已注册设备cat/proc/devices输出里能看到你的驱动名和主设备号确认是否注册成功。9.3 lsmod —— 查看已加载模块lsmod|greprelay9.4 常见问题排查现象可能原因解决方案insmod 报错 Invalid module format内核版本不匹配用目标板同版本内核编译/dev/ 下没有设备节点class_create 失败检查 dmesg 日志open 报 No such device设备号没注册成功检查 init 函数返回值GPIO 申请失败GPIO 被其他驱动占用检查设备树 GPIO 占用情况read/write 返回 -1copy_to/from_user 失败检查用户空间缓冲区大小字符设备驱动是 Linux 工控开发的根基把继电器、DI、DO 这套跑通之后后续的 ADC、PWM、I2C 驱动框架都是同一套路——换一套file_operations底层换成对应的子系统 API 而已。

相关新闻

openEuler基于certbot申请内网ssl泛域名免费证书

openEuler基于certbot申请内网ssl泛域名免费证书

2026/7/25 2:52:44

一、安装 Certbot pip3 install certbot certbot-nginx二、申请证书 单域名申请: certbot certonly --manual --preferred-challenges dns -d jk.xxx.cn泛域名申请: certbot certonly --manual --preferred-challenges dns -d *.xxx.cn -d xxx.cn命令详解…

腾讯元宝生成的内容如何导出 借助 AI 导出鸭化解格式错乱、排版偏移难题,横向对比五种导出方式挑选最优途径

腾讯元宝生成的内容如何导出 借助 AI 导出鸭化解格式错乱、排版偏移难题,横向对比五种导出方式挑选最优途径

2026/7/25 2:52:44

引言 腾讯元宝产出的文案、方案、图文排版内容,很多用户在保存归档、对外交付时都面临导出难题,常会出现排版错位、图文分离、格式乱码等状况。普通复制、办公软件转换很难完整保留元宝原本的排版样式,批量处理效率低下。AI导出鸭针对性适配腾…

软考 系统架构设计师历年真题集萃(303)

软考 系统架构设计师历年真题集萃(303)

2026/7/25 2:52:44

接前一篇文章:软考 系统架构设计师历年真题集萃(302) 第608题 某厂生产的某种电视机,销售价为每台2500元,去年的总销售量为25000台,固定成本总额为250万元,可变成本总额为4000万元,税率为16%,则该产品年销售量的盈亏平衡点为( )台(只有在年销售量超过它时才能盈利…

RAG技术在工业自动化智能监盘系统中的应用

RAG技术在工业自动化智能监盘系统中的应用

2026/7/25 3:52:47

1. 项目背景与核心价值在工业自动化领域,设备运行监控一直是保障生产安全的重要环节。传统监盘工作需要操作人员长时间紧盯各种参数曲线,不仅容易疲劳漏检,对新手更是不小的挑战。这个项目创造性地将RAG(检索增强生成)…

联邦学习中的多模态模型微调与FediLoRA技术解析

联邦学习中的多模态模型微调与FediLoRA技术解析

2026/7/25 3:52:47

1. 项目背景与核心挑战在联邦学习框架下进行多模态模型微调时,数据模态缺失是一个普遍存在的现实问题。传统方法通常假设所有参与方都具备完整的模态数据,但在实际医疗、金融等隐私敏感场景中,不同机构采集的数据类型往往存在差异。比如医院A…

计算机毕业设计之基于SpringBoot的流浪动物救助管理

计算机毕业设计之基于SpringBoot的流浪动物救助管理

2026/7/25 3:52:47

当下社会,信息技术充斥社会各个领域,已融入人们生活的点滴,日常中人们管理信息、办理业务、购买商品等都可以网络线上进行,快速而又便利,特别是随着移动互联网时代的到来,更是让人们随时享受着网络给带来的…

基于YOLOv5的智能垃圾分类检测系统开发实践

基于YOLOv5的智能垃圾分类检测系统开发实践

2026/7/25 3:52:47

1. 项目背景与核心价值垃圾识别检测系统是当前计算机视觉在环保领域的重要应用方向。传统垃圾分类主要依赖人工分拣,效率低且成本高。我们团队基于YOLOv5框架开发的这套系统,能够实现垃圾的实时检测与分类,准确率可达92%以上。这个项目特别适…

脑虎科技柔性脑机接口获绿色通道:硬膜下植入技术突破与应用前景

脑虎科技柔性脑机接口获绿色通道:硬膜下植入技术突破与应用前景

2026/7/25 3:52:47

这次我们来看一个在脑机接口领域具有重要意义的进展——脑虎科技的硬膜下植入式柔性脑机接口获得"绿色通道"审批。这个项目标志着我国在高端医疗器械创新方面迈出了关键一步,对于推动脑机接口技术从实验室走向临床应用具有里程碑意义。脑虎科技这次获批的…

AI模型计算预算评估:从性能到安全的多维度实践指南

AI模型计算预算评估:从性能到安全的多维度实践指南

2026/7/25 3:42:47

在AI模型开发和部署的实践中,我们常常面临一个看似简单却影响深远的问题:如何准确评估不同计算资源配置对模型性能和安全性的影响?这个问题不仅关系到项目预算的分配效率,更直接决定了AI系统能否在实际应用中稳定运行。最近在评估…

微服务进阶:服务网格与Istio

微服务进阶:服务网格与Istio

2026/7/24 4:17:29

541|微服务进阶:服务网格与Istio 上篇文章我们聊了微服务的基本概念和拆分方法。 但微服务多了,问题也多了: 服务之间怎么通信? 怎么监控每个服务的调用链路? 熔断、限流、重试怎么做? 安全认证怎么统一? 以前这些都靠SDK库(比如Hystrix、Feign),每个服务都要集成…

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

2026/7/24 19:29:25

一、零售门店全域协同业务背景与行业痛点 1.1 门店超级终端设备矩阵(连锁便利店/商超标准配置) 自助收银Kiosk一体机:顾客结算、自助核销优惠券、商品素材预览;运营折叠平板:店长后台商品上新、图片录入、活动配置、…

噗叽短视频界面分析

噗叽短视频界面分析

2026/7/23 1:54:13

1 和小红书类似,可以采用类似判断方法------------其实他比小红书好判断,因为他没有图片,控件位置几乎是固定的,都不用判断------------2 因为他没有点赞按钮------------而且几乎所有控件位置都是完全一样的,所以我就…

挑战一天速通Spring全家桶!

挑战一天速通Spring全家桶!

2026/7/25 0:02:22

不知道各位Java好大哥们闲的时候会不会去关注Spring目前的官网,你会发现他的slogan是: Spring makes Java Simple。它让Java的开发变得更加简单。某种意义上来说:是Spring成就了Java!但随之而来的就是:由他之后诞生出来的各种组件…

挑战一天速通Java高并发!

挑战一天速通Java高并发!

2026/7/25 0:02:22

有出去面试的朋友肯定深有感受,像我们刚入行那会面试的加分项现在卷得已经成为了面试的基础题(手动狗头)。其中最典型的就属这个Java并发编程了。之前一般只有大厂才会有高并发编程相关的面试内容,但现在只要你入了Java行业就会涉…

从暴雪到米哈游都在用的平衡性评估框架,深度拆解LSTM+胜率归因分析法(附开源工具链)

从暴雪到米哈游都在用的平衡性评估框架,深度拆解LSTM+胜率归因分析法(附开源工具链)

2026/7/25 0:02:22

更多请点击: https://kaifayun.com 第一章:AI 游戏平衡性分析 现代游戏开发中,AI 不再仅用于控制 NPC 行为,更被深度整合进游戏平衡性调优流程。通过强化学习与对抗性仿真,AI 可以在数百万局对局中自动识别数值失衡点…