import tkinter as tk from tkinter import ttk, messagebox import serial import serial.tools.list_ports import threading import time from datetime import datetime import crcmod class ATEApplication: def __init__(self, root): self.root = root self.root.title("LARK-1FQ ATE设备监控") self.root.geometry("680x480") # 串口相关变量 self.serial_port = None self.is_connected = False self.read_thread = None self.stop_thread = False self.receive_buffer = bytearray() # 接收缓冲区 # 创建CRC计算函数 self.crc16_func = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000) # 数据存储 self.frequency_values = [0, 0, 0, 0] # ch0-ch3 频率值 self.peak_values = [0, 0, 0, 0] # ch0-ch3 峰峰值 self.key_status = 0 # 按键状态 self.sd_status = 0 # SD卡状态 # 上下限设置 self.freq_limits = [ {"min": 7, "max": 9}, # ch0 {"min": 7, "max": 9}, # ch1 {"min": 7, "max": 9}, # ch2 {"min": 7, "max": 9} # ch3 ] self.peak_limits = [ {"min": 2000, "max": 4000}, # ch0 {"min": 2000, "max": 4000}, # ch1 {"min": 2000, "max": 4000}, # ch2 {"min": 2000, "max": 4000} # ch3 ] self.create_widgets() self.refresh_ports() # 设置关闭事件 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) def create_widgets(self): # 1. 串口相关界面 serial_frame = ttk.LabelFrame(self.root, text="串口设置", padding=10) serial_frame.pack(fill="x", padx=10, pady=5) # 串口号选择 ttk.Label(serial_frame, text="串口号:").grid(row=0, column=0, padx=5) self.port_var = tk.StringVar() self.port_combobox = ttk.Combobox(serial_frame, textvariable=self.port_var, width=15) self.port_combobox.grid(row=0, column=1, padx=5) self.refresh_btn = ttk.Button(serial_frame, text="刷新", command=self.refresh_ports) self.refresh_btn.grid(row=0, column=2, padx=5) # 波特率选择 ttk.Label(serial_frame, text="波特率:").grid(row=0, column=3, padx=5) self.baud_var = tk.StringVar(value="19200") baud_combobox = ttk.Combobox(serial_frame, textvariable=self.baud_var, values=["9600", "19200", "38400", "57600", "115200"], width=10) baud_combobox.grid(row=0, column=4, padx=5) # 打开关闭按钮 self.open_btn = ttk.Button(serial_frame, text="打开串口", command=self.open_serial) self.open_btn.grid(row=0, column=5, padx=5) self.close_btn = ttk.Button(serial_frame, text="关闭串口", command=self.close_serial, state="disabled") self.close_btn.grid(row=0, column=6, padx=5) # 调试信息显示 self.debug_var = tk.StringVar(value="等待连接...") debug_label = ttk.Label(serial_frame, textvariable=self.debug_var, foreground="blue") debug_label.grid(row=1, column=0, columnspan=7, pady=5) # 2. 数值比较界面 value_frame = ttk.LabelFrame(self.root, text="数值比较", padding=10) value_frame.pack(fill="both", expand=True, padx=10, pady=5) # 频率部分(左边) freq_frame = ttk.LabelFrame(value_frame, text="频率 (Hz)") freq_frame.pack(side="left", fill="both", expand=True, padx=5) self.freq_widgets = [] for i in range(4): ch_frame = ttk.Frame(freq_frame) ch_frame.pack(fill="x", pady=2) ttk.Label(ch_frame, text=f"CH{i}:").pack(side="left") # 最小值输入 min_frame = ttk.Frame(ch_frame) min_frame.pack(side="left", padx=2) ttk.Label(min_frame, text="min:").pack() min_var = tk.StringVar(value="7") min_entry = tk.Entry(min_frame, textvariable=min_var, width=5, bg="white") min_entry.pack() min_entry.bind('', lambda e, ch=i: self.update_freq_limit(ch)) # 当前值显示 value_label = tk.Label(ch_frame, text="0.0", width=8, relief="sunken", bg="white") value_label.pack(side="left", padx=5) # 最大值输入 max_frame = ttk.Frame(ch_frame) max_frame.pack(side="left", padx=2) ttk.Label(max_frame, text="max:").pack() max_var = tk.StringVar(value="9") max_entry = tk.Entry(max_frame, textvariable=max_var, width=5, bg="white") max_entry.pack() max_entry.bind('', lambda e, ch=i: self.update_freq_limit(ch)) self.freq_widgets.append({ "min_var": min_var, "max_var": max_var, "min_entry": min_entry, "max_entry": max_entry, "value_label": value_label }) # 峰峰值部分(右边) peak_frame = ttk.LabelFrame(value_frame, text="峰峰值 (mV)") peak_frame.pack(side="right", fill="both", expand=True, padx=5) self.peak_widgets = [] for i in range(4): ch_frame = ttk.Frame(peak_frame) ch_frame.pack(fill="x", pady=2) ttk.Label(ch_frame, text=f"CH{i}:").pack(side="left") # 最小值输入 min_frame = ttk.Frame(ch_frame) min_frame.pack(side="left", padx=2) ttk.Label(min_frame, text="min:").pack() min_var = tk.StringVar(value="2000") min_entry = tk.Entry(min_frame, textvariable=min_var, width=5, bg="white") min_entry.pack() min_entry.bind('', lambda e, ch=i: self.update_peak_limit(ch)) # 当前值显示 value_label = tk.Label(ch_frame, text="0", width=8, relief="sunken", bg="white") value_label.pack(side="left", padx=5) # 最大值输入 max_frame = ttk.Frame(ch_frame) max_frame.pack(side="left", padx=2) ttk.Label(max_frame, text="max:").pack() max_var = tk.StringVar(value="4000") max_entry = tk.Entry(max_frame, textvariable=max_var, width=5, bg="white") max_entry.pack() max_entry.bind('', lambda e, ch=i: self.update_peak_limit(ch)) self.peak_widgets.append({ "min_var": min_var, "max_var": max_var, "min_entry": min_entry, "max_entry": max_entry, "value_label": value_label }) # 3. 状态显示界面 status_frame = ttk.LabelFrame(self.root, text="状态显示", padding=10) status_frame.pack(fill="x", padx=10, pady=5) # 通讯状态 comm_frame = ttk.Frame(status_frame) comm_frame.pack(side="left", expand=True) ttk.Label(comm_frame, text="通讯状态:").pack() self.comm_status = tk.Label(comm_frame, text="●", font=("Arial", 20), fg="red") self.comm_status.pack() # SD卡状态 sd_frame = ttk.Frame(status_frame) sd_frame.pack(side="left", expand=True) ttk.Label(sd_frame, text="SD卡状态:").pack() self.sd_status_label = tk.Label(sd_frame, text="●", font=("Arial", 20), fg="red") self.sd_status_label.pack() # 按键状态 key_frame = ttk.Frame(status_frame) key_frame.pack(side="left", expand=True) ttk.Label(key_frame, text="按键状态:").pack() self.key_status_label = tk.Label(key_frame, text="●", font=("Arial", 20), fg="red") self.key_status_label.pack() def refresh_ports(self): """刷新可用串口列表""" ports = serial.tools.list_ports.comports() port_list = [port.device for port in ports] self.port_combobox['values'] = port_list if port_list and not self.port_var.get(): self.port_var.set(port_list[0]) def open_serial(self): """打开串口""" port = self.port_var.get() baudrate = self.baud_var.get() if not port: messagebox.showerror("错误", "请选择串口号") return try: self.serial_port = serial.Serial( port=port, baudrate=int(baudrate), bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.1 ) # 清空缓冲区 self.clear_serial_buffer() self.receive_buffer = bytearray() self.is_connected = True self.open_btn.config(state="disabled") self.close_btn.config(state="normal") self.refresh_btn.config(state="disabled") # 启动读取线程 self.stop_thread = False self.read_thread = threading.Thread(target=self.serial_read_loop) self.read_thread.daemon = True self.read_thread.start() self.update_debug_info("串口打开成功,开始发送查询命令...") messagebox.showinfo("成功", "串口打开成功") except Exception as e: messagebox.showerror("错误", f"打开串口失败: {str(e)}") def close_serial(self): """关闭串口""" self.stop_thread = True self.is_connected = False if self.serial_port and self.serial_port.is_open: self.serial_port.close() self.open_btn.config(state="normal") self.close_btn.config(state="disabled") self.refresh_btn.config(state="normal") self.comm_status.config(fg="red") self.update_debug_info("串口已关闭") def clear_serial_buffer(self): """清空串口缓冲区""" if self.serial_port and self.serial_port.is_open: try: self.serial_port.reset_input_buffer() self.serial_port.reset_output_buffer() self.receive_buffer.clear() self.update_debug_info("串口缓冲区已清空") except Exception as e: self.update_debug_info(f"清空缓冲区错误: {e}") def update_debug_info(self, message): """更新调试信息""" timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3] debug_msg = f"[{timestamp}] {message}" self.debug_var.set(debug_msg) print(debug_msg) def calculate_crc(self, data): """计算CRC并返回对调后的两个字节""" if not data: return bytes.fromhex('0000') # 计算CRC crc_value = self.crc16_func(data) # 转换为两个字节并对调 crc_bytes = crc_value.to_bytes(2, byteorder='big') crc_swapped = bytes([crc_bytes[1], crc_bytes[0]]) return crc_swapped def send_and_receive_command(self): """发送命令并接收响应""" if not (self.serial_port and self.serial_port.is_open): return try: # 清空接收缓冲区 self.clear_serial_buffer() # 发送查询命令 full_command = bytes.fromhex('5A04123400097991') self.serial_port.write(full_command) self.update_debug_info(f"发送命令: {full_command.hex().upper()}") # 等待100ms让设备准备响应 time.sleep(0.1) # 读取响应数据 if self.serial_port.in_waiting: data = self.serial_port.read(self.serial_port.in_waiting) if data: self.update_debug_info(f"收到 {len(data)} 字节响应数据") self.process_received_data(data) else: self.update_debug_info("未收到响应数据") self.root.after(0, lambda: self.comm_status.config(fg="red")) else: self.update_debug_info("无响应数据可读") self.root.after(0, lambda: self.comm_status.config(fg="red")) except Exception as e: self.update_debug_info(f"发送接收命令错误: {e}") self.root.after(0, lambda: self.comm_status.config(fg="red")) def serial_read_loop(self): """串口读取循环""" last_send_time = 0 send_interval = 0.2 # 200ms发送一次 while self.is_connected and not self.stop_thread: current_time = time.time() # 发送并接收命令 if current_time - last_send_time >= send_interval: self.send_and_receive_command() last_send_time = current_time time.sleep(0.01) def process_received_data(self, data): """处理接收到的数据""" hex_data = data.hex().upper() self.update_debug_info(f"原始响应数据: {hex_data}") # 检查数据长度 if len(data) < 23: self.update_debug_info(f"数据长度不足: {len(data)}字节,期望至少23字节") return # 查找帧头 frame_start = -1 for i in range(len(data) - 2): if data[i] == 0x5A and data[i+1] == 0x04 and data[i+2] == 0x12: frame_start = i break if frame_start == -1: self.update_debug_info("未找到帧头 5A0412") return self.update_debug_info(f"找到帧头,位置: {frame_start}") # 提取完整帧(从帧头开始的23字节) if len(data) - frame_start >= 23: frame_data = data[frame_start:frame_start+23] self.process_received_frame(frame_data) else: self.update_debug_info("帧数据不完整") def process_received_frame(self, data): """处理接收到的完整数据帧(23字节)""" hex_data = data.hex().upper() self.update_debug_info(f"处理数据帧: {hex_data}") # CRC校验 - 前21字节计算CRC,最后2字节是CRC if not self.check_crc(data): self.update_debug_info("CRC校验失败") self.root.after(0, lambda: self.comm_status.config(fg="red")) return # 解析数据 try: # 解析峰峰值 (4个通道,每个2字节) peak_values = [] for i in range(4): start_idx = 3 + i * 2 # 跳过帧头5A0412 (3字节) peak_value = (data[start_idx] << 8) | data[start_idx + 1] peak_values.append(peak_value) # 解析频率 (4个通道,每个2字节,需要除以10) freq_values = [] for i in range(4): start_idx = 11 + i * 2 # 峰峰值后是频率 freq_value = (data[start_idx] << 8) | data[start_idx + 1] freq_values.append(freq_value / 10.0) # 解析状态 key_status = data[19] # 按键状态 sd_status = data[20] # SD卡状态 self.update_debug_info(f"解析成功: 峰峰值{peak_values}, 频率{freq_values}, 按键{key_status}, SD卡{sd_status}") # 更新界面 self.root.after(0, self.update_display, peak_values, freq_values, key_status, sd_status) # 通讯成功,更新状态 self.root.after(0, lambda: self.comm_status.config(fg="green")) except Exception as e: self.update_debug_info(f"解析数据错误: {e}") self.root.after(0, lambda: self.comm_status.config(fg="red")) def check_crc(self, data): """CRC校验 - 前21字节计算CRC,最后2字节是CRC(需要对调)""" try: if len(data) < 23: return False # 分离数据(前21字节)和CRC(最后2字节) data_part = data[:21] # 前21字节是数据 crc_received = data[21:23] # 最后2字节是CRC # 计算期望的CRC值(对调后的) expected_crc = self.calculate_crc(data_part) # 比较CRC result = crc_received == expected_crc if not result: self.update_debug_info(f"CRC不匹配: 收到{crc_received.hex().upper()}, 期望{expected_crc.hex().upper()}") return result except Exception as e: self.update_debug_info(f"CRC校验错误: {e}") return False def update_display(self, peak_values, freq_values, key_status, sd_status): """更新显示""" # 更新峰峰值 for i in range(4): value = peak_values[i] self.peak_values[i] = value widget = self.peak_widgets[i] widget["value_label"].config(text=str(value)) # 检查范围并设置颜色 in_range = self.peak_limits[i]["min"] <= value <= self.peak_limits[i]["max"] color = "green" if in_range else "red" widget["value_label"].config(bg=color) # 如果越界,设置上下限输入框颜色 min_color = "red" if value < self.peak_limits[i]["min"] else "white" max_color = "red" if value > self.peak_limits[i]["max"] else "white" widget["min_entry"].config(bg=min_color) widget["max_entry"].config(bg=max_color) # 更新频率 for i in range(4): value = freq_values[i] self.frequency_values[i] = value widget = self.freq_widgets[i] widget["value_label"].config(text=f"{value:.1f}") # 检查范围并设置颜色 in_range = self.freq_limits[i]["min"] <= value <= self.freq_limits[i]["max"] color = "green" if in_range else "red" widget["value_label"].config(bg=color) # 如果越界,设置上下限输入框颜色 min_color = "red" if value < self.freq_limits[i]["min"] else "white" max_color = "red" if value > self.freq_limits[i]["max"] else "white" widget["min_entry"].config(bg=min_color) widget["max_entry"].config(bg=max_color) # 更新状态 self.key_status = key_status self.sd_status = sd_status # 按键状态:0=未按下,1=按下 key_color = "green" if key_status == 1 else "red" self.key_status_label.config(fg=key_color) # SD卡状态:5=成功,其他=失败 sd_color = "green" if sd_status == 5 else "red" self.sd_status_label.config(fg=sd_color) self.update_debug_info("界面更新完成") def update_freq_limit(self, channel): """更新频率上下限""" try: min_val = float(self.freq_widgets[channel]["min_var"].get()) max_val = float(self.freq_widgets[channel]["max_var"].get()) # 验证最小值不大于最大值 if min_val > max_val: messagebox.showerror("错误", "最小值不能大于最大值") return self.freq_limits[channel]["min"] = min_val self.freq_limits[channel]["max"] = max_val # 立即检查当前值并更新显示 self.check_freq_value(channel) except ValueError: messagebox.showerror("错误", "请输入有效的数字") def update_peak_limit(self, channel): """更新峰峰值上下限""" try: min_val = float(self.peak_widgets[channel]["min_var"].get()) max_val = float(self.peak_widgets[channel]["max_var"].get()) # 验证最小值不大于最大值 if min_val > max_val: messagebox.showerror("错误", "最小值不能大于最大值") return self.peak_limits[channel]["min"] = min_val self.peak_limits[channel]["max"] = max_val # 立即检查当前值并更新显示 self.check_peak_value(channel) except ValueError: messagebox.showerror("错误", "请输入有效的数字") def check_freq_value(self, channel): """检查指定通道的频率值是否在范围内""" value = self.frequency_values[channel] widget = self.freq_widgets[channel] # 检查范围并设置颜色 in_range = self.freq_limits[channel]["min"] <= value <= self.freq_limits[channel]["max"] color = "green" if in_range else "red" widget["value_label"].config(bg=color) # 如果越界,设置上下限输入框颜色 min_color = "red" if value < self.freq_limits[channel]["min"] else "white" max_color = "red" if value > self.freq_limits[channel]["max"] else "white" widget["min_entry"].config(bg=min_color) widget["max_entry"].config(bg=max_color) def check_peak_value(self, channel): """检查指定通道的峰峰值是否在范围内""" value = self.peak_values[channel] widget = self.peak_widgets[channel] # 检查范围并设置颜色 in_range = self.peak_limits[channel]["min"] <= value <= self.peak_limits[channel]["max"] color = "green" if in_range else "red" widget["value_label"].config(bg=color) # 如果越界,设置上下限输入框颜色 min_color = "red" if value < self.peak_limits[channel]["min"] else "white" max_color = "red" if value > self.peak_limits[channel]["max"] else "white" widget["min_entry"].config(bg=min_color) widget["max_entry"].config(bg=max_color) def check_current_values(self): """检查当前值是否在范围内""" for i in range(4): self.check_freq_value(i) self.check_peak_value(i) def on_closing(self): """程序关闭时的清理工作""" self.stop_thread = True if self.serial_port and self.serial_port.is_open: self.serial_port.close() self.root.destroy() if __name__ == "__main__": root = tk.Tk() app = ATEApplication(root) root.mainloop()