《驱动学习 - Linux键盘按键驱动 》
实现键盘驱动,让开发板的4个按键代表键盘中的L、S、空格键、回车键
1.先来介绍以下几个结构体使用和函数,下面代码中会用到
struct input_dev { void *private; const char *name; //设备名字 const char *phys; //文件路径,比如 input/buttons const char *uniq; struct input_id id; unsigned long evbit[NBITS(EV_MAX)]; //表示支持哪类事件,常用有以下几种事件(可以多选) //EV_SYN 同步事件,当使用input_event()函数后,就要使用这个上报个同步事件 //EV_KEY 键盘事件 //EV_REL (relative)相对坐标事件,比如鼠标 //EV_ABS (absolute)绝对坐标事件,比如摇杆、触摸屏感应 //EV_MSC 其他事件,功能 //EV_LED LED灯事件 //EV_SND (sound)声音事件 //EV_REP 重复键盘按键事件 //(内部会定义一个定时器,若有键盘按键事件一直按下/松开,就重复定时,时间一到就上报事件) //EV_FF 受力事件 //EV_PWR 电源事件 //EV_FF_STATUS 受力状态事件 unsigned long keybit[NBITS(KEY_MAX)]; //存放支持的键盘按键值 //键盘变量定义在:include/linux/input.h, 比如: KEY_L(按键L) unsigned long relbit[NBITS(REL_MAX)]; //存放支持的相对坐标值 unsigned long absbit[NBITS(ABS_MAX)]; //存放支持的绝对坐标值 unsigned long mscbit[NBITS(MSC_MAX)]; //存放支持的其它事件,也就是功能 unsigned long ledbit[NBITS(LED_MAX)]; //存放支持的各种状态LED unsigned long sndbit[NBITS(SND_MAX)]; //存放支持的各种声音 unsigned long ffbit[NBITS(FF_MAX)]; //存放支持的受力设备 unsigned long swbit[NBITS(SW_MAX)]; //存放支持的开关功能 ... ...
struct input_dev *input_allocate_device(void); //向内核中申请一个input_dev设备,然后返回这个设备 input_unregister_device(struct input_dev *dev); //卸载/sys/class/input目录下的input_dev这个类设备, 一般在驱动出口函数写 input_free_device(struct input_dev *dev); //释放input_dev这个结构体, 一般在驱动出口函数写 set_bit(nr,p); //设置某个结构体成员p里面的某位等于nr,支持这个功能 /* 比如: set_bit(EV_KEY,buttons_dev->evbit); //设置input_dev结构体buttons_dev->evbit支持EV_KEY set_bit(KEY_S,buttons_dev->keybit); //设置input_dev结构体buttons_dev->keybit支持按键”S” */ void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value); //上报事件 // input_dev *dev :要上报哪个input_dev驱动设备的事件 // type : 要上报哪类事件, 比如按键事件,则填入: EV_KEY // code: 对应的事件里支持的哪个变量,比如按下按键L则填入: KEY_L //value:对应的变量里的数值,比如松开按键则填入1,松开按键则填入0
input_sync(struct input_dev *dev); //同步事件通知
为什么使用了input_event()上报事件函数,就要使用这个函数?
因为input_event()函数只是个事件函数,所以需要这个input_sync()同步事件函数来通知系统,然后系统才会知道
input_sync()代码如下:
static inline void input_sync(struct input_dev *dev) { input_event(dev, EV_SYN, SYN_REPORT, 0); //就是上报同步事件,告诉内核:input_event()事件执行完毕 }
2.完整代码
/* 参考drivers\input\keyboard\gpio_keys.c */ #include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/irq.h> #include <asm/gpio.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> struct pin_desc{ int irq; char *name; unsigned int pin; unsigned int key_val; }; struct pin_desc pins_desc[4] = { {IRQ_EINT8, "K1", S3C2410_GPG0, KEY_L}, {IRQ_EINT11, "K2", S3C2410_GPG3, KEY_S}, {IRQ_EINT13, "K3", S3C2410_GPG5, KEY_ENTER}, {IRQ_EINT14, "K4", S3C2410_GPG6, KEY_LEFTSHIFT}, }; static struct input_dev *buttons_dev; static struct pin_desc *irq_pd; static struct timer_list buttons_timer; static irqreturn_t buttons_irq(int irq, void *dev_id) { /* 10ms后启动定时器 */ irq_pd = (struct pin_desc *)dev_id; mod_timer(&buttons_timer, jiffies+HZ/100); return IRQ_RETVAL(IRQ_HANDLED); } static void buttons_timer_function(unsigned long data) { struct pin_desc * pindesc = irq_pd; unsigned int pinval; if (!pindesc) return; pinval = s3c2410_gpio_getpin(pindesc->pin); if (pinval) { /* 松开 : 最后一个参数: 0-松开, 1-按下 */ input_event(buttons_dev, EV_KEY, pindesc->key_val, 0); input_sync(buttons_dev); } else { /* 按下 */ input_event(buttons_dev, EV_KEY, pindesc->key_val, 1); input_sync(buttons_dev); } } static int buttons_init(void) { int i; /* 1. 分配一个input_dev结构体 */ buttons_dev = input_allocate_device();; /* 2. 设置 */ /* 2.1 能产生哪类事件 */ set_bit(EV_KEY, buttons_dev->evbit); set_bit(EV_REP, buttons_dev->evbit); /* 2.2 能产生这类操作里的哪些事件: L,S,ENTER,LEFTSHIT */ set_bit(KEY_L, buttons_dev->keybit); set_bit(KEY_S, buttons_dev->keybit); set_bit(KEY_ENTER, buttons_dev->keybit); set_bit(KEY_LEFTSHIFT, buttons_dev->keybit); /* 3. 注册 */ input_register_device(buttons_dev); /* 4. 硬件相关的操作 */ init_timer(&buttons_timer); buttons_timer.function = buttons_timer_function; add_timer(&buttons_timer); for (i = 0; i < 4; i++) { request_irq(pins_desc[i].irq, buttons_irq, IRQT_BOTHEDGE, pins_desc[i].name, &pins_desc[i]); } return 0; } static void buttons_exit(void) { int i; for (i = 0; i < 4; i++) { free_irq(pins_desc[i].irq, &pins_desc[i]); } del_timer(&buttons_timer); input_unregister_device(buttons_dev); input_free_device(buttons_dev); } module_init(buttons_init); module_exit(buttons_exit); MODULE_LICENSE("GPL");
3.调试
这边是通过按键模拟键盘输入,所以一旦按键按下就会触发中断,在中断中会将事件上报,然后处理。
因为是键盘输入,所以内核是定位到tty1输出的。
cat /dev/tty1
但是打印出来是错误的。
用hexdump /dev/tty1 也就是十六进制显示/dev/tty1里面的内容,会先去open这个设备,然后去读取这个设备的数据,最后显示出来。
这些数值是什么意思呢?
hexdump是先执行open后,然后read这个设备,那么肯定会调用event.c中的read函数,可以根据read函数分析出来这些数据是什么意思。
code = 26,就是按键值没错。因此表示驱动方面没问题。
后来排查,是因为开发板启动了QT,显示到QT上了。因此关闭QT就可以正常显示。
正常在命令行模式下,运行是的sh应用程序
通过ls -l /proc/770/fd可以查看
标准输入、输出、错误都是到串口1上的。
通过exec 0</dev/tty1将/dev/tty1的内容输出到标准输出中