
拿到“龙芯k - 走马观碑组MPU驱动移植”这个项目标题时我第一反应就是这活儿听起来不大但真要跑通牵扯的链条一点都不短。所谓“MPU驱动移植”放到实际场景里基本可以锁定为在龙芯平台上把MPU6050这颗六轴惯性传感器的Linux内核驱动调通让它能稳定地输出加速度和角速度数据。这事儿对于刚接触龙芯LoongArch架构、或者准备做嵌入式外设适配的同学来说是一条非常典型的学习路径从硬件接线、设备树编写到内核配置、驱动模块编译再到用户态数据验证每一步都有坑但每一步也都能学到东西。这篇东西我打算按自己实际做过的流程来写以龙芯LS2K0500开发板为主线兼顾3A5000这类平台上的差异。目的是让你拿着这片文章不敢说闭着眼睛能复制但至少能少走一半弯路。适合谁看手上刚好有龙芯板子、想把I2C类传感器或外设驱动跑起来的人或者对Linux内核驱动移植流程感兴趣、想找一个完整案例来练手的学生和工程师。1. 先看懂这个项目在做什么1.1 项目目标和交付物拆解“走马观碑组”这个组名看着像是一个内部项目组的代号实际干的事情却很具体把MPU6050的驱动移植到龙芯平台上。MPU6050是InvenSense现在属于TDK出的六轴运动处理传感器内部集成了一颗三轴加速度计和一颗三轴陀螺仪通过I2C接口与主控通信地址默认为0x68。这颗芯片在消费电子和嵌入式领域用得非常多无人机、平衡车、机器人、姿态检测模块里都能看到它Linux内核里也早就有了成熟的上游驱动支持。所以这个项目的交付物其实包含三块第一内核里能识别并绑定这个设备的驱动也就是CONFIG_INV_MPU6050_I2C对应的那套代码源码路径在drivers/iio/imu/inv_mpu6050/第二描述硬件连接关系的设备树节点告诉内核“这个MPU6050挂在哪个I2C控制器上、中断脚接哪里、安装方向是怎样的”第三用户态能访问的数据接口也就是IIO子系统生成的/sys/bus/iio/devices/iio:device0/目录从里面读原始值、量程和采样频率。很多刚做驱动移植的人容易把目标理解成“写一个驱动文件”其实在Linux驱动模型下尤其是这种已经有上游驱动的芯片真正的难点不在写代码而在把“设备树描述”和“内核配置”这两件事做对让既有驱动能在新架构的板子上被正确加载和枚举出来。1.2 为什么选MPU6050作为移植对象我个人的看法是MPU6050几乎是做驱动移植入门最理想的一颗芯片。原因有三个第一硬件接口简单。它只需要I2C加上一根可选的INT中断脚总共四根线不像一些复杂的PCIe设备或者网卡那样需要处理中断控制器、DMA通道、电源管理等一堆东西。第二内核驱动成熟稳定。从内核3.x时代开始inv_mpu6050驱动就在持续维护到了5.x、6.x版本已经非常完善支持IIO框架、支持DMP固件加载、支持trigger缓冲。这意味着我们不需要从零造轮子只需要让这个驱动在龙芯的LoongArch架构下跑起来就行。第三验证成本低。一块板子、一个传感器模块、几根杜邦线就能完成全部验证。我在实际项目中用LS2K0500迷你开发板飞了几根线半小时内就把硬件链路搭起来了剩下的时间全在跟内核和设备树较劲。还有一点不能忽略MPU6050的量产资料、官方手册、社区帖子都非常全。一旦驱动加载出问题排查资料比某些冷门芯片多一个数量级。对于刚接触龙芯平台的人来说选它做第一个移植对象心理压力会小很多。1.3 移植思路不自己造轮子复用内核上游驱动这里要强调一个关键判断移植驱动和编写驱动是两码事。对于MPU6050几乎没有理由去从寄存器级别重新写一个字符设备驱动。你去网上搜确实能找到很多裸机或者RTOS下的MPU6050驱动代码但在Linux系统里那些代码基本用不上硬塞进去反而是灾难。正确的做法是复用内核里的inv_mpu6050驱动它已经处理好了寄存器初始化、量程设置、FIFO读取、中断处理、IIO接口注册等所有脏活累活。所以我们的移植工作重心其实是以下几步确认内核版本里带有inv_mpu6050驱动源码、使能对应的Kconfig选项、确认I2C控制器驱动可用、编写正确的设备树节点、编译内核或模块、部署并验证。这个思路同样适用于其他I2C类传感器比如BMP280、LSM6DS3等套路是一致的换了芯片也只是改一下compatible字符串和属性参数。2. 环境准备与内核源码初始化2.1 龙芯平台的交叉编译环境搭建在龙芯板子上做驱动开发有两种玩法。一种是直接在板子上装好Loongnix或LoongOS系统原生编译优点是不用管交叉编译链但缺点也很明显板子性能有限编内核时间很长。另一种是在PC上搭交叉编译环境出内核镜像和模块再拷到板子上这也是我推荐的方式。交叉编译工具链方面龙芯官方提供了loongarch64-linux-gnu-前缀的gcc工具链。如果你是x86的PC可以从龙芯开源社区或者一些第三方镜像站下载。装好之后设置一下环境变量export ARCHloongarch export CROSS_COMPILEloongarch64-linux-gnu-注意从内核5.19开始LoongArch架构的代码正式合入了上游主线所以理论上你直接拿一个6.x版本的上游内核也能编译出可用的LoongArch内核。但实际使用中开发板的板级支持代码往往只在龙芯维护的内核分支里比如LS2K0500的板级设备树、I2C控制器的pinctrl配置这些在上游主线里不一定齐全。所以强烈建议直接用龙芯开发板对应SDK里的内核源码而不是自己去上游找最新版省去一堆适配的麻烦。2.2 拿到带板级补丁的内核源码以LS2K0500为例配套的SDK里会提供完整的内核源码包。拿到源码之后第一件事不是急着改配置而是先确认两件事一是驱动源码drivers/iio/imu/inv_mpu6050/是否存在二是板级设备树文件arch/loongarch/boot/dts/loongson/或者类似路径下有没有对应的dts文件里面有没有I2C控制器的定义。在确认驱动源码存在之后先加载默认配置。不同版本的SDK可能配置项名称不一样有的是ls2k0500_defconfig有的是loongson3_defconfig根据实际SDK文档来。加载完默认配置后用menuconfig做可视化修改make loongson3_defconfig make menuconfig在菜单里依次找到Device Drivers --- * Industrial I/O support --- * InvenSense MPU6050 devices --- * InvenSense MPU6050 I2C driver这里需要注意IIO框架是必选项不能选成模块。因为MPU6050驱动依赖IIO核心的符号如果IIO本身是模块而MPU6050驱动也是模块加载顺序会变成麻烦事。在我的实践中CONFIG_IIOy、CONFIG_INV_MPU6050_I2Cm是比较稳的组合MPU6050驱动用模块方式方便单独更新和调试IIO核心编进内核避免依赖顺序问题。2.3 I2C控制器的配置确认设备树写得再漂亮如果I2C控制器驱动没使能一切都白搭。在menuconfig里还要确认Device Drivers --- * I2C support --- * I2C device interface * Synopsys DesignWare I2C adapter龙芯很多芯片的I2C控制器用的是DesignWare的IP核对应内核驱动是i2c-designware。LS2K0500内部集成的I2C控制器也是这个风格所以CONFIG_I2C_DESIGNWARE这系列选项要开。如果这个没开dmesg里会完全看不到I2C适配器节点后面i2cdetect也就无从谈起。另外建议打开I2C的调试工具支持也就是CONFIG_I2C_CHARDEV这样用户态才能用i2c-tools里的i2cdetect、i2cget这些命令直接访问总线排查阶段几乎离不开它们。3. 设备树编写与驱动绑定3.1 找到I2C控制器节点并确认状态龙芯的设备树管理方式和ARM平台很像板级dts文件里会定义好几个I2C控制器但默认不一定都打开。我碰到过一种很典型的情况dts里I2C0、I2C1、I2C2都在但状态字段全是disabled需要手动把用到的那个改成okay。比如在LS2K0500的dts文件里可能会看到类似这样的节点i2c0 { status okay; clock-frequency 400000; pinctrl-names default; pinctrl-0 i2c0_pins; };这里有几件事要确认。第一是status必须改成okay否则内核不会注册这个I2C适配器第二是clock-frequencyMPU6050最高支持400kHz快速模式但实际使用中如果线材比较长、或者上拉电阻没选好400kHz可能会不稳建议先设在100kHz跑通再说第三是pinctrl引脚的配置这个最容易出问题如果引脚复用不对就算I2C控制器状态正常SDA和SCL也出不了波形。3.2 添加MPU6050子节点关键字段逐个解释I2C控制器节点搞定之后在它下面添加MPU6050的子节点。这是整个移植过程中最核心的一步我直接给出一个我在项目中实际验证过的写法i2c0 { status okay; clock-frequency 400000; mpu605068 { compatible invensense,mpu6050; reg 0x68; interrupt-parent gpio; interrupts 12 IRQ_TYPE_EDGE_RISING; mount-matrix 0, 1, 0, -1, 0, 0, 0, 0, 1; }; };先看compatible。这个字符串必须和驱动里的of_match_table完全一致否则驱动不会绑定到这个设备。在inv_mpu6050_i2c.c里匹配表写的是invensense,mpu6050如果你写成别的名字驱动根本不会probe而且由于I2C不会自动匹配设备ID除非驱动里还有i2c_device_id表dmesg里会只有设备被枚举的记录看不到驱动加载的记录。再看reg 0x68。这是I2C从机地址。MPU6050的AD0引脚接地时地址是0x68接高电平时是0x69。这个地址必须和硬件实际接法吻合不然I2C核心在访问设备时会收到NACKi2cdetect也扫不到设备。interrupt-parent和interrupts是中断配置。如果硬件上把MPU6050的INT引脚接到了某个GPIO这里就写对应的GPIO控制器和GPIO编号。需要注意中断触发类型MPU6050默认的INT输出是高电平有效但实际波形取决于寄存器配置和FIFO溢出状态我在调试中更常用IRQ_TYPE_EDGE_RISING这是因为驱动初始化后会开中断使能传感器一旦有数据就产生一个上升沿。如果你在设备树里配置了中断但硬件上其实没接线这个中断的probe可能会失败或者设备工作异常。这种情况下最简单的办法是去掉中断配置让驱动工作在轮询模式后面你自己在采样周期上做处理。mount-matrix是传感器安装方向矩阵。MPU6050装到板子上未必是正方向可能转了90度或者倒装了这个矩阵就是告诉内核“传感器的物理方向与逻辑方向之间的映射关系”。如果不写驱动会使用默认的单位矩阵也就是认为传感器方向和板子坐标系一致。做姿态解算之前这个必须校准否则算出来的欧拉角全是歪的。但对一个以“先把数据读出来”为目标的移植项目来说用单位矩阵先跑通完全没问题。3.3 硬件接线与电平确认设备树是软件侧的“接线图”但硬件侧接得不对软件再怎么调都没用。MPU6050模块一般有八个引脚我们常用的只有四个VCC、GND、SCL、SDA。接线方面有几个细节容易踩坑。第一是VCC很多MPU6050模块是3.3V供电也有少数5V供电的版本必须确认清楚接错电压轻则传感器不工作重则冒烟。第二是SDA和SCL它们是开漏输出必须有上拉电阻。大部分现成模块上已经焊接了上拉电阻但如果你用的是裸芯片就需要自己在I2C总线上加上拉电阻通常4.7kΩ到10kΩ都行。第三是地线必须共地这个看上去是常识但我在实验室里经常看到有人只接VCC、GND、SDA忘了SCL然后找半天问题。板子侧的I2C引脚最好先查一下开发板的原理图确认用的是哪个I2C控制器、对应到板子上的哪个排针。LS2K0500开发板一般会把I2C引脚引出来标注为I2C0_SCL、I2C0_SDA之类照着接就行。千万不要凭感觉猜测我就遇到过把SDA和SCL接反的情况设备树怎么改都不对最后用示波器一看两根线上都有波形但就是没有正确的ACK应答。4. 编译驱动、部署镜像与用户态验证4.1 把驱动编成模块还是直接编进内核inv_mpu6050驱动在Kconfig里提供了两种选择编进内核y或者编成模块m。我建议在开发和调试阶段用m原因很实际改设备树或者改驱动参数时只需要重新编译模块和dtb不用每次都重编整个内核、刷整个镜像验收效率高很多。等确认稳定了再考虑编进内核减少启动时加载模块的步骤。编译命令很简单在确认menuconfig配置保存之后直接执行make -j$(nproc) Image modules这里Image是内核镜像目标龙芯平台上一般用Image而不是zImage。modules目标会编译所有配置为m的驱动包括你要的inv_mpu6050_i2c.ko和相关依赖模块。编译完成后可以在源码目录里用find确认一下产物find . -name *mpu6050*正常情况下会看到inv_mpu6050_core.ko和inv_mpu6050_i2c.ko两个文件前者是驱动核心逻辑后者是I2C总线绑定层。如果你只编出来一个说明Kconfig里有些依赖项没满足比如IIO可能被设成了模块导致驱动也变成模块但依赖关系奇怪。4.2 部署内核、设备树和模块到开发板部署方式取决于你的开发板启动方式。LS2K0500这类板子一般可以从网络、SD卡或者USB存储启动我这里说一种通用的做法把新编译的Image、dtb文件和.ko模块都拷到板子的启动分区然后在引导参数里指定新的镜像。模块拷贝的时候要注意不能只拷.ko因为驱动模块有依赖关系inv_mpu6050_i2c.ko依赖inv_mpu6050_core.ko后者又依赖IIO核心。如果你把模块目录整个同步过去比如make INSTALL_MOD_PATH/mnt/rootfs modules_install这样会自动处理模块依赖和depmod是最省心的做法。然后把整个内核模块目录打包拷到板子上。如果只是单模块调试也可以手动拷贝cp drivers/iio/imu/inv_mpu6050/inv_mpu6050_core.ko /lib/modules/$(uname -r)/ cp drivers/iio/imu/inv_mpu6050/inv_mpu6050_i2c.ko /lib/modules/$(uname -r)/ depmod -adepmod这步别省它会生成模块依赖文件后面你用modprobe加载的时候才知道去哪个文件找依赖。4.3 用i2cdetect和sysfs验证设备是否真正工作部署完成重启之后第一件事是确认I2C总线上能看到MPU6050。在板子上运行i2cdetect -y 0这里的0是I2C总线的编号具体是哪个取决于设备树注册顺序可以通过ls /dev/i2c-*查看。正常情况下在地址0x68的位置会显示68标志这个地址上有设备应答。如果这一步能看到设备说明硬件链路和数据链路都是通的。接下来加载驱动modprobe inv_mpu6050_i2c然后查看dmesgdmesg | tail -20成功的话会看到类似inv-mpu6050 i2c-0: 0x68: whoami 0x68这样的日志这是驱动读取WHO_AM_I寄存器后打印的读到0x68说明传感器正确回复了身份信息。驱动加载成功之后IIO子系统会自动创建设备节点。进入目录cd /sys/bus/iio/devices/iio:device0/ ls你会看到一堆in_accel_x_raw、in_accel_y_raw、in_accel_z_raw、in_anglvel_x_raw、in_anglvel_y_raw、in_anglvel_z_raw之类的文件。用cat直接读cat in_accel_z_raw静止放置时Z轴的加速度原始值应该在16384附近因为默认量程是±2g而1g对应的就是16384 LSB。如果读数接近0或者满偏那大概率是量程配置、安装方向或者传感器本身状态有问题。一个很实用的验证技巧是读一下非轴向的数据比如把板子水平放置时X轴和Y轴的加速度原始值应该在0附近Z轴接近16384。如果X轴也很大说明传感器没有放平或者mount-matrix配置不对。5. 移植过程中的坑与排查记录5.1 常见问题速查表我在实际移植过程中踩过的坑加上帮别人排查过的案例汇总成下面这张表。如果你在做这个项目遇到问题可以先对着表查一遍。现象可能原因排查方向i2cdetect扫描不到设备从机地址写错、接线松动、上拉电阻缺失、供电不足用万用表量VCC确认AD0电平示波器看SDA/SCL波形扫描到设备但modprobe后无反应设备树compatible不匹配、驱动源码未编译进模块检查dts里的compatible是否等于invensense,mpu6050确认ko文件存在驱动加载但dmesg报probe失败中断配置错误、WHO_AM_I读不到临时去掉interrupt字段用i2cget -y 0 0x68 0x75看返回是否是0x68sysfs里没有IIO设备目录IIO核心未开启、驱动编译成模块但未加载依赖确认CONFIG_IIOy检查dmesg有没有注册IIO设备的日志数据读出来全是0或满偏传感器芯片进入休眠、量程配置未生效、mount-matrix错误用i2cset唤醒设备确认设备树无异常属性核对原始值范围内核编译时报LoongArch架构兼容错误驱动依赖了x86或ARM特有代码检查驱动源码里的#include和条件编译通常需要补架构相关的头文件或函数5.2 典型问题i2cdetect扫描不到设备的排查思路这个现象出现概率最高但原因也最多样。我第一次在LS2K0500上接MPU6050时i2cdetect扫了一大圈一个地址都没出来。当时以为设备树写错了反复检查配置后来才发现是模块的SDA和SCL接反了。所以在动软件之前先用示波器或者逻辑分析仪看波形。如果确认接线没问题就检查地址。MPU6050的7位地址是0x68注意这是7位地址有些工具显示的是8位地址左移一位变成0xD0别被这个搞晕。i2cdetect显示的是7位地址所以0x68是正常的。还有一种可能I2C总线上有其他设备占用了地址导致MPU6050无法应答。多设备共用总线时地址冲突是常事。这时候要么改硬件上AD0的电平把地址改成0x69要么换一条空闲的I2C总线。5.3 典型问题驱动已加载但没有生成IIO设备节点设备在总线上能扫到驱动模块也能insmod进去但/sys/bus/iio/devices/下面空荡荡。这个问题我曾经被坑了一整个下午最后定位到原因是设备树里少了mount-matrix属性而某个版本的内核驱动在解析该属性失败时会直接返回-EINVAL导致probe中断。这个问题的排查套路是看dmesgdmesg | grep -i mpu dmesg | grep -i inv通常能看到类似inv-mpu6050 i2c-0: mount matrix not found: -22或者更模糊的probe of i2c-0 failed with error -22。这里-22就是-EINVAL基本可以断定是某个设备树属性没写或者写错了。解决办法很简单把我在3.2节里给出的完整设备树片段原样贴进去编译替换dtb就能过。需要提醒的是有些龙芯SDK里的内核版本可能对这个驱动的支持不完整比如inv_mpu6050的设备树解析代码在旧内核里对mount-matrix的处理不如新内核健壮。遇到这种情况要么升级SDK内核要么像我一样写全所有支持的属性强行绕过去。5.4 板级固件与模拟环境的补充说明龙芯开发板的启动固件有的叫BIOS有的叫PMON视平台而定偶尔也会对驱动移植产生影响。我遇到过一次I2C控制器始终注册不上的情况设备树、内核配置、引脚复用全查了个遍都没问题最后是刷新了板级固件才解决。这个概率不高但如果你所有常规排查手段都无效可以往这个方向想一想看看官方有没有针对外设枚举或I2C控制器的新版固件。另外很多人会问手头没有实体板子能不能先把驱动移植的流程跑通答案是可以用龙芯模拟环境。QEMU支持LoongArch虚拟化能启动龙芯内核配合busybox根文件系统做模块加载验证。不过要注意模拟环境里没有真实的I2C硬件控制器所以MPU6050这种真实外设是枚举不出来的。它的价值在于提前验证驱动模块在LoongArch架构下的编译兼容性、加载时机和依赖关系等真机到位之后再处理设备树和硬件层面的问题。我用这个方式在等板子的空窗期把inv_mpu6050.ko的编译和模块依赖先跑通了为后续节省了不少时间。6. 实操心得与后续扩展建议做完整套MPU6050驱动移植我最大的感受是这个项目的价值不在于“把一颗传感器的驱动跑起来”本身而在于它把嵌入式Linux开发中几个最关键的知识点串成了一条完整的链路。你在这个过程中会接触设备树、接触内核Kconfig体系、接触IIO子系统、接触I2C协议栈、接触模块加载机制任何一个环节理解不到位设备就是跑不起来。这种“全链路联调”的经验比单独看某个子系统的文档要深刻得多。最后再分享一个我在调试中养成的习惯每改动一个环节只验证一个变量。比如先确保i2cdetect能扫到设备再加载驱动加载驱动之后先看dmesg有没有probe成功再去看sysfs节点sysfs节点出现了再去读数据。不要一次性同时改设备树、换内核配置、调驱动代码否则出了错根本不知道是哪一步引入的。这个习惯帮我省掉的排查时间比我写过的任何一段代码都值钱。如果你已经把这套流程走通了后续可以考虑的方向是给MPU6050接上中断脚用IIO的trigger模式做中断驱动采样替代最简单的轮询读取或者加载DMP固件用硬件姿态解算替换纯软件算法。这些都是在当前驱动框架基础上自然的延伸本质上还是我们对同一颗传感器在Linux系统里用法的深化。到了那个阶段你再回头看“MPU驱动移植”这个项目标题会发现它只是一个起点而不是终点。