ARTICLE DETAIL

资讯详情

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

龙芯平台MPU6050驱动移植实战指南

龙芯平台MPU6050驱动移植实战指南 这个项目名看起来挺有意思——“走马观碑组MPU驱动移植”缩写叫“龙芯k”实际干的事就是在龙芯平台上把一个惯性传感器很典型的就是MPU6050这类六轴芯片的Linux内核驱动跑起来。标题里的“儇”多半是组名或者笔误不影响理解。这类工作我这些年做过不少也踩过不少坑趁这次把整个移植流程、原理、踩坑点全部摊开聊一遍希望能帮到正在做国产平台驱动适配的朋友。先给没接触过这块的读者一个定位这件事的本质是把原本在x86/ARM平台上写好的内核外设驱动通过修改总线适配层、设备树描述、配置选型等方式让它在龙芯的CPU平台上重新编译、加载并正常工作。听起来不复杂真正上手之后才会发现硬件时序差异、内核版本差异、甚至是大小端字节序、DTS节点写法、I2C地址探测这些细节任何一环都可能让你折腾好几天。1. 项目整体设计与思路拆解1.1 核心需求解析这个项目到底在解决什么问题先说清楚这个项目的基本盘。龙芯平台这几年在党政办公、工控、嵌入式领域铺量很大尤其是龙芯K系列面向嵌入式和工控场景大量设备需要接入各种外围传感器。MPU6050是个很典型的六轴传感器自带三轴陀螺仪和三轴加速度计还带一个DMP运动处理器在姿态解算、跌倒检测、平衡车、工业振动监测这些场景里到处都是。驱动移植要解决的事情说白了就是四层第一层硬件层面I2C总线的电气特性和时序是否符合MPU6050的规格第二层内核层面龙芯内核里有没有对应的I2C控制器驱动有没有I2C设备驱动框架第三层设备描述层面设备树DTS或者板级文件里有没有正确描述这个传感器的挂载位置和参数第四层应用层面驱动加载起来之后用户态能不能正确读到数据数据单位、量程是否合理。我在实际做这个项目的时候发现很多人一上来就急着编译驱动、写测试程序结果卡在第三层——设备树没配对内核根本没把设备枚举出来。所以这篇文章我会花比较多篇幅讲设备树和内核配置这是最容易出问题也最容易被忽略的部分。1.2 方案选型为什么选择“从内核主线移植”路线而不是“写裸驱动”当时我们面临两条路一条是参考Mainline内核里已经存在的mpu6050驱动直接移植另一条是抛开内核框架自己写一个简单的字符设备驱动直接操作I2C寄存器。我们最终选了前者原因有几点这几点的权衡过程很值得参考第一Mainline的mpu6050驱动已经非常成熟。它基于IIOIndustrial I/O子系统实现包含了完整的寄存器初始化序列、自检逻辑、中断处理、FIFO数据读取支持代码审过一次、社区维护多年出错的概率远比我们自己写的低。自己写驱动的代价不光是要重新熟悉MPU6050那一百多个寄存器的含义还要自己处理设备休眠唤醒、FIFO溢出、陀螺仪和加速度计量程切换这些边角逻辑调试成本不可控。第二IIO子系统提供了一个统一的上层接口。驱动挂接到IIO之后用户态可以直接通过sysfs节点读数据也可以通过iio_utils库拿数据未来如果要从MPU6050换成ICM20602这类同生态传感器只需要小改驱动或者换设备树应用层基本不用动。第三龙芯内核本身已经包含大量IIO框架代码。因为龙芯也在往工业控制、机器人这些领域靠内核里I2C、GPIO中断、IIO框架这些基础模块都是全的我们移植的时候不需要从零去补框架只需要补芯片驱动本身。结论就是能站在内核框架的肩膀上解决问题就不要自己从地板开始砌。这不是偷懒是工程效率的合理选择。2. 开发环境搭建与龙芯平台准备2.1 交叉编译工具链与环境变量龙芯K系列用的是LoongArch指令集跟常见的x86、ARM都不一样所以不能用gcc直接用必须用龙芯的交叉编译工具链。一般龙芯官方提供的工具链命名格式是loongarch64-linux-gnu-gcc安装完工具链之后环境变量建议这样配实测比较稳export CROSS_COMPILEloongarch64-linux-gnu- export ARCHloongarch这两个变量export之后以后编译内核、编译驱动模块、编译设备树都会自动带上交叉编译参数不需要每个Makefile里手动改。需要注意一个比较隐蔽的坑龙芯的交叉编译工具链有glibc版本和musl libc版本之分。如果目标系统是BusyBoxmusl这种精简rootfs工具链必须也用musl版本否则编出来的驱动模块虽然能insmod但一旦调用到某些依赖libc的函数就会出错符号找不到很难排查。我当时就是犯了这个错误刚开始用的glibc工具链加载模块时报了一堆undefined symbol一度以为是内核配置问题。2.2 模拟环境先行没有硬件也能验证流程这个项目其实有很现实的问题——龙芯的开发板不一定随时在手边尤其是驱动开发初期频繁烧写固件很费时间。我们在做的时候用了一个很有效的方式先在一个模拟环境里把整个流程跑通包括内核编译、驱动编入、设备探测、数据读取然后再到真机上去复现。这里说的模拟环境主要是QEMU模拟LoongArch虚拟机以及龙芯官方提供的模拟器。QEMU下跑LoongArch虚拟机流程大致是下载QEMU的loongarch版本目前主线QEMU已经支持LoongArch的virt机型准备一个LoongArch的虚拟机镜像通常用Loongnix或者Buildroot自己构建把带MPU6050驱动编进内核启动虚拟机后用i2c-stub或者模拟I2C控制器的方式验证驱动加载路径。可能有人会问模拟环境里没有真实的MPU6050芯片怎么验证驱动这里有两个思路一是用i2c-stub。它是内核自带的一个I2C设备模拟工具可以在没有硬件的情况下虚拟出一组I2C适配器然后注册虚拟设备。虽然它没法模拟MPU6050的寄存器行为但可以验证驱动是否被正确加载、probe是否被调用、I2C读写流程是否走通。二是用QEMU的I2C模型配合virtsi设备直接把MPU6050的寄存器模型用软件实现出来挂到虚拟I2C总线上。这个工作量比较大但是对驱动验证来说非常彻底。模拟环境的最大价值在于它能把“内核编译错误”“设备树语法错误”“驱动与内核API不匹配”这类问题在几分钟内暴露出来而不用每次都在真机上烧卡、重启、看串口。我个人的建议是驱动移植类的项目一定先去做“编译加载SysFS节点检查”这三步的模拟验证再去碰真机。2.3 固件与BIOS更新这一步别跳过龙芯K系列的板卡在启动过程中会先执行一段固件类似BIOS/bootloader初始化内存、总线、外设资源。很多时候我们发现I2C控制器工作不正常排查到最后发现是固件版本太老I2C控制器时钟配置有问题或者设备树里描述的中断控制器和固件实际初始化出来的不匹配。所以项目开始之前建议先把龙芯板卡的BIOS更新到官方发布的最新版本。更新固件本身不算复杂一般官方会提供烧写工具和固件包。操作步骤大致如下从官方渠道下载对应板卡的最新固件注意区分主板型号和CPU型号固件刷错了大概率变砖将固件文件放到FAT32格式的U盘里板卡上电时进入固件更新模式不同板卡按键不同有的是按Delete有的是按F2看官方手册在固件更新界面选择U盘里的固件文件开始升级升级完成后断电重启进固件设置菜单确认I2C、GPIO等外设的中断号分配是否正确。这一步花的时间不长但能省掉后面一大半“硬件层面莫名其妙”的排查时间。3. MPU6050驱动移植的完整实操3.1 内核配置准备从IIO到I2C的依赖链MPU6050驱动在Linux内核里属于IIO子系统下的运动传感器类。从内核主线来看需要打开的核心配置项如下CONFIG_IIOy CONFIG_IIO_BUFFERy CONFIG_IIO_TRIGGERED_BUFFERy CONFIG_MPU6050_I2Cy CONFIG_INPUT_MPU6050y (看内核版本有的版本用这个)在龙芯平台上还需要确保I2C控制器驱动是开启的例如CONFIG_I2Cy CONFIG_I2C_LOONGSONy # 龙芯I2C控制器的驱动具体名称以内核源码为准如果打算把驱动编成模块动态加载就用m而不是y。我个人在调试阶段更喜欢编成模块这样方便反复insmod/rmmod不用频繁重启。配置方法make loongson3_defconfig # 具体defconfig名称看官方内核 make menuconfig在menuconfig中依次进入Device Drivers - Industrial I/O support - Inertial measurement units找到Invensense MPU6050 devices。一个实操心得龙芯官方内核可能和Mainline内核有差异有的官方内核里MPU6050驱动可能没有默认编译需要手动勾选有的老内核里驱动路径在Device Drivers - Staging或sensors目录下要多翻几下。3.2 设备树编写的关键细节设备树是这次移植的重中之重。MPU6050挂载在I2C总线上所以设备树里要做两件事一是确认I2C控制器的状态是okay二是把MPU6050作为一个子节点挂到正确的I2C控制节点下。一个典型的MPU6050设备树节点长这样i2c0 { status okay; clock-frequency 400000; /* 尽量用400kHz读数据更快 */ mpu605068 { compatible invensense,mpu6050; reg 0x68; /* MPU6050的I2C地址默认是0x68 */ interrupt-parent gpio; /* 具体用哪个中断控制器看板级设计 */ interrupts 20 2; /* GPIO 20上升沿触发 */ mount-matrix 0, 1, 0, -1, 0, 0, 0, 0, 1; /* 安装矩阵根据芯片实际摆放方向设置 */ }; };这些属性很多但真正影响驱动能不能跑起来的关键字段其实就几个reg字段。MPU6050的I2C地址由芯片AD0引脚的接地/接高决定接地是0x68接高是0x69。这个必须和实际硬件对上不然驱动probe阶段就会失败。interrupt-parent和interrupts。如果这两个字段不对中断方式读数据就起不来。如果只是想先读数据可以在驱动里绕过中断用轮询方式但终归不如中断方式高效后续还是要修。mount-matrix。这个很多人会忽略如果装芯片的时候方向跟设备树里的默认矩阵不一致读出来的加速度和角速度方向就会是反的或者轴序错乱姿态解算结果完全不可用。我之前遇到过一个问题I2C地址写的是0x68设备也确实是这个地址但modprobe之后dmesg里始终报“Failed to read WHO_AM_I”。后来用i2cdetect扫描了一下发现设备其实在0x69上硬件工程师看错了AD0引脚的接线。这种问题不看实际硬件光盯代码永远找不到原因。设备树写完之后需要通过编译生成dtb文件make dtbs然后有两种方式生效一种是把dtb替换到启动分区里另一种是在u-boot或GRUB启动参数里指定新的dtb。龙芯平台通常是后者更灵活启动时在引导菜单里按提示替换dtb文件。3.3 驱动编译与模块安装如果内核已经配置好了驱动编译非常直接make modules make modules_install INSTALL_MOD_PATH/path/to/rootfs编译出来的ko文件路径一般类似drivers/iio/imu/inv_mpu6050/inv-mpu6050-i2c.ko如果只用模块方式加载手动拷贝也行cp drivers/iio/imu/inv_mpu6050/*.ko /path/to/rootfs/lib/modules/$(kernel_version)/kernel/drivers/iio/imu/inv_mpu6050/ depmod -b /path/to/rootfs # 重新生成modules.dep实际加载过程我建议这么做方便看清问题modprobe inv-mpu6050-i2c # 或者 insmod inv-mpu6050-i2c.ko dmesg | tail -50加载成功的标志是dmesg里出现类似这样的信息inv-mpu6050 i2c-0:0x68: mounting matrix not found: using identity... inv-mpu6050 i2c-0:0x68: whoam i 0x68 ok如果看到“whoam i”校验失败基本可以断定I2C通信或者地址不对按前面说的排查。3.4 用sysfs和工具验证驱动工作状态驱动加载成功只是第一步数据对不对才是关键。IIO框架下的驱动加载之后可以在/sys/bus/iio/devices/下看到新的设备节点通常是iio:device0。在这个目录下会有很多有意思的文件ls /sys/bus/iio/devices/iio:device0/重点关注这几个in_accel_x_raw, in_accel_y_raw, in_accel_z_raw加速度计三轴原始值in_anglvel_x_raw, in_anglvel_y_raw, in_anglvel_z_raw陀螺仪三轴原始值in_temp_raw温度原始值in_accel_scale, in_anglvel_scale原始值换算成物理值的比例系数name设备名称应该是mpu6050lable设备标签如果设备树里配了lable字段就会显示原始值读出来是带符号整数换算方式很简单实际加速度(m/s^2) in_accel_x_raw * in_accel_scale 实际角速度(度/s) in_anglvel_x_raw * in_anglvel_scale举个例子如果in_accel_scale是0.000598raw值是4096那么实际加速度是4096 * 0.000598 ≈ 2.45 m/s²。芯片静止平放时理论上Z轴应该接近9.8 m/s²X/Y轴接近0如果偏差很大就要考虑量程配置或者mount-matrix的问题了。3.5 静态编译进内核的处理如果在生产环境一般不希望依赖模块加载而是直接把驱动编进内核镜像。此时需要把.config里的CONFIG_MPU6050_I2C改为y同时确保IIO相关配置也是y然后重新编译内核make -j$(nproc) make dtbs编完后把新内核镜像和dtb一起烧写或拷贝到启动分区。此时设备启动后不需要任何modprobe操作/sys/bus/iio/devices/下直接就会出现设备节点。这也是我比较推荐的生产环境做法——少一个模块加载步骤就少一个出错环节也少一个被恶意替换ko文件的安全风险。4. 常见问题与调试技巧实录4.1 I2C探测不到设备先分清是总线问题还是设备问题这是整个移植过程中最高频的问题。建议的排查顺序是第一步用i2cdetect扫描I2C总线i2cdetect -y 0 # 0是I2C总线编号龙芯平台可能从1或2开始看实际情况扫描结果会以矩阵形式显示总线上所有有响应的I2C地址。如果0x68或0x69位置有“UU”或者“68”“69”字样说明设备在线且内核驱动已经绑定如果是“--”说明地址上没探测到设备。第二步如果i2cdetect都扫不到设备大概率是硬件问题I2C上拉电阻没焊、AD0引脚接法不对、电源没供上、SCL/SDA接反了。此时用示波器量一下SCL/SDA波形最直接。第三步如果i2cdetect能看到设备但驱动probe报错则重点看dmesg里的错误信息。此处需要区分“设备发现失败”和“设备初始化失败”两者的排查路径完全不同。4.2 数据全是0或者数据跳变剧烈如果驱动加载成功、节点也有但读出来的数据要么全0要么跳变到非常夸张的数值通常是两类原因一类是量程配置不对。MPU6050加速度计的量程默认是±2g如果应用里设置成了±16g而驱动初始化时序没有正确写入量程寄存器raw读数就会超过正常范围。排查方法是通过IIO node里的in_accel_scale值判断如果scale值跟预期量程不匹配说明量程寄存器没写对需要检查驱动初始化序列。另一类是接线干扰。I2C线如果太长或者没有上拉电阻高速传输时数据容易出错表现就是读出数值随机跳变。这个时候降低I2C时钟频率比如从400kHz降到100kHz往往能缓解问题。设备树里clock-frequency改一下重新编dtb就行。4.3 中断方式不生效只能轮询MPU6050的INT引脚可以配置成多种触发方式。设备树里interrupts字段如果配置的是上升沿但芯片实际输出的是低电平有效驱动就会一直触发不了中断。排查方法是在设备树里先删掉中断配置让驱动回退到轮询模式。如果轮询模式下数据正常就基本可以断定是中断触发方式不匹配。然后查阅MPU6050的INT_PIN_CFG寄存器地址0x37的配置确认ACTL位设置修改设备树中断类型或者驱动初始化代码里的INT配置。我在实际项目中遇到过非常隐蔽的一个问题MPU6050的中断输出是开漏结构需要外部上拉才能正常工作。龙芯的GPIO内部上拉如果没使能INT引脚永远是低电平驱动和中断自然全部失效。查了一下午最后发现就是少配了一个GPIO上拉属性。4.4 内核API版本不匹配编译报错龙芯官方内核可能不会跟Mainline最新版完全同步。MPU6050驱动如果从最新内核拷贝到老内核上很可能遇到一些API变更导致编译不过。常见的坑有i2c_driver结构体里的probe函数签名变化新版用.probe_new老版用.probedevice_property_read_u32系列函数的引入版本devm_iio_device_register和iio_device_register的差异。解决办法很简单优先使用龙芯官方内核源码树里自带的MPU6050驱动版本不要盲目从最新Mainline拷贝过来用。如果必须使用新驱动就需要对照官方内核的API差异做适配。这块没有捷径多读内核代码、多查内核版本log。4.5 常见问题速查表现象可能原因排查/解决办法i2cdetect扫不到设备硬件接线、AD0地址错、供电示波器量波形、检查AD0电平i2cdetect有设备probe报WHO_AM_I失败I2C地址与设备树reg不一致用i2cdetect确认实际地址修改设备树驱动加载成功数据全为0量程写错、传感器处于睡眠模式检查PWR_MGMT_1寄存器重新初始化数据跳变剧烈接线干扰、时钟过快降低I2C时钟到100kHz检查上拉中断触发不了触发方式不对、开漏无上拉查INT_PIN_CFG配置GPIO上拉编译报probe签名错误内核API版本不匹配用官方内核自带驱动或适配API5. 写在最后的一点经验这个项目做完之后我最大的体会是驱动移植这件事看似是写代码、改配置实际上拼的是“硬件认知内核框架理解耐心排查”三样东西。做好一个底层驱动移植三分靠写代码七分靠排查而排查最忌讳的就是瞎试。给准备入手的读者两个最实在的建议第一模拟环境一定要用起来先让编译、设备树、加载路径都走通再上真机能帮你省掉大量测试时间第二遇到问题时先把“设备是否在线、地址是否正确、中断配置是否匹配”这三个基本盘查清楚再去怀疑驱动本身。最后再分享一个细节MPU6050芯片有个自检功能self-test驱动加载成功并读到数据之后建议把传感器静止放置读一组静态数据确认加速度计Z轴接近重力加速度、陀螺仪三轴接近0再开始做动态姿态解算。这个小小的验证习惯能帮你把“驱动通了但数据方向反了”这类后期问题提前暴露出来。
返回列表