import tkinter as tk from tkinter import ttk, messagebox import struct import serial import serial.tools.list_ports
class MotorControlGUI: def __init__(self, root): self.root = root self.root.title("闭环步进电机控制台 ") self.root.geometry("550x650") self.ser = None self.setup_ui()
def setup_ui(self): # --- 1. 串口配置区 --- frame_serial = tk.LabelFrame(self.root, text="1. 硬件通信设置", padx=10, pady=10) frame_serial.pack(fill="x", padx=10, pady=5)
tk.Label(frame_serial, text="端口:").pack(side="left") self.port_combo = ttk.Combobox(frame_serial, values=[p.device for p in serial.tools.list_ports.comports()], width=10) self.port_combo.pack(side="left", padx=5)
tk.Label(frame_serial, text="波特率:").pack(side="left") self.baud_combo = ttk.Combobox(frame_serial, values=["115200", "9600", "38400", "57600"], width=8) self.baud_combo.current(0) # 默认选中 115200 self.baud_combo.pack(side="left", padx=5)
self.btn_connect = tk.Button(frame_serial, text="连接串口", command=self.toggle_serial, bg="#2196F3", fg="white") self.btn_connect.pack(side="left", padx=10)
# --- 2. 参数设置区 --- frame_params = tk.LabelFrame(self.root, text="2. 控制参数设置", padx=10, pady=10) frame_params.pack(fill="x", padx=10, pady=5)
self.inputs = {} fields = [ ("电机 ID (Hex)", "01"), ("转速 (RPM)", "1500"), ("加速度 (0-255)", "0"), ("脉冲数 (32000=10圈)", "32000") ]
for text, default in fields: f = tk.Frame(frame_params) f.pack(fill="x", pady=3) tk.Label(f, text=text, width=18, anchor="w").pack(side="left") ent = tk.Entry(f) ent.insert(0, default) ent.pack(side="right", expand=True, fill="x", padx=5) self.inputs[text] = ent
# 方向单选 f_dir = tk.Frame(frame_params) f_dir.pack(fill="x", pady=5) tk.Label(f_dir, text="旋转方向:", width=18, anchor="w").pack(side="left") self.dir_var = tk.StringVar(value="01") tk.Radiobutton(f_dir, text="逆时针 CCW (01)", variable=self.dir_var, value="01").pack(side="left") tk.Radiobutton(f_dir, text="顺时针 CW (00)", variable=self.dir_var, value="00").pack(side="left")
# 模式单选 f_mode = tk.Frame(frame_params) f_mode.pack(fill="x", pady=5) tk.Label(f_mode, text="位置模式:", width=18, anchor="w").pack(side="left") self.mode_var = tk.StringVar(value="00") tk.Radiobutton(f_mode, text="相对位置 (00)", variable=self.mode_var, value="00").pack(side="left") tk.Radiobutton(f_mode, text="绝对位置 (01)", variable=self.mode_var, value="01").pack(side="left")
# --- 3. 操作按钮区 --- frame_action = tk.Frame(self.root) frame_action.pack(fill="x", padx=10, pady=5)
tk.Button(frame_action, text="生成并发送自定义指令", bg="#4CAF50", fg="white", font=("Arial", 11, "bold"), height=2, command=self.send_custom_cmd).pack(fill="x", pady=2)
tk.Button(frame_action, text="一键发送说明书原版示例指令 (仅作测试)", bg="#FF9800", fg="white", command=self.send_exact_example).pack(fill="x", pady=2)
# --- 4. 运行日志区 --- tk.Label(self.root, text="运行日志 (接收与发送):").pack(anchor="w", padx=10) self.txt_log = tk.Text(self.root, height=10, bg="#eeeeee") self.txt_log.pack(fill="both", expand=True, padx=10, pady=5)
def toggle_serial(self): if not self.ser: try: self.ser = serial.Serial(self.port_combo.get(), int(self.baud_combo.get()), timeout=0.1) self.btn_connect.config(text="断开连接", bg="#f44336") self.txt_log.insert(tk.END, f"成功连接 {self.port_combo.get()} ({self.baud_combo.get()})\n") except Exception as e: messagebox.showerror("连接失败", f"无法打开串口,可能被占用或不存在:\n{e}") else: self.ser.close() self.ser = None self.btn_connect.config(text="连接串口", bg="#2196F3") self.txt_log.insert(tk.END, "串口已断开。\n") self.txt_log.see(tk.END)
def send_exact_example(self): # 无视任何输入框,直接发送说明书里一模一样的 13 个字节 cmd = bytes.fromhex("01 FD 01 05 DC 00 00 00 7D 00 00 00 6B") self.execute_send("说明书测试", cmd)
def send_custom_cmd(self): try: # 提取所有参数 addr = int(self.inputs["电机 ID (Hex)"].get(), 16) speed = int(self.inputs["转速 (RPM)"].get()) accel = int(self.inputs["加速度 (0-255)"].get()) pulses = int(self.inputs["脉冲数 (32000=10圈)"].get()) direction = int(self.dir_var.get(), 16) mode = int(self.mode_var.get(), 16) sync = 0x00 # 默认不启用多机同步
# 严格打包前 12 字节的数据 # 格式: B(ID) + B(FD) + B(方向) + H(速度) + B(加速度) + I(脉冲) + B(模式) + B(同步) packet = struct.pack('>BB B H B I B B', addr, 0xFD, direction, speed, accel, pulses, mode, sync)
# 【核心修改】:不计算校验,直接强制在末尾拼接 0x6B cmd = packet + bytes([0x6B])
self.execute_send("自定义指令", cmd)
except Exception as e: messagebox.showerror("参数格式错误", f"请确保输入的是纯数字!\n详细错误: {str(e)}")
def execute_send(self, label, cmd): hex_str = cmd.hex(' ').upper() self.txt_log.insert(tk.END, f"[{label}] 发送: {hex_str}\n") self.txt_log.see(tk.END)
if self.ser and self.ser.is_open: try: self.ser.write(cmd) # 等待并读取电机返回的数据 (预期为 4 个字节,例如 01 FD 02 6B) resp = self.ser.read(10) if resp: self.txt_log.insert(tk.END, f" ---> 收到返回: {resp.hex(' ').upper()}\n") else: self.txt_log.insert(tk.END, f" ---> (无响应,请检查A/B线、波特率或使能状态)\n") except Exception as e: self.txt_log.insert(tk.END, f" ---> (串口写入失败: {e})\n") else: self.txt_log.insert(tk.END, " ---> (串口未连接,指令仅作预览计算)\n")
self.txt_log.insert(tk.END, "-" * 40 + "\n") self.txt_log.see(tk.END)
if __name__ == "__main__": root = tk.Tk() app = MotorControlGUI(root) root.mainloop() |