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.31

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-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

更多原创文章请关注微信公众号。另外,中年润还代理销售韦东山老师的视频教程,欢迎读者点击下面二维码购买。在中年润这里购买了韦东山老师的视频教程,除了能得到韦东山官方的技术支持外,还能获得中年润细致入微的技术和非技术的支持和帮助。欢迎选购哦。

公众号二维码如下图

入群小助手二维码如下图

中年润代理销售的韦东山视频购买地址如下图

如果略有所获,欢迎赞赏,您的支持对中年润无比重要

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值