准备知识点:
原子位操作 , linux输入设备的应用程序编程
在linux内核里用struct input_dev的一个对象来表示一个输入设备. 用一位二进制表示是否支持相应的功能, 多种功能需要多位, 用数组来表示所需的多位二进制数.
#include
struct input_dev {
//这部分内容可"cat /proc/bus/input/device"来查看
const char *name; //输入设备名
const char *phys;
const char *uniq;
struct input_id id; //设备id, 可指定厂家号,产品型号
...
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //表示此输入设备所支持的事件类型. 支持的事件在相应的位设1.
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //表示此输入设备所支持的键码。键码共有768个,需要768位来表示,支持的键码需在相应位设1. 查KEY_*
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //表示支持的相对坐标的数据类型。如x, y, z轴的数据. 查 REL_*
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];//表示支持的绝对坐标的数据类型. 如x, y坐标. 查ABS_*
...
unsigned int repeat_key;
struct timer_list timer; //此定时是用于重复提交键数据使用. 我们只需设置事件EV_REP就可以了, 内核里会设置此定时器
...
}
事件类型有:
//注意宏的值表示在第几位设1就是支持此事件
#define EV_SYN 0x00 //同步,表示设备驱动的数据已更新. 所有的输入设备都需要此功能。所以这功能会在输入设备注册时自动在evbit[0]的第0位设1
#define EV_KEY 0x01 //表示此输入设备有按键功能
#define EV_REL 0x02 //相对坐标, 鼠标
#define EV_ABS 0x03 //绝对坐标, 触摸屏
#define EV_MSC 0x04
...
#define EV_REP 0x14 //支持按键重复提交。也就支持按着键不动时,自动按间隔时间提交按键数据.
...
#define EV_CNT (EV_MAX+1) //事件总数,也就是共需多少位. BITS_TO_LONGS把位数转成需要多少个long数组的元素个数.
/
在linux内核源码文档目录里的”input-programming.txt”提供一个最简单的按键的输入设备驱动范例.
总结步骤:
(1)
struct input_dev *dev;
dev = input_allocate_device(); //动态分配空间,并部分成员初始化.
(2)
//初始化成员
dev->name = "mykeys";
dev->evbit[0] = BIT_MASK(EV_KEY); //设置支持的事件类型, BIT_MASK(EV_KEY)就是表示在第EV_KEY位设1.
dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); //设置支持的键码
(3)
//注册输入设备
input_register_device(dev);
(4)
//请求按键对应的IO口中断
request_irq(gpio_to_irq(KEY_IO), button_interrupt, 双边沿触发, "button", NULL))
(5)
//在按键的中断处理函数里,提交键码数据
input_report_key(dev, BTN_0, !gpio_get_value(KEY_IO)); //注意最后的一个参数值非0时表示键按下, 0时表示松手
input_sync(dev); //提交数据
//
把前面的矩阵键盘的驱动改造成标准的输入设备驱动:
#include
#include
#include
#include
#include
#include
#include
int rows[] = {GPIOA(7), GPIOA(8), GPIOA(9), GPIOA(10)};
int cols[] = {GPIOA(20), GPIOA(21), GPIOC(4), GPIOC(7)};
struct timer_list mytimer;
//用一个数组记录哪个按键按下,当松手中断发生时,就用此数组里记录的按键作松手的按键
char keys[4][4];
int key_codes[4][4] = {
{KEY_L, KEY_S, KEY_SPACE, KEY_ENTER},
{KEY_A, KEY_UP, KEY_B, KEY_ESC},
{KEY_LEFT, KEY_DOWN, KEY_RIGHT, KEY_DOT},
{KEY_C, KEY_D, KEY_E, KEY_F},
}; //按键对应的键码表
struct input_dev *dev;
#define NAME "mykeypad"
void all_cols_output_level(int level)
{
int i;
for (i = 0; i < ARRAY_SIZE(cols); i++)
gpio_set_value(cols[i], level);
}
void timer_func(unsigned long data)
{
int r = data;
enable_irq(gpio_to_irq(rows[r]));
}
irqreturn_t irq_func(int irqno, void *arg)
{
int r = (int )arg; //r表示哪一行,由request_irq时指定
int i;
int stat = gpio_get_value(rows[r]);
disable_irq_nosync(irqno);
/////检查列////
if (stat) //行线为高电平, 则是有按键松手
{
for (i = 0; i < ARRAY_SIZE(cols); i++)
{
if (keys[r][i]) //根据数据里存放的记录来判断是哪个按键松手
{
// printk("key[%d][%d] released\n", r, i);
input_report_key(dev, key_codes[r][i], 0); //松手的键码
keys[r][i] = 0;
break;
}
}
}
else //行线为低电平,则是有按键按下
{
for (i = 0; i < ARRAY_SIZE(cols); i++)
{
all_cols_output_level(1); //所有的列输出高电平
gpio_set_value(cols[i], 0); //逐一改变一列线的电平
if (!(gpio_get_value(rows[r])) && !(gpio_get_value(rows[r]))) //判断行线电平是否随着列线改变, 如改变则是按下
{
keys[r][i] = 1;
// printk("key[%d][%d] pressed\n", r, i);
input_report_key(dev, key_codes[r][i], 1); //按下的键码
break;
}
}
all_cols_output_level(0); //所有的列输出低电平
}
input_sync(dev); //提交键码数据
//////////////
mytimer.data = r;
mod_timer(&mytimer, jiffies+HZ*50/1000); // 50ms后重新打开中断
return IRQ_HANDLED;
}
static int __init test_init(void)
{
int ret, i, j, k;
init_timer(&mytimer);
mytimer.function = timer_func;
//输入设备的初始化
dev = input_allocate_device();
dev->name = NAME;
set_bit(EV_KEY, dev->evbit); //设置支持的事件
set_bit(EV_REP, dev->evbit); //支持重复提交按键数据。即按下不动时,会自动定时重复提交按下的键码
for (i = 0; i < ARRAY_SIZE(key_codes); i++)
{
for (j = 0; j < ARRAY_SIZE(key_codes[0]); j++)
set_bit(key_codes[i][j], dev->keybit); //设置支持的键码
}
input_register_device(dev); //注册输入设备
////////////////////////
//申请行线的中断
for (j = 0, k = 0; j < ARRAY_SIZE(rows); k++, j++)
{
ret = gpio_request(rows[j], NAME);
if (ret < 0)
goto err1;
ret = request_irq(gpio_to_irq(rows[k]), irq_func, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, NAME, (void *)k);
if (ret < 0)
goto err1;
}
//把列全部输出低电平
for (i = 0; i < ARRAY_SIZE(cols); i++)
{
ret = gpio_request(cols[i], NAME);
if (ret < 0)
goto err0;
gpio_direction_output(cols[i], 0);
}
return 0;
err1:
while (k-- > 0)
free_irq(gpio_to_irq(rows[k]), (void *)k);
while (j > 0)
gpio_free(rows[--j]);
err0:
while (i > 0)
gpio_free(cols[--i]);
return ret;
}
static void __exit test_exit(void)
{
int i, j;
del_timer(&mytimer);
for (i = 0; i < ARRAY_SIZE(cols); i++)
gpio_free(cols[i]);
for (j = 0; j < ARRAY_SIZE(rows); j++)
{
gpio_free(rows[j]);
free_irq(gpio_to_irq(rows[j]), (void *)j);
}
input_unregister_device(dev);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
在linux内核在输入设备驱动注册后,会自动生成设备驱动对应的设备文件,并提供了字符设备驱动接口供用户进程调用(输入设备驱动里无需再实现字符设备驱动). 可用”cat /proc/bus/input/devices”查出输入设备驱动对应的设备文件.
找出设备文件后,就可以用linux输入设备的应用程序编程的方法来获取键码数据。也可以移植qt应用程序,在qt程序里直接用keyPressEvent来捕捉键值.
也可以把输入设备作shell的标准输入来测试, shell命令"exec 0(所有输入设备驱动提交的键码数据都会有一份提供到tty1对应的驱动里,所以可以从tty1里获取到输入设备驱动的键码).
执行命令后, 按下矩阵键盘的键,终端就可以出现相应的键值