• «
  • 1
  • 2
  • »
  • Pages: 1/2     Go
主题 : 新手原创:mini2440按键驱动及详细解释 复制链接 | 浏览器收藏 | 打印
级别: 新手上路
UID: 7830
精华: 0
发帖: 45
金钱: 335 两
威望: 183 点
综合积分: 90 分
注册时间: 2009-07-30
最后登录: 2017-09-13
楼主  发表于: 2009-09-24 23:37

 新手原创:mini2440按键驱动及详细解释

个人总结,欢迎提问和拍砖。。。^_^。。。后续后继续完善!
按键驱动总结
废话不多说,新手总结按键驱动,如果你是高手就当作打酱油路过啦~~~
程序框架参考《linux设备驱动开发详解》
程序可以分成两部分来看:
第一部分:按键侦测,主要包括中断的初始化、中断处理、按键去抖、等待按键松开,侦测
完成返回按键键值。
第二部分:按键事件处理,主要包括,将案件事件保存至循环链表(其实就是一个数组,可
以循环保存数据)、将案件事件反馈给应用程序(Read函数)。
 

其中涉及到的数据结构有以下几个:
第一个数据结构:设备驱动结构
struct KEY_DEV
{
       unsigned int tkeystatus[KEY_NUM];    //6个按键的状态,每个key对应一个位置
       unsigned char tbuf[MAX_KEY_BUF]; //按键缓冲区,保存按键事件,Read函数从这读取
       unsigned int head,tail;                          //按键缓冲区头和尾,指向缓冲区数据的头和尾
       wait_queue_head_t wq;                 //等待队列,当无数据可读时read函数将挂在这
       struct cdev cdev;                               //没什么好解释啦,^_^
};
所以这个结构体里面各个变量分工是这样的:
unsigned int tkeystatus[KEY_NUM]
当有中断触发的时候,先在tkeystatus[KEY_NUM]这里的对应位置记为
“待定(KEYSTATUS_X)”,当延时20ms后如果通过读IO口的方式发现该端口仍然处于低电平,那么就认为确实有按下这个按键,那么在tkeystatus[KEY_NUM]这里的对应位置记为“按下(KEYSTATUS_DOWN)”

unsigned char tbuf[MAX_KEY_BUF]
按键缓冲区,当确认某一个按键被按下后,将按键编号(看你喜欢怎么标食一个按键啦,随便定义,只要能区分就行了)记录在这里。

unsigned int head,tail;
指向缓冲区里面数据的头尾,比如:


Read函数来读数据的时候就判断HeadTail是不是指向同一个地方,如果指向同一个地方就表示无数据可读,反之,则把数据读出。

Wq
等待队列,当应用程序的Read采用阻塞方式读取的时候,如果当前没按键按下,那么就不能让Read函数返回,所以就用一个等待队列把Read函数挂起来(Read函数),当有数据可读的时候再把队列上的Read函数唤醒(keyEvent函数)。
 

第二个数据结构:
static struct timer_list g_tkey_timer[KEY_NUM]; //6个按键去抖计时器
主要用于计时函数,比如去抖、等待按键松开等。
第三个数据结构:
struct KEY_INFO
{
       int  irq_no;                         //中断号
       unsigned int gpio_port;          //GPOI端口
       int  key_no;                        //自己安排的按键号
};
很简单明了的结构体,一看就知道啦,所以不多说咯!
 

下面开始讲程序架构
第一部分:按键侦测。
1、  程序框架图,左边是框图,右边相应的部分实现过程。
本程序所有按键都是采用中断低电平触发方式。


第二部分:按键事件处理
主要负责按键记录以及与应用层沟通,当应用层调用Read函数的时候,如果在缓冲区有数据则马上反馈,如果无数据则判断应用层是否用阻塞方式读取,如果阻塞方式读取则将Read函数挂起,否则返回。
涉及函数一:keyEvent()记录按键事件到缓冲区。
涉及函数二:key_read()很明白啦…^_^

二楼发代码!
级别: 新手上路
UID: 7830
精华: 0
发帖: 45
金钱: 335 两
威望: 183 点
综合积分: 90 分
注册时间: 2009-07-30
最后登录: 2017-09-13
1楼  发表于: 2009-09-24 23:38
下面是完整驱动代码:
//********************************************//
//                     书写规范                  //
//结构体定义:一律大写字母,中间可用"_"区分      //
//全局变量  :全部用小写字母,加前缀"g_"          //
//局部变量  :全部用小写字母组合,无其他前后缀 //
//指针变量    :在变量前加"p",优先级比"g_"低      //
//数组        :在变量前加"t",优先级比"g_"低      //
//自制函数    :自制函数名字都以"key_"作为前缀      //
//********************************************//

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>

//*******************************
//some macro  define
//*******************************
#define KEY_NUM                    6
#define MAX_KEY_BUF                16    //for circle link
#define MINI2440_KEY_MAJOR      250
#define KEY_BUF_CLR                0x01
#define KEY_DELAY_20MS            (HZ/50) //delay 20ms
#define KEY_DELAY_100MS            (HZ/10) //delay 100ms

//buf循环链表:让x在mod进制下循环
//要求(mod-1)转换为二进制后必须是所有的"1"是连续在低位放置
//比如:mod=8; mod-1=7; 7 = 0x0000 0111(B)
//而不能是比如mod=9; mod-1=8; 8 = 0x0000 1000(B)
#define INC_BUF_POINTOR(x,mod)    ((++(x))&((mod)-1))

//判断Key所在端口的状态
#define ISKEY_DOWN(key) (s3c2410_gpio_getpin(g_tkey_info[key].gpio_port) == 0)


//*******************************
//key status define
//*******************************
#define KEYSTATUS_UP            0 //none action
#define KEYSTATUS_DOWN            1 //press key
#define KEYSTATUS_X                2 //unsure state

static int g_key_major = MINI2440_KEY_MAJOR;

struct KEY_DEV
{
    unsigned int tkeystatus[KEY_NUM];     //6个按键的状态
    unsigned char tbuf[MAX_KEY_BUF];     //按键缓冲区
    unsigned int head,tail;                //按键缓冲区头和尾
    wait_queue_head_t wq;        //等待队列
    struct cdev cdev;
};

struct KEY_DEV *g_pkey_dev;

static struct timer_list g_tkey_timer[KEY_NUM]; //6个按键去抖计时器

struct KEY_INFO
{
    int  irq_no;        //中断号
    unsigned int gpio_port;        //GPOI端口
    int  key_no;
};

struct KEY_INFO g_tkey_info[KEY_NUM] =
{
    //定义按键所使用的资源
    { IRQ_EINT8,  S3C2410_GPG0,  0 }, //NO. 1 2 3 ... 与电路图匹配

    { IRQ_EINT11, S3C2410_GPG3,  1 },
    
    { IRQ_EINT13, S3C2410_GPG5,  2 },

    { IRQ_EINT15, S3C2410_GPG7,  3 },

    { IRQ_EINT14, S3C2410_GPG6,  4 },

    { IRQ_EINT19, S3C2410_GPG11, 5 },
};

static irqreturn_t key_eint_handler(int irq, void *dev_id)
{
    int cnt,key_index;
    key_index = 0;
    for(cnt=0; cnt<KEY_NUM; cnt++)
    {
        if(g_tkey_info[cnt].irq_no == irq)
        {
            key_index = g_tkey_info[cnt].key_no;
            break;
        }
    }
//    printk(KERN_NOTICE "Eint %d\n",key_index);    
    disable_irq(g_tkey_info[key_index].irq_no); //disable irq

    g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_X; //set key in unsure state

    g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_20MS; //set timer value
    add_timer(&g_tkey_timer[key_index]); //start timer
    return IRQ_HANDLED;    
}

static int request_irqs(void)
{
    //申请中断
    struct KEY_INFO *key_info;
    int i;
    for(i=0; i<(sizeof(g_tkey_info)/sizeof(g_tkey_info[1])); i++)
    {
        key_info = g_tkey_info + i;
//        set_external_irq(key_info->irq_no, EXT_LOWLEVEL, GPIO_PULLUP_DIS); //set INT low voltage level target
//        if(request_irq(key_info->irq_no, , SA_INTERRUPT, "Mini2440_Key", i))

//        /include/linux/irq.h        
//        #define    RQ_TYPE_NONE              0x00000000    /* Default, unspecified type */
//        #define IRQ_TYPE_EDGE_RISING    0x00000001  /* Edge rising type */
//        #define IRQ_TYPE_EDGE_FALLING   0x00000002  /* Edge falling type */
//        #define IRQ_TYPE_EDGE_BOTH         (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
//        #define IRQ_TYPE_LEVEL_HIGH     0x00000004  /* Level high type */
//        #define IRQ_TYPE_LEVEL_LOW      0x00000008  /* Level low type */
//        #define IRQ_TYPE_SENSE_MASK     0x0000000f  /* Mask of the above */
//        #define IRQ_TYPE_PROBE          0x00000010  /* Probing in progress */
//        set_external_irq(key_info->irq_no, IRQ_TYPE_LEVEL_LOW, GPIO_PULLUP_DIS); //set INT low voltage level target

//        int set_irq_type (unsigned int irq, unsigned int type);
        set_irq_type(g_tkey_info.irq_no, IRQ_TYPE_LEVEL_LOW);

/* /include/linux/interrupt.h
* flags used only by the kernel as part of the irq handling routines.
* IRQF_DISABLED - keep irqs disabled when calling the action handler
* IRQF_SAMPLE_RANDOM - irq is used to feed the random generator
* IRQF_SHARED - allow sharing the irq among several devices
* IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur
* IRQF_TIMER - Flag to mark this interrupt as timer interrupt
* IRQF_PERCPU - Interrupt is per cpu
* IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
* IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is
*                registered first in an shared interrupt is considered for
*                performance reasons)
*
* #define IRQF_DISABLED       0x00000020
* #define IRQF_SAMPLE_RANDOM  0x00000040
* #define IRQF_SHARED            0x00000080
* #define IRQF_PROBE_SHARED   0x00000100
* #define IRQF_TIMER            0x00000200
* #define IRQF_PERCPU         0x00000400
* #define IRQF_NOBALANCING    0x00000800
* #define IRQF_IRQPOLL        0x00001000
*/
        if(request_irq(key_info->irq_no, key_eint_handler, IRQF_DISABLED,"Mini2440_Key", &i))
        {
            return -1;
        }
    }
    return 0;
}

void free_irqs(void)
{
    struct KEY_INFO *key_info;
    int i;
    for(i=0; i<(sizeof(g_tkey_info)/sizeof(g_tkey_info[1])); i++)
    {
        key_info = g_tkey_info + i;
        free_irq(key_info->irq_no, &i);
    }
}

static void keyEvent(int key_index)
{
    g_pkey_dev->tbuf[g_pkey_dev->head] = key_index;
    g_pkey_dev->head = INC_BUF_POINTOR(g_pkey_dev->head,MAX_KEY_BUF);
    wake_up_interruptible(&g_pkey_dev->wq);
}

static void key_timer_handler(unsigned long data)
{
    int key_index = data;
    //printk("B:get key %d\n",s3c2410_gpio_getpin(g_tkey_info[key_index].gpio_port));
    if (ISKEY_DOWN(key_index))
    {
    //    printk(KERN_NOTICE "B\n");
        if(g_pkey_dev->tkeystatus[key_index] == KEYSTATUS_X)
        {
            g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_DOWN; //change key state
            g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS; //re_initial timer
            
            keyEvent(key_index);

            add_timer(&g_tkey_timer[key_index]); //restart timer
        }
        else //wait for user release the key
        {
            g_tkey_timer[key_index].expires = jiffies + KEY_DELAY_100MS;
            add_timer(&g_tkey_timer[key_index]);
        }
    }
    else //user have released the key
    {
        g_pkey_dev->tkeystatus[key_index] = KEYSTATUS_UP;
        //del_timer(&g_tkey_timer[key_index]);
        enable_irq(g_tkey_info[key_index].irq_no);
    }
}

static int key_open(struct inode *inode, struct file *filp)
{
    printk(KERN_NOTICE "key opened\n");    
    g_pkey_dev->head = g_pkey_dev->tail = 0;
    return 0;
}

static int key_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t key_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
    unsigned int ret,temp;
    unsigned long flag;
    retry:
    if(g_pkey_dev->head != g_pkey_dev->tail)
    {
        local_irq_save(flag); //进入临界区,关闭中断
        ret = g_pkey_dev->tbuf[g_pkey_dev->tail]; //读取尾部指针所指内容
        g_pkey_dev->tail = INC_BUF_POINTOR(g_pkey_dev->tail, MAX_KEY_BUF);
        local_irq_restore(flag); //退出临界区
        //printk(KERN_NOTICE "driver key_read,key no:%d\n",ret);
        temp = copy_to_user(buf, &ret, sizeof(unsigned int));
        //printk(KERN_NOTICE "copy to user return %d\n", temp);
        return (sizeof(unsigned int));
    }
    else
    {
        //printk(KERN_NOTICE "A\n");
        if(filp->f_flags & O_NONBLOCK)
        {
            return -EAGAIN;
        }

        //printk("E:test %d\n",s3c2410_gpio_getpin(g_tkey_info[0].gpio_port));
        interruptible_sleep_on(&(g_pkey_dev->wq));
        
        goto retry;
    }
//    return 0;
}

static int key_ioctl(struct inode *inodep, struct file *filp, unsigned int cmd, unsigned long arg)
{
    unsigned long flag;
    switch(cmd)
    {
        case KEY_BUF_CLR:
            local_irq_save(flag);
            g_pkey_dev->head = g_pkey_dev->tail = 0;
            local_irq_restore(flag);
            printk(KERN_NOTICE "key buf is clear\n");
            break;

        default:
            return - EINVAL;
    }
    return 0;
}

static struct file_operations g_tkey_fops =
{
    .owner      = THIS_MODULE,
    .open    = key_open,        //打开设备
    .release = key_release,        //关闭设备
    .read     = key_read,        //读取按键的键值
    .ioctl     = key_ioctl,        //清除缓冲区
};

static void key_setup_cdev(struct KEY_DEV *pdev, int index)
{
    //1. cdev init
    //2. cdev bind fops
    //3. cdev add
    int err, devno;
    devno = MKDEV(g_key_major, index);

    cdev_init(&(g_pkey_dev->cdev), &g_tkey_fops);
    pdev->cdev.owner = THIS_MODULE;
    pdev->cdev.ops = &g_tkey_fops;

    err = cdev_add(&pdev->cdev, devno, 1);
    if(err)
    {
        printk(KERN_NOTICE "Error %d adding dev %d", err, index);
    }
}

static int mini2440_key_init(void)
{
    //**********************************
    //申请设备号,添加设备
    //**********************************
    int ret,i;
    dev_t devno = MKDEV(g_key_major, 0);

    if(g_key_major)
    {
        ret = register_chrdev_region(devno, 1, "Mini2440_Key");
    }
    else
    {
        ret = alloc_chrdev_region(&devno, 0, 1,"Mini2440_Key");
        g_key_major = MAJOR(devno);
    }

    if(ret < 0)
    {
        return ret;
    }

    g_pkey_dev = kmalloc(sizeof(struct KEY_DEV), GFP_KERNEL);
    if(!g_pkey_dev)
    {
        ret = -ENOMEM;
        goto fail_malloc;
    }
    memset(g_pkey_dev, 0, sizeof(struct KEY_DEV));

    key_setup_cdev(g_pkey_dev, 0);
    
    //**********************************
    //申请设备号,添加设备 完毕!
    //下面初始化其他内容
    //**********************************
    
    request_irqs(); //request all the key irq

    g_pkey_dev->head = g_pkey_dev->tail = 0; //initial key_dev
    
    for(i=0; i<KEY_NUM; i++)
    {
        g_pkey_dev->tkeystatus = KEYSTATUS_UP;
    }

    init_waitqueue_head(&(g_pkey_dev->wq)); //initial wait queue

    for(i=0; i<KEY_NUM; i++)
    {
    //    setup_timer(&g_tkey_timer, key_timer_handler,i);
        g_tkey_timer.function = key_timer_handler;
        g_tkey_timer.data = i;
        init_timer(&g_tkey_timer);
    }

    return 0;

    fail_malloc:unregister_chrdev_region(devno, 1);
    return ret;
}

static void key_exit(void)
{
    free_irqs(); //free irq
    cdev_del(&g_pkey_dev->cdev); //del cdev
    kfree(g_pkey_dev); //free memory
    g_pkey_dev = NULL;
    unregister_chrdev_region(MKDEV(g_key_major,0), 1);
}

MODULE_AUTHOR("Benson");
MODULE_LICENSE("Dual BSD/GPL");

module_param(g_key_major, int, S_IRUGO);
module_init(mini2440_key_init);
module_exit(key_exit);
级别: 新手上路
UID: 7830
精华: 0
发帖: 45
金钱: 335 两
威望: 183 点
综合积分: 90 分
注册时间: 2009-07-30
最后登录: 2017-09-13
2楼  发表于: 2009-09-24 23:40
测试代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    int fd,ret,key_num;
    fd = open("/dev/Mini2440_Key", 0);
    if(fd < 0)
    {
        printf("open error!");
        return -1;
    }
    while(1)
    {
    //    printf("A\n");
        ret = read(fd, &key_num, sizeof(int));
        printf("you press the key %d\n", key_num);
    }
    close(fd);
    return 0;
}
级别: 侠客
UID: 8790
精华: 0
发帖: 54
金钱: 340 两
威望: 104 点
综合积分: 108 分
注册时间: 2009-09-06
最后登录: 2013-05-24
3楼  发表于: 2009-09-27 15:44
测试过吗?
级别: 新手上路
UID: 9637
精华: 0
发帖: 25
金钱: 190 两
威望: 77 点
综合积分: 50 分
注册时间: 2009-10-08
最后登录: 2012-10-10
4楼  发表于: 2009-10-15 15:51
open最好用 NONBLOCK 参数,非阻塞模式。
否则只有按键后才能执行其他操作。
我在磨剑
级别: 新手上路
UID: 8085
精华: 1
发帖: 7
金钱: 90 两
威望: 50 点
综合积分: 34 分
注册时间: 2009-08-09
最后登录: 2009-12-01
5楼  发表于: 2009-10-21 23:59
楼主在板子上实测过这段代码么?
级别: 新手上路
UID: 7830
精华: 0
发帖: 45
金钱: 335 两
威望: 183 点
综合积分: 90 分
注册时间: 2009-07-30
最后登录: 2017-09-13
6楼  发表于: 2009-10-24 15:14

 回 5楼(33479758) 的帖子

实测。。。都是自己敲进去的。。。在mini2440上运行过
我在磨剑
级别: 新手上路
UID: 8085
精华: 1
发帖: 7
金钱: 90 两
威望: 50 点
综合积分: 34 分
注册时间: 2009-08-09
最后登录: 2009-12-01
7楼  发表于: 2009-11-04 19:40
楼主您好  我最近一直在学习您的这段按键驱动代码 但是总是编译过不去 会不会是您发帖粘贴的时候丢了些字符 不知能否发给我一份源文件呢? 多谢了  我的邮箱是xwj_888@tom.com
级别: 新手上路
UID: 7830
精华: 0
发帖: 45
金钱: 335 两
威望: 183 点
综合积分: 90 分
注册时间: 2009-07-30
最后登录: 2017-09-13
8楼  发表于: 2009-11-06 22:45
key_driver.tar (16 K) 下载次数:102
附件是驱动的源代码,只要修改Makefile里面的内核路就可以make了,欢迎多交流!
不知道怎么把附件整理到1楼去,请版主帮忙了!
级别: 新手上路
UID: 11472
精华: 0
发帖: 3
金钱: 15 两
威望: 3 点
综合积分: 6 分
注册时间: 2009-12-08
最后登录: 2017-09-13
9楼  发表于: 2009-12-24 21:03
学习一下
  • «
  • 1
  • 2
  • »
  • Pages: 1/2     Go