linux驱动篇-button-int-poll
本篇是linux下按键设备驱动,采用的中断法和poll机制,也是属于字符设备类的驱动,一起来动手吧。下面的话,老朋友可以跳过了直接从《需求描述》章节看起,新朋友可以试着看看。
前言
前言主要介绍了中年润写文章的目的,新朋友可以参考中年润其它文章来了解中年润的初衷。另外,之后的文章会大量借助流程图来表达中心思想,比较细节的步骤请大家参考中年润以前写过的文章。
最近中美贸易战正酣,中年润也想尽自己的绵薄之力。同时希望能有更多的嵌入式工作者踏实下来,努力钻研相关技术。为中国的强大出一份力。
总体目标
本篇文章的目标是介绍如何从自顶向下从零编写linux下的按键驱动。着力从总体思路,需求端,分析端,实现端,详尽描述一个完整需求的开发流程。
本示例采用arm920架构,天祥电子生产的tx2440a开发板,核心为三星的s3c2440。Linux版本为2.6.31,是已经移植好的版本。编译器为arm920t-eabi-4.1.2.tar。
总体思路
总体思路是严格遵循需求的开发流程来,不遗漏任何思考环节。读者在阅读时请先跟中年润的思路走一遍,然后再抛弃中年润的思路,按照自己的思路走一遍,如果遇到困难请先自己思考,实在不会再来参考中年润的思路和实现。
中年润在写代码的的总体思路如下:
需求描述—能够详细完整的描述一个需求。
需求分析—根据需求描述,提取可供实现的功能,需要有定量或者定性的指标。
需求分解—根据需求分析,考虑要实现需求所需要做的工作。
编写思路—根据需求分解从总体上描述应该如何编写代码。
详细步骤—根据编写思路,落实具体步骤。
编写框架—根据编写思路,实现总体框架。
具体代码—根据编写框架,编写每一个函数里所需要实现的小功能,主要是实现驱动代码,测试代码。
Makefile—用来编译驱动代码。
目录结构—用来说明当完成编码后的结果。
测试步骤—说明如何对驱动进行测试,主要是加载驱动模块,执行测试代码。
执行结果—观察执行结果是否符合预期。
结果总结—回顾本节的思路,知识点,api,结构体。
实战目标—说明如何根据本文档训练。
需求描述
编写按键驱动,要求使用中断法和poll机制。
需求分析
一张图概括用户进程和中断的交互流程。
用户的工作流程如下:
1 用户进程调用open系统调用打开/dev/mybutton设备节点,获取fd
2 用户进程拿到fd之后,调用poll系统调用检查fd的状态
2.1 如果超时则返回0
2.2 如果出错返回-1
2.3 如果有数据返回一个大于0的值
2.4 有数据则调用read系统调用读取fd中的值
3 如果按下或松开按键,在中断中保存当前的数据,并标记已有数据
4 用户进程直接调用read调用读取数据
我们需要做什么呢?针对用户的操作,驱动需要提供节点和以下几个函数。
需求分解
总体思路:
中断中保存数据,read能够返回数据,poll函数能够根据当前数据状态返回是否可读或者可写。
根据用户的每一个动作可以分析出驱动所应该做的动作
1 用户进程调用open系统调用打开/dev/mybutton设备节点,获取fd
1.1 需要提供一个设备节点/dv/mybutton,需要提供一个操作设备节点的open函数
2 用户进程拿到fd之后,调用poll系统调用检查fd的状态
2.1 如果超时则返回0,由poll系统调用+驱动保证
驱动poll需要返回0,表示当前不可读
2.2 如果出错返回-1,由poll系统调用保证
2.3 如果有数据返回一个大于0的值,由poll系统调用+驱动保证
2.3.1 驱动poll需要返回当前状态可以读或者写
2.4 有数据则调用read系统调用读取fd中的值
2.4.1 需要提供操作设备节点的read函数,用来将读取的数据返回
2.5 驱动poll关键点
驱动poll需要将当前进程加入等待队列头,给系统调用poll使用
3 如果按下或松开按键,在中断中保存当前的数据,并标记已有数据
3.1 需要一个中断处理函数
3.2 需要能够检测当前按下的是哪个按键
3.3 需要在中断中保存数据,并标记当前数据状态为有
4 用户进程直接调用read调用读取数据
4.1 需要提供一个read函数
4.2 read函数能够直接操作被保存的数据并返回给用户
最终需求就分解为
1注册字符设备,以及代表字符设备的节点
2编写open函数
3编写read函数
4编写中断处理函数
5编写poll函数
用一张图表示我们需要做的工作。
编写思路
1 入口函数
1.1 注册字符设备
1.2 创建一个类
1.3 在这个类下创建一个设备
2 出口函数
2.1 卸载字符设备
2.2 卸载class下的设备
2.3 销毁这个类
3 构造file_operations结构体
3.1 实现button_open函数
根据4个引脚的中断号,注册4个外部中断(用来感知不同的引脚),并实现中断处理函数
3.2 中断处理函数
1 获取当前引脚的电平值
2 根据电平值判断当前是按下还是松开
松开为高电平,返回0x8x
按下为低电平,返回0x0x
3 标记中断已经触发
3.2 实现button_read函数
将在中断中保存的数据返回给用户,并将标志位清零,表示已无数据
3.3 实现button_release函数
释放注册的4个中断
3.4 实现poll函数
如果无数据,直接返回0
如果有数据,则返回可读的标志位
详细步骤
略
编写框架
略
驱动代码
因代码比较简单,我们直接通过编写思路来编写驱动代码。
/* 本代码根据中年润文章<Button-int-poll>编写思路编写 */
#include <linux/module.h>
#include <linux/ioport.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/init.h>
#include <linux/serial_core.h>
#include <linux/serial.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-fns.h>
#include <plat/regs-serial.h>
#include <linux/poll.h>/* 需求分析 --- 需求分解 --- 编写框架 --- 编写代码 *//* 编写代码 */volatile unsigned long * gpfconf;
volatile unsigned long * gpfdat;
static struct class *my_button_cls;
static struct device * my_button_dev;
DECLARE_WAIT_QUEUE_HEAD(button_waitq);//引脚描述符
struct pin_desc{int pin;int key_val;
};//引脚描述符,按引脚的不同定义不同的值,在中断中进行处理
struct pin_desc pin_desc[4] =
{{S3C2410_GPF4_EINT4,0x1},{S3C2410_GPF5_EINT5,0x2}, {S3C2410_GPF6_EINT6,0x3},{S3C2410_GPF7_EINT7,0x4},
};//标志是否进入中断
static int do_press_loos;/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
/* 主要是为了区分是按下还是松开 */
static unsigned char key_val = 0;
/* 3.2 中断处理函数 */
irqreturn_t button_irq(int irq, void * devid)
{int pin_val;struct pin_desc * pin_readed;pin_readed = (struct pin_desc *)devid;/* 1 获取当前引脚的电平值 */pin_val = s3c2410_gpio_getpin(pin_readed->pin);/* 2 根据电平值判断当前是按下还是松开 */ if (pin_val) /* 松开为高电平,返回0x8x */{key_val = 0x80 | pin_readed->key_val;}else /* 按下为低电平,返回0x0x */{//把当前按键的键值给一个全局静态变量,在read函数里给用户key_val = pin_readed->key_val;}/* 3 标记中断已经触发 */do_press_loos = 1;return IRQ_RETVAL(IRQ_HANDLED);
}/* 3.1 实现button_open函数 */
static int button_open (struct inode * inode, struct file * filep)
{ /* 根据4个引脚的中断号,注册4个外部中断(用来感知不同的引脚),并实现中断处理函数 *///request_irq里已经帮忙将GPF4-GPF7设置成中断引脚了,另外这里只检测下降沿int ret;ret = request_irq(IRQ_EINT4,button_irq,IRQ_TYPE_EDGE_FALLING,"S1",(void *)&pin_desc[0]);if (ret)goto errout;ret = request_irq(IRQ_EINT5,button_irq,IRQ_TYPE_EDGE_FALLING,"S2",(void *)&pin_desc[1]);if (ret)goto errout;ret = request_irq(IRQ_EINT6,button_irq,IRQ_TYPE_EDGE_FALLING,"S3",(void *)&pin_desc[2]);if (ret)goto errout;ret = request_irq(IRQ_EINT7,button_irq,IRQ_TYPE_EDGE_FALLING,"S4",(void *)&pin_desc[3]);if (ret)goto errout;return 0;
errout:return ret;
}/* 3.2 实现button_read函数 */
static ssize_t button_read (struct file * filep, char __user * buff, size_t size, loff_t * pos)
{ /* 将在中断中保存的数据返回给用户,并将标志位清零,表示已无数据 */int ret;if (size != 1)return -EINVAL;if (key_val == 0)return -EINVAL;//拷贝数据到用户空间ret = copy_to_user(buff, &key_val, sizeof(key_val));if (ret) {return -EFAULT;}//读取数据完毕后需要将标志位清零,表示暂时无数据可读do_press_loos = 0;key_val = 0;return 1;
}
static ssize_t button_write (struct file * filep, const char __user * buff, size_t size, loff_t * pos)
{return 0;
}
/* 3.3 实现button_release函数 */
static int button_release (struct inode * inode, struct file * filep)
{/* 释放注册的4个中断 */free_irq(IRQ_EINT4, &pin_desc[0]);free_irq(IRQ_EINT5, &pin_desc[1]);free_irq(IRQ_EINT6, &pin_desc[2]);free_irq(IRQ_EINT7, &pin_desc[3]);return 0;
}
/* 3.4 实现poll函数 */
unsigned int button_poll (struct file * filep, struct poll_table_struct * wait)
{/* 如果无数据,直接返回0 */unsigned int mask = 0;//将当前进程挂入等待队列头poll_wait(filep,&button_waitq,wait);/* 如果有数据,则返回可读的标志位 */if (do_press_loos) {mask |= POLLIN | POLLRDNORM;}return mask;
}/* 3 构造file_operations结构体 */
static struct file_operations button_fops = {.owner = THIS_MODULE,.open = button_open,.read = button_read,.write = button_write,.release = button_release,.poll = button_poll,};static int major;
/* 1 入口函数 */
static int my_button_init(void)
{int ret = 0;/* 1.1 注册字符设备 */major = register_chrdev(0,"button-poll",&button_fops);/* 1.2 创建一个类 */ my_button_cls = class_create(THIS_MODULE,"button-poll");/* 1.3 在这个类下创建一个设备 */ my_button_dev = device_create(my_button_cls,NULL,MKDEV(major,0),NULL,"mybutton");return ret;
}
/* 2 出口函数 */
static void my_button_exit(void)
{/*2.1 卸载字符设备*/unregister_chrdev(major,"button-poll");/* 2.2 卸载class下的设备 */device_unregister(my_button_dev);/* 2.3 销毁这个类 */class_destroy(my_button_cls);return;
}
/* 修饰 */
module_init(my_button_init);
module_exit(my_button_exit);
MODULE_LICENSE("GPL");
测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>int main(int argc, char **argv)
{int fd,ret;unsigned char key_val;struct pollfd fds[1];fd = open("/dev/mybutton", O_RDWR);if (fd < 0) {printf("can't open!\n");}fds[0].fd = fd;fds[0].events = POLLIN;while (1){ret = poll(fds, 1, 100);if (ret == 0) {printf("time out\n");} else if (ret > 0) {read(fd, &key_val, 1);printf("key_val = 0x%x\n", key_val);} else {printf("somthing error,errno is %d\n",ret);}}return 0;
}
Makefile
KERN_DIR = /home/linux/tools/linux-2.6.31all:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-m += button_drv_int_poll.o
目录结构
linux@dell_chi:~/nfs_root/5-button-poll$ ls
button (编译好的测试可执行文件)
button_drv_int_poll.c (驱动代码)
button_test_poll.c (测试代码)
Makefile
测试步骤
0 在linux下的makefile +180行处配置好arch为arm,cross_compile为arm-linux-(或者arm-angstrom-linux-gnueabi-)
1 在menuconfig中配置好内核源码的目标系统为s3c2440
2 在pc上将驱动程序编译生成.ko,命令:make
3 在pc上将测试程序编译生成elf可执行文件,生成的button就是我们所要使用的命令。
编译:arm-angstrom-linux-gnueabi-gcc button_test_poll.c -o button
4 挂载nfs,这样就可以在开发板上看到pc端的.ko文件和测试文件
mount -t nfs -o nolock,vers=2 192.168.0.105:/home/linux/nfs_root /mnt/nfs
5 insmod button_drv.ko,加载按键的驱动模块
6执行命令./button,依次按下四个按键,观察串口的打印信息。
执行结果
[root@TX2440A 5-button-poll]# insmod button_drv_int_poll.ko
[root@TX2440A 5-button-poll]# ./button
time out
time out
key_val = 0x4
time out
key_val = 0x4
time out
time out
key_val = 0x3
time out
key_val = 0x3
key_val = 0x2
time out
key_val = 0x2
time out
time out
key_val = 0x1
key_val = 0x1
结果总结
在本篇文章中,中年润跟读者分享了按键字符设备驱动的编写思路和方法,其中贯穿始终的有几个函数和关键数据结构,它们分别是:
struct file_operations
struct class
struct class_device
register_chrdev
class_create
device_create
unregister_chrdev
device_destroy
class_destroy
ioremap
iounmap
DECLARE_WAIT_QUEUE_HEAD
request_irq / free_irq
poll_wait
请读者尽力去了解这些函数的作用,入参,返回值。
问题总结
Poll机制不用在中断处理函数里加wake_up_interruptible,也不用在read函数里加wait_event_interruptible。因为使用poll机制的进程不是靠我们加在中断函数里的唤醒函数里唤醒的。有想要了解详细机制的小伙伴可以参考我的另一篇文章《api分析篇-poll》。
实战目标
1请读者根据《需求描述》章节,独立编写需求分析和需求分解。
2请读者根据需求分析和需求分解,独立编写编写思路和详细步骤。
3请读者根据编写思路,独立写出编写框架。
4请读者根据详细步骤,独立编写驱动代码和测试代码。
5请读者根据《Makefile》章节,独立编写Makefile。
6请读者根据《测试步骤》章节,独立进行测试。
7请读者抛开上述练习,自顶向下从零开始再编写一遍驱动代码,测试代码,makefile
8如果无法独立写出7,请重复练习1-6,直到能独立写出7。
参考资料
《linux设备驱动开发祥解》
《TX2440开发手册及代码》
《韦东山嵌入式教程》
《鱼树驱动笔记》
《s3c2440a》芯片手册英文版和中文版
致谢
感谢在嵌入式领域深耕多年的前辈,感谢中年润的家人,感谢读者。没有前辈们的开拓,我辈也不能站在巨人的肩膀上看世界;没有家人的鼎力支持,我们也不能集中精力完成自己的工作;没有读者的关注和支持,我们也没有充足的动力来编写和完善文章。看完中年润的文章,希望读者学到的不仅仅是如何编写代码,更进一步能够学到一种思路和一种方法。
为了省去驱动开发者搜集各种资料来写驱动,中年润后续有计划按照本模板编写linux下的常见驱动,敬请读者关注。
联系方式
微信群:自顶向下学嵌入式(可先加微信号:runzhiqingqing, 通过后会邀请入群。)
微信订阅号:自顶向下学嵌入式 公众号:EmbeddedAIOT
CSDN博客:中年润 网址:https://blog.csdn.net/chichi123137
邮箱:834759803@qq.com
QQ群:766756075
更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者点击下面二维码购买。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎选购哦。
公众号二维码如下图
入群小助手二维码如下图
中年润代理销售的韦东山视频购买地址如下图
如果略有所获,欢迎赞赏,您的支持对中年润无比重要
linux驱动篇-button-int-poll相关推荐
- linux驱动篇之 driver_register 过程分析(二)bus_add_driver
linux驱动篇之 driver_register 过程分析(二) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...
- Linux驱动篇之内核模块
Linux驱动篇之内核模块 1.基本概念 模块与驱动: Linux中,将设备分为三种基本的类型. 字符设备 块设备 网络接口 Linux中还有一个很重要的概念,模块.可在运行时添加到内核中的代码被称为 ...
- 嵌入式学习之Linux驱动篇-迅为视频更新了
想学习Linux驱动但是无从下手的同学,学习Linux驱动但是一直不能入门的同学,学习了很多视频和资料还是很懵的同学快来学习拉 https://www.bilibili.com/video/BV1Vy ...
- linux驱动篇之 driver_register 过程分析(一)
linux驱动注册过程分析--driver_register(一) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...
- linux驱动篇-Button-中断法
本篇是linux下按键设备驱动,采用的中断法,也是属于字符设备类的驱动,一起来动手吧.下面的话,老朋友可以跳过了直接从<需求描述>章节看起,新朋友可以试着看看. 前言 在嵌入式行业,有很多 ...
- linux驱动篇-LCD
前言 在嵌入式行业,有很多从业者.我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题.或者是维护一个模块或方向,一搞就是好几年. 时间长了,中年润发现我们 ...
- linux驱动篇-touchscreen-完整版
Touchscreen 本篇文章为触摸屏驱动完整版本,为的是给时间充裕的同学详细讲解.如要时间有限可以看精简版,传送门在下面. https://blog.csdn.net/chichi123137/a ...
- linux 内核驱动的poll,嵌入式Linux驱动开发(五)——poll机制原理以及驱动实现...
前情回顾: 再开始今天的内容之前,先简单review一下,我们都用了什么方案来获取按键值,他们的特点都是什么.只有不断地理清了思路,我们才能够更好的理解,为何会出现如此多的解决方案,当遇到问题的时候, ...
- 从单片机到ARM Linux驱动——Linux驱动入门篇
大一到大二这段时间里学习过单片机的相关知识,对单片机有一定的认识和了解.如果要深究其原理可能还差了一些火候.知道如何编写程序来点量一个LED灯,改一改官方提供的例程来实现一些功能做一些小东西,对IIC ...
最新文章
- 编程入门到进大厂,你需要这套学习架构
- spoj Test(hash)
- 职场减压妙计:主动降职
- Mysql count() 语句
- Leetcode--239. 滑动窗口最大值
- 电脑怎么换自己的壁纸_怎么才能给自己的爱机一个合理的价格—想把自己电脑回收的...
- PyTorch【torchvision】
- 开发中遇到的java小知识
- 简单入门Buffer
- python中max函数用法_python奇技淫巧——max/min函数的用法
- 网络广告计费方式CPM、CPA、CPS、CPT、CPC及比较分析
- MacBook 快捷键
- 通信工程专业就业怎么样?难不难学?
- SpringBoot中Redis报错:NOAUTH Authentication required.; nested exception is redis.clients.jedis.exceptio
- css语义化命名_为什么我只在生产中使用语义命名
- word2vec的词向量神经网络的embedding层的关系
- 不能有比这个再靠谱的星座分析了
- linux系统怎么重启网卡?linux重启网卡的三种教程
- python(64位)安装超详细
- QQ会员抽奖系统引流源码_适合引流,营销,推广
热门文章
- HTTPS安全网站访问
- 使用Git+GoogleCode管理代码
- MySQL(InnoDB剖析):15---table之(表空间:段(segment)、区(extent)、页(page))
- Arduino溶氧仪
- “回车”(Carriage Return)和“换行”(Line Feed)
- 快速查看CAD图纸怎么操作呢?
- 访问http请求, webservice接口报错证书问题
- WIN/MAC使用exFat格式共用移动硬盘
- centos 下安装ati显卡驱动方法
- 下载了免费的txt电子书,如何用Windows电脑阅读?