实验平台:Linux-3.14 + Android 4.4
本文以led灯为例,led灯使用gpio口来控制,输出高电平点亮led,driver使用linux内核自带的leds-gpio.c驱动,那么对于对于我们来说,只需要在dts文件(或者其他地方)中配置一下就可以了,配置示例如下:
gpioleds {
compatible = "gpio-leds";
leds_red {
label = "red";
default-state = "off";
gpios = <&mcu_gpio n 0>;
};
};
要使用leds-gpio.c这个驱动呢,首先要将compatible属性赋值为"gpio-leds",然后是子节点,子节点可以有多个(这里以充电指示灯红灯为例),在子节点中lable属性的值将在/sys/class/leds目录下创建一个名为red的子目录,而default-state属性表示led灯的默认状态(on或off),而在gpios属性中,mcu_gpio是和具体平台相关的,会对应一个gpio节点,n表示使用的哪一个gpio口,而最后一个值是一个标志位,表示低电平有效的标志位,如果需要低电平点亮led灯,那么这里就需要赋值为1,否则赋值0就好了,好了,led灯的driver层就介绍完了,驱动初始化完整之后,将创建一个/sys/class/leds/red这样一个目录,其中我们这里会用到一个文件是brightness,往里写1即点亮led灯,写0即关闭led灯。
led的操作相应大家都应该很清楚了,那么接下来看HAL层代码应该怎样编写,代码如下:
/* hardware/xxx/led/led.c */
#define LOG_TAG "Led"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <cutils/log.h>
#include <hardware/led.h>
const char * const LED_FILE = "/sys/class/leds/red/brightness";
static int write_int(const char *path, int value)
{
int fd;
fd = open(path, O_RDWR);
if (fd < 0) {
ALOGE("write_int failed to open %s\n", path);
return -errno;
}
char buffer[20];
int bytes = sprintf(buffer, "%d\n", value);
int amt = write(fd, buffer, bytes);
close(fd);
return amt == -1 ? -errno : 0;
}
static int led_device_close(struct hw_device_t *dev)
{
struct led_device_t *led_dev = (struct led_device_t *)dev;
ALOGI("%s\n", __func__);
if (led_dev) {
free(led_dev);
}
return 0;
}
static int led_set_on(struct led_device_t *dev, int enable)
{
ALOGI("%s\n", __func__);
if (enable) { /* turn on led */
write_int(LED_FILE, 1);
}
return 0;
}
static int led_set_off(struct led_device_t *dev, int enable)
{
ALOGI("%s\n", __func__);
if (enable) { /* turn off led */
write_int(LED_FILE, 0);
}
return 0;
}
static int led_device_open(const struct hw_module_t *module, const char *name,
struct hw_device_t **device)
{
struct led_device_t *dev;
ALOGI("%s\n", __func__);
dev = (struct led_device_t *)malloc(sizeof(struct led_device_t));
memset(dev, 0, sizeof(struct led_device_t));
dev->common.tag = HARDWARE_DEVICE_TAG;
dev->common.version = 0;
dev->common.module = (struct hw_module_t *)module;
dev->common.close = led_device_close;
dev->set_on = led_set_on;
dev->set_off = led_set_off;
*device = &dev->common;
return 0;
}
static struct hw_module_methods_t led_module_methods = {
.open = led_device_open,
};
struct led_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = LED_HARDWARE_MODULE_ID,
.dso = NULL,
.name = "Led Module",
.author = "Tracy",
.methods = &led_module_methods,
},
};
操作都是很简单的,跟前面描述的一样,往brightness文件中写1即打开led灯,写0即关闭led灯,这里需要注意一点的是HAL_MODULE_INFO_SYM前面不要加const关键字,大家可能在网上会看到有的HAL层代码这里有个const关键字,但在Android 4.4版本上是不行的,其它版本也许加上可能没有问题。
然后其中用到了一个led.h这样一个头文件,代码如下:
/* hardware/libhardware/include/hardware/led.h */
#ifndef __ANDROID_HAL_LED_H
#define __ANDROID_HAL_LED_H
#include <hardware/hardware.h>
struct led_module_t {
struct hw_module_t common;
};
struct led_device_t {
struct hw_device_t common;
int (*set_on)(struct led_device_t *dev, int enable);
int (*set_off)(struct led_device_t *dev, int enable);
};
#define LED_HARDWARE_MODULE_ID "led"
#endif /* __ANDROID_HAL_LED_H */
Android.mk如下:
# hardware/xxx/led/Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := led.c
LOCAL_MODULE_TAGS := optional
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_MODULE := led.default
include $(BUILD_SHARED_LIBRARY)
编译完成之后将在out目录中的system/lib/hw/中生成一个名为led.default.so的一个so文件(使用mm方式编译的,如果需要在编译系统时就编译这里的代码,需要修改相应的的地方,这里就不去描述了)。
HAL层代码介绍完毕,我们都知道Java是不能直接调用c代码的,所以这里需要编写JNI,代码如下:
/* frameworks/base/services/jni/com_android_server_LedService.cpp */
#define LOG_TAG "Led"
#include <jni.h>
#include <stdlib.h>
#include <JNIHelp.h>
#include <utils/Log.h>
#include <hardware/led.h>
namespace android {
struct led_device_t *LedDevice = NULL;
static led_device_t *get_device(hw_module_t *module, const char *name)
{
int err;
hw_device_t *device;
ALOGI("%s\n", __func__);
err = module->methods->open(module, name, &device);
if (err == 0) {
return (led_device_t *)device;
}
return NULL;
}
static jboolean init_native(JNIEnv *env, jobject obj)
{
hw_module_t *module;
ALOGI("%s\n", __func__);
if (hw_get_module(LED_HARDWARE_MODULE_ID, (const hw_module_t **)&module) == 0) {
LedDevice = get_device(module, LED_HARDWARE_MODULE_ID);
if (LedDevice != NULL)
return true;
}
return false;
}
static void finalize_native(JNIEnv *env, jobject obj)
{
ALOGI("%s\n", __func__);
if (LedDevice != NULL)
free(LedDevice);
}
static void seton_native(JNIEnv *env, jobject obj)
{
ALOGI("%s\n", __func__);
if (LedDevice != NULL)
LedDevice->set_on(LedDevice, 1);
}
static void setoff_native(JNIEnv *env, jobject obj)
{
ALOGI("%s\n", __func__);
if (LedDevice != NULL)
LedDevice->set_off(LedDevice, 1);
}
static JNINativeMethod methods_table[] = {
{ "_init", "()Z", (void *)init_native },
{ "_finalize", "()V", (void *)finalize_native },
{ "_set_on", "()V", (void *)seton_native },
{ "_set_off", "()V", (void *)setoff_native },
};
int register_android_server_LedService(JNIEnv *env)
{
return jniRegisterNativeMethods(env, "com/android/server/LedService",
methods_table, NELEM(methods_table));
}
};
JNI代码编写好了之后,还需要调用register_android_server_LedService函数去完成JNI的注册,这里就在该目录下的onload.cpp去调用:
/* frameworks/base/services/jni/onload.cpp */
namespace android {
/* ... */
int register_android_server_LedService(JNIEnv *env);
/* ... */
};
using namespace android;
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
/* ... */
register_android_server_LedService(env);
/* ... */
}
当然还需要修改对应的Android.mk文件:
LOCAL_SRC_FILES:= \
com_android_server_LedService.cpp \
onload.cpp
这部分代码编译之后将在system/lib/目录下生成libandroid_servers.so。
JNI部分也写好了,还需要framework层代码:
/* frameworks/base/services/java/com/android/server/LedService.java */
package com.android.server;
import android.util.Log;
public class LedService {
private static final String TAG = "Led";
static {
System.load("/system/lib/libandroid_servers.so");
}
public LedService() {
Log.i(TAG, "_init()");
_init();
}
public void finalize() throws Throwable {
Log.i(TAG, "_finalize()");
_finalize();
super.finalize();
}
public void set_on() {
Log.i(TAG, "_set_on()");
_set_on();
}
public void set_off() {
Log.i(TAG, "_set_off()");
_set_off();
}
private static native boolean _init();
private static native void _finalize();
private static native void _set_on();
private static native void _set_off();
}
注意这里需要调用Sytem.load去加载native库,否则会出现找不到native方法的问题。
最后是应用层,activity代码如下:
/* package/apps/LedTest/src/com/example/ledtest/MainActivity.java */
package com.example.ledtest;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.android.server.LedService;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final LedService led_service = new LedService();
final TextView led_status = (TextView)this.findViewById(R.id.led_status);
Button led_on = (Button)this.findViewById(R.id.led_on);
led_on.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
led_status.setText("status: on");
led_service.set_on();
}
});
Button led_off = (Button)this.findViewById(R.id.led_off);
led_off.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
led_status.setText("status: off");
led_service.set_off();
}
});
}
}
app层有简单的提供了三个控件,静态文本显示led灯的状态消息,两个按钮用于打开、关闭led灯,其他地方都没有特别需要注意的,只是要稍微注意一下Android.mk文件。
前面忘记说明framework层代码编译之后将生成services.jar这样一个jar包,所以在编写Android.mk文件时需要在LOCAL_JAVA_LIBRARIES指定加载services这个jar包,完整的Android.mk在下面:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := LedTest
LOCAL_SDK_VERSION := current
LOCAL_JAVA_LIBRARIES := services
#LOCAL_JNI_SHARED_LIBRARIES := libjni_mosaic libjni_tinyplanet
include $(BUILD_PACKAGE)
注意在app层是直接通过调用framework层的代码,所以在app层实例化了一个类LedService,好了代码就介绍完了。