先说背景。走马观碑组这次领到的任务是“龙芯k - 走马观碑组MPU驱动移植”,目标很明确:把我们之前在一颗ARM平台的MCU(STM32H750)上调通的MPU6050六轴姿态传感器驱动,完整搬到一块龙芯2K系列的嵌入式Linux板卡上。很多人一听“驱动移植”就觉得要改写底层寄存器,其实真正动脑子的地方不是寄存器,而是“访问方式变了、平台资源变了、调试手段也变了”。这篇文章就围绕这次移植展开,把我们在项目里的拆解思路、环境准备、代码改造和真机调试过程原原本本记录下来,给后面要碰类似工作的同学做一份可以直接对照的参考。
文章适合谁看?如果你需要把一段在裸机/MCU上跑得好好的传感器驱动,挪到嵌入式Linux平台上继续跑;或者你不确定用户态驱动、内核驱动该怎么选,遇到I2C地址漂移、数据全零、量程换算不对这类问题,那这篇应该能帮你省下不少时间。即使你还没碰过龙芯硬件,代码里大部分平台差异处理逻辑也一样能复用到其他Linux板卡上。
1. 先想清楚:这个“MPU驱动移植”到底在移什么
很多人拿到类似任务,第一反应是打开原来的工程,看一下MPU6050的初始化代码,复制粘贴到新工程,然后编译。这个做法不能说错,但大概率会卡在后面的调试阶段。动手之前,把“MPU驱动”四个字拆成三个层次理解,思路会清楚很多。
1.1 MPU6050驱动里,真正不能动和必须动的部分
MPU6050本身是一颗I2C/SPI接口的运动处理单元,里面集成了三轴加速度计和三轴陀螺仪,还带一个内部的DMP(Digital Motion Processor)。这颗芯片所有功能最终都落到寄存器读写上。而“驱动”这个词,在跑Linux的板子上其实包含着两层意思:第一层是芯片自身的寄存器配置与数据解析逻辑,第二层是芯片挂在哪个总线上、CPU怎么访问这个总线里的设备。
我们原本的工程是在STM32H750裸机环境下写的,用的是HAL库的 HAL_I2C_Mem_Read 和 HAL_I2C_Mem_Write 来发I2C事务。整个MPU6050的初始化序列、唤醒流程、量程配置、FIFO读取、加速度和角速度的十六位数据解析,这些属于“芯片自身逻辑”。它们和CPU是哪家没有关系,只要你提供的I2C读写函数行为一致,就完全不需要动。
真正必须动的,是底层那个I2C读写函数本身,以及跟平台绑定的一些资源,比如引脚复用、中断配置、时基。H750上用的是硬件I2C外设和GPIO外部中断,而龙芯2K这套Linux系统里,I2C控制器已经被内核接管了,用户态程序不可能也不应该去直接操作I2C控制器的寄存器。你只能通过内核暴露的 /dev/i2c-x 接口,用 ioctl 的方式发起读写事务。也就是说,这次移植的中心任务,是写一个“Linux版”的I2C HAL层,去替换掉原来H750上依赖HAL库的那层实现。
1.2 用一层“驱动边界”把平台差异隔离掉
如果直接把Linux的i2c-dev调用代码散落在整个传感器驱动里,后面维护会很难受。我们团队的习惯是画一条清晰的边界:上层只关心“读寄存器、写寄存器、读一批数据”这三个动作,底层则根据平台实现这三个动作。在H750上,底层实现就包一层函数,里面调HAL库;在龙芯Linux上,底层实现重新包一层,里面走文件操作和ioctl。上层什么都不用知道。
这个设计很像搬家。家里的沙发、柜子、电器怎么摆,你不需要因为从一楼搬到五楼就重新设计一遍;你需要做的只是把“搬运通道”这段路径跑通,确保每件东西能原封不动地进门。传感器驱动里的姿势解算算法、寄存器配置表就是家具,I2C HAL层就是那条搬运通道。新建平台时只重写搬运通道,家具还原样摆放。
后来证明这个隔离层的价值非常大。我们在龙芯2K板子上验证时需要切换不同总线,从 /dev/i2c-0 换到 /dev/i2c-2,只需要改一处open路径参数,其余代码一行没动。如果前期没做这层隔离,改起来就不是改一处而是全工程搜I2C,容易漏。
1.3 用户态驱动 vs 内核驱动,为什么这次选了用户态
嵌入式工程师看到“驱动”两个字,很容易默认要写一个内核模块。实际项目里要分情况。走马观碑组这次的目标是把姿态传感器先跑起来,验证硬件接线、I2C总线和后续算法效果,属于算法验证和前期集成阶段。在这种情况下,内核里i2c-dev机制完全够用,直接在用户态通过设备文件操作寄存器,开发效率明显更高,改个代码不用重新编译内核,也不用担心模块加载把整个系统搞挂。
当然用户态方案也有不足,比如实时性差一些、中断处理做不到内核态那么直接。如果这个传感器后续要跟内核里其他子系统做深度配合,比如要作为一个标准工业输入设备被引用,那就可以在用户态跑通之后,再把它封装成一个标准的内核I2C客户端驱动。这两种路径不是互斥关系,而是验证阶段和生产阶段的先后关系。我见过很多团队一开始就要求写内核驱动,结果调试环境变量多了一倍,麻烦也翻倍。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 龙芯平台环境搭建:板子、工具链与可复现的准备过程
环境准备听起来不像技术活,但这次项目至少有一半的“坑”是环境没对齐导致的。我按实际踩过的顺序说说,尽量让后面的人少走弯路。
2.1 拿到板子先别急着接线:确认I2C总线和引脚状态
我们手头的龙芯2K开发板,板载引出多路I2C,理论上挂MPU6050很轻松。但“理论上”和“实际可用”之间隔着一个设备树。板子上的I2C控制器并不会把所有通道都默认打开,引脚也可能被复用成别的功能。所以第一件事不是拿杜邦线去怼,而是在终端里跑:
bash复制ls /sys/bus/i2c/devices/
i2cdetect -l
i2cdetect -l 会列出当前系统注册的所有I2C总线,能看到类似 i2c-2 这样的条目,说明这条总线已经被内核识别并注册了。如果列表里根本没有你要用的总线号,那说明设备树里对应节点可能没有打开,或者状态写的是 disabled。这时候应该去找这块板卡配套的BSP资料,确认是哪组引脚复用了I2C功能,检查内核设备树里对应的I2C控制器节点是否设置为 status = "okay"。
还要提醒一点:不要只依赖原理图来判断总线号。同一颗SoC可能有多个I2C控制器,板卡厂商在设备树里的顺序不一定跟SoC手册完全一致。
