android傳感器(sensor)分析(以mma8451重力感應器爲基礎)

一、前言

 傳感器是現在手機上不可或缺的的一部分,到了android4.0系統總共支持13類的傳感器分別爲

#define SENSOR_TYPE_ACCELEROMETER       1
#define SENSOR_TYPE_MAGNETIC_FIELD      2
#define SENSOR_TYPE_ORIENTATION         3
#define SENSOR_TYPE_GYROSCOPE           4
#define SENSOR_TYPE_LIGHT               5
#define SENSOR_TYPE_PRESSURE            6
#define SENSOR_TYPE_TEMPERATURE         7   
#define SENSOR_TYPE_PROXIMITY           8
#define SENSOR_TYPE_GRAVITY             9
#define SENSOR_TYPE_LINEAR_ACCELERATION 10
#define SENSOR_TYPE_ROTATION_VECTOR     11
#define SENSOR_TYPE_RELATIVE_HUMIDITY   12
#define SENSOR_TYPE_AMBIENT_TEMPERATURE 13

對於這麼多類別的傳感器,android提供一個統一的架構來方便開發者使用。我們下面就要來分析這個架構。
本文以飛思卡爾mma8451 三軸重力感應器爲基礎來分析的。傳感器架構遵循了標準的android架構,包括應用程序層-> framework層->jni層->hal層->內核空間->設備驅動。
對於應用程序層,framework層和jni層我們僅僅做簡單的介紹,重點放在分析hal層、內核空間和驅動中,因爲作爲一個驅動工程師這纔是重點!

 
二、架構分析
 1.應用程序層
 從網上找到一個android應用程序訪問傳感器的方法,流程如下

//1、取得重力感應器Sensor對象
//在 Activity 中定義以下成員變量:
private SensorManager mManager = null;
private Sensor mSensor = null;
//以下代碼加入到 onCreate() 方法中:
mManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);  //創建SensorManager
mSensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);//得到重力感應器
//2、創建監聽器
//在 Activity 中定義以下成員變量:
private SensorEventListener mListener = null;
//以下代碼加入到 onCreate() 方法中:
mListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
public void onSensorChanged(SensorEvent event) {
float x = event.values[SensorManager.DATA_X];
float y = event.values[SensorManager.DATA_Y];
float z = event.values[SensorManager.DATA_Z];
doSomething(x, y, z);
}
};
//3、註冊監聽器
//以下代碼加入到 onResume() 方法中:
mManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
//這裏 SENSOR_DELAY_GAME 還可以是以下常量:
//SENSOR_DELAY_FASTEST
//SENSOR_DELAY_UI
//SENSOR_DELAY_NORMAL
//4、取消監聽器
//以下代碼加入到 onPause() 方法中:
mManager.unregisterListener(mListener);
參考:http://www.oschina.net/code/snippet_12_687

從上面流程來看,首先我們要獲取sensorservice,然後創建一個SensorManager,再從SensorManager從獲取到具體傳感器
最後創建一個監聽來監聽這個傳感器的事件。因此下面來看framework層。

2.framework層
SensorService代碼在android\frameworks\base\services\sensorservice\SensorService.cpp中,
app中通過getSystemService()方法可以訪問到getSystemService
SensorManager代碼在android\frameworks\base\core\java\android\hardware\SensorManager.java中,
他是sensor事件的總管理器,包括打開設備讀取事件 分發事件等功能


3jni層
jni層主要包含兩個文件
android\frameworks\base\core\jni\android_hardware_SensorManager.cpp//主要是method的註冊,爲java提供下層訪問接口
android\frameworks\base\services\sensorservice\SensorDevice.cpp//主要是動態鏈接庫訪問,jni層和hal層訪問方式是通過
動態鏈接庫來實現的,這個文件裏實現了動態鏈接庫的打開,和訪問函數封裝等一些操作

4.hal層
在這個層裏,我們主要就分析動態鏈接庫的實現的主要功能有哪些
這個庫實現代碼在android\hardware\imx\libsensors\sensors.cpp中它的入口代碼爲:

struct sensors_module_t HAL_MODULE_INFO_SYM = {
        common: {
                tag: HARDWARE_MODULE_TAG,
                version_major: 1,
                version_minor: 1,
                id: SENSORS_HARDWARE_MODULE_ID,
                name: "Freescale Sensor module",
                author: "Freescale Semiconductor Inc.",
                methods: &sensors_module_methods,//鏈接庫的操作集
        },
        get_sensors_list: sensors__get_sensors_list,    //sensorlist實現
};

從上面可以看出上層訪問sensor的話主要的是兩個,一是操作集sensors_module_methods,一個是sensorlist我們分別來看
首先是sensorlist

static int sensors__get_sensors_list(struct sensors_module_t* module,
                                     struct sensor_t const** list)
{
        *list = sSensorList;
        return ARRAY_SIZE(sSensorList);
}
static const struct sensor_t sSensorList[] = {
        { "MMA 3-axis Accelerometer",
          "Freescale Semiconductor Inc.",
          1, SENSORS_ACCELERATION_HANDLE,
          SENSOR_TYPE_ACCELEROMETER, RANGE_A, CONVERT_A, 0.30f, 20000, { } },
        { "MAG3110 3-axis Magnetic field sensor",
          "Freescale Semiconductor Inc.",
          1, SENSORS_MAGNETIC_FIELD_HANDLE,
          SENSOR_TYPE_MAGNETIC_FIELD, 1500.0f, CONVERT_M, 0.50f, 100000, { } },
        { "MAG3110 Orientation sensor",
          "Freescale Semiconductor Inc.",
          1, SENSORS_ORIENTATION_HANDLE,
          SENSOR_TYPE_ORIENTATION, 360.0f, CONVERT_O, 0.50f, 100000, { } },
        { "MPL3115 Pressure sensor",
          "Freescale Semiconductor Inc.",
          1, SENSORS_PRESSURE_HANDLE,
          SENSOR_TYPE_PRESSURE, 1100.0f, CONVERT_PRESSURE, 0.35f, 0, { } },
        { "MPL3115 Temperature sensor",
          "Freescale Semiconductor Inc.",
          1, SENSORS_TEMPERATURE_HANDLE,
          SENSOR_TYPE_TEMPERATURE, 85.0f, CONVERT_TEMPERATURE, 0.35f, 0, { } },
        { "ISL29023 Light sensor",
          "Intersil",
          1, SENSORS_LIGHT_HANDLE,
          SENSOR_TYPE_LIGHT, 16000.0f, 1.0f, 0.35f, 0, { } },
};

從這裏可以看出getsensorlist就是得到我們預先寫好的一個結構體數據的指針.這個數組是根據我們硬件的情況來的
根據不同的平臺需要不同的裁剪的修改。



然後來看sensors_module_methods傳感器操作集

static struct hw_module_methods_t sensors_module_methods = {
        open: open_sensors
};
static int open_sensors(const struct hw_module_t* module, const char* id,
                        struct hw_device_t** device)
{
        int status = -EINVAL;
        sensors_poll_context_t *dev = new sensors_poll_context_t();

        memset(&dev->device, 0, sizeof(sensors_poll_device_t));

        dev->device.common.tag = HARDWARE_DEVICE_TAG;
        dev->device.common.version  = 0;
        dev->device.common.module   = const_cast<hw_module_t*>(module);
        dev->device.common.close    = poll__close;
        dev->device.activate        = poll__activate;
        dev->device.setDelay        = poll__setDelay;
        dev->device.poll            = poll__poll;

        *device = &dev->device.common;
        status = 0;

        return status;
}

從上面可以看出一個傳感器dev的操作有最終有4個:poll__close、poll__activate、poll__setDelay、poll__poll
我們來看這四個函數分別實現什麼功能

static int poll__close(struct hw_device_t *dev) //直接刪除設備上下文
{
    sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
    if (ctx) {
        delete ctx;
    }
    return 0;
}

static int poll__activate(struct sensors_poll_device_t *dev,
        int handle, int enabled) {
    sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
    return ctx->activate(handle, enabled);
}               |
                V
int sensors_poll_context_t::activate(int handle, int enabled) {
    int index = handleToDriver(handle);
    int err = 0 ;

    if (index < 0) return index;
    if(handle == ID_O || handle ==  ID_M){
        err = mSensors[accel]->enable(ID_A, enabled);
        if(err)
            return err;
    }
    err |=  mSensors[index]->enable(handle, enabled);
    if (enabled && !err) {
        const char wakeMessage(WAKE_MESSAGE);
        int result = write(mWritePipeFd, &wakeMessage, 1);
        LOGE_IF(result<0, "error sending wake message (%s)", strerror(errno));
    }
    return err;
}

//注意這裏的mSensors的定義爲SensorBase* mSensors;也就是說這是SensorBase這個類的一個實例化那麼enable這個桉樹應該在SensorBase類裏實現的
代碼在android\hardware\imx\libsensors\SensorBase.cpp中

int SensorBase::enable(int32_t handle, int enabled)
{
    sensorBaseEnable(handle,enabled);
    return 0;
}

int SensorBase::sensorBaseEnable(int32_t handle,int enabled){
  。。。。。。。。。。。。。
    if((enable && mUser[what] == 1) || (enable ==0  &&  mUser[what] == 0 )) {
        snprintf(buf,sizeof(buf),"%d",enable);
        write_sysfs(sysfs_enable,buf,strlen(buf));  //向sysfs文件系統中寫入數據,這裏涉及到了文件系統的操作進入了內核空間。
        mEnabled &= ~(1<<what);
        mEnabled |= (uint32_t(enable)<<what);
    }

  。。。。。。。。。。。。。。
}


static int poll__setDelay(struct sensors_poll_device_t *dev,
        int handle, int64_t ns) {
    sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
    return ctx->setDelay(handle, ns);
}                   |
                    V
int SensorBase::sensorBaseSetDelay(int32_t handle, int64_t ns){
    char buf[6];
    int ms;
    ms = ns/1000/1000;
    if(ms < mMinPollDelay)
        ms = mMinPollDelay ;
    else if(ms > mMaxPollDelay)
        ms = mMaxPollDelay;
    snprintf(buf,sizeof(buf),"%d",ms);
    return write_sysfs(sysfs_poll,buf,strlen(buf)); //想文件系統中寫入數據 進入內核空間
}

static int poll__poll(struct sensors_poll_device_t *dev,
        sensors_event_t* data, int count) {
    sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
    return ctx->pollEvents(data, count);
}                   |
                    V
static int poll__poll(struct sensors_poll_device_t *dev,
        sensors_event_t* data, int count) {
    sensors_poll_context_t *ctx = (sensors_poll_context_t *)dev;
    return ctx->pollEvents(data, count);
}                   
我們來看pollEvents這個函數實現了設備事件的讀取
int sensors_poll_context_t::pollEvents(sensors_event_t* data, int count)
{
    int nbEvents = 0;
    int n = 0;

    do {
        // see if we have some leftover from the last poll()
        for (int i=0 ; count && i<numSensorDrivers ; i++) {     //循環讀取所有sensorlist中設備的event
            SensorBase* const sensor(mSensors[i]);

            if ((mPollFds[i].revents & POLLIN) || (sensor->hasPendingEvents())) {
                int nb = sensor->readEvents(data, count);       //讀取事件
                if (nb < count) {
                    // no more data for this sensor
                    mPollFds[i].revents = 0;
                }
                count -= nb;
                nbEvents += nb;
                data += nb;
            }
        }
。。。。。。。。。。。。。。。。

    return nbEvents;
}
繼續看

int SensorBase::readEvents(sensors_event_t* data, int count)
{
    。。。。。。
    ssize_t n = mInputReader.fill(data_fd);     //填充eventbuf
       //mInputReader代碼在android\hardware\imx\libsensors\InputEventReader.cpp中這個類,在這裏就不多做分析了,
       //內容比較簡單,就是從設備文件中讀取數據然後進行一定的處理傳送給上層
 。。。。。。。。
            for (int j=0 ; count && mPendingMask && j<numSensors ; j++) {

                if (mPendingMask & (1<<j)) {
                    mPendingMask &= ~(1<<j);
                    mPendingEvents[j].timestamp = time;
                    if (mEnabled & (1<<j)) {
                        *data++ = mPendingEvents[j];  //數據複製到buf中  
                        count--;
                        numEventReceived++; 
                    }
                }
            }
        。。。。。。。。。。
    return numEventReceived;    //返回讀數個數
}

從上面可以看出這個函數是將event的數據複製到 data這個buf中然後返回,我們仔細看這裏的一個參數data_fd
這是event讀取的關鍵,它是初始化是在SensorBase的構造函數中:

SensorBase::SensorBase(
        const char* dev_name,   //打開的設備文件的名字
        const char* data_name)
    : dev_name(dev_name), data_name(data_name),
      dev_fd(-1), data_fd(-1),
      mInputReader(64)
{
    if (data_name)
        data_fd = openInput(data_name);
}                       |
                        |
                        V
int SensorBase::openInput(const char* inputName)
{
    int fd = -1;
    int input_id = -1;
    const char *dirname = "/dev/input";
    const char *inputsysfs = "/sys/class/input";
    char devname[PATH_MAX];
    char *filename;
    DIR *dir;
    struct dirent *de;

    dir = opendir(dirname);
    if(dir == NULL)
        return -1;
    strcpy(devname, dirname);
    filename = devname + strlen(devname);
    *filename++ = '/';
    while((de = readdir(dir))) {
        if(de->d_name[0] == '.' &&
                (de->d_name[1] == '\0' ||
                        (de->d_name[1] == '.' && de->d_name[2] == '\0')))
            continue;
        strcpy(filename, de->d_name);
        fd = open(devname, O_RDONLY);       //這裏涉及到打開設備文件

       。。。。。。。。。。。。。。。。。。。。。。。。
    }
    closedir(dir);
    LOGE_IF(fd<0, "couldn't find '%s' input device", inputName);
    return fd;
}
到了這裏hal層的分析已經完成了,總結一下:當android上層打開這個動態鏈接庫的時候,sensors_poll_context_t這個類會
自動對每個sensor進行例化,然後打開所有的sensor。當上層訪問PollEvent時,就會直接將讀取到的event返回給上層。hal層
對內核設備文件的訪問,並不是標準的接口,而是廠家自己規定的,因此要訪問不同的設備,還要根據驅動的不同配合着來

5.kernel層
  對於kernel的input系統分析,我們前邊有篇文章專門介紹過http://blog.csdn.net/dkleikesa/article/details/9384009大家可以參考

6.內核驅動
 剩下的主要是設備驅動。在kernel中傳感器設備都是hwmon類的,hwmon全名hardware monitor,是linux規定的硬件監測的設備類,這個類爲我們提供了,設備文件和設備屬性設定的通用方法,不過在freescale的android平臺中貌似都沒用到,這個平臺android層和kernel層是通過input設備來通信的,下面 我們以mma8451三軸重力感應器爲例子來講述sensor驅動的具體寫法

 mma8451是一款i2c接口的傳感器,驅動的整體架構也是跟其他i2c設備一樣的,因此我們重點來開 probe 和 event發送的函數

static int __devinit mma8451_probe(struct i2c_client *client,
				   const struct i2c_device_id *id)
{
	int result, client_id;
	struct input_dev *idev;
	struct i2c_adapter *adapter;

	mma8451_i2c_client = client;
	adapter = to_i2c_adapter(client->dev.parent);
	result = i2c_check_functionality(adapter,   //檢測此i2c設備是否支持I2C_FUNC_SMBUS_BYTE和I2C_FUNC_SMBUS_BYTE_DATA這兩個屬性
					 I2C_FUNC_SMBUS_BYTE |
					 I2C_FUNC_SMBUS_BYTE_DATA);
	if (!result)
		goto err_out;

	client_id = i2c_smbus_read_byte_data(client, MMA8451_WHO_AM_I);//讀ID

	if (client_id != MMA8451_ID && client_id != MMA8452_ID
	    && client_id != MMA8453_ID) {
		dev_err(&client->dev,
			"read chip ID 0x%x is not equal to 0x%x or 0x%x!\n",
			result, MMA8451_ID, MMA8452_ID);
		result = -EINVAL;
		goto err_out;
	}

	/* Initialize the MMA8451 chip */
	result = mma8451_change_mode(client, senstive_mode);   //初始化mma8451 就是配置一些寄存器
	if (result) {
		dev_err(&client->dev,
			"error when init mma8451 chip:(%d)\n", result);
		goto err_out;
	}

	hwmon_dev = hwmon_device_register(&client->dev);//創建一個硬件監聽設備
	if (!hwmon_dev) {
		result = -ENOMEM;
		dev_err(&client->dev, "error when register hwmon device\n");
		goto err_out;
	}

	mma8451_idev = input_allocate_polled_device();  //分配一個poll類型的input設備
	if (!mma8451_idev) {
		result = -ENOMEM;
		dev_err(&client->dev, "alloc poll device failed!\n");
		goto err_alloc_poll_device;
	}
	mma8451_idev->poll = mma8451_dev_poll;//poll回調函數
	mma8451_idev->poll_interval = POLL_INTERVAL;
	mma8451_idev->poll_interval_min = POLL_INTERVAL_MIN;
	mma8451_idev->poll_interval_max = POLL_INTERVAL_MAX;
	idev = mma8451_idev->input;
	idev->name = "mma845x";//這個名字必須和hal層的名字對應上否則找不到設備文件
	idev->id.bustype = BUS_I2C;
	idev->evbit[0] = BIT_MASK(EV_ABS);

	input_set_abs_params(idev, ABS_X, -8192, 8191, INPUT_FUZZ, INPUT_FLAT); //設備支持的3個數據
	input_set_abs_params(idev, ABS_Y, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);
	input_set_abs_params(idev, ABS_Z, -8192, 8191, INPUT_FUZZ, INPUT_FLAT);

	result = input_register_polled_device(mma8451_idev);//註冊這個poll設備
	if (result) {
		dev_err(&client->dev, "register poll device failed!\n");
		goto err_register_polled_device;
	}
	result = sysfs_create_group(&idev->dev.kobj, &mma8451_attr_group);//爲這個設備創建sysfs 設備文件
	if (result) {
		dev_err(&client->dev, "create device file failed!\n");
		result = -EINVAL;
		goto err_register_polled_device;
	}
	mma_status.position = *(int *)client->dev.platform_data;
	return 0;
err_register_polled_device:
	input_free_polled_device(mma8451_idev);
err_alloc_poll_device:
	hwmon_device_unregister(&client->dev);
err_out:
	return result;
}

從這個函數可知,重力感應器是屬於poll類型的設備,不是普通的input設備,event格式走的是標準的input_event格式,但是隻有
EV_ABS 這一個屬性,爲的是event不會被android標準input子系統獲取走。這個設備的event只有三個數 分別爲三軸的重力的大小


下面我們看,poll設備註冊的poll回調函數

static void mma8451_dev_poll(struct input_polled_dev *dev)
{
	report_abs();
}

static void report_abs(void)
{
	short x, y, z;
	int result;
	int retry = 3;

	mutex_lock(&mma8451_lock);
	if (mma_status.active == MMA_STANDBY)
		goto out;
	/* wait for the data ready */
	do {
		result = i2c_smbus_read_byte_data(mma8451_i2c_client,
						  MMA8451_STATUS);
		retry--;
		msleep(1);
	} while (!(result & MMA8451_STATUS_ZYXDR) && retry > 0);
	if (retry == 0)
		goto out;
	if (mma8451_read_data(&x, &y, &z) != 0)
		goto out;
	mma8451_adjust_position(&x, &y, &z); //調整xyz 三軸
	input_report_abs(mma8451_idev->input, ABS_X, x);    //報告事件
	input_report_abs(mma8451_idev->input, ABS_Y, y);
	input_report_abs(mma8451_idev->input, ABS_Z, z);
	input_sync(mma8451_idev->input);
out:
	mutex_unlock(&mma8451_lock);
}
//從這個函數可以看出 sensor的事件報告方式是標準的input_report_abs()函數,並沒什麼特殊性不多做說明


三、總結
到了這裏整個的sensor訪問流程就完成了。這個流程,內核部分走的是標準的input設備 ,hal層將內核設備訪問封裝成動態鏈接庫,jni層通過訪問動態鏈接庫,來訪問硬件設備。kernel和動態鏈接庫直接並沒有標準的接口,這裏把傳感器作爲input設備只是實現功能的一種方法,我們也可以把傳感器設置爲一個字符設備,然後通過修改hal層來實現相應的功能。當然由於靈活性,當我們爲整個系統添加或者刪除一個傳感器時,也要修改更多的代碼。有利也有弊吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章