【正确连接线方法】CP2102模块(红版)USB转TTL模块连接TB6600驱动器,TXD连接PUL-,5v连接PUL+<PUL+连接DIR+>,GND连接GND。
import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import time
class TB6600_Stepper_Controller:
def __init__(self, root):
self.root = root
self.root.title("TB6600步进电机控制器 - 硬件方向控制")
self.root.geometry("500x700")
self.ser = None
self.is_connected = False
self.continuous_running = False # 连续运行状态(用于启动/停止按钮)
self.manual_controlling = False # 手动控制状态(用于方向键)
# 单次按键转动的步数
self.steps_per_press = 20
self.steps_per_rev = 200
self.current_direction = "CW"
self.create_interface()
self.update_port_list()
self.setup_keyboard_bindings()
def create_interface(self):
# 创建样式来设置字体
style = ttk.Style()
style.configure("Small.TLabel", font=("Arial", 8))
style.configure("Small.TButton", font=("Arial", 8))
# 标题
title_frame = ttk.Frame(self.root)
title_frame.pack(fill="x", padx=4, pady=10)
ttk.Label(title_frame, text="TB6600步进电机控制器",
font=("Arial", 12, "bold")).pack()
ttk.Label(title_frame, text="硬件方向控制 - 键盘控制",
foreground="red", font=("Arial", 8)).pack()
# 串口连接
com_frame = ttk.LabelFrame(self.root, text="串口连接", padding=5)
com_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(com_frame, text="端口:", style="Small.TLabel").grid(row=0, column=0, sticky="w")
self.port_var = tk.StringVar()
self.port_combo = ttk.Combobox(com_frame, textvariable=self.port_var, width=15)
self.port_combo.grid(row=0, column=1, padx=2)
ttk.Button(com_frame, text="刷新", command=self.update_port_list, width=6, style="Small.TButton").grid(row=0,
column=2,
padx=2)
self.connect_btn = ttk.Button(com_frame, text="连接", command=self.toggle_connection, width=6,
style="Small.TButton")
self.connect_btn.grid(row=0, column=3, padx=2)
self.status_label = ttk.Label(com_frame, text="状态: 未连接", foreground="red", style="Small.TLabel")
self.status_label.grid(row=1, column=0, columnspan=4, sticky="w", pady=(5, 0))
# 控制按钮
control_frame = ttk.LabelFrame(self.root, text="电机控制", padding=10)
control_frame.pack(fill="x", padx=10, pady=5)
# 方向指示器
self.direction_indicator = tk.Label(control_frame, text="⚪ 停止",
font=("Arial", 16, "bold"), foreground="gray")
self.direction_indicator.pack(pady=5)
# 控制按钮
btn_frame = ttk.Frame(control_frame)
btn_frame.pack(pady=10)
ttk.Button(btn_frame, text="连续运行",
command=self.toggle_continuous_run, width=8, style="Small.TButton").pack(side="left", padx=5)
ttk.Button(btn_frame, text="停止",
command=self.stop_all, width=8, style="Small.TButton").pack(side="left", padx=5)
# 键盘控制说明
key_frame = ttk.Frame(control_frame)
key_frame.pack(pady=5)
ttk.Label(key_frame, text="键盘控制: ← 左转 | → 右转 (点动模式)",
font=("Arial", 9), foreground="blue").pack()
# 速度控制
speed_frame = ttk.LabelFrame(self.root, text="速度控制", padding=10)
speed_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(speed_frame, text="转速:", style="Small.TLabel").grid(row=0, column=0, sticky="w")
self.speed_var = tk.DoubleVar(value=60)
self.speed_scale = ttk.Scale(speed_frame, from_=1, to=200, variable=self.speed_var,
orient="horizontal", command=self.update_speed_display, length=200)
self.speed_scale.grid(row=0, column=1, padx=(10, 0))
self.speed_display = ttk.Label(speed_frame, text="60 RPM", style="Small.TLabel")
self.speed_display.grid(row=0, column=2, padx=10)
# 步长控制
step_frame = ttk.LabelFrame(self.root, text="步长控制", padding=10)
step_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(step_frame, text="单次按键步数:", style="Small.TLabel").grid(row=0, column=0, sticky="w")
self.step_var = tk.IntVar(value=self.steps_per_press)
self.step_scale = ttk.Scale(step_frame, from_=1, to=100, variable=self.step_var,
orient="horizontal", command=self.update_step_display, length=200)
self.step_scale.grid(row=0, column=1, padx=(10, 0))
self.step_display = ttk.Label(step_frame, text=f"{self.steps_per_press} 步", style="Small.TLabel")
self.step_display.grid(row=0, column=2, padx=10)
# 位置显示
pos_frame = ttk.LabelFrame(self.root, text="位置信息", padding=10)
pos_frame.pack(fill="x", padx=10, pady=5)
ttk.Label(pos_frame, text="当前位置:", style="Small.TLabel").grid(row=0, column=0, sticky="w")
self.position_var = tk.StringVar(value="0 步")
ttk.Label(pos_frame, textvariable=self.position_var, style="Small.TLabel").grid(row=0, column=1, sticky="w",
padx=(5, 0))
ttk.Button(pos_frame, text="复位", command=self.reset_position, width=6, style="Small.TButton").grid(row=0,
column=2,
padx=10)
# 日志
log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=5)
log_frame.pack(fill="both", expand=True, padx=10, pady=5)
self.log_text = tk.Text(log_frame, height=6, width=40, font=("Arial", 8))
scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
self.log_text.configure(yscrollcommand=scrollbar.set)
self.log_text.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
self.current_position = 0
def setup_keyboard_bindings(self):
"""设置键盘绑定"""
self.root.bind('<KeyPress-Left>', self.left_key_pressed)
self.root.bind('<KeyPress-Right>', self.right_key_pressed)
self.root.focus_set()
def left_key_pressed(self, event):
"""左方向键按下 - 点动模式"""
# 如果正在连续运行,则不响应点动操作
if not self.is_connected or self.continuous_running or self.manual_controlling:
return
self.steps_per_press = self.step_var.get()
self.manual_controlling = True
self.current_direction = "CCW"
self.direction_indicator.config(text="🢀 左转运行", foreground="blue")
self.log_message(f"左转 {self.steps_per_press} 步")
threading.Thread(target=self.send_left_commands, daemon=True).start()
def right_key_pressed(self, event):
"""右方向键按下 - 点动模式"""
if not self.is_connected or self.continuous_running or self.manual_controlling:
return
self.steps_per_press = self.step_var.get()
self.manual_controlling = True
self.current_direction = "CW"
self.direction_indicator.config(text="🢂 右转运行", foreground="green")
self.log_message(f"右转 {self.steps_per_press} 步")
threading.Thread(target=self.send_right_commands, daemon=True).start()
def send_left_commands(self):
"""发送左转命令"""
try:
for _ in range(self.steps_per_press):
if not self.manual_controlling or self.continuous_running:
break
# 修复:只发送一个字节,与右转保持一致
self.ser.write(bytes([0x32])) # 左转命令
self.ser.flush()
self.current_position -= 1
self.root.after(0, self._update_position_display)
# 计算脉冲间隔
speed_rpm = self.speed_var.get()
pulse_interval = 60.0 / (speed_rpm * self.steps_per_rev)
time.sleep(max(0.001, pulse_interval))
except Exception as e:
self.log_message(f"左转命令错误: {e}")
finally:
self.manual_controlling = False
if not self.continuous_running: # 如果没有在连续运行,才显示停止状态
self.root.after(0, lambda: self.direction_indicator.config(text="⚪ 停止", foreground="gray"))
self.log_message("左转完成")
def send_right_commands(self):
"""发送右转命令"""
try:
for _ in range(self.steps_per_press):
if not self.manual_controlling or self.continuous_running:
break
# 修复:只发送一个字节,与左转保持一致
self.ser.write(bytes([0x02])) # 右转命令
self.ser.flush()
self.current_position += 1
self.root.after(0, self._update_position_display)
# 计算脉冲间隔
speed_rpm = self.speed_var.get()
pulse_interval = 60.0 / (speed_rpm * self.steps_per_rev)
time.sleep(max(0.001, pulse_interval))
except Exception as e:
self.log_message(f"右转命令错误: {e}")
finally:
self.manual_controlling = False
if not self.continuous_running:
self.root.after(0, lambda: self.direction_indicator.config(text="⚪ 停止", foreground="gray"))
self.log_message("右转完成")
def toggle_continuous_run(self):
"""切换连续运行状态"""
if not self.is_connected:
self.log_message("错误: 请先连接串口")
return
if not self.continuous_running:
# 开始连续运行
self.continuous_running = True
self.log_message(f"开始连续运行 - {self.current_direction}方向")
self.direction_indicator.config(
text="🢂 右转运行" if self.current_direction == "CW" else "🢀 左转运行",
foreground="green" if self.current_direction == "CW" else "blue"
)
threading.Thread(target=self.continuous_run, daemon=True).start()
else:
# 停止连续运行
self.continuous_running = False
self.log_message("连续运行已暂停")
def continuous_run(self):
"""连续运行电机"""
while self.continuous_running and self.is_connected:
try:
if self.current_direction == "CW":
# 右转命令 - 修复:与点动模式保持一致
self.ser.write(bytes([0x31]))
self.current_position += 1
else:
# 左转命令
self.ser.write(bytes([0x32]))
self.current_position -= 1
self.ser.flush()
self.root.after(0, self._update_position_display)
# 计算脉冲间隔
speed_rpm = self.speed_var.get()
pulse_interval = 60.0 / (speed_rpm * self.steps_per_rev)
time.sleep(max(0.001, pulse_interval))
except Exception as e:
self.log_message(f"连续运行错误: {e}")
self.continuous_running = False
break
# 连续运行停止后更新状态
if not self.manual_controlling:
self.root.after(0, lambda: self.direction_indicator.config(text="⚪ 停止", foreground="gray"))
def stop_all(self):
"""停止所有运动"""
self.continuous_running = False
self.manual_controlling = False
self.direction_indicator.config(text="⚪ 停止", foreground="gray")
self.log_message("所有运动已停止")
def update_port_list(self):
ports = serial.tools.list_ports.comports()
port_list = [port.device for port in ports]
self.port_combo['values'] = port_list
if port_list:
self.port_var.set(port_list[0])
def toggle_connection(self):
if not self.is_connected:
self.connect_serial()
else:
self.disconnect_serial()
def connect_serial(self):
port = self.port_var.get()
if not port:
messagebox.showerror("错误", "请选择串口端口")
return
try:
self.ser = serial.Serial(
port=port,
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=1
)
self.is_connected = True
self.connect_btn.config(text="断开")
self.status_label.config(text=f"状态: 已连接 {port}", foreground="green")
self.log_message(f"✓ 连接到 {port}")
except Exception as e:
self.log_message(f"✗ 连接失败: {str(e)}")
messagebox.showerror("连接错误", f"无法连接串口: {str(e)}")
def disconnect_serial(self):
self.stop_all()
if self.ser and self.ser.is_open:
self.ser.close()
self.is_connected = False
self.connect_btn.config(text="连接")
self.status_label.config(text="状态: 未连接", foreground="red")
self.log_message("已断开连接")
def update_speed_display(self, event=None):
speed = self.speed_var.get()
self.speed_display.config(text=f"{speed:.0f} RPM")
def update_step_display(self, event=None):
steps = self.step_var.get()
self.step_display.config(text=f"{steps} 步")
def reset_position(self):
self.current_position = 0
self._update_position_display()
self.log_message("位置已复位")
def _update_position_display(self):
self.position_var.set(f"{self.current_position} 步")
def log_message(self, message):
timestamp = time.strftime("%H:%M:%S")
log_entry = f"[{timestamp}] {message}\n"
self.log_text.insert(tk.END, log_entry)
self.log_text.see(tk.END)
def on_closing(self):
self.disconnect_serial()
self.root.destroy()
def main():
root = tk.Tk()
app = TB6600_Stepper_Controller(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()
if __name__ == "__main__":
main()
3508

被折叠的 条评论
为什么被折叠?



