上位机代码编写
根据比赛提供的《赛题说明》,上位机需要在输入绳长时,计算并显示对应的重力加速度;在输入重力加速度时,计算并显示单摆绳长,并需要模拟单摆动画。将上述消息发送给AI生成代码。
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import serial
import serial.tools.list_ports
import threading
import time
import re
import math
class PendulumExperiment:
def __init__(self, root):
self.root = root
self.root.title("单摆实验模拟器")
self.root.geometry("800x600")
self.root.resizable(True, True)
# 实验数据变量
self.port = None
self.ser = None
self.is_connected = False
self.get1_time = 0.0 # 第一个光电门时间(us)
self.get2_time = 0.0 # 第二个光电门时间(us)
self.direction = "未知" # 运动方向
self.string_length = tk.StringVar(value="1.0") # 绳长(m)
self.gravity = tk.StringVar(value="9.8") # 重力加速度(m/s²)
self.period = 0.0 # 周期(s)
# 创建界面
self.create_widgets()
# 启动串口监听线程
self.running = True
self.listen_thread = threading.Thread(target=self.listen_serial, daemon=True)
self.listen_thread.start()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 串口设置区域
serial_frame = ttk.LabelFrame(main_frame, text="串口设置", padding="10")
serial_frame.pack(fill=tk.X, pady=5)
ttk.Label(serial_frame, text="串口号:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.port_var = tk.StringVar()
self.port_combobox = ttk.Combobox(serial_frame, textvariable=self.port_var, width=10)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(serial_frame, text="波特率:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
self.baud_var = tk.StringVar(value="9600")
self.baud_combobox = ttk.Combobox(serial_frame, textvariable=self.baud_var,
values=["9600", "19200", "38400", "57600", "115200"], width=10)
self.baud_combobox.grid(row=0, column=3, padx=5, pady=5)
self.refresh_btn = ttk.Button(serial_frame, text="刷新", command=self.refresh_ports)
self.refresh_btn.grid(row=0, column=4, padx=5, pady=5)
self.connect_btn = ttk.Button(serial_frame, text="连接", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=5, padx=5, pady=5)
# 数据显示区域
data_frame = ttk.LabelFrame(main_frame, text="实验数据", padding="10")
data_frame.pack(fill=tk.X, pady=5)
# 光电门时间
ttk.Label(data_frame, text="光电门1时间(us):").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
self.get1_label = ttk.Label(data_frame, text="0.0")
self.get1_label.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(data_frame, text="光电门2时间(us):").grid(row=0, column=2, padx=10, pady=5, sticky=tk.W)
self.get2_label = ttk.Label(data_frame, text="0.0")
self.get2_label.grid(row=0, column=3, padx=10, pady=5, sticky=tk.W)
# 运动方向
ttk.Label(data_frame, text="小球运动方向:").grid(row=1, column=0, padx=10, pady=5, sticky=tk.W)
self.direction_label = ttk.Label(data_frame, text="未知")
self.direction_label.grid(row=1, column=1, padx=10, pady=5, sticky=tk.W)
# 周期
ttk.Label(data_frame, text="周期(s):").grid(row=1, column=2, padx=10, pady=5, sticky=tk.W)
self.period_label = ttk.Label(data_frame, text="0.0")
self.period_label.grid(row=1, column=3, padx=10, pady=5, sticky=tk.W)
# 计算区域
calc_frame = ttk.LabelFrame(main_frame, text="参数计算", padding="10")
calc_frame.pack(fill=tk.X, pady=5)
ttk.Label(calc_frame, text="绳长(m):").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
ttk.Entry(calc_frame, textvariable=self.string_length, width=15).grid(row=0, column=1, padx=10, pady=5)
ttk.Label(calc_frame, text="重力加速度(m/s²):").grid(row=0, column=2, padx=10, pady=5, sticky=tk.W)
ttk.Entry(calc_frame, textvariable=self.gravity, width=15).grid(row=0, column=3, padx=10, pady=5)
self.calc_g_btn = ttk.Button(calc_frame, text="由绳长计算重力加速度", command=self.calculate_gravity)
self.calc_g_btn.grid(row=0, column=4, padx=10, pady=5)
self.calc_l_btn = ttk.Button(calc_frame, text="由重力加速度计算绳长", command=self.calculate_length)
self.calc_l_btn.grid(row=0, column=5, padx=10, pady=5)
# 单摆模拟区域
sim_frame = ttk.LabelFrame(main_frame, text="单摆模拟", padding="10")
sim_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.canvas = tk.Canvas(sim_frame, bg="white")
self.canvas.pack(fill=tk.BOTH, expand=True)
# 初始绘制单摆
self.draw_pendulum(0)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="日志", padding="10")
log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.log_text = scrolledtext.ScrolledText(log_frame, height=5, wrap=tk.WORD)
self.log_text.pack(fill=tk.BOTH, expand=True)
self.log_text.config(state=tk.DISABLED)
# 刷新端口列表
self.refresh_ports()
def refresh_ports(self):
"""刷新可用串口号列表"""
ports = serial.tools.list_ports.comports()
self.port_combobox['values'] = [port.device for port in ports]
if ports:
self.port_combobox.current(0)
def toggle_connection(self):
"""切换串口连接状态"""
if self.is_connected:
self.disconnect()
else:
self.connect()
def connect(self):
"""连接到指定串口"""
try:
port = self.port_var.get()
baudrate = int(self.baud_var.get())
if not port:
messagebox.showerror("错误", "请选择串口号")
return
self.ser = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.connect_btn.config(text="断开")
self.log("已连接到串口: " + port)
except Exception as e:
messagebox.showerror("连接错误", str(e))
self.log("连接失败: " + str(e))
def disconnect(self):
"""断开串口连接"""
if self.ser and self.ser.is_open:
self.ser.close()
self.is_connected = False
self.connect_btn.config(text="连接")
self.log("已断开串口连接")
def listen_serial(self):
"""监听串口数据的线程函数"""
while self.running:
if self.is_connected and self.ser and self.ser.is_open:
try:
data = self.ser.readline().decode().strip()
if data:
self.log(f"收到数据: {data}")
self.process_data(data)
except Exception as e:
self.log(f"串口错误: {str(e)}")
self.is_connected = False
self.root.after(0, lambda: self.connect_btn.config(text="连接"))
time.sleep(0.01)
def process_data(self, data):
"""处理收到的串口数据"""
# 匹配格式 get1:XXX,get2:XXX
pattern = r'get1:(\d+),get2:(\d+)'
match = re.match(pattern, data)
if match:
try:
new_get1 = float(match.group(1))
new_get2 = float(match.group(2))
# 更新时间显示
self.root.after(0, lambda: self.get1_label.config(text=str(new_get1)))
self.root.after(0, lambda: self.get2_label.config(text=str(new_get2)))
# 判断运动方向
direction = "从光电门1到光电门2" if new_get1 < new_get2 else "从光电门2到光电门1"
self.direction = direction
self.root.after(0, lambda: self.direction_label.config(text=direction))
# 计算周期 (两个光电门时间差的2倍,假设经过两个光电门为半周期)
time_diff = abs(new_get1 - new_get2) / 1000000 # 转换为秒
self.period = time_diff * 2
self.root.after(0, lambda: self.period_label.config(text=f"{self.period:.6f}"))
# 更新单摆模拟
angle = math.sin(time.time() * 2) * 30 # 简单的摆动模拟
self.root.after(0, lambda: self.draw_pendulum(angle if direction == "从光电门1到光电门2" else -angle))
# 保存新时间值
self.get1_time = new_get1
self.get2_time = new_get2
except ValueError as e:
self.log(f"数据解析错误: {str(e)}")
def draw_pendulum(self, angle_deg):
"""在画布上绘制单摆"""
self.canvas.delete("all")
# 画布尺寸
width = self.canvas.winfo_width() or 600
height = self.canvas.winfo_height() or 300
# 摆长 (根据画布高度调整)
pendulum_length = min(width, height) * 0.4
# 悬挂点
pivot_x = width / 2
pivot_y = height / 3
# 计算摆球位置
angle_rad = math.radians(angle_deg)
ball_x = pivot_x + pendulum_length * math.sin(angle_rad)
ball_y = pivot_y + pendulum_length * math.cos(angle_rad)
# 绘制悬挂点
self.canvas.create_oval(pivot_x - 5, pivot_y - 5, pivot_x + 5, pivot_y + 5, fill="black")
# 绘制摆线
self.canvas.create_line(pivot_x, pivot_y, ball_x, ball_y, width=2)
# 绘制摆球
ball_radius = 15
self.canvas.create_oval(ball_x - ball_radius, ball_y - ball_radius,
ball_x + ball_radius, ball_y + ball_radius, fill="red")
# 绘制光电门
gate1_x = pivot_x - pendulum_length * 0.7
gate2_x = pivot_x + pendulum_length * 0.7
gate_y = pivot_y + pendulum_length * 0.5
# 光电门1
self.canvas.create_line(gate1_x - 10, gate_y - 20, gate1_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate1_x + 10, gate_y - 20, gate1_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate1_x, gate_y - 30, text="光电门1")
# 光电门2
self.canvas.create_line(gate2_x - 10, gate_y - 20, gate2_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate2_x + 10, gate_y - 20, gate2_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate2_x, gate_y - 30, text="光电门2")
def calculate_gravity(self):
"""根据绳长和周期计算重力加速度"""
try:
l = float(self.string_length.get())
if self.period <= 0:
messagebox.showwarning("警告", "请先获取周期数据")
return
# 单摆周期公式: T = 2π√(l/g) → g = 4π²l/T²
g = (4 * math.pi **2 * l) / (self.period** 2)
self.gravity.set(f"{g:.4f}")
self.log(f"计算得到重力加速度: {g:.4f} m/s²")
except ValueError:
messagebox.showerror("错误", "请输入有效的绳长数值")
def calculate_length(self):
"""根据重力加速度和周期计算绳长"""
try:
g = float(self.gravity.get())
if self.period <= 0:
messagebox.showwarning("警告", "请先获取周期数据")
return
# 单摆周期公式: T = 2π√(l/g) → l = gT²/(4π²)
l = (g * self.period **2) / (4 * math.pi** 2)
self.string_length.set(f"{l:.4f}")
self.log(f"计算得到绳长: {l:.4f} m")
except ValueError:
messagebox.showerror("错误", "请输入有效的重力加速度数值")
def log(self, message):
"""添加日志信息"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def on_close(self):
"""关闭窗口时的处理"""
self.running = False
self.disconnect()
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = PendulumExperiment(root)
root.protocol("WM_DELETE_WINDOW", app.on_close)
root.mainloop()
运行代码,发现单摆模型不正常,需要增加更多描述,光电门的位置在绳子垂直时的左右两测,当摆动时,绳子摆动幅度大于两个光电门的间距,请修改一下单摆模拟,另外,单摆模拟可以固定摆动幅度,只需要示意其摆动的方向即可,即定性不定量,让AI修改代码
def process_data(self, data):
"""处理收到的串口数据"""
# 匹配格式 get1:XXX,get2:XXX
pattern = r'get1:(\d+),get2:(\d+)'
match = re.match(pattern, data)
if match:
try:
new_get1 = float(match.group(1))
new_get2 = float(match.group(2))
# 更新时间显示
self.root.after(0, lambda: self.get1_label.config(text=str(new_get1)))
self.root.after(0, lambda: self.get2_label.config(text=str(new_get2)))
# 判断运动方向
direction = "从光电门1到光电门2" if new_get1 < new_get2 else "从光电门2到光电门1"
self.direction = direction
self.root.after(0, lambda: self.direction_label.config(text=direction))
# 计算周期 (两个光电门时间差的2倍,假设经过两个光电门为半周期)
time_diff = abs(new_get1 - new_get2) / 1000000 # 转换为秒
self.period = time_diff * 2
self.root.after(0, lambda: self.period_label.config(text=f"{self.period:.6f}"))
# 更新单摆模拟 - 使用固定幅度,仅体现方向变化
self.root.after(0, self.update_pendulum_direction)
# 保存新时间值
self.get1_time = new_get1
self.get2_time = new_get2
except ValueError as e:
self.log(f"数据解析错误: {str(e)}")
def update_pendulum_direction(self):
"""根据运动方向更新单摆位置,使用固定幅度"""
# 固定摆动角度幅度为30度
if self.direction == "从光电门1到光电门2":
angle = 30 # 向右摆动到最大幅度
else:
angle = -30 # 向左摆动到最大幅度
self.draw_pendulum(angle)
def draw_pendulum(self, angle_deg):
"""在画布上绘制单摆,调整光电门位置为垂直位置两侧"""
self.canvas.delete("all")
# 画布尺寸
width = self.canvas.winfo_width() or 600
height = self.canvas.winfo_height() or 300
# 摆长 (根据画布高度调整)
pendulum_length = min(width, height) * 0.4
# 悬挂点
pivot_x = width / 2
pivot_y = height / 3
# 计算摆球位置
angle_rad = math.radians(angle_deg)
ball_x = pivot_x + pendulum_length * math.sin(angle_rad)
ball_y = pivot_y + pendulum_length * math.cos(angle_rad)
# 绘制悬挂点
self.canvas.create_oval(pivot_x - 5, pivot_y - 5, pivot_x + 5, pivot_y + 5, fill="black")
# 绘制摆线
self.canvas.create_line(pivot_x, pivot_y, ball_x, ball_y, width=2)
# 绘制摆球
ball_radius = 15
self.canvas.create_oval(ball_x - ball_radius, ball_y - ball_radius,
ball_x + ball_radius, ball_y + ball_radius, fill="red")
# 调整光电门位置:位于绳子垂直时(平衡位置)的左右两侧
# 垂直位置时摆球的x坐标(平衡位置)
balance_x = pivot_x
# 光电门距离平衡位置的水平距离(约为摆长的1/4)
gate_offset = pendulum_length * 0.25
gate1_x = balance_x - gate_offset # 平衡位置左侧
gate2_x = balance_x + gate_offset # 平衡位置右侧
gate_y = pivot_y + pendulum_length * 0.5 # 垂直方向位置不变
# 光电门1(左侧)
self.canvas.create_line(gate1_x - 10, gate_y - 20, gate1_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate1_x + 10, gate_y - 20, gate1_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate1_x, gate_y - 30, text="光电门1")
# 光电门2(右侧)
self.canvas.create_line(gate2_x - 10, gate_y - 20, gate2_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate2_x + 10, gate_y - 20, gate2_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate2_x, gate_y - 30, text="光电门2")
# 绘制平衡位置参考线(虚线)
self.canvas.create_line(balance_x, pivot_y, balance_x, pivot_y + pendulum_length,
dash=(2, 2), fill="gray")
修改后,又发现其计算的重力加速度数值不正确,怀疑是将两次光电门触发的时间之差作为单摆周期时间,给AI以下提示词修改代码:1当没有检测时间大于3秒后,绳子垂直于平面 2这个计算重力加速度有问题,应该是获取三次的时间数据,用第一次与第三次的时间差为单摆周期的时间
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import serial
import serial.tools.list_ports
import threading
import time
import re
import math
class PendulumExperiment:
def __init__(self, root):
self.root = root
self.root.title("单摆实验模拟器")
self.root.geometry("800x600")
self.root.resizable(True, True)
# 实验数据变量
self.port = None
self.ser = None
self.is_connected = False
self.time_records = [] # 存储时间记录,用于计算周期
self.max_records = 3 # 最多保存3条记录
self.direction = "未知" # 运动方向
self.string_length = tk.StringVar(value="1.0") # 绳长(m)
self.gravity = tk.StringVar(value="9.8") # 重力加速度(m/s²)
self.period = 0.0 # 周期(s)
self.last_detection_time = time.time() # 最后一次检测时间
self.current_angle = 0 # 当前摆角
# 创建界面
self.create_widgets()
# 启动串口监听线程
self.running = True
self.listen_thread = threading.Thread(target=self.listen_serial, daemon=True)
self.listen_thread.start()
# 启动位置检查定时器
self.check_position()
def create_widgets(self):
# 创建主框架
main_frame = ttk.Frame(self.root, padding="10")
main_frame.pack(fill=tk.BOTH, expand=True)
# 串口设置区域
serial_frame = ttk.LabelFrame(main_frame, text="串口设置", padding="10")
serial_frame.pack(fill=tk.X, pady=5)
ttk.Label(serial_frame, text="串口号:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
self.port_var = tk.StringVar()
self.port_combobox = ttk.Combobox(serial_frame, textvariable=self.port_var, width=10)
self.port_combobox.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(serial_frame, text="波特率:").grid(row=0, column=2, padx=5, pady=5, sticky=tk.W)
self.baud_var = tk.StringVar(value="9600")
self.baud_combobox = ttk.Combobox(serial_frame, textvariable=self.baud_var,
values=["9600", "19200", "38400", "57600", "115200"], width=10)
self.baud_combobox.grid(row=0, column=3, padx=5, pady=5)
self.refresh_btn = ttk.Button(serial_frame, text="刷新", command=self.refresh_ports)
self.refresh_btn.grid(row=0, column=4, padx=5, pady=5)
self.connect_btn = ttk.Button(serial_frame, text="连接", command=self.toggle_connection)
self.connect_btn.grid(row=0, column=5, padx=5, pady=5)
# 数据显示区域
data_frame = ttk.LabelFrame(main_frame, text="实验数据", padding="10")
data_frame.pack(fill=tk.X, pady=5)
# 光电门时间
ttk.Label(data_frame, text="光电门1时间(us):").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
self.get1_label = ttk.Label(data_frame, text="0.0")
self.get1_label.grid(row=0, column=1, padx=10, pady=5, sticky=tk.W)
ttk.Label(data_frame, text="光电门2时间(us):").grid(row=0, column=2, padx=10, pady=5, sticky=tk.W)
self.get2_label = ttk.Label(data_frame, text="0.0")
self.get2_label.grid(row=0, column=3, padx=10, pady=5, sticky=tk.W)
# 运动方向
ttk.Label(data_frame, text="小球运动方向:").grid(row=1, column=0, padx=10, pady=5, sticky=tk.W)
self.direction_label = ttk.Label(data_frame, text="未知")
self.direction_label.grid(row=1, column=1, padx=10, pady=5, sticky=tk.W)
# 周期
ttk.Label(data_frame, text="周期(s):").grid(row=1, column=2, padx=10, pady=5, sticky=tk.W)
self.period_label = ttk.Label(data_frame, text="0.0")
self.period_label.grid(row=1, column=3, padx=10, pady=5, sticky=tk.W)
# 时间记录显示
ttk.Label(data_frame, text="时间记录:").grid(row=2, column=0, padx=10, pady=5, sticky=tk.W)
self.records_label = ttk.Label(data_frame, text="无数据")
self.records_label.grid(row=2, column=1, padx=10, pady=5, sticky=tk.W)
# 计算区域
calc_frame = ttk.LabelFrame(main_frame, text="参数计算", padding="10")
calc_frame.pack(fill=tk.X, pady=5)
ttk.Label(calc_frame, text="绳长(m):").grid(row=0, column=0, padx=10, pady=5, sticky=tk.W)
ttk.Entry(calc_frame, textvariable=self.string_length, width=15).grid(row=0, column=1, padx=10, pady=5)
ttk.Label(calc_frame, text="重力加速度(m/s²):").grid(row=0, column=2, padx=10, pady=5, sticky=tk.W)
ttk.Entry(calc_frame, textvariable=self.gravity, width=15).grid(row=0, column=3, padx=10, pady=5)
self.calc_g_btn = ttk.Button(calc_frame, text="由绳长计算重力加速度", command=self.calculate_gravity)
self.calc_g_btn.grid(row=0, column=4, padx=10, pady=5)
self.calc_l_btn = ttk.Button(calc_frame, text="由重力加速度计算绳长", command=self.calculate_length)
self.calc_l_btn.grid(row=0, column=5, padx=10, pady=5)
# 单摆模拟区域
sim_frame = ttk.LabelFrame(main_frame, text="单摆模拟", padding="10")
sim_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.canvas = tk.Canvas(sim_frame, bg="white")
self.canvas.pack(fill=tk.BOTH, expand=True)
# 初始绘制单摆(垂直位置)
self.draw_pendulum(0)
# 日志区域
log_frame = ttk.LabelFrame(main_frame, text="日志", padding="10")
log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
self.log_text = scrolledtext.ScrolledText(log_frame, height=5, wrap=tk.WORD)
self.log_text.pack(fill=tk.BOTH, expand=True)
self.log_text.config(state=tk.DISABLED)
# 刷新端口列表
self.refresh_ports()
def refresh_ports(self):
"""刷新可用串口号列表"""
ports = serial.tools.list_ports.comports()
self.port_combobox['values'] = [port.device for port in ports]
if ports:
self.port_combobox.current(0)
def toggle_connection(self):
"""切换串口连接状态"""
if self.is_connected:
self.disconnect()
else:
self.connect()
def connect(self):
"""连接到指定串口"""
try:
port = self.port_var.get()
baudrate = int(self.baud_var.get())
if not port:
messagebox.showerror("错误", "请选择串口号")
return
self.ser = serial.Serial(port, baudrate, timeout=1)
self.is_connected = True
self.connect_btn.config(text="断开")
self.log("已连接到串口: " + port)
except Exception as e:
messagebox.showerror("连接错误", str(e))
self.log("连接失败: " + str(e))
def disconnect(self):
"""断开串口连接"""
if self.ser and self.ser.is_open:
self.ser.close()
self.is_connected = False
self.connect_btn.config(text="连接")
self.log("已断开串口连接")
def listen_serial(self):
"""监听串口数据的线程函数"""
while self.running:
if self.is_connected and self.ser and self.ser.is_open:
try:
data = self.ser.readline().decode().strip()
if data:
self.log(f"收到数据: {data}")
self.process_data(data)
self.last_detection_time = time.time() # 更新最后检测时间
except Exception as e:
self.log(f"串口错误: {str(e)}")
self.is_connected = False
self.root.after(0, lambda: self.connect_btn.config(text="连接"))
time.sleep(0.01)
def check_position(self):
"""检查是否需要将单摆重置到垂直位置"""
if time.time() - self.last_detection_time > 3: # 3秒未检测到数据
if self.current_angle != 0:
self.current_angle = 0
self.root.after(0, lambda: self.draw_pendulum(0))
self.log("3秒未检测到数据,单摆已重置到垂直位置")
# 继续定时检查
self.root.after(1000, self.check_position) # 每1秒检查一次
def process_data(self, data):
"""处理收到的串口数据"""
# 匹配格式 get1:XXX,get2:XXX
pattern = r'get1:(\d+),get2:(\d+)'
match = re.match(pattern, data)
if match:
try:
new_get1 = float(match.group(1))
new_get2 = float(match.group(2))
# 更新时间显示
self.root.after(0, lambda: self.get1_label.config(text=str(new_get1)))
self.root.after(0, lambda: self.get2_label.config(text=str(new_get2)))
# 判断运动方向
direction = "从光电门1到光电门2" if new_get1 < new_get2 else "从光电门2到光电门1"
self.direction = direction
self.root.after(0, lambda: self.direction_label.config(text=direction))
# 保存时间记录(使用两个光电门的平均时间)
avg_time = (new_get1 + new_get2) / 2
self.time_records.append(avg_time)
# 只保留最近的3条记录
if len(self.time_records) > self.max_records:
self.time_records.pop(0)
# 更新记录显示
records_text = ", ".join([f"{t:.1f}" for t in self.time_records])
self.root.after(0, lambda: self.records_label.config(text=records_text))
# 计算周期(使用第一次和第三次的时间差)
if len(self.time_records) >= 3:
time_diff = (self.time_records[2] - self.time_records[0]) / 1000000 # 转换为秒
self.period = time_diff
self.root.after(0, lambda: self.period_label.config(text=f"{self.period:.6f}"))
self.log(f"计算周期: {self.period:.6f} s (使用第1和第3次数据)")
# 更新单摆模拟(固定幅度,只体现方向)
angle = 30 if direction == "从光电门1到光电门2" else -30
self.current_angle = angle
self.root.after(0, lambda: self.draw_pendulum(angle))
except ValueError as e:
self.log(f"数据解析错误: {str(e)}")
def draw_pendulum(self, angle_deg):
"""在画布上绘制单摆,光电门位于垂直位置两侧"""
self.canvas.delete("all")
# 画布尺寸
width = self.canvas.winfo_width() or 600
height = self.canvas.winfo_height() or 300
# 摆长 (根据画布高度调整)
pendulum_length = min(width, height) * 0.4
# 悬挂点
pivot_x = width / 2
pivot_y = height / 3
# 计算摆球位置
angle_rad = math.radians(angle_deg)
ball_x = pivot_x + pendulum_length * math.sin(angle_rad)
ball_y = pivot_y + pendulum_length * math.cos(angle_rad)
# 绘制悬挂点
self.canvas.create_oval(pivot_x - 5, pivot_y - 5, pivot_x + 5, pivot_y + 5, fill="black")
# 绘制摆线
self.canvas.create_line(pivot_x, pivot_y, ball_x, ball_y, width=2)
# 绘制摆球
ball_radius = 15
self.canvas.create_oval(ball_x - ball_radius, ball_y - ball_radius,
ball_x + ball_radius, ball_y + ball_radius, fill="red")
# 绘制平衡位置参考线(虚线)
self.canvas.create_line(pivot_x, pivot_y, pivot_x, pivot_y + pendulum_length,
dash=(2, 2), fill="gray")
# 调整光电门位置:位于绳子垂直时(平衡位置)的左右两侧
# 垂直位置时摆球的x坐标(平衡位置)
balance_x = pivot_x
# 光电门距离平衡位置的水平距离(约为摆长的1/4)
gate_offset = pendulum_length * 0.25
gate1_x = balance_x - gate_offset # 平衡位置左侧
gate2_x = balance_x + gate_offset # 平衡位置右侧
gate_y = pivot_y + pendulum_length * 0.5 # 垂直方向位置
# 光电门1(左侧)
self.canvas.create_line(gate1_x - 10, gate_y - 20, gate1_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate1_x + 10, gate_y - 20, gate1_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate1_x, gate_y - 30, text="光电门1")
# 光电门2(右侧)
self.canvas.create_line(gate2_x - 10, gate_y - 20, gate2_x - 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_line(gate2_x + 10, gate_y - 20, gate2_x + 10, gate_y + 20, width=2, dash=(4, 2))
self.canvas.create_text(gate2_x, gate_y - 30, text="光电门2")
def calculate_gravity(self):
"""根据绳长和周期计算重力加速度"""
try:
l = float(self.string_length.get())
if self.period <= 0 or len(self.time_records) < 3:
messagebox.showwarning("警告", "请先获取至少3组时间数据")
return
# 单摆周期公式: T = 2π√(l/g) → g = 4π²l/T²
g = (4 * math.pi **2 * l) / (self.period** 2)
self.gravity.set(f"{g:.4f}")
self.log(f"计算得到重力加速度: {g:.4f} m/s²")
except ValueError:
messagebox.showerror("错误", "请输入有效的绳长数值")
def calculate_length(self):
"""根据重力加速度和周期计算绳长"""
try:
g = float(self.gravity.get())
if self.period <= 0 or len(self.time_records) < 3:
messagebox.showwarning("警告", "请先获取至少3组时间数据")
return
# 单摆周期公式: T = 2π√(l/g) → l = gT²/(4π²)
l = (g * self.period **2) / (4 * math.pi** 2)
self.string_length.set(f"{l:.4f}")
self.log(f"计算得到绳长: {l:.4f} m")
except ValueError:
messagebox.showerror("错误", "请输入有效的重力加速度数值")
def log(self, message):
"""添加日志信息"""
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, f"{time.strftime('%H:%M:%S')} - {message}\n")
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def on_close(self):
"""关闭窗口时的处理"""
self.running = False
self.disconnect()
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = PendulumExperiment(root)
root.protocol("WM_DELETE_WINDOW", app.on_close)
root.mainloop()
至此上位机代码完成
