做物联网或硬件小项目时,MPU6050几乎是绕不开的传感器。它内部集成了三轴加速度计和三轴陀螺仪,通过I2C总线就能读取数据,价格便宜又稳定。如果服务器端用的是PHP,比如在树莓派上跑了一套PHP的监控系统,想拿到三轴加速度数据来判断设备倾斜或震动,该怎么实现?这篇文章就把从接线、开启I2C,到用PHP代码读出并换算加速度数据的完整流程讲清楚。

一、准备工作:接线与系统配置
MPU6050模块一般有8个引脚,实际用到的只有4根线:VCC接3.3V,GND接地,SCL接I2C时钟线,SDA接I2C数据线。以树莓派为例,SCL对应物理引脚5,SDA对应物理引脚3。接好线以后先别急着写代码,需要先在系统里把I2C功能打开。
以Raspberry Pi OS为例,执行sudo raspi-config,选择Interface Options,再选I2C,确认启用后重启。接着安装I2C工具包:
sudo apt-get update sudo apt-get install -y i2c-tools php php-cli
装好后运行sudo i2cdetect -y 1(老款树莓派总线号是0),正常情况下会在地址0x68的位置看到一个数字。如果看到的是0x69,说明模块的AD0引脚被拉高了,读取时地址要对应改掉。如果整个表格都是空的,优先检查接线是否松动,尤其是VCC有没有接对电压,接5V虽然多数模块能扛,但3.3V才是安全选择。
二、PHP读取I2C数据的两种方式
PHP本身没有内置的I2C函数,但Linux把I2C设备映射成了设备文件,路径形如/dev/i2c-1。第一种方式是直接用PHP的文件函数操作这个设备文件;第二种是调用i2c-tools里的i2cget和i2cset命令。两种方式各有优劣,下面分别说明。
1. 通过shell_exec调用i2cget
这种方式最简单,不需要额外扩展,代码可读性也好。MPU6050的加速度数据存放在0x3B到0x40这6个寄存器里,每轴两个字节,高字节在前。下面是读取原始值的示例:
<?php
// 读取指定寄存器的原始数据
function readRegister($bus, $addr, $reg) {
$cmd = sprintf('i2cget -y %d 0x%02x 0x%02x', $bus, $addr, $reg);
return hexdec(trim(shell_exec($cmd)));
}
$bus = 1; // I2C总线号
$addr = 0x68; // MPU6050默认地址
// 读出三轴加速度的6个原始字节
$ax = (readRegister($bus, $addr, 0x3B) << 8) | readRegister($bus, $addr, 0x3C);
$ay = (readRegister($bus, $addr, 0x3D) << 8) | readRegister($bus, $addr, 0x3E);
$az = (readRegister($bus, $addr, 0x3F) << 8) | readRegister($bus, $addr, 0x40);
echo "AX原始值: $ax\n";注意有符号数的问题。MPU6050输出的是16位有符号整数,用上面方法拼出来的数如果超过32767,实际值要减去65536。也就是加一句if ($ax > 32767) $ax -= 65536;的判断,否则读出来会是六万多这种离谱数值。
2. 直接操作设备文件
调用外部命令每次都有进程开销,采样频率高的时候性能会跟不上。更高效的做法是用fopen打开设备文件,配合ioctl指定从机地址。可惜PHP没有直接暴露ioctl,需要借助ffi扩展(PHP 7.4以上可用),示例:
<?php
$ffi = FFI::cdef("
int ioctl(int fd, unsigned long request, unsigned long arg);
#define I2C_SLAVE 0x0703;
");
$fd = fopen('/dev/i2c-1', 'r+b');
if (!$fd) die('无法打开I2C设备');
$ffi->ioctl(fileno($fd), 0x0703, 0x68);
// 一次性写入寄存器地址,再读取6个字节
fwrite($fd, pack('C', 0x3B));
$raw = fread($fd, 6);
$bytes = unpack('C6', $raw);
$ax = ($bytes[1] << 8) | $bytes[2];
if ($ax > 32767) $ax -= 65536;
echo "AX原始值: $ax\n";这种方式的读取速度比shell_exec快一个数量级,做实时监测建议用这个方案。需要说明的是fileno在PHP里并没有直接实现,实际项目中可以用FFI::cast拿到文件描述符,或者干脆用C写个小工具配合PHP调用。对于一般的项目,第一种方式完全够用,先跑通逻辑再考虑优化。
三、初始化传感器与加速度换算
MPU6050上电后默认处于休眠状态,直接读寄存器只会得到一串固定值或者全零。必须先往电源管理寄存器0x6B写入0来唤醒它,同时建议配置加速度量程寄存器0x1C。量程有四档可选,对应的灵敏度换算系数如下表:
| 量程档位 | 寄存器值 | 灵敏度(LSB/g) |
|---|---|---|
| ±2g | 0x00 | 16384 |
| ±4g | 0x08 | 8192 |
| ±8g | 0x10 | 4096 |
| ±16g | 0x18 | 2048 |
唤醒和配置的PHP代码:
<?php
function writeRegister($bus, $addr, $reg, $value) {
$cmd = sprintf('i2cset -y %d 0x%02x 0x%02x 0x%02x', $bus, $addr, $reg, $value);
shell_exec($cmd);
}
$bus = 1; $addr = 0x68;
writeRegister($bus, $addr, 0x6B, 0x00); // 唤醒芯片
sleep(1);
writeRegister($bus, $addr, 0x1C, 0x00); // 设置量程为±2g,灵敏度16384换算很简单,用原始值除以灵敏度就得到以g为单位的加速度。静态放置时Z轴读数应该接近1g(重力),X轴和Y轴接近0,这是验证数据是否正常最直观的方法:
$accelX = $ax / 16384; // 单位:g
$accelY = $ay / 16384;
$accelZ = $az / 16384;
printf("X: %.2fg Y: %.2fg Z: %.2fg\n", $accelX, $accelY, $accelZ);四、封装成类并做数据平滑
实际项目中裸写代码不好维护,建议把读写逻辑封装成一个类,同时加上滑动平均滤波。MPU6050的原始数据噪声不小,直接用会有明显抖动,简单的滑动平均就能让曲线平滑很多:
<?php
class MPU6050 {
private $bus;
private $addr;
private $sensitivity;
private $buffer = ['x' => [], 'y' => [], 'z' => []];
public function __construct($bus = 1, $addr = 0x68, $range = 16384) {
$this->bus = $bus;
$this->addr = $addr;
$this->sensitivity = $range;
$this->init();
}
private function cmd($pattern, $reg, $val = null) {
$args = [$this->bus, $this->addr, $reg];
if ($val !== null) $args[] = $val;
shell_exec(vsprintf($pattern, $args));
}
private function readReg($reg) {
return hexdec(trim(shell_exec(sprintf(
'i2cget -y %d 0x%02x 0x%02x', $this->bus, $this->addr, $reg))));
}
private function init() {
$this->cmd('i2cset -y %d 0x%02x 0x%02x 0x%02x', 0x6B, 0x00);
usleep(100000);
$this->cmd('i2cset -y %d 0x%02x 0x%02x 0x%02x', 0x1C, 0x00);
}
public function readRaw($axisReg) {
$hi = $this->readReg($axisReg);
$lo = $this->readReg($axisReg + 1);
$val = ($hi << 8) | $lo;
return $val > 32767 ? $val - 65536 : $val;
}
public function readAccel() {
foreach (['x' => 0x3B, 'y' => 0x3D, 'z' => 0x3F] as $k => $reg) {
$this->buffer[$k][] = $this->readRaw($reg) / $this->sensitivity;
if (count($this->buffer[$k]) > 10) array_shift($this->buffer[$k]);
}
$avg = function($arr) { return array_sum($arr) / count($arr); };
return array_map($avg, $this->buffer);
}
}
// 使用示例
$sensor = new MPU6050(1, 0x68, 16384);
while (true) {
$a = $sensor->readAccel();
printf("X:%.2f Y:%.2f Z:%.2f\n", $a['x'], $a['y'], $a['z']);
usleep(500000);
}这个类用了最近10次采样的滑动平均,采样间隔0.5秒,适合姿态判断这类低频场景。如果要捕捉震动、碰撞这类瞬态信号,滑动平均反而会把尖峰抹平,这时应该直接取瞬时值,或者用峰值保持算法。
五、常见问题排查
第一个常见问题是i2cdetect扫描不到设备。先确认I2C已经启用,再换杜邦线排除接触不良,部分劣质模块还需要在AD0和GND之间处理。第二个问题是PHP执行i2cget报权限错误,因为默认只有root能访问/dev/i2c-1。解决办法要么把PHP进程用root跑(不推荐),要么把用户加入i2c用户组:sudo usermod -aG i2c www-data,加完记得重启服务。
第三个问题是读出来全是0xFF或者数值跳变严重。全FF多半是地址不对或者芯片没唤醒;数值乱跳可能是供电不稳,MPU6050模块自带的稳压芯片质量参差不齐,换个电源试试。另外VLogic引脚如果模块有的话,要接3.3V,悬空可能导致通信异常。最后提醒一点,如果数据偶尔读出错误值,可以在读取后做范围校验,原始值超出合理范围就丢弃这一帧重读,这样能显著提高数据可靠性。
php读取传感器数据mpu6050三轴加速度树莓派php修改时间:2026-09-04 07:54:54