Bma020 acceleration sensor流程文档

Bma020 acceleration sensor流程

 

云科世纪   戴杨一如

 

 

 

 

 

壹、Kernel层:

源文件:kernel/drivers/input/misc/  Bma020_driver.c,     Bma020.c, bma020.h

 

一、数据采集的两种方式:

Bma020 默认通过轮询方式采集数据,只有当宏BMA020_ENABLE_IRQ被定义且结构体bma020_data 中bool interruptible变量为真时,才使用中断方式采集数据。在bma020_probe(), bma020_set_enable, bma020_setdelay中都可以看到如下条件判断:

#ifdef BMA020_ENABLE_IRQ

       if (data->interruptible) {

       ……

 

二、读写寄存器函数的两种方式:

当需要读写寄存器时,若宏BMA020_SMBUS被定义,则采用i2c_smbus方式,否则采用i2c_master方式,常见判断如下:

#ifdef BMA020_SMBUS

       tempvalue = i2c_smbus_read_word_data(client, 0x00);

#else

       i2c_master_send(client, (char*)&tempvalue, 1);

       i2c_master_recv(client, (char*)&tempvalue, 1);

#endif

 

三、宏BMA020_SET_BITSLICE

       在bma020中,当需要改写某个寄存器的某(几)个bit时(并不改整个字节),就会用到这个宏:

              #define BMA020_SET_BITSLICE(regvar, bitname, val)\

                       (regvar & ~bitname##__MSK) | ((val<<bitname##__POS)&bitname##__MSK) 

              它的作用是,先把寄存器值regvar中需要写的bit置零,然后将它的bitname位改写为val。

       同样,当需要读取寄存器的某(几)个bit时(并不是读取整个字节),会这个宏:

              #define BMA020_GET_BITSLICE(regvar, bitname)\

                     (regvar & bitname##__MSK) >> bitname##__POS

              它将用0覆盖regvar中用户不需要的值,只得到bitname所表示的值

       bitname##__MSK表示掩码,bitname##__POS表示位移。例如,若bitname占regvar中0~7的345bit,则MSK为二进制的00111000,POS为3

 

四、sysfs接口

       Bma020通过sysfs接口向上层提供获取/设置sensor enable、获取/设置sensor delay的方法。

              742行:static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR | S_IWGRP,

                          bma020_show_enable, bma020_set_enable);

static DEVICE_ATTR(poll_delay, S_IRUGO | S_IWUSR | S_IWGRP,

                          bma020_show_delay, bma020_set_delay);

              这个宏原型是:#define DEVICE_ATTR(_name, _mode, _show, _store) \

struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)

可见主要实现了bma020_show_enable, bma020_set_enable, bma020_show_delay, bma020_set_delay这几个回调函数。

 

五、流程

从模块初始化开始BMA020_init()->  i2c_add_driver()->  i2c_register_driver() ->  driver_register()  ->  bus_add_driver()  ->  driver_attach()  ->  __driver_attach()  ->  driver_probe_device()  -> really_probe()  其中有这样一句:

Dd.c 261行:ret = drv->probe(dev);

在这里调用了调用了驱动的探针函数bma020_probe()

接下来看探针函数。

测试适配器是否支持I2C_FUNC_I2C:

       760行:i2c_check_functionality(client->adapter, I2C_FUNC_I2C)

给主数据结构体分配空间:

       768行:data = kmalloc(sizeof(struct bma020_data), GFP_KERNEL)

创建设备客户端结构体,并立刻读取设备的0x00寄存器,若不为02则失败(778~796行)。

接下来给函数局部变量struct bma020_data *data赋值:

       data->bma020.bus_write = bma020_i2c_write;

       data->bma020.bus_read = bma020_i2c_read;

       data->bma020.delay_msec = bma020_i2c_delay;

       三个回调函数均在本源文件定义,bma020_i2c_write()与bma020_i2c_read()将在后面的bma020_read_accel_xyz()函数中被调用,到那时再具体讨论。bma020_i2c_delay()简单延时msec毫秒.

       接下来,调用软复位函数

801行: bma020_soft_reset();

该函数将设备0x0a寄存器写2, 软重置设备.

       紧接着初始化设备,调用初始化函数:

802行: bma020_init(&(data->bma020));

       可以看到, 传入的实参是刚才赋值的(data指针指向的bma020结构体)的地址,该结构体包含上述三个回调函数.在bma020_init()中, 该地址被赋给全局变量指针p_bma020, 即使之指向data->bma020, 随后,完成对p_bma020所指向结构体的一系列赋值,主要包括dev_addr, Chip Id, ml_version, al_version.

       然后调用另一个初始化函数,称为BMA_Init();

              在BMA_Init()中,先将寄存器地址0x14写入buffer[0]

寄存器0x14控制两个值:

4:3 bit Range设置可探测的加速度范围,分为:-2~2g,-4~4g,-8~8g;

2:0 bit Bandwidth设置采样频率,分为25~1500不等的7个等级;

              随后调用

539行:ret = BMA_I2C_RxData(buffer, 2);

在函数BMA_I2C_RxData 中,通过i2c传输函数i2c_transfer()与设备交互,完成Range,Bandwidth,SPI protocol的初始化。

       再调用杂项设备注册函数:

              810行:err = misc_register(&bma_device);

       接着初始化互斥锁:

              811行:mutex_init(&data->lock);

       I/O空间注册,为输入子系统申请 input_dev 结构

              818行:input_dev = input_allocate_device();

       把新申请的内存赋给公共变量可见的“输入设备结构体”指针input_dev,之后几行都通过这个指针,使用linux输入设备的操作管理设备。

              825行:data->input_dev = input_dev;

       初始化上述内存,给出设备名:

              826行:input_set_drvdata(input_dev, data);

              827行:input_dev->name = "acc";

       接下来通过input_set_capability()函数分别标记设备的x,y,z轴报值,使设备能响应上层的轮询事件。再通过input_set_abs_params()函数设置上报的最大值与最小值。

       注册输入设备:

              838行:input_register_device(input_dev);

       下面定义设备采集数据的方式,如上第一节所述,有两种可供选择的方式,分别是中断和轮询。

采用中断方式则注册中断处理函数:

              847行:err = request_irq(client->irq, bma020_irq_handler, IRQF_TRIGGER_RISING, "bma020", &data->bma020);

       采用轮询方式步骤较多,先初始化一个计时器:

              860行:hrtimer_init(。。。);

       然后给几个bma020_data结构体成员赋值,分别定义:轮询所需时间、读取一个fifo条目所需时间,并用前者除以后者,把得数赋予“fifo条目数目”。再实现计时器结构体中的重置回调函数:

              860~866行:hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);

                                   data->polling_delay = ns_to_ktime(200 * NSEC_PER_MSEC); //this data need to carefully modified

                                   data->time_to_read = 10000000LL;

                                   delay_ns = ktime_to_ns(data->polling_delay);

                                   do_div(delay_ns, data->time_to_read);

                                   data->entries = delay_ns;

                                   data->timer.function = bma020_timer_func;

              这里使用work queue机制,bma020_timer_func()中通过container_of(timer, struct bma020_data, timer); (610行)得到timer的父结构bma020_data,并利用bma020_data中的指针把给定工作(即&bma020_data->work)提交给创建的工作队列bma020_data->bma020_wq:

                     612行:queue_work(bma020_data->bma020_wq, &bma020_data->work);

       负责轮询的计时器触发了一个工作队列请求,我们需要一个线程来读取i2c上的数据,这种读取可以是缓慢且阻塞的。下面这句用于创建一个单线程的、属于驱动自己的工作队列以及相应的内核进程:

              870行:data->bma020_wq = create_singlethread_workqueue("bma020_wq");

       初始化工作队列:

877行:INIT_WORK(&data->work, bma020_work_func);

       再来看回调函数bma020_work_func()的实现。工作函数调用报值函数,向上报值,存储在input_event队列中:

       567行     bma020_report_acc_values();

              在其中调用读值函数bma020_read_accel_xyz(&acc);

                     读值函数首先调用BMA020_BUS_READ_FUNC(),它实际上调用了probe()运行之初,被赋予data->bma020的回调函数bma020_i2c_read(),这在上面提到过。

在bma020_i2c_write()与bma020_i2c_read()中,将reg_addr作为基地址,通过一个while循环,根据条件分别使用如上第二节所述两种读写寄存器方式,写/读 寄存器len次, 每次写/读地址+1。

可以看到此处BMA020_BUS_READ_FUNC()的第四实参是6,故该函数读取寄存器0x02~0x07的值

然后使用上述第三节的宏BMA020_GET_BITSLICE分别取出xyz轴的值,并左右移动54位以清零其它位。结果保存在acc指针指向的bma020acc_t结构中

       然后开始报值:

       575行:input_report_rel(bma020_data->input_dev, REL_RX, acc.x);

                  input_report_rel(bma020_data->input_dev, REL_RY, acc.y);

                  input_report_rel(bma020_data->input_dev, REL_RZ, acc.z);

                  input_sync(bma020_data->input_dev);

                     这两个函数都直接调用input_event(),input_event函数有四个参数dev type code value, 对于以上前三行来说,

Type:EV_REL(相对坐标,code值表示轨迹的类型,参看include/linux/input.h)

code:REL_RX / REL_RY / REL_RZ

value: xyz轴的值

                                   而第四行input_sync()的参数如下:

                                          Type:EV_SYN

                                          Code:SYN_REPORT

                                          Value:0

                     报值之后,report函数通过静态局部变量static int fcount 做一个判断,若连续5次三个轴报值的绝对值之和都大于200,则重置Gsensor。

              接着回到bma020_work_func,它调用hrtimer_start()来重置计时器。

上述为一次work的全部内容。

       到此,在probe()中便完成了以轮询方式收集数据的设置。

       接下来通过device_create_file函数在/sys/class/下创建两个属性文件,上层就通过对这些属性文件进行读写来完成对应的数据操作:

              880~889行:

    if (device_create_file(&input_dev->dev, &dev_attr_enable) < 0) {

             pr_err("Failed to create device file(%s)!\n", dev_attr_enable.attr.name);

             goto err_device_create_file;

      }

 

      if (device_create_file(&input_dev->dev,      &dev_attr_poll_delay) < 0) {

             pr_err("Failed to create device file(%s)!\n",

                           dev_attr_poll_delay.attr.name);

             goto err_device_create_file2;

      }

       接着实现休眠唤醒的回调函数:

              891,892行:      data->early_suspend.suspend = bma020_early_suspend;

                                          data->early_suspend.resume  = bma020_late_resume;

              在休眠函数中简单地调用另一个函数:

465行:bma020_set_mode(BMA020_MODE_SLEEP);

这个函数定义在bma020.c,调用的实参是2。在这个函数中调用BMA020_SET_BITSLICE,实际上是往0x15寄存器的WAKE_UP位写0,往0x0A寄存器的sleep位写1.

       385~390行:comres = p_bma020->BMA020_BUS_READ_FUNC(p_bma020->dev_addr, BMA020_WAKE_UP__REG, &data1, 1 );

              data1  = BMA020_SET_BITSLICE(data1, BMA020_WAKE_UP, mode);               

        comres += p_bma020->BMA020_BUS_READ_FUNC(p_bma020->dev_addr, BMA020_SLEEP__REG, &data2, 1 );

              data2  = BMA020_SET_BITSLICE(data2, BMA020_SLEEP, (mode>>1));

           comres += p_bma020->BMA020_BUS_WRITE_FUNC(p_bma020->dev_addr, BMA020_WAKE_UP__REG, &data1, 1);

             comres += p_bma020->BMA020_BUS_WRITE_FUNC(p_bma020->dev_addr, BMA020_SLEEP__REG, &data2, 1);

然后该写全局变量

       391行:p_bma020->mode = mode;

              唤醒函数也只是简单地调用bma020_set_mode(BMA020_MODE_NORMAL);不过是以0为参数。而实际上就是往0x15的WAKE_UP位写0,往0x0A寄存器的sleep位写0.

       回到probe中,注册休眠函数:

              894行:register_early_suspend(&data->early_suspend);

       最后,设置gsensor进入休眠模式:

              896行:bma020_set_mode(BMA020_MODE_SLEEP);

       以上就是探针函数probe()的全部流程,而本驱动注册时主要就是执行probe函数。

 

 

 

贰、HAL层

源文件:device/cct/common/libsku7sensors/  AccSensor.cpp , AccSensor.h

 

一、构造函数

       AccSensor::AccSensor()

首先给事件变量mPendingEvent的成员赋值,分别给这些成员赋值version、sensor、type、acceleration.status。注意到重力传感器的type是SENSOR_TYPE_ACCELEROMETER。

       然后通过sensor_get_class_path()去打开SYSFS文件系统下的文件这个文件:/sys/class/input/inputX/name,如果读到“ACC”,就说明找到,则令found = 1; 且把mClassPath的值写为“/sys/class/input/inputX”(就是那个name为ACC的节点)

二、使用sysfs接口的函数

1、setEnable()

这个函数可以开/关Gsensor,开和关由其第二个参数控制。函数内部会把开关命令与当前状态作对比,若相同则什么也不做。然后,当需要开时,调用本源文件的enable_sensor(),当需要关时,调用本源文件的disable_sensor(),最后改写设备状态mEnabled = newState;

 

(1)enable_sensor()

简单地调用writeDisable(0);

     再看writeDisable()函数。它用上述构造函数中获得的mClassPath路径、以O_RDWR方式去打开设备节点,而后进入逻辑判断,当形参为0时,

229行:buf[0] = '1';

并写入设备节点:

            234行:err = write(fd, buf, sizeof(buf));

     这与驱动程序中的bma020_set_enable()函数以下几行对应:

Bma020driver.c 645行 if (sysfs_streq(buf, "1"))

                                                new_enable = true;

                                                        else if (sysfs_streq(buf, "0"))

                                                         new_enable = false;

              于是实现了enable设备。

              (2)disable_sensor()

                     简单地调用writeDisable(1);细节同上。

       2、setDelay()

       用于设置延迟时间,先设置结构体变量:

              mDelay = ns;

       再调用本地函数update_delay();

              Update_dalay()中几乎是简单地调用本地函数set_delay()

                     Set_delay()简单地调用writeDelay(ns)

                            writeDelay(ns),类似刚刚(1)中的writeDisable(),先打开设备节点,随后将延迟秒数稍微处理后写入:

                                   write(fd, buf, strlen(buf)+1);

 

三、轮询函数

      reandEvents()

该函数前几行填充一些结构体并做一些判断,然后通过mInputReader.readEvent()函数从input event队列读值,再通过processEvent()函数做简单处理。然后判断event类型,若是EV_SYN,则认为这次读值的过程完成(完成了XYZ轴各1次的读取),一共读取count次,count为reandEvents()的形参。

 

 

 

 

 

叁、JNI层

 

源文件:frameworks/base/services/sensorservice/GravitySensor.cpp

              frameworks/base/libs/gui/Sensor.cpp

              frameworks/base/services/ sensorservice/SensorDevice.cpp

frameworks/base/core/jni/android_hardware_SensorManager.cpp

 

一、构造函数

GravitySensor::GravitySensor (sensor_t const* list, size_t count)

       构造函数先遍历数组list(第一个参数),若其type == SENSOR_TYPE_ACCELEROMETER(注意到HAL层AccSensor构造函数给事件变量赋值时,type = SENSOR_TYPE_ACCELEROMETER),则调用sensor()(定义在本章第二个源文件中),构造一个传感器,赋予mAccelerometer。且一旦找个一个匹配项就退出循环,故mAccelerometer的值是list的第一个Gsensor。

 

二、操作函数

       Process()。该函数接收事件参数,首先判断是否为加速度传感事件(type == SENSOR_TYPE_ACCELEROMETER),若是,则根据RotationMatrix中的设置对三个轴的值做一番修正,目的是缩小系统性误差。

       Activate(),setDelay()。这两个函数均调用类SensorFusion中的相应函数。在其中又会分别调用SensorDevice的activate()和setdelay()(定义于本章第三个源文件)。SensorDevice::setDelay(),SensorDevice::activate()递归。

 

三、getSensor()

       该函数为结构体sensor_t hwSensor赋值,随后将该结构传入sensor的构造函数:Sensor sensor(&hwSensor); 并返回该Sensor。

posted @ 2013-01-17 12:15  勒达与天鹅  阅读(2028)  评论(0编辑  收藏  举报