目录
1. 输入系统
1.1 基本概念
1.2 获取输入设备信息
1.3 输入系统框架
2. 输入系统编程
2.1 确定对应关系
2.2 编写代码步骤
3. 开发板上的编程
学习带着问题学会比较好,比如,在Linux系统下,我们如何编写程序获得鼠标的位置?键盘的哪一个按键被按下了?触摸屏的哪个位置被触摸了?
学过单片机的都知道,这些本质上都是一些信号,由驱动程序解析,但我们讲的是应用编程,也就是说从驱动程序中直接获取我们需要的。
那么必然就需要一套规范的接口,不然每个人写的都不一样,做个应用,还得阅读驱动源码,就离谱了。
嵌入式Linux入门完整教程:嵌入式Linux教程—裸机、应用、驱动完整教程目录
常见的输入设备有键盘、鼠标、触摸屏、蓝牙等等,用户通过这些输入设备与Linux系统进行数据交换。
Linux 系统为了统一管理这些输入设备,实现了一套能兼容所有输入设备的框架:输入系统。驱动开发人员基于这套框架开发出程序,应用开发人员就可以使用统一的 API 去使用设备。
确定设备信息:输入设备的设备节点名为/dev/input/eventX(也可能是/dev/eventX, X表示 0、 1、 2 等数字)。查看设备节点,可以执行以下命令:
ls /dev/input
如果是开发板可能没有input文件夹,直接就在/dev下。
怎么知道这些设备节点对应什么硬件呢?可以执行以下命令:
cat /proc/bus/input/devices
I:id of the device(设备 ID)
N:name of the device设备名称
P:physical path to the device in the system hierarchy系统层次结构中设备的物理路径。
S:sysfs path位于 sys 文件系统的路径
U:unique identification code for the device(if device has it)设备的唯一标识码
H:list of input handles associated with the device.与设备关联的输入句柄列表。
B:bitmaps(位图)
值得注意的是 B 位图,比如上图中“ B: EV=b”用来表示该设备支持哪类输入事件。 b 的二进制是 1011, bit0、 1、 3 为 1,表示该设备支持 0、 1、 3 这三类事件,即 EV_SYN、 EV_KEY、 EV_ABS。
再举一个例子,“ B: ABS=2658000 3”如何理解?
它表示该设备支持 EV_ABS 这一类事件中的哪一些事件。这是 2 个 32 位的数 字: 0x2658000、 0x3, 高位在 前低 位在 后, 组成一 个 64 位 的数字 :“ 0x2658000,00000003”,数值为 1 的位有: 0、 1、 47、 48、 50、 53、 54,即:0、 1、 0x2f、 0x30、 0x32、 0x35、 0x36,对应以下这些宏:
内核版本低的在 /usr/include/linux/input.h下。
以按键为例:按键按下–>输入系统驱动层–>输入系统核心层–>输入系统事件层—>用户空间
对于应用程序软件编程的角度,我们只需要关注用户空间得到按键按下以后获取的是什么事件就可以了,例如我想知道我当前按下的按是短按还是长按?或者我想知道当前我按下键盘的是空格键还是回车键等等。
在input子系统中,每个事件的发生都使用事件(type)->子事件(code)->值(value) 所有的输入设备的主设备号都是13,input-core通过次设备来将输入设备进行分类,如0-31是游戏杆,32-63是鼠标(对应Mouse Handler)、64-95是事件设备(如触摸屏,对应Event Handler)。
在input.h中的源码:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
timeval 表示的是“自系统启动以来过了多少时间”,它是一个结构体,含有“ tv_sec、 tv_usec”两项(即秒、微秒)。
Linux输入子系统支持的事件类型
事件类型 | 编码 | 含义 |
---|---|---|
EV_SYN | 0x00 | 同步事件 |
EV_KEY | 0x01 | 按键事件(鼠标,键盘等) |
EV_REL | 0x02 | 相对坐标(如:鼠标移动,报告相对最后一次位置的偏移) |
EV_ABS | 0x03 | 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置) |
EV_MSC | 0x04 | 其它 |
EV_SW | 0x05 | 开关 |
EV_LED | 0x11 | 按键/设备灯 |
EV_SND | 0x12 | 声音/警报 |
EV_REP | 0x14 | 重复 |
EV_FF | 0x15 | 力反馈 |
EV_PWR | 0x16 | 电源 |
EV_FF_STATUS | 0x17 | 力反馈状态 |
EV_MAX | 0x1f | 事件类型最大个数和提供位掩码支持 |
驱动程序上报完一系列的数据后,会上报一个“同步事件”,表示数据上报完毕。 APP 读到“同步事件”时,就知道已经读完了当前的数据。同步事件也是一个 input_event 结构体,它的 type、code、 value 三项都是 0。
通过上文已经知道了,可以通过:
ls /dev/input
查看输入设备节点,通过:
cat /proc/bus/input/devices
查看对应的硬件。
但是还是有点模糊,比如出现好几个Mouse,到底是哪个? 通过:
hexdump /dev/input/event2 //event3、event4
来测试一下,当测试event2时,发现鼠标移动就不断输出数据,说明正是event2。
第一步:定义一个事件的结构体变量,我们要用到事件,需要得到事件,在程序中肯定需要一个变量来描述这个事件。
第二步:打开设备节点,Linux中一切皆文件,所以用open函数,打开输入设备节点
第三步:读取事件,读取打开的输入设备节点中的数据,到我们第一步的定义的事件变量中
第四步:利用获取的事件信息来完成我们自己编写的处理逻辑,这里就简单输出一下鼠标位置。
完整代码
#include
#include
#include
#include
#include
int main(void)
{
//1、定义一个结构体变量用来描述input事件
struct input_event event_mouse ;
//2、打开input设备的事件节点 我的通用USB鼠标事件的节点是event2
int fd = -1 ;
fd = open("/dev/input/event2", O_RDONLY);
if(-1 == fd)
{
printf("open mouse event fair!\n");
return -1 ;
}
while(1)
{
//3、读事件
read(fd, &event_mouse, sizeof(event_mouse));
if(EV_ABS == event_mouse.type)
{
//code表示位移X或者Y,当判断是X时,打印X的位移value
//当判断是Y时,打印Y的位移value
if(event_mouse.code == ABS_X)
{
printf("event_mouse.code_X:%d\n", event_mouse.code);
printf("event_mouse.value_X:%d\n", event_mouse.value);
}
else if(event_mouse.code == ABS_Y)
{
printf("event_mouse.code_Y:%d\n", event_mouse.code);
printf("event_mouse.value_Y:%d\n", event_mouse.value);
}
}
}
close(fd);
return 0 ;
}
EV=b,B表示支持KEY,ABS事件,可以看到KEY=400 0 0 0 0 0 0 0 0 0 0 ,也就是第330位为1,十六进制就是0x14A,查源码
#define BTN_TOUCH 0x14a
表示支持KEY事件的BTN_TOUCH子事件。ABS自己算一算,我就不用了。
我开发板上只有触摸屏这一个输入设备,用之前文章中的Linux下LCD显示程序随便改改
功能:按压触摸屏变黑,释放,屏幕变白
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static int fd_fb;
static struct fb_var_screeninfo var; /* Current var */
static int screen_size;
static unsigned char *fb_base;
static unsigned int line_width;
static unsigned int pixel_width;
void lcd_put_pixel(int x, int y, unsigned int color)
//传入的 color 表示颜色,它的格式永远是 0x00RRGGBB,即 RGB888。
//当 LCD 是 16bpp 时,要把 color 变量中的 R、 G、 B 抽出来再合并成 RGB565 格式。
{
unsigned char *pen_8 = fb_base+y*line_width+x*pixel_width;
//计算(x,y)坐标上像素对应的 Framebuffer 地址。
unsigned short *pen_16;
unsigned int *pen_32;
unsigned int red, green, blue;
pen_16 = (unsigned short *)pen_8;
pen_32 = (unsigned int *)pen_8;
switch (var.bits_per_pixel)
{
//对于 8bpp, color 就不再表示 RBG 三原色了,这涉及调色板的概念, color 是调色板的值。
case 8:
{
*pen_8 = color;
break;
}
case 16:
{
/* 565 */
//先从 color 变量中把 R、 G、 B 抽出来。
red = (color >> 16) & 0xff;
green = (color >> 8) & 0xff;
blue = (color >> 0) & 0xff;
//把 red、 green、 blue 这三种 8 位颜色值,根据 RGB565 的格式,
//只保留 red 中的高 5 位、 green 中的高 6 位、 blue 中的高 5 位,
//组合成一个新的 16 位颜色值。
color = ((red >> 3) << 11) | ((green >> 2) << 5) | (blue >> 3);
//把新的 16 位颜色值写入 Framebuffer
*pen_16 = color;
break;
}
case 32:
{
//对于 32bpp,颜色格式跟 color 参数一致,可以直接写入Framebuffer
*pen_32 = color;
break;
}
default:
{
printf("can't surport %dbpp\n",var.bits_per_pixel);
break;
}
}
}
int main(void)
{
//1、定义一个结构体变量用来描述input事件
struct input_event event_TouchScreen ;
int fd = -1 ;
int i;
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0)
{
printf("can't open /dev/fb0\n");
return -1;
}
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var))
{
printf("can't get var\n");
return -1;
}
line_width = var.xres * var.bits_per_pixel / 8;
pixel_width = var.bits_per_pixel / 8;
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1)
{
printf("can't mmap\n");
return -1;
}
//2、打开input设备的事件节点
fd = open("/dev/event0", O_RDONLY);
if(-1 == fd)
{
printf("open mouse event fair!\n");
return -1 ;
}
while(1)
{
//3、读事件
read(fd, &event_TouchScreen, sizeof(event_TouchScreen));
if(EV_KEY == event_TouchScreen.type)
{
//code表示位移X或者Y,当判断是X时,打印X的位移value
//当判断是Y时,打印Y的位移value
if(event_TouchScreen.code == BTN_TOUCH)
{
if(event_TouchScreen.value==0)
/* 清屏: 全部设为白色 */
memset(fb_base, 0xff, screen_size);
else
/* 清屏: 全部设为黑色 */
memset(fb_base, 0x0, screen_size);
}
else;
}
}
close(fd);
close(fd_fb);
return 0 ;
}
完成,复杂的逻辑就自己根据需求编写就行了。