http://blog.csdn.net/morixinguan/article/details/76850600
上节,写WIFI MAC地址的时候我们已经知道,MTKAndroid系统的Gsensor校准的数据其实也是存储在NVRAM中的,Gsensor隶属于传感器系统架构。
接下来我们来看下Gsensor校准的基准图像:
那么如何来校准Gsensor的X,Y,Z三个方向呢?我们可以参考MTK提供的工厂测试factroymode的代码:
位置在:vendor\mediatek\proprietary\factory\src\test\ftm_gs_cali.c ftm_gsensor.c
ftm_gs_cali.c就是校准的源码,我们可以打开来看看它具体的实现原理:
在ftm_gs_cali.c的static void*gs_cali_update_iv_thread(void *priv)这个函数中,我们可以看到如何校准Gsensor数值的过程:
static void *gs_cali_update_iv_thread(void *priv) { struct gsc_data *dat = (struct gsc_data *)priv; struct gsc_priv *gsc = &dat->gsc; struct itemview *iv = dat->iv; int err = 0, len = 0; char *status; HwmData cali; static int op = -1; int max_retry = 3, retry_period = 100, retry=0; unsigned int flags = 1; LOGD(TAG "%s: Start\n", __FUNCTION__); //打开gsensor err = gsensor_open(&(gsc->fd)); if(err) { memset(dat->info, 0x00, sizeof(dat->info)); sprintf(dat->info, uistr_info_sensor_init_fail); iv->redraw(iv); GSCLOGE("gs_cali_open() err = %d(%s)\n", err, dat->info); pthread_exit(NULL); return NULL; } /** Enable G-sensor **/ //使能Gsensor,让它开始工作 while ((err = ioctl(gsc->fd, GSENSOR_IOCTL_INIT, &flags)) && (retry ++ < max_retry)) usleep(retry_period*1000); if (err) { LOGD("enable g-sensor fail: %s", strerror(errno)); return -1; } while(1) { if(dat->exit_thd) { break; } pthread_mutex_lock(&dat->gsc.evtmutex); if(op != dat->gsc.pending_op) { op = dat->gsc.pending_op; GSCLOGD("op: %d\n", dat->gsc.pending_op); } pthread_mutex_unlock(&dat->gsc.evtmutex); err = 0; if(op == GS_OP_CLEAR) { memset(&dat->gsc.cali_nvram, 0x00, sizeof(dat->gsc.cali_nvram)); memset(&dat->gsc.cali_drv, 0x00, sizeof(dat->gsc.cali_drv)); err = gsensor_rst_cali(gsc->fd); if(err) { GSCLOGE("rst calibration: %d\n", err); } else if((err = gsensor_write_nvram(&dat->gsc.cali_nvram)) != 0) { GSCLOGE("write nvram: %d\n", err); } if(err) { snprintf(dat->gsc.status, sizeof(dat->gsc.status), uistr_info_sensor_cali_fail); //dat->mod->test_result = FTM_TEST_FAIL; } else { snprintf(dat->gsc.status, sizeof(dat->gsc.status), uistr_info_sensor_cali_ok); //dat->mod->test_result = FTM_TEST_PASS; } gsc->bUpToDate = false; pthread_mutex_lock(&dat->gsc.evtmutex); dat->gsc.pending_op = GS_OP_NONE; pthread_mutex_unlock(&dat->gsc.evtmutex); } else if(op == GS_OP_CALI_PRE) { err = 0; /*by-pass*/ snprintf(dat->gsc.status, sizeof(dat->gsc.status), uistr_info_sensor_cali_ongoing); pthread_mutex_lock(&dat->gsc.evtmutex); dat->gsc.pending_op = GS_OP_CALI; pthread_mutex_unlock(&dat->gsc.evtmutex); } else if(op == GS_OP_CALI) { if(!dat->gsc.cali_delay || !dat->gsc.cali_num || !dat->gsc.cali_tolerance) { GSCLOGE("ignore calibration: %d %d %d\n", dat->gsc.cali_delay, dat->gsc.cali_num, dat->gsc.cali_tolerance); } //执行校准的动作 else if((err = gsensor_calibration(gsc->fd, dat->gsc.cali_delay, dat->gsc.cali_num, dat->gsc.cali_tolerance, 0, &cali)) != 0) { GSCLOGE("calibrate acc: %d\n", err); } //设置校准cali,让校准的数据开始生效 else if((err = gsensor_set_cali(gsc->fd, &cali)) != 0) { GSCLOGE("set calibration fail: (%s) %d\n", strerror(errno), err); } else if((err = gsensor_get_cali(gsc->fd, &cali)) != 0) { GSCLOGE("get calibration fail: (%s) %d\n", strerror(errno), err); } //将校准的数据写入到nvram中去 else if((err = gsensor_write_nvram(&cali)) != 0) { GSCLOGE("write nvram fail: (%s) %d\n", strerror(errno), err); } else { dat->gsc.cali_delay = dat->gsc.cali_num = dat->gsc.cali_tolerance = 0; dat->gsc.bUpToDate = false; } if(err) { len = snprintf(dat->gsc.status, sizeof(dat->gsc.status), uistr_info_sensor_cali_fail); dat->mod->test_result = FTM_TEST_FAIL; } else { len = snprintf(dat->gsc.status, sizeof(dat->gsc.status), uistr_info_sensor_cali_ok); dat->mod->test_result = FTM_TEST_PASS; } pthread_mutex_lock(&dat->gsc.evtmutex); dat->gsc.pending_op = GS_OP_NONE; pthread_mutex_unlock(&dat->gsc.evtmutex); } err = gs_cali_update_info(gsc); if(err) { GSCLOGE("gs_cali_update_info() = (%s), %d\n", strerror(errno), err); break; } len = 0; len += snprintf(dat->info+len, sizeof(dat->info)-len, "R: %+7.4f %+7.4f %+7.4f\n", gsc->dat.x, gsc->dat.y, gsc->dat.z); len += snprintf(dat->info+len, sizeof(dat->info)-len, "D: %+7.4f %+7.4f %+7.4f\n", gsc->cali_drv.x, gsc->cali_drv.y, gsc->cali_drv.z); len += snprintf(dat->info+len, sizeof(dat->info)-len, "N: %+7.4f %+7.4f %+7.4f\n", gsc->cali_nvram.x, gsc->cali_nvram.y, gsc->cali_nvram.z); len += snprintf(dat->info+len, sizeof(dat->info)-len, "%s\n", gsc->status); iv->set_text(iv, &dat->text); iv->redraw(iv); } //关闭gsensor gs_cali_close(gsc); LOGD(TAG "%s: Exit\n", __FUNCTION__); pthread_exit(NULL); return NULL; }
这段代码虽然很多,但我们可以找出校准Gsensor的核心流程主要如下:
(1) 打开Gsensor
(2) 使能Gsensor
(3) 执行校准的动作
(4) 设置校准的Cail,让校准的数据生效
(5) 将校准得到的数据写入到nvram中
(6) 关闭Gsensor
核心流程我们已经清楚了,那么接下来如何来写这个程序呢?我们要弄明白,这些函数上哪个文件里去找这是第一步:
通过grep命令搜索相关函数,最终确定,这些函数的头文件在:
./pskyed/libs/em_emmc_comm/libhwm/include/libhwm.h这里,在这个头文件中有相关的函数可以给我们使用:
extern int gsensor_calibration(int fd, int period, int count, int tolerance, int trace, HwmData *cali); extern int gsensor_write_nvram(HwmData *dat); extern int gsensor_read_nvram(HwmData *dat); extern int gsensor_rst_cali(int fd); extern int gsensor_set_cali(int fd, HwmData *dat); extern int gsensor_get_cali(int fd, HwmData *dat); extern int gsensor_read(int fd, HwmData *dat); extern int gsensor_init(int fd); extern int gsensor_close(int fd); extern int gsensor_open(int *fd); extern int gyroscope_calibration(int fd, int period, int count, int tolerance, int trace, HwmData *cali); extern int gyroscope_write_nvram(HwmData *dat); extern int gyroscope_read_nvram(HwmData *dat); extern int gyroscope_rst_cali(int fd); extern int gyroscope_set_cali(int fd, HwmData *dat); extern int gyroscope_get_cali(int fd, HwmData *dat); extern int gyroscope_read(int fd, HwmData *dat); extern int gyroscope_close(int fd); extern int gyroscope_open(int *fd); extern int gyroscope_init(int fd);
那么这些函数的源码在哪里呢?源码是没有的,因为MTK厂商将这部分代码给封装成了so动态库文件,所以,我们需要找到这个头文件对应的so文件,这样我们才能使用这个头文件,调用到so动态库中的函数。
通过搜索得知,这个so动态库文件在以下路径:
./pskyed/libs/em_emmc_comm/libhwm/libhwm.so
知道这些以后,下面我们就可以写一个简单的程序来验证这个过程了,这个留给读者自己去测试,接口我已经写好了,我的项目源码不便于公开,请读者自己拿去修改验证,流程是一样的,接口没有改过,至于想实现什么样的效果请读者自己去尝试添加,移植我的程序进行修改。
下面实现这个校准程序:
gs_cali.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <sys/mount.h> #include <sys/statfs.h> #include <sys/reboot.h> #include <dirent.h> #include <linux/input.h> #include <math.h> #include <dirent.h> #include <ctype.h> #include <errno.h> #include <linux/hwmsensor.h> #include <linux/sensors_io.h> #include "Keypad.h" #include "libhwm.h" #define GSENSOR_NAME "/dev/gsensor" #define OPEN_FILE_FAIR -1 #define OPEN_SUCCESS 0 #define RETURE_SUCCESS 0 #define ENABLED_SUCCESS 0 #define GSENSOR_CALIBRATION_SUCCESS 0 #define ENABLED_FAIR -2 #define CALIBRATION_FAIR -3 #define SET_CALIBRATION_FAIR -4 #define WRITE_NVRAM_FAIR -5 int gs_fd ; //打开gsensor int gs_open(char *gs_name) ; //关闭gsensor int gs_close(int fd) ; //gsensor开始工作 int gs_enable(unsigned int command) ; //校准gsensor int gs_calibration(unsigned int cali_delay ,unsigned int cali_num ,unsigned int cali_tolerance) ; int main(void) { int gs_ret = 0 ; int cali_delay = 50; int cali_num = 20; //这里的20表示把校准的数值做20次平均 //如果想要更精确,也可以做40次平均计算 int cali_tolerance = 20 ; //40 //打开gsensor gs_ret = gs_open(GSENSOR_NAME); if(gs_ret != 0){ printf("gs open fair!\n") ; return -1 ; } //使能gsensor gs_ret = gs_enable(GSENSOR_IOCTL_INIT); if(gs_ret != 0){ printf("gs enable fair!\n") ; return -2 ; } //校准---->包括:执行校准的动作、设置校准数值、将校准数值写入nvram gs_ret = gs_calibration(cali_delay,cali_num,cali_tolerance); if(gs_ret != 0){ printf("gs_calibration fair!\n") ; return -3 ; } //关闭gsensor gs_ret = gs_close(gs_fd); if(gs_ret != 0){ printf("gs_close fair!\n"); return -4 ; } printf("runexec call gsensorCalibrate end\n"); return 0 ; } //1、open int gs_open(char *gs_name) { gs_fd = open(gs_name,O_RDONLY) ; if(gs_fd < 0) { printf("open gsensor dev fair!\n") ; return OPEN_FILE_FAIR ; } printf("gsensor open success!\n"); return OPEN_SUCCESS ; } //2、enable gsensor int gs_enable(unsigned int command) { int err = 0; unsigned int flags = 1; int max_retry = 3, retry_period = 100, retry=0; while ((err = ioctl(gs_fd, command, &flags)) && (retry ++ < max_retry)) ; usleep(retry_period*1000); if (err) { printf("enable g-sensor fail: %s", strerror(errno)); return ENABLED_FAIR; } printf("enable gsensor success!\n"); return ENABLED_SUCCESS ; } //3、校准 int gs_calibration(unsigned int cali_delay ,unsigned int cali_num ,unsigned cali_tolerance) { int err ; int flag = 0; HwmData dat; //dat.x dat.y dat.z HwmData cali; HwmData cali_nvram; while(1) { //执行校准的动作 err = gsensor_calibration(gs_fd , cali_delay , cali_num , cali_tolerance , 0 , &cali); if(err != 0) { printf("calibrate acc: %d\n", err); return CALIBRATION_FAIR ; } //设置校准cali,让校准数据生效 err = gsensor_set_cali(gs_fd,&cali) ; if(err != 0) { printf("set calibration fail: (%s) %d\n", strerror(errno), err); return SET_CALIBRATION_FAIR ; } //将校准数据写入nvram中 err = gsensor_write_nvram(&cali) ; if(err != 0) { printf ("write nvram fail: (%s) %d\n", strerror(errno), err); return WRITE_NVRAM_FAIR ; } flag = 1 ; if(flag == 1) { printf("Gsensor calibrate success!\n") ; break ; } } return GSENSOR_CALIBRATION_SUCCESS ; } //关闭 int gs_close(int fd) { close(fd) ; return 0 ; }
然后写一个简单的Android.mk
LOCAL_PATH:=$(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LOCAL_SHARED_LIBRARIES += libcutils libutils libhwm LOCAL_STATIC_LIBRARIES += libz libstdc++ LOCAL_SRC_FILES:= \ gs_cali.c LOCAL_MODULE:= gsensor_calibrate include $(BUILD_EXECUTABLE)
将.c和Android.mk一并放在external下的一个自己定义的文件夹中,比如gs_cali文件夹,然后最后编译会在out目录下生成gsensor_calibrate这个二进制文件。
特别需要注意一点:使用这种方法进行校准后,需要将gsensor驱动的自动校准功能屏蔽,具体屏蔽方法还需要分析驱动源代码。
但这仅仅只是校准数值而已,如何把校准完的数值读出来呢?参考ftm_gsensor.c这个文件源代码,最终得知是通过打开/dev/gsensor这个节点,然后通过总线访问机制获取:
/sys/bus/platform/drivers/gsensor/sensordata这个文件存放的数据。
读取Gsensor的x,y,z的数据的方法可以参考static int gsensor_read(struct acc_priv *acc)这个函数的实现方法,但它的方法和我的是有区别的,MTK中实现的gsensor_read方法是读取从驱动中读取已经转化成十进制的数据,而我读取的是原始数据十六进制,所以必须要做进制转换,我们可以来看下这个函数:
static int gsensor_read(struct acc_priv *acc) { static char buf[128]; int x, y, z, err; if(acc->fd == -1) { FTGLOGE("invalid file descriptor\n"); err = -EINVAL; } else if((acc->support_selftest == 1) && (!acc->selftest) && (err = gsensor_selftest(acc, 10, 20))) { FTGLOGE("selftest fail: %s(%d)\n", strerror(errno), errno); } else { //使用GSENSOR_IOCTL_READ_SENSORDATA命令获取sensor的数据,并存储在buf里 err = ioctl(acc->fd, GSENSOR_IOCTL_READ_SENSORDATA, buf); if(err) { FTGLOGE("read data fail: %s(%d)\n", strerror(errno), errno); } //从buf中将x,y,z三个值取出来 else if(3 != sscanf(buf, "%x %x %x", &x, &y, &z)) { FTGLOGE("read format fail: %s(%d)\n", strerror(errno), errno); } else { //除以1000,并转成浮点数 acc->evt.x = (float)(x)/1000; acc->evt.y = (float)(y)/1000; acc->evt.z = (float)(z)/1000; err = 0; gsensor_statistic(acc); //返回gsensor数据 //add sensor data to struct sp_ata_data for PC side return_data.gsensor.g_sensor_x = acc->evt.x; return_data.gsensor.g_sensor_y = acc->evt.y; return_data.gsensor.g_sensor_z = acc->evt.z; return_data.gsensor.accuracy = 3; } } return err; }
预知详情,可以去分析factory工厂测试的源码,看看具体实现,这里就简单的一笔带过了。
当然我们也可以采用:./pskyed/libs/em_emmc_comm/libhwm/include/libhwm.h这里面的读取gsensor数据的接口,如果要用就需要在Android.mk中包含相关的动态库。
使用adb进入Android根文件系统,此时,通过cat命令可以得知/sys/bus/platform/drivers/gsensor/sensordata这个文件存放的数据并不是字符串,而是十六进制数据,以下是机器平放在桌子上读取的x,y,z的数值:
Sensordata文件的数值: 算法转换:
-------------------------------------------------------------------------------------------------------------------------------
X:0000 转十进制(float)0/1000 = 0
Y:0072 转十进制(float)114/ 1000 = 0.114
Z:264f 转十进制(float)9807/ 1000 = 9.807
-------------------------------------------------------------------------------------------------------------------------------
x,y,z数值的校准范围是(0,0,9.8),以上数值说明机器校准后的数值是正确的,误差比较小。
在Window上用Notepad++打开这个文件看到里面的数据如下。
使用Winhex查看sensordata这个文件
以下是我实现的方法:
//Gsensor读取
//这个文件存放的数据不是字符串,而是十六进制数据,所以必须做进制转换,然后才能转成浮点数
#define GSENSOR_NAME "/sys/bus/platform/drivers/gsensor/sensordata" //存储gsensor节点数据的结构体 struct gsensor_info { //x,y,z坐标值 char x[10] ; char y[10] ; char z[10] ; }; int Hex_to_dec(const char* str) ; static int check_flag ; int Gsensor_Test() { struct gsensor_info g ; char ln[80];//用于存取读出数据的数组 FILE *f; int i,n; char *p; int x,y,z ; float x1,y1,z1 ; int xv = 0 , yv = 0 , zv = 0 ; while(1) { f=fopen(GSENSOR_NAME,"r");//打开txt文件 if(NULL==f){ printf("Can not open file sensordata!\n");//判断是否可以打开文件 return 1; } i=0; while (1) { //从文件中读取一行数据 if (NULL==fgets(ln,80,f)) break; p = ln ; sscanf(p,"%s%s%s",g.x,g.y,g.z); i++; } //存储的数据是十六进制,需要做数据转换 x = Hex_to_dec(g.x); y = Hex_to_dec(g.y); z = Hex_to_dec(g.z); x1 = (float)x / 1000 ; y1 = (float)y / 1000 ; z1 = (float)z / 1000 ; printf("x: %4.2f y:%4.2f z:%4.2f\n",x1,y1,z1); break ; } return 0 ; while_r: while(1); } //16进制转10进制算法实现 int Hex_to_dec(const char* str) { int value; if (! str) { return 0; } value = 0; while (1) { if ((*str >= '0') && (*str <= '9')) { value = value*16 + (*str - '0'); } else if ((*str >= 'A') && (*str <= 'F')) { value = value*16 + (*str - 'A') + 10; } else if ((*str >= 'a') && (*str <= 'f')) { value = value*16 + (*str - 'a') + 10; } else { break; } str++; } return value; }