本篇是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相关推荐

  1. linux驱动篇之 driver_register 过程分析(二)bus_add_driver

    linux驱动篇之 driver_register 过程分析(二) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...

  2. Linux驱动篇之内核模块

    Linux驱动篇之内核模块 1.基本概念 模块与驱动: Linux中,将设备分为三种基本的类型. 字符设备 块设备 网络接口 Linux中还有一个很重要的概念,模块.可在运行时添加到内核中的代码被称为 ...

  3. 嵌入式学习之Linux驱动篇-迅为视频更新了

    想学习Linux驱动但是无从下手的同学,学习Linux驱动但是一直不能入门的同学,学习了很多视频和资料还是很懵的同学快来学习拉 https://www.bilibili.com/video/BV1Vy ...

  4. linux驱动篇之 driver_register 过程分析(一)

    linux驱动注册过程分析--driver_register(一) 个人笔记,欢迎转载,请注明出处,共同分享 共同进步 http://blog.csdn.net/richard_liujh/artic ...

  5. linux驱动篇-Button-中断法

    本篇是linux下按键设备驱动,采用的中断法,也是属于字符设备类的驱动,一起来动手吧.下面的话,老朋友可以跳过了直接从<需求描述>章节看起,新朋友可以试着看看. 前言 在嵌入式行业,有很多 ...

  6. linux驱动篇-LCD

    前言 在嵌入式行业,有很多从业者.我们工作的主旋律是拿开源代码,拿厂家代码,完成产品的功能,提升产品的性能,进而解决各种各样的问题.或者是维护一个模块或方向,一搞就是好几年. 时间长了,中年润发现我们 ...

  7. linux驱动篇-touchscreen-完整版

    Touchscreen 本篇文章为触摸屏驱动完整版本,为的是给时间充裕的同学详细讲解.如要时间有限可以看精简版,传送门在下面. https://blog.csdn.net/chichi123137/a ...

  8. linux 内核驱动的poll,嵌入式Linux驱动开发(五)——poll机制原理以及驱动实现...

    前情回顾: 再开始今天的内容之前,先简单review一下,我们都用了什么方案来获取按键值,他们的特点都是什么.只有不断地理清了思路,我们才能够更好的理解,为何会出现如此多的解决方案,当遇到问题的时候, ...

  9. 从单片机到ARM Linux驱动——Linux驱动入门篇

    大一到大二这段时间里学习过单片机的相关知识,对单片机有一定的认识和了解.如果要深究其原理可能还差了一些火候.知道如何编写程序来点量一个LED灯,改一改官方提供的例程来实现一些功能做一些小东西,对IIC ...

最新文章

  1. 编程入门到进大厂,你需要这套学习架构
  2. spoj Test(hash)
  3. 职场减压妙计:主动降职
  4. Mysql count() 语句
  5. Leetcode--239. 滑动窗口最大值
  6. 电脑怎么换自己的壁纸_怎么才能给自己的爱机一个合理的价格—想把自己电脑回收的...
  7. PyTorch【torchvision】
  8. 开发中遇到的java小知识
  9. 简单入门Buffer
  10. python中max函数用法_python奇技淫巧——max/min函数的用法
  11. 网络广告计费方式CPM、CPA、CPS、CPT、CPC及比较分析
  12. MacBook 快捷键
  13. 通信工程专业就业怎么样?难不难学?
  14. SpringBoot中Redis报错:NOAUTH Authentication required.; nested exception is redis.clients.jedis.exceptio
  15. css语义化命名_为什么我只在生产中使用语义命名
  16. word2vec的词向量神经网络的embedding层的关系
  17. 不能有比这个再靠谱的星座分析了
  18. linux系统怎么重启网卡?linux重启网卡的三种教程
  19. python(64位)安装超详细
  20. QQ会员抽奖系统引流源码_适合引流,营销,推广

热门文章

  1. HTTPS安全网站访问
  2. 使用Git+GoogleCode管理代码
  3. MySQL(InnoDB剖析):15---table之(表空间:段(segment)、区(extent)、页(page))
  4. Arduino溶氧仪
  5. “回车”(Carriage Return)和“换行”(Line Feed)
  6. 快速查看CAD图纸怎么操作呢?
  7. 访问http请求, webservice接口报错证书问题
  8. WIN/MAC使用exFat格式共用移动硬盘
  9. centos 下安装ati显卡驱动方法
  10. 下载了免费的txt电子书,如何用Windows电脑阅读?