原文网址: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\n", __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\n", __LINE__);
printk("ret: %d\n", 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\n", 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\n");
return -1;
} while (1)
{
// 阻塞模式, 没有按键按下时, 进程会被阻塞
ret = read(fd_key, buf, MAX_KEY_BUF); for (i=0; i<ret; i++)
{
printf("Key %d is down\n", buf[i]);
}
} return 0;
}

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

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

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

【转】s3c2440 按键驱动 — 字符设备的更多相关文章

  1. 【驱动】linux设备驱动·字符设备驱动开发

    Preface 前面对linux设备驱动的相应知识点进行了总结,现在进入实践阶段! <linux设备驱动入门篇>:http://infohacker.blog.51cto.com/6751 ...

  2. linux驱动---字符设备的注册register_chrdev说起

    首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&memory_fops),向内核注册了一个字符设备. 第一个参数是主设备号,0 ...

  3. 字符设备驱动、平台设备驱动、设备驱动模型、sysfs的比较和关联

    转载自:http://www.kancloud.cn/yueqian_scut/emlinux/106829 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动.设备驱动模型和sy ...

  4. [kernel]字符设备驱动、平台设备驱动、设备驱动模型、sysfs几者之间的比较和关联

    转自:http://www.2cto.com/kf/201510/444943.html Linux驱动开发经验总结,绝对干货! 学习Linux设备驱动开发的过程中自然会遇到字符设备驱动.平台设备驱动 ...

  5. Linux字符设备简单示例

    1. Linux字符设备是一种按字节来访问的设备,字符驱动则负责驱动字符设备,这样的驱动通常实现open.close.read和write系统调用.例如:串口.Led.按键等. 2. 通过字符设备文件 ...

  6. 字符设备驱动(六)按键poll机制

    title: 字符设备驱动(六)按键poll机制 tags: linux date: 2018-11-23 18:57:40 toc: true --- 字符设备驱动(六)按键poll机制 引入 在字 ...

  7. Linux嵌入式学习-烟雾传感器驱动-字符设备驱动-按键驱动

    MQ-2烟雾气敏传感器模块在X210v3开发板上的驱动. 现在需要一个MQ-2烟雾气敏传感器模块的驱动.其检测烟雾超过一定的标准后,会返回一个不同的电平,和按键驱动差不多. 但是在编写驱动的时候,需要 ...

  8. 深入浅出:Linux设备驱动之字符设备驱

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面向流 ...

  9. Tiny6410 LED字符设备驱动

    1.查看用户手册 led1.led2.led3.led4 连接的分别是 GPK4.GPK5.GPK6.GPK7 2.查询6410芯片手册 下面还需要3个步骤: 1.设置GPIO为OUTPUT. 将GP ...

随机推荐

  1. Facade外观模式 笔记

    Facede模式: 把内部系统复杂隐藏,提供一个方便统一的接口. 微波炉在界面简单操作下就可以烹饪出美味佳肴, 微波炉内部运作原理,各个组件互相交互运作,使用者并不需要关心.  而且关心的话可能没有多 ...

  2. yii2使用Gii生成代码

    本章节将介绍怎样使用 Gii 去自己主动生成 Web 网站经常使用功能的代码.使用 Gii 生成代码很easy,仅仅要依照 Gii 页面上的介绍输入正确的信息就可以. 贯穿本章节,你将会学到: 在你的 ...

  3. c# linq的一些运用

    最近在学习xml.linq 网上也找了一些资料都不大全面,因此在这写了一点东西和大家分享,由于本人知识有限,如有错误请指证 可扩展标记语言,标准通用标记语言的子集,一种用于标记电子文件使其具有结构性的 ...

  4. C#中将图片文件转化为二进制数组-用于数据库存储

    在项目开发中,使用SQL Server存储数据,数据类型image可以保存图片.但是在存储之前需要将图片转化为二进制数组的形式进行赋值. 将图片文件转换为二进制数组 /// <summary&g ...

  5. HTTP调试 抓包 工具 Fiddle 简介 示例

    简介 1.常用抓包工具对比: Firebug虽然可以抓包,但是对于分析http请求的详细信息,不够强大.模拟http请求的功能也不够,且firebug常常是需要"无刷新修改",如果 ...

  6. mongodb的tailCursor的设计思想

    http://derickrethans.nl/mongodb-and-solr.html 这是mongodb的php客户端的写法

  7. FileUpload 简单上传+小预览

    页面代码 : <form id="form1" runat="server"> <div> <asp:FileUpload ID= ...

  8. BestCoder Round 59 (HDOJ 5500) Reorder the Books

    Problem Description dxy has a collection of a series of books called “The Stories of SDOI”,There are ...

  9. Swift - 35 - 使用闭包简化语法

    //: Playground - noun: a place where people can play import UIKit // 初始化一个整数数组 var arr = [1, 3, 5, 7 ...

  10. (一)Angularjs - 入门

    AngularJS进行应用开发的一个重要的思维模式: 从构造声明式界面入手 ng-app: 这个指定定义并且关联了使用angularJS的HTML页面部分 ng-model: 这个指定定义并绑定Ang ...