利用Arduino UNO Q和一个IMU制作一个可穿戴设备
如果你小时候玩过《过山车大亨》,你一定知道那种感觉:你建造一座过山车,向游客开放,然后屏住呼吸,等待游戏计算出刺激度、紧张度和恶心值。太刺激?没人坐。太无聊?也没人坐。准确地获得全部三个数值,才是整个游戏的全部重点。
我一直好奇真正的 coaster 会得到怎样的评分。我想制作一个可穿戴设备,帮助你揭晓答案。
幸运的是,1962年,梅尔巴·斯通在观察袋鼠时意外发明了这种可穿戴设备的终极收纳装置——臀包。将一个Arduino UNO Q和一个IMU装进臀包里,骑上真正的滑板车,就能在手机上以RCT风格的界面显示你的兴奋度、强度和恶心值。然后像分享你最酷滑板车搭建的截图一样,把结果分享到社交媒体上。
看看这些来自冒险乐园的真实脚垫效果如何!
工作原理
该系统分为三个阶段,其计算评分的方式与原版游戏相同:
记录:你在排队时,手机上按下了开始键。SparkFun ICM-20948 加速度计/陀螺仪以 200Hz 的频率开始采样加速度和旋转信息,并通过内部桥接器将数据从 Arduino UNO Q 的 STM322 微控制器传输至高通 Dragonwing MPU。
流程:骑行结束后,按下停止键。在高通端运行的Python代码会从原始传感器数据中提取骑行的物理指标,例如峰值加速度、横向力、飞行时长等。前几秒的数据(当你站在队列中静止时)用于校准“向下”方向,因此防丢包中IMU的方向并不重要。
预测:提取出的特征将输入三个Edge Impulse回归模型,分别用于兴奋度、强度和恶心感,这些模型均基于实际《过山车大亨2》游戏数据训练。各模型输出0到10分的经典评分,结果以一个看起来直接来自游戏的Web界面进行展示。
入门指南
完整项目可在Arduino项目中心以可直接导入的包形式获取:
RollerCoasterTestMeter 是 Arduino 项目中心
下载 ZIP 文件,然后在 Arduino App Lab 中进入“我的应用”→“创建新应用+”,导入 ZIP 文件。该文件包包含代码、Python 代码、WebUI、第三方库以及预训练的 Edge Impulseulse 模型,您所需的一切内容均已包含在内。
导入后,如果尚未安装,请安装以下草图库:
SparkFun 9轴IMU扩展板 - ICM 20948 - Arduino库
Arduino_RouterBridge(通常已预装)
硬件
是时候收集你的材料了。
组装简单,无需任何工具!UNO Q 采用双脑架构,轻松完成所有复杂任务。STM3222 负责实时传感器采样,而高通 MPU MPU 同时运行 Python、机器学习推理和一个 Web 服务器。无需在公园里携带笔记本电脑。
线路只有一根线缆。SparkFun扩展板连接到UNO Q的Qwiic接口,插入后即可完成。请确保连接到SparkFun板的Qwiic一侧,而不是I2C一侧。
为了固定“外壳”,我使用了3M命令贴片将IMU固定在电池组上,以防止骑行过程中晃动。当然,你也可以使用任何你喜欢的紧固件!整个结构安装在电池顶部,通过一根短USB-C线缆连接。
首先,将魔术贴固定好。然后将USB线连接到Arduino和电池上。
训练模型
这里就变得有趣了。过山车大亨的评分系统并非随机,而是由游乐设施的物理特性决定的确定性函数。克里斯·索耶的原始代码会将最大G力、过山车坡道数量、飞行时间、速度和翻转次数等参数映射为兴奋度、强度和恶心度评分。
得益于诺兰·康奈威(Nolan Conaway)整理的一组数据集(可在GitHub和Kaggle上获取),我们获得了游戏中189种过山车设计的输入特征和输出评分,这便是我们的训练数据。
选择可靠的功能
RCT2数据集包含11个骑行特征,但惯性测量单元(IMU)无法同等准确地测量所有特征。这是项目中最重要的设计决策:我们究竟应该信任哪些特征?
加速度计直接给出的是加速度,而不是速度。要得到速度,必须对加速度进行时间积分,而这种积分会严重漂移。在测试中,一次4分钟的过山车体验估计出的速度高达每小时512英里。再进行距离积分则效果更差。计算下坡、估算下坡高度以及追踪颠簸路段,都需要任意调整阈值,或者依赖陀螺仪积分,而这会导致累积误差。
因此,我们将IMU可测量的特征集缩小为五项,无需进行任何积分或估计:
•最大正向垂直加速度:即你被推入座椅时的极限。直接加速峰值,完全可靠。
•最大负垂直加速度:你感受到的最轻重状态。相同——直接加速度计读数。
•最大侧向G值:转弯时你承受的最大横向加速度。直接读取。
•总时长:处于近失重状态(垂直加速度低于0.5)所花费的秒数。这只是直接读数乘以采样率后的阈值,无需积分。
•骑行时间:骑行持续多久。一个钟表。这一点绝不能搞错。
其他所有内容——速度、距离、掉落次数、掉落高度和翻转次数都被删除了。这带来了一个不错的副作用:原本包含11个特征的数据集有42行缺失翻转数据,我们不得不将其删除。现在仅剩5个特征,全部189行数据均可使用。从更少但更好的特征中获得了更多的训练数据。
准备数据
PythonPythonPython 脚本会清理原始 CSV 文件,并输出三个文件,每个文件对应一个目标分数,格式符合 Edge Impulse 的 CSV Wizard Wizard Wizard 要求。每个文件包含五个特征列以及一个标签列,用于存储数值评分(兴奋度、强度或恶心感)。需要注意的是:强度数据集中有一个异常值,为 44.94,而其他所有数值均低于 10,这很可能是故意设计的糟糕过山车体验。脚本会自动移除该异常值,因为单个极端异常值将彻底破坏基于 189 个样本训练的回归模型。
以下是剧本:
Edge Impulse 设置
由于 Edge Impulse 的回归块每个样本只接受一个数值标签,因此我创建了三个独立的 Edge Impulse 项目(一个用于兴奋感,一个用于强度,一个用于恶心)。
每个流程的工作方式都完全相同:
•通过CSV向导上传CSV文件(不要使用常规上传器,后者需要带有时间戳的时间序列数据)
•使用扁平化处理块和回归学习块创建一个冲动
•在“展开”块中,取消勾选除“平均值”之外的所有选项(因为每个“轴”本身就是一个单一值,而非时间序列),并启用标准缩放(StandardScaler)归一化。
•使用36→16神经元架构训练300个周期
对于关注此事的人,有几点值得说明:
为什么只使用“平均”?Flattenatten 模块专为时间序列数据设计,用于在样本窗口内计算统计量(均值、最小值、最大值、均方根、标准差、偏度、峰度)。但我们的数据已经是表格形式:每一行是一组预计算好的数值,而非时间序列。当 max_speed 是单个数值时,计算“max_speed 的平均值”只会得到……那个数值本身。因此我们只检查 Average,它起到的是直接通过的作用。如果没有这个操作,第一次训练时有 35 个输入特征(5 个轴 × 7 个统计量),其中大部分是冗余的副本或零值。
为什么需要使用StandardScaler归一化?我们的特征尺度差异很大。骑行时间最高可达186秒,而最大横向偏移量max_lateral_gs则最高仅为3.32。如果不进行归一化,网络的梯度更新将主要受幅度最大的特征主导。StandardScaler能将所有特征置于同等水平(均值为0,标准差为1)。这种差异非常显著:未进行归一化的第一个兴奋模型损失为8.32,甚至比猜测均值还要差。而经过归一化后,损失降至0.92。
为什么是36个神经元?由于仅有5个输入特征和189个样本,网络需要保持较小规模,以避免数据记忆。36个神经元的初级层提供了足够的容量来学习特征之间的交互(例如,“高侧向G值结合长时间骑行预测兴奋度高”),而16个神经元的次级层则将这些交互压缩为单一输出。
为什么恶心的损失设为0.001?恶心是学习过程中最难掌握的目标。在默认的学习率0.005下,验证损失持续上升,而训练损失却下降(典型的过拟合现象)。将学习率降至0.001后,模型能够收敛且不会过度拟合。原游戏中恶心与横向加速度和翻转动作高度相关,但由于我们去除了翻转动作,模型可用的信号就减少了。
结果
训练后,三个模型的表现如下:
•兴奋度:MAE 0.71,可解释方差 0.34
•强度:MAE 0.96,可解释方差 0.22
•Nausea:MAE 1.06,可解释方差 -0.57
低于1的MAE表示预测值通常在0到10分的尺度上相差不超过1分。兴奋度和强度表现良好。恶心感较弱,但仍能区分不同游乐项目。
每个模型都是为 Linux(AARCH64,搭载高通 QNN)构建的,生成的 .eim 文件非常小:推理时间仅 111 毫秒,占用 1.22 千字节 RAM 和 10.88 千字节闪存。在 UNO Q 上同时运行这三个模型几乎完全免费。
夹紧真实世界数据
我在公园里发现了一件事:真正的过山车速度比RCT2游戏数据中的数值更极端。一辆名为“怪物”的游乐设施达到了6.71g的垂直加速度和41秒的飞行时间,远超训练数据中记录的最高值6.26g和13.68秒。如果没有安全护栏,模型会严重外推,预测出超过20秒的成绩。
修复方法很简单:在运行推理前,将每个输入特征限制在训练数据中观察到的范围内。这意味着超过游戏物理限制的骑行行为会有效使该特征达到上限。虽然评分在区分极端剧烈骑行时的能力有所下降,但分数仍保持在0到10的范围内,骑行之间的排名也依然合理。
在《过山车大亨》中,只有极少数人的兴奋值超过10,因此这是一种合理的方式,将游戏中的得分映射到真实的过山车上。
构建应用程序
该应用程序使用三个组件在Arduino App Lab中运行。如果你从Arduino项目中心导入了ZIP文件,这些组件已经全部准备就绪,本节将说明各组件如何组合在一起。
草图(STM32 侧)
ArduinoArduinoArduino 软件运行在 STM322 微控制器上,负责实时处理 IMU 采样。它通过 I2C 以 200Hz 的频率读取 ICM-20948,配置为 ±8G 加速度(滑行时可达到 5-6G)和 ±1000°/ss 角速度。采样数据以每 1000 次为一组进行批量处理,并通过桥接器发送,以降低 RPC 的开销。
录制由 Python Python Python 端控制。草图会一直空闲,直到收到 set_recording(true))) 调用,然后开始数据流传输。
PythonPythonPython 端(高通 MPU)
这是操作的核心部分。它通过桥接回调从草图接收IMU数据,运行信号处理以提取5个特征,将这些特征输入到三个.eim模型中,并提供WebUI和API端点服务。
信号处理是其中最有趣的部分。前约20个样本(在骑行者静止时记录)用于建立重力校准向量。之后的每个样本都会投影到该向量上,以确定垂直和横向加速度,无论IMU在腰包中的安装方向如何。这意味着你无需担心安装方向的问题,只需将设备放入腰包中即可使用。
根据校准后的数据,代码提取了五个特征:峰值垂直和横向加速度(直接来自加速度计)、飞行时间(垂直加速度低于0.5的时段)以及飞行时长。
还有一个最低运动阈值:如果最大G力从未超过1.5,且骑行时间少于30秒,应用程序会提示你没有足够的骑行数据,而不是给出静坐评分。当我第一次测试到可用版本时,它竟然愉快地将一张静止的办公桌评为6.21分刺激度,我才真正体会到这一点。
WebUI
界面是对RCT用户界面的致敬。游戏标志性的棕红色调、倒角窗框,以及来自游戏的骑行数据窗口布局。三个评分既以带标签的文字形式(例如“8.51(非常高)”)显示,也以动态进度条的形式呈现。
分享按钮使用 html2canvascanvas 抓取结果面板并保存,以便您可以在喜爱的社交媒体平台上分享。
带它去公园
在 App Lab 中,为应用启用自动启动选项,使其在开机时自动运行。然后将热点设置为自动连接。UNO Q Q 可独立运行 WiFi WiFi WiFi 热点,无需手机 tethering 或路由器。只需一条命令即可完成设置:
通过 systemd 服务实现自动启动,且热点设置为开机自动连接,公园工作流程如下:
•插入电池
•请等待60秒
•将您的手机连接到“RCT-IRL”WiFi
•打开书签
•点击队列中的开始按钮,静止2-3秒进行校准
•乘坐过山车
•开始步行或爬楼梯前,请先在应用中点击停止。
•查看并分享你的成绩
公园日比赛结果
我带着设备去了冒险乐园,骑了三辆过山车。以下是模型们的看法:
怪物:兴奋度8.51,强度10.00,恶心感6.20。当天最刺激的体验,也是我绝对最爱的一次!
Outlaw:兴奋度9.01,强度10.00,恶心感5.64。这是所有过山车中横向G值最高的,达到5.18g——每转一次都让你感受到强烈的冲击。兴奋度略高于Monster,强度相当,但恶心感更轻。
龙卷风:兴奋度10.00,强度9.32,恶心5.64。尽管正向加速度(Gs)最低(2.43),但负向加速度(Gs)最强(-2.17),且骑行时间长达5分钟,堪称经典之作!
这些模型在骑行体验上有着明显差异:Monster 最具身体强度,Outlaw 在刺激感与强度之间达到了最佳平衡,而 Tornadoornado 则凭借持续的飞行时间和负重力加速度,在纯粹的刺激感方面表现最出色。
本文编译自hackster.io





