按键驱动(平台文件、中断)

来源:互联网 发布:网络管理(兼职) 编辑:程序博客网 时间:2024/06/05 06:28

1. 基本的思路(两种方法)

    方法一:直接以驱动的方式,在一个文件中将设备资源写好,驱动直接使用。

    方法二:以 设备 和 驱动 分离的思想,驱动中用获取函数的方式使用设备。方法二的实现


2. 实现方法一

文件结构:

.
├── dri
│   ├── key_irq.c
│   └── Makefile
└── test
    └── test.c

key_irq.c

#include <linux/module.h>  #include <linux/kernel.h>  #include <linux/fs.h>  #include <linux/init.h>  #include <linux/delay.h>  #include <linux/irq.h>  #include <asm/uaccess.h>  #include <asm/irq.h>  #include <asm/io.h>  #include <linux/poll.h>#include <linux/interrupt.h>#include <mach/regs-gpio.h>#include <mach/hardware.h>#include <linux/wait.h>#include <linux/device.h>#include <mach/gpio.h>#include <linux/interrupt.h>#include <linux/sched.h>#include <linux/timer.h>#define DEVICE_NAME "jz2440_button_drv"static unsigned char key_vals[4];   /* 保存要传递给用户空间的4个按键值 */static struct timer_list key_timers[4];#define KEYDOWN_DELAY (HZ/100)  /* 按键按下时的去抖延时是10ms */#define KEYUP_DELAY   (HZ/200)  /* 按键抬起时的去抖延时是5ms */struct jz2440_key_t{    const char *name;    unsigned int pin;    unsigned char key_val;    unsigned int irq;  /*按键的中断号 */    unsigned int setting;  /* 按键对应的功能的掩码 */    unsigned int id;};/* * key s2 is GPF0 -- EINT0 * key s3 is GPF2 -- EINT2 * key s4 is GPG3 -- EINT11 * key s5 is GPG11-- EINT19 */struct jz2440_key_t key_table[] ={    {"Button S2", S3C2410_GPF(0),  0, IRQ_EINT(0),  S3C2410_GPF0_EINT0,   0},    {"Button S3", S3C2410_GPF(2),  0, IRQ_EINT(2),  S3C2410_GPF2_EINT2,   1},    {"Button S4", S3C2410_GPG(3),  0, IRQ_EINT(11), S3C2410_GPG3_EINT11,  2},    {"Button S5", S3C2410_GPG(11), 0, IRQ_EINT(19), S3C2410_GPG11_EINT19, 3},};static struct class *button_drv_class;int major;unsigned int key_pressed;unsigned int key_pressed_middle_flag;static DECLARE_WAIT_QUEUE_HEAD(button_waitq);irqreturn_t irq_handler(int irq, void *dev_id){    int key_num = *((int *)dev_id);    unsigned int pinval = s3c2410_gpio_getpin(key_table[key_num].pin);    if (!pinval)    {        key_pressed_middle_flag = 1;        mod_timer(&key_timers[key_num], jiffies + KEYDOWN_DELAY);    }else    {        key_pressed_middle_flag = 0;        mod_timer(&key_timers[key_num], jiffies + KEYUP_DELAY);    }    return IRQ_RETVAL(IRQ_HANDLED);}void fun_timer(unsigned long arg){    int pinval = s3c2410_gpio_getpin(key_table[arg].pin);    if (!pinval)    {        if (key_pressed_middle_flag == 1)        {            /*printk("pree down\n");*/            key_pressed = 1;            key_table[arg].key_val = 1;            wake_up_interruptible(&button_waitq);  /* 唤醒由 poll_wait 而引起的阻塞 */        }    }else    {        if (key_pressed_middle_flag == 0)        {            /*printk("pree up\n");*/            key_pressed = 0;            key_table[arg].key_val = 0;        }    }}static int jz2440_button_drv_open(struct inode *inode, struct file *filp){    int i;    for (i = 0; i < 4; i++)    {        s3c2410_gpio_cfgpin(key_table[i].pin, key_table[i].setting);  /* 将芯片引脚设置成中断 */        irq_set_irq_type(key_table[i].irq, IRQ_TYPE_EDGE_BOTH);  /* 下降沿触发 */        request_irq(key_table[i].irq, irq_handler, IRQF_DISABLED, key_table[i].name, &key_table[i].id);  /* 向系统内核申请快速中断 */        setup_timer(&key_timers[i], fun_timer, i);    }    printk("button_drv_open\n");    return 0;}static int jz2440_button_drv_close(struct inode *inode, struct file *filp){    int i;    for (i = 0; i < 4; i++)    {        del_timer(&key_timers[i]);        /*disable_irq(key_table[i].irq);*/        free_irq(key_table[i].irq, &key_table[i].id);    }    return 0;}/* 读的时候按键的 size 是字节的个数 ,返回成功读取的个数 */ssize_t jz2440_button_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos){    int j;    unsigned long ret;    if (size != sizeof(key_vals))        return -EINVAL;    /* 如果是非阻塞的读 */    if (file->f_flags & O_NONBLOCK)    {        if (!key_pressed) /* 非阻塞的读,按键没有按下过 */            return -EAGAIN;    }    else if (!key_pressed) /* 阻塞的读,按键没有按下过 */    {        wait_event_interruptible(button_waitq, key_pressed);    }    key_pressed = 0;    for (j = 0; j < 4; j++)    {        key_vals[j] = key_table[j].key_val;    }    ret = copy_to_user(buf, key_vals, size); /* 返回 key_vals 数组数据到用户空间 */    return ret ? -EFAULT : min(sizeof(key_vals), size);}unsigned int jz2440_button_drv_poll (struct file *filp, struct poll_table_struct *wait){    unsigned int mask = 0;    poll_wait(filp, &button_waitq, wait); /* 将此线程放入等待队列头(此处还要看select传递过来的参数) */      if (key_pressed)        mask |= POLLIN | POLLRDNORM; /* 标示数据可获得 */      return mask;  }static struct file_operations button_drv_fops ={    .owner = THIS_MODULE,    .open  = jz2440_button_drv_open,    .release = jz2440_button_drv_close,    .read  = jz2440_button_drv_read,    .poll  = jz2440_button_drv_poll,};int button_drv_init(void){    /* auto choice major */    major = register_chrdev(0, DEVICE_NAME, &button_drv_fops); /* 自动分配设备号,并注册字符设备 */    if (major < 0)    {        printk("jz2440 button driver can't register major number!\n");        return major;    }    button_drv_class = class_create(THIS_MODULE, DEVICE_NAME); /* 创建一个类 */    if (IS_ERR(button_drv_class))    {        printk("error, fail to init button_drv_class");        return -1;    }    device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME); /* 创建设备节点 */    printk("BUTTON_DRV initialized! \n");    return 0;}void button_drv_exit(void){    device_destroy(button_drv_class, MKDEV(major, 0)); //删掉设备节点    class_destroy(button_drv_class);  //注销类    unregister_chrdev(major, DEVICE_NAME);  //卸载驱动    printk("exit is finished.\n");}module_init(button_drv_init);module_exit(button_drv_exit);MODULE_LICENSE("GPL");

说明:

    【1】中断说明,向系统内核申请了4个中断,可以用 cat /proc/interrupts 中查看到,按键设置成了中断方式,上升和下降沿触发的中断

    【2】消抖,按下 向系统中添加了一个10ms的定时器,定时到了后,看是不是状态跟中断中的相同,定时器自动失效,抬起 向系统中添加了一个5ms的定时器 ...

    【3】read 中用等待对列的方式,提供阻塞读的支持,如果用户程序以阻塞的方式读取,就会进去等待队列中,当有按键按下的时候,将等待队列中的按键唤醒,并将数据返回给用户,但是程序中不是 read 引起的等待,是由 select 引起的等待

    【4】有4个按键,这四个按键对应着4个定时器,可以同时按下

    【5】如果出现中断号不能释放掉的情况,并且用的是mini2440 的配置文件,很可能是你的mini2440的配置文件中有冲突,你需要去掉默认的按键的配置,这个部分可以查看jz2440:编译内核 这篇文章


Makefile

ifeq ($(KERNELRELEASE),)#KERNELDIR ?= /lib/modules/$(shell uname -r)/build KERNELDIR ?= ~/wor_lip/linux-3.4.112PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module*.PHONY: modules modules_install cleanelseobj-m := key_irq.oendif

说明:

    【1】一定要注意 KERNELDIR 这个变量,一定要设置成下载的内核源码的路径,不要用Ubuntu的路径,否则会出现找不到头文件的编译错误


test.c

#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/select.h>#include <fcntl.h>#include <unistd.h>#include <signal.h>int fd;fd_set rds;void stop(int signo){    close(fd);    FD_CLR(fd, &rds);    _exit(0);}int main(int argc, const char *argv[]){    unsigned char data_buf[4];    signal(SIGINT, stop); /* 按下ctrl+c,调用stop函数 */    /* 阻塞的读 */    if ((fd = open("/dev/jz2440_button_drv", O_RDONLY)) < 0)    {        perror("fail to open");          return -1;      }    while (1)    {        int ret, i;        FD_ZERO(&rds);        FD_SET(fd, &rds);  /* 将fd文件描述符添加到rds这个文件描述符集中去 */        ret = select(fd + 1, &rds, NULL, NULL, NULL);                printf("doing\n");        if (ret < 0)        {            printf("read jz2440 button fail!\n");            continue;        }else if (ret == 0)        {            printf("read jz2440 button time out!\n");            continue;        }        if (FD_ISSET(fd, &rds))        {            ret = read(fd, data_buf, sizeof(data_buf));            if (ret < 4)            {                printf("read not complete.\n");                break;            }            for (i = 0 ; i < 4; i++)            {                printf("S%d:%d ", 2 + i, data_buf[i]);            }                printf("\n");        }    }    return 0;}

说明:

    这个文件编译的命令是 

    arm-none-linux-gnueabi-gcc test.c -o button -march=armv4t


打印的结果是

[root@lip ~]# insmod key_irq.ko 
BUTTON_DRV initialized! 
[root@lip ~]# ./button 
button_drv_open
doing
S2:0 S3:1 S4:0 S5:0 
doing
S2:0 S3:0 S4:1 S5:0 
doing
S2:1 S3:0 S4:0 S5:0 
doing
S2:0 S3:0 S4:0 S5:1 

0 0