在 Android 嵌入式设备中,IMU 传感器经常用于屏幕旋转、姿态检测、电子罗盘、运动识别和设备方向判断等场景。
很多时候,我们以为“系统能读到传感器数据”就代表 IMU 调好了,但实际项目中并不是这样。IMU 调试真正麻烦的地方往往在于:
- Kernel 层是否真的有原始数据
- HAL 层是否正确完成单位换算
- Android 坐标系是否和整机方向一致
- ACC / GYRO / MAG 是否统一到同一套坐标系
- 磁力计是否受到干扰或方向配置错误
1. IMU 里到底有什么?
常见九轴 IMU 一般包含三类传感器:
ACC = Accelerometer,加速度计
GYRO = Gyroscope,陀螺仪
MAG = Magnetometer,磁力计
它们各自解决的问题不同:
加速度计主要测量设备在 X/Y/Z 三个方向上的加速度。设备静止时,它也会测到重力加速度,所以常用于判断设备朝向和屏幕旋转。
陀螺仪测量的是角速度,也就是设备绕 X/Y/Z 三个轴旋转的速度。它适合用于判断设备姿态变化,但长时间积分会有漂移。
磁力计测量地磁场方向,可以理解为电子罗盘。它可以帮助判断设备的绝对方向,比如东西南北。但磁力计非常容易受到外部磁场、金属结构、喇叭、电机、磁铁等影响。
所以,单独使用某一种传感器都不完美。Android 通常会结合 ACC、GYRO、MAG 做传感器融合,从而得到 Gravity、Linear Acceleration、Rotation Vector、Orientation 等虚拟传感器。
根据 Android 官方对传感器的描述:
传感器框架使用标准的 3 轴坐标系来表示数据值。对于大多数传感器,当设备处于默认屏幕方向时,会相对于设备屏幕来定义坐标系。当设备处于默认屏幕方向时,X 轴为水平向右延伸,Y 轴为垂直向上延伸,Z 轴为垂直于屏幕向外延伸。在此坐标系中,屏幕后面的坐标将具有负 Z 值。以下传感器使用此坐标系:
- 加速度传感器
- 重力传感器
- 陀螺仪
- 线性加速度传感器
- 地磁场传感器
在 Android 系统中,IMU 数据通常会经历这样一条链路:
IMU 硬件
↓ I2C / SPI
Linux Kernel IIO Driver
↓ /sys/bus/iio/devices/iio:deviceX
Sensor HAL / IIO SubHAL
↓
Android SensorService
↓
APP / 测试工具
这条链路里,每一层负责的事情不同。
Kernel IIO 层负责和硬件通信,并暴露 raw 数据。
Sensor HAL 负责读取 IIO 数据,完成单位换算、坐标映射,并注册成 Android 标准传感器。
SensorService 负责统一管理 Android 传感器,并向 APP 提供标准接口。
APP 层最终看到的是 Android 标准传感器事件,例如:
android.sensor.accelerometer
android.sensor.gyroscope
android.sensor.magnetic_field
所以调试 IMU 时,不建议一开始就只盯着 APP。更稳妥的方法是从底到上逐层确认。
3. Kernel IIO 层:先确认底层有没有数据
Linux IIO 子系统会把传感器暴露到类似下面的路径:
/sys/bus/iio/devices/
可以先查看当前系统中有哪些 IIO 设备:
ls /sys/bus/iio/devices/
再查看每个 IIO 设备的名称:
for d in /sys/bus/iio/devices/iio:device*; do
echo "===== $d ====="
cat "$d/name"
done
如果能看到对应 IMU 的设备名,说明 Kernel 已经识别到了传感器。
进入对应设备目录后,通常可以看到类似 raw 节点:
in_accel_x_raw
in_accel_y_raw
in_accel_z_raw
in_anglvel_x_raw
in_anglvel_y_raw
in_anglvel_z_raw
in_magn_x_raw
in_magn_y_raw
in_magn_z_raw
其中:
in_accel_*_raw 加速度计原始数据
in_anglvel_*_raw 陀螺仪原始数据
in_magn_*_raw 磁力计原始数据
可以通过循环读取 raw 数据,观察设备运动时数值是否变化:
D=/sys/bus/iio/devices/iio:device1
while true; do
ax=$(cat $D/in_accel_x_raw)
ay=$(cat $D/in_accel_y_raw)
az=$(cat $D/in_accel_z_raw)
gx=$(cat $D/in_anglvel_x_raw)
gy=$(cat $D/in_anglvel_y_raw)
gz=$(cat $D/in_anglvel_z_raw)
mx=$(cat $D/in_magn_x_raw)
my=$(cat $D/in_magn_y_raw)
mz=$(cat $D/in_magn_z_raw)
echo "ACC=[$ax $ay $az] GYRO=[$gx $gy $gz] MAG=[$mx $my $mz]"
sleep 0.5
done
判断标准很简单:
- 静止时 ACC 应有一个轴接近重力方向
- 翻转设备时 ACC 三轴应明显变化
- 静止时 GYRO 应接近 0
- 旋转设备时 GYRO 应明显变化
- 水平旋转设备时 MAG 的 X/Y 应连续变化
如果 raw 数据正常,说明硬件通信和 Kernel 驱动基本可用。
4. raw 数据不能直接用,还需要 scale 换算
IIO 层输出的是 raw 原始值,它不是最终物理单位。要得到实际数据,需要结合 scale:
实际物理值 = raw × scale
常见 scale 节点包括:
cat in_accel_scale
cat in_anglvel_scale
cat in_magn_scale
可以简单理解为:
Accelerometer = raw_accel × in_accel_scale 单位:m/s²
Gyroscope = raw_gyro × in_anglvel_scale 单位:rad/s
Magnetometer = raw_mag × in_magn_scale 单位:通常为 uT 或需要进一步转换
比如加速度计在设备静止时,三轴合成值应该接近重力加速度:
accel_total = sqrt(x² + y² + z²)
理论上应接近:
9.8 m/s²
磁力计也可以计算三轴合成值:
mag_total = sqrt(x² + y² + z²)
正常地磁场通常是几十 uT。如果磁场模长明显达到几百 uT 甚至更高,就要怀疑附近存在磁铁、喇叭、电机、铁桌子、屏蔽罩、电感等干扰源。
这里需要注意:
scale 解决的是“单位问题”
orientation 解决的是“方向问题”
这两个问题不要混在一起。
5. Android 坐标系:很多问题都出在这里
Android Sensor 使用的是整机坐标系,而不是芯片坐标系。
通常情况下,Android 坐标定义为:
+X:屏幕右边
+Y:屏幕上边
+Z:从屏幕向外,朝向用户
但是传感器芯片焊接到 PCB 后,芯片自己的 X/Y/Z 很可能和整机的 Android X/Y/Z 不一致。因此需要在 HAL 配置中做坐标映射。
例如某个配置:
<orientation rotate="true">
<x map="1" negate="false"/>
<y map="0" negate="false"/>
<z map="2" negate="true"/>
</orientation>
它表示:
Android X = raw Y
Android Y = raw X
Android Z = -raw Z
其中:
map 表示取哪条 raw 轴,0=X,1=Y,2=Z
negate 表示是否取反
很多屏幕旋转异常、方向反了、姿态不对,本质上都是 orientation 映射不正确。
6. 一个容易忽略的坑:MAG 不一定和 ACC/GYRO 同轴
很多 IMU 芯片内部的磁力计并不一定和加速度计、陀螺仪使用完全相同的轴向定义。
也就是说,ACC/GYRO 的 raw X/Y/Z 到 Android X/Y/Z 可能是一套映射,而 MAG 可能需要另一套映射。

最终目标不是让三个配置“写得一样”,而是让 APP 层看到的:
Accelerometer X/Y/Z
Gyroscope X/Y/Z
Magnetic Field X/Y/Z
全部落到同一套 Android 设备坐标系中。
如果 ACC/GYRO 都正常,但是指南针方向反、Rotation Vector 异常、Orientation 抖动,就应该重点检查 MAG 的坐标映射和磁校准。
7. Sensor HAL 层:确认是否注册和上报
HAL 层可以理解为 Kernel IIO 和 Android Framework 之间的“翻译层”。
它一般负责:
- 找到正确的 IIO 设备
- 读取 raw / scale / buffer 数据
- 完成单位换算
- 完成 orientation 坐标映射
- 注册 Android 标准 Sensor
- 持续上报 sensor event
可以通过下面命令查看 Android 是否识别到了传感器:
dumpsys sensorservice
重点看几个部分:
Sensor List
Active sensors
Active connections
Recent Sensor events
Fusion States
如果 Sensor List 中能看到 accelerometer、gyroscope、magnetic_field,说明 HAL 已经注册成功。
如果 Active sensors 中能看到对应 sensor,说明系统或 APP 正在使用它。
如果 Recent Sensor events 中能看到连续事件,并且 timestamp 持续递增,说明 HAL 正在持续向 SensorService 上报数据。
如果 Fusion States 中 9-axis fusion 已启用,并且四元数不再是全 0,说明 Android 已经拿到了基础传感器数据,并开始进行姿态融合。
8. APP 层:不能只看“有没有数据”
APP 层可以使用 Sensors Toolbox、Sensor Test 或自研 SensorManager 工具来验证。
但 APP 层测试不能只看“有没有数据”,而要按 Android 坐标系做固定动作,看符号是否符合预期。
加速度计测试
屏幕朝上平放:
X ≈ 0
Y ≈ 0
Z ≈ +9.8
屏幕朝下:
X ≈ 0
Y ≈ 0
Z ≈ -9.8
顶部朝上:
X ≈ 0
Y ≈ +9.8
Z ≈ 0
右侧朝上:
X ≈ +9.8
Y ≈ 0
Z ≈ 0
加速度计最适合用来判断 Android 坐标方向是否正确。
陀螺仪测试
静止时:
X ≈ 0
Y ≈ 0
Z ≈ 0
屏幕朝上平放,从屏幕正面看,逆时针旋转设备:
Gyro Z 应为正
顺时针旋转设备:
Gyro Z 应为负
这个符合右手定则:大拇指指向 Android +Z,四指弯曲方向就是正旋转方向。
磁力计测试
磁力计看 Magnetic Field,单位一般是 uT。
先看总磁场是否合理:
mag_total = sqrt(x² + y² + z²)
正常地磁场通常为几十 uT。
屏幕朝上水平旋转时,MAG X/Y 应该连续变化,不能某个轴完全不动,也不能跳变非常离谱。
在北半球,屏幕朝上时,地磁场通常有向地下的分量,因此:
屏幕朝上:MAG Z 通常为负
屏幕朝下:MAG Z 通常为正
如果指南针方向反了,优先检查 MAG 的 map/negate 配置。
如果方向大体正确但偏差较大,则要考虑磁力计校准和外部磁干扰。
9. 磁力计为什么最难调?
磁力计的问题通常分两类。
第一类是轴向问题。比如 X/Y 交换、某个轴取反、Z 方向反。这类问题必须改 orientation,校准无法解决。
第二类是磁干扰和校准问题。比如方向大体正确,但是指南针偏几十度,或者不同位置偏差不一样。这类问题可能和硬铁、软铁干扰有关。
常见干扰源包括:
磁铁
喇叭
电机
铁桌子
磁吸外壳
屏蔽罩
大电流线缆
电感
测试磁力计时,最好远离这些干扰源,并做 8 字校准动作。
判断方法可以简单记住:
方向反了,多半是轴向配置问题
方向大体对但偏差大,多半是磁干扰或校准问题
10. 总结
Android IMU 调试不是一个单点问题,而是一条完整链路:
硬件通信
Kernel IIO
Sensor HAL
SensorService
APP 层显示
坐标映射
磁力计校准
其中最容易出问题的是两个地方:
- orientation 坐标映射
- 磁力计方向和磁干扰
调试时只看“有没有数据”是不够的。更重要的是判断:
- 数据单位是否正确
- Android X/Y/Z 是否符合整机坐标
- ACC/GYRO/MAG 是否统一到同一套坐标系
- 磁力计是否受到外部干扰
- 融合传感器结果是否稳定
只要按照 Kernel IIO → HAL → SensorService → APP 的顺序逐层排查,IMU 问题通常都能比较快定位。调试顺序:
- 确认硬件供电和通信接口
- 确认 Kernel IIO 设备存在
- 确认 ACC/GYRO/MAG raw 数据会变化
- 确认 scale 换算后的数值合理
- 确认 HAL 能注册 Android sensor
- 确认 SensorService 有 Recent Sensor events
- 确认 APP 能收到数据
- 先调 ACC 坐标
- 再调 GYRO 坐标
- 最后单独处理 MAG 方向和校准
这个顺序很重要。不要一开始就调指南针,也不要在 ACC/GYRO 方向还没确认时反复修改 MAG。希望这篇来自 云栈社区 的梳理能帮你少走弯路。