嵌入式Linux之我行——2440按键驱动实例开发详解(带去抖动)

来源:互联网 发布:成都游戏美工招聘 编辑:程序博客网 时间:2024/06/04 18:23
嵌入式Linux之我行——按键驱动在2440上的实例开发(带去抖动) 
分类: 内核、驱动开发篇

原文链接:http://blog.chinaunix.net/space.php?uid=22174347&do=blog&cuid=2097608

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正。
  • 共享资源,欢迎转载:http://hbhuanggang.cublog.cn

一、开发环境

  • 主  机:VMWare--Fedora 9
  • 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
  • 编译器:arm-linux-gcc-4.3.2

二、实现步骤

1. 硬件原理图分析。由原理图可知每个按键所用到的外部中断分别是EINT8、EINT11、EINT13、EINT14、EINT15、EINT19,所对应的IO口分别是GPG0、GPG3、GPG5、GPG6、GPG7、GPG11。再由按键的接口电路可知,当按键按下时按键接通,中断线上原有的VDD33V高电平被拉低,从而触发中断的产生。

 
2. 开始编写合适mini2440的按键驱动(含去抖动功能),文件名:my2440_buttons.c

1)按键驱动基本框架。这里我就指定主设备号为232,简单的注册为字符设备,另定义了一个结构体把按键要用到的资源组织起来

#include<linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>

#include<mach/regs-gpio.h>

#include<mach/hardware.h>

#define DEVICE_NAME   "my2440_buttons" 
//设备名称
#define DEVICE_MAJOR   232             
//主设备号

//组织硬件资源结构体
struct button_irq_desc 
{
    int irq;        
//中断号
    int pin;        
//对应的IO引脚
    int pin_setting;
//引脚配置
    char *name;     
//按键名称,注意这个名称,在后面的一个现象中会出现
};

//定义6个按键资源结构体数组
static struct button_irq_desc button_irqs[]= 
{
    {IRQ_EINT8 , S3C2410_GPG0, S3C2410_GPG0_EINT8  , "KEY0"},
    {IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG3_EINT11 , "KEY1"},
    {IRQ_EINT13, S3C2410_GPG5, S3C2410_GPG5_EINT13 , "KEY2"},
    {IRQ_EINT14, S3C2410_GPG6, S3C2410_GPG6_EINT14 , "KEY3"},
    {IRQ_EINT15, S3C2410_GPG7, S3C2410_GPG7_EINT15 , "KEY4"},
    {IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG11_EINT19,"KEY5"},
};

static int __init button_init(void)
{
    int ret;

    
//注册字符设备
    ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME,&buttons_fops);

    if(ret < 0)
    {
        printk(DEVICE_NAME " register faild!\n");
        return ret;
    }

    return 0;
}

static void __exit button_exit(void)
{
    
//注销字符设备
    unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 button driver");

2)设备注册时用到的设备操作结构体buttons_fops的定义。这里由于按键是输入的设备,所以这里只有read,没有write,另因在应用程序中要实时监测哪个按键被按下,故这里就用poll在内核中遍历,来提供给应用中的select遍历判断资源是否可获取,可获取才来read

#include<linux/poll.h>   //poll要用到的头文件

 

//设备操作列表
static struct file_operations buttons_fops= 
{
    .owner        = THIS_MODULE,
    .open        = buttons_open,
    .release      = buttons_close,
    .read        = buttons_read,
    .poll         = buttons_poll,
};


3)设备操作结构体中open的实现。 在open中分别实现了IO口的配置、中断触发的方式、申请中断、初始化6个按键的初始状态和初始化6个按键去抖动定时器。中断服务程序为buttons_interrupt,传过去的参数是当前的中断号和索引;定时器服务程序为buttons_timer,传过去的参数是当前定时器的索引。注意:这里有一个关键字volatile,为什么要用这个关键字呢?请看这里:http://blog.chinaunix.net/u1/41845/showart_2038284.html

//中断要用到的头文件

#include<linux/interrupt.h>
#include<linux/irq.h>
#include<asm/irq.h>

 

#define KEY_DOWN            0   //按键按下                    
#define KEY_UP              1   
//按键抬起                
#define KEY_UNCERTAIN       2   
//按键不确定                    
#define KEY_COUNT           6  //6个按键

 

static volatile intkey_status[KEY_COUNT];     //记录6个按键的状态 
static struct timer_list key_timers[KEY_COUNT]; //6个按键去抖动定时器

 

staticint buttons_open(struct inode*inode, struct file *file)
{
    int i;
    int ret;

    for(= 0; i < KEY_COUNT; i++)
    {
        //设置6个IO口为中断触发方式
        s3c2410_gpio_cfgpin(button_irqs[i].pin, button_irqs[i].pin_setting);

        //设置中断下降沿为有效触发
        set_irq_type(button_irqs[i].irq, IRQ_TYPE_EDGE_FALLING);
        
        //申请中断(类型为快速中断,中断服务时屏蔽所有外部中断?)
        ret = request_irq(button_irqs[i].irq, buttons_interrupt, IRQF_DISABLED,button_irqs[i].name,(void *)i);

        if(ret)
        {
            break;
        }

        //初始化6个按键的状态为抬起
        key_status[i]= KEY_UP;

        //初始化并设置6个去抖定时器
        setup_timer(&key_timers[i], buttons_timer, i);
    }

    if(ret)
    {
        //中断申请失败处理
        i--;

        for(; i>= 0; i--)
        {
            //释放已注册成功的中断
            disable_irq(button_irqs[i].irq);
            free_irq(button_irqs[i].irq,(void *)i);
        }

        return -EBUSY;
    }

    return 0;
}


4)中断服务程序和去抖动定时器服务程序的实现。这里的中断服务和定时器服务互相的作用,首先中断触发后启动延时定时器,进入定时器服务后处理按键的状态,最后当前按键抬起后,中断服务又开始处理新的中断

#define KEY_TIMER_DELAY1  (HZ/50)      //按键按下去抖延时20毫秒        
#define KEY_TIMER_DELAY2 (HZ/10)      //按键抬起去抖延时100毫秒

 

staticvolatileint ev_press = 0;      //按键按下产生标识,用于在读设备的时候来判断是否有数据可读,否则进程睡眠

static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //等待队列的定义并初始化

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
    //获取当前按键资源的索引
    int key =(int)dev_id;

    //判断当前按键的状态已经抬起后才服务中断
    if(key_status[key]== KEY_UP)
    {
        //设置当前按键的状态为不确定
        key_status[key]= KEY_UNCERTAIN;

        //设置当前按键按下去抖定时器的延时并启动定时器
        key_timers[key].expires= jiffies + KEY_TIMER_DELAY1;
        add_timer(&key_timers[key]);
    }

    return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer(unsignedlong arg)
{
    //获取当前按键资源的索引
    int key =arg;

    //获取当前按键引脚上的电平值来判断按键是按下还是抬起
    int up = s3c2410_gpio_getpin(button_irqs[key].pin);

    if(!up)//低电平,按键按下
    {
        if(key_status[key]== KEY_UNCERTAIN)
        {
            //标识当前按键状态为按下
            key_status[key]= KEY_DOWN;

            //标识当前按键已按下并唤醒等待队列让设备进行读取
            ev_press = 1;

              wake_up_interruptible(&button_waitq);
        }

        
//设置当前按键抬起去抖定时器的延时并启动定时器
        key_timers[key].expires= jiffies + KEY_TIMER_DELAY2;
        add_timer(&key_timers[key]);
    }
    else
//高电平,按键抬起
    {
        
//标识当前按键状态为抬起
        key_status[key]= KEY_UP;
    }
}

5)读设备的实现。从电路图可以看出按键设备相对于CPU来说为输入设备,所以这里只有read,而没有write

staticint buttons_read(structfile *file,char __user *buf,size_t count,loff_t*offp)
{
    unsigned long ret;

    if(!ev_press)//按键按下发生标识,0没有发生

    {
        if(file->f_flags& O_NONBLOCK)
        {
            //应用程序若采用非阻塞方式读取则返回错误
            return -EAGAIN;
        }
        else
        {
            //以阻塞方式读取且按键没按下产生,让等待队列进入睡眠
            wait_event_interruptible(button_waitq, ev_press);
        }
    }

    //1为按键按下产生,并清除标识为0,准备给下一次判断用
    ev_press = 0;

    //将内核中的按键状态数据拷贝到用户空间给应用程序使用
    ret = copy_to_user(buf,(void *)key_status,min(sizeof(key_status),count));

    return ret ?-EFAULT : min(sizeof(key_status),count);
}


6)驱动中的轮询。这个与应用程序中的select的使用相对应
 

//驱动程序中的轮询,用于应用程序中的轮询查询是否可对设备进行访问
static int buttons_poll(structfile *file,struct poll_table_struct *wait)
{
    unsigned int mask= 0;

    //添加等待队列到等待队列表中(poll_table)
    poll_wait(file,&button_waitq, wait);

    if(ev_press)
    {
        //标识数据可以获得
        mask |= POLLIN| POLLRDNORM;
    }

    return mask;
}


7)设备的关闭。
 

staticint buttons_close(struct inode*inode, struct file *file)
{
    int i;

    //释放6个定时器和中断
    for(= 0; i < KEY_COUNT; i++)
    {
        del_timer(&key_timers[i]);

        disable_irq(button_irqs[i].irq);
        free_irq(button_irqs[i].irq,(void *)i);
    }

    return 0;
}


3. 完整的按键驱动代码

/*
 ===================================================
 Name            : my2440_buttons.c
 Author          : Huang Gang
 Date            : 09/11/2009
 Copyright       : GPL
 Description     : my2440 buttons driver
 ===================================================
 */


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/poll.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>

#define DEVICE_NAME         "my2440_buttons"    //设备名称
#define DEVICE_MAJOR        232                 //主设备号                
#define KEY_TIMER_DELAY1    (HZ/50)             //按键按下去抖延时20毫秒        
#define KEY_TIMER_DELAY2    (HZ/10)             //按键抬起去抖延时100毫秒
#define KEY_DOWN            0                   //按键按下                    
#define KEY_UP              1                   //按键抬起                
#define KEY_UNCERTAIN       2                   //按键不确定                    
#define KEY_COUNT           6                   //6个按键                    

static volatileint ev_press = 0;                //按键按下产生标志
static volatileint key_status[KEY_COUNT];       //记录6个按键的状态    
static struct timer_list key_timers[KEY_COUNT];  //定义6个按键去抖动定时器
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);    //定义并初始化等待队列

//组织硬件资源结构体
struct button_irq_desc    
{
    int irq;            //中断号
    int pin;            //引脚
    int pin_setting;    //引脚配置
    char *name;         //按键名称,注意这个名称,在后面的一个现象中会出现
};

//定义6个按键资源结构体数组
static struct button_irq_desc button_irqs[]= 
{
    {IRQ_EINT8 , S3C2410_GPG0, S3C2410_GPG0_EINT8 , "KEY0"},
    {IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG3_EINT11 , "KEY1"},
    {IRQ_EINT13, S3C2410_GPG5, S3C2410_GPG5_EINT13 , "KEY2"},
    {IRQ_EINT14, S3C2410_GPG6, S3C2410_GPG6_EINT14 , "KEY3"},
    {IRQ_EINT15, S3C2410_GPG7, S3C2410_GPG7_EINT15 , "KEY4"},
    {IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG11_EINT19,"KEY5"},
};

static irqreturn_t buttons_interrupt(int irq,void *dev_id)
{
    //获取当前按键资源的索引
    int key =(int)dev_id;

    if(key_status[key]== KEY_UP)
    {
        //设置当前按键的状态为不确定
        key_status[key]= KEY_UNCERTAIN;

        //设置当前按键按下去抖定时器的延时并启动定时器
        key_timers[key].expires= jiffies + KEY_TIMER_DELAY1;
        add_timer(&key_timers[key]);
    }

    return IRQ_RETVAL(IRQ_HANDLED);
}

static void buttons_timer(unsignedlong arg)
{
    //获取当前按键资源的索引
    int key =arg;

    //获取当前按键引脚上的电平值来判断按键是按下还是抬起
    int up = s3c2410_gpio_getpin(button_irqs[key].pin);

    if(!up)//低电平,按键按下
    {
        if(key_status[key]== KEY_UNCERTAIN)
        {
            //标识当前按键状态为按下
            key_status[key]= KEY_DOWN;

            //标识当前按键已按下并唤醒等待队列
            ev_press = 1;
            wake_up_interruptible(&button_waitq);
        }

        //设置当前按键抬起去抖定时器的延时并启动定时器
        key_timers[key].expires= jiffies + KEY_TIMER_DELAY2;
        add_timer(&key_timers[key]);
    }
    else//高电平,按键抬起
    {
        //标识当前按键状态为抬起
        key_status[key]= KEY_UP;
    }
}

static int buttons_open(struct inode*inode, struct file *file)
{
    int i;
    int ret;

    for(= 0; i < KEY_COUNT; i++)
    {
        //设置6个IO口为中断触发方式
        s3c2410_gpio_cfgpin(button_irqs[i].pin, button_irqs[i].pin_setting);

        //设置中断下降沿为有效触发
        set_irq_type(button_irqs[i].irq, IRQ_TYPE_EDGE_FALLING);
        
        //申请中断(类型为快速中断,中断服务时屏蔽所有外部中断?)
        ret = request_irq(button_irqs[i].irq, buttons_interrupt, IRQF_DISABLED,button_irqs[i].name,(void *)i);

        if(ret)
        {
            break;
        }

        //初始化6个按键的状态为抬起
        key_status[i]= KEY_UP;

        //初始化并设置6个去抖定时器
        setup_timer(&key_timers[i], buttons_timer, i);
    }

    if(ret)
    {
        //中断申请失败处理
        i--;

        for(; i>= 0; i--)
        {
            //释放已注册成功的中断
            disable_irq(button_irqs[i].irq);
            free_irq(button_irqs[i].irq,(void *)i);
        }

        return -EBUSY;
    }

    return 0;
}

static int buttons_close(struct inode*inode, struct file *file)
{
    int i;

    //释放6个定时器和中断
    for(= 0; i < KEY_COUNT; i++)
    {
        del_timer(&key_timers[i]);

        disable_irq(button_irqs[i].irq);
        free_irq(button_irqs[i].irq,(void *)i);
    }

    return 0;
}

static int buttons_read(structfile *file,char __user *buf,size_t count,loff_t*offp)
{
    unsigned long ret;

    if(!ev_press)//判断按键按下产生标识,0没有产生

    {
        if(file->f_flags& O_NONBLOCK)
        {
            //应用程序若采用非阻塞方式读取则返回错误

            return -EAGAIN;
        }
        else
        {
            //以阻塞方式读取且按键按下没有产生,让等待队列进入睡眠
            wait_event_interruptible(button_waitq, ev_press);
        }
    }

    //1为按键按下产生,并清除标识为0,准备给下一次判断用
    ev_press = 0;

    //将内核中的按键状态数据拷贝到用户空间给应用程序使用
    ret = copy_to_user(buf,(void *)key_status,min(sizeof(key_status),count));

    return ret ?-EFAULT : min(sizeof(key_status),count);
}

//驱动程序中的轮询,用于应用程序中的轮询查询是否可对设备进行访问
static int buttons_poll(structfile *file,struct poll_table_struct *wait)
{
    unsigned int mask= 0;

    //添加等待队列到等待队列表中(poll_table)
    poll_wait(file,&button_waitq, wait);

    if(ev_press)
    {
        //标识数据可以获得
        mask |= POLLIN| POLLRDNORM;
    }

    return mask;
}

//设备操作列表
static struct file_operations buttons_fops= 
{
    .owner        = THIS_MODULE,
    .open        = buttons_open,
    .release      = buttons_close,
    .read        = buttons_read,
    .poll         = buttons_poll,
};

static int __init button_init(void)
{
    int ret;

    //注册字符设备
    ret = register_chrdev(DEVICE_MAJOR, DEVICE_NAME,&buttons_fops);

    if(ret < 0)
    {
        printk(DEVICE_NAME " register faild!\n");
        return ret;
    }

    return 0;
}

static void __exit button_exit(void)
{
    //注销字符设备
    unregister_chrdev(DEVICE_MAJOR, DEVICE_NAME);
}

module_init(button_init);
module_exit(button_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huang Gang");
MODULE_DESCRIPTION("My2440 button driver");


4.将按键驱动代码部署到内核中。
 

#cp-f my2440_buttons.c/linux-2.6.30.4/drivers/char//把驱动源码复制到内核驱动的字符设备下


#gedit/linux-2.6.30.4/drivers/char/Kconfig//添加按键设备配置

config MY2440_BUTTONS
    tristate "My2440 Buttons Device"
    depends on ARCH_S3C2440
    default y
    ---help---
      My2440 User Buttons


#gedit/linux-2.6.30.4/drivers/char/Makefile//添加按键设备配置

obj-$(CONFIG_MY2440_BUTTONS)+= my2440_buttons.o


5.配置内核,选择按键设备选项

#make menuconfig

Device Drivers--->
    Character devices --->
        <*> My2440 Buttons Device(NEW)


6. 编译内核并下载到开发板上,查看已加载的设备:#cat /proc/devices,可以看到my2440_buttons的主设备号为232

7.编写应用程序测试按键驱动,文件名:buttons_test.c

#include<stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc,char **argv)
{
    int fd;
    int key_status[6];

    //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
    fd = open("/dev/my2440_buttons", 0);

    if(fd < 0)
    {
        printf("Open Buttons Device Faild!\n");
        exit(1);
    }

    while(1)
    {
        int i;
        int ret;
        fd_set rds;
        
        FD_ZERO(&rds);
        FD_SET(fd,&rds);
        
        //应用程序进行轮询,查询是否可对设备进行访问
        ret = select(fd+ 1, &rds, NULL,NULL, NULL);
        
        if(ret< 0)
        {
            printf("Read Buttons Device Faild!\n");
            exit(1);
        }
        
        if(ret== 0)
        {
            printf("Read Buttons Device Timeout!\n");
        }
        else if(FD_ISSET(fd,&rds))
        {
            //读设备
            ret = read(fd, key_status,sizeof(key_status));

            if(ret!= sizeof(key_status))
            {
                if(errno!= EAGAIN)
                {
                    printf("Read Button Device Faild!\n");
                }

                continue;
            }
            else
            {
                for(i= 0; i < 6; i++)
                {
                    //对应驱动中按键的状态,为0即按键被按下
                    if(key_status[i]== 0)
                    {
                        printf("Key%d DOWN\n", i+ 1);
                    }
                }
            }
        }
    }

    close(fd);

    return 0;
}


8.在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上

#arm-linux-gcc-o buttons_test buttons_test.c


9. 在开发板上的文件系统中创建一个按键设备的节点,然后运行测试程序,效果图如下,观测按开发板上的按键时,在串口工具中会输出对应按键被按下的信息,也不会出现抖动现象(即按某个按键时,不会多次产生该按键按下的情况)

三、补充问题

1.当我们启动开发板后,按键驱动就会被自动加载,这个时候,我们执行#cat /proc/interrupts命令查看系统当前使用的中断情况,没有发现有按键的中断,这是为什么?看看我们的驱动代码就知道了,原来,按键驱动中的中断申请是在设备打开里面,这个时候设备只加载了还没有打开,所以这里还没有

2.修改驱动代码,把中断的申请放到设备初始化加载里面(即将open中所有的代码移到button_init中),再来看看系统中断使用的情况,按键的中断就出现了。52、55、57、58、59、63分别为6个按键的中断号,KEY0~KEY5按键名称就是驱动中提到注意的地方(注意这个名称,在后面的一个现象中会出现),就是在这里出现了

 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 天猫盒子没声音怎么办 天猫魔盒播放声音很低怎么办 天猫魔盒3a卡顿怎么办 天猫网络机顶盒打不开了怎么办 天猫机顶盒遥控器丢了怎么办 天猫机顶盒没有遥控器怎么办 天猫机顶盒很卡怎么办 天猫机顶盒没遥控器怎么办 天猫机顶盒看不了怎么办 天猫机顶盒变黑白怎么办 天猫精灵丢了怎么办 咪咕盒子没信号怎么办 猫los灯亮了怎么办 网络猫los闪红灯怎么办 台式电脑二级网页打不开.怎么办 光纤猫los红闪怎么办 系统管理员账户密码被更改怎么办 花呗不能使用了怎么办 开通余额宝提示身份验证失败怎么办 蚂蚁花呗刷脸认证老失败怎么办 火狐浏览器登录系统后打不开怎么办 花呗自动扣费怎么办 彪马运动裤买大了怎么办 淘宝卖家认证无法通过怎么办 淘宝开店申请未认证该怎么办 传照片到淘宝看不到照片怎么办 淘宝店铺秒出复核怎么办 钱盾身份认证一直不通过怎么办 淘宝开店一直不让认证通过怎么办 淘宝店铺不卖东西怎么办 企业误进虚空的增值税发票怎么办 唯品会商品有的不支持退换货怎么办 苹果手机和平板电脑共享怎么办 ipad被锁定了停用了怎么办 word文档被锁定不能编辑怎么办 苹果平板id忘了怎么办 ipad有id锁怎么办换主板 ipad刷机后忘记id密码怎么办 网购买东西手机号错了怎么办? 淘宝卖家虚假交易违规怎么办 好朋友问我借身份证开网店怎么办