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。