MPU6050驱动移植龙芯K平台:I2C设备树与IIO框架踩坑全记录

发布时间:2026/9/8 4:51:32
MPU6050驱动移植龙芯K平台:I2C设备树与IIO框架踩坑全记录 “走马观碑组”这个名字是组长在一次季度总结会上起的意思是我们看问题要又快又准像古代那些扫一眼碑文就能背下来的神人。结果这次接到的任务——把MPU6050六轴传感器驱动从老平台移植到龙芯K平台的Linux内核里——恰恰把大家按在地上磨了整整三周。如果你也有过“驱动能编译但硬件不认账”的经历这篇复盘应该对你有用。实际工作不复杂但很碎要先搞清楚龙芯K平台的I2C控制器在设备树里怎么描述再看内核里现有的inv_mpu6050驱动能不能直接匹配最后解决中断、时钟、采样率和掉电唤醒一堆问题。后面我把完整链路和踩坑过程都写出来包括设备树怎么写、内核选项怎么开以及遇到i2cdetect扫不到设备时到底应该先查哪儿。1. 从“复制粘贴”到“跑通验收”——这次MPU驱动移植到底难在哪1.1 任务背景为什么要把MPU6050驱动挪到龙芯K上我们组一直做一款小型运动监测模块原来主控用的是ARM Cortex-A7姿态数据和云台控制都靠MPU6050。后来产品做平台切换核心板换成龙芯K系列要求传感器和外设尽量不动。理论上MPU6050走I2C任何CPU只要有时序都能读但真实情况远非如此内核里没有现成设备树节点、I2C控制器寄存器配置不一样、中断号和旧平台对不上原来的驱动代码里还掺了不少平台相关的头文件。这次移植的目标很明确让MPU6050在龙芯K平台上稳定输出三轴加速度和三轴角速度。不是说能读出数据就算完而是要经得起产品级的长时间运行。所以项目刚开始我就跟组里打了个预防针——这不是一个“今天改改明天就跑通”的活而是要把从设备树到I2C核心再到IIO子系统整条链路都走一遍。1.2 移植前的技术摸底三件事必须提前做开始动手前我带着组员做了三件事这三件事后来被证明帮我们省了至少两天debug时间。第一查清楚龙芯K平台有几个I2C控制器各自在设备树里叫什么名字物理引脚有没有被复用成GPIO。龙芯的SoC引脚复用很强一个引脚既可能是I2C的SCL也可能是普通GPIOBSP默认配置不一定是你要的样子。第二确认BSP内核版本。我们手上是Linux 5.10里面已经有IIO框架下的inv_mpu6050驱动理论上可以少写很多代码。第三用示波器和万用表把MPU6050的I2C引脚、中断脚都量一遍确认没有接反、没有虚焊。技术摸底的意义在于把“移植驱动”拆成“改设备树调内核配置验证中断路径”三个独立小任务。每个小任务都可以单独验证任何一个失败都能精确定位而不是等到最后烧完系统才发现一堆问题叠在一起。1.3 我们定义的验收标准别让“驱动能编译”糊弄过去驱动移植最容易出现的一种情况是模块编译进了内核日志里也打印了probe成功大家就以为搞定了。但实际一读数据不是全0就是乱跳。所以我在本项目里定了一个四层验收标准防止“假成功”。上电后i2cdetect能扫到0x68设备地址且地址稳定。驱动probe成功/sys/bus/iio/devices/iio:device0节点出现能读到加速度和角速度数据。连续跑24小时不能出现FIFO溢出和中断丢失。同样一套设备和设备树换到相同型号的另外两块板子上仍能正常工作。这个标准看着简单但第四条就替我们挡下了不少坑。因为有些板子的接线或者焊接问题会导致代码在A板上能跑B板上就挂如果只拿一块板子验收很容易漏掉硬件一致性问题。2. 先把地基打对龙芯K平台的I2C与设备树配置2.1 通信链路从CPU寄存器到传感器寄存器的一整条路MPU6050是一个六轴IMU内部包含三轴加速度计和三轴陀螺仪通过I2C从机接口和外部通信从机地址默认是0x68AD0接地或0x69AD0接高。龙芯K平台片内集成多个I2C控制器对我们来说CPU侧的“主设备”就是一系列寄存器加中断控制器。驱动移植的本质是让Linux I2C核心能找到这条总线让inv_mpu6050驱动能找到挂在总线上的从设备然后通过标准的I2C读写接口去操作传感器寄存器。说起来像流水线但每个环节都有各自的“脾气”I2C控制器要有时钟、引脚要复用对、设备树要描述对、驱动要和设备树匹配上。任何一环脱节数据就到不了用户态。2.2 设备树节点一个字节都不能错在龙芯K的BSP里I2C控制器已经由芯片厂商描述好了通常像这样i2c2 { status okay; clock-frequency 100000; pinctrl-names default; pinctrl-0 i2c2_pins; };而MPU6050需要在这个父节点下增加子节点i2c2 { #address-cells 1; #size-cells 0; mpu605068 { compatible invensense,mpu6050; reg 0x68; interrupt-parent gpio; interrupts 19 2; }; };很多新手在这里直接复制示例但有三个细节我必须强调reg地址必须是7位I2C地址写成0xD0之类的是错的。I2C协议里8位地址的最后一位是读写位设备树里填的是高7位。interrupts里的第二个数字是触发类型2表示下降沿触发8表示低电平触发。这个必须跟硬件实际接法一致不同触发方式对中断丢失的影响非常大后面踩坑章节还会细说。compatible字符串必须跟驱动里的of_match_table完全一致多一个空格、少一个字母都不行。字符匹配失败驱动不会进入probe而且内核不会报错只会安静地忽略。我们项目里MPU6050的中断脚接在GPIO 19硬件设计是低电平有效所以设备树最终写的是interrupts 19 8。这个“8”是很多教程里不太会讲清楚的坑很多人照抄19 2也能跑但一旦中断丢起来你就知道差别了。2.3 内核配置该开的选项一个都不能少我比较倾向于把驱动编成模块调试期迭代快改一行代码重新编译模块比整个内核重编省太多时间。内核配置需要确保下面这几个选项是开着的CONFIG_I2CyCONFIG_I2C_CHARDEVy方便用i2cdetect/i2cget调试CONFIG_IIOyCONFIG_IIO_BUFFERyCONFIG_INV_MPU6050_I2Cm这里特别提醒CONFIG_IIO_BUFFER经常被遗漏。没有它即使probe成功你也不会看到可用的buffered数据接口iio_info看不到采样数据上层应用就更测不出来了。而且这个选项默认可能不是开启的必须手动进menuconfig确认。2.4 框架选型为什么坚持用IIO而不是老式input子系统老式Linux驱动里也有用input子系统上报事件的写法上报事件类型是EV_ABS配合input_abs_setup把六轴数据丢给用户态。这种方式不是不行但MPU6050本质是传感器IIOIndustrial I/O框架才是正道缓冲、触发、采样频率、温度补偿、掉电唤醒都是“一等公民”上层应用也能通过统一的/sys/bus/iio/接口访问。另一个重要原因是内核主线代码的可持续性。5.10内核里inv_mpu6050驱动已经比较成熟我们决定不用厂商提供的旧补丁直接用主线代码。虽然主线驱动在个别龙芯平台上可能会有I2C时序兼容性问题但至少保证后续内核升级时我们能跟上社区节奏。项目遇到问题也能拿主线代码去和BSP对比更容易定位是哪一层动了手脚。3. 逐行走通移植过程从内核源码到模块加载3.1 准备交叉编译环境与内核源码我们手上有的是龙芯官方BSP源码包里面自带交叉编译器。以2K1000样片为例目标平台是MIPS64小端交叉编译前缀是mips64el-linux-gnuabi64-。设置环境变量的方式如下export ARCHmips export CROSS_COMPILEmips64el-linux-gnuabi64-然后先生成默认配置再打开我们需要的内核选项make loongson2k_defconfig make menuconfig在menuconfig里逐项找到Device Drivers - I2C supportDevice Drivers - Industrial I/O support - Inertial Measurement Units - Invensense MPU6050 devices把MPU6050相关项勾选为M模块I2C保持打开/dev/i2c-*字符设备也建议打开。3.2 修改设备树并编译DTS龙芯BSP的dts文件通常在arch/mips/boot/dts/loongson/目录下。我们需要改的是板级dts文件比如loongson2k1000.dtsi和对应的开发板dts。在i2c2节点里加上MPU6050子节点后执行make dtbs编译完成后在arch/mips/boot/dts/下会生成对应的dtb文件。烧写时需要连同内核镜像一起更新。这里有一个无数人踩过的坑只更新内核Image不更新dtb设备树改了等于没改因为引导程序加载的是旧dtb内核根本不认识你新加的节点。检查dtb是否生效可以反编译看一下dtc -I dtb -O dts arch/mips/boot/dts/loongson/loongson2k1000.dtb | grep -A5 mpu6050如果能看到mpu605068节点说明dtb已经带上了。3.3 编译驱动模块执行make modules后会在drivers/iio/imu/inv_mpu6050/下生成两个关键文件inv-mpu6050.ko和inv-mpu6050-i2c.ko。inv-mpu6050.ko是核心驱动负责IIO设备注册、寄存器初始化、数据读取逻辑。inv-mpu6050-i2c.ko是I2C传输适配层负责把核心驱动的读写请求转成I2C控制器的实际时序。这两个模块有依赖关系加载时必须先加载核心模块或者用modprobe让系统自动处理依赖。如果只用insmod且顺序反了会报“Unknown symbol”错误因为核心驱动里引用了I2C适配层的导出符号。模块拷贝到板子上之后先执行depmod -a然后用modprobe inv-mpu6050-i2c3.4 加载后确认probe流程加载成功后马上看内核日志dmesg | tail -30正常会看到类似“iio device registered”或者驱动的name提示。这时候去/sys/bus/i2c/devices/下看应该会出现2-0068这样的目录前面是总线号后面是设备地址。再进/sys/bus/iio/devices/看到iio:device0就说明设备枚举成功了。如果只看到I2C核心创建了设备但驱动没有probe先别急着怀疑驱动代码去核对compatible字符串和reg值。我把这个过程做成了一张自检表步骤操作验证命令内核源码make loongson2k_defconfiggrep CONFIG_INV_MPU6050 .config设备树增加mpu605068子节点dtc -I dtb -O dts ...编译模块make modulesls drivers/iio/imu/inv_mpu6050/*.ko加载驱动modprobe inv-mpu6050-i2cdmesg | grep -i imu设备节点确认IIO设备注册ls /sys/bus/iio/devices/4. 翻车合集传感器找不到、读出全是0xFF、probe就是不进4.1 第一批板子I2C扫描不到设备的排查链路这是最让人崩溃的一个问题板子通电后执行i2cdetect -y 1列表里完全没有0x68全是“--”。当时组里第一反应是驱动没编对但后来发现驱动根本没机会参与因为I2C设备压根没出现在总线上。排查链路要一步一步来。第一步先确认I2C总线号。龙芯2K1000有多个I2C控制器设备树里的i2c2在Linux里可能被注册成i2c-1或i2c-2不同BSP版本编号策略还不一样。用i2cdetect -l列出所有总线再找到对应设备树节点的那条。我们板子上i2c2对应的是i2c-1但很多教程默认用i2c-2白白扫了半天。第二步检查硬件连接。用示波器量MPU6050的SCL和SDA如果SCL有波形但SDA一直被拉低十有八九是SDA上拉电阻没焊或者焊错了位置。如果两根线都没有波形检查引脚复用也许它被BSP默认配成了GPIO。第三步确认从机地址。AD0脚接地是0x68接VCC是0x69。我们有一块板子的AD0丝印标反实际是0x69所以一直扫0x68当然没有。还有一块板子是AD0悬空地址在上下电瞬间不稳定有时扫到0x68有时扫不到。在我们这个案例里第一批三块板子中一块是AD0引脚问题其余两块是总线号没对上。最终通过逐个对照原理图和i2cdetect输出确认跟驱动代码半毛钱关系都没有。4.2 读到全0xFF或全0x00硬件电气问题还是软件初始化顺序i2cdetect能扫到地址心里踏实一点但用i2cget -y 1 0x68 0x75读取WHO_AM_I寄存器时返回0xFF。这说明I2C从机没有正确响应。全0xFF通常意味着从机没拉低ACK可能是如下几个原因SDA/SCL上拉电阻缺失或阻值太大信号上升沿太慢。MPU6050的VDDIO参考电压和主控电平不匹配。我们遇到过一个真实案例VDDIO接的是5V而龙芯I2C控制器是3.3V电平两边信号识别混乱。I2C时钟频率过高。如果BSP默认配了400kHz但板子线缆比较长可以先降回100kHz试一次。全0x00的情况通常是SCL一直为低或者是传感器处于sleep状态且读写时序异常。确认初始化顺序很重要上电后先写PWR_MGMT_1地址0x6B把SLEEP位置0然后延时50ms再读WHO_AM_I。如果一上电就直接读MPU6050还在复位过程中自然读不出正确值。4.3 compatible对不上导致probe函数不执行这个问题最迷惑。设备树节点加了i2cdetect也能看到设备modprobe也成功了但dmesg里就是没有probe日志。更诡异的是/sys/bus/i2c/devices/下已经出现了2-0068目录说明I2C核心把设备枚举出来了但驱动没有绑定上。排查方法很简单cat /sys/bus/i2c/devices/2-0068/name看看设备名称是不是符合预期。如果设备目录名和驱动匹配表里的compatible不一致绑定就会失败。我们曾经把设备树的compatible写成invensense,mpu6050但某个分支的驱动里of_device_id数组只写了invensense,mpu6050-byte-swap这类变体导致一直绑定不上。还有一种可能是模块依赖没加载完整。inv-mpu6050.ko没先加载inv-mpu6050-i2c.ko虽然加载了但核心驱动不存在probe会被deferred。dmesg里会看到“probe deferred”字样有时候会被日志刷掉看漏。用这个命令专门查dmesg | grep -i defer4.4 FIFO溢出导致数据卡死中断和轮询的取舍probe成功iio_info也能看到设备但连续读取一段时间后某一个轴的角速度数据始终不变像被冻住一样。实际上这是FIFO溢出问题。inv_mpu6050驱动默认用FIFO做数据缓冲FIFO满之后如果没有及时读取清空新数据写不进去旧数据一直顶在头上用户态读到的自然永远是旧值。dmesg里会周期性出现“FIFO overflow”错误。我们把中断触发方式从下降沿改成低电平触发后这个问题得到明显缓解。原因是边沿触发如果正好在CPU屏蔽中断期间到来这个边沿就永远丢了驱动永远不知道有新数据FIFO自然越积越满。而低电平触发不同只要电平一直保持低等CPU打开中断后依然能识别到pending事件不会漏掉。如果板子用的是轮询模式要额外确认CONFIG_IIO_TRIGGERED_BUFFER已经打开否则buffer提交不上数据也是死的。5. 驱动活了距离产品化还差这几步5.1 先用命令行验证六轴数据合理性probe成功后不要急着写应用程序先用命令行快速验证数据。静止状态下依次读取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如果量程是±2g静止时Z轴读数应该接近163841g对应的LSBX/Y轴接近0。陀螺仪三个轴应该接近0偏差在几十LSB以内正常。如果Z轴读数明显不对先查PWR_MGMT_1寄存器确认SLEEP位是0然后确认量程寄存器配置是否和预期一致。用iio_info可以一次性看到所有通道和采样频率比一个个cat方便得多。这一步数据对了再往上写应用才是水到渠成的事。5.2 采样率、FIFO阈值与CPU占用率的平衡MPU6050的加速度计和陀螺仪最高支持4kHz采样但I2C带宽有限中断频率太高CPU会一直在处理中断。我们最终把加速度计和陀螺仪输出速率都配置在100HzFIFO采样率也是100Hz实测CPU占用率不到3%。在设备树里可以这样配置但不同内核版本支持的属性名有差异mpu605068 { compatible invensense,mpu6050; reg 0x68; interrupt-parent gpio; interrupts 19 8; inven,accel_freq 100; inven,gyro_freq 100; };修改属性前一定要去drivers/iio/imu/inv_mpu6050/inv_mpu6050_core.c里查一下驱动对设备树属性的解析代码因为有些版本用的是invensense,accel_freq多一个字母不影响编译但会被驱动静默忽略。5.3 低功耗设计把MPU6050睡眠和唤醒做好如果产品是电池供电MPU6050默认上电后处于sleep状态我们唤醒后如果长时间不采集应该再次写PWR_MGMT_1寄存器进入sleep状态需要时再唤醒采集。内核里inv_mpu6050驱动提供了runtime PM支持在设备树节点里加一条wakeup-source;即可同时确保内核打开CONFIG_PMy。我们实测启用runtime PM后待机电流从约800μA降到90μA。如果板子上还有别的功耗大户这个数字仅供参考但趋势是对的。做产品的人都知道传感器本身的功耗往往不是大头但白给的功耗省一点是一点。5.4 项目收尾把踩坑沉淀成组内公共文档项目结束时我把这次踩坑整理成四页文档核心格式是“问题现象—排查步骤—根因—永久修正”。设备树修正后的版本固化到了BSP基线里而不是只存在某个人的工作目录。i2cdetect扫描命令和dmesg过滤命令也写成可执行脚本放到组内共享。后续再有人做同类型传感器迁移直接跑一遍自检脚本就能筛掉大部分硬件问题。这次移植给我最大的感受是所谓“驱动移植”真正难的不是驱动本身而是对硬件平台的掌握。龙芯K平台和ARM平台在I2C时序、中断控制器行为、BSP内核配置上都有微妙差异任何一点差异都可能让传感器数据变得不可用。但把这些差异摸清楚以后替换传感器型号、扩展到其他IMU也就是换个compatible字符串的事。

相关新闻