linux3.4.2 按键驱动 - 中断、防抖、poll、同步、异步

目录

一   使用中断检测按键是否按下

二  使用定时器进行防抖

三 驱动的休眠与唤醒

四  linux驱动poll机制

五  使用异步通信主动发送信号给app

六 使用信号量实现同步互斥

七 使用原子实现同步互斥

八  驱动源码分享

九  以poll方式写应用程序

十 以signal方式获取驱动发出的信号


一   使用中断检测按键是否按下

更多中断处理过程,推荐博客:https://www.cnblogs.com/amanlikethis/p/6941666.html

1.注册一个中断

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
            const char *name, void *dev)
irq申请的硬件中断号,可在 arch/arm/mach-s3c24xx/include/mach/irqs.h 文件中查看
handler中断处理函数,dev_id参数将传递给它。 typedef irqreturn_t (*irq_handler_t)(int, void *)
flags

触发中断条件,这几个flag是可以通过或的方式同时使用,可在头文件linux/interrupt.h中查看,这里例举几个:

IRQF_TRIGGER_RISING :上升沿触发

IRQF_TRIGGER_FALLING :下降沿触发

IRQF_TRIGGER_HIGH:高电平触发

IRQF_TRIGGER_LOW:低电平触发

name中断名称,通常为设备驱动名称,在/proc/interrupts中查看
dev在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。
返回值

0表示成功,-INVAL

-INVAL表示中断号无效或处理函数指针为NULL,

-EBUSY表示中断已经被占用且不能共享。

2.声明一个中断处理程序:

static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs)

3.释放中断

void free_irq(unsigned int irq, void *dev_id)

二  使用定时器进行防抖

1.定时器结构体与常用API介绍,可在linux/timer.h中查看

/********************************定时器结构体***************************/
struct timer_list {
	//All fields that change during normal runtime grouped to the same cacheline
	struct list_head entry;
	unsigned long expires;//定时器滴答数,当前的滴答数为jiffies
	struct tvec_base *base;

	void (*function)(unsigned long);//超时处理函数
	unsigned long data;//处理函数的参数

	int slack;

#ifdef CONFIG_TIMER_STATS
	int start_pid;
	void *start_site;
	char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
	struct lockdep_map lockdep_map;
#endif
};

/**********************定时器常用API接口****************************/
init_timer(struct timer_list*):定时器初始化函数; 
add_timer(struct timer_list*):添加定时器; 
mod_timer(struct timer_list *, unsigned long jiffier_timerout):修改定时器的超时时间
timer_pending(struct timer_list *):状态查询,如果在系统的定时器列表中则返回1,否则返回0; 
del_timer(struct timer_list*):删除定时器。 

2. 使用定时器步骤

struct timer_list  my_timer_list;      //定义一个定时器,可以把它放在你的设备结构中
init_timer(&my_timer_list);            //初始化一个定时器
my_timer_list.expire=jiffies+HZ;       //1s后运行服务程序,iffies记录了系统启动后的滴答数
my_timer_list.function=timer_function; //定时器服务函数
add_timer(&my_timer_list);             //添加定时器
void timer_function(unsigned long)     //写定时器服务函数
mod_timer(&my_timer_list,HZ);          //重新加载定时器时间
del_timer(&my_timer_list);             //当定时器不再需要时删除定时器
del_timer_sync(&my_timer_list);        //基本和del_timer一样,推荐使用del_timer_sync

三 驱动的休眠与唤醒

更多可参考博客:https://blog.csdn.net/Yeshangzhu/article/details/78051798

#include <linux/sched.h>

static DECLARE_WAIT_QUEUE_HEAD(wq);//定义队列
static volatile unsigned char condition = 0;

wake_up_interruptible(&wq);//退出休眠

/*当condition = 0,进入休眠
 *当condition = 1,不休眠*/
wait_event_interruptible(wq, condition);//进入休眠

四  linux驱动poll机制

poll翻译为轮询,该轮询需要设置超时时间。在按键驱动中,poll机制可理解为:在一定时间内,不断查询按键是否按下,当按键按下或者超时时间到才会立即返回。

1. poll常用宏定义以及其含义,可在poll.h文件中查看

#define POLLIN		0x0001  //有数据可读。
#define POLLPRI		0x0002	//有紧迫数据可读。
#define POLLOUT		0x0004	//写数据不会导致阻塞。
#define POLLERR		0x0008	//指定的文件描述符发生错误。
#define POLLHUP		0x0010	//指定的文件描述符挂起事件。
#define POLLNVAL	0x0020  //指定的文件描述符非法。
#define POLLRDNORM	0x0040  //有普通数据可读。
#define POLLRDBAND	0x0080  //有优先数据可读。
#define POLLWRNORM	0x0100  //写普通数据不会导致阻塞。
#define POLLWRBAND	0x0200  //写优先数据不会导致阻塞。
#define POLLMSG		0x0400  //消息可用。
#define POLLREMOVE	0x1000  //删除指定的文件描述符

2. app调用poll,会进入到do_sys_poll函数中的do_poll函数,在do_poll函数会调用驱动中的poll函数

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,struct timespec *end_time)
{
    struct poll_wqueues table;
    ......
    poll_initwait(&table);
    fdcount = do_poll(nfds, head, &table, end_time);
    poll_freewait(&table);
    ......
}


static int do_poll(unsigned int nfds,  struct poll_list *list,
		   struct poll_wqueues *wait, struct timespec *end_time)
{
    ......
    for (;;) {
       ......
       /*在do_pollfd调用我们驱动中的poll函数:mask = file->f_op->poll(file, pwait);
        * 如果驱动返回非0值,那么count++,满足break条件,返回app
        * 如果驱动返回0值,那么系统会调用poll_schedule_timeout函数进入休眠*/
        if (do_pollfd(pfd, pt)) {
           count++;
           pt->_qproc = NULL;
        }
         ......
        //break条件:count非0,超时,有信号在等待处理
        if (count || timed_out)
          break;
         ......
         //进入休眠
         if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
            timed_out = 1;
    }
}

3. 编写驱动中的poll函数

unsigned int key_poll (struct file *file, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	/* 将进程挂在key_waitq队列上 */  
	poll_wait(file, &key_waitq, wait);

	/* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0  
     * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 */
	if(ev_press == 1){
		mask |= POLLIN | POLLRDNORM;/* 表示有普通数据可读 */  
	}

	/* 有按键按下时,mask |= POLLIN | POLLRDNORM, 系统会立即返回用户空间
	   没有按键按下时,mask = 0 ,系统会在内核空间进入休眠*/  
	return mask;
}

五  使用异步通信主动发送信号给app

1.信号类型可在signal.h中查看

1SIGHUP挂起 
2SIGINT中断 
3SIGQUIT退出 
4SIGILL非法指令 
5SIGTRAP断点或陷阱指令 
6SIGABRTabort发出的信号 
7SIGBUS非法内存访问 
8SIGFPE浮点异常 
9SIGKILLkill信号不能被忽略、处理和阻塞
10SIGUSR1用户信号1 
11SIGSEGV无效内存访问 
12SIGUSR2用户信号2 
13SIGPIPE管道破损,没有读端的管道写数据 
14SIGALRMalarm发出的信号 
15SIGTERM终止信号 
16SIGSTKFLT栈溢出 
17SIGCHLD子进程退出默认忽略
18SIGCONT进程继续 
19SIGSTOP进程停止不能被忽略、处理和阻塞
20SIGTSTP进程停止 
21SIGTTIN进程停止,后台进程从终端读数据时 
22SIGTTOU进程停止,后台进程想终端写数据时 
23SIGURGI/O有紧急数据到达当前进程默认忽略
24SIGXCPU进程的CPU时间片到期 
25SIGXFSZ文件大小的超出上限 
26SIGVTALRM虚拟时钟超时 
27SIGPROFprofile时钟超时 
28SIGWINCH窗口大小改变默认忽略
29SIGIOI/O相关 
30SIGPWR关机默认忽略
31SIGSYS系统调用异常 

2.驱动中实现异步通信方式如下:

static struct fasync_struct *key_async;
static struct file_operations key_fops ={
	.fasync = keys_fasync
};

---------------------------------------------------------------------------
/* 当应用程序调用了fcntl(fd, F_SETFL, Oflags | FASYNC);  
 * 则最终会调用驱动的fasync函数,在这里则是fifth_drv_fasync 
 * fifth_drv_fasync最终又会调用到驱动的fasync_helper函数 
 * fasync_helper函数的作用是初始化/释放fasync_struct 
 */ 
static int keys_fasync(int fd, struct file *filp, int on)
{
	printk("driver: keys_fasync\n");
	return fasync_helper(fd, filp, on, &key_async);//key_async初始化
}

-----------------------------------------------------------------------------
/* 用kill_fasync函数告诉应用程序,有数据可读了  
 * button_fasync结构体里包含了发给谁(PID指定) 
 * SIGIO表示要发送的信号类型 
 * POLL_IN表示发送的原因(有数据可读了) 
 */
 kill_fasync(&key_async, SIGIO, POLL_IN);

六 使用信号量实现同步互斥

定义信号量:

struct semaphore sem;

初始化信号量:

void sema_init(struct semaphore * sem, int val);
void sema_init(struct semaphore * sem); //初始化为0

获取信号量:

void down(struct semaphore * sem);//获取不到信号量时,等待信号量
int   down_interruptible(struct semaphore * sem);
int   down_trylock(struct semaphore * sem);//获取不到信号量时立刻返回

释放信号量:

up(struct semaphore * sem);

七 使用原子实现同步互斥

static atomic_t can_open = ATOMIC_INIT(1); //定义原子变量并初始化为1
atomic_read(atomic_t *v);//返回原子变量的值
void atomic_inc(atomic_t *v);//原子变量增加1
void atomic_dec(atomic_t *v);//变量减1
int atomic_dec_and_test(atomic_t *v);//变量减1后,测试是否为0, 为0则返回true, 否则返回false。

八  驱动源码分享

#include <linux/module.h>  //定义了THIS_MODULE宏
#include <linux/fs.h>      //定义了file_operations结构体
#include <linux/device.h>  //定义了class_create/device_create/class_destory/device_destory函数
                           //定义了class 与 class_device结构体
#include <linux/interrupt.h>//定义了IRQF_TRIGGER_RISING 宏
#include <linux/input.h>   //定义了按键相关
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/gpio.h>    //包含了s3c2410_gpio_cfgpin等io操作函数
#include <asm/uaccess.h>   //定义了copy_to_user函数
#include <asm/io.h>        //定义了ioremap 与iounremap函数
#include <mach/regs-gpio.h>//包含了GPIO相关宏
#include <mach/irqs.h>     //包含了中断相关定义
#include <linux/types.h>
#include <linux/spinlock.h>

#define DEVICE_NAME "key_drv"
static int major;
static struct class *key_class;
static struct device *key_class_dev;
static DECLARE_WAIT_QUEUE_HEAD(key_waitq);//定义队列
static volatile unsigned char ev_press;
static struct fasync_struct *key_async;
static struct timer_list keys_timer;
static struct semaphore sem;//定义信号量

typedef struct {
	int irq;	//按键的外部中断标志位, 可在irqs.h中查找有那些中断
	char *name;	//名称,一段字符串,自己定义.
	unsigned int pin;//引脚
	unsigned int key_val;//键值,键盘上每个按键(0~9,a~z...)都有一个对应的键值,用于判断按下了那个键.
}key_desc_t;
static key_desc_t *keydesc = NULL;
static key_desc_t key_desc[4] = {
	{IRQ_EINT0,"s2",S3C2410_GPF(0),0x2},
	{IRQ_EINT2,"s3",S3C2410_GPF(2),0x3},
	{IRQ_EINT11,"s4",S3C2410_GPG(3),0x4},
	{IRQ_EINT19,"s5",S3C2410_GPG(11),0x5},
};

/**
 * 超时处理函数
 */
void keys_timer_handler(unsigned long data)
{
	unsigned int i,pin_v;

	for(i=0; i<4; i++){
		pin_v = s3c2410_gpio_getpin(key_desc[i].pin);
		if(pin_v == 0){//低电平,按键按下
			key_desc[i].key_val |= 0x80;//最高位置一
		}else{//高电平,没有按键按下
			key_desc[i].key_val &= ~0x80;//最高位清零
		}
	}
	
	ev_press = 1;
	wake_up_interruptible(&key_waitq);/* 唤醒休眠的进程 */ 

	/* 用kill_fasync函数告诉应用程序,有数据可读了  
     * button_fasync结构体里包含了发给谁(PID指定) 
     * SIGIO表示要发送的信号类型 
     * POLL_IN表示发送的原因(有数据可读了) 
     */
	kill_fasync(&key_async, SIGIO, POLL_IN);
}

/*中断处理函数,当发生中断时会调用该函数*/
static irqreturn_t irq_handler(int irq, void *dev)
{
	//jiffies记录自系统启动一来产生的节拍数
	//HZ-1s,HZ/100 = 10ms
	mod_timer(&keys_timer, jiffies+msecs_to_jiffies(10));//修改定时器,10ms后产生定时器超时中断

	return IRQ_RETVAL(IRQ_HANDLED);
}

static ssize_t key_read (struct file *file, char __user *buf, size_t size, loff_t *loff)
{
	unsigned char key_v[4],i;
	if(buf == NULL ){
		return -EINVAL;
	}

	/*没有任何按键操作时,ev_press = 0,进入休眠
	 *当有按键按下时,ev_press = 1,不休眠
	*/
	wait_event_interruptible(key_waitq,ev_press);

	for(i = 0;i<4; i++){
		key_v[i] = key_desc[i].key_val;
	}
	if(copy_to_user(buf, key_v, 4)){
		printk("copy_to_user error");
	}
	ev_press = 0;//清零
	return 4;
}

/** 注册中断,按键检测引脚默认为高电平,当有按键按下时为低电平*/
static int key_open (struct inode *inode, struct file *file)
{
	int i = 0;
	int ret;
	if (file->f_flags & O_NONBLOCK){
		if (down_trylock(&sem))//获取不到信号时立即返回
			return -EBUSY;
	}else{
		down(&sem);//获取不到信号时,等待信号返回
	}
	for(i = 0;i<4;i++){
		ret = request_irq(key_desc[i].irq,//中断号,可在irqs.h中查看
		            irq_handler,//中断处理函数
					IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,//中断触发条件,可在interrupt.h中查看
					key_desc[i].name,//中断名,自己定义的一段字符串
					&key_desc[i]);//发生中断时,该指针会传递给中断函数.
		if(ret){
			printk("request_irq error,ret = %d",ret);
		}
	}
	return 0;
}

static unsigned int key_poll (struct file *file, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	/* 将进程挂在key_waitq队列上 */  
	poll_wait(file, &key_waitq, wait);

	/* 当没有按键按下时,即不会进入按键中断处理函数,此时ev_press = 0  
     * 当按键按下时,就会进入按键中断处理函数,此时ev_press被设置为1 */
	if(ev_press == 1){
		mask |= POLLIN | POLLRDNORM;/* 表示有普通数据可读 */  
	}

	/* 有按键按下时,mask |= POLLIN | POLLRDNORM, 系统会立即返回用户空间
	   没有按键按下时,mask = 0 ,系统会在内核空间进入休眠*/  
	return mask;
}

static int key_release (struct inode *inode, struct file *file)
{
	int i = 0;
	for(i = 0;i<4;i++){
		free_irq(key_desc[i].irq,&key_desc[i]);
	}
	up(&sem);
	return 0;
}

/* 当应用程序调用了fcntl(fd, F_SETFL, Oflags | FASYNC);  
 * 则最终会调用驱动的fasync函数,在这里则是fifth_drv_fasync 
 * fifth_drv_fasync最终又会调用到驱动的fasync_helper函数 
 * fasync_helper函数的作用是初始化/释放fasync_struct 
 */ 
static int key_fasync (int fd, struct file *filp, int on)
{
	printk(">>key_fasync\n");
	return fasync_helper(fd, filp, on, &key_async);//key_async初始化
}


static struct file_operations key_fops ={
	.owner = THIS_MODULE,
	.open = key_open,
	.read = key_read,
	.release = key_release,
	.poll = key_poll,
	.fasync = key_fasync
};


/*入口函数*/
static int __init key_drv_init(void)
{
    major = register_chrdev(0, DEVICE_NAME, &key_fops);
	key_class = class_create(THIS_MODULE, DEVICE_NAME);
	if(IS_ERR(key_class))
		return PTR_ERR(key_class);

	key_class_dev = device_create(key_class, NULL, MKDEV(major,0), NULL, "keys");// /dev/key
	if(unlikely(IS_ERR(key_class_dev)))
	  return PTR_ERR(key_class_dev);
	
	//初始化定时器,防抖动
	init_timer(&keys_timer);
	keys_timer.function = keys_timer_handler;//定时器的处理函数
	add_timer(&keys_timer);

	//初始化信号量,同一时刻最多只能有1个应用打开程序。
	sema_init(&sem,1);

	s3c2410_gpio_cfgpin(key_desc[0].pin,S3C2410_GPF0_EINT0);
	s3c2410_gpio_cfgpin(key_desc[1].pin,S3C2410_GPF2_EINT2);
	s3c2410_gpio_cfgpin(key_desc[2].pin,S3C2410_GPG3_EINT11);
	s3c2410_gpio_cfgpin(key_desc[3].pin,S3C2410_GPG11_EINT19);
	return 0;
}
/*出口函数*/
static void __exit key_drv_exit(void)
{
    unregister_chrdev(major, DEVICE_NAME);
	device_destroy(key_class, MKDEV(major, 0));
	class_destroy(key_class);
	del_timer(&keys_timer);
}

module_init(key_drv_init);
module_exit(key_drv_exit);
MODULE_LICENSE("GPL");

九  以poll方式写应用程序

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <unistd.h>
 
int main(int argc, char **argv)
{
	unsigned char key_val[4];
	int ret;
	struct pollfd fds;
	fds.fd = open("/dev/keys",O_RDWR);
	if (fds.fd < 0){
		printf("can't open!\n");
	}
	fds.events = POLLIN;
	while (1)
	{
		ret = poll(&fds, 1, 5000);//需要监视的设备数为5秒
		if (ret == 0){
			printf("time out\n");
		}else{
			read(fds.fd, &key_val, 4);
			printf("key_val = 0x%2x,0x%2x,0x%2x,0x%2x\n", key_val[0],key_val[1],key_val[2],key_val[3]);
		}
	}
	return 0;
}

十 以signal方式获取驱动发出的信号

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fd;
void signal_fun(int signum)
{
	unsigned char key_val[4];
	read(fd, key_val, 4);
	printf("key_val = 0x%02x,0x%02x,0x%02x,0x%02x\n", key_val[0],key_val[1],key_val[2],key_val[3]);
}

int main(int argc, char **argv)
{
	unsigned char key_val;
	int ret;
	int Oflags;

	signal(SIGIO, signal_fun);
	fd = open("/dev/keys", O_RDWR | O_NONBLOCK);
	if (fd < 0){
		printf("can't open!\n");
	}
	fcntl(fd, F_SETOWN, getpid());//设置异步I/O所有权
	Oflags = fcntl(fd, F_GETFL); //获得文件状态标记
	fcntl(fd, F_SETFL, Oflags | FASYNC);//设置文件状态标记

	while (1)
	{
		sleep(1000);
	}	return 0;
}

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值