先说结论:龙芯平台移植MPU6050驱动这件事,本质上没有被“国产平台”四个字吓住的必要。我这次代表走马观碑组在龙芯3A5000加LS7A桥片的板子上,把一个MPU6050六轴传感器完整跑通,从用户态裸读、i2c-dev验证,到最后走内核IIO驱动加设备树接管,整个过程比预想中顺利,但也踩了几个网上教程根本不会写的坑。这篇文章就是把整个MPU驱动移植过程摊开讲清楚,包括龙芯I2C控制器怎么定位、MPU6050有哪些寄存器必须处理、三条移植路线怎么选、设备树怎么改、以及最后数据到底怎么验证。如果你手上也有一块龙芯板子,或者后续要在LoongArch平台上做传感器适配,这篇文章应该能帮你省下不少排查时间。
1. 动手之前,先把龙芯平台的几个“底子”摸清楚
很多人在龙芯上做驱动移植卡住,不是卡在MPU6050本身,而是卡在平台认知上。龙芯和树莓派、STM32那套玩法有不少差异,我先把这次项目里摸清楚的平台背景交代一下。
1.1 龙芯I2C控制器长在哪里,设备树又是怎么回事
龙芯3A5000这类桌面/工控处理器,CPU内核本身不带外设控制器,大量I/O功能放在桥片LS7A上,I2C控制器也在这里。2K系列则不同,属于SoC集成,I2C控制器直接做在片内。这两个平台在设备树里的描述方式不完全一样,搜资料时要先确认自己手上的芯片型号,别拿3A5000的dts照搬到2K1000上。
以我用的3A5000加LS7A为例,设备树源码一般在内核树的arch/loongarch/boot/dts/loongson/目录下,桥片的外设节点写在类似ls7a.dtsi的文件里。I2C控制器节点名字通常是i2c@1fe00000这种风格,但不同内核版本、不同板级文件命名有差异。内核源码里对应的驱动在drivers/i2c/busses/i2c-ls2x.c,这个文件是龙芯I2C控制器的原生驱动,改设备树前最好先看一眼这个驱动支持的 compatible 字符串和寄存器布局。
注意:龙芯的固件(PMON或UEFI)会把设备树传给内核,有些版本固件也内置了一份DTB。改完dts后最稳妥的做法是连同内核一起重新编译打包,而不是单独指望加载外部dtb。不同板的固件行为不一样,我这边踩过的教训是“改了dts但没生效”,最后发现是固件优先用了内置DTB,折腾了我们一下午。
1.2 硬件连接:先量电平,再谈软件
MPU6050模块大部分是3.3V供电,SCL/SDA引脚电平也是3.3V。龙芯板子的I2C引脚通常通过排针引出,也可能是GPIO复用。连线并不复杂,但有一个原则必须遵守:先确认电平,再上电。MPU6050不是5V耐受器件,把SDA/SCL接到5V上基本就报废了。
我这次的实际接线方式如下:
| MPU6050引脚 | 龙芯板对应位置 | 备注 |
|---|---|---|
| VCC | 3.3V电源 | 不能用5V |
| GND | 电源地 | 和板子共地 |
| SCL | I2C0_SCL或指定GPIO | 查板卡原理图确认 |
| SDA | I2C0_SDA或指定GPIO | 查板卡原理图确认 |
| AD0 | GND | 接地后I2C地址为0x68 |
| INT | 暂不接 | 第一版调试不建议接中断 |
AD0这个引脚值得多说一句。AD0接地时传感器地址是0x68,接高电平时是0x69。如果总线上只想挂一个MPU6050,建议直接接地。上拉电阻的问题也要注意:很多现成模块已经自带上拉电阻,裸芯片的话SCL/SDA需要各接一个4.7k电阻到VDD,不然I2C波形立不起来,表现就是i2cdetect偶尔能扫到、偶尔扫不到。
1.3 交叉编译环境和目标板通信
驱动移植最终要编内核,交叉工具链是起步条件。我用的发行版仓库里直接有gcc-loongarch64-linux-gnu,如果没有,也可以从Loongnix软件源或龙芯开源社区下载交叉工具链。内核源码建议直接使用龙芯厂商维护的内核分支,或者主线新内核。老内核的问题在于可能根本没有LoongArch架构支持,更不用说LS7A的I2C驱动。
基本编译命令如下,前面是配置,后面是编译:
make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- loongson3_defconfig make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- menuconfig make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- -j$(nproc) Image新一点的内核可能在defconfig命名上有所不同,有的叫loongarch_defconfig,有的叫loongson3_defconfig,具体看内核目录下的arch/loongarch/configs/。目标板通信方面,我一般串口和SSH双通道备着:串口看早期启动日志,SSH用来传内核包、跑i2c-tools。内核编译完成后替换目标板/boot下的内核镜像,重启后通过uname -a确认有没有真的跑上新内核。
2. 先弄懂MPU6050这颗传感器到底要“喂”什么
MPU6050是一个I2C从设备,通信本身不复杂,但它有几个寄存器不处理好的话,驱动写了也是白写。我建议在写任何代码之前,先把下面这些细节吃透,尤其是WHO_AM_I和电源管理寄存器。
2.1 必须记住的寄存器地图
很多移植教程上来就贴代码,不解释寄存器。但实际情况是,一旦数据不对,你还是要回来翻手册。这里列几个这次项目里离不开的寄存器:
| 寄存器地址 | 名称 | 作用 |
|---|---|---|
| 0x75 | WHO_AM_I | 设备ID,复位值0x68,用来验证I2C链路是否打通 |
| 0x6B | PWR_MGMT_1 | 电源管理,bit6是SLEEP,bit7是DEVICE_RESET |
| 0x19 | SMPLRT_DIV | 采样率分频 |
| 0x1A | CONFIG | 数字低通滤波DLPF配置 |
| 0x1B | GYRO_CONFIG | 陀螺仪量程配置 |
| 0x1C | ACCEL_CONFIG | 加速度计量程配置 |
| 0x3B | ACCEL_XOUT_H | 加速度数据,X/Y/Z各2字节,高字节在前 |
| 0x43 | GYRO_XOUT_H | 陀螺仪数据,X/Y/Z各2字节,高字节在前 |
| 0x41 | TEMP_OUT_H | 温度数据 |
其中0x75这个寄存器是排障的第一抓手。在用户态用i2cget直接读0x75,如果能读到0x68,说明I2C物理链路、设备地址、电平配置都没有问题;如果读不到,那后面谈数据都是空谈。另外提醒一句,网上不少人把MPU6050写错成“mp6050”,搜资料的时候注意关键词,不然会漏掉一些本来很有用的帖子。
2.2 读出来的原始值怎么换算成物理量
MPU6050输出的是16位有符号补码,范围大约在-32768到32767。默认量程下:
- 加速度计默认±2g,灵敏度16384 LSB/g,换算公式:
加速度(g) = 原始值 / 16384。 - 陀螺仪默认±250°/s,灵敏度131 LSB/(°/s),换算公式:
角速度(°/s) = 原始值 / 131。
量程不同,换算系数完全不同,这个新手很容易翻车。完整对应关系如下:
| 量程配置 | 加速度量程 | 加速度灵敏度 | 陀螺仪量程 | 陀螺仪灵敏度 |
|---|---|---|---|---|
| 默认 | ±2g | 16384 LSB/g | ±250°/s | 131 LSB/(°/s) |
| 可选 | ±4g | 8192 LSB/g | ±500°/s | 65.5 LSB/(°/s) |
| 可选 | ±8g | 4096 LSB/g | ±1000°/s | 32.8 LSB/(°/s) |
| 可选 | ±16g | 2048 LSB/g | ±2000°/s | 16.4 LSB/(°/s) |
比如你把陀螺仪量程设成了±2000,还套用131的系数去换算,出来的角速度会夸张到完全没法用。我习惯在驱动初始化时明确配置量程,而不是依赖复位默认值,这样代码可读性更好,后续调参也方便。
2.3 内核里其实有现成的MPU6050驱动,别重复造轮子
这是本次移植最重要的一条认知:Linux内核自带的drivers/iio/imu/inv_mpu6050/目录下已经实现了MPU6050的完整驱动,包括I2C接口、寄存器初始化、IIO框架接入。它是IIO(Industrial I/O)子系统的一部分,不是传统的杂项设备驱动。也就是说,在龙芯上移植MPU6050最正规、最省力的路线不是“从零写一个驱动”,而是“配置内核选项加修改设备树,让现成驱动匹配上我们的硬件”。
IIO框架的好处在于:驱动注册后会在用户态暴露一组标准化的in_accel_x_raw、in_gyro_y_raw之类的属性节点,上层应用不需要知道底层寄存器地址,只要读sysfs节点就能拿到传感器数据。这对比传统misc驱动要规范得多,将来接上层算法、接libiio、接iio-hwmon都方便。
3. 三条移植路线选哪条,取决于你要干什么
同样的硬件,在龙芯上有三种玩法:用户态直读、用i2c-dev在用户态操作、内核IIO驱动。三条路线我都实际跑过,下面把各自情况说清楚。
3.1 路线一:用户态直读,最快验证硬件好坏
不需要编译任何内核模块,也不需要设备树,只要内核开启了/dev/i2c-N设备支持(CONFIG_I2C_CHARDEV),就可以用C或Python直接访问I2C总线和MPU6050。
C语言的简化版本大概长这样,用来验证硬件链路:
#include <stdio.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <linux/i2c-dev.h> int main(void) { int fd = open("/dev/i2c-0", O_RDWR); if (fd < 0) { perror("open"); return 1; } // AD0接地,设备地址0x68 if (ioctl(fd, I2C_SLAVE_FORCE, 0x68) < 0) { perror("ioctl"); return 1; } // 唤醒MPU6050:PWR_MGMT_1写0,清掉SLEEP位 uint8_t wake[] = {0x6B, 0x00}; if (write(fd, wake, 2) != 2) { perror("write wake"); return 1; } usleep(100000); // 读WHO_AM_I,正常应返回0x68 uint8_t reg = 0x75; uint8_t who = 0; write(fd, ®, 1); read(fd, &who, 1); printf("WHO_AM_I = 0x%02X\n", who); // 读加速度X轴高字节和低字节,拼成16位补码 reg = 0x3B; uint8_t data[6]; write(fd, ®, 1); read(fd, data, 6); int16_t ax = (data[0] << 8) | data[1]; int16_t ay = (data[2] << 8) | data[3]; int16_t az = (data[4] << 8) | data[5]; printf("accel raw: %d %d %d\n", ax, ay, az); close(fd); return 0; }这段代码基本就是MPU6050驱动的最核心部分。它适合在第一步验证“传感器本身是不是好的”“I2C链路是不是通的”。但它的缺点也很明显:没有中断、没有缓冲、没有量程配置的管理,姿态算法在上面无法稳定跑起来。所以我只把它当“探针”用,不做正式方案。
3.2 路线二:i2c-dev框架,不编译内核也能先跑起来
路线二本质上和路线一共享同一个/dev/i2c-N接口,但操作方式更接近“在用户态写一个半完整驱动”。Linux内核只要开了CONFIG_I2C_CHARDEV=y,用户态就可以打开/dev/i2c-0、/dev/i2c-1等文件。用Python的smbus库会更高效:
import smbus import time bus = smbus.SMBus(0) # 总线号要看板子实际注册为几号 addr = 0x68 # 唤醒 bus.write_byte_data(addr, 0x6B, 0x00) time.sleep(0.1) # 读WHO_AM_I who = bus.read_byte_data(addr, 0x75) print("WHO_AM_I =", hex(who)) # 读加速度X/Y/Z原始值 data = bus.read_i2c_block_data(addr, 0x3B, 6) ax = (data[0] << 8) | data[1] ay = (data[2] << 8) | data[3] az = (data[4] << 8) | data[5] print("accel raw:", ax, ay, az)这个路线的价值在于快速原型验证。你想确认数据手册里的某个寄存器行为,或者想临时调整量程、DLPF设置,直接写几行脚本就能看效果,不用每改一次就编一次内核。但它同样没有系统级的设备管理,适合调试和测试,不适合作为产品驱动的最终形态。
3.3 路线三:正规军,内核IIO驱动加设备树
真正要长期使用,还是得走内核IIO驱动。这条路线是“配置驱动框架加设备树描述”,开发量反而最小。三条路线的对比如下:
| 方案 | 开发量 | 适用场景 | 缺点 |
|---|---|---|---|
| 用户态C直读 | 最小 | 验证硬件、快速测试 | 无中断、无缓冲、不规范 |
| i2c-dev+Python | 小 | 原型验证、寄存器调试 | 性能一般、无设备管理 |
| 内核IIO驱动+设备树 | 中等 | 正式产品、算法对接 | 需要改设备树和编译内核 |
我最终选择路线三。接下来就把这部分完整展开。
4. 龙芯上把MPU6050驱动真正挂进内核的完整过程
这部分是实操核心。从设备树修改到内核配置,再到驱动加载确认,一步步来。
4.1 设备树修改:把MPU6050描述为I2C0的子节点
在龙芯的设备树中,I2C控制器节点本身已经存在,但未必使能,也未必把MPU6050挂上去。我们要做的事情就是给对应的I2C控制器加一个子节点。以下是精简后的设备树片段,以I2C0为例:
&i2c0 { status = "okay"; clock-frequency = <100000>; #address-cells = <1>; #size-cells = <0>; mpu6050@68 { compatible = "invensense,mpu6050"; reg = <0x68>; interrupt-parent = <&gpio>; interrupts = <某个GPIO中断号 IRQ_TYPE_EDGE_RISING>; mount-matrix = "0", "1", "0", "-1", "0", "0", "0", "0", "1"; }; };我强烈建议第一版先把interrupt-parent和interrupts这两行注释掉,不要配置中断。原因后面会详细讲,先记住结论:没有中断配置,驱动也能正常工作,做轮询读取;配置错了反而可能probe失败。mount-matrix不是必须的,但如果你板子上传感器的安装方向和芯片主轴不一致,这个属性可以帮你做旋转矩阵换算,避免上层算法里做额外翻转。
compatible = "invensense,mpu6050"是驱动匹配的关键。内核的inv_mpu6050_i2c.c驱动里i2c_device_id表里就有这个字符串,字符串对不上,设备树写了也白写。
还有一个容易忽略的点:要确认你挂在哪个I2C控制器上。龙芯板子可能引出多个I2C总线,比如I2C0、I2C1,设备树里对应多个节点,把子节点写错总线,i2cdetect会在错误的/dev/i2c-N上扫不到设备。我这次就经历过“明明接了排针标着I2C0,结果设备树里I2C0节点是disabled、I2C1才是默认打开的”这种反直觉情况。
4.2 内核配置:把inv_mpu6050驱动编进来
设备树改好后,需要在内核配置里打开IIO子系统和MPU6050驱动。相关选项如下:
CONFIG_IIO=y CONFIG_IIO_BUFFER=y CONFIG_IIO_KFIFO_BUF=y CONFIG_INV_MPU6050_IIO=y CONFIG_INV_MPU6050_I2C=y如果你用menuconfig,可以直接搜索MPU6050关键字找到对应项。CONFIG_INV_MPU6050_IIO是驱动核心部分,CONFIG_INV_MPU6050_I2C是I2C传输层。两者都打开后,重新编译内核、替换目标板内核并重启,驱动就应该被加载了。
编译过程中有个经验:确定自己的内核版本,老内核里drivers/iio/imu/inv_mpu6050/的Kconfig菜单路径可能有细微差别。先用make menuconfig搜索关键字,确认选项真实存在,再保存配置,不要凭记忆写.config。
4.3 确认驱动加载:dmesg和sysfs双验证
重启后,第一件事看日志:
dmesg | grep -i mpu正常情况下能看到类似“inv-mpu6050 i2c-0: 0x68: who_am_i 0x68”或者“i2c inv-mpu6050: probe success”之类的日志。如果出现“failed to read who_am_i”,那就要回头查硬连接和I2C总线。
接着检查IIO设备节点是否注册成功:
ls /sys/bus/iio/devices/ cat /sys/bus/iio/devices/iio:device0/name cat /sys/bus/iio/devices/iio:device0/in_accel_scale cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw如果name返回类似“mpu6050”的内容,说明驱动已经成功接管传感器。in_accel_scale返回的是把原始值换算成标准单位(加速度单位m/s²)的比例系数。默认±2g量程下,这个值大约0.000598,也就是9.80665 / 16384。in_accel_x_raw是16位原始值,两者相乘就是物理加速度。
4.4 中断和DMP扩展:等基础通路稳定了再考虑
很多人一上来就想把INT引脚中断和DMP(数字运动处理器)都搞起来。我的建议是优先级往后放。DMP本身是InvenSense的闭源固件,内核自带的inv_mpu6050驱动并不直接包含DMP固件加载功能(部分厂商分支会带上),要完整跑DMP输出四元数还需要移植MPL库或者使用带DMP支持的第三方方案,工作量不小。
中断则属于“可以加但不需要一开始就加”的功能。IIO驱动在没有中断的情况下会用轮询模式工作,10Hz采样完全够用。先把数据通路跑通,再考虑用中断和buffer模式做更高帧率的姿态算法,这才是正确的推进节奏。
5. 数据验证:从i2cdetect到姿态数据判读
驱动加载成功不代表数据正确。我这次在验证阶段用了三层手段,从底层往上层层确认。
5.1 i2c-tools三板斧:i2cdetect、i2cget、i2cdump
在目标板上安装i2c-tools,然后依次执行:
# 列出所有I2C总线 i2cdetect -l # 扫描总线0上的设备,如果看到68或69,说明设备在该总线上 i2cdetect -y 0扫到0x68后,再确认WHO_AM_I寄存器:
i2cget -y 0 0x68 0x75返回0x68,I2C链路就没问题。如果想看完整寄存器状态,用i2cdump -y 0 0x68,可以一页一页翻寄存器值,对排查量程配置、是否处于sleep状态都很有用。
5.2 从IIO节点读取加速度和角速度
驱动加载成功后,直接读sysfs节点就行:
cat /sys/bus/iio/devices/iio:device0/in_accel_scale cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw cat /sys/bus/iio/devices/iio:device0/in_accel_y_raw cat /sys/bus/iio/devices/iio:device0/in_accel_z_raw cat /sys/bus/iio/devices/iio:device0/in_gyro_x_raw cat /sys/bus/iio/devices/iio:device0/in_gyro_y_raw cat /sys/bus/iio/devices/iio:device0/in_gyro_z_raw加速度原始值乘以in_accel_scale,得到m/s²。板子平放时,z轴方向应该接近9.8,x/y轴接近0。陀螺原始值乘以in_gyro_scale,得到rad/s,静止时三个轴都在0附近小幅波动。如果静止状态下加速度三轴合成模长远远偏离9.8,比如只有2,那大概率数据没对,常见原因是传感器还在sleep,或者读错了寄存器。
5.3 用buffer模式做连续采样
如果要给姿态算法供数,不能手动一个个cat节点,要用IIO的buffer模式。先把要采样的通道enable,再enable buffer:
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_x_en echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_en echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_z_en echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_gyro_x_en echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_gyro_y_en echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_gyro_z_en echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable cat /dev/iio:device0 | xxdbuffer里每个采样点的数据顺序和scan_elements里enable的顺序一致,每个通道是16位有符号小端整数。如果插入了timestamp,还要按8字节时间戳来解析。想省事的话可以编译内核源码包里的tools/iio/iio_generic_buffer,输出更直观。
5.4 数据合理性检查,别等算法跑起来才发现数据是反的
最后一个验证步骤是“物理合理性普查”。把板子分别平放、竖放、倒放,记录三轴加速度变化。平放时z轴约±9.8,竖放时对应轴约±9.8,其余轴接近0。陀螺仪用手快速转动板子,数值应立刻响应,停稳后回归0附近。这个验证不能省,我有一次就是装反了方向,上层算法输出的姿态角整个镜像翻转,排查了很久才发现是安装方向没有用mount-matrix处理。
6. 这次移植最容易翻车的几个点,逐个说清楚
最后把这次踩过的坑按概率从高到低排一遍。每个坑我都给了一个明确的处理思路。
6.1 i2cdetect扫不到设备:按顺序排查三个最常见因素
最常遇到的情况就是i2cdetect -y 0什么也扫不到。我的排查顺序是:供电和地先量一遍,确保VCC真的是3.3V,GND和板子共地;然后量SCL/SDA对地电压,正常情况下两个引脚被上拉到3.3V左右,如果量到0V,可能是管脚复用配置不对,也可能是引脚接反了;最后确认总线号,i2cdetect扫的是1号总线但你设备接在0号上,当然什么都扫不到。
还有一种情况是模块自带上拉,但龙芯板子的引脚被重复配置成其他功能,导致I2C控制器没有真正把引脚接管过去。这时候要么去pinctrl配置里把复用关系改对,要么干脆走i2c-gpio用GPIO模拟I2C,这部分后面会单独说。
6.2 WH0_AM_I能读到0x68,但加速度数据全是0:问题出在唤醒
这个坑很有迷惑性。I2C通信正常,WHO_AM_I也对,但读加速度和陀螺的数据寄存器全是0。原因很简单:MPU6050默认处于sleep状态,PWR_MGMT_1的SLEEP位是1,传感器内部大部分功能没有启动,数据寄存器不会更新。
处理方式就是初始化时向PWR_MGMT_1(0x6B)写0,清掉SLEEP位,然后至少要等100ms让内部时钟和传感电路稳定,再开始读数据。这个唤醒操作放在所有初始化的最前面,否则后续配置都可能无效。
6.3 设备树里配了中断反而probe失败:龙芯中断配置的坑
我一开始在设备树里给MPU6050加了interrupt-parent和interrupts,结果dmesg里报中断申请失败,驱动probe直接失败。问题出在我把中断号理解成了线性编号,实际上龙芯LS7A的GPIO中断要按GPIO bank来组织,中断号和GPIO号之间存在映射关系,不同板号、不同bank映射还不同。
这也是我为什么在前面强调“第一版别配中断”。龙芯的中断控制器行为和x86/ARM不完全一样,查资料的成本很高。先把传感器数据通过轮询跑通,再根据板卡的原理图和固件文档去梳理GPIO中断映射,风险会小很多。如果你确实要用中断,建议直接用GPIO的中断属性,不要自己去猜中断号。
6.4 硬件I2C控制器不给力时:i2c-gpio是最后的逃生通道
最后一个备用方案,也是排查问题时很好用的隔离手段:不用芯片自带的I2C控制器,改用两个GPIO模拟I2C时序。内核的i2c-gpio驱动是通用的,设备树里描述两个GPIO后,系统会注册一个新的I2C总线,传感器挂在这条总线上同样能被inv_mpu6050驱动识别。
设备树示意如下:
i2c-gpio@0 { compatible = "i2c-gpio"; gpios = <&gpio 4 GPIO_ACTIVE_HIGH>, /* SDA */ <&gpio 5 GPIO_ACTIVE_HIGH>; /* SCL */ i2c-gpio,sda-open-drain; i2c-gpio,scl-open-drain; #address-cells = <1>; #size-cells = <0>; mpu6050@68 { compatible = "invensense,mpu6050"; reg = <0x68>; }; };i2c-gpio方式速度通常不超过100kHz,对MPU6050这种传感器完全够用。它的价值在于:当你不确定是“芯片原生I2C控制器的问题”还是“传感器的问题”时,用GPIO模拟I2C可以干净地隔离故障源。我上次遇到一个板子的原生I2C控制器死活不产生波形,换用i2c-gpio后传感器立刻就能读,问题明显出在控制器或管脚复用配置上,而不是传感器本身。
最后说点大实话。这次龙芯MPU驱动移植,最大的教训不是MPU6050有多难,而是平台差异比想象中更容易让人绕远路。先用户态把硬件验证透,再上内核驱动;先不配中断,再逐步加功能;优先复用内核现成的inv_mpu6050驱动,而不是自己从寄存器开始写。这套顺序让我省了很多无谓的排障时间。另外手头最好留一个逻辑分析仪或者示波器看I2C波形,实在没有,i2cdetect回显的粘性也可以帮你判断波形质量。后续这个项目我打算在把DMP固件加载跑通,然后从IIO buffer里直接拿传感器数据喂给姿态解算,到时候再写一篇续篇。