今天写一下按键驱动,本次并没有用输入子系统,但仍然不适合新手直接学,建议先看一下其他人写的按键驱动,然后再看这个,本博文主要是为了复习一下之前的知识.
硬件平台:tiny4412(Cortex A9);
软件平台:Linux-3.5
本次用按键驱动LED灯,我用的tiny4412开发板上有4个按键,4个LED灯,所以可以灯和按键一一对应,首先先查看原理图,如下:
LED:
对用的引脚分别是GPM4_0, GPM4_1, GPM4_2, GPM4_3,如下图:
我们查阅datesheet:
从上图可以知道LED相关引脚的配置寄存器的地址是:0x110002E0,那么数据寄存器就是0x110002E0 + 4,接下来在找按键的,如下图:
查询到对应的引脚分别是GPX3_2, GPX3_3, GPX3_4, GPX3_5:
这个我们就不查datasheet了,因为三星的寄存器地址都已经封装好了,我们可以在驱动了通过宏直接调用,当然,所有按键的都是封装好了,其实上面的LED的引脚也可以不用查datasheet,不过,为了把知识都用上,就这里还是保留一部分直接操作寄存器的语句.接下来就直接上代码了,代码不是很难,关键部分是有注释的,特别地给中断增加了软件消抖,程序如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPM4COM 0x110002E0
#define KEY_DEV_MAJOR 0
#define LED_Switch1 _IOW('L', 0x1234, int)
#define LED_Switch2 _IOW('L', 0x1235, int)
#define LED_Switch3 _IOW('L', 0x1236, int)
#define LED_Switch4 _IOW('L', 0x1237, int)
volatile unsigned long *led_conf = NULL;
volatile unsigned long *led_data = NULL;
struct key_event{
int code;
int value;
};
// 设备对象属性
struct exynos4412_intkey{
dev_t devno;
struct cdev *cdev;
struct class *cls;
struct device *dev;
int irqno;
struct key_event event;
unsigned char have_data;
wait_queue_head_t wq_head;
};
// 单个按键信息的对象
struct key_desc{
char *name;
int gpio;
int code;
int int_flag; // 中断触发方式
};
struct exynos4412_intkey *key_dev;
// 定义所有按键的信息集合, GPX3_2, 3_3, 3_4, 3_5.
struct key_desc all_keys[] = {
[0] = {
.name = "key1_button",
.gpio = EXYNOS4_GPX3(2),
.code = KEY_UP,
.int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[1] = {
.name = "key2_button",
.gpio = EXYNOS4_GPX3(3),
.code = KEY_DOWN,
.int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[2] = {
.name = "key3_button",
.gpio = EXYNOS4_GPX3(4),
.code = KEY_LEFT,
.int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
[3] = {
.name = "key4_button",
.gpio = EXYNOS4_GPX3(5),
.code = KEY_RIGHT,
.int_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
},
};
int key_open(struct inode *inode, struct file *filp)
{
printk("---------%s---------\n", __func__);
// 演示inode的作用--->获取节点的主次设备号,就可以知道当前操作的是哪个设备
unsigned int major = imajor(inode);
unsigned int minor = iminor(inode);
printk("major : %d, minor: %d\n", major, minor);
// 也可以通过filp获取到inode
printk("filp...major: %d, minor: %d\n",
imajor(filp->f_path.dentry->d_inode),
iminor(filp->f_path.dentry->d_inode));
// 数据的共享
static int a = 155;
filp->private_data = &a;
/**************************以上为增值功能********************************/
// 先对LED的端口进行清0操作
*led_conf &= ~(0xffff);
// 将4个IO口16位都设置为output输出状态
*led_conf |= (0x1111);
// 初始化数据
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->have_data = 0;
return 0;
}
ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret = -1;
// 判断当前是什么模式
if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
return -EAGAIN;
// 没有数据的时候进行休眠
wait_event_interruptible(key_dev->wq_head, key_dev->have_data);
ret = copy_to_user(buf, &key_dev->event, count);
if(ret > 0){
printk("copy to user failed!\n");
return -EFAULT;
}
// 清空数据,以备下次数据来临
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->have_data = 0;
return count;
}
ssize_t key_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
int val = -1,
ret = -1;
ret = copy_from_user(&val, buf, count);
if(ret != 0){
printk("copy from user failed!\n");
return -EINVAL;
}
switch(val)
{
case 0:
printk(KERN_EMERG"led1_on\n");
*led_data &= ~0x1;
break;
case 1:
printk(KERN_EMERG"led2_on\n");
*led_data &= ~(0x1 << 1);
break;
case 2:
printk(KERN_EMERG"led3_on\n");
*led_data &= ~(0x1 << 2);
break;
case 3:
printk(KERN_EMERG"led4_on\n");
*led_data &= ~0x8;
break;
case 4:
printk(KERN_EMERG"ledall_on\n");
*led_data &= ~0xf;
break;
case 5:
printk(KERN_EMERG"ledall_off\n");
*led_data |= 0xf;
break;
default:
break;
}
return ret;
}
long key_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = -1;
switch(cmd)
{
case LED_Switch1:
if(arg){
gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED");
gpio_direction_output(EXYNOS4X12_GPM4(0), 0);
gpio_free(EXYNOS4X12_GPM4(0));
} else {
gpio_request(EXYNOS4X12_GPM4(0), "KEY1_LED");
gpio_direction_output(EXYNOS4X12_GPM4(0), 1);
gpio_free(EXYNOS4X12_GPM4(0));
}
break;
case LED_Switch2:
if(arg){
gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED");
gpio_direction_output(EXYNOS4X12_GPM4(1), 0);
gpio_free(EXYNOS4X12_GPM4(1));
} else {
gpio_request(EXYNOS4X12_GPM4(1), "KEY2_LED");
gpio_direction_output(EXYNOS4X12_GPM4(1), 1);
gpio_free(EXYNOS4X12_GPM4(1));
}
break;
case LED_Switch3:
if(arg){
gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED");
gpio_direction_output(EXYNOS4X12_GPM4(2), 0);
gpio_free(EXYNOS4X12_GPM4(2));
} else {
gpio_request(EXYNOS4X12_GPM4(2), "KEY3_LED");
gpio_direction_output(EXYNOS4X12_GPM4(2), 1);
gpio_free(EXYNOS4X12_GPM4(2));
}
break;
case LED_Switch4:
if(arg){
gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED");
gpio_direction_output(EXYNOS4X12_GPM4(3), 0);
gpio_free(EXYNOS4X12_GPM4(3));
} else {
gpio_request(EXYNOS4X12_GPM4(3), "KEY4_LED");
gpio_direction_output(EXYNOS4X12_GPM4(3), 1);
gpio_free(EXYNOS4X12_GPM4(3));
}
break;
default:
*led_data |= 0xf;
break;
}
return 0;
}
int key_close(struct inode *inode, struct file *filp)
{
printk("---------%s---------\n", __func__);
int *p = (int *)filp->private_data;
printk("close data: %d\n", *p);
*led_data |= 0xf;
return 0;
}
const struct file_operations key_fops = {
// .owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.unlocked_ioctl = key_ioctl,
.release = key_close,
};
irqreturn_t key_irq_handle(int irqno, void *devid)
{
// 区分不同的中断
struct key_desc *p = (struct key_desc *)devid;
udelay(20); // delay 20us stabilization in key pressed
if(gpio_get_value(p->gpio)){ // 弹起
printk(" %s UP!\n", p->name);
key_dev->event.code = p->code;
key_dev->event.value = 0;
} else { // 按下
printk(" %s Down!\n", p->name);
key_dev->event.code = p->code;
key_dev->event.value = 1;
}
// 唤醒队列
wake_up_interruptible(&key_dev->wq_head);
key_dev->have_data = 1; // 设置是有数据
return IRQ_HANDLED;
}
static void __exit intkey_exit(void)
{
int i;
printk("---------%s---------\n", __func__);
for(i = 0; i < ARRAY_SIZE(all_keys); i++)
free_irq(gpio_to_irq(all_keys[i].gpio), &all_keys[i]);
device_destroy(key_dev->cls, key_dev->devno);
cdev_del(key_dev->cdev);
class_destroy(key_dev->cls);
unregister_chrdev_region(key_dev->devno, 1);
kfree(key_dev);
}
static int __init intkey_init(void)
{
int ret = -1;
printk("---------%s---------\n", __func__);
// 1,实例化设备对象
key_dev = kzalloc(sizeof(struct exynos4412_intkey), GFP_KERNEL);
if(NULL == key_dev){
printk("kzalloc failed\n");
return -ENOMEM;
}
// 2,申请设备号
if(KEY_DEV_MAJOR > 0){
// 静态申请
key_dev->devno = MKDEV(KEY_DEV_MAJOR, 0);
ret = register_chrdev_region(key_dev->devno, 1, "key_drv");
} else {
// 动态申请
ret = alloc_chrdev_region(&key_dev->devno, 16, 1, "key_drv");
}
if(ret < 0){
printk("register failed!\n");
ret = -EINVAL;
goto err1;
}
// 3,cdev的操作
// 3.1 分配一个cdev
key_dev->cdev = cdev_alloc();
// 3.2 初始化cdev
cdev_init(key_dev->cdev, &key_fops);
// 3.3 注册一个cdev
cdev_add(key_dev->cdev, key_dev->devno, 1);
// 4,创建设备类
key_dev->cls = class_create(THIS_MODULE, "key_cls");
if(IS_ERR(key_dev->cls)){
printk("class create failed!\n");
ret = PTR_ERR(key_dev);
goto err2;
}
// 5,创建设备节点
key_dev->dev = device_create(key_dev->cls, NULL, key_dev->devno, NULL, "key%d", 1);
if(IS_ERR(key_dev->dev)){
printk("device create failed!\n");
ret = PTR_ERR(key_dev->dev);
goto err3;
}
// 6,硬件初始化
int i = 0;
for(i = 0; i < ARRAY_SIZE(all_keys); i++){
ret = request_irq(gpio_to_irq(all_keys[i].gpio), key_irq_handle, all_keys[i].int_flag,
all_keys[i].name, &all_keys[i]);
if(ret != 0){
printk("request irq failed!\n");
goto err4;
}
}
led_conf = (volatile unsigned long *)ioremap(GPM4COM, 8);
led_data = led_conf + 1;
// 7,初始化一个队列头
init_waitqueue_head(&key_dev->wq_head);
return 0;
err4:
device_destroy(key_dev->cls, key_dev->devno);
err3:
cdev_del(key_dev->cdev);
class_destroy(key_dev->cls);
err2:
unregister_chrdev_region(key_dev->devno, 1);
err1:
kfree(key_dev);
return ret;
}
module_init(intkey_init);
module_exit(intkey_exit);
MODULE_LICENSE("GPL");
下面再写个测试程序:
#include
#include
#include
#include
#include
#include
#include
#define LED_Switch1 _IOW('L', 0x1234, int)
#define LED_Switch2 _IOW('L', 0x1235, int)
#define LED_Switch3 _IOW('L', 0x1236, int)
#define LED_Switch4 _IOW('L', 0x1237, int)
struct key_event{
int code;
int value;
};
int main(void)
{
int ret = -1,
fd = -1,
val = -1;
struct key_event event;
fd = open("/dev/key1", O_RDWR);
if(fd < 0){
perror("open");
exit(1);
}
while(1)
{
ret = read(fd, &event, sizeof(struct key_event));
if(ret < 0){
perror("read");
exit(1);
}
if(event.code == KEY_UP){
if(event.value){
printf(" key1 DOWN!\n");
ioctl(fd, LED_Switch1, 1);
}
else {
printf(" key1 UP!\n");
ioctl(fd, LED_Switch1, 0);
}
}
if(event.code == KEY_DOWN){
if(event.value){
printf(" key2 DOWN!\n");
val = 1;
write(fd, &val, 4);
}
else {
printf(" key2 UP!\n");
ioctl(fd, LED_Switch2, 0);
}
}
if(event.code == KEY_LEFT){
if(event.value){
printf(" key3 DOWN!\n");
ioctl(fd, LED_Switch3, 1);
}
else {
printf(" key3 UP!\n");
ioctl(fd, LED_Switch3, 0);
}
}
if(event.code == KEY_RIGHT){
if(event.value){
printf(" key4 DOWN!\n");
ioctl(fd, LED_Switch4, 1);
}
else {
printf(" key4 UP!\n");
ioctl(fd, LED_Switch4, 0);
}
}
usleep(3000);
}
if(close(fd) < 0){
perror("close");
exit(1);
}
}
好了,还有Makefile:
#Linux源代码的路径
KERNEL_DIR = /home/george/1702/exynos/linux-3.5
#获取当前路径
CUR_DIR = $(shell pwd)
MYAPP = key_test
MODULE = intkey
all:
make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
arm-none-linux-gnueabi-gcc -o $(MYAPP) $(MYAPP).c
clean:
make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
$(RM) $(MYAPP)
install:
cp -raf *.ko $(MYAPP) /home/george/1702/exynos/filesystem/1702
#需要编译的目标程序
obj-m = $(MODULE).o
其中测试程序是key_test.c,驱动程序是intkey.c.
编译验证一下,按一个键就可以亮一个LED.