Chinaunix首页 | 论坛 | 博客
  • 博客访问: 350317
  • 博文数量: 159
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 182
  • 用 户 组: 普通用户
  • 注册时间: 2013-11-02 10:42
文章分类

全部博文(159)

文章存档

2015年(18)

2014年(132)

2013年(9)

分类: LINUX

2014-05-28 18:31:09

经过为期一周左右的时间,参考《linux设备驱动开发详解》把mini2440开发板的按键驱动完成了。
 

程序可以分成两部分来看:

第一部分:按键侦测,主要包括中断的初始化、中断处理、按键去抖、等待按键松开,侦测

完成返回按键键值。

第二部分:按键事件处理,主要包括,将案件事件保存至循环链表(其实就是一个数组,可

以循环保存数据)、将案件事件反馈给应用程序(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()很明白啦…^_^

 

完整驱动代码:

 

//********************************************//

//                     书写规范                 //

//结构体定义:一律大写字母,中间可用"_"区分     //

//全局变量 :全部用小写字母,加前缀"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[i].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[i] = KEYSTATUS_UP;
    }

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


    for(i=0; i<KEY_NUM; i++)
    {
    //    setup_timer(&g_tkey_timer[i], key_timer_handler,i);

        g_tkey_timer[i].function = key_timer_handler;
        g_tkey_timer[i].data = i;
        init_timer(&g_tkey_timer[i]);
    }

    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);

完整测试代码:

#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;
}


阅读(404) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~