先让姿态变得可见,再谈机器人控制。

把 N100 IMU、树莓派、Python Web 服务和浏览器 3D 显示串起来,用实物反馈校验坐标系。

N100 IMU 姿态调试器的信息图,展示浏览器、Flask、串口与树莓派之间的数据链路

这篇在系列中的位置

上一篇我们先搭好了 Mac 工作端、树莓派硬件端和 SSH 远程连接。这一篇开始进入第一个真正的小工程: 不碰整机控制,先把一个 IMU 传感器读懂、看见、调顺。

这篇的目标不是让读者一下子学会机器人控制,而是建立一个重要习惯: 复杂系统先拆成小工具,小工具先做到可观测、可验证、可复盘。

为什么先做这个工具

Qmini 的复刻不是一上来就把所有系统接起来。对 IMU 这类传感器,最稳妥的方式是先做一个独立最小工程: 能读到数据、能确认坐标系、能直观看到姿态变化,再把它接入机器人主体。

这次我们选择轮趣 N100 IMU,先做了一个 Python + Flask + Three.js 的 Web 姿态显示器。它运行在树莓派上,Mac 浏览器通过局域网访问。

IMU 是什么

IMU 的英文全称是 Inertial Measurement Unit,中文一般叫“惯性测量单元”。这个名字听起来有点硬核,但它做的事很直观: 帮机器感知“我现在怎么动、怎么歪、朝哪里倾斜”。

你可以把 IMU 想象成一个很小的“身体感觉器官”。人闭上眼睛时,仍然能感觉自己有没有歪、有没有转圈、有没有突然加速,这是因为身体里有平衡感。机器人没有这种天然感觉,所以需要 IMU。

常见 IMU 里通常会有这些传感器:

  • 加速度计: 感知加速度,也能感知重力方向。手机横过来自动旋转屏幕,就和它有关。
  • 陀螺仪: 感知旋转速度。比如设备正在绕某个方向转得快不快。
  • 磁力计: 感知地磁方向,有点像电子指南针。

IMU 能做什么:

  • 判断设备是否水平。
  • 判断设备向前倾、向后仰、向左歪、向右歪。
  • 辅助机器人保持平衡。
  • 辅助无人机、云台、相机防抖、运动相机和手机判断姿态。

你身边很多东西都有类似 IMU 的传感器:

  • 手机和平板: 自动横竖屏、计步、游戏体感控制。
  • 无人机: 判断机身姿态,保持悬停和平稳飞行。
  • 运动相机和云台: 判断画面是否倾斜,用来防抖。
  • 汽车和机器人: 感知转弯、倾斜、震动和姿态变化。
  • VR 手柄和头显: 感知手和头的转动。

我们这次用的 N100,就是一个可以通过 USB 串口输出姿态和传感器数据的小 IMU 模块。

IMU 实际读出来的是什么

我们打开 Web 页面后,会看到一些看起来很“工程”的数据: 加速度、角速度、磁场、姿态角、四元数。它们不是玄学,每一类都有很明确的含义。

加速度计: 感知“被推”和“重力在哪”

加速度计读出来的是加速度,常见单位是 m/s²。我们页面里显示的是:

X 轴加速度
Y 轴加速度
Z 轴加速度

如果你拿着 IMU 静止不动,它其实仍然会读到一个很大的加速度,接近 9.8 m/s²。这不是因为它在跑,而是因为它感受到了重力。

可以这样理解:

  • 静止放在桌面上时,IMU 会知道“重力主要压在哪个轴上”。
  • 把某个轴朝天或朝地时,那个轴的数值会接近 9.8 或 -9.8。
  • 快速晃动 IMU 时,加速度数值会变得更乱,因为这时既有重力,也有你手的运动加速度。

所以加速度计特别适合回答这些问题:

  • 设备现在是不是水平?
  • 哪个方向朝上?哪个方向朝下?
  • 机器人是不是向前倾、向后仰、向左歪、向右歪?

这也是为什么我们最后选择用重力加速度来驱动 3D 模型。它简单、直观,而且非常适合校验俯仰和横滚方向。

陀螺仪: 感知“正在怎么转”

陀螺仪读出来的是角速度,常见单位是 rad/s,也就是“每秒转过多少角度”。页面里显示的是:

X 轴角速度
Y 轴角速度
Z 轴角速度

如果加速度计像是在回答“我现在歪向哪里”,陀螺仪更像是在回答“我正在绕哪个轴旋转,转得有多快”。

举个例子:

  • 你绕 X 轴转动 IMU,X 轴角速度会明显变化。
  • 你绕 Y 轴转动 IMU,Y 轴角速度会明显变化。
  • 你绕 Z 轴转动 IMU,Z 轴角速度会明显变化。

陀螺仪很灵敏,适合捕捉快速转动。但它也有一个问题: 如果只靠陀螺仪长期累加角度,误差会慢慢积累,就像你闭着眼走路,刚开始方向还对,走久了就会偏。

所以实际姿态估计通常会把加速度计、陀螺仪,有时还有磁力计结合起来。

磁力计: 像一个电子指南针

磁力计读的是磁场强度,常见单位可能是 mG。它可以帮助设备判断大致朝向,类似指南针。

但磁力计很容易受环境影响。比如电机、大电流线缆、磁铁、金属结构都会干扰它。所以在机器人里,磁力计可以作为参考,但不能盲目信任。

对我们这个阶段来说,磁力计不是最核心的数据。我们主要用它确认 N100 是否正常输出完整传感器数据。

姿态角: 人类更容易看懂的角度

姿态角通常会写成:

  • Roll: 横滚,可以理解成左右歪。
  • Pitch: 俯仰,可以理解成前后点头。
  • Yaw: 航向,可以理解成在水平面上转头。

它们很适合给人看,因为“倾斜了 10 度”比“四元数某个分量变了 0.03”更直观。

不过姿态角有一个麻烦: 当角度接近某些特殊位置时,会出现数学上的表达问题,常被称为“万向节锁”。你不需要现在深入理解,只要先记住: 姿态角适合人看,但程序内部不一定最喜欢用它。

四元数: 给程序用的姿态表达

四元数看起来像四个数字:

w, x, y, z

第一次看到它会有点吓人,因为它不像角度那样直观。但可以把四元数理解成一种“给程序看的旋转说明书”。

如果姿态角像是在说:

先横滚多少度,再俯仰多少度,再航向多少度

四元数更像是在说:

绕某个空间方向,一次性旋转多少

它的好处是:

  • 适合计算机连续计算姿态。
  • 不容易遇到姿态角那种特殊角度问题。
  • 很适合 3D 图形、机器人和飞行器内部使用。

在我们的项目里,N100 会输出 AHRS 四元数,但为了把坐标系先调清楚,我们暂时没有直接用它驱动 3D 模型,而是先用重力加速度驱动。这样做的好处是: 新手更容易把“我怎么摆放传感器”和“画面怎么转”对应起来。

这些数据怎么用在本文工具里

当前 N100 Web Viewer 的使用方式可以总结成这样:

  • 加速度计: 用来驱动 3D 模型的俯仰和横滚显示。
  • 陀螺仪: 用来观察当前是否正在绕某个轴旋转。
  • 磁力计: 用来确认磁场数据是否正常输出。
  • 姿态角: 用来给人快速观察 Roll、Pitch、Yaw。
  • 四元数: 保留在数据面板里,作为 N100 原始姿态输出,后续可用于更完整的姿态融合和机器人控制。

如果你是第一次调 IMU,建议先盯住加速度和陀螺仪:

  • 慢慢改变摆放姿态,看加速度主分量怎么变。
  • 绕某个轴转动,看对应轴的角速度是否明显变化。
  • 再观察 3D 模型是否和真实动作一致。

这套系统怎么连起来

从读者角度看,最终只是打开浏览器,看见一个 3D 模型跟着真实 N100 动。但背后其实有几层:

流程概览
  1. N100 IMU<br/>输出加速度、角速度、姿态数据

简单说:

  • N100 负责测量。
  • 树莓派负责读取 N100。
  • Python 负责解析数据。
  • Flask 负责把数据变成网页能访问的接口。
  • 浏览器负责显示数字和 3D 模型。

当前成果

  • 树莓派通过 /dev/ttyUSB0 读取 N100,波特率 921600。
  • 后端用 Python 解析 N100 数据帧,并通过 /api/snapshot 提供聚合状态。
  • 前端用 Three.js 显示 N100 结构模型。
  • 页面支持串口选择、波特率配置、连接/断开、设置初始位置。
  • 数值面板保留 N100 串口直读原始数据。
  • 3D 姿态显示使用重力加速度驱动,用于校验俯仰、横滚和坐标轴方向。

NED 和 NWU 是什么

调 IMU 最容易卡住的地方,不是“有没有数据”,而是“这些数据到底朝哪个方向算正”。

坐标系可以理解成一套约定: 我们说 X、Y、Z 三个方向时,到底哪个方向是正方向。

NED 是 North-East-Down:

  • N: North,北,X 轴指向北。
  • E: East,东,Y 轴指向东。
  • D: Down,下,Z 轴指向地面。

NWU 是 North-West-Up:

  • N: North,北,X 轴指向北。
  • W: West,西,Y 轴指向西。
  • U: Up,上,Z 轴指向天空。

可以用一个生活化比喻:

  • NED 像“航空视角”: 飞机常常关心向下高度、俯冲、下降,所以 Z 正方向可以定义为向下。
  • NWU 像“地面机器人视角”: 我们站在地上,更习惯说天空是上,所以 Z 正方向定义为向上。

这两个坐标系都不是错的,它们只是约定不同。真正危险的是混着用: 一会儿按 NED 理解,一会儿按 NWU 理解,3D 模型就会反着转、歪着转,甚至看起来像“传感器坏了”。

N100 手册里提到: 如果使用上位机或系统引脚直接读取数据,参考坐标系是 NED;如果通过 ROS SDK 读取,参考坐标系是 NWU。我们这次是 Python 串口直读,所以最终决定: 数值面板和 API 保留 N100 原始 NED 数据,不擅自改成 NWU。

关键经验

1. 先保留原始数据

N100 手册里有一个容易踩坑的点: 上位机或系统引脚直读数据采用北东地 NED 坐标系,而 ROS SDK 读取数据采用北西天 NWU 坐标系。

我们当前是串口直读,所以最终决定 Web API 和数值面板只显示 N100 原始 NED 数据,不在后端混入临时转换字段。

2. 用实物姿态校验坐标系

只看代码很难把坐标系想清楚。更可靠的方法是把 IMU 摆到几个明确姿态,再看加速度主分量落在哪个轴上。

最终确认的 N100 无外壳模块方向:

  • 正面视角 USB 在下时,+X 向上。
  • +Y 向右。
  • +Z 指向板内。
  • 模块正面朝上平放时,+Z 指向地面。

3. 把模糊反馈变成测试用例

“绕 X 轴反了”这类反馈还不够精确,因为观察方向会影响顺时针/逆时针判断。后来我们把问题改写成明确用例:

当原始 Y 从 0 增至约 +9.8 时,图形应沿 X 轴、面对 X- 逆时针旋转。

这个用例可以直接转成向量模拟,并最终固定了前端显示层的旋转方向。

4. 实时页面要避免请求堆积

一开始前端同时高频请求多个 API,浏览器出现过 net::ERR_INSUFFICIENT_RESOURCES。后来改成单一 /api/snapshot 聚合接口,并加上请求防重入。

3D 模型卡顿也不是单纯的 WebGL 问题。我们把数据更新和渲染分开: 数据层只更新目标四元数,渲染层在 requestAnimationFrame 中用 slerp 平滑追踪,观感明显更顺。

我们是怎么和 Agent 协作完成的

如果你也想用自己的 AI Agent 复刻类似工具,不要一开始就说“帮我做一个完整机器人系统”。更好的沟通方式是把目标拆小,并不断把真实观察反馈给 Agent。

这次我们的协作大致是这样:

  1. 先定义最小目标 “我想用 Python 读取 N100 IMU 数据,并用 Web 页面显示出来。”

  2. 补充硬件事实 “树莓派地址是 raspberrypi.local,N100 插在 USB 上,可能是 /dev/ttyUSB0,波特率用 921600。”

  3. 先要需求文档,再写代码 先让 Agent 生成需求和方案,确认功能范围: 手动选择端口、手动设置波特率、Flask/FastAPI 选择、Three.js 3D 显示、第一版不做 CSV。

  4. 让 Agent 做最小工程 不急着接 Qmini 主工程,先做 n100-web-viewer 这样一个独立项目。

  5. 用真实现象反馈问题 比如:

    • “网页打不开。”
    • “只能看到加载页面。”
    • “水平放桌面时 Z 是负数。”
    • “绕 X 轴方向反了。”
    • “当 Y 从 0 到 9.8 时,面对 X- 应该逆时针。”
  6. 把模糊反馈改成可测试语句 “不对”当然可以说,但更有效的是描述清楚:

    • 我现在怎么摆放 IMU。
    • 哪个轴朝天。
    • 数值大概是多少。
    • 画面应该怎么转。
    • 实际画面怎么转。
  7. 要求 Agent 保留文档 每次关键决策都写入项目日志和方案文档。后面你忘了为什么这么改,可以回头复盘。

可以直接复制下面这种提示词给自己的 Agent:

我正在复刻 Qmini 机器人,先做一个 N100 IMU 最小调试工具。
请先不要接入完整机器人系统,而是创建一个独立 Python Web 工程。
要求:
1. 树莓派读取 N100 串口数据。
2. Web 页面显示加速度、角速度、姿态信息。
3. 用 Three.js 显示一个 3D 模型。
4. 支持手动选择串口和波特率。
5. 保留原始数据,不要随意改坐标系。
6. 每次坐标系修正都写入项目日志。
我会用真实摆放姿态和传感器读数来反馈你继续调整。

启动方法

ssh [email protected]
cd $PROJECT_DIR/n100-web-viewer
. .venv/bin/activate
python app.py --port /dev/ttyUSB0 --baud 921600 --host 0.0.0.0 --web-port 5000

浏览器访问:

http://raspberrypi.local:5000/

开发其他 IMU 或陀螺仪功能时,默认保持这个服务关闭,避免占用 /dev/ttyUSB0 和 5000 端口。

下一篇可以写什么

  • 如何整理 Qmini 的官方资料和版本基线。
  • 如何从 BOM 开始拆解采购清单。
  • 如何规划树莓派端和开发/仿真机的职责边界。