一、所选主题和项目介绍
1.1 项目背景
在航空模拟训练、无人机姿态监测和体感交互控制等应用场景中,实时获取和可视化展示物体姿态信息是一个重要需求。传统的姿态监测系统往往需要昂贵的专用设备,且数据显示方式单一,缺乏直观的三维可视化交互体验。
本项目使用M5StickC PLUS2开发板(内置MPU6886六轴姿态传感器),实现一款高性价比的飞机姿态监测节点。通过互补滤波算法实时解算横滚角(Roll)、俯仰角(Pitch)和偏航角(Yaw),在M5StickC PLUS2的1.14英寸135×240 TFT竖屏上绘制人工地平仪,同时通过MQTT协议将姿态数据上报到树莓派Web服务器,在浏览器中通过Three.js 3D引擎实现飞机模型的实时姿态同步,为用户提供沉浸式的姿态监测体验。
1.2 项目目标
本项目使用M5StickC PLUS2 (SKU: K016-H2) 开发板,配合内置MPU6886六轴惯性传感器,实现一款智能飞机姿态监测与三维可视化系统,主要功能如下:
- 姿态数据采集:通过MPU6886六轴IMU传感器采集加速度和角速度数据
- 实时姿态解算:采用互补滤波算法,实时计算横滚角、俯仰角和偏航角
- M5StickC端姿态显示:1.14英寸TFT竖屏绘制人工地平仪,实时显示姿态坐标图像
- MQTT数据上报:通过MQTT协议将姿态数据实时上报至树莓派服务器
- Web三维可视化:浏览器中通过Three.js 3D引擎显示飞机模型,姿态实时同步
- Web端姿态显示:网页中显示姿态坐标数值和人工地平仪
- 一键归零:M5StickC PLUS2按键一键复位偏航角
1.3 创新点
- 互补滤波姿态解算:加速度计与陀螺仪数据融合,实现高精度姿态解算,有效抑制陀螺仪漂移
- M5StickC端人工地平仪:在135×240像素的竖屏上绘制旋转人工地平仪,视觉效果直观
- Web端3D姿态同步:Three.js 3D引擎构建飞机模型,姿态数据通过WebSocket实时推送到浏览器
- 双端姿态显示:M5StickC屏幕和Web页面同时显示姿态坐标图像,双重可视化
- 实时MQTT+WebSocket架构:端到端延迟低于200ms,流畅的实时姿态跟踪体验
- 一键复位:M5StickC PLUS2板载按键可一键复位偏航角,方便校准
二、硬件介绍
2.1 主控设备
M5StickC PLUS2 (SKU: K016-H2) — 基于ESP32-PICO-V3-02的高度集成物联网可编程控制器,内置MPU6886六轴姿态传感器,配备1.14英寸TFT竖屏。
参数 | 规格 |
|---|---|
主控芯片 | ESP32-PICO-V3-02,双核240MHz |
Flash | 16MB |
PSRAM | 8MB |
显示屏 | 1.14英寸 ST7789V,135×240 竖屏 |
六轴传感器 | MPU6886 (加速度+陀螺仪) |
按键 | 板载按键BtnA (GPIO 0) |
电池 | 内置锂电池,支持USB充电 |
尺寸 | 48.2 × 25.0 × 13.8mm |
2.2 传感器模块
MPU6886六轴惯性传感器 — 集成3轴加速度计和3轴陀螺仪的高精度IMU,通过内部I2C与主控通信。
参数 | 规格 |
|---|---|
加速度计量程 | ±2g / ±4g / ±8g / ±16g (可配置) |
加速度计精度 | 0.05% |
陀螺仪量程 | ±250°/s / ±500°/s / ±1000°/s / ±2000°/s (可配置) |
陀螺仪精度 | 0.05°/s |
通信接口 | 内部I2C (地址0x68) |
数据输出频率 | 最高4000Hz (加速度计) / 最高4000Hz (陀螺仪) |
2.3 树莓派设备
树莓派3B+ (Raspberry Pi 3 Model B+)
参数 | 规格 |
|---|---|
CPU | BCM2837B0, Cortex-A53 1.4GHz (四核) |
RAM | 1GB LPDDR2 |
网络 | 2.4/5GHz WiFi, 千兆以太网 |
存储 | MicroSD卡 (推荐16GB以上) |
核心服务组件
组件 | 用途 | 运行方式 |
|---|---|---|
Mosquitto | MQTT消息代理 | systemd服务 |
| Web服务器+MQTT订阅+WebSocket推送 | Python服务 |
Flask | HTTP网页服务器 | Python模块 |
Flask-SocketIO | WebSocket实时通信 | Python模块 |
Three.js | 3D引擎 (浏览器端) | JavaScript库 |
2.4 硬件连接
M5StickC PLUS2为一体化设计,所有传感器已集成在板载,无需外接传感器模块。仅需USB Type-C供电即可运行。
M5StickC接口 | 连接设备 | 功能 |
|---|---|---|
USB Type-C | 5V电源/固件下载 | 供电与编程 |
板载MPU6886 | 内部I2C | 六轴姿态数据采集 |
板载ST7789V | SPI | 135x240 TFT显示 |
板载按键BtnA | GPIO 0 | 用户输入(偏航角归零) |
板载LED | GPIO 10 | 状态指示 |
6轴姿态传感器 | 内置I2C | 加速度+陀螺仪 |
三、方案框图和项目设计思路
3.1 系统架构

3.2 设备启动流程

3.3 姿态解算算法
采用互补滤波算法融合加速度计和陀螺仪数据:
- 加速度计计算姿态:
- Roll_acc = atan2(accel_y, accel_z) × 180/π
- Pitch_acc = atan2(-accel_x, √(accel_y² + accel_z²)) × 180/π
- 互补滤波融合:
- Roll = α × (Roll + gyro_x × dt) + (1-α) × Roll_acc
- Pitch = α × (Pitch + gyro_y × dt) + (1-α) × Pitch_acc
- 滤波系数 α = 0.96,短时信任陀螺仪,长时由加速度计校正
- 偏航角积分:
- Yaw += gyro_z × dt (陀螺仪积分,可通过按键归零)
四、原理图和PCB展示及介绍
4.1 设计过程
通过KiCad完成了电路的原理图绘制和PCB版图设计。在实际动手环节,作为电路设计的新手,这次从零开始画原理图、布PCB的经历让我学到了很多。设计过程中得到了老师的指导和反馈,谢谢老师。
4.2 硬件连接原理图
本项目设计了专用的I2C适配板(PCB),用于连接M5StickC PLUS2主控与外围接口增加飞机操作。

4.3 PCB布局展示

五、软件流程和关键代码介绍
5.1 主程序结构
"""
File: airplane_attitude.py
硬件: M5StickC PLUS2 (K016-H2) + MPU6886 IMU
功能特性:
1. MPU6886六轴IMU姿态数据采集
2. 互补滤波实时姿态解算 (Roll/Pitch/Yaw)
3. 1.14" TFT 135x240竖屏人工地平仪显示
4. MQTT数据上报到树莓派
5. 按键一键偏航角归零
开发平台: UiFlow2 (MicroPython)
Web IDE: https://uiflow2.m5stack.com/
"""
5.2 姿态解算模块 (UiFlow2 MicroPython)
def update_attitude():
global _roll, _pitch, _yaw, _last_time
import time
from M5 import Imu
now = time.ticks_ms()
dt = time.ticks_diff(now, _last_time) / 1000.0
if dt <= 0.0 or dt > 0.1:
dt = 0.01
_last_time = now
accel = Imu.getAccel()
gyro = Imu.getGyro()
ax, ay, az = accel[0], accel[1], accel[2]
gx, gy, gz = gyro[0], gyro[1], gyro[2]
accel_roll = math.atan2(ay, az) * _RAD_TO_DEG
accel_pitch = math.atan2(-ax,
math.sqrt(ay * ay + az * az)) * _RAD_TO_DEG
gx_dps = gx * _RAD_TO_DEG
gy_dps = gy * _RAD_TO_DEG
gz_dps = gz * _RAD_TO_DEG
_roll = 0.96 * (_roll + gx_dps * dt) + 0.04 * accel_roll
_pitch = 0.96 * (_pitch + gy_dps * dt) + 0.04 * accel_pitch
_yaw += gz_dps * dt
5.3 人工地平仪显示模块 (UiFlow2 MicroPython)
# M5StickC PLUS2 竖屏显示 (135x240)
# 全屏地平仪每80ms刷新一次,文字数值每10ms更新
# 大幅减少屏幕闪烁
_DISPLAY_INTERVAL = 8 # 每8次循环全屏刷新一次
_display_counter = 0
def _draw_scanline_horizon(roll, pitch):
roll_rad = math.radians(roll)
cos_r = math.cos(roll_rad)
sin_r = math.sin(roll_rad)
if abs(sin_r) < 0.001:
sin_r = 0.001
Lcd.fillScreen(_SKY_COLOR)
for y in range(240): # 竖屏240行
y_local = y - 85 + int(pitch)
x_horizon = 67 - (y_local * cos_r) / sin_r
if cos_r >= 0:
x_start = int(max(0, min(135, x_horizon)))
if x_start < 135:
Lcd.fillRect(x_start, y, 135 - x_start, 1, _GROUND_COLOR)
else:
x_end = int(max(0, min(135, x_horizon)))
if x_end > 0:
Lcd.fillRect(0, y, x_end, 1, _GROUND_COLOR)
def update_display(roll, pitch, yaw):
global _display_counter
_display_counter += 1
if _display_counter >= _DISPLAY_INTERVAL:
_draw_scanline_horizon(roll, pitch)
_draw_horizon_line(roll)
_draw_pitch_ladder(roll, pitch)
_draw_aircraft_symbol()
_display_counter = 0
_draw_attitude_text(roll, pitch, yaw)
_draw_status_line()
5.4 MQTT数据上报 (UiFlow2 MicroPython)
from umqtt import MQTTClient
_MQTT_TOPIC = b"fastbond4/m5stick_airplane_attitude/attitude"
def send_attitude_mqtt(roll, pitch, yaw):
if not _mqtt_connected or _mqtt_client is None:
return
payload = '{{"roll":{:.2f},"pitch":{:.2f},"yaw":{:.2f}}}'.format(
roll, pitch, yaw)
try:
_mqtt_client.publish(_MQTT_TOPIC, payload)
except Exception as e:
print("[MQTT] Publish failed:", e)
_mqtt_connected = False
# 新增:MQTT代理可达性检测,避免连接阻塞
def _test_broker(host, port, timeout_ms=3000):
import usocket
addr = usocket.getaddrinfo(host, port)[0][4]
s = usocket.socket()
s.settimeout(timeout_ms / 1000)
s.connect(addr)
s.close()
return True
5.5 树莓派Web服务器
# MQTT消息回调 - 接收姿态数据并推送到WebSocket客户端
def on_mqtt_message(client, userdata, msg):
global _current_attitude
payload = msg.payload.decode('utf-8')
data = json.loads(payload)
if "roll" in data and "pitch" in data and "yaw" in data:
with _attitude_lock:
_current_attitude = data
# 通过WebSocket推送到所有连接的浏览器
socketio.emit('attitude_update', data)
@app.route('/')
def index():
return render_template('index.html')
5.6 Three.js 3D飞机模型
function createAirplane() {
airplane = new THREE.Group();
// 机身: 圆柱体
const fuselage = new THREE.Mesh(
new THREE.CylinderGeometry(0.25, 0.15, 1.6, 12),
bodyMat
);
fuselage.rotation.x = Math.PI / 2;
// 机翼: 长方体
const mainWing = new THREE.Mesh(
new THREE.BoxGeometry(1.6, 0.04, 0.4),
wingMat
);
// 尾翼、垂直尾翼、发动机...
// 姿态更新
airplane.rotation.order = 'YXZ';
airplane.rotation.x = pitchRad;
airplane.rotation.y = yawRad;
airplane.rotation.z = rollRad;
}
六、硬件功能展示图及说明
6.1 M5StickC PLUS2显示界面


6.2 网页显示界面


6.3 树莓派

6.4 树莓派端日志

七、设计中遇到的难题和解决方法
7.1 IMU数据噪声滤波
问题描述:MPU6886加速度计原始数据存在高频噪声,导致横滚角和俯仰角波动明显,影响显示效果。
解决方案:
- 采用互补滤波算法融合加速度计和陀螺仪数据
- 滤波系数α=0.96,短时信任陀螺仪动态响应,长时由加速度计校正漂移
- 时间步长dt限幅保护,防止异常值导致滤波发散
# 互补滤波核心公式
_roll = 0.96 * (_roll + gx_dps * dt) + 0.04 * accel_roll
_pitch = 0.96 * (_pitch + gy_dps * dt) + 0.04 * accel_pitch
7.2 陀螺仪零偏漂移
问题描述:陀螺仪存在零偏误差,长时间运行后偏航角会逐渐漂移。
解决方案:
- 静态时采集陀螺仪零偏值进行校准
- 按键一键归零偏航角
- 互补滤波只对Roll和Pitch进行加速度计校正,Yaw通过按键手动复位
if BtnA.wasPressed():
reset_yaw() # 偏航角归零
print("[Button] Yaw reset")
7.3 135x240竖屏人工地平仪绘制与闪烁优化
问题描述:M5StickC PLUS2的1.14英寸135×240竖屏相比AtomS3R的128×128小方屏,扫描行数从128行增加到240行,每次全屏重绘耗时更长(约80ms),导致画面严重闪烁。
解决方案:
- 分频刷新策略:全屏地平仪每80ms刷新一次(8个循环),文字数值每10ms更新一次
- 文字更新只改变LCD光标位置的文本内容,不触发全屏重绘
- 地平线、俯仰梯和飞机符号随全屏刷新一起绘制,减少绘制次数
# 分频刷新核心逻辑
_DISPLAY_INTERVAL = 8 # 全屏刷新间隔
_display_counter = 0
def update_display(roll, pitch, yaw):
global _display_counter
_display_counter += 1
if _display_counter >= _DISPLAY_INTERVAL:
# 每80ms全屏重绘地平仪(避免闪烁)
_draw_scanline_horizon(roll, pitch)
_draw_horizon_line(roll)
_draw_pitch_ladder(roll, pitch)
_draw_aircraft_symbol()
_display_counter = 0
# 每10ms更新姿态数值文字(文字更新快,不闪烁)
_draw_attitude_text(roll, pitch, yaw)
_draw_status_line()
7.4 MQTT连接阻塞问题
问题描述:当MQTT代理不可达时,MQTTClient.connect()会长时间阻塞,导致主循环无法启动。
解决方案:
- 新增
_test_broker()函数,使用3秒超时Socket探测代理可达性 - 只有探测成功后才创建MQTT连接
- 代理不可达时优雅跳过,主循环正常启动
def _test_broker(host, port, timeout_ms=3000):
import usocket
addr = usocket.getaddrinfo(host, port)[0][4]
s = usocket.socket()
s.settimeout(timeout_ms / 1000)
s.connect(addr)
s.close()
return True
def connect_mqtt(...):
if not _test_broker(_MQTT_BROKER, _MQTT_PORT):
print("[MQTT] Broker unreachable, skipping")
return False
# 才执行MQTTClient.connect()
7.5 MQTT与WebSocket实时桥接
问题描述:需要将MQTT接收到的姿态数据实时推送到多个浏览器客户端,确保低延迟同步。
解决方案:
- 树莓派端使用Flask-SocketIO实现WebSocket服务
- MQTT消息回调中直接通过SocketIO向所有客户端广播姿态数据
- 线程安全锁保护共享数据,避免竞态条件
def on_mqtt_message(client, userdata, msg):
data = json.loads(msg.payload)
with _attitude_lock:
_current_attitude = data
socketio.emit('attitude_update', data) # WebSocket广播
7.6 Three.js 3D飞机模型构建
问题描述:需要在浏览器中构建一个可被姿态数据驱动的3D飞机模型,要求模型结构完整且旋转顺序正确。
解决方案:
- 使用Three.js基本几何体组合构建飞机(机身、机翼、尾翼、发动机等)
- 设置rotation.order为YXZ,与航空标准姿态角旋转顺序一致
- 添加人工地平仪背景纹理、网格辅助线和多方向光源增强视觉效果
airplane.rotation.order = 'YXZ';
airplane.rotation.x = pitchRad; // 俯仰
airplane.rotation.y = yawRad; // 偏航
airplane.rotation.z = rollRad; // 横滚
八、心得体会
8.1 项目收获
- IMU姿态解算:深入理解了加速度计和陀螺仪的工作原理,掌握了互补滤波姿态解算算法
- 嵌入式显示技术:在135×240竖屏上绘制旋转人工地平仪的技术,理解了坐标旋转和像素级渲染
- Web 3D可视化:掌握了Three.js 3D引擎构建三维模型和实时姿态同步的技术
- 实时数据管道:实践了MQTT + WebSocket的端到端实时数据推送架构
- 双端同步显示:实现了设备端和Web端同步显示姿态坐标图像的完整方案
- 嵌入式系统设计:熟悉了UiFlow2开发环境、MicroPython编程和M5Burner固件烧录工具的使用
8.2 技术亮点
- 互补滤波高精度解算:加速度计和陀螺仪融合,姿态解算精度高、响应快
- 竖屏人工地平仪:135×240像素竖屏上实现带旋转的人工地平仪,视觉效果出色
- 分频刷新防闪烁:全屏地平仪80ms刷新、文字10ms更新的分频策略,彻底解决竖屏闪烁问题
- MQTT非阻塞连接:Socket探测+超时机制,代理不可达时优雅跳过
- Web端3D实时同步:Three.js 3D飞机模型毫秒级跟随M5StickC姿态变化
- 双端姿态显示:设备端LCD和Web页面同时显示姿态坐标图像
- 端到端低延迟:MQTT + WebSocket架构,端到端延迟低于200ms
- 一键偏航角归零:M5StickC按键便捷复位偏航角,提升使用体验
8.3 改进建议
- 地磁传感器融合:增加地磁传感器修正偏航角漂移,实现完整9轴姿态解算
- 扩展ESP-NOW无线通信:增加ESP-NOW低延迟局域网通信,减少对WiFi路由器的依赖
- 多节点组网:支持多个M5StickC姿态节点同时接入,实现多机姿态同步显示
- 数据记录回放:在树莓派SQLite数据库中存储姿态数据,支持历史姿态回放
- 体感交互游戏:利用M5StickC姿态控制网页游戏中的物体,实现体感交互
- 移动端适配:优化Web页面在手机和平板上的显示效果
- 姿态数据滤波:引入卡尔曼滤波进一步提升姿态解算精度
- OTA固件升级:支持远程无线固件更新
- 自定义PID参数:Web界面允许用户调节互补滤波系数
创意方向关联
本项目的技术创新为以下创意方向提供了新的思路:
1. 人工智能在嵌入式系统中的应用
本项目的姿态解算技术为AI应用提供了基础:
- 姿态行为识别:机器学习模型通过学习姿态变化模式,识别用户的动作意图
- 异常姿态检测:AI模型检测异常姿态,用于跌倒检测、设备故障预警
- 运动模式分析:通过持续的姿态数据分析用户运动模式
2. 体感交互游戏
实时姿态追踪是体感交互的核心技术:
- 飞行模拟器:M5StickC作为飞行摇杆的体感控制器
- VR/AR交互:低延迟姿态数据用于VR头显和AR设备的交互控制
- 运动健身:监测用户运动姿态,提供实时反馈和矫正
3. 无人机 / 机器人控制
姿态解算是无人机和机器人控制的基础:
- 无人机飞控:IMU姿态数据用于无人机飞行控制系统的姿态反馈
- 机器人平衡:两轮自平衡机器人基于Roll/Pitch数据的PID控制
- 机械臂姿态:多节点姿态传感器用于机械臂关节角度测量
技术迁移价值
本项目开发的技术方案可迁移到:
- AI应用:姿态行为识别、异常检测、运动模式分析
- 体感交互:飞行模拟器、VR/AR控制、运动监测
- 无人机/机器人:飞控系统、平衡控制、机械臂姿态
致谢
感谢 DigiKey 和 电子森林 提供的FastBond4活动支持,本次活动链接:https://www.eetree.cn/page/digikey-fastbond
感谢 Three.js 开源社区提供的优秀3D引擎和开发资源。