A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 梦缠绕的时候 黑马粉丝团   /  2018-7-4 11:14  /  2106 人查看  /  3 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

1. 概述

地磁使用的是AKM8975地磁传感器,其kernel部分的驱动代码路径是\kernel\drivers\

misc\akm8975.c,android的HAL层的路径是qics1003\hardware\libhardware\modules\

libsensors\AkmSensor.cpp,还有不同于其他传感器的是,地磁还需要第三方的库文件,AKM8975的库文件路径是在文件夹\qics1003\external\akmd8975下,主要提供了对计算磁场和方向的一些函数接口,其软件流程如下图所示:


图1.1  AKM8975软件流程

本文档主要对kernel部分和HAL部分进行分析,还有就是对库文件进行部分分析。

[url=]2. Kernel[/url]部分驱动

在kernel部分主要完成了创建input设备用来上报地磁数据、注册/dev/akm8975_dev设备用于完成第三方库文件对驱动的IOCTL控制,创建/sys/class/compass/akm8975/下的各个节点用于对重力方向地磁的delay与enable设置,读取akm8975中的数据、接收HAL层设置的重力数据。

1.        初始化

首先运行的是akm8975_probe()这个函数,在这里进行了以下关键操作:

u  s_akm->layout = pdata->layout;根据芯片贴片位置设置layout值(layout的取值方式在文档AK8975_Android_Porting_Guide_E.pdf第12页);

u  调用akm8975_i2c_check_device( )检测AKM8975的ID号( 0x48);

u  调用akm8975_input_init( )设置input输入系统,用于上报地磁方向的数据;

u  INIT_DELAYED_WORK(&s_akm->work, akm8975_delayed_work);初始化一个延时工作队列,用于在中断服务程序响应后读取数据;

u  调用request_threaded_irq( )添加中断服务程序akm8975_irq( );

u  调用misc_register( );注册一个杂项设备(/dev/akm8975_dev);

u  调用create_sysfs_interfaces( )创建了/sys/class/compass/akm8975/下的所有节点。

2.        中断处理

以下为akm8975的中断服务程序,首先禁止中断然后调用schedule_delayed_work()执行函数akm8975_delayed_work()完成读数据的操作,并且调用wake_up(&akm->drdy_wq)唤醒drdy_wq等待队列表示数据准备完成,并且调用enable_irq(akm->irq)使能中断。

static irqreturn_t akm8975_irq(int irq, void *handle)

{

         structakm8975_data *akm = handle;

    disable_irq_nosync(irq);

    schedule_delayed_work(&akm->work, 0);

         returnIRQ_HANDLED;

}

3.        /sys/class/compass/akm8975/下的节点

代码中通过调用create_device_attributes(akm->class_dev,akm8975_attributes);来创建了以下节点:

static struct device_attribute akm8975_attributes[] ={

         __ATTR(enable_acc,0660, akm8975_enable_acc_show, akm8975_enable_acc_store),

         __ATTR(enable_mag,0660, akm8975_enable_mag_show, akm8975_enable_mag_store),

         __ATTR(enable_ori,0660, akm8975_enable_ori_show, akm8975_enable_ori_store),

         __ATTR(delay_acc,  0660, akm8975_delay_acc_show,  akm8975_delay_acc_store),

         __ATTR(delay_mag,  0660, akm8975_delay_mag_show,  akm8975_delay_mag_store),

         __ATTR(delay_ori,  0660, akm8975_delay_ori_show,  akm8975_delay_ori_store),

#ifdef AKM8975_DEBUG_IF

         __ATTR(mode,  0220, NULL, akm8975_mode_store),

         __ATTR(bdata,0440, akm8975_bdata_show, NULL),

         __ATTR(asa,   0440, akm8975_asa_show, NULL),

#endif

         __ATTR_NULL,

};

这些节点的enable、delay函数的实现方式都非常接近,下面就介绍一下enable_acc节点的enable_store函数的操作:

static ssize_t akm8975_enable_acc_store(

         structdevice *dev, struct device_attribute *attr,char const *buf, size_t count)

{

         returnakm8975_sysfs_enable_store(dev_get_drvdata(dev), buf, count, ACC_DATA_FLAG);

}


static ssize_t akm8975_sysfs_enable_store(

         structakm8975_data *akm, char const *buf, size_t count, int pos)

{

         int en =0;

         if (NULL== buf)

                   return-EINVAL;

         if (0 ==count)

                   return0;

         if(false == get_value_as_int(buf, count, &en))

                   return-EINVAL;

         en = en? 1 : 0;

         mutex_lock(&akm->val_mutex);

         akm->enable_flag&= ~(1<<pos);

         akm->enable_flag|= ((uint32_t)(en))<<pos;

         mutex_unlock(&akm->val_mutex);

         akm8975_sysfs_update_active_status(akm);

         returncount;

}

在akm8975_enable_acc_store中调用了函数akm8975_sysfs_enable_store(),通过ACC_DATA_FLAG标识完成对enable_flag中G-sensor的使能标志位置1。这个标志主要是用来在上报数据时判断对应的标志是否使能。调用akm8975_sysfs_update_active_status()完成对akm->active变量置1,并且调用wake_up(&akm->open_wq)唤醒akm->open_wq工作队列。

还有一个节点是:

static struct bin_attribute akm8975_bin_attributes[] ={

         __BIN_ATTR(accel,0220, 6, NULL,NULL, akm8975_bin_accel_write),

         __BIN_ATTR_NULL

};

主要是实现HAL层向将G-sensor数据写入到akm8975中,供第三方库文件读取。



4.        /dev/akm8975_dev设备节点

驱动中注册了一个杂项设备akm8975_dev,主要实现了ioctl的功能供第三方的库文件操作,主要包含以下操作:

#define ECS_IOCTL_READ              _IOWR(AKMIO, 0x01, char*)

#define ECS_IOCTL_WRITE             _IOW(AKMIO, 0x02, char*)

#define ECS_IOCTL_SET_MODE          _IOW(AKMIO, 0x03, short)

#define ECS_IOCTL_GETDATA           _IOR(AKMIO, 0x04, char[SENSOR_DATA_SIZE])

#define ECS_IOCTL_SET_YPR           _IOW(AKMIO, 0x05,int[YPR_DATA_SIZE])

#define ECS_IOCTL_GET_OPEN_STATUS   _IOR(AKMIO, 0x06, int)

#define ECS_IOCTL_GET_CLOSE_STATUS  _IOR(AKMIO, 0x07, int)

#define ECS_IOCTL_GET_DELAY         _IOR(AKMIO, 0x08, long longint[AKM_NUM_SENSORS])

#define ECS_IOCTL_GET_LAYOUT        _IOR(AKMIO, 0x09, char)

#define ECS_IOCTL_GET_ACCEL                          _IOR(AKMIO, 0x30, short[3])

²  ECS_IOCTL_READ:通过I2C读取AKM8975寄存器中的数据

²  ECS_IOCTL_WRITE:通过I2C向AKM8975寄存器写入数据

²  ECS_IOCTL_SET_MODE:设置AKM8975的工作模式

²  ECS_IOCTL_GETDATA:读取地磁数据

²  ECS_IOCTL_SET_YPR:将第三方库计算的结果保存到驱动中,并产生input事件,在这里是通过调用AKECS_SetYPR( ),判断akm->enable_flag来确定是否上报

²  ECS_IOCTL_GET_OPEN_STATUS:查看akm8975的打开状态

²  ECS_IOCTL_GET_CLOSE_STATUS:查看akm8975的关闭状态

²  ECS_IOCTL_GET_DELAY:获得驱动设置的延时事件

²  ECS_IOCTL_GET_LAYOUT:获得芯片的贴片位置

²  ECS_IOCTL_GET_ACCEL:获得G-sensor的数据

[url=]3. HAL[/url]部分

地磁的HAL部分的代码在\qics1003\hardware\libhardware\modules\libsensors\AkmSensor.cpp中, 其提供了函数接口供qics1003\hardware\libhardware\modules\libsensors\

sensors.cpp调用,在sensors.cpp中调用open_sensors( ):

static int open_sensors(const struct hw_module_t*module, const char* id,struct hw_device_t** device)

{

        intstatus = -EINVAL;

        sensors_poll_context_t *dev = new sensors_poll_context_t();

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

******

        *device= &dev->device.common;

        status =0;

        returnstatus;

}

其中通过new sensors_poll_context_t( )创建了一个sensors_poll_context_t实例,首先执行构造函数sensors_poll_context_t( ):

sensors_poll_context_t::sensors_poll_context_t()

{

         mSensors[acc]= new AcclerSensor();

         mPollFds[acc].fd= mSensors[acc]->getFd();

         mPollFds[acc].events= POLLIN;

         mPollFds[acc].revents= 0;

    mSensors[akm] = new AkmSensor();

    mPollFds[akm].fd= mSensors[akm]->getFd();

   mPollFds[akm].events = POLLIN;

mPollFds[akm].revents= 0;

******

    mWritePipeFd= wakeFds[1];

   mPollFds[wake].fd = wakeFds[0];

   mPollFds[wake].events = POLLIN;

   mPollFds[wake].revents = 0;

}

在函数中又通过调用mSensors[akm] = new AkmSensor( )创建了一个AkmSensor实例,class AkmSensor则实现在上文提到的AkmSensor.cpp中。首先运行构造函数AkmSensor( ):

AkmSensor::AkmSensor()

: SensorBase(NULL,"compass"),

     mPendingMask(0),

     mInputReader(32)

{

         for (inti=0; i<numSensors; i++) {

                   mEnabled= 0;

                   mDelay= 0;

         }

****

    if (data_fd){

                   strcpy(input_sysfs_path,"/sys/class/compass/akm8975/");

                   input_sysfs_path_len =strlen(input_sysfs_path);

         } else {

                   input_sysfs_path[0]= '\0';

                   input_sysfs_path_len= 0;

         }

}

在SensorBase( )中,通过调用data_fd = openInput(data_name)将找到compass在input中对应的节点位置,并返回文件的句柄保存到data_fd中,在AkmSensor.cpp中就可以用data_fd句柄读取input系统中的数据。后面将/sys/class/compass/akm8975/路径保存到input_sysfs_path中,这个路径主要是用来在setEnable( )、setDelay( )时对enable和delay的节点进行设置。

同时在这个路径下还有一个节点“accel“,这个节点就是完成将G-sensor的数据写回到akm8975的驱动中,其代码如下:

int AkmSensor::setAccel(sensors_event_t* data)

{

         int err;

         int16_tacc[3];

         acc[0] =(int16_t)(data->acceleration.x / GRAVITY_EARTH * AKSC_LSG);

         acc[1] =(int16_t)(data->acceleration.y / GRAVITY_EARTH * AKSC_LSG);

         acc[2] =(int16_t)(data->acceleration.z / GRAVITY_EARTH * AKSC_LSG);

         strcpy(&input_sysfs_path[input_sysfs_path_len],"accel");

         err =write_sys_attribute(input_sysfs_path, (char*)acc, 6);

         if (err< 0) {

                   LOGD("AkmSensor:%s write failed.",

                            &input_sysfs_path[input_sysfs_path_len]);

         }

         returnerr;

}

这个写G-sensor的数据到akm8975的驱动中的函数在sensors.cpp的activate( ),pollEvents()中:

if (mSensors[akm]->getEnable(ID_M) ||mSensors[akm]->getEnable(ID_O)) {

if(!mag_enable_gsensor){

                   if(mSensors[acc]->getEnable(ID_A)){

                   gsensor_enable_flag= 1;               

                   }else{

                                     err = mSensors[acc]->setEnable(ID_A, 1);

                                     mag_enable_gsensor= 1;

                            }

                   }

         }

如果打开了地磁触感器就会判断mSensors[acc]->getEnable(ID_A)标志G-sensor是否打开,如果没有打开这会mSensors[acc]->setEnable(ID_A, 1)调用打开G-sensor。并且在pollEvents( )中通过(mSensors[akm]))->setAccel(&data[nb-1])将G-sensor的数据写入到akm8975的驱动中。

读取input子系统中的数据主要是通过函数readEvents( )调用processEvent( )完成读取input中的数据。

[url=]4. 第三方库文件[/url]

第三方库文件路径在qics1003\external\akmd8975文件夹下,首先进入man.c的main()函数大致流程如下:

l AKD_InitDevice()  打开"/dev/akm8975_dev"设备节点

l ReadAK8975FUSEROM() 读取AKM8975的ID

l MeasureSNGLoop() 进入地磁测量代码

在MeasureSNGLoop()中完成了读取在驱动中设置的delay、读取地磁sensor中的数据、计算地磁方位等操作,代码大致流程如下:

l GetInterval() 读取驱动中设置的delay

l AKD_SetMode() 通过IOCTL中的cmd:ECS_IOCTL_SET_MODE设置AKM8975到采样模式

l AKD_GetMagneticData() 通过IOCTL中的cmd:ECS_IOCTL_GETDATA获取采样数据

l AKD_GetAccelerationData()  在AOT_GetAccelerationData函数中通过IOCTL中的cmd:ECS_IOCTL_GET_ACCEL获得G-sensor数据

l CalcDirection() 计算方向,在这里调用了libAK8975.a中的库函数计算地磁方向

l Disp_MeasurementResultHook() 在AKD_SetYPR( )中通过过IOCTL中的cmd:ECS_IOCTL_SET_YPR设置计算结果到驱动中,并触发input上报数据。




5. 附件

磁性零件

PCB布局标准推荐距离(mm)

磁性开关

30

扬声器

10 ~ 20

振动电机

10

摄像头模组

10

记忆卡插槽

5

屏蔽件

10

表 4.1  电子罗盘布局与磁性元件的距离推荐

电流波动(mA)

距离电源线PCB布局

标准推荐距离(mm)

2

0.2

10

1

50

5

100

10

200

20

3 个回复

倒序浏览
回复 使用道具 举报
回复 使用道具 举报
棒棒哒
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马