从加速度计数据到欧拉角:行空板与Micro:bit姿态解算实战
2026/7/28 11:22:48 网站建设 项目流程

1. 项目缘起:从“知道倾斜”到“知道朝哪倾斜”

最近在做一个智能小车项目,需要让小车在斜坡上自动调整姿态。手头正好有行空板和Micro:bit,它们都内置了加速度计。一开始,我的想法很简单:读取加速度计的X、Y、Z轴数据,哪个轴的数值变化大,不就说明朝哪个方向倾斜了吗?

实际操作后,我发现事情没那么简单。当我将设备平放,然后绕Z轴(垂直轴)旋转时,X和Y轴的加速度读数确实会变化,这能告诉我设备在水平面内的“朝向”发生了变化。但是,当我将设备绕X轴(前后轴)抬起时,不仅Y轴(左右轴)的读数会变,连Z轴(上下轴)的读数也会剧烈变化。单纯比较三个轴的原始数值,根本无法清晰、直观地告诉我:“设备现在前倾了30度”或者“设备左倾了45度”。得到的只是一堆让人困惑的、耦合在一起的数字。

这让我意识到,我需要的是“欧拉角”。这是一个在机器人、无人机、游戏开发等领域非常基础且重要的概念。简单来说,欧拉角用三个角度(通常称为Roll翻滚、Pitch俯仰、Yaw偏航)来描述一个物体在三维空间中的旋转姿态。这正是我需要的:一个明确的、人类可理解的倾斜描述。于是,我决定深入探究如何利用行空板和Micro:bit的加速度计数据,计算出准确的倾斜方位(欧拉角)。

2. 核心原理:加速度计如何“感知”重力与姿态

要理解如何计算,首先得明白加速度计在静止状态下到底测量了什么。当我们的设备静止时,它主要受到一个力:重力。加速度计测量的是“比力”,即除重力外所有外力造成的加速度。但在静止时,这个外力为零,加速度计测量到的实际上是重力加速度在它自身坐标系各轴上的分量。

想象加速度计内部有一个微小的“质量块”,通过弹簧悬挂。当设备倾斜时,重力会以不同的角度拉扯这个质量块,从而在X、Y、Z轴上产生不同的分力。这就是加速度计读数的来源。

关键点在于:当设备静止时,三轴加速度的矢量和的大小,理论上应等于当地的重力加速度g(约9.8 m/s²)。我们可以利用这个特性。假设我们读取到的原始加速度值为 (ax, ay, az)。

那么,设备相对于水平面的俯仰角(Pitch,绕Y轴旋转)和横滚角(Roll,绕X轴旋转)可以通过以下三角函数关系估算:

  • 俯仰角 Pitch (θ)pitch = atan2(-ax, sqrt(ay*ay + az*az))
  • 横滚角 Roll (φ)roll = atan2(ay, az)

这里用到了atan2(y, x)函数,而不是简单的atan(y/x)atan2能正确处理所有象限的角度,返回一个介于 -π 到 π(或 -180° 到 180°)之间的值,这对于全角度测量至关重要。

为什么公式里有负号?这取决于你定义的坐标系方向。在常见的“东北天”坐标系或行空板/Micro:bit的默认坐标系中,Z轴向上为正。当设备前倾(低头)时,X轴的正向加速度分量会增加(重力在X轴正向的分量变大),为了得到“前倾为正角”的直观结果,需要在公式中对ax取负。这是一个必须注意的坐标系约定问题。

至于偏航角(Yaw,绕Z轴旋转),单纯依靠加速度计是无法求得的。因为无论设备如何绕垂直轴旋转,重力在各轴上的分量是不变的。这就好比一个静止的摆锤,无法告诉你东西南北。要获得偏航角,必须引入磁力计(电子罗盘)或陀螺仪进行数据融合。

注意:上述公式是“基于重力矢量”的估算方法。它的优点是计算简单,在设备静止或缓慢移动时非常准确。但缺点也很明显:一旦设备本身存在线性加速度(比如小车突然加速),加速度计测量到的就不再是纯粹的重力分量,计算结果会包含巨大的误差。这就是所谓的“加速度计对运动敏感”。

3. 硬件实操:行空板与Micro:bit的配置与数据读取

虽然原理相同,但行空板(基于Python)和Micro:bit(基于图形化或MicroPython)在具体操作上有所不同。

3.1 行空板(Python)环境搭建与数据读取

行空板通常运行完整的Linux系统,我们可以使用Python及其丰富的库。对于内置的MPU6050等IMU芯片,常用smbus2或专门的驱动库。

首先确保连接行空板的Wi-Fi,通过SSH或Jupyter Notebook进行编程。安装必要的库(如果尚未安装):

pip install smbus2 numpy

以下是读取MPU6050原始数据并计算姿态角的示例代码核心部分:

import smbus2 import time import math # MPU6050默认地址 MPU6050_ADDR = 0x68 # 初始化I2C总线 bus = smbus2.SMBus(1) # 行空板I2C-1通常对应物理引脚3(SDA), 5(SCL) # 唤醒MPU6050 bus.write_byte_data(MPU6050_ADDR, 0x6B, 0) def read_raw_data(addr): # 读取高8位和低8位数据,合并为16位有符号整数 high = bus.read_byte_data(MPU6050_ADDR, addr) low = bus.read_byte_data(MPU6050_ADDR, addr+1) value = (high << 8) | low # 处理负数(补码) if value > 32768: value -= 65536 return value def get_accel_data(): # 读取加速度计原始值,MPU6050默认量程为±2g,灵敏度16384 LSB/g accel_x = read_raw_data(0x3B) / 16384.0 accel_y = read_raw_data(0x3D) / 16384.0 accel_z = read_raw_data(0x3F) / 16384.0 return accel_x, accel_y, accel_z def calculate_angles(ax, ay, az): # 计算俯仰角和横滚角,结果转换为度 pitch = math.atan2(-ax, math.sqrt(ay*ay + az*az)) * 180 / math.pi roll = math.atan2(ay, az) * 180 / math.pi return pitch, roll try: while True: acc_x, acc_y, acc_z = get_accel_data() pitch, roll = calculate_angles(acc_x, acc_y, acc_z) print(f"Accel: X={acc_x:.2f}g, Y={acc_y:.2f}g, Z={acc_z:.2f}g | Pitch={pitch:.1f}°, Roll={roll:.1f}°") time.sleep(0.1) # 100ms读取周期 except KeyboardInterrupt: print("程序结束")

实操心得一:校准与单位。代码中直接将原始数据除以16384得到以重力加速度g为单位的数值。但在实际使用前,最好做一个简单的静态校准:将板子水平静止放置,读取100个样本,计算ax和ay的均值(理论上应为0),az的均值(理论上应为1g)。将后续读数减去这些零偏,能有效消除传感器固有的微小偏差。

3.2 Micro:bit(MakeCode/JavaScript)图形化与代码实现

Micro:bit的操作更加简单直观,特别适合教育和快速原型开发。

图形化(Blocks)方法

  1. 在MakeCode编辑器中,使用输入类别下的加速度(mg) xyz积木块,可以直接读取以毫伽(milli-g,千分之一重力加速度)为单位的加速度值。
  2. 利用数学类别中的atan2sqrt等积木块,搭建计算公式。
  3. 通过基本类别中的显示数字显示字符串在LED点阵上显示角度,或者通过串行类别输出到电脑查看。

JavaScript/Text代码方法(在MakeCode中切换到JavaScript视图):

basic.forever(function () { // 读取加速度,单位是毫伽。1g = 1000mg。 let ax = input.acceleration(Dimension.X) / 1000 let ay = input.acceleration(Dimension.Y) / 1000 let az = input.acceleration(Dimension.Z) / 1000 // 计算俯仰角和横滚角(度) let pitch = Math.atan2(-ax, Math.sqrt(ay * ay + az * az)) * 180 / Math.PI let roll = Math.atan2(ay, az) * 180 / Math.PI // 串口输出,方便在控制台绘图或记录 serial.writeValue("Pitch", pitch) serial.writeValue("Roll", roll) // 在点阵上简单显示俯仰角(取整) basic.showNumber(Math.round(pitch)) basic.pause(200) // 暂停200ms避免刷新过快 })

实操心得二:Micro:bit的坐标系。务必查阅Micro:bit的官方文档确认其坐标系。通常,正面朝上时:X轴左右(右为正),Y轴前后(前为正),Z轴上下(上为正)。这与公式中的假设一致。如果你把Micro:bit竖起来用,坐标系方向就变了,公式可能需要调整。

4. 进阶挑战:互补滤波与姿态融合初探

如前所述,纯加速度计方案在运动时失效,而陀螺仪(测量角速度)虽然对运动不敏感,但存在积分漂移(误差会随时间累积)。因此,在实际应用中,如自平衡车、无人机,都会采用“传感器融合”算法,结合加速度计和陀螺仪的优点。

最经典、最易实现的算法之一是互补滤波。其思想非常直观:利用高通滤波器滤除加速度计数据中的高频噪声(即运动干扰),利用低通滤波器滤除陀螺仪数据中的低频噪声(即漂移),然后将两者融合。

以下是互补滤波在行空板上的一个简化实现思路(假设已能读取陀螺仪数据gx, gy, gz,单位为度/秒):

# 初始化角度(用加速度计估算) pitch_acc, roll_acc = calculate_angles(ax, ay, az) pitch, roll = pitch_acc, roll_acc # 互补滤波系数(通常取0.98左右,需要根据实际情况调整) alpha = 0.98 dt = 0.01 # 采样周期,10ms while True: # 1. 读取当前加速度计和陀螺仪数据 ax, ay, az = get_accel_data() gx, gy, gz = get_gyro_data() # 假设已实现 # 2. 用加速度计估算瞬时角度(低频可靠,高频不可靠) pitch_acc = math.atan2(-ax, math.sqrt(ay*ay + az*az)) * 180 / math.pi roll_acc = math.atan2(ay, az) * 180 / math.pi # 3. 用陀螺仪积分计算角度变化(高频可靠,低频会漂移) # 注意:陀螺仪测得的是绕其自身坐标系的角速度,需要转换。 # 简单情况下,可以近似认为: pitch_gyro = pitch + gx * dt # 绕X轴旋转影响Pitch roll_gyro = roll + gy * dt # 绕Y轴旋转影响Roll # 4. 互补滤波融合 pitch = alpha * pitch_gyro + (1 - alpha) * pitch_acc roll = alpha * roll_gyro + (1 - alpha) * roll_acc print(f"Fused: Pitch={pitch:.1f}°, Roll={roll:.1f}°") time.sleep(dt)

为什么是pitch = pitch + gx * dt这是一个常见的理解误区。陀螺仪输出的角速度是绕其传感器本体坐标系各轴的旋转速率。当设备姿态变化时,本体坐标系和世界坐标系的关系也在变。在俯仰角和横滚角较小的情况下,可以近似认为绕本体X轴的旋转(gx)主要引起俯仰角(Pitch)变化,绕本体Y轴的旋转(gy)主要引起横滚角(Roll)变化。但在大角度时,这种对应关系会变得复杂,涉及到旋转顺序问题,这就引出了“万向节锁”和更复杂的融合算法(如卡尔曼滤波、Mahony滤波、Madgwick滤波)。

注意:这个简化互补滤波示例仅用于说明原理。在实际工程中,需要处理坐标系转换、积分误差、滤波系数调参等诸多问题。对于Micro:bit,由于其内置传感器和计算能力限制,实现复杂的实时融合可能比较吃力,但对于学习概念和进行慢速实验已经足够。

5. 应用场景与项目构思

掌握了倾斜方位计算,我们可以玩出很多花样:

  1. 姿态控制游戏:用行空板做一个“平衡球”游戏,通过倾斜板子控制屏幕上的小球移动,避开障碍。Pitch和Roll角直接映射为游戏手柄的两个摇杆。
  2. 水平仪与倾角仪:这是最直接的应用。用Micro:bit的LED点阵模拟一个气泡水平仪,或者用行空板的大屏幕显示更精确的数字和图形化水平仪。
  3. 智能小车姿态监控:在小车上安装行空板,实时监控车体在越野时的俯仰和侧倾角度,超过安全阈值时报警或自动减速。
  4. 相机云台增稳:虽然性能有限,但作为原理验证,可以用舵机搭建一个两轴云台,使用行空板计算出的姿态角作为反馈,尝试抵消手持的抖动。
  5. 互动艺术装置:将计算出的倾斜角度映射到灯光颜色、声音频率或投影图像的变化上,制作一个随着观众互动而改变的物理界面。

一个具体的Micro:bit项目示例——简易数字水平仪

  • 目标:在MakeCode的模拟器中或实际Micro:bit上,用5x5的LED点阵显示当前横滚角。
  • 逻辑
    • 计算Roll角(范围-90°到90°)。
    • 将-90°映射到最左边一列LED(x=0),90°映射到最右边一列LED(x=4)。
    • 在映射得到的X坐标上,点亮中间一行(y=2)的LED,模拟气泡。
    • 为了更明显,可以同时点亮气泡上下各一个LED(y=1, y=3)。
  • 代码片段(MakeCode JavaScript):
    basic.forever(function () { let ay = input.acceleration(Dimension.Y) / 1000 let az = input.acceleration(Dimension.Z) / 1000 let roll = Math.atan2(ay, az) * 180 / Math.PI // 将角度映射到0-4的X坐标 // 角度范围-90到90,映射到0-4 let x = Math.map(roll, -90, 90, 0, 4) x = Math.constrain(Math.round(x), 0, 4) // 约束并取整 basic.clearScreen() // 在(x,2)位置及上下点亮LED,模拟气泡 led.plot(x, 1) led.plot(x, 2) led.plot(x, 3) basic.pause(100) })

6. 避坑指南与调试技巧

在实际操作中,你肯定会遇到各种问题。以下是我踩过的一些坑和总结的技巧:

坑1:角度跳动剧烈,数据不稳定。

  • 可能原因:传感器噪声、代码采样率过高或未滤波、设备本身存在微小振动。
  • 解决思路
    • 软件滤波:对读取到的原始加速度数据或计算出的角度进行低通滤波。最简单的是移动平均滤波,例如取最近10次读数的平均值。
    # 简单的移动平均滤波示例(角度值) angle_history = [] window_size = 10 def low_pass_filter(new_value): angle_history.append(new_value) if len(angle_history) > window_size: angle_history.pop(0) return sum(angle_history) / len(angle_history) filtered_pitch = low_pass_filter(pitch)
    • 调整采样率:过高的采样率可能引入更多高频噪声。根据应用需要,将采样间隔调整到50-100ms可能比10ms更稳定。
    • 硬件固定:确保设备稳定放置,避免在风扇、电机旁等有振动源的环境中使用。

坑2:静止时角度不为零,存在固定偏差。

  • 可能原因:传感器未校准,或放置的平面本身不水平。
  • 解决思路:执行一次静态校准。将设备尽可能水平放置,运行一段校准程序,记录下此时加速度计三个轴的读数 (ax0, ay0, az0)。在后续计算中,将每次的读数减去这个零偏:ax_calibrated = ax - ax0。对于水平基准,可以认为此时俯仰和横滚应为0,利用这个关系反推零偏更准确。

坑3:当设备某个面朝上时,角度计算突然出错或跳变。

  • 可能原因:遇到了数学计算中的奇点,例如当设备竖直(Z轴水平)时,用于计算Roll角的atan2(ay, az)公式中,az可能接近0,导致计算结果不稳定或溢出。更根本的问题是“万向节锁”。
  • 理解万向节锁:当俯仰角Pitch接近±90°时,横滚角Roll和偏航角Yaw的旋转轴会重合,失去一个自由度,导致姿态解算奇异。这是用欧拉角表示姿态的固有缺陷。
  • 应对策略:对于大部分地面应用(倾斜角通常在±60°以内),可以忽略。如果需要全姿态测量,需要考虑使用四元数进行姿态表示和更新,这超出了本文范围,但它是解决万向节锁和进行高效传感器融合(如Mahony, Madgwick滤波)的基础。

调试技巧:可视化是关键

  • 串口绘图:无论是行空板还是Micro:bit,都尽量将原始数据(ax, ay, az)和计算后的角度(pitch, roll)通过串口发送到电脑。使用像Arduino IDE的串口绘图器、CoolTerm、或者Python的Matplotlib库,实时绘制曲线。观察当你倾斜设备时,哪些曲线如何变化,这与你的理论预期是否一致,是排查问题最快的方法。
  • 分步验证:先单独验证加速度计读数是否合理(平放时Z≈1g, X,Y≈0;竖直时对应轴≈1g)。再验证角度计算,用手持方式缓慢地将设备倾斜到已知角度(如靠在书本形成的30度斜坡上),看计算结果是否匹配。

从读取三个原始数字,到理解它们背后的重力分量,再到利用三角函数解算出直观的倾斜角度,这个过程本身就是一个将物理感知、数学建模和编程实现相结合的绝佳练习。行空板和Micro:bit降低了硬件门槛,让我们能专注于算法和逻辑。虽然纯加速度计方案有局限,但它为我们打开了惯性导航世界的大门,理解了它,再去学习融合陀螺仪、磁力计的更复杂算法,就会知其然也知其所以然。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询