十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

龙芯平台MPU6050驱动移植实战:从设备树到IIO数据读取

龙芯平台MPU6050驱动移植实战:从设备树到IIO数据读取 1. 先说结论这次“MPU驱动移植”到底移植了什么接到“走马观碑组”这个项目时我第一反应是又要从零写一个MPU6050的字符设备驱动了说实话团队成员一开始也都这么以为甚至有人已经在查MPU6050的寄存器手册准备把FIFO、DMP、中断这些全自己撸一遍。但把需求往细里一拆发现根本不是那么回事。我们手里的硬件是一块基于龙芯k平台的嵌入式开发板要接一颗InvenSense MPU6050六轴惯性传感器用在姿态采集和运动监测试验上。系统是Linux内核版本基于龙芯SDK提供的内核源码。所谓的“移植”真正的边界不是“写一个驱动”而是让Linux内核里已有的MPU6050驱动在龙芯k这个具体平台上被正确识别、编译、加载并稳定出数。这个判断很关键。因为一旦把目标定义为“移植”而不是“从零开发”整个技术路线的复杂度就完全不一样了。Linux内核从很早开始就已经把MPU6050驱动纳入主线代码在drivers/iio/imu/inv_mpu6050/目录下维护得很完整支持I2C和SPI两种总线接口。所以我们的任务其实可以拆成四件事在内核源码里确认当前SDK版本有没有包含或可以配置出该驱动在设备树里把MPU6050的I2C地址、中断引脚正确描述出来把驱动编译成内核模块或编入内核镜像并在目标板上加载成功通过IIO子系统的sysfs接口读取六轴原始数据验证数据是否正常。这篇文章就是把这四步完整记下来顺带把我们踩过的坑、做的取舍、以及最后沉淀下来的排查方法一并说清楚。如果你手头是龙芯、飞腾或其他国产嵌入式平台要做传感器驱动适配的这篇东西应该能帮你少走一大段弯路。2. 移植路线选择IIO框架、i2c-dev直读、自写驱动的对比与取舍2.1 为什么不是自己写一个独立驱动先说结论自己从寄存器层面写一个MPU6050驱动在商业项目里是万不得已才做的事除非你用的是RTOS裸机环境或者内核版本老到根本没有现成驱动可用。MPU6050本身并不复杂I2C地址默认0x68或0x69初始化无非是配置电源管理寄存器、设置量程、配置采样率、读FIFO或直接读寄存器。这些工作大半天就能写完。但问题在于Linux下驱动不是“能不能读到数据”这么简单你还要考虑中断处理、睡眠唤醒管理、与用户空间的接口规范、热插拔总线的设备模型对接等。这些基础设施如果自己造成本远高于驱动本身。我们当时评估过自写方案估算工作量至少需要一到两周还要写用户态的接口库而且后续维护全落到自己头上。相比之下主线内核的inv_mpu6050驱动已经支持加速度计、陀螺仪原始数据读取温度传感器读取采样率、量程、低通滤波配置FIFO读取I2C辅助总线可用于扩展磁力计中断支持这些能力通过标准IIO框架暴露给用户空间意味着我们之后要写姿态融合算法时可以直接读sysfs节点或者用libiio不用再跟寄存器打交道。2.2 那能不能直接打开i2c-dev用ioctl直读很多做快速验证的朋友第一反应是我在用户态开/dev/i2c-0用ioctl发I2C读写命令读寄存器不就行了确实行而且我们前期验证硬件连线时就是这么干的。但我强烈不建议把它作为“移植”交付方案原因有三一是不符合Linux设备模型应用层代码得死死绑定I2C设备路径换一颗挂在其他总线上的传感器就得改代码二是中断没法优雅地处理MPU6050的数据就绪中断、DMP中断这类功能在用户态裸读下很难利用起来三是多个进程并发访问同一个I2C从设备时会打架你还要自己在应用层做锁。所以我们最终选择了第三条路用内核主线现有的IIO驱动框架。2.3 确认内核里到底有没有这个驱动拿到SDK内核源码后第一步不是急着配置而是确认代码是否存在。我在内核源码根目录执行find drivers/iio/imu -maxdepth 2 -type f | head -50正常情况下可以看到drivers/iio/imu/inv_mpu6050/目录里面有inv_mpu6050_core.c、inv_mpu6050_i2c.c、inv_mpu6050_spi.c等文件。如果这个目录存在事情就成功了一半。判断哪个平台总线驱动会被编译关键是看Kconfig文件。我翻了一下里面有两个关键配置项CONFIG_INV_MPU6050_IIO CONFIG_INV_MPU6050_I2CINV_MPU6050_IIO是核心驱动INV_MPU6050_I2C是I2C总线接入层。我们要把这两个配置项都打开驱动才会被编出来。这里很多人会漏只开I2C不开IIO核心编出来的模块文件是空的反过来也是。如果你拿到的SDK内核比较老连inv_mpu6050目录都没有也没关系可以从主线内核drivers/iio/imu/inv_mpu6050/整个目录拷贝过来再补上Kconfig和Makefile的对应条目就能用。这个操作我们后来在另一个老版本内核上也验证过风险很低因为该驱动的依赖项只有I2C、IIO几个核心选项。三种路线的对比我当时整理过一张表给项目组评审用方案开发量维护成本功能完整性是否推荐i2c-dev用户态直读极低中仅原始寄存器读写仅用于验证硬件自写字符设备驱动高高按需定制但要做大量基础工作不推荐内核IIO现成驱动低低完整支持六轴温度FIFO中断推荐现在回看这个选择是整个项目最正确的一步。后续所有工作都是在给现有驱动“铺路”而不是在修驱动本身。3. 设备树对接与I2C探测让龙芯k先找到这颗MPU60503.1 先确认硬件挂在哪条I2C总线上设备树这一步是多数移植项目翻车最多的地方但只要你按顺序做其实并不难。在写设备树之前先做两件事一是查板级原理图确认MPU6050的SCL/SDA接在龙芯k的哪个I2C控制器上二是用示波器或万用表确认传感器供电。我们板卡上MPU6050挂在I2C0板载3.3V供电AD0引脚直接接地所以I2C地址是0x68。如果你板子的AD0接了上拉电阻地址就是0x69后面所有设备树和调试命令都要跟着改。你可以在目标板上用i2cdetect -l看看系统里识别出了几条I2C总线i2cdetect -l正常情况下会输出类似i2c-0 i2c DesignWare I2C adapter I2C adapter i2c-1 i2c DesignWare I2C adapter I2C adapter这个输出能帮你确认控制器驱动已经加载。如果i2cdetect -l本身不存在说明系统没装i2c-tools用包管理装一下即可。3.2 在设备树里添加MPU6050节点龙芯k平台的内核设备树路径随内核版本和SDK不同会有差异常见位置在arch/loongarch/boot/dts/loongson/下老一些的版本在arch/mips/boot/dts/loongson/下。找到板级dts或dtsi文件后在对应的I2C控制器节点下添加子节点。我加的节点大致是这样i2c0 { status okay; clock-frequency 400000; mpu605068 { compatible invensense,mpu6050; reg 0x68; interrupt-parent gpio; interrupts 12 IRQ_TYPE_LEVEL_HIGH; }; };这里有两个点需要特别说明。第一个是compatible。我看到很多教程喜欢写invensense,mpu6050但不同内核版本里驱动匹配的字符串可能会带后缀比如invensense,mpu6050是核心匹配真实I2C驱动还支持invensense,mpu6050、invensense,mpu6500、invensense,mpu9250这类兼容。建议在内核源码里搜一下of_device_id确认grep -rn of_match_table drivers/iio/imu/inv_mpu6050/第二个是interrupt-parent和reg。reg是I2C从机地址必须和AD0引脚电平对应。中断这一行我当时是照抄示例填的结果成了整个项目里最大的坑后面单开一节讲。3.3 不用重编内核也能验证设备树的方法设备树不是写了就生效的。龙芯k的引导程序会加载DTB如果你改了dts要么重新编译内核生成新的dtb要么把设备树编成内核模块再用device tree overlay加载。对开发阶段来说重新编译dtb再替换是最直接的路径。一个很实用的技巧先不改dts用i2cdetect扫一遍I2C0总线确认传感器在硬件层面能被发现。i2cdetect -y -r 0如果地址0x68上有设备输出里会显示68。这一步能提前把硬件连线问题隔离掉避免你辛辛苦苦编完内核才发现传感器根本没上电。如果扫不到按顺序排查用万用表确认传感器VDD和GND确认SDA/SCL是否接反确认I2C总线上拉电阻是否存在很多核心板为了省成本没放上拉导致通信不稳定换一个I2C地址再扫一次可能是AD0电平不对。我们这个项目在扫描阶段一切正常0x68稳稳当当出现所以当时还挺乐观觉得后面会很顺。结果编译加载阶段给了我们一个下马威。3.4 重编设备树并确认加载后的节点确认dts修改无误后我重新编了内核和设备树文件。这个命令跟龙芯处理器架构紧密相关我们用的交叉编译前缀是loongarch64-linux-gnu-make ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- loongson_k_defconfig make ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- dtbs编出来的dtb文件替换到引导分区后重启在/sys/firmware/devicetree/base/下应能看到对应节点ls /sys/firmware/devicetree/base/ | grep i2c以及ls /sys/firmware/devicetree/base/soc/i2cxxxxx/能看到mpu605068子目录说明设备树节点已经生效。这一步过了驱动匹配才有基础。4. 内核配置、交叉编译与模块上板异常报错排查实录4.1 打开内核配置的完整流程在龙芯k平台的Linux内核里打开MPU6050驱动需要调整几个配置项。我用make menuconfig或直接改.config都可以重点是把下面几个符号打开CONFIG_IIOy CONFIG_IIO_BUFFERy CONFIG_IIO_TRIGGERED_BUFFERy CONFIG_INV_MPU6050_IIOm CONFIG_INV_MPU6050_I2Cm注意几个细节CONFIG_IIO最好直接编译进内核不要设成模块因为IIO子系统是其他传感器驱动的基础设施如果它本身是模块驱动加载顺序会很麻烦。CONFIG_INV_MPU6050_IIO和CONFIG_INV_MPU6050_I2C我们设成m也就是编成.ko内核模块。这样开发阶段方便单独替换驱动不用每次改驱动都重刷整个内核。如果内核版本较老还要确认CONFIG_I2C和CONFIG_GPIO_CDEV这类基础选项是开着的。龙芯SDK默认一般都开但保险起见查一下。在源码目录执行make ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- modules_prepare然后编译模块make ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- Mdrivers/iio/imu/inv_mpu6050 modules编译完成后在drivers/iio/imu/inv_mpu6050/下会生成inv-mpu6050-core.ko和inv-mpu6050-i2c.ko两个文件。注意模块名是带连字符的跟Makefile里的目标名不一定完全一样。4.2 模块加载时最容易出现的“背调”错误把两个.ko拷贝到目标板上执行modprobe前先手动跑一次insmod inv-mpu6050-core.ko insmod inv-mpu6050-i2c.ko我们的第一个报错就是inv_mpu6050: version magic 5.10.27 loongarch should be 5.10.84 loongarch原因很直接模块是用当前源码树编译的但板子上运行的内核版本和源码树版本不一致。龙芯SDK经常出现这种情况厂商把内核源码打了自己的补丁版本号却和实际烧进去的镜像不一致。排查方法简单粗暴先看目标板上到底在跑哪个版本uname -r然后回源码目录make kernelrelease对比。不一致时按源码树的版本重新编译整个内核镜像并烧写或者反过来用正在运行的内核对应的config重新编译模块源码。这里要特别提醒不要用修改版本号取巧比如手动改EXTRAVERSION让uname输出一致内核模块的 vermagic 还包含编译器、内核配置和大版本信息改了也会在加载时报别的错。最规范的做法是让运行镜像和源码树完全对应。4.3 驱动匹配失败的经典场景I2C设备树节点没被识别把模块加载成功后我们以为dmesg会立刻刷出一长串初始化日志结果只看到一行inv_mpu6050 i2c-0:0: probe failed with error -ENXIO-ENXIO通常意味着I2C传输失败驱动发WHO_AM_I读命令时根本没收到应答。这时候如果只盯着驱动代码看很容易绕进死胡同。我们的排查顺序是这样的先跑i2cdetect -y -r 0确认从设备还在再用i2cget手动读寄存器i2cget -y 0 0x68 0x75正常返回0x68或0x70MPU6050对WHO_AM_I的应答值为0x68或0x70取决于寄存器映射版本。如果i2cget能读出来说明硬件和I2C总线完全正常问题出在设备树与驱动匹配之间。后来发现原因是设备树节点的compatible和驱动里的of_device_id表没对齐。SDK内核里的驱动被厂商改过compatible字符串变成了invensense,mpu6050,vendor之类带后缀的东西而我们的dts里写的是标准字符串。匹配不上驱动框架直接拒绝probe。最终我把dts里的compatible改成跟驱动源码里完全一致才恢复正常。这件事启发我设备树写完后一定要在目标板上用这个命令看最终生效值cat /sys/firmware/devicetree/base/soc/i2cxxxxx/mpu605068/compatible4.4 模块加载成功后还要确认IIO设备节点出现驱动probe成功后dmesg会输出类似inv_mpu6050 i2c-0:0: Detected MPU6050这时检查/sys/bus/iio/devices/能看到iio:device0目录。进去看一眼ls /sys/bus/iio/devices/iio:device0/里面应该有一堆in_accel_*、in_anglvel_*、in_temp_*开头的属性文件。到这里移植工作已经完成了80%。剩下20%是验证数据是不是真能反映芯片运动。5. 运动数据校验从原始寄存器读到在sysfs里看六轴变化5.1 原始读数和实际物理量的换算关系IIO框架暴露给用户空间的是原始寄存器值不是标准单位。MPU6050的加速度计默认量程是±2g陀螺仪默认量程是±250°/s需要在应用层做换算。换算系数分别如下加速度灵敏度是16384 LSB/g所以物理加速度 原始值 / 16384 * 9.8 m/s²陀螺仪灵敏度是131 LSB/(°/s)所以角速度 原始值 / 131 °/s温度推荐公式是 T 原始值 / 340 36.53 °Ccat /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_temp_raw5.2 静态验证看重力向量和零偏把开发板平放在桌面上理论上Z轴加速度应该接近1g也就是原始值约16384X和Y轴接近0。我们实测出的值是in_accel_x_raw: -80 in_accel_y_raw: 160 in_accel_z_raw: 16310这个结果是合理的偏差来自芯片安装角度和温度漂移。如果Z轴实测值偏离16384太多比如变成8000甚至负的大概率是量程寄存器配置没生效或者芯片安装方向理解错了。陀螺仪在静止状态下三轴原始值应该非常接近0。我们这边测出来in_anglvel_x_raw: -3 in_anglvel_y_raw: 11 in_anglvel_z_raw: 2这个零偏水平对消费级IMU来说算正常拿来做姿态解算前需要做零偏校准把静止时的均值作为补偿量减掉。5.3 动态验证用手翻转板子观察数据联动静态验证只能说明寄存器能读不能说明陀螺仪真的在感知运动。我们把板子绕X轴快速翻转90度在翻转瞬间抓取数据watch -n 0.1 cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw能看到X轴瞬时读数跳到几千翻转结束后归零。同样地拿起板子左右平移加速度计的X/Y轴会有明显变化。这一步很重要能确认IMU的六个轴都活着排除假焊或引脚接触不良导致某一路无响应的情况。如果你想在用户态持续读数据可以用iio_info这个工具或者直接用Python的pylibiio库。简单验证阶段我更喜欢直接用shell脚本循环读毕竟不依赖额外编译环境while true; do ax$(cat /sys/bus/iio/devices/iio:device0/in_accel_x_raw) az$(cat /sys/bus/iio/devices/iio:device0/in_accel_z_raw) gx$(cat /sys/bus/iio/devices/iio:device0/in_anglvel_x_raw) echo $(date %H:%M:%S) ax$ax az$az gx$gx sleep 0.1 done5.4 采样率和量程配置MPU6050驱动默认配置不一定满足所有应用场景。我们做运动监测时发现默认采样率太低需要调高。IIO框架下可以直接用sysfs接口修改cat /sys/bus/iio/devices/iio:device0/in_accel_sampling_frequency_available echo 1000 /sys/bus/iio/devices/iio:device0/in_accel_sampling_frequency量程同理cat /sys/bus/iio/devices/iio:device0/in_accel_scale_available echo 0.000598550415 /sys/bus/iio/devices/iio:device0/in_accel_scale这里有个容易忽略的点加速度计量程变化会影响in_accel_scale的值而用户在应用层读取原始值再手动换算时必须同步使用新的scale系数。IIO框架的in_accel_scale属性就是为了解决这个一致性问题的推荐读到的物理值直接用原始值 * scale而不是自己写死灵敏度。6. 复盘中的三个硬坑中断映射、内核版本和总线电气特性6.1 中断引脚映射设备树里看似“无害”的一行卡了我们两天文章前面我提到dts里加了这行interrupts 12 IRQ_TYPE_LEVEL_HIGH;当时以为照着芯片手册写个GPIO号就行结果probe日志反复出现inv_mpu6050 i2c-0:0: IRQ configuration failed -EINVAL问题出在龙芯k的GPIO中断控制器跟ARM平台的GPIO编号体系完全不同。ARM平台上设备树里可以直接写interrupts gpio 12 IRQ_TYPE_LEVEL_HIGH来表示GPIO控制器下的第12号引脚但龙芯平台需要先弄清自己的GPIO控制器映射到了哪个中断号还要确认对应的pinctrl驱动有没有初始化。我不建议你在不熟悉平台中断路由的情况下照抄ARM设备树的写法。稳妥的排查路径是先去掉dts里的interrupt-parent和interrupts两行让驱动以轮询方式工作确认六轴数据稳定后再看数据手册查具体GPIO中断号用内核的GPIO中断调试接口逐步验证引脚能不能产生中断。对我们这个项目来说最终不需要中断也能满足需求因为采样率要求不高轮询模式就够用了。所以我把中断节点从设备树里移除驱动反而更稳定。如果你的应用必须用中断建议单独开一个星期专门搞GPIO路由验证别把它跟MPU6050驱动移植混在一起并行排查否则会互相干扰。6.2 内核版本不一致导致的模块加载失败前面提到的version magic报错看似是个小问题实际上暴露了龙芯SDK一个常见现象源码树和预编译镜像版本不一致。这里我把排查步骤再完整列一遍方便你直接照着做。在源码目录执行make ARCHloongarch CROSS_COMPILEloongarch64-linux-gnu- kernelrelease在目标板执行uname -r如果两个输出不一致不要急着改源码版本号而是先确认板子上的内核config是否跟源码目录里的config一致zcat /proc/config.gz | grep CONFIG_LOCALVERSION如果板子系统里没开CONFIG_IKCONFIG看不到config就把源码目录的.config拷到目标板对比scripts/extract-ikconfig的输出。最理想的解决方案是用SDK源码目录重新编译完整内核镜像烧写进板子保证运行内核和源码树完全对应。这样后面每编一个模块都不会再碰到魔数不匹配问题。6.3 I2C总线电气特性400kHz不稳定降到100kHz立刻稳定在数据验证阶段我们遇到过一个诡异现象刚开机时数据正常运行十几分钟后偶尔读到-121-EREMOTEIO或-6-ENXIO。一开始怀疑是芯片过热或驱动问题排查半天后发现问题出在I2C总线的上拉强度和时钟频率组合上。MPU6050硬件上虽然没有外接上拉电阻但龙芯k核心板内部I2C控制器的上拉电阻太弱400kHz通信时信号沿不够陡抗干扰能力差稍微有点线缆长度或电源波动就开始出错。我们的修复方法是把设备树里的clock-frequency从400000改成100000i2c0 { status okay; clock-frequency 100000; };改完后连续跑了72小时没再丢过数。对MPU6050这种采样率上限1kHz的传感器来说I2C 100kHz完全够用没必要追求高速。如果你板子上有条件建议在SDA和SCL线上各加一个2.2kΩ到4.7kΩ的上拉电阻这是从硬件层面根治I2C不稳定问题的办法。没有条件改硬件时降低总线时钟是最快最有效的软件规避手段。6.4 其他辅助排查工具模块加载后如果怀疑驱动内部有计算问题可以打开内核动态调试echo file drivers/iio/imu/inv_mpu6050/* p /sys/kernel/debug/dynamic_debug/control然后再看dmesg驱动的函数级日志会全部打出来。这个手段在Probe阶段尤其有用能精确看到驱动卡在哪个函数里。推荐一套检查顺序写入你的项目检查单i2cdetect -l确认I2C控制器存在i2cdetect -y -r 0确认0x68设备在线i2cget -y 0 0x68 0x75确认能读到WHO_AM_Idmesg | grep inv看驱动日志ls /sys/bus/iio/devices/确认IIO设备节点静态读三轴和温度确认数值在合理范围动态翻转板子确认数据联动。只要按这个顺序走基本能把硬件、设备树、驱动、内核配置四个层面的问题完全隔离。这次MPU6050驱动移植从开始到稳定出数前后用了大约四天。真正改驱动代码的时间几乎为零绝大部分时间都耗在设备树匹配和平台差异上。我个人最大的体会是国产嵌入式平台的寄存器手册和GPIO中断模型往往不按主流开发板的套路来设备树中ARM平台的写法不能直接生搬硬套。拿到一块新板子先把I2C器件扫描通、把数据读出来再回头搞中断和高级功能这个顺序能省掉无数头大的排查时间。如果你也在做类似的传感器驱动适配建议保存一下第5节的检查清单按部就班来不要把时间浪费在怀疑驱动本身。龙芯k平台加MPU6050这个组合只要把设备树和内核版本这两关过了后面就是一片坦途。
返回列表