团队里给项目起名的同事大概也没想到,这个“走马观碑组MPU驱动移植”的任务,最后会变成我近期花时间最多的一件小事。事情本身的起因很简单:手头到了一块龙芯K平台开发板,想在上面接一颗MPU6050六轴传感器,做姿态数据采集。MPU6050这颗芯片不稀奇,Linux内核里也早就有了现成驱动,但当真要把它在龙芯K的Linux环境里跑起来,中间隔着的不是代码,而是一堆平台差异、设备树和固件细节。
这里的MPU指的不是微处理器概念里的MPU,而是InvenSense(现在属于TDK)的MPU6050,一颗经典的六轴惯性传感器,三轴加速度加三轴陀螺仪,I2C接口,功耗低、价格便宜,是很多姿态测量和动作识别项目的入门首选。整个移植工作可以概括成一句话:把Linux内核里为MPU6050写的IIO驱动,在龙芯K平台上完成编译、适配和验证,让传感器数据能稳定、正确地被系统读取。
这篇文章适合正在做龙芯平台外设适配的嵌入式工程师、要参加相关比赛的学生团队,以及对Linux内核IIO子系统感兴趣的读者。整篇记录是实操流水账加踩坑心得,不会只罗列命令,关键步骤都会解释为什么这么做。
1. 项目到底要做什么:走马观碑组的一次MPU驱动移植选型
1.1 龙芯K平台和一颗小传感器的组合
龙芯K系列是龙芯在桌面和嵌入式方向的一条产品线,基于LoongArch指令集架构。和常见的x86、ARM平台相比,它的软件生态还在快速完善中,很多外设驱动在主线Linux里已经能跑,但不同板卡之间的差异仍然存在。我们手头这块龙芯K开发板,跑Linux 5.15内核,板上引出了多路I2C、SPI、UART等常用接口,理论上看,接一颗I2C接口的MPU6050是标准操作。
但“理论上看”和“实际能用”之间隔着一座山。MPU6050的Linux驱动虽然在内核里,但驱动被编译进内核、被设备树匹配、被I2C控制器正确枚举是一连串相互依赖的动作。在x86平台或者成熟的ARM开发板上,这些链路大多被厂商调好了,换到龙芯平台,每一步都可能掉链子。尤其是LoongArch的内核分支、板级设备树、片上I2C控制器的差异,会把一个看似简单的“接个传感器”变成一次小型落地项目。
这次项目的最终目标很明确:让MPU6050的数据出现在龙芯K平台的/sys/bus/iio/devices/目录下,可以通过标准IIO接口读到加速度和角速度原始值,并且长时间运行不报错。不是从零写一个驱动,而是把内核里现成的驱动“移植”到目标平台并跑通。
1.2 技术路线:为什么选“内核主线驱动加设备树适配”
动手之前,团队内部其实评估过三条技术路线。
第一条是用户态直读I2C,就是利用Linux的i2c-dev接口,写一个用户态程序通过/dev/i2c-N直接读写MPU6050寄存器。这条路线最简单,几行代码就能读WHO_AM_I,但缺点也明显:占着总线不放、没有中断支持、没有缓存机制,数据实时性和通用性都差,而且完全绕开了内核的驱动模型,属于“能用但不好用”。
第二条是把厂商提供的MPU6050驱动源码交叉编译成内核模块。很多传感器厂商会提供linux驱动源码包,但这些源码往往只针对某个特定内核版本,拿到新平台后需要改不少接口。而且这类驱动质量参差不齐,有的还带着私有API,后续维护成本很高。
第三条就是这次实际采用的方案:复用内核主线drivers/iio/imu/inv_mpu6050驱动,通过设备树节点把I2C总线和传感器匹配起来,针对龙芯平台做配置和必要的小改动。选这条路的原因很直接:主线驱动由内核社区长期维护,IIO框架也更现代,上层应用好对接。对一颗2012年就发布的传感器来说,主线驱动的成熟度远超大多数厂商驱动。
| 方案 | 开发量 | 稳定性 | 可维护性 | 适用场景 |
|---|---|---|---|---|
| 用户态i2c-dev直读 | 最小 | 一般 | 差 | 快速验证硬件链路 |
| 厂商驱动源码移植 | 中等 | 取决于源码质量 | 中等 | 主线无驱动的传感器 |
| 主线驱动+设备树适配 | 小 | 高 | 好 | 主线已有支持的传感器 |
如果你手里的传感器在主线IIO子系统里本来就有驱动,别犹豫,直接走第三条路线。很多人在“自己写一个驱动”和“改动现有驱动”之间选择了前者,结果把大量时间花在了重复造轮子上。
1.3 怎么才算移植成功
没有验收标准的移植都是耍流氓。这次项目在动手前就列了一个五条验收清单,后面所有工作都对着这个清单推进:
- i2cdetect能扫到0x68地址的设备节点,说明I2C链路正常。
- 驱动probe成功,dmesg里能看到inv-mpu6050相关的芯片ID信息。
- /sys/bus/iio/devices/iio:device0下能读到in_accel_*和in_gyro_*属性。
- 采样频率可以配置,并能通过FIFO或直接读取方式连续拿到数据。
- 板卡连续运行两小时以上,无I2C超时、无内核错误、数据无异常跳变。
第五条看上去简单,但恰恰是最容易暴露问题的。比如驱动默认配置的采样率偏高时,如果I2C控制器时钟配置不对,跑一会就会出现FIFO溢出,数据开始丢帧。所以验收标准里一定要包含稳定性测试,不能只读到一个数据就算完事。
2. 动手之前的四项准备:龙芯K平台环境与内核源码梳理
2.1 先摸清楚板卡硬件拓扑
很多移植失败的问题,根子不在驱动代码上,而在硬件连接没搞清楚。拿到板卡第一步,不是插电开整,而是找原理图和用户手册,确认三个问题:传感器接到了哪个I2C控制器,中断脚接到了哪个GPIO,以及传感器供电电压是多少。
我们这块板卡的I2C2控制器把SDA和SCL引到了扩展排针上,传感器模块的VDD接3.3V,AD0引脚悬空,因此I2C地址是0x68。如果你用的模块AD0接了高电平,地址就会变成0x69,这是排查时特别容易忽略的点。有条件的话,拿万用表量一下SDA、SCL上有没有上拉电阻,很多便宜的MPU6050模块板上自带上拉,焊接在开发板上的版本则要看板子设计。I2C总线没有上拉或者上拉电阻过大,都会导致通信不稳定,但又不至于完全不通,这种“薛定谔的设备”最耽误时间。
另外留意中断脚。MPU6050的INT脚是开漏输出,如果你要使用数据就绪中断,得在设备树里配置对GPIO。但如果第一阶段只做数据轮询,可以先不接中断脚,等基本数据通路通了再补。我们这次先用了轮询模式,后面才加中断,逐步增加复杂度,排查起来会舒服很多。
2.2 交叉编译器与内核源码准备
龙芯K平台属于LoongArch架构,不能用x86的gcc直接编译内核,需要准备loongarch64交叉工具链。龙芯官网提供了预编译的工具链,解压后把bin目录加入PATH即可。内核源码版本尽量与板卡厂商提供的一致,如果板子跑的是5.15,就用主线5.15版本;如果厂商在内核里打了一些板级补丁,最好直接用厂商发布的内核源码树,否则后面容易出现设备树节点对不上的情况。
配置内核前先检查一下编译器版本是否满足内核要求。LoongArch架构对gcc版本有一定要求,太老的编译器会直接编译失败,报一些莫名其妙的错误。建议先执行一次简单的编译验证,比如编译kernel/sysctl.o,确认工具链没问题,再开始配置工作。
export ARCH=loongarch export CROSS_COMPILE=loongarch64-linux-gnu- make loongson3_defconfig如果是厂商提供的BSP内核,配置目标名称可能不同,比如ls2k_defconfig之类,以厂商文档为准。生成.config之后,再用menuconfig补齐后面要用的配置项。
2.3 先用龙芯模拟环境把内核跑起来
在真机上反复刷机调试很费时间,尤其是一开始内核配置还可能缺东西。我们这阶段用了龙芯平台常用的QEMU模拟环境,用qemu-system-loongarch64配合一个精简根文件系统,把刚编译出来的内核先跑一遍,确认能够正常启动到shell。
这一步的核心价值是前置验证,把“内核能不能启动”和“模块能不能加载”这两个问题,从每次几分钟的刷机循环里解放出来。模拟环境里虽然不会真的有MPU6050芯片,但可以通过观察内核启动日志,确认I2C控制器、GPIO控制器、IIO子系统这些关键选项是否已经被编译进内核。
qemu-system-loongarch64 -M virt -m 1G -kernel vmlinux -drive file=rootfs.ext4,format=raw -append "root=/dev/vda console=ttyS0" -nographic启动后能看到内核日志正常滚动,进入shell后手动加载一个测试模块,确认模块加载链路没问题,说明基础内核配置OK。当然,模拟环境和真实硬件的驱动路径仍有差异,I2C外设级验证最终还是要回到真机。但先跑模拟环境,能帮你把“软件问题”和“硬件问题”这两类问题在前面切分开。
2.4 确认I2C总线编号和设备在线状态
等到真机就绪后,第一件事是确认I2C总线编号。龙芯K平台可能有多个I2C控制器,板卡文档里说的I2C2在Linux里可能编号为i2c-1或i2c-2,不能想当然。用i2cdetect -l列出所有总线,再逐个扫描。
i2cdetect -l i2cdetect -y 2如果扫描结果里没有0x68,先从硬件查起:传感器是否上电、SDA/SCL是否接反、地址是否是0x69、总线上是否有其他设备占用了同一地址。我曾经遇到一次扫描不到设备,最后发现是杜邦线接触不良,重新插拔后就好了。硬件问题优先排查,不要在软件配置上反复折腾。
确认总线能扫到设备后,可以用i2c-tools里的i2cget直接读WHO_AM_I寄存器(地址0x75),如果返回0x68,说明I2C通信完全正常,可以进入驱动移植阶段。
i2cget -y 2 0x68 0x75如果这里能读到正确ID,后面所有驱动问题都聚焦在“软件匹配”上,问题范围瞬间缩小很多。
3. 核心实现:MPU6050驱动在龙芯Linux内核里的移植步骤
3.1 先看懂inv_mpu6050驱动框架
Linux主线里MPU6050的驱动位于drivers/iio/imu/inv_mpu6050/目录下,核心文件包括inv_mpu6050_core.c、inv_mpu6050_i2c.c、inv_mpu6050_ring.c、inv_mpu6050_buffer.c等。大致分工是:core文件处理传感器初始化和IIO设备注册,ring和buffer处理数据缓冲与触发采样,i2c文件负责与I2C控制器打交道。
这个驱动本质上是一个I2C驱动,通过i2c_driver结构体向内核注册。它既支持通过i2c_device_id匹配设备树节点,也支持通过of_match_table匹配compatible字符串。MPU6050的compatible字符串是"invensense,mpu6050",设备树节点只要写好这个compatible,并挂在正确的I2C总线下,驱动就能被调用到probe函数。
用IIO框架的好处是,驱动注册完成后,用户态不需要调用read()去读一个字符设备,而是通过/sys/bus/iio/devices/iio:device0/目录下的各种属性文件直接读取数据。每个属性都有标准命名,比如in_accel_x_raw代表X轴加速度计的原始值,in_gyro_z_raw代表Z轴陀螺仪的原始值。上层应用可以完全不关心寄存器地址,只跟属性打交道。
3.2 设备树节点的写法与注意点
设备树是整个移植过程中最容易出错也最关键的部分。节点写法看起来很简单,但隐含细节很多。下面是我们最终使用的设备树片段,挂在i2c2控制器下:
&i2c2 { status = "okay"; clock-frequency = <400000>; mpu6050@68 { compatible = "invensense,mpu6050"; reg = <0x68>; interrupt-parent = <&gpio0>; interrupts = <5 IRQ_TYPE_EDGE_RISING>; mount-matrix = "0", "1", "0", "-1", "0", "0", "0", "0", "1"; }; };时钟频率这里有一个常见误区。MPU6050在数据手册里标注的最大I2C时钟是400kHz,也就是Fast Mode,所以clock-frequency = <400000>从芯片角度没问题。但实际能不能跑400k,取决于龙芯平台I2C控制器的实际时钟分频,以及总线上电容、上拉电阻是否满足要求。如果高速模式不稳定,日志里出现I2C NACK或超时,降到100kHz试试,往往能解决。
interrupt-parent和interrupts要看板级设备树里GPIO控制器的实际标签。我们板卡上接到了gpio0的第5脚,所以interrupt-parent = <&gpio0>,interrupts = <5 IRQ_TYPE_EDGE_RISING>。如果你的板卡GPIO控制器标签不同,直接照抄会编译报错或者probe时中断申请失败,一定要去平台dtsi里确认节点名。
mount-matrix是传感器的安装方向矩阵。如果你的传感器水平放置且X轴朝前,可以不写或者用单位矩阵。我们板卡安装后坐标系和默认方向不一样,所以用了上面的旋转矩阵。可以把mount-matrix理解成“传感器坐标系转换到设备坐标系”的旋转矩阵,数字格式是三行三列的字符串数组。一开始不确定方向时先不写,等读出实际数据再改。
3.3 内核配置项:一个都不能少
设备树写好后,要确认内核配置打开了相关子系统的支持。MPU6050驱动依赖IIO框架和IIO触发缓冲区,少了任何一个,驱动都能编译,但运行时不是找不到设备就是没有buffer节点。
Device Drivers -> Industrial I/O support -> Industrial I/O core Enable buffer support within IIO Enable triggered buffer support within IIO Accelerometers -> InvenSense MPU6050 devices InvenSense MPU6050 I2C driver Gyroscopes -> InvenSense MPU6050 devices不同内核版本的菜单布局略有差异。5.15版本里,MPU6050的加速度和陀螺仪功能放在同一个驱动目录下,勾选InvenSense MPU6050 I2C driver即可。除了IIO本身,还需要确保CONFIG_I2C、CONFIG_OF(Open Firmware设备树支持)、CONFIG_GPIOLIB以及对应GPIO中断控制器配置打开。
CONFIG_IIO=y CONFIG_IIO_BUFFER=y CONFIG_IIO_TRIGGERED_BUFFER=y CONFIG_INV_MPU6050_I2C=m CONFIG_INV_MPU6050_IIO=y我当时踩过一个坑:CONFIG_INV_MPU6050_I2C编成了模块,但rootfs里没有/lib/modules/对应目录,导致modprobe直接失败。如果只是为了快速验证,初期可以直接编进内核(=y),省去模块拷贝这一步。等确认功能正常后再改为模块,便于后续迭代。
3.4 代码层适配:什么时候需要动源码
我们这次比较顺利,inv_mpu6050驱动在5.15内核里可以直接编译通过,没有改一行代码。但如果你用的内核版本比较老或比较新,可能会遇到API差异。比如老内核里regmap_read_bypassed这个函数在某个版本后改了签名,或者I2C的struct i2c_client字段有变动。
这类问题排查思路是:编译报错后,先别急着猜,去对应内核版本的头文件和官方git日志里查这个函数的定义变化。比如报错信息提示inv_mpu6050_i2c.c里某行调用了regmap_read_bypassed,但在你的内核头文件里找不到这个函数,就去drivers/base/regmap/regmap.c里搜索当前版本对应的函数名。常见替代方案可能是regmap_read和带locking的版本。
如果改动不复杂,可以直接在驱动源码里做条件编译适配:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) ret = regmap_read_bypassed(regmap, reg, &val); #else ret = regmap_read(regmap, reg, &val); #endif但我的建议是,如果驱动源码需要改的地方超过三处,先停下来评估一下是不是内核版本差太远。找罪受不如换一个与平台内核版本相近的驱动版本。
3.5 编译、部署与验证的完整步骤
设备树和内核配置搞定后,进入编译部署环节。如果驱动编成模块,只需要编译模块部分,拷到板子上加载。建议编译模块时同时把设备树编一遍,确保dts语法没问题。
make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- dtbs make ARCH=loongarch CROSS_COMPILE=loongarch64-linux-gnu- M=drivers/iio/imu/inv_mpu6050 modules编译产物中会有一个inv-mpu6050-i2c.ko文件,这是I2C接口版本的驱动模块。拷到板子后先看内核是否已经识别到设备,再加载模块。
scp drivers/iio/imu/inv_mpu6050/inv-mpu6050-i2c.ko root@<board_ip>:/tmp/ ssh root@<board_ip> "insmod /tmp/inv-mpu6050-i2c.ko && dmesg | tail -20"如果一切正常,dmesg里会出现类似下面的日志:
inv-mpu6050 i2c-2:8: Found Invensense MPU6050 chip iio iio:device0: setup the trigger successfully此时再查看IIO设备目录:
ls /sys/bus/iio/devices/ cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw cat /sys/bus/iio/devices/iio:device0/in_gyro_y_raw连读几十次,数值应该在零附近有微小抖动,而不是固定值或跳变到离谱的大数。如果数值正常,说明整条数据通路已经打通。
更专业的验证方法是用内核源码自带的小工具iio_generic_buffer,可以配置采样次数、采样频率,并把数据存成文件。
cd tools/iio make ./iio_generic_buffer -a -n iio:device0 -c 100这个工具会打印设备类型、采样频率、可用通道等信息,并连续采样100个点。它能直观看到FIFO有没有溢出、每次采样的时间戳是否正常。我第一次跑的时候打印的采样频率明显偏高,数据时间戳跳动,后来在设备树里把clock-frequency从400k降到100k才恢复正常,非常典型的平台差异问题。
4. 实测排障:龙芯平台MPU6050移植中遇到的坑与修复方法
4.1 一张速查表,先解决八成问题
整个调试过程中遇到的问题可以整理成一张速查表。如果你在移植中卡住,先对照这张表排查,大概率能省一晚上的时间。
| 现象 | 可能原因 | 排查手段 | 解决方向 |
|---|---|---|---|
| i2cdetect扫描不到设备 | 接线问题、地址错误、模块未上电 | 万用表测量、检查AD0电平 | 重新接插线、配置地址为0x69 |
| 扫描到设备但驱动probe失败 | dts compatible不匹配 | dmesg查看驱动匹配过程 | 检查设备树字符串拼写 |
| WHO_AM_I读出来不是0x68 | I2C通信受干扰、上拉弱 | 降低clock-frequency | 频率从400k降到100k |
| 读到加速度全为固定大数 | 传感器供电不稳或初始化失败 | 读scale属性、检查PWR_MGMT_1 | 重新上电、复位传感器 |
| FIFO溢出或采样时间戳异常 | 采样频率过高、I2C时序问题 | 降低采样频率 | 检查in_*_sampling_frequency |
| 中断不触发或申请失败 | GPIO复用配置不对 | 检查gpiochip、pinctrl | 修改pinctrl和interrupts配置 |
| 编译驱动报错找不到函数 | 内核版本API差异 | 搜索当前内核对应函数 | 修改驱动或换驱动版本 |
这张表里最常见的一类问题就是设备树匹配相关的。dmesg里如果出现“inv-mpu6050: probe of 2-0068 failed with error -22”,基本可以确定是设备树参数有问题。error -22对应EINVAL,通常是某个属性值不合法,比如reg写错格式、interrupts缺少flags。
4.2 三个印象最深的难题
第一个难题是I2C总线编号对不上。设备树里把传感器挂在&i2c2下,但实际扫描后发现根本扫不到设备。捣鼓了半小时发现,龙芯K平台设备树里的i2c2在Linux运行时枚举出来的总线号不是2,而是4。因为dtsi里还有多个I2C控制器,有些status是disabled,但编号顺序并不连续。后来我改用i2cdetect -l先列出所有总线,再逐个扫描,最终在i2c-4上找到了设备,设备树节点名称不改,但代码里要根据实际枚举结果调试。
第二个难题是中断引脚的GPIO复用。设备树里配好了interrupts,但模块probe时申请中断一直失败。排查后发现板卡上这颗GPIO默认被复用成了其他功能,需要在dts里配置pinctrl将引脚切换为GPIO模式。这类问题在带内部复用功能的SoC上极其常见,尤其是龙芯K这样外设控制器较多的芯片。解决方案是在传感器节点里添加pinctrl属性,在board级dts里定义对应的引脚配置:
pinctrl-0 = <&gpio0_5_pin>; pinctrl-names = "default";具体引脚标签要以板卡dts的pinctrl头文件为准。如果你不确定,先不配interrupt属性,改用轮询模式,数据照样能读,只是不能用数据就绪中断。
第三个难题来自一块老批次的板卡。I2C通信在跑采样时总出现随机超时,一开始怀疑是驱动或者传感器损坏,后来查看板级固件changelog,发现旧版固件对I2C控制器的时钟配置描述不完整,官方更新固件后这个问题就不存在了。从那以后我每拿到一块新板子,第一件事就是记录固件版本号,并确认官方是否有已知问题说明。在嵌入式平台调试外设,很多莫名其妙的硬件问题和驱动没关系,更新板级固件往往比改代码更有效。
4.3 数据校准和方向验证的粗浅经验
驱动能出数据只是第一步,数据准不准是另一回事。由于MPU6050是MEMS传感器,芯片出厂时有零偏,而且不可能完全理想地安装在电路板上,所以读取原始值后,必须结合scale属性和零偏校准。
加速度计的scale属性在/sys/bus/iio/devices/iio:device0/in_accel_scale里,陀螺仪对应in_gyro_scale。物理值等于raw值乘以scale。比如in_accel_x_raw读出来是1024,in_accel_scale是0.000598,那么实际加速度大约是0.612g。校准通常分两步:
第一步是零偏校准,把设备静止放置在水平面上,连续采集1000个样本,分别求X、Y、Z三轴的均值。理论上水平静止时,加速度计Z轴应该约等于1g,X和Y接近0,陀螺仪三轴都接近0。实测均值与理论值的偏差,就是该轴的零偏。
第二步是尺度校准,一般以重力加速度1g作为参考,把Z轴读数调整为标准值,计算出scale修正系数。不过大多数应用直接用内核默认scale就够了,真正的关键是方向校准。如果旋转板卡后,加速度读数的正负方向和实际运动方向相反,就需要mount-matrix,或者在上层应用里做坐标变换。
方向验证的方法很朴素:把板卡分别沿X轴、Y轴、Z轴旋转90度,观察对应轴的读数变化。X轴朝上时,in_accel_x_raw应该约等于+1g,朝下则约等于-1g,以此类推。我们当时发现X轴和Y轴数据和实际方向交换了,于是通过mount-matrix做了一个90度旋转校准。
4.4 模拟环境到真机的差异处理
模拟环境里验证不了真实的I2C设备,这是模拟器天生的局限。但不能因此否定模拟环境的价值。我把模拟环境用来验证三件事:内核能否启动、IIO相关的模块能否加载、设备树编译后的二进制能否被正确解析。这三件事在模拟环境里跑通,能大幅缩短真机调试周期。
在真机联调时,我会先用用户态程序直读寄存器,确认I2C链路没问题,再加载内核驱动。这个顺序很重要,它能把问题分成“硬件链路问题”和“驱动匹配问题”两层。如果用户态i2cget都读不到WHO_AM_I,就不用去查驱动,老老实实回去查接线和上拉电阻。
5. 移植完成之后:一点心得与可以继续折腾的方向
5.1 这次移植沉淀下来几条不好听但很实在的经验
移植工作做得越多,越觉得“移植”这个动作的本质不是写代码,而是做一次对接口差异和硬件行为的梳理。你要搞清楚目标平台的设备树怎么写,I2C控制器怎么枚举,GPIO中断怎么映射,然后把这些信息汇总成内核驱动要求的样子。
遇到问题先看dmesg,再看数据手册,最后查源码,这个顺序能少走很多弯路。dmesg会直接告诉你驱动卡在哪一步,比如是probe阶段就失败,还是读取寄存器时超时,指向性完全不同。而数据手册的作用,是在你怀疑“内核驱动是不是写错了”的时候,能靠寄存器定义来判断真正的问题在哪。大多数情况下,内核主线驱动不会错,错的是平台配置和硬件连接。
给后来者最实在的建议是:记录要同步做。每次改动设备树或配置后,把dmesg的关键输出、I2C扫描结果、实测数据都存下来。上次排一个GPIO复用问题,就是翻出了两小时前的配置记录,才意识到自己改过pinctrl,省下了重复排查的时间。
5.2 驱动跑通之后,还有几件值得折腾的事
如果你移植的驱动已经稳定读取数据,下一步有几个方向很值得尝试。
第一个是配置数据就绪中断。当前用的轮询方式虽然简单,但CPU占用高、实时性差。把INT引脚接到指定GPIO,通过IIO触发系统实现中断驱动采样,可以让CPU在无数据时进入休眠,数据来了再唤醒。MPU6050驱动本身支持这功能,关键是把设备树里的interrupts配置准确。
第二个是启用FIFO。MPU6050内部有1024字节FIFO,可以缓存多组采样数据,适合低功耗场景,MCU不需要每次采样都唤醒总线。驱动里对应的就是inv_mpu6050_buffer相关代码,通过IIO buffer接口可以读到FIFO里的数据。
第三个是玩DMP。MPU6050内置数字运动处理器,可以输出四元数,把姿态解算的负载从CPU搬到传感器内部。不过主线驱动的DMP支持不算完整,想用DMP的话需要结合厂商的相关代码或研究社区补丁。如果你只是偶尔读一下原始数据,DMP可有可无,但如果要做惯性导航或体感交互,它就是真正的加分项。
5.3 最后分享一个调试小习惯
调试I2C外设时,我习惯准备一根杜邦线和一个逻辑分析仪。逻辑分析仪不需要多贵,便宜的那种能看到I2C波形就够用。当软件层层配置都查不出问题的时候,用逻辑分析仪抓一下SDA和SCL波形,能瞬间看出是总线根本没通信、ACK位超时,还是地址错误。很多“玄学”问题,抓完波形就变成“显然”问题了。
这次移植虽然只是龙芯平台上一个小传感器的适配,但整个过程把I2C控制器、设备树、IIO驱动、GPIO中断、固件更新这些嵌入式开发的常见内容都串了一遍。对组里的新人来说,这比单纯看文档学内核驱动有意思得多,也扎实得多。如果你手头正好有龙芯板子和一颗MPU6050,按照上面的流程走一遍,大概率也能在半天内跑通,祝顺利。