Posts tagged ‘kernel’

android驱动之虚拟按键

1    背景

nexus one工业设计简洁,类似于iphone只有一个按键的设计,只有中间的一个轨迹球。但是android标准键盘是有 HOME,MENU,BACK,SEARCH等,但是同时要保持工业设计。nexus one是这样解决问题的,显示屏是800X480,但是在电容触摸屏是8xx*480的就是比800要大的地方就变成了虚拟按键,模拟了android标准按键。

2    方案

要实现,虚拟按键,在android里面是靠两层协助实现,底层要把虚拟按键在比显示屏多出的地方规定好虚拟按键的位置大小以及键值等,给上层一文件接口。上层java层启动一个服务来读取这一区域的按键响应,这样就是大体的架构。具体实现如下:

2.1    底层虚拟按键功能实现方案

简而言之,就是在内核中把虚拟按键的所有信息给上层给出,用什么方式?就是用sys文件系统的方式,sys文件系统的路径是约定好的所以代码如下实现。给出信息的协议格式是一段连续的字符串,每个按键有六项分别用冒号分割,按键按键之间也是用冒号分割,六项按顺序分别是:

键类型:键值:按键区域中心x坐标:按键区域中心y坐标:按键区域宽:按键区域高

arch/arm/mach-msm/board-mahimahi.c

static ssize_t mahimahi_virtual_keys_show(struct kobject *kobj,
			       struct kobj_attribute *attr, char *buf)
{
	if (system_rev > 2) {
		/* center: x: back: 55, menu: 172, home: 298, search 412, y: 835 */
		return sprintf(buf,
			__stringify(EV_KEY) ":" __stringify(KEY_BACK)  ":55:835:90:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU)   ":172:835:125:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_HOME)   ":298:835:115:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":412:835:95:55"
		   "\n");
	} else {
		/* center: x: home: 55, menu: 185, back: 305, search 425, y: 835 */
		return sprintf(buf,
			__stringify(EV_KEY) ":" __stringify(KEY_HOME)  ":55:835:70:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_MENU)   ":185:835:100:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_BACK)   ":305:835:70:55"
		   ":" __stringify(EV_KEY) ":" __stringify(KEY_SEARCH) ":425:835:70:55"
		   "\n");
	}
}
 
static struct kobj_attribute mahimahi_virtual_keys_attr = {
	.attr = {
		.name = "virtualkeys.synaptics-rmi-touchscreen",
		.mode = S_IRUGO,
	},
	.show = &mahimahi_virtual_keys_show,
};
 
static struct attribute *mahimahi_properties_attrs[] = {
	&mahimahi_virtual_keys_attr.attr,
	NULL
};
 
static struct attribute_group mahimahi_properties_attr_group = {
	.attrs = mahimahi_properties_attrs,
};
 
struct kobject *properties_kobj;
 
properties_kobj = kobject_create_and_add("board_properties", NULL);
if (properties_kobj)
	ret = sysfs_create_group(properties_kobj,
					 &mahimahi_properties_attr_group);
if (!properties_kobj || ret)
	pr_err("failed to create board_properties\n");

2.2    JAVA上层方案

Java层主要是读取按键信息,然后经过一定的算法,来识别虚拟按键,基本不需要修改,但最好还是熟悉java层的架构这样出问题的时候利于定位
frameworks/base/services/java/com/android/server/KeyInputQueue.java

/*这是虚拟按键的类里面包括了VirtualKey所用到的成员变量和按键定位方法*/
    static class VirtualKey {
        int scancode;
        int centerx;
        int centery;
        int width;
        int height;
 
        int hitLeft;
        int hitTop;
        int hitRight;
        int hitBottom;
 
        InputDevice lastDevice;
        int lastKeycode;
 
        boolean checkHit(int x, int y) {
            return (x >= hitLeft && x <= hitRight                     && y >= hitTop && y <= hitBottom);
        }
 
        void computeHitRect(InputDevice dev, int dw, int dh) {
            if (dev == lastDevice) {
                return;
            }
            if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "computeHitRect for " + scancode
                    + ": dev=" + dev + " absX=" + dev.absX + " absY=" + dev.absY);
 
            lastDevice = dev;
 
            int minx = dev.absX.minValue;
            int maxx = dev.absX.maxValue;
 
            int halfw = width/2;
            int left = centerx - halfw;
            int right = centerx + halfw;
            hitLeft = minx + ((left*maxx-minx)/dw);
            hitRight = minx + ((right*maxx-minx)/dw);
 
            int miny = dev.absY.minValue;
            int maxy = dev.absY.maxValue;
 
            int halfh = height/2;
            int top = centery - halfh;
            int bottom = centery + halfh;
            hitTop = miny + ((top*maxy-miny)/dh);
            hitBottom = miny + ((bottom*maxy-miny)/dh);
        }
    }
/*以下就是与底层接口的函数,如果这个函数和底层接口正常,基本上虚拟按键就能够ok*/
    private void readVirtualKeys(String deviceName) {
        try {
            FileInputStream fis = new FileInputStream(
                    "/sys/board_properties/virtualkeys." + deviceName);
/*这里就是读取kernel给出信息的地方,也就是地层与上层接口的地方,所以整个实现的重点就是这里*/
            InputStreamReader isr = new InputStreamReader(fis);
            BufferedReader br = new BufferedReader(isr, 2048);
            String str = br.readLine();
            if (str != null) {
                String[] it = str.split(":");
                if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "***** VIRTUAL KEYS: " + it);
                final int N = it.length-6;
                for (int i=0; i<=N; i+=6) {
                    if (!"0x01".equals(it[i])) {
                        Log.w(TAG, "Unknown virtual key type at elem #" + i
                                + ": " + it[i]);
                        continue;
                    }
                    try {
                        VirtualKey sb = new VirtualKey();
                        sb.scancode = Integer.parseInt(it[i+1]);
                        sb.centerx = Integer.parseInt(it[i+2]);
                        sb.centery = Integer.parseInt(it[i+3]);
                        sb.width = Integer.parseInt(it[i+4]);
                        sb.height = Integer.parseInt(it[i+5]);
                        if (DEBUG_VIRTUAL_KEYS) Log.v(TAG, "Virtual key "
                                + sb.scancode + ": center=" + sb.centerx + ","
                                + sb.centery + " size=" + sb.width + "x"
                                + sb.height);
                        mVirtualKeys.add(sb);
                    } catch (NumberFormatException e) {
                        Log.w(TAG, "Bad number at region " + i + " in: "
                                + str, e);
                    }
                }
            }
            br.close();
        } catch (FileNotFoundException e) {
            Log.i(TAG, "No virtual keys found");
        } catch (IOException e) {
            Log.w(TAG, "Error reading virtual keys", e);
        }
    }

2.3    总结

方案基本上就是这样,主要是调试工作可能需要一段时间,还有如果要做虚拟按键,还需要硬件的支持(超过显示区域的触摸屏区域)。本代码基于android 2.1请根据实际情况修改

相关日志

android驱动之jogball

1 简介

JogBall是首款Android手机-HTC Dream上附带的类似轨迹球Trackball的滑轮,通过Jogball用户可以轻松的实现网页的翻页、地图翻

转等操作,这种在手机上增加轨迹球的方式可以取消传统的4维导航键,Jogball并非HTC公司的首例,早在HTC 3300(Dopod P800)就

实现了这项功能,轨迹球操作方式的加入可以实现像电脑上鼠标一样轻松操控。现在jogball基本上算是android手机的标准配置了。

2 框图&架构


2.1 gpio_event子系统

Jogball驱动是由linux代码中的gpio_event子系统实现的。
由以下文件组成:

  • driver/input/misc/gpio_event.c
  • driver/input/misc/gpio_input.c
  • driver/input/misc/gpio_output.c
  • driver/input/misc/gpio_matrix.c
  • driver/input/misc/gpio_axis.c

gpio_event子系统是一个关于gpio的输入设备的通用代码,里面可以包含一般嵌入式设备中需要的键盘阵列和坐标有关的输入等。

2.2 Jogball架构

Jogball是归类于gpio_axis.c中的,是一个相对坐标系,判断电信号的高低来对坐标加或者减一定数值。具体细节在gpio_axis.c中

gpio_axis.c中的结构体初始化是在xxx-borad.c中的,基本架构就是这样,细节在下文叙述。

3 硬件描述


3.1 Jogball机械特性

Jogball是一个基于滚球滚动的输入设备,所以它机械特性也是比较重要,知道他的机械特性,便于我们理解驱动。
Jogball和以前的滚球鼠标比较相似,依靠滚轮的滚动来带动四周的圆柱体旋转。每个圆柱体端头都有一个磁铁,这个磁体铁从横断

面来看就是由N极的扇形和S极的扇形来组成一个圆周。这样当磁铁旋转的时候,N极和S极切换,使霍尔IC产生高低电平。
注意:在滚球旋转时,两个相对方向只有一个方向可以转动,这是为了减化软件编写的一个机械特性。

3.2 Jogball的电气特性

Jogball最重要的电特性就是这个霍尔IC,

霍尔元件的工作原理:所谓霍尔效应,是指磁场作用于载流金属导体、半导体中的载流子时,产生横向电位差的物理现

象。金属的霍尔效应是1879年被美 国物理学家霍尔发现的。当电流通过金属箔片时,若在垂直于电流的方向施加磁场,则金属箔片

两侧面会出现横向电位差。半导体中的霍尔效应比金属箔片中更为明 显,而铁磁金属在居里温度以下将呈现极强的霍尔效应。

所以jogball是旋转磁轴引起磁场的变幻,来产生高低电压的。从而根据机械特性就不难理解jogball的数字输入过程。

3.3 Jogball的原理图

其实jogball只是机械结构,原理图是hall ic的,有兴趣的人在这里下载

4 数据结构

struct hero_axis_info {
        struct gpio_event_axis_info info; /*gpio_event_axis结构的信息*/
        uint16_t in_state;/*记录输入的高低电平信号*/
        uint16_t out_state;/*记录输出的高低电平信号*/
        uint16_t temp_state;/*临时存储高低电平*/
        uint16_t threshold;/*门限值,超过这个值上报给input子系统*/
};
 
struct gpio_event_axis_info {
        /* initialize to gpio_event_axis_func */
        struct gpio_event_info info; /*gpio_event信息*/
        uint8_t  count; /*gpio的数量*/
        uint8_t  type; /* EV_REL or EV_ABS */
        uint16_t code; /*输入键值类型*/
        uint16_t decoded_size; /*gpio 数组的大小*/
        uint16_t (*map)(struct gpio_event_axis_info *info, uint16_t in); /*解析回调函数*/
        uint32_t *gpio; /*gpio端口数组*/
        uint32_t flags; /*调试标值*/
        uint32_t enable_emc_protect_delay; /*保护间隔时间*/
        uint16_t emc_gpio_state; /* 延时中用到gpio_state*/
        atomic_t emc_disable_irqnum;/*延时中用到的屏蔽的中断号*/
};
 
struct gpio_event_info {
        int (*func)(struct input_dev *input_dev,
        struct gpio_event_info *info,
        void **data, int func);
        /*给gpio_event子系统中实现注册到内核中的回调函数*/
        int (*event)(struct input_dev *input_dev,
        struct gpio_event_info *info,
        void **data, unsigned int type,
        unsigned int code, int value); /* out events */
};
 
struct gpio_event_platform_data {
        const char *name;/*名字*/
        struct gpio_event_info **info;/*gpio_event的结构指针*/
        size_t info_count;/*注册到axis中的info 结构体的数量*/
        int (*power)(const struct gpio_event_platform_data *pdata, bool on);/*电源管理回调函数*/
};

5 代码分析

5.1 通用代码

driver/input/misc/gpio_event.c

/*gpio_event基本数据结构*/
struct gpio_event {
        struct input_dev *input_dev;
        const struct gpio_event_platform_data *info;
        struct early_suspend early_suspend;
        void *state[0];
};
 
/*注册给input的回调函数*/
static int gpio_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 
/*调用下层给的回调函数来实现具体的注册*/
static int gpio_event_call_all_func(struct gpio_event *ip, int func)
/*电源管理*/
#ifdef CONFIG_HAS_EARLYSUSPEND
void gpio_event_suspend(struct early_suspend *h)
 
void gpio_event_resume(struct early_suspend *h)
#endif
/*注册*/
static int __init gpio_event_probe(struct platform_device *pdev)
/*注销*/
static int gpio_event_remove(struct platform_device *pdev)
 
static struct platform_driver gpio_event_driver
static int __devinit gpio_event_init(void)
 
static void __exit gpio_event_exit(void)

gpio_event.c:主要是gpio_event子系统的框架

5.2 soc代码

driver/input/misc/gpio_axis.c

struct gpio_axis_state {
struct input_dev *input_dev;/*input设备结构*/
struct gpio_event_axis_info *info; /*坐标系数据结构*/
uint32_t pos; /*位置*/
struct hrtimer emc_hrtimer_delay; /* 延时时间*/
atomic_t atomic_emc_hrtimer_is_run; /*延时的原子变量*/
};
/*上报input数据*/
static void gpio_event_update_axis(struct gpio_axis_state *as, int report)
/*时间处理函数*/
static enum hrtimer_restart emc_progress_hrtimer_handler_func(struct hrtimer *timer)
/*中断处理函数*/
static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id)
/*给上层的注册回调函数*/
int gpio_event_axis_func(struct input_dev *input_dev, struct gpio_event_info *info, void **data, int func)

5.3 platform代码

例如:/arch/arm/match-msm/board-72×7.c

uint16_t hero_axis_map(struct gpio_event_axis_info *info, uint16_t in)
{
        struct hero_axis_info *ai = container_of(info, struct hero_axis_info, info);
        uint16_t out = ai->out_state;
 
if (nav_just_on) {
        if (jiffies == nav_on_jiffies || jiffies == nav_on_jiffies + 1)
                goto ignore;
        nav_just_on = 0;
}
 
if((ai->in_state ^ in) & 1) /*检测在方向1上有没有数值变化*/
        out--;
if((ai->n_state ^ in) & 2) /*检测在方向1相对方向上有没有数值变化*/
        out++;
ai->;out_state = out;
ignore:
        ai->in_state = in;
if (ai->out_state - ai->temp_state == ai->threshold) {
        ai->temp_state++;
        ai->out_state = ai->temp_state;
} else if (ai->temp_state - ai-out_state == ai->threshold) {
        ai->temp_state--;
        ai->out_state = ai->temp_state;
} else if (abs(ai->out_state - ai->;temp_state) > ai->threshold)
        ai->temp_state = ai->out_state;
 
return ai->temp_state;
}

以上函数,是最终的数据解析函数,也就是把硬件中读出来的数据,转化为实际的位置变化的函数。此函数主要做了差错控制,增加

了程序的鲁棒性和可订制性。

static struct hero_axis_info hero_x_axis = {
        .threshold = 1,
                .info = {
                .info.func = gpio_event_axis_func,
                .count = ARRAY_SIZE(hero_x_axis_gpios),
                .type = EV_REL,
                .code = REL_X,
                .decoded_size = 1U << ARRAY_SIZE(hero_x_axis_gpios),
                .map = hero_axis_map,
                .gpio = hero_x_axis_gpios,
                .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION /*| GPIOEAF_PRINT_RAW | GPIOEAF_PRINT_EVENT */,
                .enable_emc_protect_delay = 1 * NSEC_PER_MSEC,
        }
};

X轴上的info结构体的初始化

static struct hero_axis_info hero_y_axis = {
        .threshold = 1,
                .info = {
                .info.func = gpio_event_axis_func,
                .count = ARRAY_SIZE(hero_y_axis_gpios),
                .type = EV_REL,
                .code = REL_Y,
                .decoded_size = 1U << ARRAY_SIZE(hero_y_axis_gpios),
                .map = hero_axis_map,
                .gpio = hero_y_axis_gpios,
                .flags = GPIOEAF_PRINT_UNKNOWN_DIRECTION /*| GPIOEAF_PRINT_RAW | GPIOEAF_PRINT_EVENT  */,
                .enable_emc_protect_delay = 1 * NSEC_PER_MSEC,
        }
};

Y轴上的info结构体的初始化

6 备注

在android的framework的代码中识别jogball设备是识别成鼠标的特性的,不仅有坐标还是由按键的,但是驱动中并没有axis中并没

有按键,所以在注册input设备的时候要加入按键的键值。如下代码:
在drivers/input/misc/gpio_axis.c中的gpio_event_axis_func函数中

as->input_dev = input_dev;
as->info = ai;
 
input_set_capability(input_dev, ai->type, ai->;code);
if (ai->type == EV_ABS) {
        input_set_abs_params(input_dev, ai->code, 0,
        ai->decoded_size - 1, 0, 0);
}

input_set_capability(input_dev, ai->type, BT_MOUSE);

本文源码是基于hero手机kernel代码
kernel_hero/arch/arm/mach-msm/board-hero.c
下载地址 http://developer.htc.com/

相关日志