目录
一 使用中断检测按键是否按下
更多中断处理过程,推荐博客: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中查看
1 | SIGHUP | 挂起 | |
2 | SIGINT | 中断 | |
3 | SIGQUIT | 退出 | |
4 | SIGILL | 非法指令 | |
5 | SIGTRAP | 断点或陷阱指令 | |
6 | SIGABRT | abort发出的信号 | |
7 | SIGBUS | 非法内存访问 | |
8 | SIGFPE | 浮点异常 | |
9 | SIGKILL | kill信号 | 不能被忽略、处理和阻塞 |
10 | SIGUSR1 | 用户信号1 | |
11 | SIGSEGV | 无效内存访问 | |
12 | SIGUSR2 | 用户信号2 | |
13 | SIGPIPE | 管道破损,没有读端的管道写数据 | |
14 | SIGALRM | alarm发出的信号 | |
15 | SIGTERM | 终止信号 | |
16 | SIGSTKFLT | 栈溢出 | |
17 | SIGCHLD | 子进程退出 | 默认忽略 |
18 | SIGCONT | 进程继续 | |
19 | SIGSTOP | 进程停止 | 不能被忽略、处理和阻塞 |
20 | SIGTSTP | 进程停止 | |
21 | SIGTTIN | 进程停止,后台进程从终端读数据时 | |
22 | SIGTTOU | 进程停止,后台进程想终端写数据时 | |
23 | SIGURG | I/O有紧急数据到达当前进程 | 默认忽略 |
24 | SIGXCPU | 进程的CPU时间片到期 | |
25 | SIGXFSZ | 文件大小的超出上限 | |
26 | SIGVTALRM | 虚拟时钟超时 | |
27 | SIGPROF | profile时钟超时 | |
28 | SIGWINCH | 窗口大小改变 | 默认忽略 |
29 | SIGIO | I/O相关 | |
30 | SIGPWR | 关机 | 默认忽略 |
31 | SIGSYS | 系统调用异常 |
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;
}