python语言TB6600步进电机控制器 -CP2102模块(红版)USB转TTL模块连接TB6600驱动器

【正确连接线方法】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()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Esoft Studio

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值