1. 概述
地磁使用AKM8975地磁传感器kernel驱动代码路径的一部分是\kernel\drivers\
misc\akm8975.c,android的HAL层的路径是qics1003\hardware\libhardware\modules\
libsensors\AkmSensor.cpp,与其它传感器不同的是,地磁还需要第三方库文件,AKM8975的库文件路径是文件夹\qics1003\external\akmd软件流程如下图所示:
图1.1 AKM8975软件流程
本文档主要正确kernel部分和HAL部分分析,部分分析库文件。
2. Kernel部分驱动
在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.中断处理
以下为akm禁止中断8975中断服务程序,然后调用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))
&nbp; 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的数据
3. HAL部分
地磁的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;
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;
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()
:
mPendingMask(0),
mInputReader(32)
{
for (inti=0; i<numSensors; i++) {
mEnabled[i]= 0;
mDelay[i]= 0;
}
****
if (data_fd){
} 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);
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{
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中的数据。
4. 第三方库文件
第三方库文件路径在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 |
表 4.2 电子罗盘布局与电源线的距离推荐