MTK Android P Sensor架构(一)

news2024/12/23 16:57:41

需求场景:

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

正文:

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

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

  • 安卓官方实现部分:
hardware/libhardware/modules/sensors
  • 芯片产商实现部分(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 microAmp
 
 checkReturn(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.cpp
 poll__poll()
  3、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorManager.cpp
  SensorManager::pollEvent()
   4、vendor\mediatek\proprietary\hardware\sensor\sensors-1.0\SensorContext.cpp
   sensors_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框架。

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

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

相关文章

从手工测试进阶中高级测试?如何突破职业瓶颈...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、手工测试如何进…

【教3妹学编程-算法题】需要添加的硬币的最小数量

3妹&#xff1a;2哥2哥&#xff0c;你有没有看到新闻&#xff0c; 有人中了2.2亿彩票大奖&#xff01; 2哥 : 看到了&#xff0c;2.2亿啊&#xff0c; 一生一世也花不完。 3妹&#xff1a;为啥我就中不了呢&#xff0c;不开心呀不开心。 2哥 : 得了吧&#xff0c;你又不买彩票&…

Vue 双向绑定:让数据与视图互动的魔法!(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

人体关键点检测3:Android实现人体关键点检测(人体姿势估计)含源码 可实时检测

目录 1. 前言 2.人体关键点检测方法 (1)Top-Down(自上而下)方法 (2)Bottom-Up(自下而上)方法&#xff1a; 3.人体关键点检测模型训练 4.人体关键点检测模型Android部署 &#xff08;1&#xff09; 将Pytorch模型转换ONNX模型 &#xff08;2&#xff09; 将ONNX模型转换…

【JVM从入门到实战】(一) 字节码文件

一、什么是JVM JVM 全称是 Java Virtual Machine&#xff0c;中文译名 Java虚拟机。 JVM 本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件。 二、JVM的功能 解释和运行 对字节码文件中的指令&#xff0c;实时的解释成机器码&#xff0c;让计算机…

图论——二分图

图论——二分图 二分图通俗解释 有一个图&#xff0c;将顶点分成两类&#xff0c;边只存在不同类顶点之间&#xff0c;同类顶点之间设有边。称图 G 为二部图&#xff0c;或称二分图&#xff0c;也称欧图。 性质 二分图不含有奇数环图中没有奇数环&#xff0c;一定可以转换为二…

005、Softmax损失

之——softmax与交叉熵 杂谈 我们常用到softmax函数与交叉熵的结合作为损失函数以监督学习&#xff0c;这里做一个小小的总结。 正文 1.softmax的基本改进 所谓softmax就是在对接全连接层输出时候把输出概率归一化&#xff0c;最基础的就是这样&#xff1a; 效果就是这样&…

图文教程:stable-diffusion的基本使用教程 txt2img(多图)

之前我介绍了SD的安装过程&#xff0c;那么这篇将介绍怎么使用SD 使用模型 SD安装好之后&#xff0c;我们只有一个默认的模型。这个模型很难满足我们的绘图需求&#xff0c;那么有2种方法。 1是自己训练一个模型&#xff08;有门槛&#xff09;2是去网站上找一个别人练好的模…

算法通关村第四关—表达式问题(黄金)

表达式问题 一、计算器问题 计算器也是非常常见的问题&#xff0c;我们看一个中等问题。LeetCode227.给你一个字符串表达式s&#xff0c;请你实现一个基本计算器来计算并返回它的值。整数除法仅保留整数部分。  你可以假设给定的表达式总是有效的。所有中间结果将在[-231,231…

关于Anaconda的安装和环境部署(此章专为新手制定)

目录 Anaconda简介 一、软件下载&#xff08;地址&#x1f447;&#xff09; 2&#xff1a;点击下载 3&#xff1a;版本选择&#xff1a; 4&#xff1a;Anaconda的安装包就下载完成了 2&#xff1a;恭喜你&#xff0c;看到这里已经完成安装了 三、部署环境 1&#xff1…

Vue 双向绑定:让数据与视图互动的魔法!(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Wireshark中的http协议包分析

Wireshark可以跟踪网络协议的通讯过程&#xff0c;本节通过http协议&#xff0c;在了解Wireshark使用的基础上&#xff0c;重温http协议的通讯过程。 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是一种面向连接的、可靠的、基于 字节流…

FastAPI之响应模型

前言 响应模型我认为最主要的作用就是在自动化文档的显示时&#xff0c;可以直接给查看文档的小伙伴显示返回的数据格式。对于后端开发的伙伴来说&#xff0c;其编码的实际意义不大&#xff0c;但是为了可以不用再额外的提供文档&#xff0c;我们只需要添加一个 response_mod…

scala变量与变量类型

1.6 变量与类型&#xff08;重点&#xff09;1.6.1 变量推断1.6.2 多变量定义1.6.3 var和val的区别 1.6.3.1 是否可变 1.6.3.2 延迟加载 1.6 变量与类型&#xff08;重点&#xff09; val修饰的变量&#xff0c;相当于Java中final修饰的变量; // 定义常量s1&#xff0c;使用…

11.进制转换:十进制转二进制【2023.12.3】

1.问题描述 计算机底层原理中常使用二进制来表示相关机器码&#xff0c;学会将十进制数转换成二进制数是一个非常重要的技能。现在编写一个程序&#xff0c;输入一个十进制数&#xff0c;将其转换成二进制数。 2.解决思路 使用内置函数bin()将十进制数转换为二进制数 3.代码…

FreeRTOS的三处栈空间设置分析

1、汇编启动代码中设置栈 这个栈空间只有300字节&#xff0c;是用于汇编启动代码早期&#xff0c;以及调用C语言的main函数&#xff08;创建任务等&#xff09;在创建好任务&#xff0c;启动调取器后&#xff0c;这个栈空间就被抛弃掉&#xff0c;后续不会使用到等调度器开启后…

问卷调查:如何防止随意作答以提高数据质量

在进行问卷调查的时候&#xff0c;我们经常要面临一个问题——答题者随意作答。这往往会导致我们收集的问卷数据不准确&#xff0c;影响问卷的最终结论。所以&#xff0c;我们要开展问卷调查的过程中&#xff0c;要运用一些合理的方法和技巧&#xff0c;避免这种情况发生。问卷…

频率、概率

频率 在相同的条件下进行试验&#xff0c;假设试验进行了次&#xff0c;其中随机事件A发生了次&#xff0c;那么就称为随机事件A发生的频率。 概率 假设随机试验E的样本空间是S&#xff0c;对于其中每个随机事件&#xff0c;都对应了一个实数&#xff0c;把这个实数称为随机…

1842_emacs使用company-irony实现C语言的自动补全

Grey 全部学习内容汇总&#xff1a;GitHub - GreyZhang/editors_skills: Summary for some common editor skills I used. 1842_emacs使用company-irony实现c语言的自动补全 irony-mode是一个自动补全的实现方案&#xff0c;配合company集成之后效果非常好。简单调试完了之后…

理解排序算法:冒泡排序、选择排序与归并排序

简介&#xff1a; 在计算机科学中&#xff0c;排序算法是基础且重要的概念。本文将介绍三种常见的排序方法&#xff1a;冒泡排序、选择排序和归并排序。我们将探讨它们的工作原理、特点和适用场景&#xff0c;以帮助读者更好地理解和选择合适的排序方法。 冒泡排序 冒泡排序是…