
写树莓派Pico系列写到第34篇今天来聊一个很多新手一上来就卡住的项目Pico通过I2C读取MPU6050六轴传感器数据。MPU6050这个东西某宝几块钱一个几乎是玩硬件的人手一块的入门传感器但能把原始数据洗干净、换算成物理单位、再拿去用的真没想象中那么多。这篇我把我实际调试的过程完整拆开讲从接线到代码从寄存器到单位换算从静态漂移到动态响应一条龙说清楚。这个项目适合谁适合刚入手树莓派Pico、想玩传感器但不知道从哪里下手的同学也适合已经能点亮LED、想往机器人、平衡小车、姿态检测方向进阶的朋友。读完你不仅能读出加速度和角速度的原始值还能搞懂每个数值代表什么、怎么校准、为什么有时候数据乱跳。后面就算你换STM32、ESP32这套I2C读MPU6050的思路也完全通用。1. 项目思路与方案选型1.1 为什么用Pico而不是直接用树莓派4B很多人在搜索“树莓派 MPU6050”的时候默认想到的是树莓派4B那种带Linux系统的板子。实际上树莓派Pico和树莓派4B是完全不同的两个物种。Pico用的是RP2040芯片是一块微控制器裸机跑MicroPython或者C/C没有操作系统上电几毫秒就能跑程序适合做实时性要求高的传感器读取和控制任务。为什么这个项目选择Pico核心原因是MPU6050这种传感器需要I2C时序精确、连续高速地读取数据而Pico作为MCU可以用MicroPython的machine.I2C库直接操作寄存器延迟非常低。树莓派4B跑Linux虽然也能通过smbus或者pigpio读但系统调度、驱动层带来的不确定性会导致数据时间戳抖动做姿态解算时体验很差。简单说Pico适合“裸金属”操作传感器4B适合“开飞机”跑视觉和处理复杂逻辑。我的建议是传感器的活交给Pico需要图像识别、网页展示这种重活再交给4B两者串口一接各干各的稳得很。1.2 MPU6050传感器特性与模块选型MPU6050是InvenSense现在归TDK推出的一款六轴运动跟踪芯片内部集成了一个三轴MEMS加速度计和一个三轴MEMS陀螺仪。加速度计用来测量物体在X、Y、Z三个方向上受到的比力单位是g重力加速度静态时可以测出重力方向陀螺仪用来测量绕三个轴的旋转角速度单位是dps度/秒动态时能感知旋转的快慢。一个负责“感知姿态”一个负责“感知转动”两者配合就是后面姿态解算的基础。市面上常见有两种形态一种是MPU6050裸片邮票孔封装需要自己画板子焊另一种是GY-521模块板载了稳压芯片、I2C上拉电阻和排针可以直接插面包板。新手强烈建议用GY-521省事。买的时候注意看一下模块背面丝印有的版本是蓝色板有的黑色板引脚定义基本一致。还有一点某些劣质模块用的可能是翻新芯片WHO_AM_I寄存器读出来不对后面排查时我会专门说这个问题。1.3 I2C总线通信基础一对线怎么传数据MPU6050和Pico之间通过I2C协议通信I2C是一种串行通信协议只需要两根线SDA数据线和SCL时钟线。它的特点是主设备Pico发起通信从设备MPU6050响应地址每次传输都由时钟线控制节奏。I2C是半双工的同一时刻只能一个方向传数据但胜在引脚少、支持一主多从一条总线上可以挂很多个传感器每个用不同地址区分。MPU6050的I2C地址由AD0引脚决定AD0接地时地址是0x68接高电平时是0x69。大多数GY-521模块默认AD0悬空或接地所以地址就是0x68。这个地址值在代码里一定要确认清楚很多人读不到数据就是在这里翻车。另外I2C总线上需要上拉电阻把SDA和SCL拉到高电平GY-521模块板上一般已经焊好了两颗4.7k或者10k的贴片电阻所以不用额外接直接用杜邦线连接就能工作。2. 硬件接线与准备工作2.1 物料清单做这个项目需要的物料非常少基本上一个抽屉就能凑齐树莓派Pico板子一块普通版即可Pico W也完全兼容GY-521模块MPU6050一个面包板和杜邦线若干公对母、母对母都备几根一根Micro USB数据线用于供电和烧录程序电脑一台装好Thonny或其他MicroPython IDE如果你手头已经有Pico和GY-521别的都不用买成本加起来不到五十块钱。特别注意不要用Arduino的5V引脚给MPU6050供电Pico输出的3.3V完全够用接5V虽然模块上通常有稳压芯片能扛住但长期跑传感器的模拟部分会引入更多噪声。2.2 接线表一根线都不能错Pico的I2C引脚有两种选择I2C0默认是GP0SDA和GP1SCLI2C1默认是GP6SDA和GP7SCL。我用的是I2C0接线如下MPU6050引脚Pico引脚说明VCC36脚3V3传感器电源3.3VGND38脚GND共地必须接SDA1脚GP0I2C数据线SCL2脚GP1I2C时钟线AD0悬空或接GND地址选择默认0x68INT可接GP2可选中断输出本教程不用很多新手容易犯的一个错误是只接SDA和SCL忘了接GND。I2C的电平是相对于GND的如果两边不共地SDA/SCL上读到的波形完全错乱表现为i2c.scan()返回空列表。第一次接的时候务必养成习惯先接电源和地再接信号线。2.3 硬件连接容易踩的坑杜邦线尽量短我实测超过20厘米的杜邦线在400kHz I2C频率下会出现偶发通信失败表现为读数偶尔全为0。如果必须用长线把I2C频率降到100kHz能缓解。面包板供电轨的质量也有讲究劣质面包板内部接触电阻大会导致模块供电电压跌落读数噪声明显变大。碰到数据乱跳先怀疑供电再怀疑线材。还有一个很隐蔽的问题Pico的GP0和GP1在MicroPython初始化I2C时内部上拉电阻不一定默认开启。好在GY-521模块板载了上拉电阻所以直接用模块没问题。如果你用的是自己画的MPU6050核心板没有板上拉需要在SDA和SCL上各接一个4.7k电阻到3.3V否则I2C根本没法通信。这一点换到ESP32之类的其他板子也一样属于通用经验。3. 核心实现MicroPython读取MPU60503.1 固件准备与开发环境Pico要跑MicroPython先得烧录MicroPython固件。去树莓派官网下载最新的.uf2固件文件按住Pico板子上的BOOTSEL按钮用Micro USB线插到电脑会弹出一个名为RPI-RP2的U盘把.uf2文件拖进去等待板子重启即完成烧录。整个过程不超过一分钟非常简单。开发环境我推荐用Thonny界面清爽自带MicroPython解释器支持可以直接在右下角选择“MicroPython (Raspberry Pi Pico)”解释器。写完代码按F5就能跑print()输出的内容直接显示在Shell窗口里。如果你喜欢命令行也可以用mpremote或rshell但调试体验不如Thonny直观。后面我讲数据可视化时会提到Thonny自带的绘图器那功能还挺好用。3.2 第一步先扫描I2C地址别急着写功能代码把硬件接好、固件烧好之后我习惯第一步先扫描I2C总线上有哪些设备确认通信链路是通的再往下写。用Thonny的Shell运行下面这段代码from machine import Pin, I2C i2c I2C(0, sclPin(1), sdaPin(0), freq400000) print(i2c.scan())如果一切正常Shell会输出[104]104是十进制的0x68。如果输出[]说明通信链路有问题回去检查接线和供电。这一步花一分钟能帮你省下后面两小时的排查时间我每次接新板子都这么做已经成了肌肉记忆。3.3 完整代码初始化到读取一条龙下面这段代码是我经过多次精简后固定下来的版本能直接跑。功能包括唤醒MPU6050、配置量程、读取加速度和陀螺仪原始值、换算成物理单位、循环打印。from machine import Pin, I2C import time # 初始化I2C0GP0SCLGP1SDA i2c I2C(0, sclPin(1), sdaPin(0), freq400000) MPU6050_ADDR 0x68 # 寄存器地址定义 PWR_MGMT_1 0x6B # 电源管理 SMPLRT_DIV 0x19 # 采样率分频 CONFIG 0x1A # 数字低通滤波器配置 GYRO_CONFIG 0x1B # 陀螺仪量程配置 ACCEL_CONFIG 0x1C # 加速度计量程配置 ACCEL_XOUT_H 0x3B # 加速度计X轴高字节 GYRO_XOUT_H 0x43 # 陀螺仪X轴高字节 # 唤醒MPU6050清零PWR_MGMT_1寄存器 i2c.writeto_mem(MPU6050_ADDR, PWR_MGMT_1, b\x00) # 采样率 陀螺仪输出频率 / (1 SMPLRT_DIV) i2c.writeto_mem(MPU6050_ADDR, SMPLRT_DIV, b\x07) # 关闭数字低通滤波器或者设置为0保持默认 i2c.writeto_mem(MPU6050_ADDR, CONFIG, b\x00) # 陀螺仪量程±250dps灵敏度131 i2c.writeto_mem(MPU6050_ADDR, GYRO_CONFIG, b\x00) # 加速度计量程±2g灵敏度16384 i2c.writeto_mem(MPU6050_ADDR, ACCEL_CONFIG, b\x00) time.sleep(0.1) # 读取16位有符号原始值 def read_raw(reg_h): data i2c.readfrom_mem(MPU6050_ADDR, reg_h, 2) value (data[0] 8) | data[1] if value 0x8000: value - 0x10000 return value # 灵敏度系数对应±2g和±250dps ACCEL_SENS 16384.0 GYRO_SENS 131.0 while True: # 加速度X、Y、Z ax read_raw(ACCEL_XOUT_H) / ACCEL_SENS ay read_raw(ACCEL_XOUT_H 2) / ACCEL_SENS az read_raw(ACCEL_XOUT_H 4) / ACCEL_SENS # 陀螺仪X、Y、Z gx read_raw(GYRO_XOUT_H) / GYRO_SENS gy read_raw(GYRO_XOUT_H 2) / GYRO_SENS gz read_raw(GYRO_XOUT_H 4) / GYRO_SENS print(ax{:.2f}g ay{:.2f}g az{:.2f}g | gx{:.1f} gy{:.1f} gz{:.1f} dps.format( ax, ay, az, gx, gy, gz)) time.sleep(0.1)这段代码在我自己的Pico上连续跑了十几个小时没出过问题。需要说明的是time.sleep(0.1)是为了让串口输出不至于刷屏实际采样率远高于10Hz。如果你后面要做姿态解算可以把print改成收集数据存列表或者直接输出CSV格式给上位机。3.4 代码逐段拆解每个参数为什么这么设初始化部分PWR_MGMT_1写0x00的意思是唤醒传感器让内部时钟源使用晶振。如果不写这一句MPU6050上电默认处于睡眠模式读出来的寄存器全是0或者固定值。这是新手最常见的“代码跑通了但数据全是0”的原因之一。SMPLRT_DIV写0x07采样率就会变成8000 / (1 7) 1000Hz。MPU6050内部陀螺仪的输出频率最高是8kHz加速度计最高1kHz经过分频后得到采样率。对于普通姿态检测1000Hz已经非常快一般不敏感设成默认也能跑。CONFIG寄存器控制数字低通滤波器DLPF的截止频率。写0x00是让滤波器绕过输出带宽最大。但是注意DLPF同时也决定了陀螺仪和加速度计的输出采样率如果后面发现数据噪声很大可以考虑把CONFIG设为0x03对应的DLPF截止频率约44Hz能滤掉不少高频毛刺。我实测下来平衡小车这类场景设成44Hz很合适。GYRO_CONFIG写0x00表示量程是±250dps对应的灵敏度是131 LSB/dps。ACCEL_CONFIG写0x00表示量程是±2g灵敏度是16384 LSB/g。这两个量程选型很有讲究选±2g和±250dps能获得最高分辨率适合做姿态检测如果做剧烈运动的动作识别比如跌倒检测加速度量程改到±16g更合适但分辨率会降低。具体怎么换算下一章详细讲。注意代码里writeto_mem和readfrom_mem是MicroPython偏底层的方法直接以寄存器地址操作跳过了驱动库。好处是原理透明、不依赖第三方库坏处是代码稍微繁琐。你也可以用别人封装好的mpu6050.py驱动文件但建议先把寄存器级的方法跑通一次这样后续遇到问题不会两眼一抹黑。4. 数据解析与单位换算详解4.1 原始数据为什么是16位有符号数MPU6050的每个测量轴输出都是16位存放在两个连续的8位寄存器里高字节在前、低字节在后。以加速度X轴为例寄存器地址0x3B存放高8位0x3C存放低8位。我把两个字节拼接成一个16位数value (data[0] 8) | data[1] if value 0x8000: value - 0x10000这段代码的意思是把高字节左移8位再加低字节得到一个0到65535之间的数如果这个数大于等于32768即最高位是1就减去65536变成负数。这就是二进制补码转十进制。MPU6050测量到的数值有正有负比如陀螺仪顺时针旋转时输出正数逆时针时输出负数所以必须做有符号转换。4.2 量程、灵敏度和单位换算的关系MPU6050的数据手册会给出一个表列出不同量程下的灵敏度系数。所谓灵敏度就是每单位物理量对应多少LSB最小刻度。量程越大测量范围越广但每个LSB代表的物理量越大分辨率越低。这个关系可以用一个简单的比喻理解尺子越长能测的东西越多但刻度越粗短尺子量程小但能读到更细的差异。我在代码里把原始值除以灵敏度系数就得到了物理单位值加速度原始值 ÷ 16384 多少g在±2g量程下陀螺仪原始值 ÷ 131 多少dps在±250dps量程下如果你改了量程灵敏度系数就要跟着改。比如陀螺仪配到±500dps时系数是65.5±2000dps时是16.4加速度配到±4g时系数是8192±8g时是4096±16g时是2048。我踩过一次坑把加速度量程配到±16g忘记改系数所有读数直接缩水了8倍静态时Z轴显示只有0.125g看起来就像传感器坏了。4.3 零偏校准与一阶低通滤波MPU6050出厂前会校准但MEMS传感器受温度、焊接应力、供电噪声影响实际静止时读数往往不是零。我的板子静止时陀螺仪Z轴输出大约1.5dps这个值叫零偏。零偏如果不校准累积起来会让姿态解算的角度一直漂移。校准的方法很简单把传感器放平静止采集100次数据取平均值把这个平均值当作零偏后续每次读取都减去它。我在代码里加了一小段示意# 静止校准陀螺仪零偏 offset_gx, offset_gy, offset_gz 0, 0, 0 SAMPLES 100 for _ in range(SAMPLES): offset_gx read_raw(GYRO_XOUT_H) offset_gy read_raw(GYRO_XOUT_H 2) offset_gz read_raw(GYRO_XOUT_H 4) time.sleep(0.01) offset_gx offset_gx / SAMPLES / GYRO_SENS offset_gy offset_gy / SAMPLES / GYRO_SENS offset_gz offset_gz / SAMPLES / GYRO_SENS校准之后再把read_raw()算出来的物理值和零偏相减。加速度计也可以顺带校准三轴偏差但相对麻烦一点需要六个面分别放置。对于普通项目先做一个水平面校准就能用。如果你的应用不是特别追求实时性可以再加一个一阶低通滤波公式是filtered alpha * raw (1 - alpha) * filteredalpha取0.1到0.3之间。这个滤波器本质上是滚动平均能明显减小信号毛刺代价是相位滞后。做姿态解算时要谨慎因为滞后会影响控制回路的稳定性做数据显示或者动作判断则无所谓。5. 实测过程与数据可视化5.1 静态测试传感器放桌上数值稳不稳把代码烧进Pico传感器平放在桌面上串口输出大概是这样ax0.01g ay0.00g az1.00g | gx-0.2 gy0.5 gz1.4 dps ax0.00g ay0.01g az1.00g | gx-0.3 gy0.4 gz1.3 dps ax0.01g ay-0.01g az0.99g | gx-0.4 gy0.6 gz1.6 dps加速度计的Z轴读数在1g附近波动这符合预期因为重力方向垂直于水平面。注意X和Y轴不是严格为0因为模块不可能是绝对水平的稍微倾斜几度就会读到重力分量。陀螺仪三个轴的输出都在零点附近跳动跳动范围在±2dps以内都算正常。如果Z轴稳定在1.4dps附近就是前面说的零偏校准掉就好。5.2 动态测试手动翻转观察数据响应拿在手上快速翻转模块串口打印值会明显变化。竖直翻转时加速度计的X轴读数会从0跳到1g左右同时Z轴从1g掉到接近0水平旋转时陀螺仪的Z轴读数会瞬间跳到几十上百dps。这一阶段我建议配合串口绘图工具看波形比看数字直观得多。Thonny自带一个“绘图器”功能打开方式是“查看 - 绘图器”然后运行会不断print数字的代码。如果一次打印多个值用空格分隔绘图器会把每个值当作一条独立曲线画出。我常把ax ay az gx gy gz用空格打印出来六个波形一起显示翻转时能非常直观地看到各轴联动关系。这个功能对调试传感器特别有用省掉了装第三方串口助手的麻烦。5.3 把数据存下来用Python做离线分析如果你想精细分析数据可以把Pico的串口输出重定向保存到文件比如在Thonny里把输出复制下来或者用串口工具设置日志保存。保存成CSV格式后用电脑上的Python加matplotlib画图很香。下面是一段简单示例import matplotlib.pyplot as plt # 读取CSV假设每行是 t,ax,ay,az,gx,gy,gz data [] with open(mpu6050_log.csv) as f: for line in f: row line.strip().split(,) data.append([float(x) for x in row]) t [r[0] for r in data] ax [r[1] for r in data] gy [r[5] for r in data] plt.figure(figsize(10, 4)) plt.plot(t, ax, labelax) plt.plot(t, gy, labelgy) plt.legend() plt.show()这种离线分析方式适合做动作识别算法研究。比如你要设计一个跌倒检测模型先采集几百条跌倒和非跌倒的数据在电脑上把特征提取好再移植到Pico上跑开发效率会高很多。6. 常见问题与排查技巧6.1 快速排查表传感器读不到先看这里现象可能原因解决办法i2c.scan()返回空列表接线错误、未共地、供电不足检查VCC、GND、SDA、SCL四根线返回列表但地址不是[104]AD0被拉高了地址变成0x69代码里改地址数据全为0传感器睡眠未唤醒写PWR_MGMT_1寄存器为0某轴读数固定不变量程和灵敏度不匹配检查GYRO_CONFIG和ACCEL_CONFIG数据乱跳供电不稳、杜邦线过长换短线、加滤波电容、降低I2C频率模块发热VCC接到了5V改回3.3V供电这张表是我几次带新手调板子的经验汇总90%的问题都能对号入座。6.2 几个深度排查实录有一次我手上这块GY-521怎么扫描都返回空列表杜邦线换了一根又一根最后发现是面包板电源轨的插孔氧化导致GND虚接。这种问题用万用表量就能发现但也遇到过手边没表的情况。我的建议是如果条件允许备一块几块钱的万用表测通断、测电压排查硬件问题效率直接翻倍。还有一次数据偶发全零看起来像传感器“抽风”。排查到最后发现是Pico的USB供电在电机转动时出现波动导致MPU6050供电电压跌落I2C通信失败。解决方案也很简单如果这板子接了电机或舵机务必单独供电或者加一个大电容。传感器电路和电机驱动电路共用一个电源是硬件调试中最常见的坑没有之一。再补充一个和C/C相关的坑。如果你后来改用Pico的C SDK开发寄存器操作和MicroPython完全一致但要注意i2c_write_blocking和i2c_read_blocking的参数里地址是7位不要左移一位当成8位地址传进去。这个错误我在从MicroPython转向C SDK时踩过整整查了一个下午。6.3 后续可以怎么扩展姿态解算和大项目衔接读取到干净的加速度和角速度数据之后下一步通常就是姿态解算。最简单的方案是互补滤波公式核心是角度 0.98 * (角度 陀螺仪角速度 * dt) 0.02 * 加速度计计算的角度。陀螺仪短期准、长期漂移加速度计长期准、短期噪声大互补滤波就是取两者长处。想要更高精度可以用Mahony算法或者Madgwick算法它们在嵌入式圈子里都有开源C代码移植到Pico也很方便。拿到姿态角之后能玩的东西就多了两轮平衡小车、四轴飞行器飞控、机械臂关节角度反馈、头戴式鼠标、跌倒报警装置甚至VR头显的头部追踪。Pico的RP2040是双核用Core0跑控制逻辑Core1跑传感器读取和姿态解算性能完全够用。我个人在这些传感器项目里最大的体会是先把数据读到一串稳定可信的数字再谈算法和上层应用。很多人一开始就想着跑通姿态解算、搞卡尔曼滤波结果连原始数据的单位和量程都没确认清楚最后整个系统都在一个错误的基础上摇摆。老老实实从I2C扫描开始把每一个打印出来的数值背后是什么物理含义搞清楚后面所有东西都会顺很多。这篇文章的项目编号是34/100如果你是从头跟着做下来的到这一步已经积累了相当扎实的硬件基础别急后面好玩的东西还多。