• 【转】s3c2440 按键驱动 — 字符设备


    原文网址:http://www.xuebuyuan.com/632893.html

    主机:VM - redhat 9.0

    开发板:FL2440,linux-2.6.12

    arm-linux-gcc:3.4.1

    (1)原理图上的按键模块,可以看到相应的GPIO口,以及中断号。

    由图可以得知GPF0等接高电平,当按键按下,则接低电平,所以将中断响应设置为下降沿触发。

    (2)驱动程序gzliu_2440_key.c,实现为一般的字符设备驱动,完整的源码如下,其中:

    // 定时器的使用参考:http://blog.csdn.net/gzliu_hit/article/details/6691355

    // s3c2410_gpio_cfgpin系列函数参考:http://blog.csdn.net/gzliu_hit/article/details/6689182

    // request_irq()参考:http://blog.csdn.net/gzliu_hit/article/details/6688929

                                          
    http://blog.csdn.net/gzliu_hit/article/details/6688816

    // cdev_init()系列函数参考:http://blog.csdn.net/gzliu_hit/article/details/6688684

    #include <linux/errno.h>
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/input.h>
    #include <linux/init.h>
    #include <linux/interrupt.h>
    #include <asm/io.h>
    #include <linux/cdev.h>
    #include <asm/uaccess.h>
    
    #include <asm/arch/regs-gpio.h>
    
    #define DEV_NAME		 "s3c2440-key"
    #define DEV_MAJOR                 250              // 主设备号
    #define MAX_KEY_BUF               16      // 按键缓冲区大小
    #define KEY_NUM                   4
    #define GZLIU_KEY_DOWN_X          0          // 按键按下,不确定是否是干扰
    #define GZLIU_KEY_DOWN            1            // 按键按下
    #define GZLIU_KEY_UP              2                   // 按键抬起
    #define KEY_TIMER_DELAY_1        (HZ/50)              // 按键按下延迟20ms
    #define KEY_TIMER_DELAY_2        (HZ/10)              // 按键抬起之前延迟100ms
    
    typedef unsigned char KEY_RET;
    
    // 设备结构体
    static struct key_dev
    {
        unsigned int key_status[KEY_NUM];   // 4个按键的状态
        KEY_RET buf[MAX_KEY_BUF];             // 按键缓冲区
        unsigned int head, tail;                     // 按键缓冲区头,尾
        wait_queue_head_t wq;                    // 等待队列
        struct cdev cdev;                               // cdev结构体
    }dev;
    
    static struct timer_list key_timer[KEY_NUM];    // 4个按键的去抖定时器
    
    // 按键硬件资源、键值信息结构体
    struct key_info
    {
        int irq;                           // 中断号
        unsigned int port;      // GPIO端口
        int port_type;                       // GPIO端口配置
        char *key;                         // 键名
    };
    
    // 中断、GPIO宏定义与s3c2410一样,来自头文件:
    // include/asm-arm/arch-s3c2410/regs-gpio.h
    // include/asm-arm/arch-s3c2410/irqs.h
    static struct key_info key_info_tab[KEY_NUM] = 
    {
        { IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, "KEY_1" }, 
        { IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, "KEY_2" },
        { IRQ_EINT3, S3C2410_GPF3, S3C2410_GPF3_EINT3, "KEY_3" },
        { IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, "KEY_4" },
    };
    
    // 中断处理程序
    static irqreturn_t s3c2440_key_irq(int irq, void *dev_id)
    {
        int key = (int)dev_id;
        
        // 关中断,转入查询模式
        // 每次按键只产生一次中断
        disable_irq(key_info_tab[key].irq);
        
        dev.key_status[key] = GZLIU_KEY_DOWN_X;       // 状态为按下
        key_timer[key].expires = jiffies + KEY_TIMER_DELAY_1;    // 延迟
        add_timer(&key_timer[key]);       // 启动定时器
        
        return IRQ_HANDLED;
    
    }/* s3c2440_key_irq() */
    
    // 申请系统中断,中断方式为下降沿触发
    static int request_irqs(void)
    {
        int i, ret;
        
        for (i=0; i<KEY_NUM; i++)
        {
            // 设置4个GPIO口为中断触发方式
            s3c2410_gpio_cfgpin(key_info_tab[i].port, key_info_tab[i].port_type);
            
            // 申请中断,快速中断,设置为下降沿触发
            // 将按键序号作为参数传入中断服务程序
            ret = request_irq(key_info_tab[i].irq, (void *)s3c2440_key_irq, SA_INTERRUPT | IRQT_FALLING, key_info_tab[i].key, (void *)i);
            if (ret)
            {
                return i;
            }
        }
        return 0;
        
    }/* request_irqs() */
    
    // 释放中断
    static void free_irqs(void)
    {
        int i;
        for (i=0; i<KEY_NUM; i++)
        {
            disable_irq(key_info_tab[i].irq);
            free_irq(key_info_tab[i].irq, (void *)i);
        }
    }
    
    // 定时器处理函数
    static void s3c2440_key_timer(unsigned long data)
    {
        int key = data;
        
        int status = s3c2410_gpio_getpin(key_info_tab[key].port);
        if (!status)      // 按键为按下状态
        {
            if (dev.key_status[key] == GZLIU_KEY_DOWN_X)     // 从中断进入
            {
                dev.key_status[key] = GZLIU_KEY_DOWN;
                dev.buf[dev.tail] = (KEY_RET)key;
                dev.tail = (dev.tail + 1) % MAX_KEY_BUF;
                wake_up_interruptible(&dev.wq);               // 唤醒等待队列
            }
            // 延迟更长的时间,等待按键抬起
            key_timer[key].expires = jiffies + KEY_TIMER_DELAY_2;
            add_timer(&key_timer[key]);
        }
        else    // 按键已经抬起
        {
            dev.key_status[key] = GZLIU_KEY_UP;
            enable_irq(key_info_tab[key].irq);                 // 按键抬起,使能中断
        }
        
    }/* s3c2440_key_timer */
    
    static int s3c2440_key_open(struct inode *inode, struct file *filp)
    {
        int i, ret;
        
        dev.head = dev.tail = 0;
        for (i=0; i<KEY_NUM; i++)
        {
            // 初始化按键状态为抬起
            dev.key_status[i] = GZLIU_KEY_UP;
            
            // 初始化定时器        
            init_timer(&key_timer[i]);
    
            key_timer[i].data = i;         // 把按键序号作为参数传入定时器处理函数
            key_timer[i].function = s3c2440_key_timer;   // 定时器相应函数
        }
        init_waitqueue_head(&(dev.wq));    // 初始化等待队列
        
        // 申请中断
        ret = request_irqs();
        if (ret > 0)                 // 如果申请失败,释放已经申请的中断
        {
        	printk("request_irqs() failed, line: %d
    ", __LINE__);
            for (i=ret; i>=0; i--)
            {
                disable_irq(key_info_tab[i].irq);
                free_irq(key_info_tab[i].irq, (void *)i);
            }
            return -EBUSY;
        }
        return 0;
        
    }/* s3c2440_key_open() */
    
    static ssize_t s3c2440_key_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)
    {
        int ret;
        unsigned int size;
    
        if (dev.head == dev.tail)         // 没有按键被按下
        {
            if (filp->f_flags & O_NONBLOCK)      // 如果应用程序采用非阻塞方式,则返回错误
            {
                return -EAGAIN;
            }
            else    // 进入阻塞模式,使应用程序休眠
            {
                wait_event_interruptible(dev.wq, dev.head != dev.tail);
            }
        }
        size = (dev.tail - dev.head) % MAX_KEY_BUF;
        ret = copy_to_user(buf, (dev.buf+dev.head), size);
        dev.head = dev.tail;
        if (ret)
        {
            return ret;
        }
        return size;
        
    }/* s3c2440_key_read() */
    
    static int s3c2440_key_release(struct inode *inode, struct file *filp)
    {
        int i;
        for (i=0; i<KEY_NUM; i++)
        {
            del_timer(&key_timer[i]);
        }
        free_irqs();
        return 0;
    }
    
    static struct file_operations s3c2440_key_fops = 
    {
    	.owner		=THIS_MODULE,
    	.open		=s3c2440_key_open,
    	.read			=s3c2440_key_read,
    	.release		=s3c2440_key_release,
    };
    
    // s3c2440-key驱动模块加载函数
    static int __init s3c2440_key_init(void)
    {
        int ret;
        dev_t devno = MKDEV(DEV_MAJOR, 0);
        
        // 申请字符设备驱动区域
        // 已知主设备号,若未知,则动态申请alloc_chrdev_region()
        ret = register_chrdev_region(devno, 1, DEV_NAME);
        if (ret < 0)
        {
            printk(DEV_NAME " register failed, line: %d
    ", __LINE__);
            printk("ret: %d
    ", ret);
            return ret;
        }
        
        // 初始化并添加cdev结构体
        cdev_init(&dev.cdev, &s3c2440_key_fops);
        dev.cdev.owner = THIS_MODULE;
        
        ret = cdev_add(&dev.cdev, devno, 1);
        if (ret)
        {
            printk("error %d when adding dev
    ", ret);
        }
        
        return 0;
    }
    
    // s3c2440-key驱动模块卸载函数
    static void __exit s3c2440_key_exit(void)
    {
        cdev_del(&dev.cdev);        // 删除cdev结构体
        unregister_chrdev_region(MKDEV(DEV_MAJOR, 0), 1);      // 注销设备区域
    }
    
    module_init(s3c2440_key_init);
    module_exit(s3c2440_key_exit);
    
    MODULE_AUTHOR("gzliu <gzliu@qq.com>");
    MODULE_DESCRIPTION("s3c2440 key driver");
    MODULE_LICENSE("GPL");
    

    (3)编译驱动模块的Makefile:

        # Makefile 2.6  
          
    ifneq ($(KERNELRELEASE),)
    obj-m:=gzliu_2440_key.o
    else
    PWD:=$(shell pwd)
    #KDIR:=/lib/modules/$(shell uname -r)/build 
    KDIR:=/root/linux-2.6.12
          
    all:
    	$(MAKE) -C $(KDIR) M=$(PWD) 
    clean:
    	rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions
    endif
    

    (4)应用层测试程序key_test.c:

    #include <stdio.h>
    #include <fcntl.h>
    
    #define MAX_KEY_BUF           16      // 按键缓冲区大小
    
    int main()
    {
    	unsigned char buf[MAX_KEY_BUF];
    	int fd_key;
    	int ret, i;
    	
    	fd_key = open("/dev/gzliu_2440_key", O_RDONLY);
    	if (fd_key == -1)
    	{
    		printf("open(fd_key) failed
    ");
    		return -1;
    	}
    	
    	while (1)
    	{
    		// 阻塞模式, 没有按键按下时, 进程会被阻塞
    		ret = read(fd_key, buf, MAX_KEY_BUF);
    	
    		for (i=0; i<ret; i++)
    		{
    			printf("Key %d is down
    ", buf[i]);
    		}
    	}
    	
    	return 0;
    }
    

    (5)通过串口将程序发送到开发板的文件系统中,在FL2440开发板上的测试结果:

    也可以实现为混杂设备驱动,就能自动创建设备节点。

    按键驱动作为混杂设备的实现:http://blog.csdn.net/gzliu_hit/article/details/6697568

  • 相关阅读:
    TCHAR字符串查找&反向查找字符串
    如何判断一个文本文件的编码
    用NETSH WINSOCK RESET命令修复网络
    #define和typedef在windows上的应用
    Visual Studio Code (vscode)编译C++
    win32 Message(MSG)消息处理
    HBRUSH to RGB value
    InvalidateRect和UpdateWindow
    Informatic ETL开发步骤
    【非官方方式】获取Disconf动态更新的配置文件的值
  • 原文地址:https://www.cnblogs.com/wi100sh/p/4875092.html
Copyright © 2020-2023  润新知