MTK Sensor驱动与安卓框架全链路解析:从设备树到传感器服务
简介面向MTK平台驱动开发者和安卓系统学习者聚焦传感器驱动与系统框架的衔接帮助快速理解硬件数据如何逐层上报至应用。压缩包共2个文件1个C文件、1个Java文件整体约32KB结构紧凑、定位明确。C文件是驱动实现核心除传感器设备结构体定义、驱动注册与初始化、中断处理及数据读写外还涉及设备树中中断线、I/O端口与时钟的配置解析以及电源管理逻辑便于对照内核设备模型进行移植调试。Java文件对应传感器框架展示传感器类型、分辨率、精度等属性定义同时体现SensorService通过JNI与底层驱动交互、SensorManager注册监听器、Binder机制分发SensorEvent的完整链路可进一步延伸到融合传感器、多传感器数据组合等应用。已有2139人学习下载适合希望掌握MTK平台传感器驱动移植、Android传感器框架调用关系及性能优化的开发者和学习者们。 干了这么多年BSP我几乎每个项目都要在MTK平台上过一遍sensor从硬件bringup到安卓层手势唤醒再到各种离奇的异常上报踩过的坑比我写过的代码都多。这篇就把MTK sensor传感器驱动和安卓层框架这条完整链路掰开揉碎从设备树里的一个节点怎么变成手机状态栏里那个加速度计图标讲清楚每一层在干什么以及那些你在文档里查不到、只能在串口日志和busybox里硬啃出来的排查经验。不管是刚上手MTK平台的新人还是被hal层报错折磨到头秃的老手这篇应该都能给你一些启发。1. 项目拆解MTK sensor 从硬件中断到安卓传感器服务的四层链路1.1 为什么 MTK 的 sensor 框架值得单独拉出来讲做过高通平台再转MTK的人第一反应基本都是“这框架怎么这么绕”。高通的sensor驱动还算规整直接挂在i2c总线上probe完之后注册进input子系统或者sensors hal链路简单直接。MTK不一样它从早期功能机时代就积累了一套自己的sensor管理框架到了智能机时代一直沿用内核里有一套sensor list机制HAL层还有对应的sensor hub协议中间又牵扯到硬件中断EINT和电源域管理。这套框架好处是厂商定制能力强一颗sensor可以从驱动层一路控制到应用层坏处就是文档少、宏多、路径深新人第一次进来很容易迷路。我经常跟同事说MTK的sensor框架本质上是一条“四级流水线”第一级是硬件芯片本身挂在I2C/SPI总线上通过中断引脚告知SoC“我有数据了”第二级是内核驱动完成设备树解析、probe、寄存器配置然后把数据通过misc设备或者netlink送上去第三级是HAL层负责跟内核通信、做数据校准和坐标系转换第四级才是安卓的SensorService面向App提供统一的传感器接口。你这颗sensor能不能用每一级都得通哪一级断了表现都不一样。1.2 一条数据链路串起来看从加速度计上报到 App 拿到坐标拿最常见的加速度计g-sensor举例芯片型号可能是bmi160或者stk8321。硬件上它挂在i2c2总线上地址0x68中断脚接到SoC的GPIO11上。你一摇手机芯片里的质量块发生位移电容变化转换成数字信号芯片检测到数据变化后拉高INT脚SoC收到中断调度到sensor驱动的中断处理函数里驱动通过i2c把寄存器里的X/Y/Z轴原始值读出来经过滤波和scale转换通过sensor list机制上报给内核的sensor core再转给HAL层注册的sensor handle。HAL层拿到数据后做坐标系映射把芯片的本地坐标转成安卓标准的屏幕坐标再继续往上报给SensorService。SensorService会根据应用的监听频率做数据分发你的微信计步、手机横竖屏切换用的就是这一条链路里的加速度计数据。这条链路里最坑的一个点是很多人以为sensor驱动probe成功了系统就能读到数据了实际上不是。MTK平台sensor数据从内核到HAL中间经常要经过一个“sensor hub”或者叫“sensor satellite”的协同处理器有些项目是独立的SCP有些是DSP。如果这个hub没起来或者HAL层跟hub通信的通道没建立你从dmesg看驱动全是好的但App里就是没数据。这个在排查问题的时候极其容易踩坑后面我会展开说。2. 内核侧驱动设备树、probe 与 sensorlist 注册2.1 点亮 sensor 的前置条件——先把设备树和硬件管脚理顺“点亮”这个词在我们这行很形象sensor能不能工作点亮之前要看一堆前置条件。我总结了几个必查项第一供电是否正常很多sensor是1.8V的数字电源加2.8V的模拟电源少了一路芯片ID都读不到第二I2C地址是否正确同一颗芯片不同封装地址可能不同比如stk8321就有0x0E和0x18两个版本第三中断引脚是否配置正确设备树里的int-gpio号、EINT号、触发方式必须跟硬件原理图严格对应第四reset脚的电平时序有些sensor上电后需要拉低再拉高复位一次时序不对芯片就僵死。设备树里典型的sensor节点长这样以加速度计为例i2c2 { status okay; stk83210e { compatible st,stk8321; reg 0x0e; interrupt-parent pio; interrupts 11 IRQ_TYPE_EDGE_FALLING; int-gpio pio 11 0; st,axis-map-x 1; st,axis-map-y 0; st,axis-map-z 2; st,negate-x 0; st,negate-y 1; st,negate-z 0; }; };注意axis-map这些属性就是我在第一节说的坐标系映射。芯片物理摆放方向跟手机屏幕方向往往不一致比如X轴朝右、Y轴朝上但芯片可能焊上去之后X轴朝下这时候就得靠这些映射项把坐标“掰正”。我见过新人在这一步偷懒结果上下左右全部反了App里的重力感应游戏整个镜像最后排查半天发现是axis-map配错了。这个不报错、不影响probe但是用户体验直接崩一定要在点亮阶段就验证。2.2 probe 流程与 sensorlist 注册设备树配置完之后驱动probe才会执行。MTK平台sensor驱动的probe跟普通i2c驱动不太一样它不仅要完成i2c_driver的标准probe还要调用MTK sensor框架的注册接口把sensor的信息注册进内核的sensor list里。这个sensor list你可以理解为一张“传感器登记表”里面记录了传感器类型、名称、vendor、采样率范围、FIFO大小等等。HAL层启动的时候会通过netlink或者ioctl读取这张表知道自己该管控哪些传感器。常见的注册代码路径是xxx_sensor_ops结构体里面填了senosr的open、close、get_data、set_delay等回调然后调用sensor_register或者直接写入/sys/bus/platform/drivers/xxx下的某个节点。这里有个非常容易踩的坑MTK的sensor框架对probe顺序有要求如果你把sensor驱动编成了模块.ko加载顺序错了或者驱动里调用了某个还没初始化的框架接口probe会返回EPROBE_DEFER然后无限重试但日志里只有一行“probe defer”很容易被忽略。我调试的时候有一个习惯probe完之后第一件事就是进shell验证sensor list里有没有这颗sensoradb shell cat /sys/bus/i2c/devices/2-000e/name adb shell dmesg | grep -i stk8321\|sensor list如果dmesg能看到[sensor] add sensor: stk8321, type: 1之类的关键日志说明内核侧已经注册成功。如果什么都搜不到趁早回头查设备树和宏开关别在HAL层白费时间。2.3 中断、手势与双击唤醒的挂载方式MTK上双击唤醒double tap to wake这种手势功能跟sensor的关系比很多人想象中紧密。它不是纯TP触摸屏的事而是g-sensor和TP配合的结果TP负责检测双击动作但系统休眠状态下TP可能处于低功耗模式这时候就需要g-sensor感知到“手机被拿起来”这个动作提前唤醒TP再由TP去识别双击手势。所以MTK平台的手势唤醒方案里g-sensor的中断必须能够在深睡模式下唤醒CPU。这涉及一个关键点sensor中断必须走EINT外部中断且配置为enable wakeup在设备树里就是wakeup-source属性同时驱动里要在suspend/resume里正确处理。我实际项目里遇到过一个情况双击唤醒在亮屏时完全正常但灭屏休眠之后就失效了。查了一圈发现是中断回调里没有调用irq_set_irq_wake中断虽然触发了但CPU在deep idle状态下根本不去响应。加上这个之后唤醒就正常了。这个经验基本每个平台都适用你如果遇到“熄屏手势失效”的问题第一个查的就是这个。3. 安卓层框架HAL、SensorService 与权限适配3.1 HAL 层 sensor list 映射与校准的关键细节从内核往上走就是HAL层。MTK的sensor HAL在vendor/mediatek/proprietary/hardware/hals/sensors/下面编译产物是libsensors相关so。HAL层干的事主要有三件第一通过/dev/sensor或者netlink跟内核通信获取sensor list建立handle和sensor的对应关系第二做数据后处理包括低通滤波、去噪、重力分解等第三处理sensor的enable/disable和采样率设置。这一层最常见的问题就是handle号对不上。HAL层定义了一堆sensor_t结构体数组数组下标就是这个sensor的handleApp通过SensorManager.getDefaultSensor(type)拿到的是安卓层的handleHAL层要把这个handle映射到内核的sensor ID上。如果映射错位最典型的症状就是你明明想拿加速度计数据结果拿到的是陀螺仪的数据而且数值非常离谱。这种问题不会报错只能靠验证数据来发现。校准这块也值得单独说。加速度计的偏移校准MTK的HAL层一般支持在工厂模式下做六面校准也就是让手机六个面朝上分别采集数据算出bias和scale矩阵存到NV或data分区。如果校准数据丢了比如OTA升级之后分区被清你会发现水平放置手机时加速度计显示的不是009.8而是1.2-0.58.9这种虽然不影响手机正常用但会影响计步、抬手亮屏这些功能的准确性。这时候可以进工厂模式重新校准或者清除校准数据让HAL层恢复到默认值很多“sensor不准”的客诉其实是这个原因。3.2 手势双击唤醒的安卓层适配双击唤醒这种手势在安卓framework层也有自己的一套逻辑。Android从很早就支持ProximitySensor配合通知栏的防误触逻辑但对“双击唤醒”没有一个统一的原生接口所以MTK一般是通过com.mediatek的扩展接口实现HAL层把g-sensor的数据识别成“拿起点亮”手势framework层有对应的DisplayPowerController会去监听这个手势事件收到之后点亮屏幕。这个适配过程会遇到一个经典的坑如果framework层没有正确注册对sensor手势的监听或者灵敏度配置过高会出现“放在兜里自动亮屏”“放在桌上自己亮屏”这种诡异现象。我自己调过的一个项目里兜里亮屏的原因是g-sensor在裤子摩擦下产生了一个超过阈值的振动被HAL层误判成了“拿起来”。解决方案是把HAL层手势识别的阈值调高加上一个“必须先检测到接近传感器遮挡才能响应抬起手势”的防误触逻辑。这类问题在bugly和客诉平台上出现频率极高做framework适配的时候一定要提前考虑。4. 高发问题排查实录planar 报错、内存泄漏与异常唤醒4.1 “sensor planar vrd has transitioned to non-recoverable”不是 sensor 的问题这里要专门写一个我在热词里看到的高频报错sensor planar vrd has transitioned to non-recoverable。很多人第一次看到带sensor字样就以为是sensor驱动挂了一顿猛查sensor代码结果查了半天无头绪。实际上这个报错跟传感器驱动本身关系不大它来自内核的电源管理DDR/电压调节相关模块planar VRDVoltage Regulator Domain是GPU或DDR供电链路里的一个平面电压调节域进入non-recoverable状态一般意味着电压调节器在响应时序上出了问题——要么是某一路LDO/PMIC的反馈异常要么是DDR频率切换时电压动态调整失败sensor只是恰好在这个时间点通过sysfs节点去读电压配置被日志抓了个正着。排查这类问题的正确姿势是去看完整的调用栈和前后时序adb shell dmesg -c adb shell cat /d/regulator/regulator_summary adb shell cat /d/rpm/master_stats同时检查/sys/class/regulator/下各regulator的use_count和电压值是否异常。我在一个项目里遇到过一次类似问题最后定位到是某个TP驱动在resume流程里违规调用了regulator_set_voltage导致DDR电压域在切换过程中出现了毛刺触发了VRD保护。所以记住一句话报错信息里的sensor可能只是“目击者”不是“肇事者”。4.2 双击唤醒灵敏度与数据漂移的取舍双击唤醒还有一个高频问题就是“飘”。这个飘有两种情况一种是物理上的数据漂移g-sensor在静止状态下零偏发生变化导致手势识别误触发另一种是逻辑上的“阈值漂移”用户使用习惯不同固定阈值无法适配所有人。前者的解决思路是加零偏校准和自校准算法MTK的HAL里一般有accel_calibration模块后者的思路是做一个自适应阈值根据历史数据均值动态调整。我实测下来双击唤醒的阈值范围一般设在sensitivity这个属性典型值是1到51最灵敏5最迟钝。很多厂商默认用3但我建议先在白天和夜晚各测试20次统计误触发率。如果夜晚误触发率高多半是阈值太小加上接近传感器的防误触判定后可以适当调低。这个活儿没有标准答案只能靠实测数据说话。4.3 MTK sensor 链路的内存泄漏排查思路热词里有个“mtk内存泄漏排查”这里给一套sensor链路相关的排查思路。sensor本身不太容易泄漏因为数据量小但sensor HAL层的客户端连接非常容易泄漏。比如App反复创建和释放SensorManager、SensorEventListener如果底层对应没有正确解绑就会在sensor service里积累sensor connection导致内存只增不减。排查思路分三步第一步用adb shell dumpsys sensorservice看当前活跃的sensor连接数和App连接分布第二步用adb shell dumpsys meminfo system_server看system_server的Java和Native内存变化第三步如果确实在HAL层发现native内存异常用libmemunreachable抓native堆。MTK平台上还有个杀手锏是cat /proc/mtk_meminfo看各模块内存占用以及打开内核的kmemleakecho 1 /sys/kernel/debug/kmemleak/scan cat /sys/kernel/debug/kmemleak不过老实说sensor链路泄漏我在实际项目里遇到的更多是HAL层的线程泄漏。MTK HAL层每个sensor可能单独启一个线程读数据如果sensor disable之后线程没有退出enable/disable反复几百次线程数量就会堆积。这种泄漏用adb shell ps -T | grep sensors一眼就能看出来线程数量异常多就是信号。4.4 常见问题速查表现象排查点常见根因解决方法probe 失败读不到 chip idi2c 通信、供电、reset 时序电源少一路/地址错误核对原理图示波器量 I2C改设备树有数据但方向错axis-map 映射坐标系没做映射修正 axis-map/negate 选项灭屏后手势失效irq wake 配置未设置 irq_set_irq_wake在 suspend 前使能 wake irqApp 拿不到数据HAL 与内核通信sensor hub 未初始化看 dmesg 中 hub 相关日志水平放置读数不准校准数据校准参数丢失工厂模式重新校准planar VRD 报错电源域电压调节时序异常检查 regulator 和驱动违规调用5. 踩坑记录与调试工具箱5.1 我常用的几个调试命令和工具搞sensor调试我最常用的不是Android Studio而是下面这几个命令行工具。第一个是getevent这条命令用来验证中断和input事件是否正常上报尤其适合排查那种“App收不到但驱动看起来正常”的问题。第二个是dumpsys sensorservice它不仅能看连接还能看每个sensor的采样率、fifo大小、最新数据刷新时间是验证HAL层配置是否生效的最快途径。第三个是/sys/bus/i2c/devices/下直接访问寄存器比如用i2cget手动读芯片ID可以确认到底是驱动问题还是硬件问题。MTK平台还有一个特点它内核里有丰富的sensor测试节点。比如老一点的平台在/sys/class/misc/m_sensor/下面有各种direct读写的接口新平台则在/sys/fs/sensor/下有sensor info目录可以手动触发sensor去校准或者读取原始数据。这些节点在不同平台位置不一样但核心价值是一样的把驱动和框架层隔离开来单独验证某一层的正确性。我每次bringup新板子都会先手动读一下传感器ID确定硬件和i2c通信OK再谈后面框架里的事情。5.2 实测下来最值得养成的几个习惯第一每次调试sensor都先抓完整的串口日志并且串口日志要从bootloader阶段就开始抓不能等进系统再抓。因为很多sensor初始化问题发生在内核启动的很早期Android跑起来之后再抓的日志可能已经丢了关键信息。第二看日志不要只看sensor关键字的行要看相关的GPIO、regulator、i2c的报错往往线索隐藏在这些看似无关的地方。第三批量测试校准数据的时候用一个脚本摄像头对着手机屏幕记录读数变化比人工怎么看都准尤其是在验证方向映射的时候。最后再分享一个我个人的习惯每次sensor调通之后我一定会在设备树里把axis-map和negate配好然后转动机器六个方向录一段视频确认坐标轴方向跟标称一致。很多人跳过这步结果就是后期量产的时候偶尔一批货出现“某些app地图方向反了”的客诉最后溯源还是回到坐标映射上。这一步不复杂但值得做。本文还有配套的精品资源点击获取