基于行空板与红外传感器的火车模型缩比测速系统设计与实现

📅 2026/7/29 11:47:39 👁️ 阅读次数 📝 编程学习
基于行空板与红外传感器的火车模型缩比测速系统设计与实现

1. 项目概述:当火车模型遇上开源硬件

玩火车模型的朋友,尤其是那些热衷于场景沙盘制作的,肯定都琢磨过一个问题:我这台按1:87比例缩小的HO级模型,在沙盘轨道上跑起来,它的“真实速度”应该是多少?看着它在弯道上呼啸而过,感觉挺快,但换算成真实世界的火车速度,可能也就时速二三十公里。这种“缩比速度”的测量,不仅仅是满足好奇心,更是评估模型动力性能、调整控制器参数、甚至模拟真实列车运行图的关键依据。传统方法要么靠目测估算,误差大;要么用昂贵的专业测速仪,不亲民。

最近我在折腾一个沙盘场景时,就遇到了这个需求。手头正好有一块行空板,这是一款集成了屏幕、Wi-Fi、多种传感器接口和强大Python环境的开源硬件。我琢磨着,能不能用它来搭建一个低成本、高精度的火车模型测速系统?结果不仅做成了,整个过程还意外地简单和有趣。今天就把这个从思路到代码,从硬件连接到数据处理的完整方案分享出来,无论你是模型爱好者还是硬件创客,都能跟着复现。

这个方案的核心,是利用行空板上的通用输入输出(GPIO)引脚,配合一个简单的红外对管传感器,在轨道特定位置形成一个“光栅”。当火车模型通过时,会阻断红外光束,产生一个电信号跳变。行空板捕捉这个跳变的时间点,结合已知的火车模型长度(缩比后的长度),就能精确计算出模型通过测速点的时间,进而得到速度。最关键的一步,是将这个“模型速度”按照比例尺,换算成它所代表的“真实火车速度”,也就是我们追求的“缩比速度”。

2. 核心思路与硬件选型解析

2.1 为什么选择“遮挡计时法”?

测量运动物体速度,物理上无非就是“测距计时”法。对于火车模型,在沙盘这个有限空间里,最实用的就是在固定距离上测量通过时间。我放弃了需要两个测量点的“区间测速”(对安装精度要求高),选择了更简单的“单点测速”,即测量模型自身长度通过一个点所需的时间。

其原理公式为:速度 = 模型长度 / 通过时间。这里的“模型长度”是已知的(例如一台HO比例的客车模型长约20厘米),我们只需要精确测量“通过时间”即可。这种方法对传感器安装要求低,只需要在轨道一侧固定一个测速点,非常适合沙盘环境。

2.2 传感器选型:红外对管 vs. 其他方案

确定了方法,接下来是选择用什么传感器来触发计时。常见的有以下几种:

  1. 霍尔传感器+磁铁:需要在模型底部贴磁铁,会改变模型配重,且磁铁可能干扰某些带芯片的数码车头。
  2. 接触式行程开关:需要机械接触,可能刮伤模型,且可靠性一般。
  3. 视觉识别(摄像头):方案最灵活,但需要处理图像,对行空板算力有要求,环境光影响大,实现复杂度高。
  4. 红外对管:包含一个红外发射管和一个接收管。当模型通过其间遮挡光束时,接收管信号变化。这是我最推荐的方案。它非接触、无干扰、成本极低(一对模块通常不到5元)、响应速度快,且输出是简单的数字电平(有遮挡/无遮挡),非常容易与行空板的GPIO接口。

注意:选择红外对管时,最好选择“调制型”的。普通红外对管容易受到环境光(特别是日光灯)的干扰,可能产生误触发。调制型的发射管以特定频率闪烁,接收管只解调该频率的信号,抗干扰能力极强。如果手头只有普通的,可以尝试在软件上做防抖处理,但稳定性会差一些。

2.3 为什么是行空板?

市面上能跑Python的单板计算机很多,比如树莓派。但我选择行空板,是因为它在这个项目上具有“开箱即用”的独特优势:

  • 集成度高:自带一块触摸屏,可以直接显示实时速度、历史记录,无需外接显示器。自带Wi-Fi,可以方便地将数据发送到电脑或手机进行进一步分析。
  • 接口友好:GPIO引脚以PH2.0接口引出,直接用杜邦线连接传感器,无需焊接或扩展板。
  • 编程环境简单:预装了基于Jupyter的Python环境,支持图形化和代码两种编程方式,对初学者极其友好。测量、计算、显示、数据记录的代码可以在一个环境中轻松完成。
  • 供电方便:一根Type-C线即可供电并编程,非常适合在沙盘边移动使用。

3. 硬件搭建与电路连接

3.1 所需材料清单

  • 行空板x1
  • 调制型红外对管模块x1(通常有3根或4根线:VCC, GND, OUT/D0)
  • PH2.0转杜邦线母头线x1(用于连接行空板GPIO)
  • 杜邦线(公对公)若干
  • 模型火车及轨道x1
  • 固定支架(如乐高积木、胶水、蓝丁胶)x1
  • USB Type-C 数据线x1

3.2 电路连接详解

红外对管模块通常有三根线:

  • VCC(红色):接电源正极。行空板的GPIO引脚中,有专门标注“3V3”的引脚,提供3.3V电压,就接这里。
  • GND(黑色或棕色):接电源负极。接行空板上任意一个“GND”引脚。
  • OUT/D0(黄色或绿色):数字信号输出线。这是我们用来检测遮挡的关键线。它需要连接到行空板的一个数字输入引脚上。我选择的是行空板上标注为“P21”的引脚(你也可以选其他如P22, P23等)。

连接步骤:

  1. 将PH2.0转杜邦线插入行空板的“P21”、“3V3”和“GND”引脚。
  2. 用杜邦线将红外模块的OUT线(黄)与行空板的P21线连接。
  3. 用杜邦线将红外模块的VCC线(红)与行空板的3V3线连接。
  4. 用杜邦线将红外模块的GND线(黑)与行空板的GND线连接。
  5. 检查连接无误后,用Type-C线为行空板上电。

3.3 传感器安装与校准

这是影响测量精度的物理关键。

  1. 定位:选择一段平直轨道。将红外对管的发射头和接收头分别固定在轨道两侧,确保它们在同一水平线上,并且光束中心大致对准轨道中心。
  2. 高度:调整传感器高度,使其光束恰好能被火车模型的车体遮挡,而不会被底盘、轮子或轨道本身干扰。对于大多数模型,光束中心距离轨面5-10毫米比较合适。
  3. 固定:使用蓝丁胶、热熔胶或小夹子将传感器牢牢固定。确保火车模型高速通过时不会碰倒传感器。
  4. 测试:在软件编写前,可以先用手在光束前晃动,观察红外模块上的信号指示灯(如果有的话)是否灵敏亮灭。同时,可以用行空板简单的读取一下P21引脚的电平,确认遮挡时电平变化正常(通常是遮挡时从高电平变为低电平,即“下降沿”)。

4. 软件程序设计:从信号捕捉到速度计算

程序的核心逻辑是“边沿检测+高精度计时”。我们将使用行空板Python库中的pinpong库(用于GPIO控制)和time库(用于高精度计时)。

4.1 初始化与引脚配置

# 导入必要的库 from pinpong.board import Board, Pin import time # 初始化行空板 Board().begin() # 初始化红外传感器引脚 (P21) 为输入模式,并启用内部上拉电阻 # 启用上拉后,默认引脚为高电平(1),当红外光被遮挡,模块输出低电平(0) ir_sensor = Pin(Pin.P21, Pin.IN, pull=Pin.PULL_UP) # 状态变量初始化 model_length_cm = 22.0 # 示例:你的火车模型长度,单位厘米。务必准确测量! scale = 87.0 # 示例:HO比例是1:87 is_blocked = False # 当前遮挡状态 last_event_time = 0 # 上次事件时间戳 current_speed_kph = 0.0 # 计算出的缩比速度(公里/小时)

实操心得Pin.PULL_UP(内部上拉)非常关键。它确保了在传感器未连接或失效时,引脚有一个确定的高电平,避免因引脚悬空产生随机抖动信号,导致误触发。这是硬件防干扰的软件辅助手段。

4.2 核心测速逻辑:状态机与时间差计算

我们不能简单地检测到低电平就开始计时,因为传感器信号可能在遮挡期间有微小抖动。一个健壮的方法是使用“状态机”,只在状态发生改变(从无遮挡到有遮挡,即下降沿)时记录时间点。

def calculate_speed(length_cm, time_interval_s, scale): """ 计算缩比速度 :param length_cm: 模型实际长度 (厘米) :param time_interval_s: 模型通过时间 (秒) :param scale: 比例尺 (如87) :return: 缩比速度 (公里/小时) """ if time_interval_s <= 0: return 0.0 # 1. 计算模型速度 (厘米/秒) model_speed_cm_per_s = length_cm / time_interval_s # 2. 换算为真实世界速度:模型速度 * 比例尺 real_speed_cm_per_s = model_speed_cm_per_s * scale # 3. 单位转换:厘米/秒 -> 公里/小时 # 厘米 -> 公里:除以 100,000 (1000*100) # 秒 -> 小时:乘以 3600 real_speed_kph = real_speed_cm_per_s * 3600 / 100000 return real_speed_kph # 主循环 while True: current_state = ir_sensor.read_digital() # 读取当前引脚电平,0=遮挡,1=未遮挡 if current_state == 0 and not is_blocked: # 状态:从未遮挡 -> 遮挡 (检测到下降沿,车头开始进入光束) is_blocked = True start_time = time.time() # 记录开始遮挡的时刻(车头进入) # 注意:这里不是最终计时开始,我们等车尾进入再开始算“车身通过” elif current_state == 1 and is_blocked: # 状态:从遮挡 -> 未遮挡 (检测到上升沿,车尾离开光束) is_blocked = False end_time = time.time() # 记录结束遮挡的时刻(车尾离开) # 计算整个车身通过光束的时间 passing_time = end_time - start_time # 调用函数计算缩比速度 current_speed_kph = calculate_speed(model_length_cm, passing_time, scale) # 这里可以打印、显示或发送速度数据 print(f"通过时间: {passing_time:.3f} 秒, 缩比速度: {current_speed_kph:.1f} km/h") # 加入短暂延时,避免循环过快消耗CPU。5-10ms是合理的。 time.sleep(0.01)

4.3 数据显示与交互界面

行空板自带屏幕,我们可以用unihiker库来创建一个简单的GUI,实时显示速度。

# 在开头导入unihiker库 from unihiker import GUI import tkinter as tk # unihiker基于tkinter # 初始化GUI gui = GUI() # 创建显示速度的标签,使用大字体 speed_label = gui.draw_text(x=120, y=100, text="0.0", font_size=80, color="blue") unit_label = gui.draw_text(x=220, y=140, text="km/h", font_size=30, color="black") title_label = gui.draw_text(x=100, y=30, text="火车模型缩比测速仪", font_size=20, color="green") # 在主循环中,更新显示部分 while True: # ... (上述状态机检测代码) ... elif current_state == 1 and is_blocked: # ... (计算速度的代码) ... # 更新屏幕显示 speed_label.config(text=f"{current_speed_kph:.1f}") # 为了界面响应,更新后可以调用 gui.update() gui.update() time.sleep(0.01)

4.4 数据记录与导出

为了分析模型在不同电压下的性能,记录每次通过的速度和时间很有用。我们可以将数据写入CSV文件。

import csv from datetime import datetime # 在程序开始时,创建或打开一个CSV文件 filename = f"train_speed_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" with open(filename, 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['Timestamp', 'Passing_Time(s)', 'Scale_Speed(km/h)']) # 写入表头 # 在主循环的测速成功后,添加记录逻辑 elif current_state == 1 and is_blocked: # ... (计算速度的代码) ... current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] with open(filename, 'a', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow([current_time, f"{passing_time:.3f}", f"{current_speed_kph:.1f}"]) print(f"[{current_time}] 记录: {passing_time:.3f}s, {current_speed_kph:.1f}km/h")

5. 精度提升与高级功能拓展

基础版本已经可以工作,但要获得更可靠、更专业的数据,还需要考虑以下几点。

5.1 软件防抖与滤波处理

红外传感器可能因灰尘、振动或电气噪声产生毛刺信号,导致误测出极短的通过时间,从而计算出荒谬的高速。我们需要在软件中加入防抖。

DEBOUNCE_TIME = 0.02 # 防抖时间20毫秒,短于这个时间的状态变化将被忽略 # 修改状态机逻辑,加入时间判断 while True: current_state = ir_sensor.read_digital() current_time = time.time() if current_state == 0 and not is_blocked: # 检测到潜在下降沿,但需要确认信号稳定低电平超过防抖时间 if (current_time - last_event_time) > DEBOUNCE_TIME: # 短暂延时后再次检测,确认仍是低电平 time.sleep(DEBOUNCE_TIME) if ir_sensor.read_digital() == 0: is_blocked = True start_time = current_time # 使用当前时间作为开始时间 last_event_time = current_time # ... 上升沿检测也做类似防抖处理 ...

5.2 多组测量与平均算法

单次测量可能受轨道接缝、轮子打滑等偶然因素影响。更科学的做法是连续测量多次,取平均值或中位数。

speed_readings = [] # 用于存储最近N次测量结果 MAX_READINGS = 5 # 在计算得到 current_speed_kph 后 speed_readings.append(current_speed_kph) if len(speed_readings) > MAX_READINGS: speed_readings.pop(0) # 移除最旧的数据 # 计算平均速度 if speed_readings: avg_speed = sum(speed_readings) / len(speed_readings) # 或者计算中位数,对异常值更鲁棒 sorted_readings = sorted(speed_readings) median_speed = sorted_readings[len(sorted_readings) // 2] # 在屏幕上可以同时显示本次速度和平均速度 gui.draw_text(x=50, y=180, text=f"Avg: {avg_speed:.1f} km/h", font_size=20, color="red")

5.3 无线数据传输与远程监控

行空板的Wi-Fi功能可以让数据飞起来。我们可以搭建一个简单的HTTP服务器,在局域网内的电脑或手机浏览器上实时查看速度图表。

import network import socket import json # 连接Wi-Fi (需要修改为你的网络信息) def connect_wifi(ssid, password): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') wlan.connect(ssid, password) while not wlan.isconnected(): pass print('network config:', wlan.ifconfig()) return wlan.ifconfig()[0] # 返回IP地址 ip_address = connect_wifi("你的Wi-Fi名", "你的密码") # 创建一个简单的HTTP服务器线程(这里为简化,展示核心思路) # 实际应用中,可以使用microdot等轻量级框架 # 在主循环中,将 current_speed_kph 存入一个全局变量或队列 # HTTP服务器线程读取这个变量,并生成一个包含实时速度的JSON或HTML页面 # 这样,在浏览器访问 http://[行空板IP]:8080 就能看到数据

6. 校准、测试与常见问题排查

系统搭建好后,必须进行校准和测试,确保数据可信。

6.1 系统校准流程

  1. 测量真实模型长度:用游标卡尺精确测量你要测速的火车模型的总长度(车钩到车钩)。这是所有计算的基础,误差会直接放大87倍(对于HO比例)。务必精确到毫米。
  2. 验证传感器触发:手动缓慢推动模型通过光束,观察行空板打印的日志或屏幕显示,确认模型车头进入、车身完全遮挡、车尾离开这三个时刻,传感器状态变化是否准确、无抖动。
  3. 静态时间校准:这不是校准时钟,而是测试系统延迟。用一个已知宽度的卡片(如5厘米宽)以恒定慢速(用手匀速推动)通过光束。记录系统测出的通过时间。根据速度 = 卡片宽度 / 时间计算出的速度,应该与你手动推动的估计速度大致吻合。如果偏差很大,检查传感器安装高度是否导致卡片未完全遮挡光束。
  4. 比例尺验证:找一个速度相对恒定的模型车头,在控制器上设定一个固定档位(例如50%油门)。用本系统和另一种方法(如用手机慢动作视频拍摄,通过帧数计算)同时测量多次,对比结果。两者应在一个合理的误差范围内(例如±5%)。

6.2 常见问题与解决方案速查表

问题现象可能原因排查与解决方法
无任何触发,屏幕无变化1. 电源未接通或接触不良。
2. 传感器模块损坏。
3. GPIO引脚连接错误。
4. 程序未运行或引脚模式设置错误。
1. 检查所有连线,重新插拔。
2. 用万用表测量模块VCC和GND间是否有3.3V电压。遮挡时测量OUT脚电压是否从~3.3V变为~0V。
3. 确认行空板引脚编号(P21)与代码一致。
4. 在代码开头添加print(“程序开始”),并打印ir_sensor.read_digital()的值观察变化。
频繁误触发,无车通过也测速1. 环境光干扰(特别是日光灯)。
2. 传感器未固定好,轻微晃动。
3. 引脚悬空(未启用内部上拉)。
4. 电源噪声。
1.首选:更换为调制型红外对管。
2. 加固传感器安装。
3. 确认代码中设置了Pin.PULL_UP
4. 在传感器VCC和GND之间并联一个10uF-100uF的电解电容,进行电源滤波。
测出的速度值明显偏大1. 模型长度输入值偏小。
2. 测量到的通过时间偏短(主要问题)。
1. 重新精确测量模型长度。
2.最常见原因:传感器光束太粗或安装位置偏低,导致车头尚未完全进入光束,车尾已经开始离开光束,实际测量的遮挡时间小于车身完全通过的时间。解决:调高传感器,确保光束能被车体完全遮挡;或使用更细光束的传感器。检查软件防抖时间是否过短,误将噪声当作触发。
测出的速度值明显偏小或不稳定1. 模型长度输入值偏大。
2. 测量到的通过时间偏长。
1. 重新精确测量模型长度。
2. 传感器响应慢?高质量红外对管响应时间在微秒级,远快于模型速度,通常不是瓶颈。
3.可能原因:软件主循环time.sleep(0.01)延时过长,导致检测上升沿有延迟。可以尝试减少延时到0.005或0.001,但会增加CPU负载。
4. 模型运行不平稳,有打滑或卡顿。多次测量取平均或中位数。
速度显示为0或极小数通过时间计算为无穷大或负数。1. 检查除法运算中,passing_time是否可能为0或负数(逻辑错误导致start_time>=end_time)。
2. 在calculate_speed函数中加入对time_interval_s是否大于一个极小值(如0.001)的判断。

6.3 性能优化与扩展思路

  • 更高精度计时time.time()在标准Python中精度约1毫秒。对于极高速的N比例模型(长度更短),可能需要微秒级精度。可以尝试使用time.perf_counter()time.perf_counter_ns()函数。
  • 多车道测速:使用多个红外对管,连接到行空板的不同GPIO引脚,可以同时监测沙盘上多条轨道的车流。
  • 速度曲线绘制:结合行空板的屏幕或通过网络将数据发送到电脑,可以绘制出模型在加速、匀速、减速阶段的速度-时间曲线,用于分析电机性能。
  • 与控制器联动:将测得的实时速度作为反馈信号,输入到模型火车控制器(如DCC系统)中,理论上可以实现闭环速度控制,让模型以恒定的缩比速度运行,这将是迈向高度仿真的关键一步。

这个项目最让我满意的地方,在于它用极低的成本(核心传感器不到一杯咖啡的钱)和一块功能丰富的开源硬件,解决了一个模型玩耍中实实在在的痛点。从看到问题、拆解需求、选型搭建、到编码调试,整个过程就是一个完整的创客项目闭环。当你第一次看到屏幕上显示出自己火车模型对应的“真实”时速时,那种将虚拟比例与现实数据连接起来的成就感,远超单纯地看着模型跑圈。希望这个详细的分享能帮你打造出自己的测速仪,让火车模型的奔跑,不仅有形,更有数。