使用行空板测量火车模型的缩比速度
简介 Introduction
铁路模型爱好者希望他们的模型铁路能够近似全尺寸真实铁路的外观和操作。这些爱好者们努力让火车在轨道沙盘上以一定速度运行。 缩比速度(Scale Speed)是代表真人大小火车的实际速度的模型速度。
译者注:"Scale Speed" 在铁路模型领域中是指模型火车运行速度与实际火车运行速度之间的比例关系。由于模型火车按照一定比例缩小,所以它们的运行速度也要相应地按比例减慢。例如,在后文中提到的HO scale模型铁路中,如果一辆实际火车的最高速度为每小时 120公里,那么其对应的HO scale模型火车在模型轨道上的最高速度应为实际速度的 1/87,这样才能真实反映实际情况。这样调整后的速度就是该模型火车的Scale Speed。
译者注:这张图表对现实中火车速度和对应的沙盘上模型火车的速度进行了对比。
实际列车速度因运行情况而异。而铁路模型的制作者将配置其布局,使其在缩放布局上的速度范围内工作。
物体的速度是单位时间内其位移的程度。
S=d/t
s = 速度
d = 行驶距离
t = 时间
我们通过缩放模型距离来代表现实世界。 模型速度计算必须将其纳入计算中。时间不按比例缩放。现实世界时间中的一分钟在 HO 比例(HO Scale)下仍然是一分钟。
译者注:HO scale是一种模型制作和铁路模型的规模,在国际上广泛使用。HO scale表示的是1:87的比例,即模型火车、轨道及周边场景的尺寸是实际火车和相关设施尺寸的1/87。这种比例尺非常适合家庭模型铁路布局,因为它大小适中,既不像更小的比例那样难以精细制作细节,也不像更大比例那样占用过多空间。它可以提供相对真实的立体感和足够的空间来构建复杂的铁路系统,包括多条线路、车站、信号系统、风景等。
行空板的比例速度测量模型引擎覆盖特定距离所需的时间,然后考虑布局的比例(即 Z、N、HO..scale)来计算速度。该项目仅关注 HO比例。可以更改用于计算比例速度的python代码公式中的比例变量,以适应其他的布局比例。
我们需要两个数据:距离和时间来进行计算。两个传感器 A 和 B 之间的距离已知。时间是通过触发传感器并读取时钟读数来确定的。火车触发传感器 A 启动计时器。当火车触发传感器 B 时,计时器停止。减去两个时间即可得出时间。
传感器选择 Sensor Choice
红外传感器(IR Sensors)的工作原理是,将一束红外光从一个 LED 传输 (TX) 到另一个 LED,后者检测或接收 (RX) 红外光信号并产生输出。
有两种类型的红外传感器。在光束阻断型传感器中,物体阻断光束以产生信号。反射型传感器依靠的则是反射红外光的物体来提供信号。
基准测试提供了性能结果,以确定适合该项目的理想传感器。传感器连接到面包板以建立电源并可以访问输出进行测试。
第一个测试是确定红外噪声是否影响传感器的性能。随机红外可以由多种设备产生。 LED灯泡就是一个例子。这种红外噪声会影响红外设备的性能。噪声可能非常高,以至于传感器无法读取实际的红外线。使用已知的红外噪声LED灯来查看是否有任何传感器受到影响。
Arduino红外接近传感器 (10±5mm~80±20mm)和5V红外光电开关(4m)传感器的性能不受红外噪声的影响。当嘈杂的 LED 灯打开且不改变状态时,红外断线传感器(50cm)会产生输出。这使得传感器免于进一步测试。
接下来的测试是确定传感器在封闭环境中是否会受到影响。在最终项目成果中,传感器将被封闭在彼此靠近的山景中。这个外壳会影响性能吗?
用于Arduino的红外IR接近传感器(10±5mm~80±20mm)在测试台上表现不佳。传感器周围的垂直表面和传感器非常接近,会导致误报读数。
5V红外光电开关(4m)的性能不受垂直表面和近距离的影响。在测试过程中确定两个传感器的发射器需要位于同一侧以消除杂散红外反射。
硬件完成后,是时候进行一些连接并编写代码了。
塑料外壳的侧面被切割以形成隧道。一堆传感器安装在隧道上。外壳顶部的一块小面包板用于支持传感器与行空板的电源连接。隧道外壳放置在轨道上方进行测试。火车驶过隧道触发了传感器。
传感器的电源由外部5V电源提供。每个传感器输出都连接到行空板上的GPIO。
红外断线传感器(50cm) SKU:SEN0503 引脚排列:
发射器:红线 = 5V DC,黑线 = GND
接收器:红线 = 5V DC,黑线 = GND,白线 = OUT (NPN)
打住...打住...接下来转入新的讨论点:代码开发
原型对于代码开发来说太麻烦了。开发代码是为了使用行空板上的两个按钮作为传感器代码的替代品。代码开发完成后,原型用于执行性能测试证明。
除了传感器工作之外,还插入了调试例程以在测试期间提供文本输出。不需要时可以禁用此功能。
概念验证 Proof of Concept
该代码在顶部提供日期和时间,在下面提供速度指示。代码计算提供实际的 MPH 读数和相应的 MPH 刻度速度以及 KPH 读数和 KPH 刻度速度。
显示读数保持 10 秒,然后读数返回零。
性能测试证明是该项目的最后阶段。为了进行完整的安装,山景(如前文图片所示)并不完整。隧道原型安装在测试轨道上,列车运行以确定速度。
得到教训 Lessons Learned
- 在测试过程中确定,当传感器安装在山上时,需要对硬件进行一些更改。
- 在隧道原型中,传感器直接位于彼此对面。当额外的车厢连接到火车引擎时,这会导致误报读数。车辆之间的间隙足以触发传感器。该计划是将传感器对偏移一定角度,以消除导致误报的间隙。
- 在代码开发和测试期间,按钮创建了理想的测试场景,只有使用原型时才变得明显。
- 由于隧道中没有车厢,红外传感器输出为高电平。当车厢阻断光束时,输出变为低电平。该低点被忽略,有利于返回到高点。在高点上触发使得火车可以拥有多个车厢并且仍然读取准确的时间。
- 另一个令人惊讶的发现是取时间计算的绝对值。这个简单的函数可以在火车向任一方向移动时计算火车速度。
- 检查照片后发现行空板上显示的时间是错误的。行空板论坛 https://www.dfrobot.com/forum/topic/329077的帖子提供了解决该问题的解决方案。
- 感谢DFRobot提供测试和发布有关行空板单板计算机的机会。
硬件清单 HARDWARE LIST
1个 行空板(链接:https://www.dfrobot.com.cn/goods-3404.html)
2个 25V红外光电开关(4m)(链接:https://www.dfrobot.com.cn/goods-3643.html)
2个 Arduino红外接近传感器(10±5mm~80±20mm)(链接:https://www.dfrobot.com.cn/goods-3654.html)
代码 Code
import time
from pinpong.board import Board, Pin #Import libary to control GPIO
from unihiker import GUI # Import the GUI module from the UniHiker library
Board().begin()
gui = GUI() # Instantiate the GUI class and create an object
deBug = 1 #When 1 additional print to console. Additional print statements are included for troubleshooting.
timer_left = False #initialize to prevent cal before sensor read
timer_right = False #initialize to prevent cal before sensor read
sensorSpace_inch = 4 # space between sensors in inches
sensorSpace_mm = 101.6 # space between sensor in mm
modelScale = 87
#Create a black background with date and time at top
#Create Title text
#Create four number displays to hold different speeds
bg = gui.fill_rect(x=0, y=0, w=240, h=320, width=3, color=(0, 0, 0))
DigitalTime=gui.draw_digit(text="0",x=10,y=310,font_size=15, color="white",angle=90)
title=gui.draw_text(text="TRAIN SPEED",x=30,y=240,font_size=20, color="white",angle=90)
MPH = gui.draw_text(x=80, y=263, text="MPH :", origin = "center",color="white",font_size=12,angle=90)
MPH2 = gui.draw_digit(x=120,y=240,text="0",origin = "center",color="white",font_size=33,angle=90)
KPH = gui.draw_text(x=80, y=140, text="KPH :", origin = "center",color="white",font_size=12,angle=90)
KPH2 = gui.draw_digit(x=120,y=100,text="0",origin = "center",color="white",font_size=33,angle=90)
SCALEMPH = gui.draw_text(x=175, y=263, text="ScaleMPH :", origin = "center",color="white",font_size=12,angle=90)
SCALEMPH2 = gui.draw_digit(x=215, y=240, text="0", origin = "center",color="white",font_size=33,angle=90)
SCALEKPH = gui.draw_text(x=175, y=140, text="ScaleKPH :", origin = "center",color="white",font_size=12,angle=90)
SCALEKPH2 = gui.draw_digit(x=215, y=100, text="0", origin = "center",color="white",font_size=33,angle=90)
#sensor23 = Pin(Pin.P27, Pin.IN) #P27 & P28 are button attachement on Unihiker can double as sensor inputs for testing
#sensor24 = Pin(Pin.P28, Pin.IN)
sensor23 = Pin(Pin.P23, Pin.IN) #P23 & P24 are Unihiker sensor inputs
sensor24 = Pin(Pin.P24, Pin.IN)
def sensor23_handler(pin): #This is what happens when pin 23 is triggered
global deBug
global timer_left
global left_count
if deBug:
print("\n sensor23--triggered--")
print("pin = ", pin)
left_count = time.perf_counter()
if deBug:
print(left_count)
timer_left = True
def sensor24_handler(pin): #This is what happens when pin 24 is triggered
global deBug
global timer_right
global right_count
if deBug:
print("\n sensor24--triggered--")
print("pin = ", pin)
right_count = time.perf_counter()
if deBug:
print(right_count)
timer_right = True
def counter_reset(): #This resets counters to zero for a restart
global timer_left
global timer_right
if deBug:
print('System Reset')
timer_left = False
timer_right = False
#Can use Unihiker button to test in absence of sensors.
#sensor23.irq(trigger=Pin.IRQ_FALLING, handler=sensor23_handler) #FALLING sensor trigger HIGH TO LOW
#sensor24.irq(trigger=Pin.IRQ_FALLING, handler=sensor24_handler) # Default used for Button Testing
# Using break beam sensor
# Sensor output is HIGH. Break beam goes LOW
# Use RISING for train with more than one car
sensor23.irq(trigger=Pin.IRQ_RISING, handler=sensor23_handler) #RISING sensor trigger LOW to HIGH
sensor24.irq(trigger=Pin.IRQ_RISING, handler=sensor24_handler)
while True:
DigitalTime.config(text=time.strftime("%Y-%m-%d %H:%M")) #Keep refreshing date and time
if timer_left == True and timer_right == True: #Both sensor have counter loaded
elapsed_time_sec = abs(right_count - left_count) # enables train speed to be calculated from either direction
miles = sensorSpace_inch/63360
kilometers = sensorSpace_mm/1000000
hours = elapsed_time_sec/3600
mph = miles/hours
MPHG = mph
kph = kilometers/hours
KPHG = kph
scaleMPH = mph*modelScale
SMPHG = scaleMPH
scaleKPH = kph*modelScale
SKPHG = scaleKPH
if deBug:
print('Elaspsed time in seconds:', elapsed_time_sec)
print('Miles:', miles)
print('Kilometers:', kilometers)
print('Hours', hours)
print("Speed in MPHG:", MPHG)
print("Scale Speed in MPH:", scaleMPH)
print("Speed in KPHG:", KPHG)
print("Scale Speed in KPH:", scaleKPH)
MPH2.config(text=(round(float(MPHG),2)))
KPH2.config(text=(round(float(KPHG),2)))
SCALEMPH2.config(text=(int(float(SMPHG))))
SCALEKPH2.config(text=(int(float(SKPHG))))
time.sleep(10)
counter_reset() #reset the counters for another pass
#Zero the display for another pass
MPH2.config(text=("0"))
KPH2.config(text=("0"))
SCALEMPH2.config(text=("0"))
SCALEKPH2.config(text=("0"))
最终效果 Final
原作者:seanconway