🚀 前言

你还在用只有枯燥数字的串口助手看数据吗?
作为嵌入式工程师,有时候我们需要更直观的方式来调试传感器。

今天给大家分享一个只需 **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}")

💣 踩坑记录 (新手必看)

在调试过程中,我遇到了几个大坑,分享给大家避免走弯路:

  1. 数据发了但没反应?

    • 原因: STM32 发送数据时忘记加 \n (换行符)

    • 解决: Python 的 readline() 必须读到换行符才肯罢休,一定要写成 sprintf(..., "...\n", ...)

  2. 串口怎么都连不上?

    • 原因: 经典的 RX 和 TX 接反了

    • 解决: 记住口诀:单片机的嘴巴 (TX) 接电脑的耳朵 (RX)。如果灯不闪,先调换这两根线试试。

  3. VPython 报错?

    • 原因: PyCharm 虚拟环境问题。

    • 解决: 确保在 PyCharm 底部的 Terminal 里安装库,或者直接在 Interpreter 设置里添加。

更多推荐