5元成本打造桌面级“数字孪生”!STM32 + VPython 实现炫酷 3D 姿态解算 (附源码)
🚀 前言
你还在用只有枯燥数字的串口助手看数据吗?
作为嵌入式工程师,有时候我们需要更直观的方式来调试传感器。
今天给大家分享一个只需 **5元成本** (STM32 + MPU6050) 就能实现的**“桌面级数字孪生”**小项目。我们将使用 **STM32 HAL库** 读取陀螺仪数据,配合电脑端的 **Python (VPython库)**,实现一个实时跟随硬件转动的 3D 模型。
**👇 先看效果图:**


🛠️ 硬件准备
本项目成本极低,适合新手练手 I2C 通信和上位机开发。
1. **主控:** STM32F103C8T6 (Blue Pill)
2. **传感器:** MPU6050 (六轴加速度/陀螺仪)
3. **通信:** USB转TTL 模块 (CH340)
4. **开发环境:** CLion + STM32CubeMX (Keil 也通用)
**接线示意图:**
| MPU6050 | STM32 | 说明 |
| -------------- | --------- | ------ |
| VCC | 3.3V | 供电 |
| GND | GND | 共地 |
| SCL | PB6 | I2C1_SCL |
| SDA | PB7 | I2C1_SDA |
大部分接线与上一篇接线相同,只是加上了MPU6050的接线

💻 下位机开发 (STM32)
我们使用 HAL 库并通过 I2C 读取 MPU6050 的原始数据,简单计算出 Pitch (俯仰角) 和 Roll (横滚角) 后,通过串口发送给电脑。
1. CubeMX 配置
* **I2C1:** 开启 I2C 功能,参数默认 (100kHz)。
* **USART1:** 开启异步模式,波特率 **115200**。
* **printf 重定向:** 记得勾选 MicroLib (Keil) 或配置好 CMake (CLion) 以支持 `sprintf`。
2. 核心代码 (main.c)
这里省略初始化代码,只展示核心逻辑:
/* 核心读取与发送逻辑 */
while (1)
{
// 1. 读取 MPU6050 原始数据 (6字节)
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x3B, 1, i2c_buf, 6, 100);
// 2. 数据合成
Acc_X = (int16_t)(i2c_buf[0] << 8 | i2c_buf[1]);
Acc_Y = (int16_t)(i2c_buf[2] << 8 | i2c_buf[3]);
Acc_Z = (int16_t)(i2c_buf[4] << 8 | i2c_buf[5]);
// 3. 简单的姿态解算 (单位转换 + atan2计算)
Ax = Acc_X / 16384.0;
Ay = Acc_Y / 16384.0;
Az = Acc_Z / 16384.0;
Pitch = atan2(Ay, sqrt(Ax * Ax + Az * Az)) * 57.295;
Roll = atan2(-Ax, Az) * 57.295;
// 4. 发送给 Python (注意格式:带逗号和换行符!)
// 格式: "Pitch,Roll\n"
sprintf(uart_buf, "%.2f,%.2f\n", Pitch, Roll);
HAL_UART_Transmit(&huart1, (uint8_t*)uart_buf, strlen(uart_buf), 100);
HAL_Delay(50); // 刷新率约 20Hz
}
🐍 上位机开发 (Python 3D)
这是本项目的灵魂!我们抛弃 Matplotlib,使用 VPython 库,它能以极少的代码在浏览器中渲染高性能 3D 场景。
1. 环境安装
pip install pyserial vpython
2. 完整 Python 代码
import serial
import time
import math
from vpython import *
# ==========================================
# ⚠️ 再次确认端口号!
MY_PORT = 'COM11'
# ==========================================
print(f"正在尝试连接 {MY_PORT} ...")
try:
ser = serial.Serial(MY_PORT, 115200, timeout=1)
print(f"✅ 成功连接到 {MY_PORT}!")
except Exception as e:
print(f"❌ 连接失败: {e}")
exit()
scene.title = "STM32 MPU6050 3D Display"
scene.width = 800
scene.height = 600
scene.forward = vector(-1, -1, -1)
my_board = box(length=6, height=0.2, width=3, color=color.orange)
pointer = arrow(pos=vector(0, 0, 0), axis=vector(3, 0, 0), color=color.red)
obj = compound([my_board, pointer])
print("开始接收数据...")
while True:
rate(50)
try:
if ser.in_waiting > 0:
# 读取原始数据
raw_line = ser.readline()
# 尝试解码
try:
line = raw_line.decode('utf-8').strip()
except:
print(f"⚠️ 解码失败: {raw_line}")
continue
# 打印收到的内容 (关键步骤!)
print(f"收到数据: [{line}]")
data = line.split(',')
if len(data) == 2:
try:
pitch_angle = float(data[0])
roll_angle = float(data[1])
obj.up = vector(0, 1, 0)
obj.axis = vector(1, 0, 0)
obj.rotate(angle=math.radians(roll_angle), axis=vector(1, 0, 0))
obj.rotate(angle=math.radians(-pitch_angle), axis=vector(0, 0, 1))
except ValueError:
print(f"⚠️ 数据格式错误,无法转成数字: {data}")
else:
print(f"⚠️ 数据长度不对 (需要2个,收到{len(data)}个): {data}")
except Exception as e:
print(f"❌ 未知错误: {e}")
💣 踩坑记录 (新手必看)
在调试过程中,我遇到了几个大坑,分享给大家避免走弯路:
-
数据发了但没反应?
-
原因: STM32 发送数据时忘记加
\n(换行符)。 -
解决: Python 的
readline()必须读到换行符才肯罢休,一定要写成sprintf(..., "...\n", ...)。
-
-
串口怎么都连不上?
-
原因: 经典的 RX 和 TX 接反了。
-
解决: 记住口诀:单片机的嘴巴 (TX) 接电脑的耳朵 (RX)。如果灯不闪,先调换这两根线试试。
-
-
VPython 报错?
-
原因: PyCharm 虚拟环境问题。
-
解决: 确保在 PyCharm 底部的 Terminal 里安装库,或者直接在 Interpreter 设置里添加。
-
更多推荐



所有评论(0)