• 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。

  • 相关阅读:
    Reface.AppStarter 基本示例
    Reface.AppStarter 类型扫描 —— 获得项目中所有的实体类型
    多线程和异步有什么关联和区别?如何实现异步?
    事件总线功能库,Reface.EventBus 详细使用教程
    代理模式是什么?如何在 C# 中实现代理模式
    监听者模式在系统中的应用 —— 事件总线
    如何将 .NetFramework WebApi 按业务拆分成多个模块
    Reface.NPI 方法名称解析规则详解
    EF 太重,MyBatis 太轻,ORM 框架到底怎么选 ?
    Reface.AppStarter 框架初探
  • 原文地址:https://www.cnblogs.com/yiru/p/2864239.html
Copyright © 2020-2023  润新知