安卓9.0Sensor框架

前言

本来如果只是给传感器写个驱动并提供能读取温湿度数据的节点,是一件比较轻松的事情,但是最近上层应用的同事要求我们按照安卓标准的流程来,这样他们就能通过注册一个服务直接读取传感器事件数据了。这样做的好处就是第三方的应用也能正常读取温湿度的数据并展示。

正文

网上分析安卓9.0 sensor相关的资料不多,下面找到了一位大神对安卓9.0整个sensor框架总结的流程图:

虽然流程比较粗糙,但是也有助于我们跟踪代码。这里重点说一下,sensor架构中的HAL层分为两部分:

  • (1)安卓官方实现部分

hardware/libhardware/modules/sensors

  • (2)芯片产商实现部分(MTK平台)

vendor/mediatek/proprietary/hardware/sensor

一般来讲,在适配一款新的sensor,改动只会涉及vendor层到kernel层,再往上都是安卓标准的,但是为了了解整个流程怎么走的,参考这位大神的博客,在这里我也稍微介绍一下framework层的部分。

代码路径:

frameworks\base\services\java\com\android\server\SystemServer.java

private void startBootstrapServices() {...mSensorServiceStart = SystemServerInitThreadPool.get().submit(() -> {TimingsTraceLog traceLog = new TimingsTraceLog(SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);traceLog.traceBegin(START_SENSOR_SERVICE);startSensorService(); /* 调用JNI接口 */traceLog.traceEnd();}, START_SENSOR_SERVICE);...
}

system_server启动之后会通过JNI接口启动sensorService。

代码路径:

frameworks\base\services\core\jni\com_android_server_SystemServer.cpp

static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) {char propBuf[PROPERTY_VALUE_MAX];property_get("system_init.startsensorservice", propBuf, "1");if (strcmp(propBuf, "1") == 0) {SensorService::instantiate();}}/** JNI registration.*/static const JNINativeMethod gMethods[] = {/* name, signature, funcPtr */{ "startSensorService", "()V", (void*) android_server_SystemServer_startSensorService },{ "startHidlServices", "()V", (void*) android_server_SystemServer_startHidlServices },};

从上面可以发现,最后调用到

android_server_SystemServer_startSensorService

函数,里面会判断属性

system_init.startsensorservice

是否为1,然后才会真正去启动

SensorService

服务。所以这里涉及到第一个改动,设置

system_init.startsensorservice

属性,这里我是直接在

build/make/tools/buildinfo.sh

里面写死为1。

用SensorService::instantiate()方式创建的sensorservice实例后,调用里面的SensorService::onFirstRef方法。

代码路径:

frameworks\native\services\sensorservice\SensorService.cpp

void SensorService::onFirstRef() {ALOGD("nuSensorService starting...");SensorDevice& dev(SensorDevice::getInstance()); /* 创建并获取SensorDevice实例 */...if (dev.initCheck() == NO_ERROR) {sensor_t const* list;ssize_t count = dev.getSensorList(&list); /* 通过SensorDevice,并调用到vendor层去获取sensor的数目 */if (count > 0) {ssize_t orientationIndex = -1;bool hasGyro = false, hasAccel = false, hasMag = false;uint32_t virtualSensorsNeeds =(1<<SENSOR_TYPE_GRAVITY) |(1<<SENSOR_TYPE_LINEAR_ACCELERATION) |(1<<SENSOR_TYPE_ROTATION_VECTOR) |(1<<SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR) |(1<<SENSOR_TYPE_GAME_ROTATION_VECTOR);for (ssize_t i=0 ; i<count ; i++) {bool useThisSensor=true;switch (list[i].type) {case SENSOR_TYPE_ACCELEROMETER:hasAccel = true;break;case SENSOR_TYPE_MAGNETIC_FIELD:hasMag = true;break;case SENSOR_TYPE_ORIENTATION:orientationIndex = i;break;case SENSOR_TYPE_GYROSCOPE:case SENSOR_TYPE_GYROSCOPE_UNCALIBRATED:hasGyro = true;break;case SENSOR_TYPE_GRAVITY:case SENSOR_TYPE_LINEAR_ACCELERATION:case SENSOR_TYPE_ROTATION_VECTOR:case SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR:case SENSOR_TYPE_GAME_ROTATION_VECTOR:if (IGNORE_HARDWARE_FUSION) {useThisSensor = false;} else {virtualSensorsNeeds &= ~(1<<list[i].type);}break;}if (useThisSensor) {registerSensor( new HardwareSensor(list[i]) );}}// it's safe to instantiate the SensorFusion object here// (it wants to be instantiated after h/w sensors have been// registered)SensorFusion::getInstance();if (hasGyro && hasAccel && hasMag) {...}if (hasAccel && hasGyro) {...}if (hasAccel && hasMag) {...}...}}
}

我这次主要是增加温湿度传感器的功能,上面的流程中没有过多涉及温湿度的,有兴趣的可以参考大神的博客自行分析。不过这里重点关注一下SensorDevice这个类,它是连接上层应用和HAL层的中间枢纽:

代码路径:

frameworks\native\services\sensorservice\SensorDevice.cpp

SensorDevice::SensorDevice(): mHidlTransportErrors(20), mRestartWaiter(new HidlServiceRegistrationWaiter()) {if (!connectHidlService()) {return;}float minPowerMa = 0.001; // 1 microAmpcheckReturn(mSensors->getSensorsList([&](const auto &list "&") {const size_t count = list.size();mActivationCount.setCapacity(count);Info model;for (size_t i=0 ; i < count; i++) {sensor_t sensor;convertToSensor(list[i], &sensor);// Sanity check and clamp power if it is 0 (or close)if (sensor.power < minPowerMa) {ALOGE("Reported power %f not deemed sane, clamping to %f",sensor.power, minPowerMa);sensor.power = minPowerMa;}mSensorList.push_back(sensor);mActivationCount.add(list[i].sensorHandle, model);checkReturn(mSensors->activate(list[i].sensorHandle, 0 /* enabled */));}}));mIsDirectReportSupported =(checkReturn(mSensors->unregisterDirectChannel(-1)) != Result::INVALID_OPERATION);
}

SensorDevice构造函数中,通过调用connectHidlService()和安卓部分的HAL层服务建立连接。连接后,就可以调用已经在HAL层注册的sensor设备了,比如这里就调用getSensorsList()来获取sensor设备列表,并放回sensor的数目。然后就是通过mSensors->activate()来“激活”sensor设备,而每个sensor具体的activate()函数由驱动工程师实现。

激活sensor设备后,就可以开始获取sensor的数据了,在SensorService中会通过poll机制去查询底层sensor的数据:

代码路径:

frameworks\native\services\sensorservice\SensorService.cpp

bool SensorService::threadLoop() {...SensorDevice& device(SensorDevice::getInstance());const int halVersion = device.getHalDeviceVersion();do {ssize_t count = device.poll(mSensorEventBuffer, numEventMax);if (count < 0) {ALOGE("sensor poll failed (%s)", strerror(-count));break;}...} while (!Thread::exitPending());ALOGW("Exiting SensorService::threadLoop => aborting...");abort();return false;
}

整个threadLoop函数里面内容挺多的,但是目前只关注读取数据的poll部分。可以看到device就是SensorDevice的一个实例,前面我们讲到上层都是通过SensorDevice和HAL层连接,这里也不例外,也是调用到了SensorDevice中的poll函数,这里我给出这个调用的流程:

1、frameworks\native\services\sensorservice\SensorDevice.cpp
SensorDevice::poll()2、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\sensors.cpppoll__poll()3、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorManager.cppSensorManager::pollEvent()4、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorContext.cppsensors_poll_context_t::pollEvent

上面简陋的流程展示了从framework层一路调用到vendor层:

int sensors_poll_context_t::pollEvent(sensors_event_t* data, int count) {int nbEvents = 0;int n = 0;int averageCount = 0, loop = 0, loopcount = 0;int backupcount = count, backuploop = 0;do {loopcount++;computeCountForEachFd(count, &averageCount, &loop);backuploop = loop;for (int i = 0; count && loop && i < numFds; i++) {SensorBase* const sensor(mSensors[i]);if (mPollFds[i].revents & POLLIN || sensor->pendingEvent()) {int nb = sensor->readEvents(data, averageCount);...}}// try to see if we can get some events immediately or just wait if// we don't have anything to return, important to update fd revents// which sensor data pending in buffer and aviod one sensor always// occupy poll bandwidth.n = TEMP_FAILURE_RETRY(poll(mPollFds, numFds, nbEvents ? 0 : -1));if (n < 0) {ALOGE("poll() failed (%s)", strerror(errno));return -errno;}} while (n && count);return nbEvents;
}

这里面我们重点关注三点 (1) mPollFds的定义如下:

struct pollfd mPollFds[numFds];

其中,

struct pollfd {int fd;        /* 文件描述符 */short events; /* 等待的事件 */short revents; /* 实际发生了的事件 */
};

所以mPollFds就是用来监听代表每个sensor是否有数据上报的文件描述符

enum {accel,magnetic,gyro,light,proximity,pressure,humidity,temperature,stepcounter,pedometer,activity,situation,scpfusion,apfusion,bio,wakeupset,numFds,
};

如果想自定义一种sensor就需要给这个枚举类型增加值。

(2) mSensors的定义如下:

SensorBase* mSensors[numFds];

SensorBase是一个基类,所有的sensor类都继承于它,比如我这次实现的湿度传感器:

class HumiditySensor : public SensorBase {private:int mEnabled;sensors_event_t mPendingEvent;SensorEventCircularReader mSensorReader;int64_t mEnabledTime;char input_sysfs_path[PATH_MAX];int input_sysfs_path_len;int mDataDiv;int64_t m_hmdy_last_ts = 0;int64_t m_hmdy_delay = 0;void processEvent(struct sensor_event const *event);public:HumiditySensor();virtual ~HumiditySensor();virtual int readEvents(sensors_event_t* data, int count);virtual int setDelay(int32_t handle, int64_t ns);virtual int enable(int32_t handle, int enabled);virtual int batch(int handle, int flags, int64_t samplingPeriodNs, int64_t maxBatchReportLatencyNs);virtual int flush(int handle);virtual int getFd() {return mSensorReader.getReadFd();};
};

从类的声明来看,定义了很多函数,比如readEvents、enable和batch等等,这些最终都会和底层驱动联系起来,后面再细说。

(3)在sensors_poll_context_t的构造函数中会对上面两点讲到的数组进行初始化:

sensors_poll_context_t::sensors_poll_context_t()
{...mSensors[humidity] = new HumiditySensor(); /* 分配一个Humidity传感器的类 */mPollFds[humidity].fd = mSensors[humidity]->getFd(); /* 获取对应sensor的字符描述符 */mPollFds[humidity].events = POLLIN; /* 等待POLLIN类型的事件 */mPollFds[humidity].revents = 0;...
}

再回到上面的

sensors_poll_context_t::pollEvent()

函数,通过

mPollFds[i].revents

判断到如果发生了POLLIN事件,证明可以获取数据了,就调用对应sensor的readEvents()

函数去获取。接下来我们就进入到sensor设备对应的HAL层里面了,现在以湿度sensor为例:

代码路径:

vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\Humidity.cpp

int HumiditySensor::readEvents(sensors_event_t* data, int count) {if (count < 1)return -EINVAL;ssize_t n = mSensorReader.fill();if (n < 0)return n;int numEventReceived = 0;struct sensor_event const* event;while (count && mSensorReader.readEvent(&event)) {processEvent(event);if (event->flush_action <= FLUSH_ACTION) {...}mSensorReader.next();}return numEventReceived;}

我们可以看到读取数据实际又是统一通过

SensorEventCircularReader

这个类来操作:

代码路径:

vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorEventReader.cpp

SensorEventCircularReader::SensorEventCircularReader(size_t numEvents): mBuffer(new struct sensor_event[numEvents * 2]),mBufferEnd(mBuffer + numEvents),mHead(mBuffer),mCurr(mBuffer),mFreeSpace(numEvents) {mReadFd = -1;mWriteFd = -1;
}

构造函数里面分配了Buffer来存储接收的数据

ssize_t SensorEventCircularReader::fill() {size_t numEventsRead = 0;if (mFreeSpace) {const ssize_t nread = TEMP_FAILURE_RETRY(read(mReadFd, mHead, mFreeSpace * sizeof(struct sensor_event)));if (nread < 0 || nread % sizeof(struct sensor_event)) {return 0;}...}return numEventsRead;}

fill顾名思义就是往分配的buffer里面填充数据,通过我们熟悉的read()函数来获取数据。

ssize_t SensorEventCircularReader::readEvent(struct sensor_event const** events) {*events = mCurr;ssize_t available = (mBufferEnd - mBuffer) - mFreeSpace;return available ? 1 : 0;
}

readEvent()

只是判断buffer中是否有数据,然后就是调用

mSensorReader.next()

获取下一个buffer。再回到

HumiditySensor::readEvents()

在读取到数据后会调用

processEvent()

去处理数据:

void HumiditySensor::processEvent(struct sensor_event const *event) {mPendingEvent.relative_humidity = (float) event->word[0] / mDataDiv;
}

mPendingEvent.relative_humidity就是最终上报给上层应用的值了。

结语

至此,framework层到vendor层的流程就分析完了,后面我们会分析kernel层的sensor框架。

参考链接

https://blog.csdn.net/goodnight1994/article/details/97503586

推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

嵌入式Linux

微信扫描二维码,关注我的公众号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/467531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Theano 更多示例

Logistic函数 logistic函数的图&#xff0c;其中x在x轴上&#xff0c;s(x)在y轴上。 如果你想对双精度矩阵上的每个元素计算这个函数&#xff0c;这表示你想将这个函数应用到矩阵的每个元素上。 嗯&#xff0c;你是这样做的&#xff1a; xT.dmatrix(x) s1/(1T.exp(-x)) logisti…

SensorKernel层框架分析

接上文安卓9.0Sensor框架前言前面我们已经讲解了sensor框架中的framework到vendor层&#xff0c;这篇文章我们将会讲解kernel层的内容。不过不同的芯片平台&#xff0c;kernel层中的sensor框架是不同的&#xff0c;这里针对的是mt8167s平台。不过这里提醒一下&#xff0c;MTK平…

Linux内核LED子系统、请务必看

前言LED子系统你要是说很难嘛&#xff0c;但是它就是控制一些简单的GPIO口&#xff0c;但是你要是说它很简单嘛&#xff0c;但是我也不见得一个初学者很快就能掌握&#xff0c;你如果是刚入门这部分的话&#xff0c;我觉得你还是要去仔细研究下这些驱动。前两天在网上看到一句话…

UVALive 4394 String painter

题目大意&#xff1a;有两个字符串A,B&#xff0c;一次刷可以把A串一段刷成同一个字母&#xff0c;问至少要刷几次才能把A串变成B串。串长≤100。 本来以为是个很简单的区间DP&#xff0c;后来发现直接区间DP是不行的&#xff0c;这玩意有后效性&#xff1a;刷完一整块之后这一…

centos 6.5 安装 lamp 后mysql不能启动_CentOS 6.5 系统 LAMP(Apache+MySQL+PHP)安装步骤

先来解释一下&#xff0c;什么是 LAMP。正如标题所言&#xff0c;LAMP 实际上就是 Linux、Apache、MySQL、PHP 四个名称的缩写&#xff0c;当然最后一个 “P” 还有其他说法是 Perl 或者 Python。不用多说了&#xff0c;本文讲解的就是 Linux、Apache、MySQL、PHP 这四个东西&a…

Redis连接实例

ECS Windows服务器 如果您本地需要通过公网访问 云数据库 Redis&#xff0c;可以在 ECS Windows 云服务器中通过 netsh 进行端口映射实现。 1. 登录 ECS Windows 服务器&#xff0c;在 CMD 执行以下命令。&#xff08;公网地址与 连接地址 请替换 为您的实际地址。&#xff09;…

什么是高内聚,低耦合?

高内聚&#xff0c;低耦合是一个老生常谈的话题&#xff0c;所以拿出来说一下我们在看Linux的一些资料&#xff0c;或者是在面试&#xff0c;又或者跟一个比较牛的大佬讨论技术的时候&#xff0c;可能会听到这个概念。所以&#xff0c;什么是高内聚&#xff0c;低耦合呢&#x…

Web API 2 入门——创建ASP.NET Web API的帮助页面(谷歌翻译)

在这篇文章中 创建API帮助页面将帮助页面添加到现有项目添加API文档在敞篷下下一步作者&#xff1a;Mike Wasson 创建Web API时&#xff0c;创建帮助页面通常很有用&#xff0c;以便其他开发人员知道如何调用API。您可以手动创建所有文档&#xff0c;但最好尽可能自动生成。 为…

Linux fork的写时复制

这个问题是一个同学在知识星球里面提问的看下面的代码#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #includ…

从单片机工程师的角度看嵌入式Linux

前言 这篇文章简单我们来一起梳理嵌入式Linux的一些知识&#xff0c;方便于一些想跟我一样想要由单片机进阶到嵌入式Linux的朋友做一些参考学习。现在随着嵌入式Linux的教程不断增多&#xff0c;相信应该有不少学单片机的朋友开始折腾这个了吧~嵌入式Linux学哪些东西 1、认识Li…

智能硬件开发神器免费送!距离产品智能化,只差一个“三明治”的距离

重磅资料包免费领取 针对人群&#xff1a;如果您对“人工智能物联网”感兴趣&#xff0c;尤其是您的企业想做产品/行业智能化&#xff0c;一定不能错过这份“物联网人必备的终极大礼包”哦&#xff5e;这几类行业玩家必看&#xff01;&#xff01;&#xff01;制造商、方案商、…

你们班上的同学现在都怎么样了?

今天跟朋友聊天&#xff0c;聊到这个话题&#xff0c;觉的有点意思&#xff0c;所以想拿出来讨论一下。小学小学的时候&#xff0c;我们读书最厉害的应该是我们班上几个老师的孩子&#xff0c;他们像是得到了老师的武功秘籍&#xff0c;读书对他们来说就是家常便饭&#xff0c;…

如何把一个float存到一个长度为4的char数组中?

我以前以为它是可以这样的看代码#include "stdio.h"int main(void) {float fa 123.56;char farray[4] {0};farray[0] ((int)fa>> 24)&0xFF;farray[1] ((int)fa>> 16)&0xFF;farray[2] ((int)fa>> 8 )&0xFF;farray[3] ((int)fa>…

OSPF次末节区域配置 201

【实验拓扑】 【实验基本配置】1、按照上图配置相应接口以及OSPF【实验要求】 1、配置Area 1 为NSSA区域2、在R6上配置1条静态路由160.1.60.0/24指向 Null0&#xff0c;并充分发到OSPF中3、将路由器1的ID修改为150.1.100.100【实验分析】次末节区域&#xff08;NSSA&#xff09…

MTK联发科2021 嵌入式C笔试题分析

题目-分割字符串因为题目是考试的同学凭记忆记下来的&#xff0c;没有记得特别仔细&#xff0c;题目只是写了个大概&#xff0c;我也是凭自己的想法来理解。输入aaa;bbb;ccc输出aaa bbb ccc /*--------------------------------------------*/ 输入,hello,hello,,输出hello hel…

DEVICE_ATTR设置0777引发血案

这个宏我们在内核里面使用非常频繁&#xff0c;这个宏的作用可以抛出sys设备节点给用户使用。用户可以读写sys/class下面的文件节点&#xff0c;以达到控制内核驱动的功效。比如&#xff0c;像这样的设备节点weiqifa:/sys/class/zigbee/onoff $ ls gpio_en power subsystem uev…

「任性」的C语言之父:因拒付论文装订费错失博士学位,论文52年后重见天日...

他是C语言之父、1983年图灵奖得主&#xff0c;还是Unix的关键开发者。然而&#xff0c;他却因为「任性」没有拿到博士学位&#xff0c;而且当年写的博士论文一丢就是半个世纪。如今&#xff0c;这一神秘的博士论文终于重见天日。很多人可能听说过 Dennis Ritchie 这个人。上世纪…

CS224n笔记3 高级词向量表示

本文转自&#xff1a;http://www.hankcs.com/nlp/cs224n-advanced-word-vector-representations.html 这节课从传统的基于计数的全局方法出发&#xff0c;过渡到结合两者优势的GloVe&#xff0c;并介绍了词向量的调参与评测方法。 复习&#xff1a;word2vec的主要思路 遍历整个…

我在深圳,但是家里托人在老家找了一份工作

最近&#xff0c;在微信公众号后台收到一份读者的留言&#xff0c;而且这位读者也是我们GX的&#xff0c;而且更幸运的是&#xff0c;跟我是一个地方的。她是疑惑如下~前辈你好~ 今天看到最新一篇有关读书的推文发现同是HC老乡&#xff0c;斗胆向您请教一些问题&#xff0c;有点…

Django之项目搭建和配置总结(一)

安装和创建虚拟环境 参考&#xff1a;linux系统下Python虚拟环境的安装和使用安装Django包 先进入虚拟环境&#xff0c;在联网下执行&#xff1a;pip install django1.8.7 1.8.7表示django的版本&#xff0c;如果不指定&#xff0c;会默认安装最新版的django。包会被安装到/usr…