
1. 项目概述当物理实验遇上开源硬件作为一名长期混迹于创客圈和教育技术领域的“老炮儿”我见过太多物理实验还停留在纸笔计算和手动掐表的阶段。最近我带着几个学生用一块行空板、一个光电传感器和几十行Python代码把高中物理课本里那个经典的“用单摆测量重力加速度”实验彻底改造了一遍。这个项目的核心就是探索信息技术与物理学科的深度融合——不是简单地把数据录入电脑而是让开源硬件和编程成为实验的“眼睛”和“大脑”实现数据采集、处理和分析的全程自动化。简单来说我们搭建了一个智能单摆实验系统。摆球下方放置一个光电门光电传感器每当摆球经过并遮挡光束时传感器就会产生一个电信号。行空板一款集成了Python环境的开源硬件实时捕获这个信号并精确记录下每次遮挡的时间戳。通过编程我们可以轻松计算出单摆的周期进而利用单摆周期公式T 2π√(L/g)反推出重力加速度g的值。整个过程从数据采集到最终结果计算全部由代码自动完成学生只需要关注实验原理、调整摆长、分析数据规律即可。这解决了传统实验的几个痛点一是人工计时存在反应误差尤其是测量多个周期时误差会累积二是数据处理繁琐需要手动记录、计算三是实验过程枯燥难以激发学生探究兴趣。而我们这套方案将DIS数字化信息系统的理念平民化、开源化让每个有兴趣的老师和学生都能低成本、高精度地复现。它非常适合中学物理教师进行教学创新、STEM教育爱好者开展项目实践以及对Python硬件编程感兴趣的初学者入门。接下来我就把这套方案的完整设计思路、硬件连接、代码编写以及踩过的坑毫无保留地分享出来。2. 核心方案设计与硬件选型解析2.1 为什么选择“行空板Python光电门”方案市面上能实现类似功能的方案不少比如专用的DIS实验套件、Arduino搭配传感器甚至用高速摄像头进行图像分析。我们最终拍板用行空板是经过一番权衡的。首先专用DIS套件虽然精度高、软件成熟但它是封闭系统价格昂贵且学生无法窥探和修改其内部的数据处理逻辑不利于培养计算思维。其次Arduino方案需要额外学习C/C和特定的IDE对于主要以Python为入门语言的学生来说有额外的学习门槛。而行空板本质上是一个内置了Python解释器和丰富库的微型Linux电脑它原生支持PythonGPIO通用输入输出口操作简单并且自带屏幕和无线网络可以直接编写代码、实时显示数据和图表甚至通过网页远程控制。这大大降低了开发难度让学生能把精力集中在物理原理和算法逻辑上而不是纠缠于环境配置。光电传感器我们选用的是常见的槽型光电开关。它内部有一个红外发射管和一个接收管当摆球通过槽隙遮挡光束时接收管状态变化从而输出一个数字信号从高电平跳变到低电平或反之。这种传感器价格低廉十几元、响应速度快微秒级完全满足单摆周期通常1-2秒的测量需求。相比于用声音传感器测量撞击声或图像识别光电方案抗环境干扰能力更强电路和代码也更简单可靠。2.2 硬件连接与电路原理硬件连接非常简单是典型的数字信号输入电路。行空板准备确保行空板已开机并进入Python编程环境如Jupyter Notebook或Thonny。找到其GPIO引脚我们通常会使用标有数字编号的引脚如D2、D3等来接收数字信号。光电传感器接线槽型光电开关一般有三根线VCC电源正极接3.3V或5V、GND电源负极、OUT信号输出线。行空板的GPIO工作电压是3.3V所以我们将传感器的VCC连接到行空板的3.3V引脚GND连接到任意的GND引脚。信号线连接最关键的一步将传感器的OUT引脚连接到行空板的一个数字输入引脚例如D2。这样当摆球未遮挡时D2读取到的是一个电平状态比如高电平1当摆球遮挡瞬间D2读取到的电平状态会发生翻转变成低电平0。我们的程序就是通过检测这个“翻转”的瞬间来计时的。注意有些光电传感器输出的是模拟量遮挡程度不同电压不同但测量单摆周期我们只需要知道“是否遮挡”这个开关量因此务必选择数字输出型的槽型光电开关。购买时看清型号或直接用万用表测试遮挡前后OUT引脚对GND的电压是跳变的如从3.3V跳到0V而不是连续变化的。整个系统的物理布局是将单摆的固定点安装好调整摆长。在摆球静止时的正下方最低点位置稳固地安装光电传感器确保摆球每次摆动到最低点时都能恰好穿过传感器的槽隙完全遮挡光束。传感器的高度和水平位置需要仔细微调这是保证数据准确的关键。3. 软件逻辑与Python代码实现3.1 数据采集的核心逻辑边沿检测程序的核心任务是精确记录摆球每次经过光电门的时刻。这里不能使用简单的“轮询”不断读取引脚状态因为那样会占用大量CPU资源且精度不高。正确的方法是使用“中断”或“事件检测”。在行空板的Pinpong库或其底层库中我们可以为输入引脚设置“边沿检测”回调函数当引脚电平发生上升沿从0到1或下降沿从1到0变化时自动触发一个我们预先定义好的函数。对于光电传感器我们通常检测下降沿假设遮挡时输出从高变低。在回调函数里我们只需要做一件事获取当前时间精确到毫秒甚至微秒并记录下来。记录下连续多个遮挡时刻就能计算出周期。3.2 完整代码实现与逐行解析下面是一个基于行空板常用库pinpong的完整示例代码。我将在代码中加入大量注释解释每一部分的意图和注意事项。# -*- coding: utf-8 -*- # 单摆测重力加速度 - 行空板 光电门 import time from pinpong.board import Board, Pin from pinpong.libs.dfrobot_ads1115 import ADS1115 import matplotlib.pyplot as plt import numpy as np # 初始化行空板 Board().begin() # 硬件配置 PHOTO_GATE_PIN D2 # 光电门信号线接在D2引脚 photo_gate Pin(PHOTO_GATE_PIN, Pin.IN) # 实验参数 pendulum_length 1.0 # 单摆摆长单位米。根据实际测量修改 num_cycles_to_measure 20 # 计划测量的周期数 # 数据存储列表 block_times [] # 记录每次遮挡发生的时间戳秒 periods [] # 计算出的周期值 # 全局变量用于在回调函数中记录状态 last_block_time 0 block_count 0 # 中断回调函数当光电门被遮挡下降沿触发时自动执行 def on_block_detected(): global last_block_time, block_count current_time time.time() # 获取当前时间戳精度很高 # 防抖处理如果两次触发间隔太短小于0.1秒可能是抖动忽略 if (current_time - last_block_time) 0.1: return block_times.append(current_time) block_count 1 print(f遮挡事件 #{block_count} 发生在: {current_time:.6f} s) # 如果已经记录了至少两次遮挡可以计算一个周期 if len(block_times) 2: period block_times[-1] - block_times[-2] periods.append(period) print(f 最近一个周期 T {period:.6f} s) last_block_time current_time # 如果达到了预定的测量次数可以停止测量这里我们选择手动停止 if block_count (num_cycles_to_measure * 2): # 一个周期有两次遮挡 print(\n已达到预定测量次数数据采集完成。) # 设置中断当D2引脚检测到下降沿从高电平到低电平时调用on_block_detected函数 photo_gate.irq(triggerPin.IRQ_FALLING, handleron_block_detected) print(单摆重力加速度测量系统已启动) print(请释放摆球开始摆动...) print(系统正在自动记录光电门遮挡事件。) print(等待数据... (按 CtrlC 停止采集)\n) try: # 主循环保持程序运行等待中断触发 while True: time.sleep(0.1) # 短暂睡眠降低CPU占用 # 可以在这里添加实时显示当前周期或状态的功能 pass except KeyboardInterrupt: print(\n\n手动停止数据采集。) # --- 数据分析与计算部分 --- print(\n *50) print(数据分析阶段) print(*50) if len(block_times) 3: print(错误采集到的遮挡事件太少无法计算周期。请检查光电门安装和连接。) else: # 1. 计算所有相邻遮挡时间间隔即半个周期 half_periods np.diff(block_times) # 使用numpy计算差值 # 2. 半个周期乘以2得到完整周期假设从一侧遮挡到下一次同侧遮挡 # 注意更严谨的做法是识别单摆的摆动方向这里我们假设每次遮挡都是同方向经过最低点。 # 一个更稳健的方法是我们实际记录的是连续遮挡时间相邻两个间隔之和是一个完整周期。 # 但我们这里采用更简单的方法直接取第1次和第3次第2次和第4次...的时间差作为周期。 full_periods [] for i in range(0, len(block_times)-2, 2): period block_times[i2] - block_times[i] full_periods.append(period) print(f共采集到 {len(block_times)} 次遮挡事件。) print(f计算出 {len(full_periods)} 个完整的周期值。) print(单个周期测量值 (s):, [f{p:.6f} for p in full_periods]) # 3. 求周期的平均值减少随机误差 avg_period np.mean(full_periods) print(f\n周期的平均值 T_avg {avg_period:.6f} s) # 4. 计算重力加速度 g # 单摆周期公式: T 2π * sqrt(L / g) g 4 * π² * L / T² import math g_calculated (4 * (math.pi ** 2) * pendulum_length) / (avg_period ** 2) print(f使用的摆长 L {pendulum_length} m) print(f计算得到的重力加速度 g {g_calculated:.6f} m/s²) print(f参考标准值 g ≈ 9.8 m/s²) print(f相对误差: {abs(g_calculated - 9.8)/9.8 * 100:.2f}%) # 5. 数据可视化 plt.figure(figsize(12, 5)) # 子图1遮挡事件的时间序列 plt.subplot(1, 2, 1) plt.plot(range(len(block_times)), block_times, bo-, label遮挡时刻) plt.xlabel(遮挡事件序号) plt.ylabel(时间戳 (s)) plt.title(光电门遮挡事件时间序列) plt.grid(True, linestyle--, alpha0.7) plt.legend() # 子图2周期值的分布 plt.subplot(1, 2, 2) plt.plot(range(len(full_periods)), full_periods, rs-, label周期测量值) plt.axhline(yavg_period, colorg, linestyle-, linewidth2, labelf平均值{avg_period:.4f}s) plt.xlabel(周期序号) plt.ylabel(周期 T (s)) plt.title(单摆周期测量值分布) plt.grid(True, linestyle--, alpha0.7) plt.legend() plt.tight_layout() plt.show() print(\n实验结束。)代码关键点解析与实操心得中断与防抖photo_gate.irq()这行设置了中断。Pin.IRQ_FALLING表示下降沿触发这是最常用的。on_block_detected是回调函数。函数内部的防抖判断if (current_time - last_block_time) 0.1:至关重要。因为传感器触发或摆球微小颤动可能造成短时间内多次误触发这个“时间窗”过滤能有效去除噪声。0.1秒的阈值对于周期约1-2秒的单摆是安全的。时间精度我们使用time.time()获取时间戳。在行空板这样的Linux系统上其精度通常可以达到微秒级远高于手动秒表。这是高精度测量的基础。周期计算策略代码中提供了两种思路。简单方法是用block_times[i2] - block_times[i]计算周期即隔一次遮挡。这要求摆球每次都是从同一侧穿过光电门。更严谨的方法是记录完整的来回摆动一次遮挡是“去”下一次是“回”但需要更复杂的逻辑判断。对于学生实验第一种方法足够直观和准确。数据处理与可视化我们使用了numpy和matplotlib库。np.diff能方便地计算列表相邻元素的差值。绘图部分将原始数据和计算结果可视化能直观地观察数据的稳定性和离散程度这是培养数据分析能力的好机会。4. 系统搭建、校准与实验操作全流程4.1 实验搭建步骤详解安装固定单摆使用铁架台和重物确保悬点牢固、不可移动。摆线选择不易拉伸的细线如尼龙线。摆球建议使用密度大的金属球以减少空气阻力的影响。安装与校准光电门将光电传感器固定在铁架台上置于摆球静止时的正下方。槽口方向与摆球运动方向垂直。关键校准步骤手动将摆球拉至一侧释放观察其摆动。微调光电门的高度和水平位置确保摆球在最低点速度最快时能完全且居中地穿过传感器的槽隙。你可以用行空板先运行一个简单的测试程序如打印引脚状态观察摆动时是否能稳定触发。理想状态是一次完整的来回摆动应触发两次一去一回。连接电路按照前述方法连接行空板和光电传感器。务必确保连接牢固避免实验过程中松动。测量摆长L用米尺测量从悬点到摆球球心的距离。这是实验中最大的误差来源之一务必多次测量取平均精确到毫米。软件准备将上面的代码通过U盘或网络传输到行空板用Thonny或Jupyter打开。修改代码中的pendulum_length变量为你实际测量的摆长。同时可以调整num_cycles_to_measure变量决定计划测量多少个周期建议20-30个以减小随机误差。4.2 进行实验与数据采集运行Python程序。控制台会显示“系统已启动...”。将摆球从平衡位置拉开一个较小角度务必小于5度以满足单摆简谐运动的条件保持静止。确认程序运行正常后释放摆球。程序会自动记录每次遮挡的时间。观察控制台输出确认遮挡事件被稳定记录。等待摆球完成预定次数的摆动例如20个完整周期。按CtrlC停止程序。数据分析部分会自动运行计算并显示平均周期和重力加速度g并绘制图表。4.3 提高测量精度的关键技巧摆角要小这是理论公式T 2π√(L/g)成立的前提。角度越大周期修正项越显著。用角度尺辅助确保起始角θ 5°。摆长测量要准悬点位置和球心位置不易确定。可以先用一个重锤线确定悬点正下方的点再测量悬点到该点的距离加上摆球半径。球半径可用游标卡尺测量。测量多个周期测量n个周期的总时间t则周期 T t/n。这样可以抵消一部分人为释放误差和计时误差。我们的程序虽然记录了每个周期但最终也是取多个周期的平均值。环境选择尽量在无风、稳定的环境下实验避免空气流动和悬点晃动。光电门优化确保摆球遮挡时光束被完全切断避免“半遮挡”状态造成触发时间的不确定性。可以使用不透明的、表面光滑的金属球。5. 常见问题排查与深度优化在实际带领学生操作时一定会遇到各种问题。下面这个排查表是我总结的“血泪经验”。问题现象可能原因排查与解决方法程序运行后无任何触发记录1. 电路连接错误或松动。2. 光电门供电不足或损坏。3. 引脚号配置错误。4. 摆球未遮挡光束或遮挡不完全。1. 用万用表检查VCC和GND间电压是否为3.3V。用导线短接OUT和GND看程序是否能触发模拟遮挡。2. 运行一个简单的引脚状态读取循环程序手动遮挡观察引脚电平是否变化。3. 确认代码中PHOTO_GATE_PIN变量与实际连接的引脚一致。触发记录过于频繁远快于单摆周期1. 传感器抖动或光线干扰如环境光变化。2. 代码中缺少防抖逻辑。1. 为光电门加装遮光罩避免环境光直射接收管。2.务必在回调函数中添加时间间隔判断如代码中的防抖处理过滤掉短时间内的多次触发。触发记录不稳定时有时无1. 摆球摆动轨迹不经过光电门中心。2. 连接线接触不良。3. 摆球表面反光或透光。1. 仔细调整光电门位置确保摆球在最低点轨迹稳定穿过槽中心。2. 检查并重新插拔所有杜邦线。3. 使用哑光黑色涂层的摆球或直接用黑色电工胶带包裹。计算出的g值偏差很大5%1.摆长L测量错误最常见。2. 摆角过大。3. 周期测量值数量太少偶然误差大。4. 光电门触发位置不是最低点。1. 重新精确测量摆长特别是球心位置。2. 确保释放角小于5度。3. 增加测量周期数到30个以上。4. 微调光电门至最低点并尝试测量不同摆长下的周期用T²-L图像法求g可以消除系统误差。行空板程序报错或库找不到1. Pinpong库未安装或版本不匹配。2. Python环境问题。1. 在行空板终端执行pip install pinpong安装或更新库。2. 确认使用的是行空板自带的Python环境而非其他虚拟环境。5.1 项目深度优化方向当基础功能实现后这个项目还有巨大的拓展空间可以引导学生进行探究式学习研究摆角对周期的影响修改程序除了记录时间还可以通过图像识别用行空板连接摄像头或角度传感器同时记录释放角。验证周期公式的修正项T 2π√(L/g) * (1 θ²/16 ...)。探究摆长与周期的关系改变摆长L测量对应的周期T。让学生将数据录入表格绘制T²关于L的图像。根据公式T² (4π²/g) * L图像应是一条过原点的直线其斜率等于4π²/g从而可以再次求解g这种方法比单次测量更精确。开发图形化实时显示界面利用行空板自带的屏幕和unihiker等GUI库制作一个实时仪表盘动态显示当前周期、实时g值、摆动次数以及实时的位移-时间模拟曲线。研究空气阻力影响使用不同大小、材质的摆球进行实验对比周期和振幅衰减的情况定性分析空气阻力的影响。数据上传与云端分析利用行空板的Wi-Fi功能将采集到的时间戳数据上传到云端服务器如ThingsBoard、EasyIoT进行更复杂的统计分析或实现多组实验数据的远程汇总与对比。这个项目最让我兴奋的地方在于它把一个经典的验证性实验变成了一个开放的、可探究的、融合了硬件、软件和物理原理的综合性项目。学生不再是被动地操作仪器、记录数据而是主动地设计系统、编写逻辑、分析结果、排查问题。当屏幕上第一次跳出那个接近9.8的g值时他们眼中闪烁的光芒远比仅仅在实验报告上填一个数字要明亮得多。这或许就是技术赋能教育、学科融合最本真的价值。